| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package org.chromium.chrome.browser.notifications; | 5 package org.chromium.chrome.browser.notifications; |
| 6 | 6 |
| 7 import android.app.Notification; | 7 import android.app.Notification; |
| 8 import android.app.PendingIntent; | |
| 9 import android.content.Context; | 8 import android.content.Context; |
| 10 import android.content.res.Resources; | 9 import android.content.res.Resources; |
| 11 import android.graphics.Bitmap; | 10 import android.graphics.Bitmap; |
| 12 import android.graphics.BitmapFactory; | 11 import android.graphics.BitmapFactory; |
| 13 import android.graphics.drawable.BitmapDrawable; | 12 import android.graphics.drawable.BitmapDrawable; |
| 14 import android.graphics.drawable.Drawable; | 13 import android.graphics.drawable.Drawable; |
| 15 import android.os.Build; | 14 import android.os.Build; |
| 16 import android.support.v4.app.NotificationCompat; | 15 import android.support.v4.app.NotificationCompat; |
| 17 import android.support.v4.app.NotificationCompat.Action; | 16 import android.support.v4.app.NotificationCompat.Action; |
| 18 import android.text.format.DateFormat; | 17 import android.text.format.DateFormat; |
| 19 import android.util.DisplayMetrics; | 18 import android.util.DisplayMetrics; |
| 20 import android.util.TypedValue; | 19 import android.util.TypedValue; |
| 21 import android.view.View; | 20 import android.view.View; |
| 22 import android.widget.RemoteViews; | 21 import android.widget.RemoteViews; |
| 23 | 22 |
| 24 import org.chromium.base.ApiCompatibilityUtils; | 23 import org.chromium.base.ApiCompatibilityUtils; |
| 25 import org.chromium.base.VisibleForTesting; | 24 import org.chromium.base.VisibleForTesting; |
| 26 import org.chromium.chrome.R; | 25 import org.chromium.chrome.R; |
| 27 import org.chromium.ui.base.LocalizationUtils; | 26 import org.chromium.ui.base.LocalizationUtils; |
| 28 | 27 |
| 29 import java.util.ArrayList; | |
| 30 import java.util.Arrays; | |
| 31 import java.util.Date; | 28 import java.util.Date; |
| 32 import java.util.List; | |
| 33 | |
| 34 import javax.annotation.Nullable; | |
| 35 | 29 |
| 36 /** | 30 /** |
| 37 * Builds a notification using the given inputs. Uses RemoteViews to provide a c
ustom layout. | 31 * Builds a notification using the given inputs. Uses RemoteViews to provide a c
ustom layout. |
| 38 */ | 32 */ |
| 39 public class CustomNotificationBuilder implements NotificationBuilder { | 33 public class CustomNotificationBuilder extends NotificationBuilderBase { |
| 40 /** | |
| 41 * Maximum length of CharSequence inputs to prevent excessive memory consump
tion. At current | |
| 42 * screen sizes we display about 500 characters at most, so this is a pretty
generous limit, and | |
| 43 * it matches what NotificationCompat does. | |
| 44 */ | |
| 45 @VisibleForTesting static final int MAX_CHARSEQUENCE_LENGTH = 5 * 1024; | |
| 46 | |
| 47 /** | |
| 48 * The maximum number of action buttons. One is for the settings button, and
two more slots are | |
| 49 * for developer provided buttons. | |
| 50 */ | |
| 51 private static final int MAX_ACTION_BUTTONS = 3; | |
| 52 | |
| 53 /** | 34 /** |
| 54 * The maximum number of lines of body text for the expanded state. Fewer li
nes are used when | 35 * The maximum number of lines of body text for the expanded state. Fewer li
nes are used when |
| 55 * the text is scaled up, with a minimum of one line. | 36 * the text is scaled up, with a minimum of one line. |
| 56 */ | 37 */ |
| 57 private static final int MAX_BODY_LINES = 7; | 38 private static final int MAX_BODY_LINES = 7; |
| 58 | 39 |
| 59 /** | 40 /** |
| 60 * The fontScale considered large for the purposes of layout. | 41 * The fontScale considered large for the purposes of layout. |
| 61 */ | 42 */ |
| 62 private static final float FONT_SCALE_LARGE = 1.3f; | 43 private static final float FONT_SCALE_LARGE = 1.3f; |
| (...skipping 20 matching lines...) Expand all Loading... |
| 83 */ | 64 */ |
| 84 private static final int WORK_PROFILE_BADGE_SIZE_DP = 16; | 65 private static final int WORK_PROFILE_BADGE_SIZE_DP = 16; |
| 85 | 66 |
| 86 /** | 67 /** |
| 87 * Material Grey 600 - to be applied to action button icons in the Material
theme. | 68 * Material Grey 600 - to be applied to action button icons in the Material
theme. |
| 88 */ | 69 */ |
| 89 private static final int BUTTON_ICON_COLOR_MATERIAL = 0xff757575; | 70 private static final int BUTTON_ICON_COLOR_MATERIAL = 0xff757575; |
| 90 | 71 |
| 91 private final Context mContext; | 72 private final Context mContext; |
| 92 | 73 |
| 93 private CharSequence mTitle; | |
| 94 private CharSequence mBody; | |
| 95 private CharSequence mOrigin; | |
| 96 private CharSequence mTickerText; | |
| 97 private Bitmap mLargeIcon; | |
| 98 private int mSmallIconId; | |
| 99 private PendingIntent mContentIntent; | |
| 100 private PendingIntent mDeleteIntent; | |
| 101 private List<Action> mActions = new ArrayList<>(MAX_ACTION_BUTTONS); | |
| 102 private Action mSettingsAction; | |
| 103 private int mDefaults = Notification.DEFAULT_ALL; | |
| 104 private long[] mVibratePattern; | |
| 105 | |
| 106 public CustomNotificationBuilder(Context context) { | 74 public CustomNotificationBuilder(Context context) { |
| 107 mContext = context; | 75 mContext = context; |
| 108 } | 76 } |
| 109 | 77 |
| 110 @Override | 78 @Override |
| 111 public Notification build() { | 79 public Notification build() { |
| 112 // TODO(mvanouwerkerk): Try inheriting from StandardNotificationBuilder
to reduce | |
| 113 // duplication. | |
| 114 | |
| 115 // A note about RemoteViews and updating notifications. When a notificat
ion is passed to the | 80 // A note about RemoteViews and updating notifications. When a notificat
ion is passed to the |
| 116 // {@code NotificationManager} with the same tag and id as a previous no
tification, an | 81 // {@code NotificationManager} with the same tag and id as a previous no
tification, an |
| 117 // in-place update will be performed. In that case, the actions of all n
ew | 82 // in-place update will be performed. In that case, the actions of all n
ew |
| 118 // {@link RemoteViews} will be applied to the views of the old notificat
ion. This is safe | 83 // {@link RemoteViews} will be applied to the views of the old notificat
ion. This is safe |
| 119 // for actions that overwrite old values such as setting the text of a {
@code TextView}, but | 84 // for actions that overwrite old values such as setting the text of a {
@code TextView}, but |
| 120 // care must be taken for additive actions. Especially in the case of | 85 // care must be taken for additive actions. Especially in the case of |
| 121 // {@link RemoteViews#addView} the result could be to append new views b
elow stale ones. In | 86 // {@link RemoteViews#addView} the result could be to append new views b
elow stale ones. In |
| 122 // that case {@link RemoteViews#removeAllViews} must be called before ad
ding new ones. | 87 // that case {@link RemoteViews#removeAllViews} must be called before ad
ding new ones. |
| 123 RemoteViews compactView = | 88 RemoteViews compactView = |
| 124 new RemoteViews(mContext.getPackageName(), R.layout.web_notifica
tion); | 89 new RemoteViews(mContext.getPackageName(), R.layout.web_notifica
tion); |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 171 } | 136 } |
| 172 if (mSettingsAction != null) { | 137 if (mSettingsAction != null) { |
| 173 builder.addAction(mSettingsAction); | 138 builder.addAction(mSettingsAction); |
| 174 } | 139 } |
| 175 | 140 |
| 176 Notification notification = builder.build(); | 141 Notification notification = builder.build(); |
| 177 notification.bigContentView = bigView; | 142 notification.bigContentView = bigView; |
| 178 return notification; | 143 return notification; |
| 179 } | 144 } |
| 180 | 145 |
| 181 @Override | |
| 182 public NotificationBuilder setTitle(CharSequence title) { | |
| 183 mTitle = limitLength(title); | |
| 184 return this; | |
| 185 } | |
| 186 | |
| 187 @Override | |
| 188 public NotificationBuilder setBody(CharSequence body) { | |
| 189 mBody = limitLength(body); | |
| 190 return this; | |
| 191 } | |
| 192 | |
| 193 @Override | |
| 194 public NotificationBuilder setOrigin(CharSequence origin) { | |
| 195 mOrigin = limitLength(origin); | |
| 196 return this; | |
| 197 } | |
| 198 | |
| 199 @Override | |
| 200 public NotificationBuilder setTicker(CharSequence tickerText) { | |
| 201 mTickerText = limitLength(tickerText); | |
| 202 return this; | |
| 203 } | |
| 204 | |
| 205 @Override | |
| 206 public NotificationBuilder setLargeIcon(Bitmap icon) { | |
| 207 mLargeIcon = icon; | |
| 208 return this; | |
| 209 } | |
| 210 | |
| 211 @Override | |
| 212 public NotificationBuilder setSmallIcon(int iconId) { | |
| 213 mSmallIconId = iconId; | |
| 214 return this; | |
| 215 } | |
| 216 | |
| 217 @Override | |
| 218 public NotificationBuilder setContentIntent(PendingIntent intent) { | |
| 219 mContentIntent = intent; | |
| 220 return this; | |
| 221 } | |
| 222 | |
| 223 @Override | |
| 224 public NotificationBuilder setDeleteIntent(PendingIntent intent) { | |
| 225 mDeleteIntent = intent; | |
| 226 return this; | |
| 227 } | |
| 228 | |
| 229 @Override | |
| 230 public NotificationBuilder addAction(int iconId, CharSequence title, Pending
Intent intent) { | |
| 231 if (mActions.size() == MAX_ACTION_BUTTONS) { | |
| 232 throw new IllegalStateException( | |
| 233 "Cannot add more than " + MAX_ACTION_BUTTONS + " actions."); | |
| 234 } | |
| 235 mActions.add(new Action(iconId, limitLength(title), intent)); | |
| 236 return this; | |
| 237 } | |
| 238 | |
| 239 @Override | |
| 240 public NotificationBuilder addSettingsAction( | |
| 241 int iconId, CharSequence title, PendingIntent intent) { | |
| 242 mSettingsAction = new Action(iconId, limitLength(title), intent); | |
| 243 return this; | |
| 244 } | |
| 245 | |
| 246 @Override | |
| 247 public NotificationBuilder setDefaults(int defaults) { | |
| 248 mDefaults = defaults; | |
| 249 return this; | |
| 250 } | |
| 251 | |
| 252 @Override | |
| 253 public NotificationBuilder setVibrate(long[] pattern) { | |
| 254 mVibratePattern = Arrays.copyOf(pattern, pattern.length); | |
| 255 return this; | |
| 256 } | |
| 257 | |
| 258 /** | 146 /** |
| 259 * If there are actions, shows the button related views, and adds a button f
or each action. | 147 * If there are actions, shows the button related views, and adds a button f
or each action. |
| 260 */ | 148 */ |
| 261 private void addActionButtons(RemoteViews bigView) { | 149 private void addActionButtons(RemoteViews bigView) { |
| 262 // Remove the existing buttons in case an existing notification is being
updated. | 150 // Remove the existing buttons in case an existing notification is being
updated. |
| 263 bigView.removeAllViews(R.id.buttons); | 151 bigView.removeAllViews(R.id.buttons); |
| 264 | 152 |
| 265 // Always set the visibility of the views associated with the action but
tons. The current | 153 // Always set the visibility of the views associated with the action but
tons. The current |
| 266 // visibility state is not known as perhaps an existing notification is
being updated. | 154 // visibility state is not known as perhaps an existing notification is
being updated. |
| 267 int visibility = mActions.isEmpty() ? View.GONE : View.VISIBLE; | 155 int visibility = mActions.isEmpty() ? View.GONE : View.VISIBLE; |
| (...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 330 | 218 |
| 331 // The input bitmap is immutable, so the output drawable will be a diffe
rent instance from | 219 // The input bitmap is immutable, so the output drawable will be a diffe
rent instance from |
| 332 // the input drawable if the work profile badge was applied. | 220 // the input drawable if the work profile badge was applied. |
| 333 if (inputDrawable != outputDrawable && outputDrawable instanceof BitmapD
rawable) { | 221 if (inputDrawable != outputDrawable && outputDrawable instanceof BitmapD
rawable) { |
| 334 view.setImageViewBitmap( | 222 view.setImageViewBitmap( |
| 335 R.id.work_profile_badge, ((BitmapDrawable) outputDrawable).g
etBitmap()); | 223 R.id.work_profile_badge, ((BitmapDrawable) outputDrawable).g
etBitmap()); |
| 336 view.setViewVisibility(R.id.work_profile_badge, View.VISIBLE); | 224 view.setViewVisibility(R.id.work_profile_badge, View.VISIBLE); |
| 337 } | 225 } |
| 338 } | 226 } |
| 339 | 227 |
| 340 @Nullable | |
| 341 private static CharSequence limitLength(@Nullable CharSequence input) { | |
| 342 if (input == null) { | |
| 343 return input; | |
| 344 } | |
| 345 if (input.length() > MAX_CHARSEQUENCE_LENGTH) { | |
| 346 return input.subSequence(0, MAX_CHARSEQUENCE_LENGTH); | |
| 347 } | |
| 348 return input; | |
| 349 } | |
| 350 | |
| 351 /** | 228 /** |
| 352 * Scales down the maximum number of displayed lines in the body text if fon
t scaling is greater | 229 * Scales down the maximum number of displayed lines in the body text if fon
t scaling is greater |
| 353 * than 1.0. Never scales up the number of lines, as on some devices the not
ification text is | 230 * than 1.0. Never scales up the number of lines, as on some devices the not
ification text is |
| 354 * rendered in dp units (which do not scale) and additional lines could lead
to cropping at the | 231 * rendered in dp units (which do not scale) and additional lines could lead
to cropping at the |
| 355 * bottom of the notification. | 232 * bottom of the notification. |
| 356 * | 233 * |
| 357 * @param fontScale The current system font scaling factor. | 234 * @param fontScale The current system font scaling factor. |
| 358 * @return The number of lines to be displayed. | 235 * @return The number of lines to be displayed. |
| 359 */ | 236 */ |
| 360 @VisibleForTesting | 237 @VisibleForTesting |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 393 TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, di
splayMetrics)); | 270 TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, di
splayMetrics)); |
| 394 } | 271 } |
| 395 | 272 |
| 396 /** | 273 /** |
| 397 * Whether to use the Material look and feel or fall back to Holo. | 274 * Whether to use the Material look and feel or fall back to Holo. |
| 398 */ | 275 */ |
| 399 private static boolean useMaterial() { | 276 private static boolean useMaterial() { |
| 400 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; | 277 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; |
| 401 } | 278 } |
| 402 } | 279 } |
| OLD | NEW |