// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.chrome.browser.notifications; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.graphics.Bitmap; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.text.style.StyleSpan; import org.chromium.base.CommandLine; import org.chromium.base.ContextUtils; import org.chromium.base.Log; import org.chromium.base.VisibleForTesting; import org.chromium.base.annotations.CalledByNative; import org.chromium.base.library_loader.ProcessInitException; import org.chromium.base.metrics.RecordHistogram; import org.chromium.base.metrics.RecordUserAction; import org.chromium.chrome.R; import org.chromium.chrome.browser.ChromeSwitches; import org.chromium.chrome.browser.init.ChromeBrowserInitializer; import org.chromium.chrome.browser.preferences.PrefServiceBridge; import org.chromium.chrome.browser.preferences.Preferences; import org.chromium.chrome.browser.preferences.PreferencesLauncher; import org.chromium.chrome.browser.preferences.website.SingleCategoryPreferences; import org.chromium.chrome.browser.preferences.website.SingleWebsitePreferences; import org.chromium.chrome.browser.preferences.website.SiteSettingsCategory; import org.chromium.chrome.browser.webapps.ChromeWebApkHost; import org.chromium.components.url_formatter.UrlFormatter; import org.chromium.webapk.lib.client.WebApkValidator; import java.net.URI; import java.net.URISyntaxException; import javax.annotation.Nullable; /** * Provides the ability for the NotificationPlatformBridgeAndroid to talk to the Android platform * notification system. * * This class should only be used on the UI thread. */ public class NotificationPlatformBridge { private static final String TAG = NotificationPlatformBridge.class.getSimpleName(); // We always use the same integer id when showing and closing notifications. The notification // tag is always set, which is a safe and sufficient way of identifying a notification, so the // integer id is not needed anymore except it must not vary in an uncontrolled way. @VisibleForTesting static final int PLATFORM_ID = -1; // Prefix for platform tags generated by this class. This allows us to verify when reading a tag // that it was set by us. private static final String PLATFORM_TAG_PREFIX = NotificationPlatformBridge.class.getSimpleName(); // We always use the same request code for pending intents. We use other ways to force // uniqueness of pending intents when necessary. private static final int PENDING_INTENT_REQUEST_CODE = 0; private static final int[] EMPTY_VIBRATION_PATTERN = new int[0]; private static NotificationPlatformBridge sInstance; private static NotificationManagerProxy sNotificationManagerOverride; private final long mNativeNotificationPlatformBridge; private final Context mAppContext; private final NotificationManagerProxy mNotificationManager; private long mLastNotificationClickMs = 0L; /** * Creates a new instance of the NotificationPlatformBridge. * * @param nativeNotificationPlatformBridge Instance of the NotificationPlatformBridgeAndroid * class. */ @CalledByNative private static NotificationPlatformBridge create(long nativeNotificationPlatformBridge) { if (sInstance != null) { throw new IllegalStateException( "There must only be a single NotificationPlatformBridge."); } sInstance = new NotificationPlatformBridge(nativeNotificationPlatformBridge); return sInstance; } /** * Returns the current instance of the NotificationPlatformBridge. * * @return The instance of the NotificationPlatformBridge, if any. */ @Nullable @VisibleForTesting static NotificationPlatformBridge getInstanceForTests() { return sInstance; } /** * Overrides the notification manager which is to be used for displaying Notifications on the * Android framework. Should only be used for testing. Tests are expected to clean up after * themselves by setting this to NULL again. * * @param proxy The notification manager instance to use instead of the system's. */ @VisibleForTesting public static void overrideNotificationManagerForTesting( NotificationManagerProxy notificationManager) { sNotificationManagerOverride = notificationManager; } private NotificationPlatformBridge(long nativeNotificationPlatformBridge) { mNativeNotificationPlatformBridge = nativeNotificationPlatformBridge; mAppContext = ContextUtils.getApplicationContext(); if (sNotificationManagerOverride != null) { mNotificationManager = sNotificationManagerOverride; } else { mNotificationManager = new NotificationManagerProxyImpl( (NotificationManager) mAppContext.getSystemService( Context.NOTIFICATION_SERVICE)); } } /** * Marks the current instance as being freed, allowing for a new NotificationPlatformBridge * object to be initialized. */ @CalledByNative private void destroy() { assert sInstance == this; sInstance = null; } /** * Returns the package for the WebAPK which should handle the URL. * * @param url The url to check. * @return Package name of the WebAPK which should handle the URL. Returns empty string if the * URL should not be handled by a WebAPK. */ @CalledByNative private String queryWebApkPackage(String url) { if (!ChromeWebApkHost.isEnabled()) return ""; String webApkPackage = WebApkValidator.queryWebApkPackage(mAppContext, url); return webApkPackage == null ? "" : webApkPackage; } /** * Invoked by the NotificationService when a Notification intent has been received. There may * not be an active instance of the NotificationPlatformBridge at this time, so inform the * native side through a static method, initializing both ends if needed. * * @param intent The intent as received by the Notification service. * @return Whether the event could be handled by the native Notification bridge. */ public static boolean dispatchNotificationEvent(Intent intent) { if (sInstance == null) { nativeInitializeNotificationPlatformBridge(); if (sInstance == null) { Log.e(TAG, "Unable to initialize the native NotificationPlatformBridge."); return false; } } String notificationId = intent.getStringExtra(NotificationConstants.EXTRA_NOTIFICATION_ID); String origin = intent.getStringExtra(NotificationConstants.EXTRA_NOTIFICATION_INFO_ORIGIN); String profileId = intent.getStringExtra(NotificationConstants.EXTRA_NOTIFICATION_INFO_PROFILE_ID); boolean incognito = intent.getBooleanExtra( NotificationConstants.EXTRA_NOTIFICATION_INFO_PROFILE_INCOGNITO, false); String tag = intent.getStringExtra(NotificationConstants.EXTRA_NOTIFICATION_INFO_TAG); Log.i(TAG, "Dispatching notification event to native: " + notificationId); if (NotificationConstants.ACTION_CLICK_NOTIFICATION.equals(intent.getAction())) { String webApkPackage = ""; if (ChromeWebApkHost.isEnabled()) { webApkPackage = intent.getStringExtra( NotificationConstants.EXTRA_NOTIFICATION_INFO_WEBAPK_PACKAGE); if (webApkPackage == null) { webApkPackage = ""; } } int actionIndex = intent.getIntExtra( NotificationConstants.EXTRA_NOTIFICATION_INFO_ACTION_INDEX, -1); sInstance.onNotificationClicked( notificationId, origin, profileId, incognito, tag, webApkPackage, actionIndex); return true; } else if (NotificationConstants.ACTION_CLOSE_NOTIFICATION.equals(intent.getAction())) { // Notification deleteIntent is executed only "when the notification is explicitly // dismissed by the user, either with the 'Clear All' button or by swiping it away // individually" (though a third-party NotificationListenerService may also trigger it). sInstance.onNotificationClosed( notificationId, origin, profileId, incognito, tag, true /* byUser */); return true; } Log.e(TAG, "Unrecognized Notification action: " + intent.getAction()); return false; } /** * Launches the notifications preferences screen. If the received intent indicates it came * from the gear button on a flipped notification, this launches the site specific preferences * screen. * * @param context The context that received the intent. * @param incomingIntent The received intent. */ public static void launchNotificationPreferences(Context context, Intent incomingIntent) { // This method handles an intent fired by the Android system. There is no guarantee that the // native library is loaded at this point. The native library is needed for the preferences // activity, and it loads the library, but there are some native calls even before that // activity is started: from RecordUserAction.record and (indirectly) from // UrlFormatter.formatUrlForSecurityDisplay. try { ChromeBrowserInitializer.getInstance(context).handleSynchronousStartup(); } catch (ProcessInitException e) { Log.e(TAG, "Failed to start browser process.", e); // The library failed to initialize and nothing in the application can work, so kill // the whole application. System.exit(-1); return; } // Use the application context because it lives longer. When using the given context, it // may be stopped before the preferences intent is handled. Context applicationContext = context.getApplicationContext(); // If we can read an origin from the intent, use it to open the settings screen for that // origin. String origin = getOriginFromTag( incomingIntent.getStringExtra(NotificationConstants.EXTRA_NOTIFICATION_TAG)); boolean launchSingleWebsitePreferences = origin != null; String fragmentName = launchSingleWebsitePreferences ? SingleWebsitePreferences.class.getName() : SingleCategoryPreferences.class.getName(); Intent preferencesIntent = PreferencesLauncher.createIntentForSettingsPage(applicationContext, fragmentName); Bundle fragmentArguments; if (launchSingleWebsitePreferences) { // Record that the user has clicked on the [Site Settings] button. RecordUserAction.record("Notifications.ShowSiteSettings"); // All preferences for a specific origin. fragmentArguments = SingleWebsitePreferences.createFragmentArgsForSite(origin); } else { // Notification preferences for all origins. fragmentArguments = new Bundle(); fragmentArguments.putString(SingleCategoryPreferences.EXTRA_CATEGORY, SiteSettingsCategory.CATEGORY_NOTIFICATIONS); fragmentArguments.putString(SingleCategoryPreferences.EXTRA_TITLE, applicationContext.getResources().getString( R.string.push_notifications_permission_title)); } preferencesIntent.putExtra(Preferences.EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArguments); // We need to ensure that no existing preference tasks are being re-used in order for the // new activity to appear on top. preferencesIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); applicationContext.startActivity(preferencesIntent); } /** * Returns a bogus Uri used to make each intent unique according to Intent#filterEquals. * Without this, the pending intents derived from the intent may be reused, because extras are * not taken into account for the filterEquals comparison. * * @param notificationId The id of the notification. * @param origin The origin to whom the notification belongs. * @param actionIndex The zero-based index of the action button, or -1 if not applicable. */ private Uri makeIntentData(String notificationId, String origin, int actionIndex) { return Uri.parse(origin).buildUpon().fragment(notificationId + "," + actionIndex).build(); } /** * Returns the PendingIntent for completing |action| on the notification identified by the data * in the other parameters. * * @param action The action this pending intent will represent. * @paramn notificationId The id of the notification. * @param origin The origin to whom the notification belongs. * @param profileId Id of the profile to which the notification belongs. * @param incognito Whether the profile was in incognito mode. * @param tag The tag of the notification. May be NULL. * @param webApkPackage The package of the WebAPK associated with the notification. Empty if * the notification is not associated with a WebAPK. * @param actionIndex The zero-based index of the action button, or -1 if not applicable. */ private PendingIntent makePendingIntent(String action, String notificationId, String origin, String profileId, boolean incognito, @Nullable String tag, String webApkPackage, int actionIndex) { Uri intentData = makeIntentData(notificationId, origin, actionIndex); Intent intent = new Intent(action, intentData); intent.setClass(mAppContext, NotificationService.Receiver.class); intent.putExtra(NotificationConstants.EXTRA_NOTIFICATION_ID, notificationId); intent.putExtra(NotificationConstants.EXTRA_NOTIFICATION_INFO_ORIGIN, origin); intent.putExtra(NotificationConstants.EXTRA_NOTIFICATION_INFO_PROFILE_ID, profileId); intent.putExtra(NotificationConstants.EXTRA_NOTIFICATION_INFO_PROFILE_INCOGNITO, incognito); intent.putExtra(NotificationConstants.EXTRA_NOTIFICATION_INFO_TAG, tag); intent.putExtra( NotificationConstants.EXTRA_NOTIFICATION_INFO_WEBAPK_PACKAGE, webApkPackage); intent.putExtra(NotificationConstants.EXTRA_NOTIFICATION_INFO_ACTION_INDEX, actionIndex); return PendingIntent.getBroadcast(mAppContext, PENDING_INTENT_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT); } /** * Generates the tag to be passed to the notification manager. * * If the generated tag is the same as that of a previous notification, a new notification shown * with this tag will replace it. * * If the input tag is not empty the output is: PREFIX + SEPARATOR + ORIGIN + SEPARATOR + TAG. * This output will be the same for notifications from the same origin that have the same input * tag. * * If the input tag is empty the output is PREFIX + SEPARATOR + ORIGIN + SEPARATOR + * NOTIFICATION_ID. * * @param notificationId The id of the notification. * @param origin The origin for which the notification is shown. * @param tag A string identifier for this notification. * @return The generated platform tag. */ @VisibleForTesting static String makePlatformTag(String notificationId, String origin, @Nullable String tag) { // The given tag may contain the separator character, so add it last to make reading the // preceding origin token reliable. If no tag was specified (it is the default empty // string), make the platform tag unique by appending the notification id. StringBuilder builder = new StringBuilder(); builder.append(PLATFORM_TAG_PREFIX) .append(NotificationConstants.NOTIFICATION_TAG_SEPARATOR) .append(origin) .append(NotificationConstants.NOTIFICATION_TAG_SEPARATOR); if (TextUtils.isEmpty(tag)) { builder.append(notificationId); } else { builder.append(tag); } return builder.toString(); } /** * Attempts to extract an origin from the tag extra in the given intent. * * See {@link #makePlatformTag} for details about the format of the tag. * * @param tag The tag from the intent extra. May be null. * @return The origin string. Returns null if there was no tag extra in the given intent, or if * the tag value did not match the expected format. */ @Nullable @VisibleForTesting static String getOriginFromTag(@Nullable String tag) { // If the user touched the settings cog on a flipped notification originating from this // class, there will be a notification tag extra in a specific format. From the tag we can // read the origin of the notification. if (tag == null || !tag.startsWith(PLATFORM_TAG_PREFIX)) return null; String[] parts = tag.split(NotificationConstants.NOTIFICATION_TAG_SEPARATOR); assert parts.length >= 3; try { URI uri = new URI(parts[1]); if (uri.getHost() != null) return parts[1]; } catch (URISyntaxException e) { Log.e(TAG, "Expected to find a valid url in the notification tag extra.", e); return null; } return null; } /** * Generates the notification defaults from vibrationPattern's size and silent. * * Use the system's default ringtone, vibration and indicator lights unless the notification * has been marked as being silent. * If a vibration pattern is set, the notification should use the provided pattern * rather than defaulting to the system settings. * * @param vibrationPatternLength Vibration pattern's size for the Notification. * @param silent Whether the default sound, vibration and lights should be suppressed. * @param vibrateEnabled Whether vibration is enabled in preferences. * @return The generated notification's default value. */ @VisibleForTesting static int makeDefaults(int vibrationPatternLength, boolean silent, boolean vibrateEnabled) { assert !silent || vibrationPatternLength == 0; if (silent) return 0; int defaults = Notification.DEFAULT_ALL; if (vibrationPatternLength > 0 || !vibrateEnabled) { defaults &= ~Notification.DEFAULT_VIBRATE; } return defaults; } /** * Generates the vibration pattern used in Android notification. * * Android takes a long array where the first entry indicates the number of milliseconds to wait * prior to starting the vibration, whereas Chrome follows the syntax of the Web Vibration API. * * @param vibrationPattern Vibration pattern following the Web Vibration API syntax. * @return Vibration pattern following the Android syntax. */ @VisibleForTesting static long[] makeVibrationPattern(int[] vibrationPattern) { long[] pattern = new long[vibrationPattern.length + 1]; for (int i = 0; i < vibrationPattern.length; ++i) { pattern[i + 1] = vibrationPattern[i]; } return pattern; } /** * Displays a notification with the given details. * * TODO(crbug.com/650302): Combine the 'action*' parameters into a single array of objects. * @param notificationId The id of the notification. * @param origin Full text of the origin, including the protocol, owning this notification. * @param profileId Id of the profile that showed the notification. * @param incognito if the session of the profile is an off the record one. * @param tag A string identifier for this notification. If the tag is not empty, the new * notification will replace the previous notification with the same tag and origin, * if present. If no matching previous notification is present, the new one will just * be added. * @param webApkPackage The package of the WebAPK associated with the notification. Empty if * the notification is not associated with a WebAPK. * @param title Title to be displayed in the notification. * @param body Message to be displayed in the notification. Will be trimmed to one line of * text by the Android notification system. * @param image Content image to be prominently displayed when the notification is expanded. * @param icon Icon to be displayed in the notification. Valid Bitmap icons will be scaled to * the platforms, whereas a default icon will be generated for invalid Bitmaps. * @param badge An image to represent the notification in the status bar. It is also displayed * inside the notification. * @param vibrationPattern Vibration pattern following the Web Vibration syntax. * @param timestamp The timestamp of the event for which the notification is being shown. * @param renotify Whether the sound, vibration, and lights should be replayed if the * notification is replacing another notification. * @param silent Whether the default sound, vibration and lights should be suppressed. * @param actionTitles Titles of actions to display alongside the notification. * @param actionIcons Icons of actions to display alongside the notification. * @param actionTypes Types of actions to display alongside the notification. * @param actionPlaceholders Placeholders of actions to display alongside the notification. * @see https://developer.android.com/reference/android/app/Notification.html */ @CalledByNative private void displayNotification(String notificationId, String origin, String profileId, boolean incognito, String tag, String webApkPackage, String title, String body, Bitmap image, Bitmap icon, Bitmap badge, int[] vibrationPattern, long timestamp, boolean renotify, boolean silent, String[] actionTitles, Bitmap[] actionIcons, String[] actionTypes, String[] actionPlaceholders) { if (actionTitles.length != actionIcons.length) { throw new IllegalArgumentException("The number of action titles and icons must match."); } Resources res = mAppContext.getResources(); // Record whether it's known whether notifications can be shown to the user at all. RecordHistogram.recordEnumeratedHistogram( "Notifications.AppNotificationStatus", NotificationSystemStatusUtil.determineAppNotificationStatus(mAppContext), NotificationSystemStatusUtil.APP_NOTIFICATIONS_STATUS_BOUNDARY); // Set up a pending intent for going to the settings screen for |origin|. Intent settingsIntent = PreferencesLauncher.createIntentForSettingsPage( mAppContext, SingleWebsitePreferences.class.getName()); settingsIntent.setData(makeIntentData(notificationId, origin, -1 /* actionIndex */)); settingsIntent.putExtra(Preferences.EXTRA_SHOW_FRAGMENT_ARGUMENTS, SingleWebsitePreferences.createFragmentArgsForSite(origin)); PendingIntent pendingSettingsIntent = PendingIntent.getActivity(mAppContext, PENDING_INTENT_REQUEST_CODE, settingsIntent, PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent clickIntent = makePendingIntent(NotificationConstants.ACTION_CLICK_NOTIFICATION, notificationId, origin, profileId, incognito, tag, webApkPackage, -1 /* actionIndex */); PendingIntent closeIntent = makePendingIntent(NotificationConstants.ACTION_CLOSE_NOTIFICATION, notificationId, origin, profileId, incognito, tag, webApkPackage, -1 /* actionIndex */); boolean hasImage = image != null; NotificationBuilderBase notificationBuilder = createNotificationBuilder(hasImage) .setTitle(title) .setBody(body) .setImage(image) .setLargeIcon(icon) .setSmallIcon(R.drawable.ic_chrome) .setSmallIcon(badge) .setContentIntent(clickIntent) .setDeleteIntent(closeIntent) .setTicker(createTickerText(title, body)) .setTimestamp(timestamp) .setRenotify(renotify) .setOrigin(UrlFormatter.formatUrlForSecurityDisplay( origin, false /* showScheme */)); for (int actionIndex = 0; actionIndex < actionTitles.length; actionIndex++) { PendingIntent intent = makePendingIntent( NotificationConstants.ACTION_CLICK_NOTIFICATION, notificationId, origin, profileId, incognito, tag, webApkPackage, actionIndex); // Don't show action button icons when there's an image, as then action buttons go on // the same row as the Site Settings button, so icons wouldn't leave room for text. Bitmap actionIcon = hasImage ? null : actionIcons[actionIndex]; // TODO(crbug.com/650302): Encode actionTypes with an enum, not a magic string! if (actionTypes[actionIndex].equals("text")) { notificationBuilder.addTextAction(actionIcon, actionTitles[actionIndex], intent, actionPlaceholders[actionIndex]); } else { notificationBuilder.addButtonAction(actionIcon, actionTitles[actionIndex], intent); } } // If action buttons are displayed, there isn't room for the full Site Settings button // label and icon, so abbreviate it. This has the unfortunate side-effect of unnecessarily // abbreviating it on Android Wear also (crbug.com/576656). If custom layouts are enabled, // the label and icon provided here only affect Android Wear, so don't abbreviate them. boolean abbreviateSiteSettings = actionTitles.length > 0 && !useCustomLayouts(hasImage); int settingsIconId = abbreviateSiteSettings ? 0 : R.drawable.settings_cog; CharSequence settingsTitle = abbreviateSiteSettings ? res.getString(R.string.notification_site_settings_button) : res.getString(R.string.page_info_site_settings_button); // If the settings button is displayed together with the other buttons it has to be the last // one, so add it after the other actions. notificationBuilder.addSettingsAction(settingsIconId, settingsTitle, pendingSettingsIntent); // The Android framework applies a fallback vibration pattern for the sound when the device // is in vibrate mode, there is no custom pattern, and the vibration default has been // disabled. To truly prevent vibration, provide a custom empty pattern. boolean vibrateEnabled = PrefServiceBridge.getInstance().isNotificationsVibrateEnabled(); if (!vibrateEnabled) { vibrationPattern = EMPTY_VIBRATION_PATTERN; } notificationBuilder.setDefaults( makeDefaults(vibrationPattern.length, silent, vibrateEnabled)); notificationBuilder.setVibrate(makeVibrationPattern(vibrationPattern)); String platformTag = makePlatformTag(notificationId, origin, tag); if (webApkPackage.isEmpty()) { mNotificationManager.notify(platformTag, PLATFORM_ID, notificationBuilder.build()); } else { WebApkNotificationClient.notifyNotification( webApkPackage, notificationBuilder, platformTag, PLATFORM_ID); } } private NotificationBuilderBase createNotificationBuilder(boolean hasImage) { if (useCustomLayouts(hasImage)) { return new CustomNotificationBuilder(mAppContext); } return new StandardNotificationBuilder(mAppContext); } /** * Creates the ticker text for a notification having |title| and |body|. The notification's * title will be printed in bold, followed by the text of the body. * * @param title Title of the notification. * @param body Textual contents of the notification. * @return A character sequence containing the ticker's text. */ private CharSequence createTickerText(String title, String body) { SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(); spannableStringBuilder.append(title); spannableStringBuilder.append("\n"); spannableStringBuilder.append(body); // Mark the title of the notification as being bold. spannableStringBuilder.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0, title.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE); return spannableStringBuilder; } /** * Determines whether to use standard notification layouts, using NotificationCompat.Builder, * or custom layouts using Chrome's own templates. * * The --{enable,disable}-web-notification-custom-layouts command line flags take precedence. * * Normally a standard layout is used on Android N+, and a custom layout is used on older * versions of Android. But if the notification has a content image, there isn't enough room for * the Site Settings button to go on its own line when showing an image, nor is there enough * room for action button icons, so a standard layout will be used here even on old versions. * * @param hasImage Whether the notification has a content image. * @return Whether custom layouts should be used. */ @VisibleForTesting static boolean useCustomLayouts(boolean hasImage) { CommandLine commandLine = CommandLine.getInstance(); if (commandLine.hasSwitch(ChromeSwitches.ENABLE_WEB_NOTIFICATION_CUSTOM_LAYOUTS)) { return true; } if (commandLine.hasSwitch(ChromeSwitches.DISABLE_WEB_NOTIFICATION_CUSTOM_LAYOUTS)) { return false; } if (Build.VERSION.CODENAME.equals("N") || Build.VERSION.SDK_INT > Build.VERSION_CODES.M) { return false; } if (hasImage) { return false; } return true; } /** * Returns whether a notification has been clicked in the last 5 seconds. * Used for Startup.BringToForegroundReason UMA histogram. */ public static boolean wasNotificationRecentlyClicked() { if (sInstance == null) return false; long now = System.currentTimeMillis(); return now - sInstance.mLastNotificationClickMs < 5 * 1000; } /** * Closes the notification associated with the given parameters. * * @param profileId of the profile whose notification this is for. * @param notificationId The id of the notification. * @param origin The origin to which the notification belongs. * @param tag The tag of the notification. May be NULL. * @param webApkPackage The package of the WebAPK associated with the notification. * Empty if the notification is not associated with a WebAPK. */ @CalledByNative private void closeNotification(String profileId, String notificationId, String origin, String tag, String webApkPackage) { // TODO(miguelg) make profile_id part of the tag. String platformTag = makePlatformTag(notificationId, origin, tag); if (webApkPackage.isEmpty()) { mNotificationManager.cancel(platformTag, PLATFORM_ID); } else { WebApkNotificationClient.cancelNotification(webApkPackage, platformTag, PLATFORM_ID); } } /** * Calls NotificationPlatformBridgeAndroid::OnNotificationClicked in native code to indicate * that the notification with the given parameters has been clicked on. * * @param notificationId The id of the notification. * @param origin The origin of the notification. * @param profileId Id of the profile that showed the notification. * @param incognito if the profile session was an off the record one. * @param tag The tag of the notification. May be NULL. * @param webApkPackage The package of the WebAPK associated with the notification. * Empty if the notification is not associated with a WebAPK. * @param actionIndex */ private void onNotificationClicked(String notificationId, String origin, String profileId, boolean incognito, String tag, String webApkPackage, int actionIndex) { mLastNotificationClickMs = System.currentTimeMillis(); nativeOnNotificationClicked(mNativeNotificationPlatformBridge, notificationId, origin, profileId, incognito, tag, webApkPackage, actionIndex); } /** * Calls NotificationPlatformBridgeAndroid::OnNotificationClosed in native code to indicate that * the notification with the given parameters has been closed. * * @param notificationId The id of the notification. * @param origin The origin of the notification. * @param profileId Id of the profile that showed the notification. * @param incognito if the profile session was an off the record one. * @param tag The tag of the notification. May be NULL. * @param byUser Whether the notification was closed by a user gesture. */ private void onNotificationClosed(String notificationId, String origin, String profileId, boolean incognito, String tag, boolean byUser) { nativeOnNotificationClosed(mNativeNotificationPlatformBridge, notificationId, origin, profileId, incognito, tag, byUser); } private static native void nativeInitializeNotificationPlatformBridge(); private native void nativeOnNotificationClicked(long nativeNotificationPlatformBridgeAndroid, String notificationId, String origin, String profileId, boolean incognito, String tag, String webApkPackage, int actionIndex); private native void nativeOnNotificationClosed(long nativeNotificationPlatformBridgeAndroid, String notificationId, String origin, String profileId, boolean incognito, String tag, boolean byUser); }