Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(201)

Side by Side Diff: chrome/android/java/src/org/chromium/chrome/browser/notifications/CustomNotificationBuilder.java

Issue 1620203004: Notification action icons prototype. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Make it work on Android and clean up. Created 4 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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.annotation.TargetApi;
7 import android.app.Notification; 8 import android.app.Notification;
8 import android.app.PendingIntent; 9 import android.app.PendingIntent;
9 import android.content.Context; 10 import android.content.Context;
10 import android.content.res.Resources; 11 import android.content.res.Resources;
11 import android.graphics.Bitmap; 12 import android.graphics.Bitmap;
12 import android.graphics.BitmapFactory; 13 import android.graphics.BitmapFactory;
13 import android.graphics.drawable.BitmapDrawable; 14 import android.graphics.drawable.BitmapDrawable;
14 import android.graphics.drawable.Drawable; 15 import android.graphics.drawable.Drawable;
16 import android.graphics.drawable.Icon;
15 import android.os.Build; 17 import android.os.Build;
16 import android.support.v4.app.NotificationCompat;
17 import android.support.v4.app.NotificationCompat.Action;
18 import android.text.format.DateFormat; 18 import android.text.format.DateFormat;
19 import android.util.DisplayMetrics; 19 import android.util.DisplayMetrics;
20 import android.util.TypedValue; 20 import android.util.TypedValue;
21 import android.view.View; 21 import android.view.View;
22 import android.widget.RemoteViews; 22 import android.widget.RemoteViews;
23 23
24 import org.chromium.base.ApiCompatibilityUtils; 24 import org.chromium.base.ApiCompatibilityUtils;
25 import org.chromium.base.VisibleForTesting; 25 import org.chromium.base.VisibleForTesting;
26 import org.chromium.chrome.R; 26 import org.chromium.chrome.R;
27 import org.chromium.ui.base.LocalizationUtils; 27 import org.chromium.ui.base.LocalizationUtils;
28 28
29 import java.util.ArrayList; 29 import java.util.ArrayList;
30 import java.util.Arrays; 30 import java.util.Arrays;
31 import java.util.Date; 31 import java.util.Date;
32 import java.util.List; 32 import java.util.List;
33 33
34 import javax.annotation.Nullable; 34 import javax.annotation.Nullable;
35 35
36 /** 36 /**
37 * Builds a notification using the given inputs. Uses RemoteViews to provide a c ustom layout. 37 * Builds a notification using the given inputs. Uses RemoteViews to provide a c ustom layout.
38 */ 38 */
39 public class CustomNotificationBuilder implements NotificationBuilder { 39 public class CustomNotificationBuilder implements NotificationBuilder {
40 // Notification.Action was only introduced in API level 19.
41 private static class Action {
42 public int iconId;
43 @Nullable
44 public Bitmap icon;
45 public CharSequence title;
46 public PendingIntent intent;
47
48 Action(int iconId, CharSequence title, PendingIntent intent) {
49 this.iconId = iconId;
50 this.title = title;
51 this.intent = intent;
52 }
53
54 Action(Bitmap icon, CharSequence title, PendingIntent intent) {
55 this.icon = icon;
56 this.title = title;
57 this.intent = intent;
58 }
59 }
60
40 /** 61 /**
41 * Maximum length of CharSequence inputs to prevent excessive memory consump tion. At current 62 * 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 63 * screen sizes we display about 500 characters at most, so this is a pretty generous limit, and
43 * it matches what NotificationCompat does. 64 * it matches what the Notification class does.
44 */ 65 */
45 @VisibleForTesting static final int MAX_CHARSEQUENCE_LENGTH = 5 * 1024; 66 @VisibleForTesting static final int MAX_CHARSEQUENCE_LENGTH = 5 * 1024;
46 67
47 /** 68 /**
48 * The maximum number of action buttons. One is for the settings button, and two more slots are 69 * The maximum number of developer provided action buttons.
49 * for developer provided buttons.
50 */ 70 */
51 private static final int MAX_ACTION_BUTTONS = 3; 71 private static final int MAX_ACTION_BUTTONS = 2;
72
73 /**
74 * The maximum width of action icons in dp units.
75 */
76 private static final int MAX_ACTION_ICON_WIDTH_DP = 32;
52 77
53 /** 78 /**
54 * The maximum number of lines of body text for the expanded state. Fewer li nes are used when 79 * 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. 80 * the text is scaled up, with a minimum of one line.
56 */ 81 */
57 private static final int MAX_BODY_LINES = 7; 82 private static final int MAX_BODY_LINES = 7;
58 83
59 /** 84 /**
60 * The fontScale considered large for the purposes of layout. 85 * The fontScale considered large for the purposes of layout.
61 */ 86 */
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after
144 configureSettingsButton(bigView); 169 configureSettingsButton(bigView);
145 170
146 if (useMaterial()) { 171 if (useMaterial()) {
147 compactView.setViewVisibility(R.id.small_icon_overlay, View.VISIBLE) ; 172 compactView.setViewVisibility(R.id.small_icon_overlay, View.VISIBLE) ;
148 bigView.setViewVisibility(R.id.small_icon_overlay, View.VISIBLE); 173 bigView.setViewVisibility(R.id.small_icon_overlay, View.VISIBLE);
149 } else { 174 } else {
150 compactView.setViewVisibility(R.id.small_icon_footer, View.VISIBLE); 175 compactView.setViewVisibility(R.id.small_icon_footer, View.VISIBLE);
151 bigView.setViewVisibility(R.id.small_icon_footer, View.VISIBLE); 176 bigView.setViewVisibility(R.id.small_icon_footer, View.VISIBLE);
152 } 177 }
153 178
154 NotificationCompat.Builder builder = new NotificationCompat.Builder(mCon text); 179 Notification.Builder builder = new Notification.Builder(mContext);
155 builder.setTicker(mTickerText); 180 builder.setTicker(mTickerText);
156 builder.setSmallIcon(mSmallIconId); 181 builder.setSmallIcon(mSmallIconId);
157 builder.setContentIntent(mContentIntent); 182 builder.setContentIntent(mContentIntent);
158 builder.setDeleteIntent(mDeleteIntent); 183 builder.setDeleteIntent(mDeleteIntent);
159 builder.setDefaults(mDefaults); 184 builder.setDefaults(mDefaults);
160 builder.setVibrate(mVibratePattern); 185 builder.setVibrate(mVibratePattern);
161 builder.setContent(compactView); 186 builder.setContent(compactView);
162 187
163 // Some things are duplicated in the builder to ensure the notification shows correctly on 188 // Some things are duplicated in the builder to ensure the notification shows correctly on
164 // Wear devices and custom lock screens. 189 // Wear devices and custom lock screens.
165 builder.setContentTitle(mTitle); 190 builder.setContentTitle(mTitle);
166 builder.setContentText(mBody); 191 builder.setContentText(mBody);
167 builder.setSubText(mOrigin); 192 builder.setSubText(mOrigin);
168 builder.setLargeIcon(mLargeIcon); 193 builder.setLargeIcon(mLargeIcon);
169 for (Action action : mActions) { 194 for (Action action : mActions) {
170 builder.addAction(action); 195 addAction(builder, action);
171 } 196 }
172 if (mSettingsAction != null) { 197 if (mSettingsAction != null) {
173 builder.addAction(mSettingsAction); 198 addAction(builder, mSettingsAction);
174 } 199 }
175 200
176 Notification notification = builder.build(); 201 Notification notification = builder.build();
177 notification.bigContentView = bigView; 202 notification.bigContentView = bigView;
178 return notification; 203 return notification;
179 } 204 }
180 205
181 @Override 206 @Override
182 public NotificationBuilder setTitle(CharSequence title) { 207 public NotificationBuilder setTitle(CharSequence title) {
183 mTitle = limitLength(title); 208 mTitle = limitLength(title);
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
229 @Override 254 @Override
230 public NotificationBuilder addAction(int iconId, CharSequence title, Pending Intent intent) { 255 public NotificationBuilder addAction(int iconId, CharSequence title, Pending Intent intent) {
231 if (mActions.size() == MAX_ACTION_BUTTONS) { 256 if (mActions.size() == MAX_ACTION_BUTTONS) {
232 throw new IllegalStateException( 257 throw new IllegalStateException(
233 "Cannot add more than " + MAX_ACTION_BUTTONS + " actions."); 258 "Cannot add more than " + MAX_ACTION_BUTTONS + " actions.");
234 } 259 }
235 mActions.add(new Action(iconId, limitLength(title), intent)); 260 mActions.add(new Action(iconId, limitLength(title), intent));
236 return this; 261 return this;
237 } 262 }
238 263
264 public NotificationBuilder addAction(Bitmap icon, CharSequence title, Pendin gIntent intent) {
265 if (mActions.size() == MAX_ACTION_BUTTONS) {
266 throw new IllegalStateException(
267 "Cannot add more than " + MAX_ACTION_BUTTONS + " actions.");
268 }
269 mActions.add(new Action(icon, limitLength(title), intent));
270 return this;
271 }
272
239 @Override 273 @Override
240 public NotificationBuilder addSettingsAction( 274 public NotificationBuilder addSettingsAction(
241 int iconId, CharSequence title, PendingIntent intent) { 275 int iconId, CharSequence title, PendingIntent intent) {
242 mSettingsAction = new Action(iconId, limitLength(title), intent); 276 mSettingsAction = new Action(iconId, limitLength(title), intent);
243 return this; 277 return this;
244 } 278 }
245 279
246 @Override 280 @Override
247 public NotificationBuilder setDefaults(int defaults) { 281 public NotificationBuilder setDefaults(int defaults) {
248 mDefaults = defaults; 282 mDefaults = defaults;
(...skipping 18 matching lines...) Expand all
267 int visibility = mActions.isEmpty() ? View.GONE : View.VISIBLE; 301 int visibility = mActions.isEmpty() ? View.GONE : View.VISIBLE;
268 bigView.setViewVisibility(R.id.button_divider, visibility); 302 bigView.setViewVisibility(R.id.button_divider, visibility);
269 bigView.setViewVisibility(R.id.buttons, visibility); 303 bigView.setViewVisibility(R.id.buttons, visibility);
270 304
271 Resources resources = mContext.getResources(); 305 Resources resources = mContext.getResources();
272 DisplayMetrics metrics = resources.getDisplayMetrics(); 306 DisplayMetrics metrics = resources.getDisplayMetrics();
273 for (Action action : mActions) { 307 for (Action action : mActions) {
274 RemoteViews view = 308 RemoteViews view =
275 new RemoteViews(mContext.getPackageName(), R.layout.web_noti fication_button); 309 new RemoteViews(mContext.getPackageName(), R.layout.web_noti fication_button);
276 310
277 if (action.getIcon() != 0) { 311 if ((action.icon != null && useActionIconBitmaps()) || action.iconId != 0) {
278 // TODO(mvanouwerkerk): If the icon can be provided by web devel opers, limit its 312 // TODO(mvanouwerkerk): If the icon can be provided by web devel opers, limit its
279 // dimensions and decide whether or not to paint it. 313 // dimensions and decide whether or not to paint it.
280 if (useMaterial()) { 314 if (useMaterial()) {
281 view.setInt(R.id.button_icon, "setColorFilter", BUTTON_ICON_ COLOR_MATERIAL); 315 view.setInt(R.id.button_icon, "setColorFilter", BUTTON_ICON_ COLOR_MATERIAL);
282 } 316 }
283 view.setImageViewResource(R.id.button_icon, action.getIcon()); 317
318 int iconWidth = 0;
319 if (action.icon != null && useActionIconBitmaps()) {
320 view.setImageViewBitmap(R.id.button_icon, action.icon);
321 iconWidth = action.icon.getWidth();
322 } else if (action.iconId != 0) {
323 view.setImageViewResource(R.id.button_icon, action.iconId);
324 BitmapFactory.Options options = new BitmapFactory.Options();
325 options.inJustDecodeBounds = true;
326 BitmapFactory.decodeResource(resources, action.iconId, optio ns);
327 iconWidth = options.outWidth;
328 }
329 iconWidth = dpToPx(
330 Math.min(pxToDp(iconWidth, metrics), MAX_ACTION_ICON_WID TH_DP), metrics);
284 331
285 // Set the padding of the button so the text does not overlap wi th the icon. Flip 332 // Set the padding of the button so the text does not overlap wi th the icon. Flip
286 // between left and right manually as RemoteViews does not expos e a method that sets 333 // between left and right manually as RemoteViews does not expos e a method that sets
287 // padding in a writing-direction independent way. 334 // padding in a writing-direction independent way.
288 BitmapFactory.Options options = new BitmapFactory.Options();
289 options.inJustDecodeBounds = true;
290 BitmapFactory.decodeResource(resources, action.getIcon(), option s);
291 int buttonPadding = 335 int buttonPadding =
292 dpToPx(BUTTON_PADDING_START_DP + BUTTON_ICON_PADDING_DP, metrics) 336 dpToPx(BUTTON_PADDING_START_DP + BUTTON_ICON_PADDING_DP, metrics)
293 + options.outWidth; 337 + iconWidth;
294 int buttonPaddingLeft = LocalizationUtils.isLayoutRtl() ? 0 : bu ttonPadding; 338 int buttonPaddingLeft = LocalizationUtils.isLayoutRtl() ? 0 : bu ttonPadding;
295 int buttonPaddingRight = LocalizationUtils.isLayoutRtl() ? butto nPadding : 0; 339 int buttonPaddingRight = LocalizationUtils.isLayoutRtl() ? butto nPadding : 0;
296 view.setViewPadding(R.id.button, buttonPaddingLeft, 0, buttonPad dingRight, 0); 340 view.setViewPadding(R.id.button, buttonPaddingLeft, 0, buttonPad dingRight, 0);
297 } 341 }
298 342
299 view.setTextViewText(R.id.button, action.getTitle()); 343 view.setTextViewText(R.id.button, action.title);
300 view.setOnClickPendingIntent(R.id.button, action.getActionIntent()); 344 view.setOnClickPendingIntent(R.id.button, action.intent);
301 bigView.addView(R.id.buttons, view); 345 bigView.addView(R.id.buttons, view);
302 } 346 }
303 } 347 }
304 348
305 private void configureSettingsButton(RemoteViews bigView) { 349 private void configureSettingsButton(RemoteViews bigView) {
306 if (mSettingsAction == null) { 350 if (mSettingsAction == null) {
307 return; 351 return;
308 } 352 }
309 bigView.setOnClickPendingIntent(R.id.origin, mSettingsAction.getActionIn tent()); 353 bigView.setOnClickPendingIntent(R.id.origin, mSettingsAction.intent);
310 if (useMaterial()) { 354 if (useMaterial()) {
311 bigView.setInt(R.id.origin_settings_icon, "setColorFilter", BUTTON_I CON_COLOR_MATERIAL); 355 bigView.setInt(R.id.origin_settings_icon, "setColorFilter", BUTTON_I CON_COLOR_MATERIAL);
312 } 356 }
313 } 357 }
314 358
315 /** 359 /**
316 * Shows the work profile badge if it is needed. 360 * Shows the work profile badge if it is needed.
317 */ 361 */
318 private void addWorkProfileBadge(RemoteViews view) { 362 private void addWorkProfileBadge(RemoteViews view) {
319 Resources resources = mContext.getResources(); 363 Resources resources = mContext.getResources();
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
369 * Scales down the maximum amount of flexible padding to use if font scaling is over 1.0. Never 413 * Scales down the maximum amount of flexible padding to use if font scaling is over 1.0. Never
370 * scales up the amount of padding, as on some devices the notification text is rendered in dp 414 * scales up the amount of padding, as on some devices the notification text is rendered in dp
371 * units (which do not scale) and additional padding could lead to cropping at the bottom of the 415 * units (which do not scale) and additional padding could lead to cropping at the bottom of the
372 * notification. Never scales the padding below zero. 416 * notification. Never scales the padding below zero.
373 * 417 *
374 * @param fontScale The current system font scaling factor. 418 * @param fontScale The current system font scaling factor.
375 * @param displayMetrics The display metrics for the current context. 419 * @param displayMetrics The display metrics for the current context.
376 * @return The amount of padding to be used, in pixels. 420 * @return The amount of padding to be used, in pixels.
377 */ 421 */
378 @VisibleForTesting 422 @VisibleForTesting
379 static int calculateScaledPadding(float fontScale, DisplayMetrics displayMet rics) { 423 static int calculateScaledPadding(float fontScale, DisplayMetrics metrics) {
380 float paddingScale = 1.0f; 424 float paddingScale = 1.0f;
381 if (fontScale > 1.0f) { 425 if (fontScale > 1.0f) {
382 fontScale = Math.min(fontScale, FONT_SCALE_LARGE); 426 fontScale = Math.min(fontScale, FONT_SCALE_LARGE);
383 paddingScale = (FONT_SCALE_LARGE - fontScale) / (FONT_SCALE_LARGE - 1.0f); 427 paddingScale = (FONT_SCALE_LARGE - fontScale) / (FONT_SCALE_LARGE - 1.0f);
384 } 428 }
385 return dpToPx(paddingScale * MAX_SCALABLE_PADDING_DP, displayMetrics); 429 return dpToPx(paddingScale * MAX_SCALABLE_PADDING_DP, metrics);
386 } 430 }
387 431
388 /** 432 /**
389 * Converts a dp value to a px value. 433 * Converts a dp value to a px value.
390 */ 434 */
391 private static int dpToPx(float value, DisplayMetrics displayMetrics) { 435 private static int dpToPx(float value, DisplayMetrics metrics) {
392 return Math.round( 436 return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, metrics));
393 TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, di splayMetrics));
394 } 437 }
395 438
396 /** 439 /**
440 * Converts a px value to a dp value.
441 */
442 private static int pxToDp(int value, DisplayMetrics metrics) {
443 return Math.round(value / (metrics.densityDpi / DisplayMetrics.DENSITY_D EFAULT));
444 }
445
446 /**
397 * Whether to use the Material look and feel or fall back to Holo. 447 * Whether to use the Material look and feel or fall back to Holo.
398 */ 448 */
399 private static boolean useMaterial() { 449 private static boolean useMaterial() {
400 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; 450 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
401 } 451 }
452
453 /**
454 * Whether to use bitmaps for action icons, if they are specified.
455 */
456 private static boolean useActionIconBitmaps() {
457 // The Notification.Action.Builder(Icon, CharSequence, PendingIntent) co nstructor is API
458 // level 23.
459 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
460 }
461
462 @SuppressWarnings("deprecation") // For addAction(int, CharSequence, Pending Intent)
463 @TargetApi(Build.VERSION_CODES.M) // For Icon.
464 private static void addAction(Notification.Builder builder, Action action) {
465 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && action.icon != nul l) {
466 Icon icon = Icon.createWithBitmap(action.icon);
467 builder.addAction(
468 new Notification.Action.Builder(icon, action.title, action.i ntent).build());
469 } else {
470 builder.addAction(action.iconId, action.title, action.intent);
471 }
472 }
402 } 473 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698