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