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