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