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 <