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