| // 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.Bundle; |
| import android.os.StrictMode; |
| import android.os.SystemClock; |
| import android.text.Spannable; |
| import android.text.SpannableStringBuilder; |
| import android.text.TextUtils; |
| import android.text.style.StyleSpan; |
| import android.util.Log; |
| |
| import org.chromium.base.CommandLine; |
| import org.chromium.base.FieldTrialList; |
| import org.chromium.base.VisibleForTesting; |
| import org.chromium.base.annotations.CalledByNative; |
| 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.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.util.UrlUtilities; |
| import org.chromium.chrome.browser.widget.RoundedIconGenerator; |
| |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.util.concurrent.TimeUnit; |
| |
| import javax.annotation.Nullable; |
| |
| /** |
| * Provides the ability for the NotificationUIManagerAndroid to talk to the Android platform |
| * notification manager. |
| * |
| * This class should only be used on the UI thread. |
| */ |
| public class NotificationUIManager { |
| private static final String TAG = NotificationUIManager.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 = NotificationUIManager.class.getSimpleName(); |
| |
| private static final int NOTIFICATION_ICON_BG_COLOR = 0xFF969696; |
| private static final int NOTIFICATION_TEXT_SIZE_DP = 28; |
| |
| // 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 NotificationUIManager sInstance; |
| private static NotificationManagerProxy sNotificationManagerOverride; |
| |
| private final long mNativeNotificationManager; |
| |
| private final Context mAppContext; |
| private final NotificationManagerProxy mNotificationManager; |
| |
| @VisibleForTesting public RoundedIconGenerator mIconGenerator; |
| private final int mLargeIconWidthPx; |
| private final int mLargeIconHeightPx; |
| private final float mDensity; |
| |
| private long mLastNotificationClickMs = 0L; |
| |
| /** |
| * Creates a new instance of the NotificationUIManager. |
| * |
| * @param nativeNotificationManager Instance of the NotificationUIManagerAndroid class. |
| * @param context Application context for this instance of Chrome. |
| */ |
| @CalledByNative |
| private static NotificationUIManager create(long nativeNotificationManager, Context context) { |
| if (sInstance != null) { |
| throw new IllegalStateException("There must only be a single NotificationUIManager."); |
| } |
| |
| sInstance = new NotificationUIManager(nativeNotificationManager, context); |
| return sInstance; |
| } |
| |
| /** |
| * Returns the current instance of the NotificationUIManager. |
| * |
| * @return The instance of the NotificationUIManager, if any. |
| */ |
| @Nullable |
| @VisibleForTesting |
| static NotificationUIManager 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 NotificationUIManager(long nativeNotificationManager, Context context) { |
| mNativeNotificationManager = nativeNotificationManager; |
| mAppContext = context.getApplicationContext(); |
| |
| if (sNotificationManagerOverride != null) { |
| mNotificationManager = sNotificationManagerOverride; |
| } else { |
| mNotificationManager = new NotificationManagerProxyImpl( |
| (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)); |
| } |
| |
| Resources resources = mAppContext.getResources(); |
| |
| mLargeIconWidthPx = |
| resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_width); |
| mLargeIconHeightPx = |
| resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_height); |
| mDensity = resources.getDisplayMetrics().density; |
| } |
| |
| /** |
| * Marks the current instance as being freed, allowing for a new NotificationUIManager |
| * object to be initialized. |
| */ |
| @CalledByNative |
| private void destroy() { |
| assert sInstance == this; |
| sInstance = null; |
| } |
| |
| /** |
| * Invoked by the NotificationService when a Notification intent has been received. There may |
| * not be an active instance of the NotificationUIManager at this time, so inform the native |
| * side through a static method, initializing the manager if needed. |
| * |
| * @param intent The intent as received by the Notification service. |
| * @return Whether the event could be handled by the native Notification manager. |
| */ |
| public static boolean dispatchNotificationEvent(Intent intent) { |
| if (sInstance == null) { |
| nativeInitializeNotificationUIManager(); |
| if (sInstance == null) { |
| Log.e(TAG, "Unable to initialize the native NotificationUIManager."); |
| return false; |
| } |
| } |
| |
| long persistentNotificationId = |
| intent.getLongExtra(NotificationConstants.EXTRA_PERSISTENT_NOTIFICATION_ID, -1); |
| 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: " + persistentNotificationId); |
| |
| if (NotificationConstants.ACTION_CLICK_NOTIFICATION.equals(intent.getAction())) { |
| int actionIndex = intent.getIntExtra( |
| NotificationConstants.EXTRA_NOTIFICATION_INFO_ACTION_INDEX, -1); |
| sInstance.onNotificationClicked( |
| persistentNotificationId, origin, profileId, incognito, tag, 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( |
| persistentNotificationId, 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) { |
| // Use the application context because it lives longer. When using he 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 persistentNotificationId The persistent 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(long persistentNotificationId, String origin, int actionIndex) { |
| return Uri.parse(origin).buildUpon().fragment( |
| persistentNotificationId + "," + 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. |
| * @param persistentNotificationId The persistent id of the notification. |
| * @param origin The origin to whom the notification belongs. |
| * @param tag The tag of the notification. May be NULL. |
| * @param actionIndex The zero-based index of the action button, or -1 if not applicable. |
| */ |
| private PendingIntent makePendingIntent(String action, long persistentNotificationId, |
| String origin, String profileId, boolean incognito, @Nullable String tag, |
| int actionIndex) { |
| Uri intentData = makeIntentData(persistentNotificationId, origin, actionIndex); |
| Intent intent = new Intent(action, intentData); |
| intent.setClass(mAppContext, NotificationService.Receiver.class); |
| |
| intent.putExtra(NotificationConstants.EXTRA_PERSISTENT_NOTIFICATION_ID, |
| persistentNotificationId); |
| 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_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 persistentNotificationId The persistent 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. |
| */ |
| private static String makePlatformTag(long persistentNotificationId, 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(persistentNotificationId); |
| } 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 notfiication 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 the defaulting to system settings. |
| * |
| * @param vibrationPatternLength Vibration pattern's size for the Notification. |
| * @param silent Whether the default sound, vibration and lights should be suppressed. |
| * @return The generated notification's default value. |
| */ |
| @VisibleForTesting |
| static int makeDefaults(int vibrationPatternLength, boolean silent) { |
| assert !silent || vibrationPatternLength == 0; |
| |
| if (silent) return 0; |
| |
| int defaults = Notification.DEFAULT_ALL; |
| if (vibrationPatternLength > 0) { |
| 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. |
| * |
| * @param persistentNotificationId The persistent 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 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 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 vibrationPattern Vibration pattern following the Web Vibration syntax. |
| * @param timestamp The timestamp of the event for which the notification is being shown. |
| * @param silent Whether the default sound, vibration and lights should be suppressed. |
| * @param actionTitles Titles of actions to display alongside the notification. |
| * @see https://developer.android.com/reference/android/app/Notification.html |
| */ |
| @CalledByNative |
| private void displayNotification(long persistentNotificationId, String origin, String profileId, |
| boolean incognito, String tag, String title, String body, Bitmap icon, |
| int[] vibrationPattern, long timestamp, boolean silent, String[] actionTitles) { |
| 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(persistentNotificationId, 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, persistentNotificationId, origin, |
| profileId, incognito, tag, -1 /* actionIndex */); |
| PendingIntent closeIntent = makePendingIntent( |
| NotificationConstants.ACTION_CLOSE_NOTIFICATION, persistentNotificationId, origin, |
| profileId, incognito, tag, -1 /* actionIndex */); |
| |
| NotificationBuilderBase notificationBuilder = |
| createNotificationBuilder() |
| .setTitle(title) |
| .setBody(body) |
| .setLargeIcon(ensureNormalizedIcon(icon, origin)) |
| .setSmallIcon(R.drawable.ic_chrome) |
| .setContentIntent(clickIntent) |
| .setDeleteIntent(closeIntent) |
| .setTicker(createTickerText(title, body)) |
| .setTimestamp(timestamp) |
| .setOrigin(UrlUtilities.formatUrlForSecurityDisplay( |
| origin, false /* showScheme */)); |
| |
| for (int actionIndex = 0; actionIndex < actionTitles.length; actionIndex++) { |
| notificationBuilder.addAction(0 /* actionIcon */, actionTitles[actionIndex], |
| makePendingIntent(NotificationConstants.ACTION_CLICK_NOTIFICATION, |
| persistentNotificationId, origin, profileId, |
| incognito, tag, actionIndex)); |
| } |
| |
| // 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(); |
| 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); |
| |
| notificationBuilder.setDefaults(makeDefaults(vibrationPattern.length, silent)); |
| if (vibrationPattern.length > 0) { |
| notificationBuilder.setVibrate(makeVibrationPattern(vibrationPattern)); |
| } |
| |
| String platformTag = makePlatformTag(persistentNotificationId, origin, tag); |
| // Temporarily allowing disk access. TODO: Fix. See http://crbug.com/577185 |
| StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); |
| StrictMode.allowThreadDiskWrites(); |
| try { |
| long time = SystemClock.elapsedRealtime(); |
| mNotificationManager.notify(platformTag, PLATFORM_ID, notificationBuilder.build()); |
| RecordHistogram.recordTimesHistogram("Android.StrictMode.NotificationUIBuildTime", |
| SystemClock.elapsedRealtime() - time, TimeUnit.MILLISECONDS); |
| } finally { |
| StrictMode.setThreadPolicy(oldPolicy); |
| } |
| } |
| |
| private NotificationBuilderBase createNotificationBuilder() { |
| if (useCustomLayouts()) { |
| 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; |
| } |
| |
| /** |
| * Ensures the availability of an icon for the notification. |
| * |
| * If |icon| is a valid, non-empty Bitmap, the bitmap will be scaled to be of an appropriate |
| * size for the current Android device. Otherwise, a default icon will be created based on the |
| * origin the notification is being displayed for. |
| * |
| * @param icon The developer-provided icon they intend to use for the notification. |
| * @param origin The origin the notification is being displayed for. |
| * @return An appropriately sized icon to use for the notification. |
| */ |
| @VisibleForTesting |
| public Bitmap ensureNormalizedIcon(Bitmap icon, String origin) { |
| if (icon == null || icon.getWidth() == 0) { |
| if (mIconGenerator == null) { |
| int cornerRadiusPx = Math.min(mLargeIconWidthPx, mLargeIconHeightPx) / 2; |
| mIconGenerator = |
| new RoundedIconGenerator(mLargeIconWidthPx, mLargeIconHeightPx, |
| cornerRadiusPx, |
| NOTIFICATION_ICON_BG_COLOR, |
| NOTIFICATION_TEXT_SIZE_DP * mDensity); |
| } |
| |
| return mIconGenerator.generateIconForUrl(origin, true); |
| } |
| |
| if (icon.getWidth() > mLargeIconWidthPx || icon.getHeight() > mLargeIconHeightPx) { |
| return icon.createScaledBitmap(icon, mLargeIconWidthPx, mLargeIconHeightPx, |
| false /* not filtered */); |
| } |
| |
| return icon; |
| } |
| |
| /** |
| * 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 over a running Finch trial. |
| * |
| * @return Whether custom layouts should be used. |
| */ |
| @VisibleForTesting |
| static boolean useCustomLayouts() { |
| // Query the field trial state first to ensure correct UMA reporting. |
| String groupName = FieldTrialList.findFullName("WebNotificationCustomLayouts"); |
| CommandLine commandLine = CommandLine.getInstance(); |
| if (commandLine.hasSwitch(ChromeSwitches.DISABLE_WEB_NOTIFICATION_CUSTOM_LAYOUTS)) { |
| return false; |
| } |
| if (commandLine.hasSwitch(ChromeSwitches.ENABLE_WEB_NOTIFICATION_CUSTOM_LAYOUTS)) { |
| return true; |
| } |
| return !groupName.equals("Disabled"); |
| } |
| |
| /** |
| * 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 persistentNotificationId The persistent id of the notification. |
| * @param origin The origin to which the notification belongs. |
| * @param tag The tag of the notification. May be NULL. |
| */ |
| @CalledByNative |
| private void closeNotification(long persistentNotificationId, String origin, String tag) { |
| String platformTag = makePlatformTag(persistentNotificationId, origin, tag); |
| mNotificationManager.cancel(platformTag, PLATFORM_ID); |
| } |
| |
| /** |
| * Calls NotificationUIManagerAndroid::OnNotificationClicked in native code to indicate that |
| * the notification with the given parameters has been clicked on. |
| * |
| * @param persistentNotificationId The persistent 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. |
| */ |
| private void onNotificationClicked(long persistentNotificationId, String origin, |
| String profileId, boolean incognito, String tag, int actionIndex) { |
| mLastNotificationClickMs = System.currentTimeMillis(); |
| nativeOnNotificationClicked(mNativeNotificationManager, persistentNotificationId, origin, |
| profileId, incognito, tag, actionIndex); |
| } |
| |
| /** |
| * Calls NotificationUIManagerAndroid::OnNotificationClosed in native code to indicate that |
| * the notification with the given parameters has been closed. |
| * |
| * @param persistentNotificationId The persistent 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(long persistentNotificationId, String origin, |
| String profileId, boolean incognito, String tag, boolean byUser) { |
| nativeOnNotificationClosed(mNativeNotificationManager, persistentNotificationId, origin, |
| profileId, incognito, tag, byUser); |
| } |
| |
| private static native void nativeInitializeNotificationUIManager(); |
| |
| private native void nativeOnNotificationClicked(long nativeNotificationUIManagerAndroid, |
| long persistentNotificationId, String origin, String profileId, boolean incognito, |
| String tag, int actionIndex); |
| private native void nativeOnNotificationClosed(long nativeNotificationUIManagerAndroid, |
| long persistentNotificationId, String origin, String profileId, boolean incognito, |
| String tag, boolean byUser); |
| } |