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