Notification.java revision 41120562018078273704039fc05e2a32d37bfa26
1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.app;
18
19import android.annotation.ColorInt;
20import android.annotation.DrawableRes;
21import android.annotation.IntDef;
22import android.annotation.SdkConstant;
23import android.annotation.SdkConstant.SdkConstantType;
24import android.content.Context;
25import android.content.Intent;
26import android.content.pm.ApplicationInfo;
27import android.content.pm.PackageManager.NameNotFoundException;
28import android.content.res.ColorStateList;
29import android.graphics.Bitmap;
30import android.graphics.Canvas;
31import android.graphics.PorterDuff;
32import android.graphics.drawable.Drawable;
33import android.media.AudioAttributes;
34import android.media.AudioManager;
35import android.media.session.MediaSession;
36import android.net.Uri;
37import android.os.BadParcelableException;
38import android.os.Build;
39import android.os.Bundle;
40import android.os.Parcel;
41import android.os.Parcelable;
42import android.os.SystemClock;
43import android.os.UserHandle;
44import android.text.TextUtils;
45import android.util.Log;
46import android.util.MathUtils;
47import android.util.TypedValue;
48import android.view.Gravity;
49import android.view.View;
50import android.widget.ProgressBar;
51import android.widget.RemoteViews;
52
53import com.android.internal.R;
54import com.android.internal.util.NotificationColorUtil;
55
56import java.lang.annotation.Retention;
57import java.lang.annotation.RetentionPolicy;
58import java.lang.reflect.Constructor;
59import java.text.NumberFormat;
60import java.util.ArrayList;
61import java.util.Arrays;
62import java.util.Collections;
63import java.util.List;
64
65/**
66 * A class that represents how a persistent notification is to be presented to
67 * the user using the {@link android.app.NotificationManager}.
68 *
69 * <p>The {@link Notification.Builder Notification.Builder} has been added to make it
70 * easier to construct Notifications.</p>
71 *
72 * <div class="special reference">
73 * <h3>Developer Guides</h3>
74 * <p>For a guide to creating notifications, read the
75 * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Status Bar Notifications</a>
76 * developer guide.</p>
77 * </div>
78 */
79public class Notification implements Parcelable
80{
81    private static final String TAG = "Notification";
82
83    /**
84     * An activity that provides a user interface for adjusting notification preferences for its
85     * containing application. Optional but recommended for apps that post
86     * {@link android.app.Notification Notifications}.
87     */
88    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
89    public static final String INTENT_CATEGORY_NOTIFICATION_PREFERENCES
90            = "android.intent.category.NOTIFICATION_PREFERENCES";
91
92    /**
93     * Use all default values (where applicable).
94     */
95    public static final int DEFAULT_ALL = ~0;
96
97    /**
98     * Use the default notification sound. This will ignore any given
99     * {@link #sound}.
100     *
101     * <p>
102     * A notification that is noisy is more likely to be presented as a heads-up notification.
103     * </p>
104     *
105     * @see #defaults
106     */
107
108    public static final int DEFAULT_SOUND = 1;
109
110    /**
111     * Use the default notification vibrate. This will ignore any given
112     * {@link #vibrate}. Using phone vibration requires the
113     * {@link android.Manifest.permission#VIBRATE VIBRATE} permission.
114     *
115     * <p>
116     * A notification that vibrates is more likely to be presented as a heads-up notification.
117     * </p>
118     *
119     * @see #defaults
120     */
121
122    public static final int DEFAULT_VIBRATE = 2;
123
124    /**
125     * Use the default notification lights. This will ignore the
126     * {@link #FLAG_SHOW_LIGHTS} bit, and {@link #ledARGB}, {@link #ledOffMS}, or
127     * {@link #ledOnMS}.
128     *
129     * @see #defaults
130     */
131
132    public static final int DEFAULT_LIGHTS = 4;
133
134    /**
135     * Maximum length of CharSequences accepted by Builder and friends.
136     *
137     * <p>
138     * Avoids spamming the system with overly large strings such as full e-mails.
139     */
140    private static final int MAX_CHARSEQUENCE_LENGTH = 5 * 1024;
141
142    /**
143     * A timestamp related to this notification, in milliseconds since the epoch.
144     *
145     * Default value: {@link System#currentTimeMillis() Now}.
146     *
147     * Choose a timestamp that will be most relevant to the user. For most finite events, this
148     * corresponds to the time the event happened (or will happen, in the case of events that have
149     * yet to occur but about which the user is being informed). Indefinite events should be
150     * timestamped according to when the activity began.
151     *
152     * Some examples:
153     *
154     * <ul>
155     *   <li>Notification of a new chat message should be stamped when the message was received.</li>
156     *   <li>Notification of an ongoing file download (with a progress bar, for example) should be stamped when the download started.</li>
157     *   <li>Notification of a completed file download should be stamped when the download finished.</li>
158     *   <li>Notification of an upcoming meeting should be stamped with the time the meeting will begin (that is, in the future).</li>
159     *   <li>Notification of an ongoing stopwatch (increasing timer) should be stamped with the watch's start time.
160     *   <li>Notification of an ongoing countdown timer should be stamped with the timer's end time.
161     * </ul>
162     *
163     */
164    public long when;
165
166    /**
167     * The resource id of a drawable to use as the icon in the status bar.
168     * This is required; notifications with an invalid icon resource will not be shown.
169     */
170    @DrawableRes
171    public int icon;
172
173    /**
174     * If the icon in the status bar is to have more than one level, you can set this.  Otherwise,
175     * leave it at its default value of 0.
176     *
177     * @see android.widget.ImageView#setImageLevel
178     * @see android.graphics.drawable.Drawable#setLevel
179     */
180    public int iconLevel;
181
182    /**
183     * The number of events that this notification represents. For example, in a new mail
184     * notification, this could be the number of unread messages.
185     *
186     * The system may or may not use this field to modify the appearance of the notification. For
187     * example, before {@link android.os.Build.VERSION_CODES#HONEYCOMB}, this number was
188     * superimposed over the icon in the status bar. Starting with
189     * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, the template used by
190     * {@link Notification.Builder} has displayed the number in the expanded notification view.
191     *
192     * If the number is 0 or negative, it is never shown.
193     */
194    public int number;
195
196    /**
197     * The intent to execute when the expanded status entry is clicked.  If
198     * this is an activity, it must include the
199     * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
200     * that you take care of task management as described in the
201     * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
202     * Stack</a> document.  In particular, make sure to read the notification section
203     * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html#HandlingNotifications">Handling
204     * Notifications</a> for the correct ways to launch an application from a
205     * notification.
206     */
207    public PendingIntent contentIntent;
208
209    /**
210     * The intent to execute when the notification is explicitly dismissed by the user, either with
211     * the "Clear All" button or by swiping it away individually.
212     *
213     * This probably shouldn't be launching an activity since several of those will be sent
214     * at the same time.
215     */
216    public PendingIntent deleteIntent;
217
218    /**
219     * An intent to launch instead of posting the notification to the status bar.
220     *
221     * <p>
222     * The system UI may choose to display a heads-up notification, instead of
223     * launching this intent, while the user is using the device.
224     * </p>
225     *
226     * @see Notification.Builder#setFullScreenIntent
227     */
228    public PendingIntent fullScreenIntent;
229
230    /**
231     * Text that summarizes this notification for accessibility services.
232     *
233     * As of the L release, this text is no longer shown on screen, but it is still useful to
234     * accessibility services (where it serves as an audible announcement of the notification's
235     * appearance).
236     *
237     * @see #tickerView
238     */
239    public CharSequence tickerText;
240
241    /**
242     * Formerly, a view showing the {@link #tickerText}.
243     *
244     * No longer displayed in the status bar as of API 21.
245     */
246    @Deprecated
247    public RemoteViews tickerView;
248
249    /**
250     * The view that will represent this notification in the expanded status bar.
251     */
252    public RemoteViews contentView;
253
254    /**
255     * A large-format version of {@link #contentView}, giving the Notification an
256     * opportunity to show more detail. The system UI may choose to show this
257     * instead of the normal content view at its discretion.
258     */
259    public RemoteViews bigContentView;
260
261
262    /**
263     * A medium-format version of {@link #contentView}, providing the Notification an
264     * opportunity to add action buttons to contentView. At its discretion, the system UI may
265     * choose to show this as a heads-up notification, which will pop up so the user can see
266     * it without leaving their current activity.
267     */
268    public RemoteViews headsUpContentView;
269
270    /**
271     * The bitmap that may escape the bounds of the panel and bar.
272     */
273    public Bitmap largeIcon;
274
275    /**
276     * The sound to play.
277     *
278     * <p>
279     * A notification that is noisy is more likely to be presented as a heads-up notification.
280     * </p>
281     *
282     * <p>
283     * To play the default notification sound, see {@link #defaults}.
284     * </p>
285     */
286    public Uri sound;
287
288    /**
289     * Use this constant as the value for audioStreamType to request that
290     * the default stream type for notifications be used.  Currently the
291     * default stream type is {@link AudioManager#STREAM_NOTIFICATION}.
292     *
293     * @deprecated Use {@link #audioAttributes} instead.
294     */
295    @Deprecated
296    public static final int STREAM_DEFAULT = -1;
297
298    /**
299     * The audio stream type to use when playing the sound.
300     * Should be one of the STREAM_ constants from
301     * {@link android.media.AudioManager}.
302     *
303     * @deprecated Use {@link #audioAttributes} instead.
304     */
305    @Deprecated
306    public int audioStreamType = STREAM_DEFAULT;
307
308    /**
309     * The default value of {@link #audioAttributes}.
310     */
311    public static final AudioAttributes AUDIO_ATTRIBUTES_DEFAULT = new AudioAttributes.Builder()
312            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
313            .setUsage(AudioAttributes.USAGE_NOTIFICATION)
314            .build();
315
316    /**
317     * The {@link AudioAttributes audio attributes} to use when playing the sound.
318     */
319    public AudioAttributes audioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
320
321    /**
322     * The pattern with which to vibrate.
323     *
324     * <p>
325     * To vibrate the default pattern, see {@link #defaults}.
326     * </p>
327     *
328     * <p>
329     * A notification that vibrates is more likely to be presented as a heads-up notification.
330     * </p>
331     *
332     * @see android.os.Vibrator#vibrate(long[],int)
333     */
334    public long[] vibrate;
335
336    /**
337     * The color of the led.  The hardware will do its best approximation.
338     *
339     * @see #FLAG_SHOW_LIGHTS
340     * @see #flags
341     */
342    @ColorInt
343    public int ledARGB;
344
345    /**
346     * The number of milliseconds for the LED to be on while it's flashing.
347     * The hardware will do its best approximation.
348     *
349     * @see #FLAG_SHOW_LIGHTS
350     * @see #flags
351     */
352    public int ledOnMS;
353
354    /**
355     * The number of milliseconds for the LED to be off while it's flashing.
356     * The hardware will do its best approximation.
357     *
358     * @see #FLAG_SHOW_LIGHTS
359     * @see #flags
360     */
361    public int ledOffMS;
362
363    /**
364     * Specifies which values should be taken from the defaults.
365     * <p>
366     * To set, OR the desired from {@link #DEFAULT_SOUND},
367     * {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. For all default
368     * values, use {@link #DEFAULT_ALL}.
369     * </p>
370     */
371    public int defaults;
372
373    /**
374     * Bit to be bitwise-ored into the {@link #flags} field that should be
375     * set if you want the LED on for this notification.
376     * <ul>
377     * <li>To turn the LED off, pass 0 in the alpha channel for colorARGB
378     *      or 0 for both ledOnMS and ledOffMS.</li>
379     * <li>To turn the LED on, pass 1 for ledOnMS and 0 for ledOffMS.</li>
380     * <li>To flash the LED, pass the number of milliseconds that it should
381     *      be on and off to ledOnMS and ledOffMS.</li>
382     * </ul>
383     * <p>
384     * Since hardware varies, you are not guaranteed that any of the values
385     * you pass are honored exactly.  Use the system defaults (TODO) if possible
386     * because they will be set to values that work on any given hardware.
387     * <p>
388     * The alpha channel must be set for forward compatibility.
389     *
390     */
391    public static final int FLAG_SHOW_LIGHTS        = 0x00000001;
392
393    /**
394     * Bit to be bitwise-ored into the {@link #flags} field that should be
395     * set if this notification is in reference to something that is ongoing,
396     * like a phone call.  It should not be set if this notification is in
397     * reference to something that happened at a particular point in time,
398     * like a missed phone call.
399     */
400    public static final int FLAG_ONGOING_EVENT      = 0x00000002;
401
402    /**
403     * Bit to be bitwise-ored into the {@link #flags} field that if set,
404     * the audio will be repeated until the notification is
405     * cancelled or the notification window is opened.
406     */
407    public static final int FLAG_INSISTENT          = 0x00000004;
408
409    /**
410     * Bit to be bitwise-ored into the {@link #flags} field that should be
411     * set if you would only like the sound, vibrate and ticker to be played
412     * if the notification was not already showing.
413     */
414    public static final int FLAG_ONLY_ALERT_ONCE    = 0x00000008;
415
416    /**
417     * Bit to be bitwise-ored into the {@link #flags} field that should be
418     * set if the notification should be canceled when it is clicked by the
419     * user.
420     */
421    public static final int FLAG_AUTO_CANCEL        = 0x00000010;
422
423    /**
424     * Bit to be bitwise-ored into the {@link #flags} field that should be
425     * set if the notification should not be canceled when the user clicks
426     * the Clear all button.
427     */
428    public static final int FLAG_NO_CLEAR           = 0x00000020;
429
430    /**
431     * Bit to be bitwise-ored into the {@link #flags} field that should be
432     * set if this notification represents a currently running service.  This
433     * will normally be set for you by {@link Service#startForeground}.
434     */
435    public static final int FLAG_FOREGROUND_SERVICE = 0x00000040;
436
437    /**
438     * Obsolete flag indicating high-priority notifications; use the priority field instead.
439     *
440     * @deprecated Use {@link #priority} with a positive value.
441     */
442    public static final int FLAG_HIGH_PRIORITY      = 0x00000080;
443
444    /**
445     * Bit to be bitswise-ored into the {@link #flags} field that should be
446     * set if this notification is relevant to the current device only
447     * and it is not recommended that it bridge to other devices.
448     */
449    public static final int FLAG_LOCAL_ONLY         = 0x00000100;
450
451    /**
452     * Bit to be bitswise-ored into the {@link #flags} field that should be
453     * set if this notification is the group summary for a group of notifications.
454     * Grouped notifications may display in a cluster or stack on devices which
455     * support such rendering. Requires a group key also be set using {@link Builder#setGroup}.
456     */
457    public static final int FLAG_GROUP_SUMMARY      = 0x00000200;
458
459    public int flags;
460
461    /** @hide */
462    @IntDef({PRIORITY_DEFAULT,PRIORITY_LOW,PRIORITY_MIN,PRIORITY_HIGH,PRIORITY_MAX})
463    @Retention(RetentionPolicy.SOURCE)
464    public @interface Priority {}
465
466    /**
467     * Default notification {@link #priority}. If your application does not prioritize its own
468     * notifications, use this value for all notifications.
469     */
470    public static final int PRIORITY_DEFAULT = 0;
471
472    /**
473     * Lower {@link #priority}, for items that are less important. The UI may choose to show these
474     * items smaller, or at a different position in the list, compared with your app's
475     * {@link #PRIORITY_DEFAULT} items.
476     */
477    public static final int PRIORITY_LOW = -1;
478
479    /**
480     * Lowest {@link #priority}; these items might not be shown to the user except under special
481     * circumstances, such as detailed notification logs.
482     */
483    public static final int PRIORITY_MIN = -2;
484
485    /**
486     * Higher {@link #priority}, for more important notifications or alerts. The UI may choose to
487     * show these items larger, or at a different position in notification lists, compared with
488     * your app's {@link #PRIORITY_DEFAULT} items.
489     */
490    public static final int PRIORITY_HIGH = 1;
491
492    /**
493     * Highest {@link #priority}, for your application's most important items that require the
494     * user's prompt attention or input.
495     */
496    public static final int PRIORITY_MAX = 2;
497
498    /**
499     * Relative priority for this notification.
500     *
501     * Priority is an indication of how much of the user's valuable attention should be consumed by
502     * this notification. Low-priority notifications may be hidden from the user in certain
503     * situations, while the user might be interrupted for a higher-priority notification. The
504     * system will make a determination about how to interpret this priority when presenting
505     * the notification.
506     *
507     * <p>
508     * A notification that is at least {@link #PRIORITY_HIGH} is more likely to be presented
509     * as a heads-up notification.
510     * </p>
511     *
512     */
513    @Priority
514    public int priority;
515
516    /**
517     * Accent color (an ARGB integer like the constants in {@link android.graphics.Color})
518     * to be applied by the standard Style templates when presenting this notification.
519     *
520     * The current template design constructs a colorful header image by overlaying the
521     * {@link #icon} image (stenciled in white) atop a field of this color. Alpha components are
522     * ignored.
523     */
524    @ColorInt
525    public int color = COLOR_DEFAULT;
526
527    /**
528     * Special value of {@link #color} telling the system not to decorate this notification with
529     * any special color but instead use default colors when presenting this notification.
530     */
531    @ColorInt
532    public static final int COLOR_DEFAULT = 0; // AKA Color.TRANSPARENT
533
534    /**
535     * Sphere of visibility of this notification, which affects how and when the SystemUI reveals
536     * the notification's presence and contents in untrusted situations (namely, on the secure
537     * lockscreen).
538     *
539     * The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always
540     * done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are
541     * shown in all situations, but the contents are only available if the device is unlocked for
542     * the appropriate user.
543     *
544     * A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification
545     * can be read even in an "insecure" context (that is, above a secure lockscreen).
546     * To modify the public version of this notification—for example, to redact some portions—see
547     * {@link Builder#setPublicVersion(Notification)}.
548     *
549     * Finally, a notification can be made {@link #VISIBILITY_SECRET}, which will suppress its icon
550     * and ticker until the user has bypassed the lockscreen.
551     */
552    public int visibility;
553
554    /**
555     * Notification visibility: Show this notification in its entirety on all lockscreens.
556     *
557     * {@see #visibility}
558     */
559    public static final int VISIBILITY_PUBLIC = 1;
560
561    /**
562     * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or
563     * private information on secure lockscreens.
564     *
565     * {@see #visibility}
566     */
567    public static final int VISIBILITY_PRIVATE = 0;
568
569    /**
570     * Notification visibility: Do not reveal any part of this notification on a secure lockscreen.
571     *
572     * {@see #visibility}
573     */
574    public static final int VISIBILITY_SECRET = -1;
575
576    /**
577     * Notification category: incoming call (voice or video) or similar synchronous communication request.
578     */
579    public static final String CATEGORY_CALL = "call";
580
581    /**
582     * Notification category: incoming direct message (SMS, instant message, etc.).
583     */
584    public static final String CATEGORY_MESSAGE = "msg";
585
586    /**
587     * Notification category: asynchronous bulk message (email).
588     */
589    public static final String CATEGORY_EMAIL = "email";
590
591    /**
592     * Notification category: calendar event.
593     */
594    public static final String CATEGORY_EVENT = "event";
595
596    /**
597     * Notification category: promotion or advertisement.
598     */
599    public static final String CATEGORY_PROMO = "promo";
600
601    /**
602     * Notification category: alarm or timer.
603     */
604    public static final String CATEGORY_ALARM = "alarm";
605
606    /**
607     * Notification category: progress of a long-running background operation.
608     */
609    public static final String CATEGORY_PROGRESS = "progress";
610
611    /**
612     * Notification category: social network or sharing update.
613     */
614    public static final String CATEGORY_SOCIAL = "social";
615
616    /**
617     * Notification category: error in background operation or authentication status.
618     */
619    public static final String CATEGORY_ERROR = "err";
620
621    /**
622     * Notification category: media transport control for playback.
623     */
624    public static final String CATEGORY_TRANSPORT = "transport";
625
626    /**
627     * Notification category: system or device status update.  Reserved for system use.
628     */
629    public static final String CATEGORY_SYSTEM = "sys";
630
631    /**
632     * Notification category: indication of running background service.
633     */
634    public static final String CATEGORY_SERVICE = "service";
635
636    /**
637     * Notification category: a specific, timely recommendation for a single thing.
638     * For example, a news app might want to recommend a news story it believes the user will
639     * want to read next.
640     */
641    public static final String CATEGORY_RECOMMENDATION = "recommendation";
642
643    /**
644     * Notification category: ongoing information about device or contextual status.
645     */
646    public static final String CATEGORY_STATUS = "status";
647
648    /**
649     * Notification category: user-scheduled reminder.
650     */
651    public static final String CATEGORY_REMINDER = "reminder";
652
653    /**
654     * One of the predefined notification categories (see the <code>CATEGORY_*</code> constants)
655     * that best describes this Notification.  May be used by the system for ranking and filtering.
656     */
657    public String category;
658
659    private String mGroupKey;
660
661    /**
662     * Get the key used to group this notification into a cluster or stack
663     * with other notifications on devices which support such rendering.
664     */
665    public String getGroup() {
666        return mGroupKey;
667    }
668
669    private String mSortKey;
670
671    /**
672     * Get a sort key that orders this notification among other notifications from the
673     * same package. This can be useful if an external sort was already applied and an app
674     * would like to preserve this. Notifications will be sorted lexicographically using this
675     * value, although providing different priorities in addition to providing sort key may
676     * cause this value to be ignored.
677     *
678     * <p>This sort key can also be used to order members of a notification group. See
679     * {@link Builder#setGroup}.
680     *
681     * @see String#compareTo(String)
682     */
683    public String getSortKey() {
684        return mSortKey;
685    }
686
687    /**
688     * Additional semantic data to be carried around with this Notification.
689     * <p>
690     * The extras keys defined here are intended to capture the original inputs to {@link Builder}
691     * APIs, and are intended to be used by
692     * {@link android.service.notification.NotificationListenerService} implementations to extract
693     * detailed information from notification objects.
694     */
695    public Bundle extras = new Bundle();
696
697    /**
698     * {@link #extras} key: this is the title of the notification,
699     * as supplied to {@link Builder#setContentTitle(CharSequence)}.
700     */
701    public static final String EXTRA_TITLE = "android.title";
702
703    /**
704     * {@link #extras} key: this is the title of the notification when shown in expanded form,
705     * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}.
706     */
707    public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big";
708
709    /**
710     * {@link #extras} key: this is the main text payload, as supplied to
711     * {@link Builder#setContentText(CharSequence)}.
712     */
713    public static final String EXTRA_TEXT = "android.text";
714
715    /**
716     * {@link #extras} key: this is a third line of text, as supplied to
717     * {@link Builder#setSubText(CharSequence)}.
718     */
719    public static final String EXTRA_SUB_TEXT = "android.subText";
720
721    /**
722     * {@link #extras} key: this is a small piece of additional text as supplied to
723     * {@link Builder#setContentInfo(CharSequence)}.
724     */
725    public static final String EXTRA_INFO_TEXT = "android.infoText";
726
727    /**
728     * {@link #extras} key: this is a line of summary information intended to be shown
729     * alongside expanded notifications, as supplied to (e.g.)
730     * {@link BigTextStyle#setSummaryText(CharSequence)}.
731     */
732    public static final String EXTRA_SUMMARY_TEXT = "android.summaryText";
733
734    /**
735     * {@link #extras} key: this is the longer text shown in the big form of a
736     * {@link BigTextStyle} notification, as supplied to
737     * {@link BigTextStyle#bigText(CharSequence)}.
738     */
739    public static final String EXTRA_BIG_TEXT = "android.bigText";
740
741    /**
742     * {@link #extras} key: this is the resource ID of the notification's main small icon, as
743     * supplied to {@link Builder#setSmallIcon(int)}.
744     */
745    public static final String EXTRA_SMALL_ICON = "android.icon";
746
747    /**
748     * {@link #extras} key: this is a bitmap to be used instead of the small icon when showing the
749     * notification payload, as
750     * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}.
751     */
752    public static final String EXTRA_LARGE_ICON = "android.largeIcon";
753
754    /**
755     * {@link #extras} key: this is a bitmap to be used instead of the one from
756     * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is
757     * shown in its expanded form, as supplied to
758     * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}.
759     */
760    public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big";
761
762    /**
763     * {@link #extras} key: this is the progress value supplied to
764     * {@link Builder#setProgress(int, int, boolean)}.
765     */
766    public static final String EXTRA_PROGRESS = "android.progress";
767
768    /**
769     * {@link #extras} key: this is the maximum value supplied to
770     * {@link Builder#setProgress(int, int, boolean)}.
771     */
772    public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
773
774    /**
775     * {@link #extras} key: whether the progress bar is indeterminate, supplied to
776     * {@link Builder#setProgress(int, int, boolean)}.
777     */
778    public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
779
780    /**
781     * {@link #extras} key: whether {@link #when} should be shown as a count-up timer (specifically
782     * a {@link android.widget.Chronometer}) instead of a timestamp, as supplied to
783     * {@link Builder#setUsesChronometer(boolean)}.
784     */
785    public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
786
787    /**
788     * {@link #extras} key: whether {@link #when} should be shown,
789     * as supplied to {@link Builder#setShowWhen(boolean)}.
790     */
791    public static final String EXTRA_SHOW_WHEN = "android.showWhen";
792
793    /**
794     * {@link #extras} key: this is a bitmap to be shown in {@link BigPictureStyle} expanded
795     * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}.
796     */
797    public static final String EXTRA_PICTURE = "android.picture";
798
799    /**
800     * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded
801     * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}.
802     */
803    public static final String EXTRA_TEXT_LINES = "android.textLines";
804
805    /**
806     * {@link #extras} key: A string representing the name of the specific
807     * {@link android.app.Notification.Style} used to create this notification.
808     */
809    public static final String EXTRA_TEMPLATE = "android.template";
810
811    /**
812     * {@link #extras} key: A String array containing the people that this notification relates to,
813     * each of which was supplied to {@link Builder#addPerson(String)}.
814     */
815    public static final String EXTRA_PEOPLE = "android.people";
816
817    /**
818     * {@link #extras} key: used to provide hints about the appropriateness of
819     * displaying this notification as a heads-up notification.
820     * @hide
821     */
822    public static final String EXTRA_AS_HEADS_UP = "headsup";
823
824    /**
825     * Allow certain system-generated notifications to appear before the device is provisioned.
826     * Only available to notifications coming from the android package.
827     * @hide
828     */
829    public static final String EXTRA_ALLOW_DURING_SETUP = "android.allowDuringSetup";
830
831    /**
832     * {@link #extras} key: A
833     * {@link android.content.ContentUris content URI} pointing to an image that can be displayed
834     * in the background when the notification is selected. The URI must point to an image stream
835     * suitable for passing into
836     * {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream)
837     * BitmapFactory.decodeStream}; all other content types will be ignored. The content provider
838     * URI used for this purpose must require no permissions to read the image data.
839     */
840    public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
841
842    /**
843     * {@link #extras} key: A
844     * {@link android.media.session.MediaSession.Token} associated with a
845     * {@link android.app.Notification.MediaStyle} notification.
846     */
847    public static final String EXTRA_MEDIA_SESSION = "android.mediaSession";
848
849    /**
850     * {@link #extras} key: the indices of actions to be shown in the compact view,
851     * as supplied to (e.g.) {@link MediaStyle#setShowActionsInCompactView(int...)}.
852     */
853    public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions";
854
855    /**
856     * {@link #extras} key: the user that built the notification.
857     *
858     * @hide
859     */
860    public static final String EXTRA_ORIGINATING_USERID = "android.originatingUserId";
861
862    /**
863     * Value for {@link #EXTRA_AS_HEADS_UP} that indicates this notification should not be
864     * displayed in the heads up space.
865     *
866     * <p>
867     * If this notification has a {@link #fullScreenIntent}, then it will always launch the
868     * full-screen intent when posted.
869     * </p>
870     * @hide
871     */
872    public static final int HEADS_UP_NEVER = 0;
873
874    /**
875     * Default value for {@link #EXTRA_AS_HEADS_UP} that indicates this notification may be
876     * displayed as a heads up.
877     * @hide
878     */
879    public static final int HEADS_UP_ALLOWED = 1;
880
881    /**
882     * Value for {@link #EXTRA_AS_HEADS_UP} that indicates this notification is a
883     * good candidate for display as a heads up.
884     * @hide
885     */
886    public static final int HEADS_UP_REQUESTED = 2;
887
888    /**
889     * Structure to encapsulate a named action that can be shown as part of this notification.
890     * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is
891     * selected by the user.
892     * <p>
893     * Apps should use {@link Notification.Builder#addAction(int, CharSequence, PendingIntent)}
894     * or {@link Notification.Builder#addAction(Notification.Action)}
895     * to attach actions.
896     */
897    public static class Action implements Parcelable {
898        private final Bundle mExtras;
899        private final RemoteInput[] mRemoteInputs;
900
901        /**
902         * Small icon representing the action.
903         */
904        public int icon;
905
906        /**
907         * Title of the action.
908         */
909        public CharSequence title;
910
911        /**
912         * Intent to send when the user invokes this action. May be null, in which case the action
913         * may be rendered in a disabled presentation by the system UI.
914         */
915        public PendingIntent actionIntent;
916
917        private Action(Parcel in) {
918            icon = in.readInt();
919            title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
920            if (in.readInt() == 1) {
921                actionIntent = PendingIntent.CREATOR.createFromParcel(in);
922            }
923            mExtras = in.readBundle();
924            mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR);
925        }
926
927        /**
928         * Use {@link Notification.Builder#addAction(int, CharSequence, PendingIntent)}.
929         */
930        public Action(int icon, CharSequence title, PendingIntent intent) {
931            this(icon, title, intent, new Bundle(), null);
932        }
933
934        private Action(int icon, CharSequence title, PendingIntent intent, Bundle extras,
935                RemoteInput[] remoteInputs) {
936            this.icon = icon;
937            this.title = title;
938            this.actionIntent = intent;
939            this.mExtras = extras != null ? extras : new Bundle();
940            this.mRemoteInputs = remoteInputs;
941        }
942
943        /**
944         * Get additional metadata carried around with this Action.
945         */
946        public Bundle getExtras() {
947            return mExtras;
948        }
949
950        /**
951         * Get the list of inputs to be collected from the user when this action is sent.
952         * May return null if no remote inputs were added.
953         */
954        public RemoteInput[] getRemoteInputs() {
955            return mRemoteInputs;
956        }
957
958        /**
959         * Builder class for {@link Action} objects.
960         */
961        public static final class Builder {
962            private final int mIcon;
963            private final CharSequence mTitle;
964            private final PendingIntent mIntent;
965            private final Bundle mExtras;
966            private ArrayList<RemoteInput> mRemoteInputs;
967
968            /**
969             * Construct a new builder for {@link Action} object.
970             * @param icon icon to show for this action
971             * @param title the title of the action
972             * @param intent the {@link PendingIntent} to fire when users trigger this action
973             */
974            public Builder(int icon, CharSequence title, PendingIntent intent) {
975                this(icon, title, intent, new Bundle(), null);
976            }
977
978            /**
979             * Construct a new builder for {@link Action} object using the fields from an
980             * {@link Action}.
981             * @param action the action to read fields from.
982             */
983            public Builder(Action action) {
984                this(action.icon, action.title, action.actionIntent, new Bundle(action.mExtras),
985                        action.getRemoteInputs());
986            }
987
988            private Builder(int icon, CharSequence title, PendingIntent intent, Bundle extras,
989                    RemoteInput[] remoteInputs) {
990                mIcon = icon;
991                mTitle = title;
992                mIntent = intent;
993                mExtras = extras;
994                if (remoteInputs != null) {
995                    mRemoteInputs = new ArrayList<RemoteInput>(remoteInputs.length);
996                    Collections.addAll(mRemoteInputs, remoteInputs);
997                }
998            }
999
1000            /**
1001             * Merge additional metadata into this builder.
1002             *
1003             * <p>Values within the Bundle will replace existing extras values in this Builder.
1004             *
1005             * @see Notification.Action#extras
1006             */
1007            public Builder addExtras(Bundle extras) {
1008                if (extras != null) {
1009                    mExtras.putAll(extras);
1010                }
1011                return this;
1012            }
1013
1014            /**
1015             * Get the metadata Bundle used by this Builder.
1016             *
1017             * <p>The returned Bundle is shared with this Builder.
1018             */
1019            public Bundle getExtras() {
1020                return mExtras;
1021            }
1022
1023            /**
1024             * Add an input to be collected from the user when this action is sent.
1025             * Response values can be retrieved from the fired intent by using the
1026             * {@link RemoteInput#getResultsFromIntent} function.
1027             * @param remoteInput a {@link RemoteInput} to add to the action
1028             * @return this object for method chaining
1029             */
1030            public Builder addRemoteInput(RemoteInput remoteInput) {
1031                if (mRemoteInputs == null) {
1032                    mRemoteInputs = new ArrayList<RemoteInput>();
1033                }
1034                mRemoteInputs.add(remoteInput);
1035                return this;
1036            }
1037
1038            /**
1039             * Apply an extender to this action builder. Extenders may be used to add
1040             * metadata or change options on this builder.
1041             */
1042            public Builder extend(Extender extender) {
1043                extender.extend(this);
1044                return this;
1045            }
1046
1047            /**
1048             * Combine all of the options that have been set and return a new {@link Action}
1049             * object.
1050             * @return the built action
1051             */
1052            public Action build() {
1053                RemoteInput[] remoteInputs = mRemoteInputs != null
1054                        ? mRemoteInputs.toArray(new RemoteInput[mRemoteInputs.size()]) : null;
1055                return new Action(mIcon, mTitle, mIntent, mExtras, remoteInputs);
1056            }
1057        }
1058
1059        @Override
1060        public Action clone() {
1061            return new Action(
1062                    icon,
1063                    title,
1064                    actionIntent, // safe to alias
1065                    new Bundle(mExtras),
1066                    getRemoteInputs());
1067        }
1068        @Override
1069        public int describeContents() {
1070            return 0;
1071        }
1072        @Override
1073        public void writeToParcel(Parcel out, int flags) {
1074            out.writeInt(icon);
1075            TextUtils.writeToParcel(title, out, flags);
1076            if (actionIntent != null) {
1077                out.writeInt(1);
1078                actionIntent.writeToParcel(out, flags);
1079            } else {
1080                out.writeInt(0);
1081            }
1082            out.writeBundle(mExtras);
1083            out.writeTypedArray(mRemoteInputs, flags);
1084        }
1085        public static final Parcelable.Creator<Action> CREATOR =
1086                new Parcelable.Creator<Action>() {
1087            public Action createFromParcel(Parcel in) {
1088                return new Action(in);
1089            }
1090            public Action[] newArray(int size) {
1091                return new Action[size];
1092            }
1093        };
1094
1095        /**
1096         * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
1097         * metadata or change options on an action builder.
1098         */
1099        public interface Extender {
1100            /**
1101             * Apply this extender to a notification action builder.
1102             * @param builder the builder to be modified.
1103             * @return the build object for chaining.
1104             */
1105            public Builder extend(Builder builder);
1106        }
1107
1108        /**
1109         * Wearable extender for notification actions. To add extensions to an action,
1110         * create a new {@link android.app.Notification.Action.WearableExtender} object using
1111         * the {@code WearableExtender()} constructor and apply it to a
1112         * {@link android.app.Notification.Action.Builder} using
1113         * {@link android.app.Notification.Action.Builder#extend}.
1114         *
1115         * <pre class="prettyprint">
1116         * Notification.Action action = new Notification.Action.Builder(
1117         *         R.drawable.archive_all, "Archive all", actionIntent)
1118         *         .extend(new Notification.Action.WearableExtender()
1119         *                 .setAvailableOffline(false))
1120         *         .build();</pre>
1121         */
1122        public static final class WearableExtender implements Extender {
1123            /** Notification action extra which contains wearable extensions */
1124            private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
1125
1126            // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
1127            private static final String KEY_FLAGS = "flags";
1128            private static final String KEY_IN_PROGRESS_LABEL = "inProgressLabel";
1129            private static final String KEY_CONFIRM_LABEL = "confirmLabel";
1130            private static final String KEY_CANCEL_LABEL = "cancelLabel";
1131
1132            // Flags bitwise-ored to mFlags
1133            private static final int FLAG_AVAILABLE_OFFLINE = 0x1;
1134
1135            // Default value for flags integer
1136            private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE;
1137
1138            private int mFlags = DEFAULT_FLAGS;
1139
1140            private CharSequence mInProgressLabel;
1141            private CharSequence mConfirmLabel;
1142            private CharSequence mCancelLabel;
1143
1144            /**
1145             * Create a {@link android.app.Notification.Action.WearableExtender} with default
1146             * options.
1147             */
1148            public WearableExtender() {
1149            }
1150
1151            /**
1152             * Create a {@link android.app.Notification.Action.WearableExtender} by reading
1153             * wearable options present in an existing notification action.
1154             * @param action the notification action to inspect.
1155             */
1156            public WearableExtender(Action action) {
1157                Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS);
1158                if (wearableBundle != null) {
1159                    mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
1160                    mInProgressLabel = wearableBundle.getCharSequence(KEY_IN_PROGRESS_LABEL);
1161                    mConfirmLabel = wearableBundle.getCharSequence(KEY_CONFIRM_LABEL);
1162                    mCancelLabel = wearableBundle.getCharSequence(KEY_CANCEL_LABEL);
1163                }
1164            }
1165
1166            /**
1167             * Apply wearable extensions to a notification action that is being built. This is
1168             * typically called by the {@link android.app.Notification.Action.Builder#extend}
1169             * method of {@link android.app.Notification.Action.Builder}.
1170             */
1171            @Override
1172            public Action.Builder extend(Action.Builder builder) {
1173                Bundle wearableBundle = new Bundle();
1174
1175                if (mFlags != DEFAULT_FLAGS) {
1176                    wearableBundle.putInt(KEY_FLAGS, mFlags);
1177                }
1178                if (mInProgressLabel != null) {
1179                    wearableBundle.putCharSequence(KEY_IN_PROGRESS_LABEL, mInProgressLabel);
1180                }
1181                if (mConfirmLabel != null) {
1182                    wearableBundle.putCharSequence(KEY_CONFIRM_LABEL, mConfirmLabel);
1183                }
1184                if (mCancelLabel != null) {
1185                    wearableBundle.putCharSequence(KEY_CANCEL_LABEL, mCancelLabel);
1186                }
1187
1188                builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
1189                return builder;
1190            }
1191
1192            @Override
1193            public WearableExtender clone() {
1194                WearableExtender that = new WearableExtender();
1195                that.mFlags = this.mFlags;
1196                that.mInProgressLabel = this.mInProgressLabel;
1197                that.mConfirmLabel = this.mConfirmLabel;
1198                that.mCancelLabel = this.mCancelLabel;
1199                return that;
1200            }
1201
1202            /**
1203             * Set whether this action is available when the wearable device is not connected to
1204             * a companion device. The user can still trigger this action when the wearable device is
1205             * offline, but a visual hint will indicate that the action may not be available.
1206             * Defaults to true.
1207             */
1208            public WearableExtender setAvailableOffline(boolean availableOffline) {
1209                setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline);
1210                return this;
1211            }
1212
1213            /**
1214             * Get whether this action is available when the wearable device is not connected to
1215             * a companion device. The user can still trigger this action when the wearable device is
1216             * offline, but a visual hint will indicate that the action may not be available.
1217             * Defaults to true.
1218             */
1219            public boolean isAvailableOffline() {
1220                return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0;
1221            }
1222
1223            private void setFlag(int mask, boolean value) {
1224                if (value) {
1225                    mFlags |= mask;
1226                } else {
1227                    mFlags &= ~mask;
1228                }
1229            }
1230
1231            /**
1232             * Set a label to display while the wearable is preparing to automatically execute the
1233             * action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
1234             *
1235             * @param label the label to display while the action is being prepared to execute
1236             * @return this object for method chaining
1237             */
1238            public WearableExtender setInProgressLabel(CharSequence label) {
1239                mInProgressLabel = label;
1240                return this;
1241            }
1242
1243            /**
1244             * Get the label to display while the wearable is preparing to automatically execute
1245             * the action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
1246             *
1247             * @return the label to display while the action is being prepared to execute
1248             */
1249            public CharSequence getInProgressLabel() {
1250                return mInProgressLabel;
1251            }
1252
1253            /**
1254             * Set a label to display to confirm that the action should be executed.
1255             * This is usually an imperative verb like "Send".
1256             *
1257             * @param label the label to confirm the action should be executed
1258             * @return this object for method chaining
1259             */
1260            public WearableExtender setConfirmLabel(CharSequence label) {
1261                mConfirmLabel = label;
1262                return this;
1263            }
1264
1265            /**
1266             * Get the label to display to confirm that the action should be executed.
1267             * This is usually an imperative verb like "Send".
1268             *
1269             * @return the label to confirm the action should be executed
1270             */
1271            public CharSequence getConfirmLabel() {
1272                return mConfirmLabel;
1273            }
1274
1275            /**
1276             * Set a label to display to cancel the action.
1277             * This is usually an imperative verb, like "Cancel".
1278             *
1279             * @param label the label to display to cancel the action
1280             * @return this object for method chaining
1281             */
1282            public WearableExtender setCancelLabel(CharSequence label) {
1283                mCancelLabel = label;
1284                return this;
1285            }
1286
1287            /**
1288             * Get the label to display to cancel the action.
1289             * This is usually an imperative verb like "Cancel".
1290             *
1291             * @return the label to display to cancel the action
1292             */
1293            public CharSequence getCancelLabel() {
1294                return mCancelLabel;
1295            }
1296        }
1297    }
1298
1299    /**
1300     * Array of all {@link Action} structures attached to this notification by
1301     * {@link Builder#addAction(int, CharSequence, PendingIntent)}. Mostly useful for instances of
1302     * {@link android.service.notification.NotificationListenerService} that provide an alternative
1303     * interface for invoking actions.
1304     */
1305    public Action[] actions;
1306
1307    /**
1308     * Replacement version of this notification whose content will be shown
1309     * in an insecure context such as atop a secure keyguard. See {@link #visibility}
1310     * and {@link #VISIBILITY_PUBLIC}.
1311     */
1312    public Notification publicVersion;
1313
1314    /**
1315     * Constructs a Notification object with default values.
1316     * You might want to consider using {@link Builder} instead.
1317     */
1318    public Notification()
1319    {
1320        this.when = System.currentTimeMillis();
1321        this.priority = PRIORITY_DEFAULT;
1322    }
1323
1324    /**
1325     * @hide
1326     */
1327    public Notification(Context context, int icon, CharSequence tickerText, long when,
1328            CharSequence contentTitle, CharSequence contentText, Intent contentIntent)
1329    {
1330        this.when = when;
1331        this.icon = icon;
1332        this.tickerText = tickerText;
1333        setLatestEventInfo(context, contentTitle, contentText,
1334                PendingIntent.getActivity(context, 0, contentIntent, 0));
1335    }
1336
1337    /**
1338     * Constructs a Notification object with the information needed to
1339     * have a status bar icon without the standard expanded view.
1340     *
1341     * @param icon          The resource id of the icon to put in the status bar.
1342     * @param tickerText    The text that flows by in the status bar when the notification first
1343     *                      activates.
1344     * @param when          The time to show in the time field.  In the System.currentTimeMillis
1345     *                      timebase.
1346     *
1347     * @deprecated Use {@link Builder} instead.
1348     */
1349    @Deprecated
1350    public Notification(int icon, CharSequence tickerText, long when)
1351    {
1352        this.icon = icon;
1353        this.tickerText = tickerText;
1354        this.when = when;
1355    }
1356
1357    /**
1358     * Unflatten the notification from a parcel.
1359     */
1360    public Notification(Parcel parcel)
1361    {
1362        int version = parcel.readInt();
1363
1364        when = parcel.readLong();
1365        icon = parcel.readInt();
1366        number = parcel.readInt();
1367        if (parcel.readInt() != 0) {
1368            contentIntent = PendingIntent.CREATOR.createFromParcel(parcel);
1369        }
1370        if (parcel.readInt() != 0) {
1371            deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel);
1372        }
1373        if (parcel.readInt() != 0) {
1374            tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
1375        }
1376        if (parcel.readInt() != 0) {
1377            tickerView = RemoteViews.CREATOR.createFromParcel(parcel);
1378        }
1379        if (parcel.readInt() != 0) {
1380            contentView = RemoteViews.CREATOR.createFromParcel(parcel);
1381        }
1382        if (parcel.readInt() != 0) {
1383            largeIcon = Bitmap.CREATOR.createFromParcel(parcel);
1384        }
1385        defaults = parcel.readInt();
1386        flags = parcel.readInt();
1387        if (parcel.readInt() != 0) {
1388            sound = Uri.CREATOR.createFromParcel(parcel);
1389        }
1390
1391        audioStreamType = parcel.readInt();
1392        if (parcel.readInt() != 0) {
1393            audioAttributes = AudioAttributes.CREATOR.createFromParcel(parcel);
1394        }
1395        vibrate = parcel.createLongArray();
1396        ledARGB = parcel.readInt();
1397        ledOnMS = parcel.readInt();
1398        ledOffMS = parcel.readInt();
1399        iconLevel = parcel.readInt();
1400
1401        if (parcel.readInt() != 0) {
1402            fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel);
1403        }
1404
1405        priority = parcel.readInt();
1406
1407        category = parcel.readString();
1408
1409        mGroupKey = parcel.readString();
1410
1411        mSortKey = parcel.readString();
1412
1413        extras = parcel.readBundle(); // may be null
1414
1415        actions = parcel.createTypedArray(Action.CREATOR); // may be null
1416
1417        if (parcel.readInt() != 0) {
1418            bigContentView = RemoteViews.CREATOR.createFromParcel(parcel);
1419        }
1420
1421        if (parcel.readInt() != 0) {
1422            headsUpContentView = RemoteViews.CREATOR.createFromParcel(parcel);
1423        }
1424
1425        visibility = parcel.readInt();
1426
1427        if (parcel.readInt() != 0) {
1428            publicVersion = Notification.CREATOR.createFromParcel(parcel);
1429        }
1430
1431        color = parcel.readInt();
1432    }
1433
1434    @Override
1435    public Notification clone() {
1436        Notification that = new Notification();
1437        cloneInto(that, true);
1438        return that;
1439    }
1440
1441    /**
1442     * Copy all (or if heavy is false, all except Bitmaps and RemoteViews) members
1443     * of this into that.
1444     * @hide
1445     */
1446    public void cloneInto(Notification that, boolean heavy) {
1447        that.when = this.when;
1448        that.icon = this.icon;
1449        that.number = this.number;
1450
1451        // PendingIntents are global, so there's no reason (or way) to clone them.
1452        that.contentIntent = this.contentIntent;
1453        that.deleteIntent = this.deleteIntent;
1454        that.fullScreenIntent = this.fullScreenIntent;
1455
1456        if (this.tickerText != null) {
1457            that.tickerText = this.tickerText.toString();
1458        }
1459        if (heavy && this.tickerView != null) {
1460            that.tickerView = this.tickerView.clone();
1461        }
1462        if (heavy && this.contentView != null) {
1463            that.contentView = this.contentView.clone();
1464        }
1465        if (heavy && this.largeIcon != null) {
1466            that.largeIcon = Bitmap.createBitmap(this.largeIcon);
1467        }
1468        that.iconLevel = this.iconLevel;
1469        that.sound = this.sound; // android.net.Uri is immutable
1470        that.audioStreamType = this.audioStreamType;
1471        if (this.audioAttributes != null) {
1472            that.audioAttributes = new AudioAttributes.Builder(this.audioAttributes).build();
1473        }
1474
1475        final long[] vibrate = this.vibrate;
1476        if (vibrate != null) {
1477            final int N = vibrate.length;
1478            final long[] vib = that.vibrate = new long[N];
1479            System.arraycopy(vibrate, 0, vib, 0, N);
1480        }
1481
1482        that.ledARGB = this.ledARGB;
1483        that.ledOnMS = this.ledOnMS;
1484        that.ledOffMS = this.ledOffMS;
1485        that.defaults = this.defaults;
1486
1487        that.flags = this.flags;
1488
1489        that.priority = this.priority;
1490
1491        that.category = this.category;
1492
1493        that.mGroupKey = this.mGroupKey;
1494
1495        that.mSortKey = this.mSortKey;
1496
1497        if (this.extras != null) {
1498            try {
1499                that.extras = new Bundle(this.extras);
1500                // will unparcel
1501                that.extras.size();
1502            } catch (BadParcelableException e) {
1503                Log.e(TAG, "could not unparcel extras from notification: " + this, e);
1504                that.extras = null;
1505            }
1506        }
1507
1508        if (this.actions != null) {
1509            that.actions = new Action[this.actions.length];
1510            for(int i=0; i<this.actions.length; i++) {
1511                that.actions[i] = this.actions[i].clone();
1512            }
1513        }
1514
1515        if (heavy && this.bigContentView != null) {
1516            that.bigContentView = this.bigContentView.clone();
1517        }
1518
1519        if (heavy && this.headsUpContentView != null) {
1520            that.headsUpContentView = this.headsUpContentView.clone();
1521        }
1522
1523        that.visibility = this.visibility;
1524
1525        if (this.publicVersion != null) {
1526            that.publicVersion = new Notification();
1527            this.publicVersion.cloneInto(that.publicVersion, heavy);
1528        }
1529
1530        that.color = this.color;
1531
1532        if (!heavy) {
1533            that.lightenPayload(); // will clean out extras
1534        }
1535    }
1536
1537    /**
1538     * Removes heavyweight parts of the Notification object for archival or for sending to
1539     * listeners when the full contents are not necessary.
1540     * @hide
1541     */
1542    public final void lightenPayload() {
1543        tickerView = null;
1544        contentView = null;
1545        bigContentView = null;
1546        headsUpContentView = null;
1547        largeIcon = null;
1548        if (extras != null) {
1549            extras.remove(Notification.EXTRA_LARGE_ICON);
1550            extras.remove(Notification.EXTRA_LARGE_ICON_BIG);
1551            extras.remove(Notification.EXTRA_PICTURE);
1552            extras.remove(Notification.EXTRA_BIG_TEXT);
1553            // Prevent light notifications from being rebuilt.
1554            extras.remove(Builder.EXTRA_NEEDS_REBUILD);
1555        }
1556    }
1557
1558    /**
1559     * Make sure this CharSequence is safe to put into a bundle, which basically
1560     * means it had better not be some custom Parcelable implementation.
1561     * @hide
1562     */
1563    public static CharSequence safeCharSequence(CharSequence cs) {
1564        if (cs == null) return cs;
1565        if (cs.length() > MAX_CHARSEQUENCE_LENGTH) {
1566            cs = cs.subSequence(0, MAX_CHARSEQUENCE_LENGTH);
1567        }
1568        if (cs instanceof Parcelable) {
1569            Log.e(TAG, "warning: " + cs.getClass().getCanonicalName()
1570                    + " instance is a custom Parcelable and not allowed in Notification");
1571            return cs.toString();
1572        }
1573
1574        return cs;
1575    }
1576
1577    public int describeContents() {
1578        return 0;
1579    }
1580
1581    /**
1582     * Flatten this notification from a parcel.
1583     */
1584    public void writeToParcel(Parcel parcel, int flags)
1585    {
1586        parcel.writeInt(1);
1587
1588        parcel.writeLong(when);
1589        parcel.writeInt(icon);
1590        parcel.writeInt(number);
1591        if (contentIntent != null) {
1592            parcel.writeInt(1);
1593            contentIntent.writeToParcel(parcel, 0);
1594        } else {
1595            parcel.writeInt(0);
1596        }
1597        if (deleteIntent != null) {
1598            parcel.writeInt(1);
1599            deleteIntent.writeToParcel(parcel, 0);
1600        } else {
1601            parcel.writeInt(0);
1602        }
1603        if (tickerText != null) {
1604            parcel.writeInt(1);
1605            TextUtils.writeToParcel(tickerText, parcel, flags);
1606        } else {
1607            parcel.writeInt(0);
1608        }
1609        if (tickerView != null) {
1610            parcel.writeInt(1);
1611            tickerView.writeToParcel(parcel, 0);
1612        } else {
1613            parcel.writeInt(0);
1614        }
1615        if (contentView != null) {
1616            parcel.writeInt(1);
1617            contentView.writeToParcel(parcel, 0);
1618        } else {
1619            parcel.writeInt(0);
1620        }
1621        if (largeIcon != null) {
1622            parcel.writeInt(1);
1623            largeIcon.writeToParcel(parcel, 0);
1624        } else {
1625            parcel.writeInt(0);
1626        }
1627
1628        parcel.writeInt(defaults);
1629        parcel.writeInt(this.flags);
1630
1631        if (sound != null) {
1632            parcel.writeInt(1);
1633            sound.writeToParcel(parcel, 0);
1634        } else {
1635            parcel.writeInt(0);
1636        }
1637        parcel.writeInt(audioStreamType);
1638
1639        if (audioAttributes != null) {
1640            parcel.writeInt(1);
1641            audioAttributes.writeToParcel(parcel, 0);
1642        } else {
1643            parcel.writeInt(0);
1644        }
1645
1646        parcel.writeLongArray(vibrate);
1647        parcel.writeInt(ledARGB);
1648        parcel.writeInt(ledOnMS);
1649        parcel.writeInt(ledOffMS);
1650        parcel.writeInt(iconLevel);
1651
1652        if (fullScreenIntent != null) {
1653            parcel.writeInt(1);
1654            fullScreenIntent.writeToParcel(parcel, 0);
1655        } else {
1656            parcel.writeInt(0);
1657        }
1658
1659        parcel.writeInt(priority);
1660
1661        parcel.writeString(category);
1662
1663        parcel.writeString(mGroupKey);
1664
1665        parcel.writeString(mSortKey);
1666
1667        parcel.writeBundle(extras); // null ok
1668
1669        parcel.writeTypedArray(actions, 0); // null ok
1670
1671        if (bigContentView != null) {
1672            parcel.writeInt(1);
1673            bigContentView.writeToParcel(parcel, 0);
1674        } else {
1675            parcel.writeInt(0);
1676        }
1677
1678        if (headsUpContentView != null) {
1679            parcel.writeInt(1);
1680            headsUpContentView.writeToParcel(parcel, 0);
1681        } else {
1682            parcel.writeInt(0);
1683        }
1684
1685        parcel.writeInt(visibility);
1686
1687        if (publicVersion != null) {
1688            parcel.writeInt(1);
1689            publicVersion.writeToParcel(parcel, 0);
1690        } else {
1691            parcel.writeInt(0);
1692        }
1693
1694        parcel.writeInt(color);
1695    }
1696
1697    /**
1698     * Parcelable.Creator that instantiates Notification objects
1699     */
1700    public static final Parcelable.Creator<Notification> CREATOR
1701            = new Parcelable.Creator<Notification>()
1702    {
1703        public Notification createFromParcel(Parcel parcel)
1704        {
1705            return new Notification(parcel);
1706        }
1707
1708        public Notification[] newArray(int size)
1709        {
1710            return new Notification[size];
1711        }
1712    };
1713
1714    /**
1715     * Sets the {@link #contentView} field to be a view with the standard "Latest Event"
1716     * layout.
1717     *
1718     * <p>Uses the {@link #icon} and {@link #when} fields to set the icon and time fields
1719     * in the view.</p>
1720     * @param context       The context for your application / activity.
1721     * @param contentTitle The title that goes in the expanded entry.
1722     * @param contentText  The text that goes in the expanded entry.
1723     * @param contentIntent The intent to launch when the user clicks the expanded notification.
1724     * If this is an activity, it must include the
1725     * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
1726     * that you take care of task management as described in the
1727     * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
1728     * Stack</a> document.
1729     *
1730     * @deprecated Use {@link Builder} instead.
1731     */
1732    @Deprecated
1733    public void setLatestEventInfo(Context context,
1734            CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) {
1735        Notification.Builder builder = new Notification.Builder(context);
1736
1737        // First, ensure that key pieces of information that may have been set directly
1738        // are preserved
1739        builder.setWhen(this.when);
1740        builder.setSmallIcon(this.icon);
1741        builder.setPriority(this.priority);
1742        builder.setTicker(this.tickerText);
1743        builder.setNumber(this.number);
1744        builder.setColor(this.color);
1745        builder.mFlags = this.flags;
1746        builder.setSound(this.sound, this.audioStreamType);
1747        builder.setDefaults(this.defaults);
1748        builder.setVibrate(this.vibrate);
1749        builder.setDeleteIntent(this.deleteIntent);
1750
1751        // now apply the latestEventInfo fields
1752        if (contentTitle != null) {
1753            builder.setContentTitle(contentTitle);
1754        }
1755        if (contentText != null) {
1756            builder.setContentText(contentText);
1757        }
1758        builder.setContentIntent(contentIntent);
1759        builder.buildInto(this);
1760    }
1761
1762    @Override
1763    public String toString() {
1764        StringBuilder sb = new StringBuilder();
1765        sb.append("Notification(pri=");
1766        sb.append(priority);
1767        sb.append(" contentView=");
1768        if (contentView != null) {
1769            sb.append(contentView.getPackage());
1770            sb.append("/0x");
1771            sb.append(Integer.toHexString(contentView.getLayoutId()));
1772        } else {
1773            sb.append("null");
1774        }
1775        sb.append(" vibrate=");
1776        if ((this.defaults & DEFAULT_VIBRATE) != 0) {
1777            sb.append("default");
1778        } else if (this.vibrate != null) {
1779            int N = this.vibrate.length-1;
1780            sb.append("[");
1781            for (int i=0; i<N; i++) {
1782                sb.append(this.vibrate[i]);
1783                sb.append(',');
1784            }
1785            if (N != -1) {
1786                sb.append(this.vibrate[N]);
1787            }
1788            sb.append("]");
1789        } else {
1790            sb.append("null");
1791        }
1792        sb.append(" sound=");
1793        if ((this.defaults & DEFAULT_SOUND) != 0) {
1794            sb.append("default");
1795        } else if (this.sound != null) {
1796            sb.append(this.sound.toString());
1797        } else {
1798            sb.append("null");
1799        }
1800        sb.append(" defaults=0x");
1801        sb.append(Integer.toHexString(this.defaults));
1802        sb.append(" flags=0x");
1803        sb.append(Integer.toHexString(this.flags));
1804        sb.append(String.format(" color=0x%08x", this.color));
1805        if (this.category != null) {
1806            sb.append(" category=");
1807            sb.append(this.category);
1808        }
1809        if (this.mGroupKey != null) {
1810            sb.append(" groupKey=");
1811            sb.append(this.mGroupKey);
1812        }
1813        if (this.mSortKey != null) {
1814            sb.append(" sortKey=");
1815            sb.append(this.mSortKey);
1816        }
1817        if (actions != null) {
1818            sb.append(" actions=");
1819            sb.append(actions.length);
1820        }
1821        sb.append(" vis=");
1822        sb.append(visibilityToString(this.visibility));
1823        if (this.publicVersion != null) {
1824            sb.append(" publicVersion=");
1825            sb.append(publicVersion.toString());
1826        }
1827        sb.append(")");
1828        return sb.toString();
1829    }
1830
1831    /**
1832     * {@hide}
1833     */
1834    public static String visibilityToString(int vis) {
1835        switch (vis) {
1836            case VISIBILITY_PRIVATE:
1837                return "PRIVATE";
1838            case VISIBILITY_PUBLIC:
1839                return "PUBLIC";
1840            case VISIBILITY_SECRET:
1841                return "SECRET";
1842            default:
1843                return "UNKNOWN(" + String.valueOf(vis) + ")";
1844        }
1845    }
1846
1847    /**
1848     * {@hide}
1849     */
1850    public static String priorityToString(@Priority int pri) {
1851        switch (pri) {
1852            case PRIORITY_MIN:
1853                return "MIN";
1854            case PRIORITY_LOW:
1855                return "LOW";
1856            case PRIORITY_DEFAULT:
1857                return "DEFAULT";
1858            case PRIORITY_HIGH:
1859                return "HIGH";
1860            case PRIORITY_MAX:
1861                return "MAX";
1862            default:
1863                return "UNKNOWN(" + String.valueOf(pri) + ")";
1864        }
1865    }
1866
1867    /**
1868     * @hide
1869     */
1870    public boolean isValid() {
1871        // Would like to check for icon!=0 here, too, but NotificationManagerService accepts that
1872        // for legacy reasons.
1873        return contentView != null || extras.getBoolean(Builder.EXTRA_REBUILD_CONTENT_VIEW);
1874    }
1875
1876    /**
1877     * @hide
1878     */
1879    public boolean isGroupSummary() {
1880        return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0;
1881    }
1882
1883    /**
1884     * @hide
1885     */
1886    public boolean isGroupChild() {
1887        return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) == 0;
1888    }
1889
1890    /**
1891     * Builder class for {@link Notification} objects.
1892     *
1893     * Provides a convenient way to set the various fields of a {@link Notification} and generate
1894     * content views using the platform's notification layout template. If your app supports
1895     * versions of Android as old as API level 4, you can instead use
1896     * {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder},
1897     * available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support
1898     * library</a>.
1899     *
1900     * <p>Example:
1901     *
1902     * <pre class="prettyprint">
1903     * Notification noti = new Notification.Builder(mContext)
1904     *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
1905     *         .setContentText(subject)
1906     *         .setSmallIcon(R.drawable.new_mail)
1907     *         .setLargeIcon(aBitmap)
1908     *         .build();
1909     * </pre>
1910     */
1911    public static class Builder {
1912        private static final int MAX_ACTION_BUTTONS = 3;
1913        private static final float LARGE_TEXT_SCALE = 1.3f;
1914
1915        /**
1916         * @hide
1917         */
1918        public static final String EXTRA_NEEDS_REBUILD = "android.rebuild";
1919
1920        /**
1921         * @hide
1922         */
1923        public static final String EXTRA_REBUILD_LARGE_ICON = "android.rebuild.largeIcon";
1924        /**
1925         * @hide
1926         */
1927        public static final String EXTRA_REBUILD_CONTENT_VIEW = "android.rebuild.contentView";
1928        /**
1929         * @hide
1930         */
1931        public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT =
1932                "android.rebuild.contentViewActionCount";
1933        /**
1934         * @hide
1935         */
1936        public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW
1937                = "android.rebuild.bigView";
1938        /**
1939         * @hide
1940         */
1941        public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT
1942                = "android.rebuild.bigViewActionCount";
1943        /**
1944         * @hide
1945         */
1946        public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW
1947                = "android.rebuild.hudView";
1948        /**
1949         * @hide
1950         */
1951        public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT
1952                = "android.rebuild.hudViewActionCount";
1953
1954        /**
1955         * The ApplicationInfo of the package that created the notification, used to create
1956         * a context to rebuild the notification via a Builder.
1957         * @hide
1958         */
1959        private static final String EXTRA_REBUILD_CONTEXT_APPLICATION_INFO =
1960                "android.rebuild.applicationInfo";
1961
1962        // Whether to enable stripping (at post time) & rebuilding (at listener receive time) of
1963        // memory intensive resources.
1964        private static final boolean STRIP_AND_REBUILD = true;
1965
1966        private Context mContext;
1967
1968        private long mWhen;
1969        private int mSmallIcon;
1970        private int mSmallIconLevel;
1971        private int mNumber;
1972        private CharSequence mContentTitle;
1973        private CharSequence mContentText;
1974        private CharSequence mContentInfo;
1975        private CharSequence mSubText;
1976        private PendingIntent mContentIntent;
1977        private RemoteViews mContentView;
1978        private PendingIntent mDeleteIntent;
1979        private PendingIntent mFullScreenIntent;
1980        private CharSequence mTickerText;
1981        private RemoteViews mTickerView;
1982        private Bitmap mLargeIcon;
1983        private Uri mSound;
1984        private int mAudioStreamType;
1985        private AudioAttributes mAudioAttributes;
1986        private long[] mVibrate;
1987        private int mLedArgb;
1988        private int mLedOnMs;
1989        private int mLedOffMs;
1990        private int mDefaults;
1991        private int mFlags;
1992        private int mProgressMax;
1993        private int mProgress;
1994        private boolean mProgressIndeterminate;
1995        private String mCategory;
1996        private String mGroupKey;
1997        private String mSortKey;
1998        private Bundle mExtras;
1999        private int mPriority;
2000        private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS);
2001        private boolean mUseChronometer;
2002        private Style mStyle;
2003        private boolean mShowWhen = true;
2004        private int mVisibility = VISIBILITY_PRIVATE;
2005        private Notification mPublicVersion = null;
2006        private final NotificationColorUtil mColorUtil;
2007        private ArrayList<String> mPeople;
2008        private int mColor = COLOR_DEFAULT;
2009
2010        /**
2011         * The user that built the notification originally.
2012         */
2013        private int mOriginatingUserId;
2014
2015        /**
2016         * Contains extras related to rebuilding during the build phase.
2017         */
2018        private Bundle mRebuildBundle = new Bundle();
2019        /**
2020         * Contains the notification to rebuild when this Builder is in "rebuild" mode.
2021         * Null otherwise.
2022         */
2023        private Notification mRebuildNotification = null;
2024
2025        /**
2026         * Whether the build notification has three lines. This is used to make the top padding for
2027         * both the contracted and expanded layout consistent.
2028         *
2029         * <p>
2030         * This field is only valid during the build phase.
2031         */
2032        private boolean mHasThreeLines;
2033
2034        /**
2035         * Constructs a new Builder with the defaults:
2036         *
2037
2038         * <table>
2039         * <tr><th align=right>priority</th>
2040         *     <td>{@link #PRIORITY_DEFAULT}</td></tr>
2041         * <tr><th align=right>when</th>
2042         *     <td>now ({@link System#currentTimeMillis()})</td></tr>
2043         * <tr><th align=right>audio stream</th>
2044         *     <td>{@link #STREAM_DEFAULT}</td></tr>
2045         * </table>
2046         *
2047
2048         * @param context
2049         *            A {@link Context} that will be used by the Builder to construct the
2050         *            RemoteViews. The Context will not be held past the lifetime of this Builder
2051         *            object.
2052         */
2053        public Builder(Context context) {
2054            /*
2055             * Important compatibility note!
2056             * Some apps out in the wild create a Notification.Builder in their Activity subclass
2057             * constructor for later use. At this point Activities - themselves subclasses of
2058             * ContextWrapper - do not have their inner Context populated yet. This means that
2059             * any calls to Context methods from within this constructor can cause NPEs in existing
2060             * apps. Any data populated from mContext should therefore be populated lazily to
2061             * preserve compatibility.
2062             */
2063            mContext = context;
2064
2065            // Set defaults to match the defaults of a Notification
2066            mWhen = System.currentTimeMillis();
2067            mAudioStreamType = STREAM_DEFAULT;
2068            mAudioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
2069            mPriority = PRIORITY_DEFAULT;
2070            mPeople = new ArrayList<String>();
2071
2072            mColorUtil = context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.LOLLIPOP ?
2073                    NotificationColorUtil.getInstance(mContext) : null;
2074        }
2075
2076        /**
2077         * Creates a Builder for rebuilding the given Notification.
2078         * <p>
2079         * Call {@link #rebuild()} to retrieve the rebuilt version of 'n'.
2080         */
2081        private Builder(Context context, Notification n) {
2082            this(context);
2083            mRebuildNotification = n;
2084            restoreFromNotification(n);
2085
2086            Style style = null;
2087            Bundle extras = n.extras;
2088            String templateClass = extras.getString(EXTRA_TEMPLATE);
2089            if (!TextUtils.isEmpty(templateClass)) {
2090                Class<? extends Style> styleClass = getNotificationStyleClass(templateClass);
2091                if (styleClass == null) {
2092                    Log.d(TAG, "Unknown style class: " + styleClass);
2093                    return;
2094                }
2095
2096                try {
2097                    Constructor<? extends Style> constructor = styleClass.getConstructor();
2098                    style = constructor.newInstance();
2099                    style.restoreFromExtras(extras);
2100                } catch (Throwable t) {
2101                    Log.e(TAG, "Could not create Style", t);
2102                    return;
2103                }
2104            }
2105            if (style != null) {
2106                setStyle(style);
2107            }
2108        }
2109
2110        /**
2111         * Add a timestamp pertaining to the notification (usually the time the event occurred).
2112         * It will be shown in the notification content view by default; use
2113         * {@link #setShowWhen(boolean) setShowWhen} to control this.
2114         *
2115         * @see Notification#when
2116         */
2117        public Builder setWhen(long when) {
2118            mWhen = when;
2119            return this;
2120        }
2121
2122        /**
2123         * Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown
2124         * in the content view.
2125         */
2126        public Builder setShowWhen(boolean show) {
2127            mShowWhen = show;
2128            return this;
2129        }
2130
2131        /**
2132         * Show the {@link Notification#when} field as a stopwatch.
2133         *
2134         * Instead of presenting <code>when</code> as a timestamp, the notification will show an
2135         * automatically updating display of the minutes and seconds since <code>when</code>.
2136         *
2137         * Useful when showing an elapsed time (like an ongoing phone call).
2138         *
2139         * @see android.widget.Chronometer
2140         * @see Notification#when
2141         */
2142        public Builder setUsesChronometer(boolean b) {
2143            mUseChronometer = b;
2144            return this;
2145        }
2146
2147        /**
2148         * Set the small icon resource, which will be used to represent the notification in the
2149         * status bar.
2150         *
2151
2152         * The platform template for the expanded view will draw this icon in the left, unless a
2153         * {@link #setLargeIcon(Bitmap) large icon} has also been specified, in which case the small
2154         * icon will be moved to the right-hand side.
2155         *
2156
2157         * @param icon
2158         *            A resource ID in the application's package of the drawable to use.
2159         * @see Notification#icon
2160         */
2161        public Builder setSmallIcon(@DrawableRes int icon) {
2162            mSmallIcon = icon;
2163            return this;
2164        }
2165
2166        /**
2167         * A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional
2168         * level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable
2169         * LevelListDrawable}.
2170         *
2171         * @param icon A resource ID in the application's package of the drawable to use.
2172         * @param level The level to use for the icon.
2173         *
2174         * @see Notification#icon
2175         * @see Notification#iconLevel
2176         */
2177        public Builder setSmallIcon(@DrawableRes int icon, int level) {
2178            mSmallIcon = icon;
2179            mSmallIconLevel = level;
2180            return this;
2181        }
2182
2183        /**
2184         * Set the first line of text in the platform notification template.
2185         */
2186        public Builder setContentTitle(CharSequence title) {
2187            mContentTitle = safeCharSequence(title);
2188            return this;
2189        }
2190
2191        /**
2192         * Set the second line of text in the platform notification template.
2193         */
2194        public Builder setContentText(CharSequence text) {
2195            mContentText = safeCharSequence(text);
2196            return this;
2197        }
2198
2199        /**
2200         * Set the third line of text in the platform notification template.
2201         * Don't use if you're also using {@link #setProgress(int, int, boolean)}; they occupy the
2202         * same location in the standard template.
2203         */
2204        public Builder setSubText(CharSequence text) {
2205            mSubText = safeCharSequence(text);
2206            return this;
2207        }
2208
2209        /**
2210         * Set the large number at the right-hand side of the notification.  This is
2211         * equivalent to setContentInfo, although it might show the number in a different
2212         * font size for readability.
2213         */
2214        public Builder setNumber(int number) {
2215            mNumber = number;
2216            return this;
2217        }
2218
2219        /**
2220         * A small piece of additional information pertaining to this notification.
2221         *
2222         * The platform template will draw this on the last line of the notification, at the far
2223         * right (to the right of a smallIcon if it has been placed there).
2224         */
2225        public Builder setContentInfo(CharSequence info) {
2226            mContentInfo = safeCharSequence(info);
2227            return this;
2228        }
2229
2230        /**
2231         * Set the progress this notification represents.
2232         *
2233         * The platform template will represent this using a {@link ProgressBar}.
2234         */
2235        public Builder setProgress(int max, int progress, boolean indeterminate) {
2236            mProgressMax = max;
2237            mProgress = progress;
2238            mProgressIndeterminate = indeterminate;
2239            return this;
2240        }
2241
2242        /**
2243         * Supply a custom RemoteViews to use instead of the platform template.
2244         *
2245         * @see Notification#contentView
2246         */
2247        public Builder setContent(RemoteViews views) {
2248            mContentView = views;
2249            return this;
2250        }
2251
2252        /**
2253         * Supply a {@link PendingIntent} to be sent when the notification is clicked.
2254         *
2255         * As of {@link android.os.Build.VERSION_CODES#HONEYCOMB}, if this field is unset and you
2256         * have specified a custom RemoteViews with {@link #setContent(RemoteViews)}, you can use
2257         * {@link RemoteViews#setOnClickPendingIntent RemoteViews.setOnClickPendingIntent(int,PendingIntent)}
2258         * to assign PendingIntents to individual views in that custom layout (i.e., to create
2259         * clickable buttons inside the notification view).
2260         *
2261         * @see Notification#contentIntent Notification.contentIntent
2262         */
2263        public Builder setContentIntent(PendingIntent intent) {
2264            mContentIntent = intent;
2265            return this;
2266        }
2267
2268        /**
2269         * Supply a {@link PendingIntent} to send when the notification is cleared explicitly by the user.
2270         *
2271         * @see Notification#deleteIntent
2272         */
2273        public Builder setDeleteIntent(PendingIntent intent) {
2274            mDeleteIntent = intent;
2275            return this;
2276        }
2277
2278        /**
2279         * An intent to launch instead of posting the notification to the status bar.
2280         * Only for use with extremely high-priority notifications demanding the user's
2281         * <strong>immediate</strong> attention, such as an incoming phone call or
2282         * alarm clock that the user has explicitly set to a particular time.
2283         * If this facility is used for something else, please give the user an option
2284         * to turn it off and use a normal notification, as this can be extremely
2285         * disruptive.
2286         *
2287         * <p>
2288         * The system UI may choose to display a heads-up notification, instead of
2289         * launching this intent, while the user is using the device.
2290         * </p>
2291         *
2292         * @param intent The pending intent to launch.
2293         * @param highPriority Passing true will cause this notification to be sent
2294         *          even if other notifications are suppressed.
2295         *
2296         * @see Notification#fullScreenIntent
2297         */
2298        public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) {
2299            mFullScreenIntent = intent;
2300            setFlag(FLAG_HIGH_PRIORITY, highPriority);
2301            return this;
2302        }
2303
2304        /**
2305         * Set the "ticker" text which is sent to accessibility services.
2306         *
2307         * @see Notification#tickerText
2308         */
2309        public Builder setTicker(CharSequence tickerText) {
2310            mTickerText = safeCharSequence(tickerText);
2311            return this;
2312        }
2313
2314        /**
2315         * Obsolete version of {@link #setTicker(CharSequence)}.
2316         *
2317         */
2318        @Deprecated
2319        public Builder setTicker(CharSequence tickerText, RemoteViews views) {
2320            mTickerText = safeCharSequence(tickerText);
2321            mTickerView = views; // we'll save it for you anyway
2322            return this;
2323        }
2324
2325        /**
2326         * Add a large icon to the notification (and the ticker on some devices).
2327         *
2328         * In the platform template, this image will be shown on the left of the notification view
2329         * in place of the {@link #setSmallIcon(int) small icon} (which will move to the right side).
2330         *
2331         * @see Notification#largeIcon
2332         */
2333        public Builder setLargeIcon(Bitmap icon) {
2334            mLargeIcon = icon;
2335            return this;
2336        }
2337
2338        /**
2339         * Set the sound to play.
2340         *
2341         * It will be played using the {@link #AUDIO_ATTRIBUTES_DEFAULT default audio attributes}
2342         * for notifications.
2343         *
2344         * <p>
2345         * A notification that is noisy is more likely to be presented as a heads-up notification.
2346         * </p>
2347         *
2348         * @see Notification#sound
2349         */
2350        public Builder setSound(Uri sound) {
2351            mSound = sound;
2352            mAudioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
2353            return this;
2354        }
2355
2356        /**
2357         * Set the sound to play, along with a specific stream on which to play it.
2358         *
2359         * See {@link android.media.AudioManager} for the <code>STREAM_</code> constants.
2360         *
2361         * <p>
2362         * A notification that is noisy is more likely to be presented as a heads-up notification.
2363         * </p>
2364         * @deprecated use {@link #setSound(Uri, AudioAttributes)} instead.
2365         * @see Notification#sound
2366         */
2367        @Deprecated
2368        public Builder setSound(Uri sound, int streamType) {
2369            mSound = sound;
2370            mAudioStreamType = streamType;
2371            return this;
2372        }
2373
2374        /**
2375         * Set the sound to play, along with specific {@link AudioAttributes audio attributes} to
2376         * use during playback.
2377         *
2378         * <p>
2379         * A notification that is noisy is more likely to be presented as a heads-up notification.
2380         * </p>
2381         *
2382         * @see Notification#sound
2383         */
2384        public Builder setSound(Uri sound, AudioAttributes audioAttributes) {
2385            mSound = sound;
2386            mAudioAttributes = audioAttributes;
2387            return this;
2388        }
2389
2390        /**
2391         * Set the vibration pattern to use.
2392         *
2393         * See {@link android.os.Vibrator#vibrate(long[], int)} for a discussion of the
2394         * <code>pattern</code> parameter.
2395         *
2396         * <p>
2397         * A notification that vibrates is more likely to be presented as a heads-up notification.
2398         * </p>
2399         *
2400         * @see Notification#vibrate
2401         */
2402        public Builder setVibrate(long[] pattern) {
2403            mVibrate = pattern;
2404            return this;
2405        }
2406
2407        /**
2408         * Set the desired color for the indicator LED on the device, as well as the
2409         * blink duty cycle (specified in milliseconds).
2410         *
2411
2412         * Not all devices will honor all (or even any) of these values.
2413         *
2414
2415         * @see Notification#ledARGB
2416         * @see Notification#ledOnMS
2417         * @see Notification#ledOffMS
2418         */
2419        public Builder setLights(@ColorInt int argb, int onMs, int offMs) {
2420            mLedArgb = argb;
2421            mLedOnMs = onMs;
2422            mLedOffMs = offMs;
2423            return this;
2424        }
2425
2426        /**
2427         * Set whether this is an "ongoing" notification.
2428         *
2429
2430         * Ongoing notifications cannot be dismissed by the user, so your application or service
2431         * must take care of canceling them.
2432         *
2433
2434         * They are typically used to indicate a background task that the user is actively engaged
2435         * with (e.g., playing music) or is pending in some way and therefore occupying the device
2436         * (e.g., a file download, sync operation, active network connection).
2437         *
2438
2439         * @see Notification#FLAG_ONGOING_EVENT
2440         * @see Service#setForeground(boolean)
2441         */
2442        public Builder setOngoing(boolean ongoing) {
2443            setFlag(FLAG_ONGOING_EVENT, ongoing);
2444            return this;
2445        }
2446
2447        /**
2448         * Set this flag if you would only like the sound, vibrate
2449         * and ticker to be played if the notification is not already showing.
2450         *
2451         * @see Notification#FLAG_ONLY_ALERT_ONCE
2452         */
2453        public Builder setOnlyAlertOnce(boolean onlyAlertOnce) {
2454            setFlag(FLAG_ONLY_ALERT_ONCE, onlyAlertOnce);
2455            return this;
2456        }
2457
2458        /**
2459         * Make this notification automatically dismissed when the user touches it. The
2460         * PendingIntent set with {@link #setDeleteIntent} will be sent when this happens.
2461         *
2462         * @see Notification#FLAG_AUTO_CANCEL
2463         */
2464        public Builder setAutoCancel(boolean autoCancel) {
2465            setFlag(FLAG_AUTO_CANCEL, autoCancel);
2466            return this;
2467        }
2468
2469        /**
2470         * Set whether or not this notification should not bridge to other devices.
2471         *
2472         * <p>Some notifications can be bridged to other devices for remote display.
2473         * This hint can be set to recommend this notification not be bridged.
2474         */
2475        public Builder setLocalOnly(boolean localOnly) {
2476            setFlag(FLAG_LOCAL_ONLY, localOnly);
2477            return this;
2478        }
2479
2480        /**
2481         * Set which notification properties will be inherited from system defaults.
2482         * <p>
2483         * The value should be one or more of the following fields combined with
2484         * bitwise-or:
2485         * {@link #DEFAULT_SOUND}, {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}.
2486         * <p>
2487         * For all default values, use {@link #DEFAULT_ALL}.
2488         */
2489        public Builder setDefaults(int defaults) {
2490            mDefaults = defaults;
2491            return this;
2492        }
2493
2494        /**
2495         * Set the priority of this notification.
2496         *
2497         * @see Notification#priority
2498         */
2499        public Builder setPriority(@Priority int pri) {
2500            mPriority = pri;
2501            return this;
2502        }
2503
2504        /**
2505         * Set the notification category.
2506         *
2507         * @see Notification#category
2508         */
2509        public Builder setCategory(String category) {
2510            mCategory = category;
2511            return this;
2512        }
2513
2514        /**
2515         * Add a person that is relevant to this notification.
2516         *
2517         * <P>
2518         * Depending on user preferences, this annotation may allow the notification to pass
2519         * through interruption filters, and to appear more prominently in the user interface.
2520         * </P>
2521         *
2522         * <P>
2523         * The person should be specified by the {@code String} representation of a
2524         * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
2525         * </P>
2526         *
2527         * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema
2528         * URIs.  The path part of these URIs must exist in the contacts database, in the
2529         * appropriate column, or the reference will be discarded as invalid. Telephone schema
2530         * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}.
2531         * </P>
2532         *
2533         * @param uri A URI for the person.
2534         * @see Notification#EXTRA_PEOPLE
2535         */
2536        public Builder addPerson(String uri) {
2537            mPeople.add(uri);
2538            return this;
2539        }
2540
2541        /**
2542         * Set this notification to be part of a group of notifications sharing the same key.
2543         * Grouped notifications may display in a cluster or stack on devices which
2544         * support such rendering.
2545         *
2546         * <p>To make this notification the summary for its group, also call
2547         * {@link #setGroupSummary}. A sort order can be specified for group members by using
2548         * {@link #setSortKey}.
2549         * @param groupKey The group key of the group.
2550         * @return this object for method chaining
2551         */
2552        public Builder setGroup(String groupKey) {
2553            mGroupKey = groupKey;
2554            return this;
2555        }
2556
2557        /**
2558         * Set this notification to be the group summary for a group of notifications.
2559         * Grouped notifications may display in a cluster or stack on devices which
2560         * support such rendering. Requires a group key also be set using {@link #setGroup}.
2561         * @param isGroupSummary Whether this notification should be a group summary.
2562         * @return this object for method chaining
2563         */
2564        public Builder setGroupSummary(boolean isGroupSummary) {
2565            setFlag(FLAG_GROUP_SUMMARY, isGroupSummary);
2566            return this;
2567        }
2568
2569        /**
2570         * Set a sort key that orders this notification among other notifications from the
2571         * same package. This can be useful if an external sort was already applied and an app
2572         * would like to preserve this. Notifications will be sorted lexicographically using this
2573         * value, although providing different priorities in addition to providing sort key may
2574         * cause this value to be ignored.
2575         *
2576         * <p>This sort key can also be used to order members of a notification group. See
2577         * {@link #setGroup}.
2578         *
2579         * @see String#compareTo(String)
2580         */
2581        public Builder setSortKey(String sortKey) {
2582            mSortKey = sortKey;
2583            return this;
2584        }
2585
2586        /**
2587         * Merge additional metadata into this notification.
2588         *
2589         * <p>Values within the Bundle will replace existing extras values in this Builder.
2590         *
2591         * @see Notification#extras
2592         */
2593        public Builder addExtras(Bundle extras) {
2594            if (extras != null) {
2595                if (mExtras == null) {
2596                    mExtras = new Bundle(extras);
2597                } else {
2598                    mExtras.putAll(extras);
2599                }
2600            }
2601            return this;
2602        }
2603
2604        /**
2605         * Set metadata for this notification.
2606         *
2607         * <p>A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's
2608         * current contents are copied into the Notification each time {@link #build()} is
2609         * called.
2610         *
2611         * <p>Replaces any existing extras values with those from the provided Bundle.
2612         * Use {@link #addExtras} to merge in metadata instead.
2613         *
2614         * @see Notification#extras
2615         */
2616        public Builder setExtras(Bundle extras) {
2617            mExtras = extras;
2618            return this;
2619        }
2620
2621        /**
2622         * Get the current metadata Bundle used by this notification Builder.
2623         *
2624         * <p>The returned Bundle is shared with this Builder.
2625         *
2626         * <p>The current contents of this Bundle are copied into the Notification each time
2627         * {@link #build()} is called.
2628         *
2629         * @see Notification#extras
2630         */
2631        public Bundle getExtras() {
2632            if (mExtras == null) {
2633                mExtras = new Bundle();
2634            }
2635            return mExtras;
2636        }
2637
2638        /**
2639         * Add an action to this notification. Actions are typically displayed by
2640         * the system as a button adjacent to the notification content.
2641         * <p>
2642         * Every action must have an icon (32dp square and matching the
2643         * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
2644         * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
2645         * <p>
2646         * A notification in its expanded form can display up to 3 actions, from left to right in
2647         * the order they were added. Actions will not be displayed when the notification is
2648         * collapsed, however, so be sure that any essential functions may be accessed by the user
2649         * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
2650         *
2651         * @param icon Resource ID of a drawable that represents the action.
2652         * @param title Text describing the action.
2653         * @param intent PendingIntent to be fired when the action is invoked.
2654         */
2655        public Builder addAction(int icon, CharSequence title, PendingIntent intent) {
2656            mActions.add(new Action(icon, safeCharSequence(title), intent));
2657            return this;
2658        }
2659
2660        /**
2661         * Add an action to this notification. Actions are typically displayed by
2662         * the system as a button adjacent to the notification content.
2663         * <p>
2664         * Every action must have an icon (32dp square and matching the
2665         * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
2666         * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
2667         * <p>
2668         * A notification in its expanded form can display up to 3 actions, from left to right in
2669         * the order they were added. Actions will not be displayed when the notification is
2670         * collapsed, however, so be sure that any essential functions may be accessed by the user
2671         * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
2672         *
2673         * @param action The action to add.
2674         */
2675        public Builder addAction(Action action) {
2676            mActions.add(action);
2677            return this;
2678        }
2679
2680        /**
2681         * Add a rich notification style to be applied at build time.
2682         *
2683         * @param style Object responsible for modifying the notification style.
2684         */
2685        public Builder setStyle(Style style) {
2686            if (mStyle != style) {
2687                mStyle = style;
2688                if (mStyle != null) {
2689                    mStyle.setBuilder(this);
2690                }
2691            }
2692            return this;
2693        }
2694
2695        /**
2696         * Specify the value of {@link #visibility}.
2697         *
2698         * @param visibility One of {@link #VISIBILITY_PRIVATE} (the default),
2699         * {@link #VISIBILITY_SECRET}, or {@link #VISIBILITY_PUBLIC}.
2700         *
2701         * @return The same Builder.
2702         */
2703        public Builder setVisibility(int visibility) {
2704            mVisibility = visibility;
2705            return this;
2706        }
2707
2708        /**
2709         * Supply a replacement Notification whose contents should be shown in insecure contexts
2710         * (i.e. atop the secure lockscreen). See {@link #visibility} and {@link #VISIBILITY_PUBLIC}.
2711         * @param n A replacement notification, presumably with some or all info redacted.
2712         * @return The same Builder.
2713         */
2714        public Builder setPublicVersion(Notification n) {
2715            mPublicVersion = n;
2716            return this;
2717        }
2718
2719        /**
2720         * Apply an extender to this notification builder. Extenders may be used to add
2721         * metadata or change options on this builder.
2722         */
2723        public Builder extend(Extender extender) {
2724            extender.extend(this);
2725            return this;
2726        }
2727
2728        private void setFlag(int mask, boolean value) {
2729            if (value) {
2730                mFlags |= mask;
2731            } else {
2732                mFlags &= ~mask;
2733            }
2734        }
2735
2736        /**
2737         * Sets {@link Notification#color}.
2738         *
2739         * @param argb The accent color to use
2740         *
2741         * @return The same Builder.
2742         */
2743        public Builder setColor(@ColorInt int argb) {
2744            mColor = argb;
2745            return this;
2746        }
2747
2748        private Drawable getProfileBadgeDrawable() {
2749            // Note: This assumes that the current user can read the profile badge of the
2750            // originating user.
2751            return mContext.getPackageManager().getUserBadgeForDensity(
2752                    new UserHandle(mOriginatingUserId), 0);
2753        }
2754
2755        private Bitmap getProfileBadge() {
2756            Drawable badge = getProfileBadgeDrawable();
2757            if (badge == null) {
2758                return null;
2759            }
2760            final int size = mContext.getResources().getDimensionPixelSize(
2761                    R.dimen.notification_badge_size);
2762            Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
2763            Canvas canvas = new Canvas(bitmap);
2764            badge.setBounds(0, 0, size, size);
2765            badge.draw(canvas);
2766            return bitmap;
2767        }
2768
2769        private boolean addProfileBadge(RemoteViews contentView, int resId) {
2770            Bitmap profileBadge = getProfileBadge();
2771
2772            contentView.setViewVisibility(R.id.profile_badge_large_template, View.GONE);
2773            contentView.setViewVisibility(R.id.profile_badge_line2, View.GONE);
2774            contentView.setViewVisibility(R.id.profile_badge_line3, View.GONE);
2775
2776            if (profileBadge != null) {
2777                contentView.setImageViewBitmap(resId, profileBadge);
2778                contentView.setViewVisibility(resId, View.VISIBLE);
2779
2780                // Make sure Line 3 is visible. As badge will be here if there
2781                // is no text to display.
2782                if (resId == R.id.profile_badge_line3) {
2783                    contentView.setViewVisibility(R.id.line3, View.VISIBLE);
2784                }
2785                return true;
2786            }
2787            return false;
2788        }
2789
2790        private void shrinkLine3Text(RemoteViews contentView) {
2791            float subTextSize = mContext.getResources().getDimensionPixelSize(
2792                    R.dimen.notification_subtext_size);
2793            contentView.setTextViewTextSize(R.id.text, TypedValue.COMPLEX_UNIT_PX, subTextSize);
2794        }
2795
2796        private void unshrinkLine3Text(RemoteViews contentView) {
2797            float regularTextSize = mContext.getResources().getDimensionPixelSize(
2798                    com.android.internal.R.dimen.notification_text_size);
2799            contentView.setTextViewTextSize(R.id.text, TypedValue.COMPLEX_UNIT_PX, regularTextSize);
2800        }
2801
2802        private void resetStandardTemplate(RemoteViews contentView) {
2803            removeLargeIconBackground(contentView);
2804            contentView.setViewPadding(R.id.icon, 0, 0, 0, 0);
2805            contentView.setImageViewResource(R.id.icon, 0);
2806            contentView.setInt(R.id.icon, "setBackgroundResource", 0);
2807            contentView.setViewVisibility(R.id.right_icon, View.GONE);
2808            contentView.setInt(R.id.right_icon, "setBackgroundResource", 0);
2809            contentView.setImageViewResource(R.id.right_icon, 0);
2810            contentView.setImageViewResource(R.id.icon, 0);
2811            contentView.setTextViewText(R.id.title, null);
2812            contentView.setTextViewText(R.id.text, null);
2813            unshrinkLine3Text(contentView);
2814            contentView.setTextViewText(R.id.text2, null);
2815            contentView.setViewVisibility(R.id.text2, View.GONE);
2816            contentView.setViewVisibility(R.id.info, View.GONE);
2817            contentView.setViewVisibility(R.id.time, View.GONE);
2818            contentView.setViewVisibility(R.id.line3, View.GONE);
2819            contentView.setViewVisibility(R.id.overflow_divider, View.GONE);
2820            contentView.setViewVisibility(R.id.progress, View.GONE);
2821            contentView.setViewVisibility(R.id.chronometer, View.GONE);
2822            contentView.setViewVisibility(R.id.time, View.GONE);
2823        }
2824
2825        private RemoteViews applyStandardTemplate(int resId) {
2826            return applyStandardTemplate(resId, true /* hasProgress */);
2827        }
2828
2829        /**
2830         * @param hasProgress whether the progress bar should be shown and set
2831         */
2832        private RemoteViews applyStandardTemplate(int resId, boolean hasProgress) {
2833            RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
2834
2835            resetStandardTemplate(contentView);
2836
2837            boolean showLine3 = false;
2838            boolean showLine2 = false;
2839            boolean contentTextInLine2 = false;
2840
2841            if (mLargeIcon != null) {
2842                contentView.setImageViewBitmap(R.id.icon, mLargeIcon);
2843                processLargeLegacyIcon(mLargeIcon, contentView);
2844                contentView.setImageViewResource(R.id.right_icon, mSmallIcon);
2845                contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
2846                processSmallRightIcon(mSmallIcon, contentView);
2847            } else { // small icon at left
2848                contentView.setImageViewResource(R.id.icon, mSmallIcon);
2849                contentView.setViewVisibility(R.id.icon, View.VISIBLE);
2850                processSmallIconAsLarge(mSmallIcon, contentView);
2851            }
2852            if (mContentTitle != null) {
2853                contentView.setTextViewText(R.id.title, processLegacyText(mContentTitle));
2854            }
2855            if (mContentText != null) {
2856                contentView.setTextViewText(R.id.text, processLegacyText(mContentText));
2857                showLine3 = true;
2858            }
2859            if (mContentInfo != null) {
2860                contentView.setTextViewText(R.id.info, processLegacyText(mContentInfo));
2861                contentView.setViewVisibility(R.id.info, View.VISIBLE);
2862                showLine3 = true;
2863            } else if (mNumber > 0) {
2864                final int tooBig = mContext.getResources().getInteger(
2865                        R.integer.status_bar_notification_info_maxnum);
2866                if (mNumber > tooBig) {
2867                    contentView.setTextViewText(R.id.info, processLegacyText(
2868                            mContext.getResources().getString(
2869                                    R.string.status_bar_notification_info_overflow)));
2870                } else {
2871                    NumberFormat f = NumberFormat.getIntegerInstance();
2872                    contentView.setTextViewText(R.id.info, processLegacyText(f.format(mNumber)));
2873                }
2874                contentView.setViewVisibility(R.id.info, View.VISIBLE);
2875                showLine3 = true;
2876            } else {
2877                contentView.setViewVisibility(R.id.info, View.GONE);
2878            }
2879
2880            // Need to show three lines?
2881            if (mSubText != null) {
2882                contentView.setTextViewText(R.id.text, processLegacyText(mSubText));
2883                if (mContentText != null) {
2884                    contentView.setTextViewText(R.id.text2, processLegacyText(mContentText));
2885                    contentView.setViewVisibility(R.id.text2, View.VISIBLE);
2886                    showLine2 = true;
2887                    contentTextInLine2 = true;
2888                } else {
2889                    contentView.setViewVisibility(R.id.text2, View.GONE);
2890                }
2891            } else {
2892                contentView.setViewVisibility(R.id.text2, View.GONE);
2893                if (hasProgress && (mProgressMax != 0 || mProgressIndeterminate)) {
2894                    contentView.setViewVisibility(R.id.progress, View.VISIBLE);
2895                    contentView.setProgressBar(
2896                            R.id.progress, mProgressMax, mProgress, mProgressIndeterminate);
2897                    contentView.setProgressBackgroundTintList(
2898                            R.id.progress, ColorStateList.valueOf(mContext.getColor(
2899                                    R.color.notification_progress_background_color)));
2900                    if (mColor != COLOR_DEFAULT) {
2901                        ColorStateList colorStateList = ColorStateList.valueOf(mColor);
2902                        contentView.setProgressTintList(R.id.progress, colorStateList);
2903                        contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList);
2904                    }
2905                    showLine2 = true;
2906                } else {
2907                    contentView.setViewVisibility(R.id.progress, View.GONE);
2908                }
2909            }
2910            if (showLine2) {
2911
2912                // need to shrink all the type to make sure everything fits
2913                shrinkLine3Text(contentView);
2914            }
2915
2916            if (showsTimeOrChronometer()) {
2917                if (mUseChronometer) {
2918                    contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
2919                    contentView.setLong(R.id.chronometer, "setBase",
2920                            mWhen + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
2921                    contentView.setBoolean(R.id.chronometer, "setStarted", true);
2922                } else {
2923                    contentView.setViewVisibility(R.id.time, View.VISIBLE);
2924                    contentView.setLong(R.id.time, "setTime", mWhen);
2925                }
2926            }
2927
2928            // Adjust padding depending on line count and font size.
2929            contentView.setViewPadding(R.id.line1, 0, calculateTopPadding(mContext,
2930                    mHasThreeLines, mContext.getResources().getConfiguration().fontScale),
2931                    0, 0);
2932
2933            // We want to add badge to first line of text.
2934            boolean addedBadge = addProfileBadge(contentView,
2935                    contentTextInLine2 ? R.id.profile_badge_line2 : R.id.profile_badge_line3);
2936            // If we added the badge to line 3 then we should show line 3.
2937            if (addedBadge && !contentTextInLine2) {
2938                showLine3 = true;
2939            }
2940
2941            // Note getStandardView may hide line 3 again.
2942            contentView.setViewVisibility(R.id.line3, showLine3 ? View.VISIBLE : View.GONE);
2943            contentView.setViewVisibility(R.id.overflow_divider, showLine3 ? View.VISIBLE : View.GONE);
2944            return contentView;
2945        }
2946
2947        /**
2948         * @return true if the built notification will show the time or the chronometer; false
2949         *         otherwise
2950         */
2951        private boolean showsTimeOrChronometer() {
2952            return mWhen != 0 && mShowWhen;
2953        }
2954
2955        /**
2956         * Logic to find out whether the notification is going to have three lines in the contracted
2957         * layout. This is used to adjust the top padding.
2958         *
2959         * @return true if the notification is going to have three lines; false if the notification
2960         *         is going to have one or two lines
2961         */
2962        private boolean hasThreeLines() {
2963            boolean contentTextInLine2 = mSubText != null && mContentText != null;
2964            boolean hasProgress = mStyle == null || mStyle.hasProgress();
2965            // If we have content text in line 2, badge goes into line 2, or line 3 otherwise
2966            boolean badgeInLine3 = getProfileBadgeDrawable() != null && !contentTextInLine2;
2967            boolean hasLine3 = mContentText != null || mContentInfo != null || mNumber > 0
2968                    || badgeInLine3;
2969            boolean hasLine2 = (mSubText != null && mContentText != null) ||
2970                    (hasProgress && mSubText == null
2971                            && (mProgressMax != 0 || mProgressIndeterminate));
2972            return hasLine2 && hasLine3;
2973        }
2974
2975        /**
2976         * @hide
2977         */
2978        public static int calculateTopPadding(Context ctx, boolean hasThreeLines,
2979                float fontScale) {
2980            int padding = ctx.getResources().getDimensionPixelSize(hasThreeLines
2981                    ? R.dimen.notification_top_pad_narrow
2982                    : R.dimen.notification_top_pad);
2983            int largePadding = ctx.getResources().getDimensionPixelSize(hasThreeLines
2984                    ? R.dimen.notification_top_pad_large_text_narrow
2985                    : R.dimen.notification_top_pad_large_text);
2986            float largeFactor = (MathUtils.constrain(fontScale, 1.0f, LARGE_TEXT_SCALE) - 1f)
2987                    / (LARGE_TEXT_SCALE - 1f);
2988
2989            // Linearly interpolate the padding between large and normal with the font scale ranging
2990            // from 1f to LARGE_TEXT_SCALE
2991            return Math.round((1 - largeFactor) * padding + largeFactor * largePadding);
2992        }
2993
2994        private void resetStandardTemplateWithActions(RemoteViews big) {
2995            big.setViewVisibility(R.id.actions, View.GONE);
2996            big.setViewVisibility(R.id.action_divider, View.GONE);
2997            big.removeAllViews(R.id.actions);
2998        }
2999
3000        private RemoteViews applyStandardTemplateWithActions(int layoutId) {
3001            RemoteViews big = applyStandardTemplate(layoutId);
3002
3003            resetStandardTemplateWithActions(big);
3004
3005            int N = mActions.size();
3006            if (N > 0) {
3007                big.setViewVisibility(R.id.actions, View.VISIBLE);
3008                big.setViewVisibility(R.id.action_divider, View.VISIBLE);
3009                if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS;
3010                for (int i=0; i<N; i++) {
3011                    final RemoteViews button = generateActionButton(mActions.get(i));
3012                    big.addView(R.id.actions, button);
3013                }
3014            }
3015            return big;
3016        }
3017
3018        private RemoteViews makeContentView() {
3019            if (mContentView != null) {
3020                return mContentView;
3021            } else {
3022                return applyStandardTemplate(getBaseLayoutResource());
3023            }
3024        }
3025
3026        private RemoteViews makeTickerView() {
3027            if (mTickerView != null) {
3028                return mTickerView;
3029            }
3030            return null; // tickers are not created by default anymore
3031        }
3032
3033        private RemoteViews makeBigContentView() {
3034            if (mActions.size() == 0) return null;
3035
3036            return applyStandardTemplateWithActions(getBigBaseLayoutResource());
3037        }
3038
3039        private RemoteViews makeHeadsUpContentView() {
3040            if (mActions.size() == 0) return null;
3041
3042            return applyStandardTemplateWithActions(getBigBaseLayoutResource());
3043        }
3044
3045
3046        private RemoteViews generateActionButton(Action action) {
3047            final boolean tombstone = (action.actionIntent == null);
3048            RemoteViews button = new RemoteViews(mContext.getPackageName(),
3049                    tombstone ? getActionTombstoneLayoutResource()
3050                              : getActionLayoutResource());
3051            button.setTextViewCompoundDrawablesRelative(R.id.action0, action.icon, 0, 0, 0);
3052            button.setTextViewText(R.id.action0, processLegacyText(action.title));
3053            if (!tombstone) {
3054                button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
3055            }
3056            button.setContentDescription(R.id.action0, action.title);
3057            processLegacyAction(action, button);
3058            return button;
3059        }
3060
3061        /**
3062         * @return Whether we are currently building a notification from a legacy (an app that
3063         *         doesn't create material notifications by itself) app.
3064         */
3065        private boolean isLegacy() {
3066            return mColorUtil != null;
3067        }
3068
3069        private void processLegacyAction(Action action, RemoteViews button) {
3070            if (!isLegacy() || mColorUtil.isGrayscaleIcon(mContext, action.icon)) {
3071                button.setTextViewCompoundDrawablesRelativeColorFilter(R.id.action0, 0,
3072                        mContext.getColor(R.color.notification_action_color_filter),
3073                        PorterDuff.Mode.MULTIPLY);
3074            }
3075        }
3076
3077        private CharSequence processLegacyText(CharSequence charSequence) {
3078            if (isLegacy()) {
3079                return mColorUtil.invertCharSequenceColors(charSequence);
3080            } else {
3081                return charSequence;
3082            }
3083        }
3084
3085        /**
3086         * Apply any necessary background to smallIcons being used in the largeIcon spot.
3087         */
3088        private void processSmallIconAsLarge(int largeIconId, RemoteViews contentView) {
3089            if (!isLegacy()) {
3090                contentView.setDrawableParameters(R.id.icon, false, -1,
3091                        0xFFFFFFFF,
3092                        PorterDuff.Mode.SRC_ATOP, -1);
3093            }
3094            if (!isLegacy() || mColorUtil.isGrayscaleIcon(mContext, largeIconId)) {
3095                applyLargeIconBackground(contentView);
3096            }
3097        }
3098
3099        /**
3100         * Apply any necessary background to a largeIcon if it's a fake smallIcon (that is,
3101         * if it's grayscale).
3102         */
3103        // TODO: also check bounds, transparency, that sort of thing.
3104        private void processLargeLegacyIcon(Bitmap largeIcon, RemoteViews contentView) {
3105            if (isLegacy() && mColorUtil.isGrayscaleIcon(largeIcon)) {
3106                applyLargeIconBackground(contentView);
3107            } else {
3108                removeLargeIconBackground(contentView);
3109            }
3110        }
3111
3112        /**
3113         * Add a colored circle behind the largeIcon slot.
3114         */
3115        private void applyLargeIconBackground(RemoteViews contentView) {
3116            contentView.setInt(R.id.icon, "setBackgroundResource",
3117                    R.drawable.notification_icon_legacy_bg);
3118
3119            contentView.setDrawableParameters(
3120                    R.id.icon,
3121                    true,
3122                    -1,
3123                    resolveColor(),
3124                    PorterDuff.Mode.SRC_ATOP,
3125                    -1);
3126
3127            int padding = mContext.getResources().getDimensionPixelSize(
3128                    R.dimen.notification_large_icon_circle_padding);
3129            contentView.setViewPadding(R.id.icon, padding, padding, padding, padding);
3130        }
3131
3132        private void removeLargeIconBackground(RemoteViews contentView) {
3133            contentView.setInt(R.id.icon, "setBackgroundResource", 0);
3134        }
3135
3136        /**
3137         * Recolor small icons when used in the R.id.right_icon slot.
3138         */
3139        private void processSmallRightIcon(int smallIconDrawableId,
3140                RemoteViews contentView) {
3141            if (!isLegacy()) {
3142                contentView.setDrawableParameters(R.id.right_icon, false, -1,
3143                        0xFFFFFFFF,
3144                        PorterDuff.Mode.SRC_ATOP, -1);
3145            }
3146            if (!isLegacy() || mColorUtil.isGrayscaleIcon(mContext, smallIconDrawableId)) {
3147                contentView.setInt(R.id.right_icon,
3148                        "setBackgroundResource",
3149                        R.drawable.notification_icon_legacy_bg);
3150
3151                contentView.setDrawableParameters(
3152                        R.id.right_icon,
3153                        true,
3154                        -1,
3155                        resolveColor(),
3156                        PorterDuff.Mode.SRC_ATOP,
3157                        -1);
3158            }
3159        }
3160
3161        private int sanitizeColor() {
3162            if (mColor != COLOR_DEFAULT) {
3163                mColor |= 0xFF000000; // no alpha for custom colors
3164            }
3165            return mColor;
3166        }
3167
3168        private int resolveColor() {
3169            if (mColor == COLOR_DEFAULT) {
3170                return mContext.getColor(R.color.notification_icon_bg_color);
3171            }
3172            return mColor;
3173        }
3174
3175        /**
3176         * Apply the unstyled operations and return a new {@link Notification} object.
3177         * @hide
3178         */
3179        public Notification buildUnstyled() {
3180            Notification n = new Notification();
3181            n.when = mWhen;
3182            n.icon = mSmallIcon;
3183            n.iconLevel = mSmallIconLevel;
3184            n.number = mNumber;
3185
3186            n.color = sanitizeColor();
3187
3188            setBuilderContentView(n, makeContentView());
3189            n.contentIntent = mContentIntent;
3190            n.deleteIntent = mDeleteIntent;
3191            n.fullScreenIntent = mFullScreenIntent;
3192            n.tickerText = mTickerText;
3193            n.tickerView = makeTickerView();
3194            n.largeIcon = mLargeIcon;
3195            n.sound = mSound;
3196            n.audioStreamType = mAudioStreamType;
3197            n.audioAttributes = mAudioAttributes;
3198            n.vibrate = mVibrate;
3199            n.ledARGB = mLedArgb;
3200            n.ledOnMS = mLedOnMs;
3201            n.ledOffMS = mLedOffMs;
3202            n.defaults = mDefaults;
3203            n.flags = mFlags;
3204            setBuilderBigContentView(n, makeBigContentView());
3205            setBuilderHeadsUpContentView(n, makeHeadsUpContentView());
3206            if (mLedOnMs != 0 || mLedOffMs != 0) {
3207                n.flags |= FLAG_SHOW_LIGHTS;
3208            }
3209            if ((mDefaults & DEFAULT_LIGHTS) != 0) {
3210                n.flags |= FLAG_SHOW_LIGHTS;
3211            }
3212            n.category = mCategory;
3213            n.mGroupKey = mGroupKey;
3214            n.mSortKey = mSortKey;
3215            n.priority = mPriority;
3216            if (mActions.size() > 0) {
3217                n.actions = new Action[mActions.size()];
3218                mActions.toArray(n.actions);
3219            }
3220            n.visibility = mVisibility;
3221
3222            if (mPublicVersion != null) {
3223                n.publicVersion = new Notification();
3224                mPublicVersion.cloneInto(n.publicVersion, true);
3225            }
3226            // Note: If you're adding new fields, also update restoreFromNotitification().
3227            return n;
3228        }
3229
3230        /**
3231         * Capture, in the provided bundle, semantic information used in the construction of
3232         * this Notification object.
3233         * @hide
3234         */
3235        public void populateExtras(Bundle extras) {
3236            // Store original information used in the construction of this object
3237            extras.putInt(EXTRA_ORIGINATING_USERID, mOriginatingUserId);
3238            extras.putParcelable(EXTRA_REBUILD_CONTEXT_APPLICATION_INFO,
3239                    mContext.getApplicationInfo());
3240            extras.putCharSequence(EXTRA_TITLE, mContentTitle);
3241            extras.putCharSequence(EXTRA_TEXT, mContentText);
3242            extras.putCharSequence(EXTRA_SUB_TEXT, mSubText);
3243            extras.putCharSequence(EXTRA_INFO_TEXT, mContentInfo);
3244            extras.putInt(EXTRA_SMALL_ICON, mSmallIcon);
3245            extras.putInt(EXTRA_PROGRESS, mProgress);
3246            extras.putInt(EXTRA_PROGRESS_MAX, mProgressMax);
3247            extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, mProgressIndeterminate);
3248            extras.putBoolean(EXTRA_SHOW_CHRONOMETER, mUseChronometer);
3249            extras.putBoolean(EXTRA_SHOW_WHEN, mShowWhen);
3250            if (mLargeIcon != null) {
3251                extras.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
3252            }
3253            if (!mPeople.isEmpty()) {
3254                extras.putStringArray(EXTRA_PEOPLE, mPeople.toArray(new String[mPeople.size()]));
3255            }
3256            // NOTE: If you're adding new extras also update restoreFromNotification().
3257        }
3258
3259
3260        /**
3261         * @hide
3262         */
3263        public static void stripForDelivery(Notification n) {
3264            if (!STRIP_AND_REBUILD) {
3265                return;
3266            }
3267
3268            String templateClass = n.extras.getString(EXTRA_TEMPLATE);
3269            // Only strip views for known Styles because we won't know how to
3270            // re-create them otherwise.
3271            boolean stripViews = TextUtils.isEmpty(templateClass) ||
3272                    getNotificationStyleClass(templateClass) != null;
3273
3274            boolean isStripped = false;
3275
3276            if (n.largeIcon != null && n.extras.containsKey(EXTRA_LARGE_ICON)) {
3277                // TODO: Would like to check for equality here, but if the notification
3278                // has been cloned, we can't.
3279                n.largeIcon = null;
3280                n.extras.putBoolean(EXTRA_REBUILD_LARGE_ICON, true);
3281                isStripped = true;
3282            }
3283            // Get rid of unmodified BuilderRemoteViews.
3284
3285            if (stripViews &&
3286                    n.contentView instanceof BuilderRemoteViews &&
3287                    n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) ==
3288                            n.contentView.getSequenceNumber()) {
3289                n.contentView = null;
3290                n.extras.putBoolean(EXTRA_REBUILD_CONTENT_VIEW, true);
3291                n.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT);
3292                isStripped = true;
3293            }
3294            if (stripViews &&
3295                    n.bigContentView instanceof BuilderRemoteViews &&
3296                    n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) ==
3297                            n.bigContentView.getSequenceNumber()) {
3298                n.bigContentView = null;
3299                n.extras.putBoolean(EXTRA_REBUILD_BIG_CONTENT_VIEW, true);
3300                n.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT);
3301                isStripped = true;
3302            }
3303            if (stripViews &&
3304                    n.headsUpContentView instanceof BuilderRemoteViews &&
3305                    n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) ==
3306                            n.headsUpContentView.getSequenceNumber()) {
3307                n.headsUpContentView = null;
3308                n.extras.putBoolean(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW, true);
3309                n.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT);
3310                isStripped = true;
3311            }
3312
3313            if (isStripped) {
3314                n.extras.putBoolean(EXTRA_NEEDS_REBUILD, true);
3315            }
3316        }
3317
3318        /**
3319         * @hide
3320         */
3321        public static Notification rebuild(Context context, Notification n) {
3322            Bundle extras = n.extras;
3323            if (!extras.getBoolean(EXTRA_NEEDS_REBUILD)) return n;
3324            extras.remove(EXTRA_NEEDS_REBUILD);
3325
3326            // Re-create notification context so we can access app resources.
3327            ApplicationInfo applicationInfo = extras.getParcelable(
3328                    EXTRA_REBUILD_CONTEXT_APPLICATION_INFO);
3329            Context builderContext;
3330            try {
3331                builderContext = context.createApplicationContext(applicationInfo,
3332                        Context.CONTEXT_RESTRICTED);
3333            } catch (NameNotFoundException e) {
3334                Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found");
3335                builderContext = context;  // try with our context
3336            }
3337
3338            Builder b = new Builder(builderContext, n);
3339            return b.rebuild();
3340        }
3341
3342        /**
3343         * Rebuilds the notification passed in to the rebuild-constructor
3344         * {@link #Builder(Context, Notification)}.
3345         *
3346         * <p>
3347         * Throws IllegalStateException when invoked on a Builder that isn't in rebuild mode.
3348         *
3349         * @hide
3350         */
3351        private Notification rebuild() {
3352            if (mRebuildNotification == null) {
3353                throw new IllegalStateException("rebuild() only valid when in 'rebuild' mode.");
3354            }
3355            mHasThreeLines = hasThreeLines();
3356
3357            Bundle extras = mRebuildNotification.extras;
3358
3359            if (extras.getBoolean(EXTRA_REBUILD_LARGE_ICON)) {
3360                mRebuildNotification.largeIcon = extras.getParcelable(EXTRA_LARGE_ICON);
3361            }
3362            extras.remove(EXTRA_REBUILD_LARGE_ICON);
3363
3364            if (extras.getBoolean(EXTRA_REBUILD_CONTENT_VIEW)) {
3365                setBuilderContentView(mRebuildNotification, makeContentView());
3366                if (mStyle != null) {
3367                    mStyle.populateContentView(mRebuildNotification);
3368                }
3369            }
3370            extras.remove(EXTRA_REBUILD_CONTENT_VIEW);
3371
3372            if (extras.getBoolean(EXTRA_REBUILD_BIG_CONTENT_VIEW)) {
3373                setBuilderBigContentView(mRebuildNotification, makeBigContentView());
3374                if (mStyle != null) {
3375                    mStyle.populateBigContentView(mRebuildNotification);
3376                }
3377            }
3378            extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW);
3379
3380            if (extras.getBoolean(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW)) {
3381                setBuilderHeadsUpContentView(mRebuildNotification, makeHeadsUpContentView());
3382                if (mStyle != null) {
3383                    mStyle.populateHeadsUpContentView(mRebuildNotification);
3384                }
3385            }
3386            extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW);
3387
3388            mHasThreeLines = false;
3389            return mRebuildNotification;
3390        }
3391
3392        private static Class<? extends Style> getNotificationStyleClass(String templateClass) {
3393            Class<? extends Style>[] classes = new Class[]{
3394                    BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class};
3395            for (Class<? extends Style> innerClass : classes) {
3396                if (templateClass.equals(innerClass.getName())) {
3397                    return innerClass;
3398                }
3399            }
3400            return null;
3401        }
3402
3403        private void setBuilderContentView(Notification n, RemoteViews contentView) {
3404            n.contentView = contentView;
3405            if (contentView instanceof BuilderRemoteViews) {
3406                mRebuildBundle.putInt(Builder.EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT,
3407                        contentView.getSequenceNumber());
3408            }
3409        }
3410
3411        private void setBuilderBigContentView(Notification n, RemoteViews bigContentView) {
3412            n.bigContentView = bigContentView;
3413            if (bigContentView instanceof BuilderRemoteViews) {
3414                mRebuildBundle.putInt(Builder.EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT,
3415                        bigContentView.getSequenceNumber());
3416            }
3417        }
3418
3419        private void setBuilderHeadsUpContentView(Notification n,
3420                RemoteViews headsUpContentView) {
3421            n.headsUpContentView = headsUpContentView;
3422            if (headsUpContentView instanceof BuilderRemoteViews) {
3423                mRebuildBundle.putInt(Builder.EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT,
3424                        headsUpContentView.getSequenceNumber());
3425            }
3426        }
3427
3428        private void restoreFromNotification(Notification n) {
3429
3430            // Notification fields.
3431            mWhen = n.when;
3432            mSmallIcon = n.icon;
3433            mSmallIconLevel = n.iconLevel;
3434            mNumber = n.number;
3435
3436            mColor = n.color;
3437
3438            mContentView = n.contentView;
3439            mDeleteIntent = n.deleteIntent;
3440            mFullScreenIntent = n.fullScreenIntent;
3441            mTickerText = n.tickerText;
3442            mTickerView = n.tickerView;
3443            mLargeIcon = n.largeIcon;
3444            mSound = n.sound;
3445            mAudioStreamType = n.audioStreamType;
3446            mAudioAttributes = n.audioAttributes;
3447
3448            mVibrate = n.vibrate;
3449            mLedArgb = n.ledARGB;
3450            mLedOnMs = n.ledOnMS;
3451            mLedOffMs = n.ledOffMS;
3452            mDefaults = n.defaults;
3453            mFlags = n.flags;
3454
3455            mCategory = n.category;
3456            mGroupKey = n.mGroupKey;
3457            mSortKey = n.mSortKey;
3458            mPriority = n.priority;
3459            mActions.clear();
3460            if (n.actions != null) {
3461                Collections.addAll(mActions, n.actions);
3462            }
3463            mVisibility = n.visibility;
3464
3465            mPublicVersion = n.publicVersion;
3466
3467            // Extras.
3468            Bundle extras = n.extras;
3469            mOriginatingUserId = extras.getInt(EXTRA_ORIGINATING_USERID);
3470            mContentTitle = extras.getCharSequence(EXTRA_TITLE);
3471            mContentText = extras.getCharSequence(EXTRA_TEXT);
3472            mSubText = extras.getCharSequence(EXTRA_SUB_TEXT);
3473            mContentInfo = extras.getCharSequence(EXTRA_INFO_TEXT);
3474            mSmallIcon = extras.getInt(EXTRA_SMALL_ICON);
3475            mProgress = extras.getInt(EXTRA_PROGRESS);
3476            mProgressMax = extras.getInt(EXTRA_PROGRESS_MAX);
3477            mProgressIndeterminate = extras.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
3478            mUseChronometer = extras.getBoolean(EXTRA_SHOW_CHRONOMETER);
3479            mShowWhen = extras.getBoolean(EXTRA_SHOW_WHEN);
3480            if (extras.containsKey(EXTRA_LARGE_ICON)) {
3481                mLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON);
3482            }
3483            if (extras.containsKey(EXTRA_PEOPLE)) {
3484                mPeople.clear();
3485                Collections.addAll(mPeople, extras.getStringArray(EXTRA_PEOPLE));
3486            }
3487        }
3488
3489        /**
3490         * @deprecated Use {@link #build()} instead.
3491         */
3492        @Deprecated
3493        public Notification getNotification() {
3494            return build();
3495        }
3496
3497        /**
3498         * Combine all of the options that have been set and return a new {@link Notification}
3499         * object.
3500         */
3501        public Notification build() {
3502            mOriginatingUserId = mContext.getUserId();
3503            mHasThreeLines = hasThreeLines();
3504
3505            Notification n = buildUnstyled();
3506
3507            if (mStyle != null) {
3508                n = mStyle.buildStyled(n);
3509            }
3510
3511            if (mExtras != null) {
3512                n.extras.putAll(mExtras);
3513            }
3514
3515            if (mRebuildBundle.size() > 0) {
3516                n.extras.putAll(mRebuildBundle);
3517                mRebuildBundle.clear();
3518            }
3519
3520            populateExtras(n.extras);
3521            if (mStyle != null) {
3522                mStyle.addExtras(n.extras);
3523            }
3524
3525            mHasThreeLines = false;
3526            return n;
3527        }
3528
3529        /**
3530         * Apply this Builder to an existing {@link Notification} object.
3531         *
3532         * @hide
3533         */
3534        public Notification buildInto(Notification n) {
3535            build().cloneInto(n, true);
3536            return n;
3537        }
3538
3539        private int getBaseLayoutResource() {
3540            return R.layout.notification_template_material_base;
3541        }
3542
3543        private int getBigBaseLayoutResource() {
3544            return R.layout.notification_template_material_big_base;
3545        }
3546
3547        private int getBigPictureLayoutResource() {
3548            return R.layout.notification_template_material_big_picture;
3549        }
3550
3551        private int getBigTextLayoutResource() {
3552            return R.layout.notification_template_material_big_text;
3553        }
3554
3555        private int getInboxLayoutResource() {
3556            return R.layout.notification_template_material_inbox;
3557        }
3558
3559        private int getActionLayoutResource() {
3560            return R.layout.notification_material_action;
3561        }
3562
3563        private int getActionTombstoneLayoutResource() {
3564            return R.layout.notification_material_action_tombstone;
3565        }
3566    }
3567
3568    /**
3569     * An object that can apply a rich notification style to a {@link Notification.Builder}
3570     * object.
3571     */
3572    public static abstract class Style {
3573        private CharSequence mBigContentTitle;
3574
3575        /**
3576         * @hide
3577         */
3578        protected CharSequence mSummaryText = null;
3579
3580        /**
3581         * @hide
3582         */
3583        protected boolean mSummaryTextSet = false;
3584
3585        protected Builder mBuilder;
3586
3587        /**
3588         * Overrides ContentTitle in the big form of the template.
3589         * This defaults to the value passed to setContentTitle().
3590         */
3591        protected void internalSetBigContentTitle(CharSequence title) {
3592            mBigContentTitle = title;
3593        }
3594
3595        /**
3596         * Set the first line of text after the detail section in the big form of the template.
3597         */
3598        protected void internalSetSummaryText(CharSequence cs) {
3599            mSummaryText = cs;
3600            mSummaryTextSet = true;
3601        }
3602
3603        public void setBuilder(Builder builder) {
3604            if (mBuilder != builder) {
3605                mBuilder = builder;
3606                if (mBuilder != null) {
3607                    mBuilder.setStyle(this);
3608                }
3609            }
3610        }
3611
3612        protected void checkBuilder() {
3613            if (mBuilder == null) {
3614                throw new IllegalArgumentException("Style requires a valid Builder object");
3615            }
3616        }
3617
3618        protected RemoteViews getStandardView(int layoutId) {
3619            checkBuilder();
3620
3621            // Nasty.
3622            CharSequence oldBuilderContentTitle = mBuilder.mContentTitle;
3623            if (mBigContentTitle != null) {
3624                mBuilder.setContentTitle(mBigContentTitle);
3625            }
3626
3627            RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(layoutId);
3628
3629            mBuilder.mContentTitle = oldBuilderContentTitle;
3630
3631            if (mBigContentTitle != null && mBigContentTitle.equals("")) {
3632                contentView.setViewVisibility(R.id.line1, View.GONE);
3633            } else {
3634                contentView.setViewVisibility(R.id.line1, View.VISIBLE);
3635            }
3636
3637            // The last line defaults to the subtext, but can be replaced by mSummaryText
3638            final CharSequence overflowText =
3639                    mSummaryTextSet ? mSummaryText
3640                                    : mBuilder.mSubText;
3641            if (overflowText != null) {
3642                contentView.setTextViewText(R.id.text, mBuilder.processLegacyText(overflowText));
3643                contentView.setViewVisibility(R.id.overflow_divider, View.VISIBLE);
3644                contentView.setViewVisibility(R.id.line3, View.VISIBLE);
3645            } else {
3646                // Clear text in case we use the line to show the profile badge.
3647                contentView.setTextViewText(R.id.text, "");
3648                contentView.setViewVisibility(R.id.overflow_divider, View.GONE);
3649                contentView.setViewVisibility(R.id.line3, View.GONE);
3650            }
3651
3652            return contentView;
3653        }
3654
3655        /**
3656         * Changes the padding of the first line such that the big and small content view have the
3657         * same top padding.
3658         *
3659         * @hide
3660         */
3661        protected void applyTopPadding(RemoteViews contentView) {
3662            int topPadding = Builder.calculateTopPadding(mBuilder.mContext,
3663                    mBuilder.mHasThreeLines,
3664                    mBuilder.mContext.getResources().getConfiguration().fontScale);
3665            contentView.setViewPadding(R.id.line1, 0, topPadding, 0, 0);
3666        }
3667
3668        /**
3669         * @hide
3670         */
3671        public void addExtras(Bundle extras) {
3672            if (mSummaryTextSet) {
3673                extras.putCharSequence(EXTRA_SUMMARY_TEXT, mSummaryText);
3674            }
3675            if (mBigContentTitle != null) {
3676                extras.putCharSequence(EXTRA_TITLE_BIG, mBigContentTitle);
3677            }
3678            extras.putString(EXTRA_TEMPLATE, this.getClass().getName());
3679        }
3680
3681        /**
3682         * @hide
3683         */
3684        protected void restoreFromExtras(Bundle extras) {
3685            if (extras.containsKey(EXTRA_SUMMARY_TEXT)) {
3686                mSummaryText = extras.getCharSequence(EXTRA_SUMMARY_TEXT);
3687                mSummaryTextSet = true;
3688            }
3689            if (extras.containsKey(EXTRA_TITLE_BIG)) {
3690                mBigContentTitle = extras.getCharSequence(EXTRA_TITLE_BIG);
3691            }
3692        }
3693
3694
3695        /**
3696         * @hide
3697         */
3698        public Notification buildStyled(Notification wip) {
3699            populateTickerView(wip);
3700            populateContentView(wip);
3701            populateBigContentView(wip);
3702            populateHeadsUpContentView(wip);
3703            return wip;
3704        }
3705
3706        // The following methods are split out so we can re-create notification partially.
3707        /**
3708         * @hide
3709         */
3710        protected void populateTickerView(Notification wip) {}
3711        /**
3712         * @hide
3713         */
3714        protected void populateContentView(Notification wip) {}
3715
3716        /**
3717         * @hide
3718         */
3719        protected void populateBigContentView(Notification wip) {}
3720
3721        /**
3722         * @hide
3723         */
3724        protected void populateHeadsUpContentView(Notification wip) {}
3725
3726        /**
3727         * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is
3728         * attached to.
3729         *
3730         * @return the fully constructed Notification.
3731         */
3732        public Notification build() {
3733            checkBuilder();
3734            return mBuilder.build();
3735        }
3736
3737        /**
3738         * @hide
3739         * @return true if the style positions the progress bar on the second line; false if the
3740         *         style hides the progress bar
3741         */
3742        protected boolean hasProgress() {
3743            return true;
3744        }
3745    }
3746
3747    /**
3748     * Helper class for generating large-format notifications that include a large image attachment.
3749     *
3750     * Here's how you'd set the <code>BigPictureStyle</code> on a notification:
3751     * <pre class="prettyprint">
3752     * Notification notif = new Notification.Builder(mContext)
3753     *     .setContentTitle(&quot;New photo from &quot; + sender.toString())
3754     *     .setContentText(subject)
3755     *     .setSmallIcon(R.drawable.new_post)
3756     *     .setLargeIcon(aBitmap)
3757     *     .setStyle(new Notification.BigPictureStyle()
3758     *         .bigPicture(aBigBitmap))
3759     *     .build();
3760     * </pre>
3761     *
3762     * @see Notification#bigContentView
3763     */
3764    public static class BigPictureStyle extends Style {
3765        private Bitmap mPicture;
3766        private Bitmap mBigLargeIcon;
3767        private boolean mBigLargeIconSet = false;
3768
3769        public BigPictureStyle() {
3770        }
3771
3772        public BigPictureStyle(Builder builder) {
3773            setBuilder(builder);
3774        }
3775
3776        /**
3777         * Overrides ContentTitle in the big form of the template.
3778         * This defaults to the value passed to setContentTitle().
3779         */
3780        public BigPictureStyle setBigContentTitle(CharSequence title) {
3781            internalSetBigContentTitle(safeCharSequence(title));
3782            return this;
3783        }
3784
3785        /**
3786         * Set the first line of text after the detail section in the big form of the template.
3787         */
3788        public BigPictureStyle setSummaryText(CharSequence cs) {
3789            internalSetSummaryText(safeCharSequence(cs));
3790            return this;
3791        }
3792
3793        /**
3794         * Provide the bitmap to be used as the payload for the BigPicture notification.
3795         */
3796        public BigPictureStyle bigPicture(Bitmap b) {
3797            mPicture = b;
3798            return this;
3799        }
3800
3801        /**
3802         * Override the large icon when the big notification is shown.
3803         */
3804        public BigPictureStyle bigLargeIcon(Bitmap b) {
3805            mBigLargeIconSet = true;
3806            mBigLargeIcon = b;
3807            return this;
3808        }
3809
3810        private RemoteViews makeBigContentView() {
3811
3812            // Replace mLargeIcon with mBigLargeIcon if mBigLargeIconSet
3813            // This covers the following cases:
3814            //   1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides
3815            //          mLargeIcon
3816            //   2. !mBigLargeIconSet -> mLargeIcon applies
3817            Bitmap oldLargeIcon = null;
3818            if (mBigLargeIconSet) {
3819                oldLargeIcon = mBuilder.mLargeIcon;
3820                mBuilder.mLargeIcon = mBigLargeIcon;
3821            }
3822
3823            RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource());
3824
3825            if (mBigLargeIconSet) {
3826                mBuilder.mLargeIcon = oldLargeIcon;
3827            }
3828
3829            contentView.setImageViewBitmap(R.id.big_picture, mPicture);
3830
3831            applyTopPadding(contentView);
3832
3833            boolean twoTextLines = mBuilder.mSubText != null && mBuilder.mContentText != null;
3834            mBuilder.addProfileBadge(contentView,
3835                    twoTextLines ? R.id.profile_badge_line2 : R.id.profile_badge_line3);
3836            return contentView;
3837        }
3838
3839        /**
3840         * @hide
3841         */
3842        public void addExtras(Bundle extras) {
3843            super.addExtras(extras);
3844
3845            if (mBigLargeIconSet) {
3846                extras.putParcelable(EXTRA_LARGE_ICON_BIG, mBigLargeIcon);
3847            }
3848            extras.putParcelable(EXTRA_PICTURE, mPicture);
3849        }
3850
3851        /**
3852         * @hide
3853         */
3854        @Override
3855        protected void restoreFromExtras(Bundle extras) {
3856            super.restoreFromExtras(extras);
3857
3858            if (extras.containsKey(EXTRA_LARGE_ICON_BIG)) {
3859                mBigLargeIconSet = true;
3860                mBigLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON_BIG);
3861            }
3862            mPicture = extras.getParcelable(EXTRA_PICTURE);
3863        }
3864
3865        /**
3866         * @hide
3867         */
3868        @Override
3869        public void populateBigContentView(Notification wip) {
3870            mBuilder.setBuilderBigContentView(wip, makeBigContentView());
3871        }
3872    }
3873
3874    /**
3875     * Helper class for generating large-format notifications that include a lot of text.
3876     *
3877     * Here's how you'd set the <code>BigTextStyle</code> on a notification:
3878     * <pre class="prettyprint">
3879     * Notification notif = new Notification.Builder(mContext)
3880     *     .setContentTitle(&quot;New mail from &quot; + sender.toString())
3881     *     .setContentText(subject)
3882     *     .setSmallIcon(R.drawable.new_mail)
3883     *     .setLargeIcon(aBitmap)
3884     *     .setStyle(new Notification.BigTextStyle()
3885     *         .bigText(aVeryLongString))
3886     *     .build();
3887     * </pre>
3888     *
3889     * @see Notification#bigContentView
3890     */
3891    public static class BigTextStyle extends Style {
3892
3893        private static final int MAX_LINES = 13;
3894        private static final int LINES_CONSUMED_BY_ACTIONS = 3;
3895        private static final int LINES_CONSUMED_BY_SUMMARY = 2;
3896
3897        private CharSequence mBigText;
3898
3899        public BigTextStyle() {
3900        }
3901
3902        public BigTextStyle(Builder builder) {
3903            setBuilder(builder);
3904        }
3905
3906        /**
3907         * Overrides ContentTitle in the big form of the template.
3908         * This defaults to the value passed to setContentTitle().
3909         */
3910        public BigTextStyle setBigContentTitle(CharSequence title) {
3911            internalSetBigContentTitle(safeCharSequence(title));
3912            return this;
3913        }
3914
3915        /**
3916         * Set the first line of text after the detail section in the big form of the template.
3917         */
3918        public BigTextStyle setSummaryText(CharSequence cs) {
3919            internalSetSummaryText(safeCharSequence(cs));
3920            return this;
3921        }
3922
3923        /**
3924         * Provide the longer text to be displayed in the big form of the
3925         * template in place of the content text.
3926         */
3927        public BigTextStyle bigText(CharSequence cs) {
3928            mBigText = safeCharSequence(cs);
3929            return this;
3930        }
3931
3932        /**
3933         * @hide
3934         */
3935        public void addExtras(Bundle extras) {
3936            super.addExtras(extras);
3937
3938            extras.putCharSequence(EXTRA_BIG_TEXT, mBigText);
3939        }
3940
3941        /**
3942         * @hide
3943         */
3944        @Override
3945        protected void restoreFromExtras(Bundle extras) {
3946            super.restoreFromExtras(extras);
3947
3948            mBigText = extras.getCharSequence(EXTRA_BIG_TEXT);
3949        }
3950
3951        private RemoteViews makeBigContentView() {
3952
3953            // Nasty
3954            CharSequence oldBuilderContentText = mBuilder.mContentText;
3955            mBuilder.mContentText = null;
3956
3957            RemoteViews contentView = getStandardView(mBuilder.getBigTextLayoutResource());
3958
3959            mBuilder.mContentText = oldBuilderContentText;
3960
3961            contentView.setTextViewText(R.id.big_text, mBuilder.processLegacyText(mBigText));
3962            contentView.setViewVisibility(R.id.big_text, View.VISIBLE);
3963            contentView.setInt(R.id.big_text, "setMaxLines", calculateMaxLines());
3964            contentView.setViewVisibility(R.id.text2, View.GONE);
3965
3966            applyTopPadding(contentView);
3967
3968            mBuilder.shrinkLine3Text(contentView);
3969
3970            mBuilder.addProfileBadge(contentView, R.id.profile_badge_large_template);
3971
3972            return contentView;
3973        }
3974
3975        private int calculateMaxLines() {
3976            int lineCount = MAX_LINES;
3977            boolean hasActions = mBuilder.mActions.size() > 0;
3978            boolean hasSummary = (mSummaryTextSet ? mSummaryText : mBuilder.mSubText) != null;
3979            if (hasActions) {
3980                lineCount -= LINES_CONSUMED_BY_ACTIONS;
3981            }
3982            if (hasSummary) {
3983                lineCount -= LINES_CONSUMED_BY_SUMMARY;
3984            }
3985
3986            // If we have less top padding at the top, we can fit less lines.
3987            if (!mBuilder.mHasThreeLines) {
3988                lineCount--;
3989            }
3990            return lineCount;
3991        }
3992
3993        /**
3994         * @hide
3995         */
3996        @Override
3997        public void populateBigContentView(Notification wip) {
3998            mBuilder.setBuilderBigContentView(wip, makeBigContentView());
3999        }
4000    }
4001
4002    /**
4003     * Helper class for generating large-format notifications that include a list of (up to 5) strings.
4004     *
4005     * Here's how you'd set the <code>InboxStyle</code> on a notification:
4006     * <pre class="prettyprint">
4007     * Notification notif = new Notification.Builder(mContext)
4008     *     .setContentTitle(&quot;5 New mails from &quot; + sender.toString())
4009     *     .setContentText(subject)
4010     *     .setSmallIcon(R.drawable.new_mail)
4011     *     .setLargeIcon(aBitmap)
4012     *     .setStyle(new Notification.InboxStyle()
4013     *         .addLine(str1)
4014     *         .addLine(str2)
4015     *         .setContentTitle(&quot;&quot;)
4016     *         .setSummaryText(&quot;+3 more&quot;))
4017     *     .build();
4018     * </pre>
4019     *
4020     * @see Notification#bigContentView
4021     */
4022    public static class InboxStyle extends Style {
4023        private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(5);
4024
4025        public InboxStyle() {
4026        }
4027
4028        public InboxStyle(Builder builder) {
4029            setBuilder(builder);
4030        }
4031
4032        /**
4033         * Overrides ContentTitle in the big form of the template.
4034         * This defaults to the value passed to setContentTitle().
4035         */
4036        public InboxStyle setBigContentTitle(CharSequence title) {
4037            internalSetBigContentTitle(safeCharSequence(title));
4038            return this;
4039        }
4040
4041        /**
4042         * Set the first line of text after the detail section in the big form of the template.
4043         */
4044        public InboxStyle setSummaryText(CharSequence cs) {
4045            internalSetSummaryText(safeCharSequence(cs));
4046            return this;
4047        }
4048
4049        /**
4050         * Append a line to the digest section of the Inbox notification.
4051         */
4052        public InboxStyle addLine(CharSequence cs) {
4053            mTexts.add(safeCharSequence(cs));
4054            return this;
4055        }
4056
4057        /**
4058         * @hide
4059         */
4060        public void addExtras(Bundle extras) {
4061            super.addExtras(extras);
4062
4063            CharSequence[] a = new CharSequence[mTexts.size()];
4064            extras.putCharSequenceArray(EXTRA_TEXT_LINES, mTexts.toArray(a));
4065        }
4066
4067        /**
4068         * @hide
4069         */
4070        @Override
4071        protected void restoreFromExtras(Bundle extras) {
4072            super.restoreFromExtras(extras);
4073
4074            mTexts.clear();
4075            if (extras.containsKey(EXTRA_TEXT_LINES)) {
4076                Collections.addAll(mTexts, extras.getCharSequenceArray(EXTRA_TEXT_LINES));
4077            }
4078        }
4079
4080        private RemoteViews makeBigContentView() {
4081            // Remove the content text so line3 disappears unless you have a summary
4082
4083            // Nasty
4084            CharSequence oldBuilderContentText = mBuilder.mContentText;
4085            mBuilder.mContentText = null;
4086
4087            RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource());
4088
4089            mBuilder.mContentText = oldBuilderContentText;
4090
4091            contentView.setViewVisibility(R.id.text2, View.GONE);
4092
4093            int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
4094                    R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};
4095
4096            // Make sure all rows are gone in case we reuse a view.
4097            for (int rowId : rowIds) {
4098                contentView.setViewVisibility(rowId, View.GONE);
4099            }
4100
4101            final boolean largeText =
4102                    mBuilder.mContext.getResources().getConfiguration().fontScale > 1f;
4103            final float subTextSize = mBuilder.mContext.getResources().getDimensionPixelSize(
4104                    R.dimen.notification_subtext_size);
4105            int i=0;
4106            while (i < mTexts.size() && i < rowIds.length) {
4107                CharSequence str = mTexts.get(i);
4108                if (str != null && !str.equals("")) {
4109                    contentView.setViewVisibility(rowIds[i], View.VISIBLE);
4110                    contentView.setTextViewText(rowIds[i], mBuilder.processLegacyText(str));
4111                    if (largeText) {
4112                        contentView.setTextViewTextSize(rowIds[i], TypedValue.COMPLEX_UNIT_PX,
4113                                subTextSize);
4114                    }
4115                }
4116                i++;
4117            }
4118
4119            contentView.setViewVisibility(R.id.inbox_end_pad,
4120                    mTexts.size() > 0 ? View.VISIBLE : View.GONE);
4121
4122            contentView.setViewVisibility(R.id.inbox_more,
4123                    mTexts.size() > rowIds.length ? View.VISIBLE : View.GONE);
4124
4125            applyTopPadding(contentView);
4126
4127            mBuilder.shrinkLine3Text(contentView);
4128
4129            mBuilder.addProfileBadge(contentView, R.id.profile_badge_large_template);
4130
4131            return contentView;
4132        }
4133
4134        /**
4135         * @hide
4136         */
4137        @Override
4138        public void populateBigContentView(Notification wip) {
4139            mBuilder.setBuilderBigContentView(wip, makeBigContentView());
4140        }
4141    }
4142
4143    /**
4144     * Notification style for media playback notifications.
4145     *
4146     * In the expanded form, {@link Notification#bigContentView}, up to 5
4147     * {@link Notification.Action}s specified with
4148     * {@link Notification.Builder#addAction(int, CharSequence, PendingIntent) addAction} will be
4149     * shown as icon-only pushbuttons, suitable for transport controls. The Bitmap given to
4150     * {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap) setLargeIcon()} will be
4151     * treated as album artwork.
4152     *
4153     * Unlike the other styles provided here, MediaStyle can also modify the standard-size
4154     * {@link Notification#contentView}; by providing action indices to
4155     * {@link #setShowActionsInCompactView(int...)} you can promote up to 3 actions to be displayed
4156     * in the standard view alongside the usual content.
4157     *
4158     * Notifications created with MediaStyle will have their category set to
4159     * {@link Notification#CATEGORY_TRANSPORT CATEGORY_TRANSPORT} unless you set a different
4160     * category using {@link Notification.Builder#setCategory(String) setCategory()}.
4161     *
4162     * Finally, if you attach a {@link android.media.session.MediaSession.Token} using
4163     * {@link android.app.Notification.MediaStyle#setMediaSession(MediaSession.Token)},
4164     * the System UI can identify this as a notification representing an active media session
4165     * and respond accordingly (by showing album artwork in the lockscreen, for example).
4166     *
4167     * To use this style with your Notification, feed it to
4168     * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
4169     * <pre class="prettyprint">
4170     * Notification noti = new Notification.Builder()
4171     *     .setSmallIcon(R.drawable.ic_stat_player)
4172     *     .setContentTitle(&quot;Track title&quot;)
4173     *     .setContentText(&quot;Artist - Album&quot;)
4174     *     .setLargeIcon(albumArtBitmap))
4175     *     .setStyle(<b>new Notification.MediaStyle()</b>
4176     *         .setMediaSession(mySession))
4177     *     .build();
4178     * </pre>
4179     *
4180     * @see Notification#bigContentView
4181     */
4182    public static class MediaStyle extends Style {
4183        static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3;
4184        static final int MAX_MEDIA_BUTTONS = 5;
4185
4186        private int[] mActionsToShowInCompact = null;
4187        private MediaSession.Token mToken;
4188
4189        public MediaStyle() {
4190        }
4191
4192        public MediaStyle(Builder builder) {
4193            setBuilder(builder);
4194        }
4195
4196        /**
4197         * Request up to 3 actions (by index in the order of addition) to be shown in the compact
4198         * notification view.
4199         *
4200         * @param actions the indices of the actions to show in the compact notification view
4201         */
4202        public MediaStyle setShowActionsInCompactView(int...actions) {
4203            mActionsToShowInCompact = actions;
4204            return this;
4205        }
4206
4207        /**
4208         * Attach a {@link android.media.session.MediaSession.Token} to this Notification
4209         * to provide additional playback information and control to the SystemUI.
4210         */
4211        public MediaStyle setMediaSession(MediaSession.Token token) {
4212            mToken = token;
4213            return this;
4214        }
4215
4216        /**
4217         * @hide
4218         */
4219        @Override
4220        public Notification buildStyled(Notification wip) {
4221            super.buildStyled(wip);
4222            if (wip.category == null) {
4223                wip.category = Notification.CATEGORY_TRANSPORT;
4224            }
4225            return wip;
4226        }
4227
4228        /**
4229         * @hide
4230         */
4231        @Override
4232        public void populateContentView(Notification wip) {
4233            mBuilder.setBuilderContentView(wip, makeMediaContentView());
4234        }
4235
4236        /**
4237         * @hide
4238         */
4239        @Override
4240        public void populateBigContentView(Notification wip) {
4241            mBuilder.setBuilderBigContentView(wip, makeMediaBigContentView());
4242        }
4243
4244        /** @hide */
4245        @Override
4246        public void addExtras(Bundle extras) {
4247            super.addExtras(extras);
4248
4249            if (mToken != null) {
4250                extras.putParcelable(EXTRA_MEDIA_SESSION, mToken);
4251            }
4252            if (mActionsToShowInCompact != null) {
4253                extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact);
4254            }
4255        }
4256
4257        /**
4258         * @hide
4259         */
4260        @Override
4261        protected void restoreFromExtras(Bundle extras) {
4262            super.restoreFromExtras(extras);
4263
4264            if (extras.containsKey(EXTRA_MEDIA_SESSION)) {
4265                mToken = extras.getParcelable(EXTRA_MEDIA_SESSION);
4266            }
4267            if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) {
4268                mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS);
4269            }
4270        }
4271
4272        private RemoteViews generateMediaActionButton(Action action) {
4273            final boolean tombstone = (action.actionIntent == null);
4274            RemoteViews button = new RemoteViews(mBuilder.mContext.getPackageName(),
4275                    R.layout.notification_material_media_action);
4276            button.setImageViewResource(R.id.action0, action.icon);
4277            button.setDrawableParameters(R.id.action0, false, -1,
4278                    0xFFFFFFFF,
4279                    PorterDuff.Mode.SRC_ATOP, -1);
4280            if (!tombstone) {
4281                button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
4282            }
4283            button.setContentDescription(R.id.action0, action.title);
4284            return button;
4285        }
4286
4287        private RemoteViews makeMediaContentView() {
4288            RemoteViews view = mBuilder.applyStandardTemplate(
4289                    R.layout.notification_template_material_media, false /* hasProgress */);
4290
4291            final int numActions = mBuilder.mActions.size();
4292            final int N = mActionsToShowInCompact == null
4293                    ? 0
4294                    : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
4295            if (N > 0) {
4296                view.removeAllViews(com.android.internal.R.id.media_actions);
4297                for (int i = 0; i < N; i++) {
4298                    if (i >= numActions) {
4299                        throw new IllegalArgumentException(String.format(
4300                                "setShowActionsInCompactView: action %d out of bounds (max %d)",
4301                                i, numActions - 1));
4302                    }
4303
4304                    final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]);
4305                    final RemoteViews button = generateMediaActionButton(action);
4306                    view.addView(com.android.internal.R.id.media_actions, button);
4307                }
4308            }
4309            styleText(view);
4310            hideRightIcon(view);
4311            return view;
4312        }
4313
4314        private RemoteViews makeMediaBigContentView() {
4315            final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
4316            RemoteViews big = mBuilder.applyStandardTemplate(getBigLayoutResource(actionCount),
4317                    false /* hasProgress */);
4318
4319            if (actionCount > 0) {
4320                big.removeAllViews(com.android.internal.R.id.media_actions);
4321                for (int i = 0; i < actionCount; i++) {
4322                    final RemoteViews button = generateMediaActionButton(mBuilder.mActions.get(i));
4323                    big.addView(com.android.internal.R.id.media_actions, button);
4324                }
4325            }
4326            styleText(big);
4327            hideRightIcon(big);
4328            applyTopPadding(big);
4329            big.setViewVisibility(android.R.id.progress, View.GONE);
4330            return big;
4331        }
4332
4333        private int getBigLayoutResource(int actionCount) {
4334            if (actionCount <= 3) {
4335                return R.layout.notification_template_material_big_media_narrow;
4336            } else {
4337                return R.layout.notification_template_material_big_media;
4338            }
4339        }
4340
4341        private void hideRightIcon(RemoteViews contentView) {
4342            contentView.setViewVisibility(R.id.right_icon, View.GONE);
4343        }
4344
4345        /**
4346         * Applies the special text colors for media notifications to all text views.
4347         */
4348        private void styleText(RemoteViews contentView) {
4349            int primaryColor = mBuilder.mContext.getColor(
4350                    R.color.notification_media_primary_color);
4351            int secondaryColor = mBuilder.mContext.getColor(
4352                    R.color.notification_media_secondary_color);
4353            contentView.setTextColor(R.id.title, primaryColor);
4354            if (mBuilder.showsTimeOrChronometer()) {
4355                if (mBuilder.mUseChronometer) {
4356                    contentView.setTextColor(R.id.chronometer, secondaryColor);
4357                } else {
4358                    contentView.setTextColor(R.id.time, secondaryColor);
4359                }
4360            }
4361            contentView.setTextColor(R.id.text2, secondaryColor);
4362            contentView.setTextColor(R.id.text, secondaryColor);
4363            contentView.setTextColor(R.id.info, secondaryColor);
4364        }
4365
4366        /**
4367         * @hide
4368         */
4369        @Override
4370        protected boolean hasProgress() {
4371            return false;
4372        }
4373    }
4374
4375    // When adding a new Style subclass here, don't forget to update
4376    // Builder.getNotificationStyleClass.
4377
4378    /**
4379     * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
4380     * metadata or change options on a notification builder.
4381     */
4382    public interface Extender {
4383        /**
4384         * Apply this extender to a notification builder.
4385         * @param builder the builder to be modified.
4386         * @return the build object for chaining.
4387         */
4388        public Builder extend(Builder builder);
4389    }
4390
4391    /**
4392     * Helper class to add wearable extensions to notifications.
4393     * <p class="note"> See
4394     * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications
4395     * for Android Wear</a> for more information on how to use this class.
4396     * <p>
4397     * To create a notification with wearable extensions:
4398     * <ol>
4399     *   <li>Create a {@link android.app.Notification.Builder}, setting any desired
4400     *   properties.
4401     *   <li>Create a {@link android.app.Notification.WearableExtender}.
4402     *   <li>Set wearable-specific properties using the
4403     *   {@code add} and {@code set} methods of {@link android.app.Notification.WearableExtender}.
4404     *   <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a
4405     *   notification.
4406     *   <li>Post the notification to the notification system with the
4407     *   {@code NotificationManager.notify(...)} methods.
4408     * </ol>
4409     *
4410     * <pre class="prettyprint">
4411     * Notification notif = new Notification.Builder(mContext)
4412     *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
4413     *         .setContentText(subject)
4414     *         .setSmallIcon(R.drawable.new_mail)
4415     *         .extend(new Notification.WearableExtender()
4416     *                 .setContentIcon(R.drawable.new_mail))
4417     *         .build();
4418     * NotificationManager notificationManger =
4419     *         (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
4420     * notificationManger.notify(0, notif);</pre>
4421     *
4422     * <p>Wearable extensions can be accessed on an existing notification by using the
4423     * {@code WearableExtender(Notification)} constructor,
4424     * and then using the {@code get} methods to access values.
4425     *
4426     * <pre class="prettyprint">
4427     * Notification.WearableExtender wearableExtender = new Notification.WearableExtender(
4428     *         notification);
4429     * List&lt;Notification&gt; pages = wearableExtender.getPages();</pre>
4430     */
4431    public static final class WearableExtender implements Extender {
4432        /**
4433         * Sentinel value for an action index that is unset.
4434         */
4435        public static final int UNSET_ACTION_INDEX = -1;
4436
4437        /**
4438         * Size value for use with {@link #setCustomSizePreset} to show this notification with
4439         * default sizing.
4440         * <p>For custom display notifications created using {@link #setDisplayIntent},
4441         * the default is {@link #SIZE_LARGE}. All other notifications size automatically based
4442         * on their content.
4443         */
4444        public static final int SIZE_DEFAULT = 0;
4445
4446        /**
4447         * Size value for use with {@link #setCustomSizePreset} to show this notification
4448         * with an extra small size.
4449         * <p>This value is only applicable for custom display notifications created using
4450         * {@link #setDisplayIntent}.
4451         */
4452        public static final int SIZE_XSMALL = 1;
4453
4454        /**
4455         * Size value for use with {@link #setCustomSizePreset} to show this notification
4456         * with a small size.
4457         * <p>This value is only applicable for custom display notifications created using
4458         * {@link #setDisplayIntent}.
4459         */
4460        public static final int SIZE_SMALL = 2;
4461
4462        /**
4463         * Size value for use with {@link #setCustomSizePreset} to show this notification
4464         * with a medium size.
4465         * <p>This value is only applicable for custom display notifications created using
4466         * {@link #setDisplayIntent}.
4467         */
4468        public static final int SIZE_MEDIUM = 3;
4469
4470        /**
4471         * Size value for use with {@link #setCustomSizePreset} to show this notification
4472         * with a large size.
4473         * <p>This value is only applicable for custom display notifications created using
4474         * {@link #setDisplayIntent}.
4475         */
4476        public static final int SIZE_LARGE = 4;
4477
4478        /**
4479         * Size value for use with {@link #setCustomSizePreset} to show this notification
4480         * full screen.
4481         * <p>This value is only applicable for custom display notifications created using
4482         * {@link #setDisplayIntent}.
4483         */
4484        public static final int SIZE_FULL_SCREEN = 5;
4485
4486        /**
4487         * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a
4488         * short amount of time when this notification is displayed on the screen. This
4489         * is the default value.
4490         */
4491        public static final int SCREEN_TIMEOUT_SHORT = 0;
4492
4493        /**
4494         * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on
4495         * for a longer amount of time when this notification is displayed on the screen.
4496         */
4497        public static final int SCREEN_TIMEOUT_LONG = -1;
4498
4499        /** Notification extra which contains wearable extensions */
4500        private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
4501
4502        // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
4503        private static final String KEY_ACTIONS = "actions";
4504        private static final String KEY_FLAGS = "flags";
4505        private static final String KEY_DISPLAY_INTENT = "displayIntent";
4506        private static final String KEY_PAGES = "pages";
4507        private static final String KEY_BACKGROUND = "background";
4508        private static final String KEY_CONTENT_ICON = "contentIcon";
4509        private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity";
4510        private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex";
4511        private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset";
4512        private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight";
4513        private static final String KEY_GRAVITY = "gravity";
4514        private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout";
4515
4516        // Flags bitwise-ored to mFlags
4517        private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1;
4518        private static final int FLAG_HINT_HIDE_ICON = 1 << 1;
4519        private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2;
4520        private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3;
4521        private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4;
4522
4523        // Default value for flags integer
4524        private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE;
4525
4526        private static final int DEFAULT_CONTENT_ICON_GRAVITY = Gravity.END;
4527        private static final int DEFAULT_GRAVITY = Gravity.BOTTOM;
4528
4529        private ArrayList<Action> mActions = new ArrayList<Action>();
4530        private int mFlags = DEFAULT_FLAGS;
4531        private PendingIntent mDisplayIntent;
4532        private ArrayList<Notification> mPages = new ArrayList<Notification>();
4533        private Bitmap mBackground;
4534        private int mContentIcon;
4535        private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY;
4536        private int mContentActionIndex = UNSET_ACTION_INDEX;
4537        private int mCustomSizePreset = SIZE_DEFAULT;
4538        private int mCustomContentHeight;
4539        private int mGravity = DEFAULT_GRAVITY;
4540        private int mHintScreenTimeout;
4541
4542        /**
4543         * Create a {@link android.app.Notification.WearableExtender} with default
4544         * options.
4545         */
4546        public WearableExtender() {
4547        }
4548
4549        public WearableExtender(Notification notif) {
4550            Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS);
4551            if (wearableBundle != null) {
4552                List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS);
4553                if (actions != null) {
4554                    mActions.addAll(actions);
4555                }
4556
4557                mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
4558                mDisplayIntent = wearableBundle.getParcelable(KEY_DISPLAY_INTENT);
4559
4560                Notification[] pages = getNotificationArrayFromBundle(
4561                        wearableBundle, KEY_PAGES);
4562                if (pages != null) {
4563                    Collections.addAll(mPages, pages);
4564                }
4565
4566                mBackground = wearableBundle.getParcelable(KEY_BACKGROUND);
4567                mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON);
4568                mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY,
4569                        DEFAULT_CONTENT_ICON_GRAVITY);
4570                mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX,
4571                        UNSET_ACTION_INDEX);
4572                mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET,
4573                        SIZE_DEFAULT);
4574                mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT);
4575                mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY);
4576                mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT);
4577            }
4578        }
4579
4580        /**
4581         * Apply wearable extensions to a notification that is being built. This is typically
4582         * called by the {@link android.app.Notification.Builder#extend} method of
4583         * {@link android.app.Notification.Builder}.
4584         */
4585        @Override
4586        public Notification.Builder extend(Notification.Builder builder) {
4587            Bundle wearableBundle = new Bundle();
4588
4589            if (!mActions.isEmpty()) {
4590                wearableBundle.putParcelableArrayList(KEY_ACTIONS, mActions);
4591            }
4592            if (mFlags != DEFAULT_FLAGS) {
4593                wearableBundle.putInt(KEY_FLAGS, mFlags);
4594            }
4595            if (mDisplayIntent != null) {
4596                wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent);
4597            }
4598            if (!mPages.isEmpty()) {
4599                wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray(
4600                        new Notification[mPages.size()]));
4601            }
4602            if (mBackground != null) {
4603                wearableBundle.putParcelable(KEY_BACKGROUND, mBackground);
4604            }
4605            if (mContentIcon != 0) {
4606                wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon);
4607            }
4608            if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) {
4609                wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity);
4610            }
4611            if (mContentActionIndex != UNSET_ACTION_INDEX) {
4612                wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX,
4613                        mContentActionIndex);
4614            }
4615            if (mCustomSizePreset != SIZE_DEFAULT) {
4616                wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset);
4617            }
4618            if (mCustomContentHeight != 0) {
4619                wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight);
4620            }
4621            if (mGravity != DEFAULT_GRAVITY) {
4622                wearableBundle.putInt(KEY_GRAVITY, mGravity);
4623            }
4624            if (mHintScreenTimeout != 0) {
4625                wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout);
4626            }
4627
4628            builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
4629            return builder;
4630        }
4631
4632        @Override
4633        public WearableExtender clone() {
4634            WearableExtender that = new WearableExtender();
4635            that.mActions = new ArrayList<Action>(this.mActions);
4636            that.mFlags = this.mFlags;
4637            that.mDisplayIntent = this.mDisplayIntent;
4638            that.mPages = new ArrayList<Notification>(this.mPages);
4639            that.mBackground = this.mBackground;
4640            that.mContentIcon = this.mContentIcon;
4641            that.mContentIconGravity = this.mContentIconGravity;
4642            that.mContentActionIndex = this.mContentActionIndex;
4643            that.mCustomSizePreset = this.mCustomSizePreset;
4644            that.mCustomContentHeight = this.mCustomContentHeight;
4645            that.mGravity = this.mGravity;
4646            that.mHintScreenTimeout = this.mHintScreenTimeout;
4647            return that;
4648        }
4649
4650        /**
4651         * Add a wearable action to this notification.
4652         *
4653         * <p>When wearable actions are added using this method, the set of actions that
4654         * show on a wearable device splits from devices that only show actions added
4655         * using {@link android.app.Notification.Builder#addAction}. This allows for customization
4656         * of which actions display on different devices.
4657         *
4658         * @param action the action to add to this notification
4659         * @return this object for method chaining
4660         * @see android.app.Notification.Action
4661         */
4662        public WearableExtender addAction(Action action) {
4663            mActions.add(action);
4664            return this;
4665        }
4666
4667        /**
4668         * Adds wearable actions to this notification.
4669         *
4670         * <p>When wearable actions are added using this method, the set of actions that
4671         * show on a wearable device splits from devices that only show actions added
4672         * using {@link android.app.Notification.Builder#addAction}. This allows for customization
4673         * of which actions display on different devices.
4674         *
4675         * @param actions the actions to add to this notification
4676         * @return this object for method chaining
4677         * @see android.app.Notification.Action
4678         */
4679        public WearableExtender addActions(List<Action> actions) {
4680            mActions.addAll(actions);
4681            return this;
4682        }
4683
4684        /**
4685         * Clear all wearable actions present on this builder.
4686         * @return this object for method chaining.
4687         * @see #addAction
4688         */
4689        public WearableExtender clearActions() {
4690            mActions.clear();
4691            return this;
4692        }
4693
4694        /**
4695         * Get the wearable actions present on this notification.
4696         */
4697        public List<Action> getActions() {
4698            return mActions;
4699        }
4700
4701        /**
4702         * Set an intent to launch inside of an activity view when displaying
4703         * this notification. The {@link PendingIntent} provided should be for an activity.
4704         *
4705         * <pre class="prettyprint">
4706         * Intent displayIntent = new Intent(context, MyDisplayActivity.class);
4707         * PendingIntent displayPendingIntent = PendingIntent.getActivity(context,
4708         *         0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT);
4709         * Notification notif = new Notification.Builder(context)
4710         *         .extend(new Notification.WearableExtender()
4711         *                 .setDisplayIntent(displayPendingIntent)
4712         *                 .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM))
4713         *         .build();</pre>
4714         *
4715         * <p>The activity to launch needs to allow embedding, must be exported, and
4716         * should have an empty task affinity. It is also recommended to use the device
4717         * default light theme.
4718         *
4719         * <p>Example AndroidManifest.xml entry:
4720         * <pre class="prettyprint">
4721         * &lt;activity android:name=&quot;com.example.MyDisplayActivity&quot;
4722         *     android:exported=&quot;true&quot;
4723         *     android:allowEmbedded=&quot;true&quot;
4724         *     android:taskAffinity=&quot;&quot;
4725         *     android:theme=&quot;@android:style/Theme.DeviceDefault.Light&quot; /&gt;</pre>
4726         *
4727         * @param intent the {@link PendingIntent} for an activity
4728         * @return this object for method chaining
4729         * @see android.app.Notification.WearableExtender#getDisplayIntent
4730         */
4731        public WearableExtender setDisplayIntent(PendingIntent intent) {
4732            mDisplayIntent = intent;
4733            return this;
4734        }
4735
4736        /**
4737         * Get the intent to launch inside of an activity view when displaying this
4738         * notification. This {@code PendingIntent} should be for an activity.
4739         */
4740        public PendingIntent getDisplayIntent() {
4741            return mDisplayIntent;
4742        }
4743
4744        /**
4745         * Add an additional page of content to display with this notification. The current
4746         * notification forms the first page, and pages added using this function form
4747         * subsequent pages. This field can be used to separate a notification into multiple
4748         * sections.
4749         *
4750         * @param page the notification to add as another page
4751         * @return this object for method chaining
4752         * @see android.app.Notification.WearableExtender#getPages
4753         */
4754        public WearableExtender addPage(Notification page) {
4755            mPages.add(page);
4756            return this;
4757        }
4758
4759        /**
4760         * Add additional pages of content to display with this notification. The current
4761         * notification forms the first page, and pages added using this function form
4762         * subsequent pages. This field can be used to separate a notification into multiple
4763         * sections.
4764         *
4765         * @param pages a list of notifications
4766         * @return this object for method chaining
4767         * @see android.app.Notification.WearableExtender#getPages
4768         */
4769        public WearableExtender addPages(List<Notification> pages) {
4770            mPages.addAll(pages);
4771            return this;
4772        }
4773
4774        /**
4775         * Clear all additional pages present on this builder.
4776         * @return this object for method chaining.
4777         * @see #addPage
4778         */
4779        public WearableExtender clearPages() {
4780            mPages.clear();
4781            return this;
4782        }
4783
4784        /**
4785         * Get the array of additional pages of content for displaying this notification. The
4786         * current notification forms the first page, and elements within this array form
4787         * subsequent pages. This field can be used to separate a notification into multiple
4788         * sections.
4789         * @return the pages for this notification
4790         */
4791        public List<Notification> getPages() {
4792            return mPages;
4793        }
4794
4795        /**
4796         * Set a background image to be displayed behind the notification content.
4797         * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
4798         * will work with any notification style.
4799         *
4800         * @param background the background bitmap
4801         * @return this object for method chaining
4802         * @see android.app.Notification.WearableExtender#getBackground
4803         */
4804        public WearableExtender setBackground(Bitmap background) {
4805            mBackground = background;
4806            return this;
4807        }
4808
4809        /**
4810         * Get a background image to be displayed behind the notification content.
4811         * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
4812         * will work with any notification style.
4813         *
4814         * @return the background image
4815         * @see android.app.Notification.WearableExtender#setBackground
4816         */
4817        public Bitmap getBackground() {
4818            return mBackground;
4819        }
4820
4821        /**
4822         * Set an icon that goes with the content of this notification.
4823         */
4824        public WearableExtender setContentIcon(int icon) {
4825            mContentIcon = icon;
4826            return this;
4827        }
4828
4829        /**
4830         * Get an icon that goes with the content of this notification.
4831         */
4832        public int getContentIcon() {
4833            return mContentIcon;
4834        }
4835
4836        /**
4837         * Set the gravity that the content icon should have within the notification display.
4838         * Supported values include {@link android.view.Gravity#START} and
4839         * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
4840         * @see #setContentIcon
4841         */
4842        public WearableExtender setContentIconGravity(int contentIconGravity) {
4843            mContentIconGravity = contentIconGravity;
4844            return this;
4845        }
4846
4847        /**
4848         * Get the gravity that the content icon should have within the notification display.
4849         * Supported values include {@link android.view.Gravity#START} and
4850         * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
4851         * @see #getContentIcon
4852         */
4853        public int getContentIconGravity() {
4854            return mContentIconGravity;
4855        }
4856
4857        /**
4858         * Set an action from this notification's actions to be clickable with the content of
4859         * this notification. This action will no longer display separately from the
4860         * notification's content.
4861         *
4862         * <p>For notifications with multiple pages, child pages can also have content actions
4863         * set, although the list of available actions comes from the main notification and not
4864         * from the child page's notification.
4865         *
4866         * @param actionIndex The index of the action to hoist onto the current notification page.
4867         *                    If wearable actions were added to the main notification, this index
4868         *                    will apply to that list, otherwise it will apply to the regular
4869         *                    actions list.
4870         */
4871        public WearableExtender setContentAction(int actionIndex) {
4872            mContentActionIndex = actionIndex;
4873            return this;
4874        }
4875
4876        /**
4877         * Get the index of the notification action, if any, that was specified as being clickable
4878         * with the content of this notification. This action will no longer display separately
4879         * from the notification's content.
4880         *
4881         * <p>For notifications with multiple pages, child pages can also have content actions
4882         * set, although the list of available actions comes from the main notification and not
4883         * from the child page's notification.
4884         *
4885         * <p>If wearable specific actions were added to the main notification, this index will
4886         * apply to that list, otherwise it will apply to the regular actions list.
4887         *
4888         * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected.
4889         */
4890        public int getContentAction() {
4891            return mContentActionIndex;
4892        }
4893
4894        /**
4895         * Set the gravity that this notification should have within the available viewport space.
4896         * Supported values include {@link android.view.Gravity#TOP},
4897         * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
4898         * The default value is {@link android.view.Gravity#BOTTOM}.
4899         */
4900        public WearableExtender setGravity(int gravity) {
4901            mGravity = gravity;
4902            return this;
4903        }
4904
4905        /**
4906         * Get the gravity that this notification should have within the available viewport space.
4907         * Supported values include {@link android.view.Gravity#TOP},
4908         * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
4909         * The default value is {@link android.view.Gravity#BOTTOM}.
4910         */
4911        public int getGravity() {
4912            return mGravity;
4913        }
4914
4915        /**
4916         * Set the custom size preset for the display of this notification out of the available
4917         * presets found in {@link android.app.Notification.WearableExtender}, e.g.
4918         * {@link #SIZE_LARGE}.
4919         * <p>Some custom size presets are only applicable for custom display notifications created
4920         * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. Check the
4921         * documentation for the preset in question. See also
4922         * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}.
4923         */
4924        public WearableExtender setCustomSizePreset(int sizePreset) {
4925            mCustomSizePreset = sizePreset;
4926            return this;
4927        }
4928
4929        /**
4930         * Get the custom size preset for the display of this notification out of the available
4931         * presets found in {@link android.app.Notification.WearableExtender}, e.g.
4932         * {@link #SIZE_LARGE}.
4933         * <p>Some custom size presets are only applicable for custom display notifications created
4934         * using {@link #setDisplayIntent}. Check the documentation for the preset in question.
4935         * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}.
4936         */
4937        public int getCustomSizePreset() {
4938            return mCustomSizePreset;
4939        }
4940
4941        /**
4942         * Set the custom height in pixels for the display of this notification's content.
4943         * <p>This option is only available for custom display notifications created
4944         * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. See also
4945         * {@link android.app.Notification.WearableExtender#setCustomSizePreset} and
4946         * {@link #getCustomContentHeight}.
4947         */
4948        public WearableExtender setCustomContentHeight(int height) {
4949            mCustomContentHeight = height;
4950            return this;
4951        }
4952
4953        /**
4954         * Get the custom height in pixels for the display of this notification's content.
4955         * <p>This option is only available for custom display notifications created
4956         * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and
4957         * {@link #setCustomContentHeight}.
4958         */
4959        public int getCustomContentHeight() {
4960            return mCustomContentHeight;
4961        }
4962
4963        /**
4964         * Set whether the scrolling position for the contents of this notification should start
4965         * at the bottom of the contents instead of the top when the contents are too long to
4966         * display within the screen.  Default is false (start scroll at the top).
4967         */
4968        public WearableExtender setStartScrollBottom(boolean startScrollBottom) {
4969            setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom);
4970            return this;
4971        }
4972
4973        /**
4974         * Get whether the scrolling position for the contents of this notification should start
4975         * at the bottom of the contents instead of the top when the contents are too long to
4976         * display within the screen. Default is false (start scroll at the top).
4977         */
4978        public boolean getStartScrollBottom() {
4979            return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0;
4980        }
4981
4982        /**
4983         * Set whether the content intent is available when the wearable device is not connected
4984         * to a companion device.  The user can still trigger this intent when the wearable device
4985         * is offline, but a visual hint will indicate that the content intent may not be available.
4986         * Defaults to true.
4987         */
4988        public WearableExtender setContentIntentAvailableOffline(
4989                boolean contentIntentAvailableOffline) {
4990            setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline);
4991            return this;
4992        }
4993
4994        /**
4995         * Get whether the content intent is available when the wearable device is not connected
4996         * to a companion device.  The user can still trigger this intent when the wearable device
4997         * is offline, but a visual hint will indicate that the content intent may not be available.
4998         * Defaults to true.
4999         */
5000        public boolean getContentIntentAvailableOffline() {
5001            return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0;
5002        }
5003
5004        /**
5005         * Set a hint that this notification's icon should not be displayed.
5006         * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise.
5007         * @return this object for method chaining
5008         */
5009        public WearableExtender setHintHideIcon(boolean hintHideIcon) {
5010            setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon);
5011            return this;
5012        }
5013
5014        /**
5015         * Get a hint that this notification's icon should not be displayed.
5016         * @return {@code true} if this icon should not be displayed, false otherwise.
5017         * The default value is {@code false} if this was never set.
5018         */
5019        public boolean getHintHideIcon() {
5020            return (mFlags & FLAG_HINT_HIDE_ICON) != 0;
5021        }
5022
5023        /**
5024         * Set a visual hint that only the background image of this notification should be
5025         * displayed, and other semantic content should be hidden. This hint is only applicable
5026         * to sub-pages added using {@link #addPage}.
5027         */
5028        public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) {
5029            setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly);
5030            return this;
5031        }
5032
5033        /**
5034         * Get a visual hint that only the background image of this notification should be
5035         * displayed, and other semantic content should be hidden. This hint is only applicable
5036         * to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}.
5037         */
5038        public boolean getHintShowBackgroundOnly() {
5039            return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0;
5040        }
5041
5042        /**
5043         * Set a hint that this notification's background should not be clipped if possible,
5044         * and should instead be resized to fully display on the screen, retaining the aspect
5045         * ratio of the image. This can be useful for images like barcodes or qr codes.
5046         * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible.
5047         * @return this object for method chaining
5048         */
5049        public WearableExtender setHintAvoidBackgroundClipping(
5050                boolean hintAvoidBackgroundClipping) {
5051            setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping);
5052            return this;
5053        }
5054
5055        /**
5056         * Get a hint that this notification's background should not be clipped if possible,
5057         * and should instead be resized to fully display on the screen, retaining the aspect
5058         * ratio of the image. This can be useful for images like barcodes or qr codes.
5059         * @return {@code true} if it's ok if the background is clipped on the screen, false
5060         * otherwise. The default value is {@code false} if this was never set.
5061         */
5062        public boolean getHintAvoidBackgroundClipping() {
5063            return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0;
5064        }
5065
5066        /**
5067         * Set a hint that the screen should remain on for at least this duration when
5068         * this notification is displayed on the screen.
5069         * @param timeout The requested screen timeout in milliseconds. Can also be either
5070         *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
5071         * @return this object for method chaining
5072         */
5073        public WearableExtender setHintScreenTimeout(int timeout) {
5074            mHintScreenTimeout = timeout;
5075            return this;
5076        }
5077
5078        /**
5079         * Get the duration, in milliseconds, that the screen should remain on for
5080         * when this notification is displayed.
5081         * @return the duration in milliseconds if > 0, or either one of the sentinel values
5082         *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
5083         */
5084        public int getHintScreenTimeout() {
5085            return mHintScreenTimeout;
5086        }
5087
5088        private void setFlag(int mask, boolean value) {
5089            if (value) {
5090                mFlags |= mask;
5091            } else {
5092                mFlags &= ~mask;
5093            }
5094        }
5095    }
5096
5097    /**
5098     * <p>Helper class to add Android Auto extensions to notifications. To create a notification
5099     * with car extensions:
5100     *
5101     * <ol>
5102     *  <li>Create an {@link Notification.Builder}, setting any desired
5103     *  properties.
5104     *  <li>Create a {@link CarExtender}.
5105     *  <li>Set car-specific properties using the {@code add} and {@code set} methods of
5106     *  {@link CarExtender}.
5107     *  <li>Call {@link Notification.Builder#extend(Notification.Extender)}
5108     *  to apply the extensions to a notification.
5109     * </ol>
5110     *
5111     * <pre class="prettyprint">
5112     * Notification notification = new Notification.Builder(context)
5113     *         ...
5114     *         .extend(new CarExtender()
5115     *                 .set*(...))
5116     *         .build();
5117     * </pre>
5118     *
5119     * <p>Car extensions can be accessed on an existing notification by using the
5120     * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods
5121     * to access values.
5122     */
5123    public static final class CarExtender implements Extender {
5124        private static final String TAG = "CarExtender";
5125
5126        private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS";
5127        private static final String EXTRA_LARGE_ICON = "large_icon";
5128        private static final String EXTRA_CONVERSATION = "car_conversation";
5129        private static final String EXTRA_COLOR = "app_color";
5130
5131        private Bitmap mLargeIcon;
5132        private UnreadConversation mUnreadConversation;
5133        private int mColor = Notification.COLOR_DEFAULT;
5134
5135        /**
5136         * Create a {@link CarExtender} with default options.
5137         */
5138        public CarExtender() {
5139        }
5140
5141        /**
5142         * Create a {@link CarExtender} from the CarExtender options of an existing Notification.
5143         *
5144         * @param notif The notification from which to copy options.
5145         */
5146        public CarExtender(Notification notif) {
5147            Bundle carBundle = notif.extras == null ?
5148                    null : notif.extras.getBundle(EXTRA_CAR_EXTENDER);
5149            if (carBundle != null) {
5150                mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON);
5151                mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT);
5152
5153                Bundle b = carBundle.getBundle(EXTRA_CONVERSATION);
5154                mUnreadConversation = UnreadConversation.getUnreadConversationFromBundle(b);
5155            }
5156        }
5157
5158        /**
5159         * Apply car extensions to a notification that is being built. This is typically called by
5160         * the {@link Notification.Builder#extend(Notification.Extender)}
5161         * method of {@link Notification.Builder}.
5162         */
5163        @Override
5164        public Notification.Builder extend(Notification.Builder builder) {
5165            Bundle carExtensions = new Bundle();
5166
5167            if (mLargeIcon != null) {
5168                carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
5169            }
5170            if (mColor != Notification.COLOR_DEFAULT) {
5171                carExtensions.putInt(EXTRA_COLOR, mColor);
5172            }
5173
5174            if (mUnreadConversation != null) {
5175                Bundle b = mUnreadConversation.getBundleForUnreadConversation();
5176                carExtensions.putBundle(EXTRA_CONVERSATION, b);
5177            }
5178
5179            builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions);
5180            return builder;
5181        }
5182
5183        /**
5184         * Sets the accent color to use when Android Auto presents the notification.
5185         *
5186         * Android Auto uses the color set with {@link Notification.Builder#setColor(int)}
5187         * to accent the displayed notification. However, not all colors are acceptable in an
5188         * automotive setting. This method can be used to override the color provided in the
5189         * notification in such a situation.
5190         */
5191        public CarExtender setColor(@ColorInt int color) {
5192            mColor = color;
5193            return this;
5194        }
5195
5196        /**
5197         * Gets the accent color.
5198         *
5199         * @see setColor
5200         */
5201        @ColorInt
5202        public int getColor() {
5203            return mColor;
5204        }
5205
5206        /**
5207         * Sets the large icon of the car notification.
5208         *
5209         * If no large icon is set in the extender, Android Auto will display the icon
5210         * specified by {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap)}
5211         *
5212         * @param largeIcon The large icon to use in the car notification.
5213         * @return This object for method chaining.
5214         */
5215        public CarExtender setLargeIcon(Bitmap largeIcon) {
5216            mLargeIcon = largeIcon;
5217            return this;
5218        }
5219
5220        /**
5221         * Gets the large icon used in this car notification, or null if no icon has been set.
5222         *
5223         * @return The large icon for the car notification.
5224         * @see CarExtender#setLargeIcon
5225         */
5226        public Bitmap getLargeIcon() {
5227            return mLargeIcon;
5228        }
5229
5230        /**
5231         * Sets the unread conversation in a message notification.
5232         *
5233         * @param unreadConversation The unread part of the conversation this notification conveys.
5234         * @return This object for method chaining.
5235         */
5236        public CarExtender setUnreadConversation(UnreadConversation unreadConversation) {
5237            mUnreadConversation = unreadConversation;
5238            return this;
5239        }
5240
5241        /**
5242         * Returns the unread conversation conveyed by this notification.
5243         * @see #setUnreadConversation(UnreadConversation)
5244         */
5245        public UnreadConversation getUnreadConversation() {
5246            return mUnreadConversation;
5247        }
5248
5249        /**
5250         * A class which holds the unread messages from a conversation.
5251         */
5252        public static class UnreadConversation {
5253            private static final String KEY_AUTHOR = "author";
5254            private static final String KEY_TEXT = "text";
5255            private static final String KEY_MESSAGES = "messages";
5256            private static final String KEY_REMOTE_INPUT = "remote_input";
5257            private static final String KEY_ON_REPLY = "on_reply";
5258            private static final String KEY_ON_READ = "on_read";
5259            private static final String KEY_PARTICIPANTS = "participants";
5260            private static final String KEY_TIMESTAMP = "timestamp";
5261
5262            private final String[] mMessages;
5263            private final RemoteInput mRemoteInput;
5264            private final PendingIntent mReplyPendingIntent;
5265            private final PendingIntent mReadPendingIntent;
5266            private final String[] mParticipants;
5267            private final long mLatestTimestamp;
5268
5269            UnreadConversation(String[] messages, RemoteInput remoteInput,
5270                    PendingIntent replyPendingIntent, PendingIntent readPendingIntent,
5271                    String[] participants, long latestTimestamp) {
5272                mMessages = messages;
5273                mRemoteInput = remoteInput;
5274                mReadPendingIntent = readPendingIntent;
5275                mReplyPendingIntent = replyPendingIntent;
5276                mParticipants = participants;
5277                mLatestTimestamp = latestTimestamp;
5278            }
5279
5280            /**
5281             * Gets the list of messages conveyed by this notification.
5282             */
5283            public String[] getMessages() {
5284                return mMessages;
5285            }
5286
5287            /**
5288             * Gets the remote input that will be used to convey the response to a message list, or
5289             * null if no such remote input exists.
5290             */
5291            public RemoteInput getRemoteInput() {
5292                return mRemoteInput;
5293            }
5294
5295            /**
5296             * Gets the pending intent that will be triggered when the user replies to this
5297             * notification.
5298             */
5299            public PendingIntent getReplyPendingIntent() {
5300                return mReplyPendingIntent;
5301            }
5302
5303            /**
5304             * Gets the pending intent that Android Auto will send after it reads aloud all messages
5305             * in this object's message list.
5306             */
5307            public PendingIntent getReadPendingIntent() {
5308                return mReadPendingIntent;
5309            }
5310
5311            /**
5312             * Gets the participants in the conversation.
5313             */
5314            public String[] getParticipants() {
5315                return mParticipants;
5316            }
5317
5318            /**
5319             * Gets the firs participant in the conversation.
5320             */
5321            public String getParticipant() {
5322                return mParticipants.length > 0 ? mParticipants[0] : null;
5323            }
5324
5325            /**
5326             * Gets the timestamp of the conversation.
5327             */
5328            public long getLatestTimestamp() {
5329                return mLatestTimestamp;
5330            }
5331
5332            Bundle getBundleForUnreadConversation() {
5333                Bundle b = new Bundle();
5334                String author = null;
5335                if (mParticipants != null && mParticipants.length > 1) {
5336                    author = mParticipants[0];
5337                }
5338                Parcelable[] messages = new Parcelable[mMessages.length];
5339                for (int i = 0; i < messages.length; i++) {
5340                    Bundle m = new Bundle();
5341                    m.putString(KEY_TEXT, mMessages[i]);
5342                    m.putString(KEY_AUTHOR, author);
5343                    messages[i] = m;
5344                }
5345                b.putParcelableArray(KEY_MESSAGES, messages);
5346                if (mRemoteInput != null) {
5347                    b.putParcelable(KEY_REMOTE_INPUT, mRemoteInput);
5348                }
5349                b.putParcelable(KEY_ON_REPLY, mReplyPendingIntent);
5350                b.putParcelable(KEY_ON_READ, mReadPendingIntent);
5351                b.putStringArray(KEY_PARTICIPANTS, mParticipants);
5352                b.putLong(KEY_TIMESTAMP, mLatestTimestamp);
5353                return b;
5354            }
5355
5356            static UnreadConversation getUnreadConversationFromBundle(Bundle b) {
5357                if (b == null) {
5358                    return null;
5359                }
5360                Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES);
5361                String[] messages = null;
5362                if (parcelableMessages != null) {
5363                    String[] tmp = new String[parcelableMessages.length];
5364                    boolean success = true;
5365                    for (int i = 0; i < tmp.length; i++) {
5366                        if (!(parcelableMessages[i] instanceof Bundle)) {
5367                            success = false;
5368                            break;
5369                        }
5370                        tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT);
5371                        if (tmp[i] == null) {
5372                            success = false;
5373                            break;
5374                        }
5375                    }
5376                    if (success) {
5377                        messages = tmp;
5378                    } else {
5379                        return null;
5380                    }
5381                }
5382
5383                PendingIntent onRead = b.getParcelable(KEY_ON_READ);
5384                PendingIntent onReply = b.getParcelable(KEY_ON_REPLY);
5385
5386                RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT);
5387
5388                String[] participants = b.getStringArray(KEY_PARTICIPANTS);
5389                if (participants == null || participants.length != 1) {
5390                    return null;
5391                }
5392
5393                return new UnreadConversation(messages,
5394                        remoteInput,
5395                        onReply,
5396                        onRead,
5397                        participants, b.getLong(KEY_TIMESTAMP));
5398            }
5399        };
5400
5401        /**
5402         * Builder class for {@link CarExtender.UnreadConversation} objects.
5403         */
5404        public static class Builder {
5405            private final List<String> mMessages = new ArrayList<String>();
5406            private final String mParticipant;
5407            private RemoteInput mRemoteInput;
5408            private PendingIntent mReadPendingIntent;
5409            private PendingIntent mReplyPendingIntent;
5410            private long mLatestTimestamp;
5411
5412            /**
5413             * Constructs a new builder for {@link CarExtender.UnreadConversation}.
5414             *
5415             * @param name The name of the other participant in the conversation.
5416             */
5417            public Builder(String name) {
5418                mParticipant = name;
5419            }
5420
5421            /**
5422             * Appends a new unread message to the list of messages for this conversation.
5423             *
5424             * The messages should be added from oldest to newest.
5425             *
5426             * @param message The text of the new unread message.
5427             * @return This object for method chaining.
5428             */
5429            public Builder addMessage(String message) {
5430                mMessages.add(message);
5431                return this;
5432            }
5433
5434            /**
5435             * Sets the pending intent and remote input which will convey the reply to this
5436             * notification.
5437             *
5438             * @param pendingIntent The pending intent which will be triggered on a reply.
5439             * @param remoteInput The remote input parcelable which will carry the reply.
5440             * @return This object for method chaining.
5441             *
5442             * @see CarExtender.UnreadConversation#getRemoteInput
5443             * @see CarExtender.UnreadConversation#getReplyPendingIntent
5444             */
5445            public Builder setReplyAction(
5446                    PendingIntent pendingIntent, RemoteInput remoteInput) {
5447                mRemoteInput = remoteInput;
5448                mReplyPendingIntent = pendingIntent;
5449
5450                return this;
5451            }
5452
5453            /**
5454             * Sets the pending intent that will be sent once the messages in this notification
5455             * are read.
5456             *
5457             * @param pendingIntent The pending intent to use.
5458             * @return This object for method chaining.
5459             */
5460            public Builder setReadPendingIntent(PendingIntent pendingIntent) {
5461                mReadPendingIntent = pendingIntent;
5462                return this;
5463            }
5464
5465            /**
5466             * Sets the timestamp of the most recent message in an unread conversation.
5467             *
5468             * If a messaging notification has been posted by your application and has not
5469             * yet been cancelled, posting a later notification with the same id and tag
5470             * but without a newer timestamp may result in Android Auto not displaying a
5471             * heads up notification for the later notification.
5472             *
5473             * @param timestamp The timestamp of the most recent message in the conversation.
5474             * @return This object for method chaining.
5475             */
5476            public Builder setLatestTimestamp(long timestamp) {
5477                mLatestTimestamp = timestamp;
5478                return this;
5479            }
5480
5481            /**
5482             * Builds a new unread conversation object.
5483             *
5484             * @return The new unread conversation object.
5485             */
5486            public UnreadConversation build() {
5487                String[] messages = mMessages.toArray(new String[mMessages.size()]);
5488                String[] participants = { mParticipant };
5489                return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent,
5490                        mReadPendingIntent, participants, mLatestTimestamp);
5491            }
5492        }
5493    }
5494
5495    /**
5496     * <p>
5497     * Helper class to add content info extensions to notifications. To create a notification with
5498     * content info extensions:
5499     * <ol>
5500     * <li>Create an {@link Notification.Builder}, setting any desired properties.
5501     * <li>Create a {@link ContentInfoExtender}.
5502     * <li>Set content info specific properties using the {@code add} and {@code set} methods of
5503     * {@link ContentInfoExtender}.
5504     * <li>Call {@link Notification.Builder#extend(Notification.Extender)} to apply the extensions
5505     * to a notification.
5506     * </ol>
5507     *
5508     * <pre class="prettyprint">Notification notification = new Notification.Builder(context) * ... * .extend(new ContentInfoExtender() * .set*(...)) * .build(); * </pre>
5509     * <p>
5510     * Content info extensions can be accessed on an existing notification by using the
5511     * {@code ContentInfoExtender(Notification)} constructor, and then using the {@code get} methods
5512     * to access values.
5513     */
5514    public static final class ContentInfoExtender implements Extender {
5515        private static final String TAG = "ContentInfoExtender";
5516
5517        // Key for the Content info extensions bundle in the main Notification extras bundle
5518        private static final String EXTRA_CONTENT_INFO_EXTENDER = "android.CONTENT_INFO_EXTENSIONS";
5519
5520        // Keys within EXTRA_CONTENT_INFO_EXTENDER for individual content info options.
5521
5522        private static final String KEY_CONTENT_TYPE = "android.contentType";
5523
5524        private static final String KEY_CONTENT_GENRES = "android.contentGenre";
5525
5526        private static final String KEY_CONTENT_PRICING_TYPE = "android.contentPricing.type";
5527
5528        private static final String KEY_CONTENT_PRICING_VALUE = "android.contentPricing.value";
5529
5530        private static final String KEY_CONTENT_STATUS = "android.contentStatus";
5531
5532        private static final String KEY_CONTENT_MATURITY_RATING = "android.contentMaturity";
5533
5534        private static final String KEY_CONTENT_RUN_LENGTH = "android.contentLength";
5535
5536
5537        /**
5538         * Value to be used with {@link #setContentTypes} to indicate that the content referred by
5539         * the notification item is a video clip.
5540         */
5541        public static final String CONTENT_TYPE_VIDEO = "android.contentType.video";
5542
5543        /**
5544         * Value to be used with {@link #setContentTypes} to indicate that the content referred by
5545         * the notification item is a movie.
5546         */
5547        public static final String CONTENT_TYPE_MOVIE = "android.contentType.movie";
5548
5549        /**
5550         * Value to be used with {@link #setContentTypes} to indicate that the content referred by
5551         * the notification item is a trailer.
5552         */
5553        public static final String CONTENT_TYPE_TRAILER = "android.contentType.trailer";
5554
5555        /**
5556         * Value to be used with {@link #setContentTypes} to indicate that the content referred by
5557         * the notification item is serial. It can refer to an entire show, a single season or
5558         * series, or a single episode.
5559         */
5560        public static final String CONTENT_TYPE_SERIAL = "android.contentType.serial";
5561
5562        /**
5563         * Value to be used with {@link #setContentTypes} to indicate that the content referred by
5564         * the notification item is a song or album.
5565         */
5566        public static final String CONTENT_TYPE_MUSIC = "android.contentType.music";
5567
5568        /**
5569         * Value to be used with {@link #setContentTypes} to indicate that the content referred by
5570         * the notification item is a radio station.
5571         */
5572        public static final String CONTENT_TYPE_RADIO = "android.contentType.radio";
5573
5574        /**
5575         * Value to be used with {@link #setContentTypes} to indicate that the content referred by
5576         * the notification item is a podcast.
5577         */
5578        public static final String CONTENT_TYPE_PODCAST = "android.contentType.podcast";
5579
5580        /**
5581         * Value to be used with {@link #setContentTypes} to indicate that the content referred by
5582         * the notification item is a news item.
5583         */
5584        public static final String CONTENT_TYPE_NEWS = "android.contentType.news";
5585
5586        /**
5587         * Value to be used with {@link #setContentTypes} to indicate that the content referred by
5588         * the notification item is sports.
5589         */
5590        public static final String CONTENT_TYPE_SPORTS = "android.contentType.sports";
5591
5592        /**
5593         * Value to be used with {@link #setContentTypes} to indicate that the content referred by
5594         * the notification item is an application.
5595         */
5596        public static final String CONTENT_TYPE_APP = "android.contentType.app";
5597
5598        /**
5599         * Value to be used with {@link #setContentTypes} to indicate that the content referred by
5600         * the notification item is a game.
5601         */
5602        public static final String CONTENT_TYPE_GAME = "android.contentType.game";
5603
5604        /**
5605         * Value to be used with {@link #setContentTypes} to indicate that the content referred by
5606         * the notification item is a book.
5607         */
5608        public static final String CONTENT_TYPE_BOOK = "android.contentType.book";
5609
5610        /**
5611         * Value to be used with {@link #setContentTypes} to indicate that the content referred by
5612         * the notification item is a comic book.
5613         */
5614        public static final String CONTENT_TYPE_COMIC = "android.contentType.comic";
5615
5616        /**
5617         * Value to be used with {@link #setContentTypes} to indicate that the content referred by
5618         * the notification item is a magazine.
5619         */
5620        public static final String CONTENT_TYPE_MAGAZINE = "android.contentType.magazine";
5621
5622        /**
5623         * Value to be used with {@link #setContentTypes} to indicate that the content referred by
5624         * the notification item is a website.
5625         */
5626        public static final String CONTENT_TYPE_WEBSITE = "android.contentType.website";
5627
5628
5629        /**
5630         * Value to be used with {@link #setPricingInformation} to indicate that the content
5631         * referred by the notification item is free to consume.
5632         */
5633        public static final String CONTENT_PRICING_FREE = "android.contentPrice.free";
5634
5635        /**
5636         * Value to be used with {@link #setPricingInformation} to indicate that the content
5637         * referred by the notification item is available as a rental, and the price value provided
5638         * is the rental price for the item.
5639         */
5640        public static final String CONTENT_PRICING_RENTAL = "android.contentPrice.rental";
5641
5642        /**
5643         * Value to be used with {@link #setPricingInformation} to indicate that the content
5644         * referred by the notification item is available for purchase, and the price value provided
5645         * is the purchase price for the item.
5646         */
5647        public static final String CONTENT_PRICING_PURCHASE = "android.contentPrice.purchase";
5648
5649        /**
5650         * Value to be used with {@link #setPricingInformation} to indicate that the content
5651         * referred by the notification item is available currently as a pre-order, and the price
5652         * value provided is the purchase price for the item.
5653         */
5654        public static final String CONTENT_PRICING_PREORDER = "android.contentPrice.preorder";
5655
5656        /**
5657         * Value to be used with {@link #setPricingInformation} to indicate that the content
5658         * referred by the notification item is available as part of a subscription based service,
5659         * and the price value provided is the subscription price for the service.
5660         */
5661        public static final String CONTENT_PRICING_SUBSCRIPTION =
5662                "android.contentPrice.subscription";
5663
5664        /**
5665         * Value to be used with {@link #setStatus} to indicate that the content referred by the
5666         * notification is available and ready to be consumed immediately.
5667         */
5668        public static final int CONTENT_STATUS_READY = 0;
5669
5670        /**
5671         * Value to be used with {@link #setStatus} to indicate that the content referred by the
5672         * notification is pending, waiting on either a download or purchase operation to complete
5673         * before it can be consumed.
5674         */
5675        public static final int CONTENT_STATUS_PENDING = 1;
5676
5677        /**
5678         * Value to be used with {@link #setStatus} to indicate that the content referred by the
5679         * notification is available, but needs to be first purchased, rented, subscribed or
5680         * downloaded before it can be consumed.
5681         */
5682        public static final int CONTENT_STATUS_AVAILABLE = 2;
5683
5684        /**
5685         * Value to be used with {@link #setStatus} to indicate that the content referred by the
5686         * notification is not available. This could be content not available in a certain region or
5687         * incompatible with the device in use.
5688         */
5689        public static final int CONTENT_STATUS_UNAVAILABLE = 3;
5690
5691        /**
5692         * Value to be used with {@link #setMaturityRating} to indicate that the content referred by
5693         * the notification is suitable for all audiences.
5694         */
5695        public static final String CONTENT_MATURITY_ALL = "android.contentMaturity.all";
5696
5697        /**
5698         * Value to be used with {@link #setMaturityRating} to indicate that the content
5699         * referred by the notification is suitable for audiences of low maturity and above.
5700         */
5701        public static final String CONTENT_MATURITY_LOW = "android.contentMaturity.low";
5702
5703        /**
5704         * Value to be used with {@link #setMaturityRating} to indicate that the content
5705         * referred by the notification is suitable for audiences of medium maturity and above.
5706         */
5707        public static final String CONTENT_MATURITY_MEDIUM = "android.contentMaturity.medium";
5708
5709        /**
5710         * Value to be used with {@link #setMaturityRating} to indicate that the content
5711         * referred by the notification is suitable for audiences of high maturity and above.
5712         */
5713        public static final String CONTENT_MATURITY_HIGH = "android.contentMaturity.high";
5714
5715        private String[] mTypes;
5716        private String[] mGenres;
5717        private String mPricingType;
5718        private String mPricingValue;
5719        private int mContentStatus = -1;
5720        private String mMaturityRating;
5721        private long mRunLength = -1;
5722
5723        /**
5724         * Create a {@link ContentInfoExtender} with default options.
5725         */
5726        public ContentInfoExtender() {
5727        }
5728
5729        /**
5730         * Create a {@link ContentInfoExtender} from the ContentInfoExtender options of an existing
5731         * Notification.
5732         *
5733         * @param notif The notification from which to copy options.
5734         */
5735        public ContentInfoExtender(Notification notif) {
5736            Bundle contentBundle = notif.extras == null ?
5737                    null : notif.extras.getBundle(EXTRA_CONTENT_INFO_EXTENDER);
5738            if (contentBundle != null) {
5739                mTypes = contentBundle.getStringArray(KEY_CONTENT_TYPE);
5740                mGenres = contentBundle.getStringArray(KEY_CONTENT_GENRES);
5741                mPricingType = contentBundle.getString(KEY_CONTENT_PRICING_TYPE);
5742                mPricingValue = contentBundle.getString(KEY_CONTENT_PRICING_VALUE);
5743                mContentStatus = contentBundle.getInt(KEY_CONTENT_STATUS, -1);
5744                mMaturityRating = contentBundle.getString(KEY_CONTENT_MATURITY_RATING);
5745                mRunLength = contentBundle.getLong(KEY_CONTENT_RUN_LENGTH, -1);
5746            }
5747        }
5748
5749        /**
5750         * Apply content extensions to a notification that is being built. This is typically called
5751         * by the {@link Notification.Builder#extend(Notification.Extender)} method of
5752         * {@link Notification.Builder}.
5753         */
5754        @Override
5755        public Notification.Builder extend(Notification.Builder builder) {
5756            Bundle contentBundle = new Bundle();
5757
5758            if (mTypes != null) {
5759                contentBundle.putStringArray(KEY_CONTENT_TYPE, mTypes);
5760            }
5761            if (mGenres != null) {
5762                contentBundle.putStringArray(KEY_CONTENT_GENRES, mGenres);
5763            }
5764            if (mPricingType != null) {
5765                contentBundle.putString(KEY_CONTENT_PRICING_TYPE, mPricingType);
5766            }
5767            if (mPricingValue != null) {
5768                contentBundle.putString(KEY_CONTENT_PRICING_VALUE, mPricingValue);
5769            }
5770            if (mContentStatus != -1) {
5771                contentBundle.putInt(KEY_CONTENT_STATUS, mContentStatus);
5772            }
5773            if (mMaturityRating != null) {
5774                contentBundle.putString(KEY_CONTENT_MATURITY_RATING, mMaturityRating);
5775            }
5776            if (mRunLength > 0) {
5777                contentBundle.putLong(KEY_CONTENT_RUN_LENGTH, mRunLength);
5778            }
5779
5780            builder.getExtras().putBundle(EXTRA_CONTENT_INFO_EXTENDER, contentBundle);
5781            return builder;
5782        }
5783
5784        /**
5785         * Sets the content types associated with the notification content. The first tag entry will
5786         * be considered the primary type for the content and will be used for ranking purposes.
5787         * Other secondary type tags may be provided, if applicable, and may be used for filtering
5788         * purposes.
5789         *
5790         * @param types Array of predefined type tags (see the <code>CONTENT_TYPE_*</code>
5791         *            constants) that describe the content referred to by a notification.
5792         */
5793        public ContentInfoExtender setContentTypes(String[] types) {
5794            mTypes = types;
5795            return this;
5796        }
5797
5798        /**
5799         * Returns an array containing the content types that describe the content associated with
5800         * the notification. The first tag entry is considered the primary type for the content, and
5801         * is used for content ranking purposes.
5802         *
5803         * @return An array of predefined type tags (see the <code>CONTENT_TYPE_*</code> constants)
5804         *         that describe the content associated with the notification.
5805         * @see ContentInfoExtender#setContentTypes
5806         */
5807        public String[] getContentTypes() {
5808            return mTypes;
5809        }
5810
5811        /**
5812         * Returns the primary content type tag for the content associated with the notification.
5813         *
5814         * @return A predefined type tag (see the <code>CONTENT_TYPE_*</code> constants) indicating
5815         *         the primary type for the content associated with the notification.
5816         * @see ContentInfoExtender#setContentTypes
5817         */
5818        public String getPrimaryContentType() {
5819            if (mTypes == null || mTypes.length == 0) {
5820                return null;
5821            }
5822            return mTypes[0];
5823        }
5824
5825        /**
5826         * Sets the content genres associated with the notification content. These genres may be
5827         * used for content ranking. Genres are open ended String tags.
5828         * <p>
5829         * Some examples: "comedy", "action", "dance", "electronica", "racing", etc.
5830         *
5831         * @param genres Array of genre string tags that describe the content referred to by a
5832         *            notification.
5833         */
5834        public ContentInfoExtender setGenres(String[] genres) {
5835            mGenres = genres;
5836            return this;
5837        }
5838
5839        /**
5840         * Returns an array containing the content genres that describe the content associated with
5841         * the notification.
5842         *
5843         * @return An array of genre tags that describe the content associated with the
5844         *         notification.
5845         * @see ContentInfoExtender#setGenres
5846         */
5847        public String[] getGenres() {
5848            return mGenres;
5849        }
5850
5851        /**
5852         * Sets the pricing and availability information for the content associated with the
5853         * notification. The provided information will indicate the access model for the content
5854         * (free, rental, purchase or subscription) and the price value (if not free).
5855         *
5856         * @param priceType Pricing type for this content. Must be one of the predefined pricing
5857         *            type tags (see the <code>CONTENT_PRICING_*</code> constants).
5858         * @param priceValue A string containing a representation of the content price in the
5859         *            current locale and currency.
5860         * @return This object for method chaining.
5861         */
5862        public ContentInfoExtender setPricingInformation(String priceType, String priceValue) {
5863            mPricingType = priceType;
5864            mPricingValue = priceValue;
5865            return this;
5866        }
5867
5868        /**
5869         * Gets the pricing type for the content associated with the notification.
5870         *
5871         * @return A predefined tag indicating the pricing type for the content (see the <code>
5872         *         CONTENT_PRICING_*</code> constants).
5873         * @see ContentInfoExtender#setPricingInformation
5874         */
5875        public String getPricingType() {
5876            return mPricingType;
5877        }
5878
5879        /**
5880         * Gets the price value (when applicable) for the content associated with a notification.
5881         * The value will be provided as a String containing the price in the appropriate currency
5882         * for the current locale.
5883         *
5884         * @return A string containing a representation of the content price in the current locale
5885         *         and currency.
5886         * @see ContentInfoExtender#setPricingInformation
5887         */
5888        public String getPricingValue() {
5889            if (mPricingType == null || CONTENT_PRICING_FREE.equals(mPricingType)) {
5890                return null;
5891            }
5892            return mPricingValue;
5893        }
5894
5895        /**
5896         * Sets the availability status for the content associated with the notification. This
5897         * status indicates whether the referred content is ready to be consumed on the device, or
5898         * if the user must first purchase, rent, subscribe to, or download the content.
5899         *
5900         * @param contentStatus The status value for this content. Must be one of the predefined
5901         *            content status values (see the <code>CONTENT_STATUS_*</code> constants).
5902         */
5903        public ContentInfoExtender setStatus(int contentStatus) {
5904            mContentStatus = contentStatus;
5905            return this;
5906        }
5907
5908        /**
5909         * Returns status value for the content associated with the notification. This status
5910         * indicates whether the referred content is ready to be consumed on the device, or if the
5911         * user must first purchase, rent, subscribe to, or download the content.
5912         *
5913         * @return The status value for this content, or -1 is a valid status has not been specified
5914         *         (see the <code>CONTENT_STATUS_*</code> for the defined valid status values).
5915         * @see ContentInfoExtender#setStatus
5916         */
5917        public int getStatus() {
5918            return mContentStatus;
5919        }
5920
5921        /**
5922         * Sets the maturity level rating for the content associated with the notification.
5923         *
5924         * @param maturityRating A tag indicating the maturity level rating for the content. This
5925         *            tag must be one of the predefined maturity rating tags (see the <code>
5926         *            CONTENT_MATURITY_*</code> constants).
5927         */
5928        public ContentInfoExtender setMaturityRating(String maturityRating) {
5929            mMaturityRating = maturityRating;
5930            return this;
5931        }
5932
5933        /**
5934         * Returns the maturity level rating for the content associated with the notification.
5935         *
5936         * @return returns a predefined tag indicating the maturity level rating for the content
5937         *         (see the <code> CONTENT_MATURITY_*</code> constants).
5938         * @see ContentInfoExtender#setMaturityRating
5939         */
5940        public String getMaturityRating() {
5941            return mMaturityRating;
5942        }
5943
5944        /**
5945         * Sets the running time (when applicable) for the content associated with the notification.
5946         *
5947         * @param length The runing time, in seconds, of the content associated with the
5948         *            notification.
5949         */
5950        public ContentInfoExtender setRunningTime(long length) {
5951            mRunLength = length;
5952            return this;
5953        }
5954
5955        /**
5956         * Returns the running time for the content associated with the notification.
5957         *
5958         * @return The running time, in seconds, of the content associated with the notification.
5959         * @see ContentInfoExtender#setRunningTime
5960         */
5961        public long getRunningTime() {
5962            return mRunLength;
5963        }
5964    }
5965
5966    /**
5967     * Get an array of Notification objects from a parcelable array bundle field.
5968     * Update the bundle to have a typed array so fetches in the future don't need
5969     * to do an array copy.
5970     */
5971    private static Notification[] getNotificationArrayFromBundle(Bundle bundle, String key) {
5972        Parcelable[] array = bundle.getParcelableArray(key);
5973        if (array instanceof Notification[] || array == null) {
5974            return (Notification[]) array;
5975        }
5976        Notification[] typedArray = Arrays.copyOf(array, array.length,
5977                Notification[].class);
5978        bundle.putParcelableArray(key, typedArray);
5979        return typedArray;
5980    }
5981
5982    private static class BuilderRemoteViews extends RemoteViews {
5983        public BuilderRemoteViews(Parcel parcel) {
5984            super(parcel);
5985        }
5986
5987        public BuilderRemoteViews(ApplicationInfo appInfo, int layoutId) {
5988            super(appInfo, layoutId);
5989        }
5990
5991        @Override
5992        public BuilderRemoteViews clone() {
5993            Parcel p = Parcel.obtain();
5994            writeToParcel(p, 0);
5995            p.setDataPosition(0);
5996            BuilderRemoteViews brv = new BuilderRemoteViews(p);
5997            p.recycle();
5998            return brv;
5999        }
6000    }
6001}
6002