Home | History | Annotate | Download | only in app
      1 /*
      2  * Copyright (C) 2012 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 androidx.core.app;
     18 
     19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
     20 
     21 import static java.lang.annotation.RetentionPolicy.SOURCE;
     22 
     23 import android.app.Activity;
     24 import android.app.Notification;
     25 import android.app.PendingIntent;
     26 import android.content.Context;
     27 import android.content.res.ColorStateList;
     28 import android.content.res.Resources;
     29 import android.graphics.Bitmap;
     30 import android.graphics.Canvas;
     31 import android.graphics.Color;
     32 import android.graphics.PorterDuff;
     33 import android.graphics.PorterDuffColorFilter;
     34 import android.graphics.drawable.Drawable;
     35 import android.media.AudioAttributes;
     36 import android.media.AudioManager;
     37 import android.net.Uri;
     38 import android.os.Build;
     39 import android.os.Bundle;
     40 import android.os.Parcelable;
     41 import android.os.SystemClock;
     42 import android.text.SpannableStringBuilder;
     43 import android.text.Spanned;
     44 import android.text.TextUtils;
     45 import android.text.style.TextAppearanceSpan;
     46 import android.util.SparseArray;
     47 import android.util.TypedValue;
     48 import android.view.Gravity;
     49 import android.view.View;
     50 import android.widget.RemoteViews;
     51 
     52 import androidx.annotation.ColorInt;
     53 import androidx.annotation.IntDef;
     54 import androidx.annotation.NonNull;
     55 import androidx.annotation.Nullable;
     56 import androidx.annotation.RequiresApi;
     57 import androidx.annotation.RestrictTo;
     58 import androidx.core.R;
     59 import androidx.core.text.BidiFormatter;
     60 import androidx.core.view.GravityCompat;
     61 
     62 import java.lang.annotation.Retention;
     63 import java.lang.annotation.RetentionPolicy;
     64 import java.text.NumberFormat;
     65 import java.util.ArrayList;
     66 import java.util.Arrays;
     67 import java.util.Collections;
     68 import java.util.List;
     69 
     70 /**
     71  * Helper for accessing features in {@link android.app.Notification}.
     72  */
     73 public class NotificationCompat {
     74 
     75     /**
     76      * Use all default values (where applicable).
     77      */
     78     public static final int DEFAULT_ALL = ~0;
     79 
     80     /**
     81      * Use the default notification sound. This will ignore any sound set using
     82      * {@link Builder#setSound}
     83      *
     84      * <p>
     85      * A notification that is noisy is more likely to be presented as a heads-up notification,
     86      * on some platforms.
     87      * </p>
     88      *
     89      * @see Builder#setDefaults
     90      */
     91     public static final int DEFAULT_SOUND = 1;
     92 
     93     /**
     94      * Use the default notification vibrate. This will ignore any vibrate set using
     95      * {@link Builder#setVibrate}. Using phone vibration requires the
     96      * {@link android.Manifest.permission#VIBRATE VIBRATE} permission.
     97      *
     98      * <p>
     99      * A notification that vibrates is more likely to be presented as a heads-up notification,
    100      * on some platforms.
    101      * </p>
    102      *
    103      * @see Builder#setDefaults
    104      */
    105     public static final int DEFAULT_VIBRATE = 2;
    106 
    107     /**
    108      * Use the default notification lights. This will ignore the
    109      * {@link #FLAG_SHOW_LIGHTS} bit, and values set with {@link Builder#setLights}.
    110      *
    111      * @see Builder#setDefaults
    112      */
    113     public static final int DEFAULT_LIGHTS = 4;
    114 
    115     /**
    116      * Use this constant as the value for audioStreamType to request that
    117      * the default stream type for notifications be used.  Currently the
    118      * default stream type is {@link AudioManager#STREAM_NOTIFICATION}.
    119      */
    120     public static final int STREAM_DEFAULT = -1;
    121     /**
    122      * Bit set in the Notification flags field when LEDs should be turned on
    123      * for this notification.
    124      */
    125     public static final int FLAG_SHOW_LIGHTS        = 0x00000001;
    126 
    127     /**
    128      * Bit set in the Notification flags field if this notification is in
    129      * reference to something that is ongoing, like a phone call.  It should
    130      * not be set if this notification is in reference to something that
    131      * happened at a particular point in time, like a missed phone call.
    132      */
    133     public static final int FLAG_ONGOING_EVENT      = 0x00000002;
    134 
    135     /**
    136      * Bit set in the Notification flags field if
    137      * the audio will be repeated until the notification is
    138      * cancelled or the notification window is opened.
    139      */
    140     public static final int FLAG_INSISTENT          = 0x00000004;
    141 
    142     /**
    143      * Bit set in the Notification flags field if the notification's sound,
    144      * vibrate and ticker should only be played if the notification is not already showing.
    145      */
    146     public static final int FLAG_ONLY_ALERT_ONCE    = 0x00000008;
    147 
    148     /**
    149      * Bit set in the Notification flags field if the notification should be canceled when
    150      * it is clicked by the user.
    151      */
    152     public static final int FLAG_AUTO_CANCEL        = 0x00000010;
    153 
    154     /**
    155      * Bit set in the Notification flags field if the notification should not be canceled
    156      * when the user clicks the Clear all button.
    157      */
    158     public static final int FLAG_NO_CLEAR           = 0x00000020;
    159 
    160     /**
    161      * Bit set in the Notification flags field if this notification represents a currently
    162      * running service.  This will normally be set for you by
    163      * {@link android.app.Service#startForeground}.
    164      */
    165     public static final int FLAG_FOREGROUND_SERVICE = 0x00000040;
    166 
    167     /**
    168      * Obsolete flag indicating high-priority notifications; use the priority field instead.
    169      *
    170      * @deprecated Use {@link NotificationCompat.Builder#setPriority(int)} with a positive value.
    171      */
    172     @Deprecated
    173     public static final int FLAG_HIGH_PRIORITY      = 0x00000080;
    174 
    175     /**
    176      * Bit set in the Notification flags field if this notification is relevant to the current
    177      * device only and it is not recommended that it bridge to other devices.
    178      */
    179     public static final int FLAG_LOCAL_ONLY         = 0x00000100;
    180 
    181     /**
    182      * Bit set in the Notification flags field if this notification is the group summary for a
    183      * group of notifications. Grouped notifications may display in a cluster or stack on devices
    184      * which support such rendering. Requires a group key also be set using
    185      * {@link Builder#setGroup}.
    186      */
    187     public static final int FLAG_GROUP_SUMMARY      = 0x00000200;
    188 
    189     /**
    190      * Default notification priority for {@link NotificationCompat.Builder#setPriority(int)}.
    191      * If your application does not prioritize its own notifications,
    192      * use this value for all notifications.
    193      */
    194     public static final int PRIORITY_DEFAULT = 0;
    195 
    196     /**
    197      * Lower notification priority for {@link NotificationCompat.Builder#setPriority(int)},
    198      * for items that are less important. The UI may choose to show
    199      * these items smaller, or at a different position in the list,
    200      * compared with your app's {@link #PRIORITY_DEFAULT} items.
    201      */
    202     public static final int PRIORITY_LOW = -1;
    203 
    204     /**
    205      * Lowest notification priority for {@link NotificationCompat.Builder#setPriority(int)};
    206      * these items might not be shown to the user except under
    207      * special circumstances, such as detailed notification logs.
    208      */
    209     public static final int PRIORITY_MIN = -2;
    210 
    211     /**
    212      * Higher notification priority for {@link NotificationCompat.Builder#setPriority(int)},
    213      * for more important notifications or alerts. The UI may choose
    214      * to show these items larger, or at a different position in
    215      * notification lists, compared with your app's {@link #PRIORITY_DEFAULT} items.
    216      */
    217     public static final int PRIORITY_HIGH = 1;
    218 
    219     /**
    220      * Highest notification priority for {@link NotificationCompat.Builder#setPriority(int)},
    221      * for your application's most important items that require the user's
    222      * prompt attention or input.
    223      */
    224     public static final int PRIORITY_MAX = 2;
    225 
    226     /**
    227      * Notification extras key: this is the title of the notification,
    228      * as supplied to {@link Builder#setContentTitle(CharSequence)}.
    229      */
    230     public static final String EXTRA_TITLE = "android.title";
    231 
    232     /**
    233      * Notification extras key: this is the title of the notification when shown in expanded form,
    234      * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}.
    235      */
    236     public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big";
    237 
    238     /**
    239      * Notification extras key: this is the main text payload, as supplied to
    240      * {@link Builder#setContentText(CharSequence)}.
    241      */
    242     public static final String EXTRA_TEXT = "android.text";
    243 
    244     /**
    245      * Notification extras key: this is a third line of text, as supplied to
    246      * {@link Builder#setSubText(CharSequence)}.
    247      */
    248     public static final String EXTRA_SUB_TEXT = "android.subText";
    249 
    250     /**
    251      * Notification extras key: this is the remote input history, as supplied to
    252      * {@link Builder#setRemoteInputHistory(CharSequence[])}.
    253      *
    254      * Apps can fill this through {@link Builder#setRemoteInputHistory(CharSequence[])}
    255      * with the most recent inputs that have been sent through a {@link RemoteInput} of this
    256      * Notification and are expected to clear it once the it is no longer relevant (e.g. for chat
    257      * notifications once the other party has responded).
    258      *
    259      * The extra with this key is of type CharSequence[] and contains the most recent entry at
    260      * the 0 index, the second most recent at the 1 index, etc.
    261      *
    262      * @see Builder#setRemoteInputHistory(CharSequence[])
    263      */
    264     public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory";
    265 
    266     /**
    267      * Notification extras key: this is a small piece of additional text as supplied to
    268      * {@link Builder#setContentInfo(CharSequence)}.
    269      */
    270     public static final String EXTRA_INFO_TEXT = "android.infoText";
    271 
    272     /**
    273      * Notification extras key: this is a line of summary information intended to be shown
    274      * alongside expanded notifications, as supplied to (e.g.)
    275      * {@link BigTextStyle#setSummaryText(CharSequence)}.
    276      */
    277     public static final String EXTRA_SUMMARY_TEXT = "android.summaryText";
    278 
    279     /**
    280      * Notification extras key: this is the longer text shown in the big form of a
    281      * {@link BigTextStyle} notification, as supplied to
    282      * {@link BigTextStyle#bigText(CharSequence)}.
    283      */
    284     public static final String EXTRA_BIG_TEXT = "android.bigText";
    285 
    286     /**
    287      * Notification extras key: this is the resource ID of the notification's main small icon, as
    288      * supplied to {@link Builder#setSmallIcon(int)}.
    289      */
    290     public static final String EXTRA_SMALL_ICON = "android.icon";
    291 
    292     /**
    293      * Notification extras key: this is a bitmap to be used instead of the small icon when showing the
    294      * notification payload, as
    295      * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}.
    296      */
    297     public static final String EXTRA_LARGE_ICON = "android.largeIcon";
    298 
    299     /**
    300      * Notification extras key: this is a bitmap to be used instead of the one from
    301      * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is
    302      * shown in its expanded form, as supplied to
    303      * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}.
    304      */
    305     public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big";
    306 
    307     /**
    308      * Notification extras key: this is the progress value supplied to
    309      * {@link Builder#setProgress(int, int, boolean)}.
    310      */
    311     public static final String EXTRA_PROGRESS = "android.progress";
    312 
    313     /**
    314      * Notification extras key: this is the maximum value supplied to
    315      * {@link Builder#setProgress(int, int, boolean)}.
    316      */
    317     public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
    318 
    319     /**
    320      * Notification extras key: whether the progress bar is indeterminate, supplied to
    321      * {@link Builder#setProgress(int, int, boolean)}.
    322      */
    323     public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
    324 
    325     /**
    326      * Notification extras key: whether the when field set using {@link Builder#setWhen} should
    327      * be shown as a count-up timer (specifically a {@link android.widget.Chronometer}) instead
    328      * of a timestamp, as supplied to {@link Builder#setUsesChronometer(boolean)}.
    329      */
    330     public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
    331 
    332     /**
    333      * Notification extras key: whether the when field set using {@link Builder#setWhen} should
    334      * be shown, as supplied to {@link Builder#setShowWhen(boolean)}.
    335      */
    336     public static final String EXTRA_SHOW_WHEN = "android.showWhen";
    337 
    338     /**
    339      * Notification extras key: this is a bitmap to be shown in {@link BigPictureStyle} expanded
    340      * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}.
    341      */
    342     public static final String EXTRA_PICTURE = "android.picture";
    343 
    344     /**
    345      * Notification extras key: An array of CharSequences to show in {@link InboxStyle} expanded
    346      * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}.
    347      */
    348     public static final String EXTRA_TEXT_LINES = "android.textLines";
    349 
    350     /**
    351      * Notification extras key: A string representing the name of the specific
    352      * {@link android.app.Notification.Style} used to create this notification.
    353      */
    354     public static final String EXTRA_TEMPLATE = "android.template";
    355 
    356     /**
    357      * Notification extras key: A String array containing the people that this
    358      * notification relates to, each of which was supplied to
    359      * {@link Builder#addPerson(String)}.
    360      */
    361     public static final String EXTRA_PEOPLE = "android.people";
    362 
    363     /**
    364      * Notification extras key: A
    365      * {@link android.content.ContentUris content URI} pointing to an image that can be displayed
    366      * in the background when the notification is selected. The URI must point to an image stream
    367      * suitable for passing into
    368      * {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream)
    369      * BitmapFactory.decodeStream}; all other content types will be ignored. The content provider
    370      * URI used for this purpose must require no permissions to read the image data.
    371      */
    372     public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
    373 
    374     /**
    375      * Notification key: A
    376      * {@link android.media.session.MediaSession.Token} associated with a
    377      * {@link android.app.Notification.MediaStyle} notification.
    378      */
    379     public static final String EXTRA_MEDIA_SESSION = "android.mediaSession";
    380 
    381     /**
    382      * Notification extras key: the indices of actions to be shown in the compact view,
    383      * as supplied to (e.g.) {@link Notification.MediaStyle#setShowActionsInCompactView(int...)}.
    384      */
    385     public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions";
    386 
    387     /**
    388      * Notification key: the username to be displayed for all messages sent by the user
    389      * including direct replies {@link MessagingStyle} notification.
    390      */
    391     public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName";
    392 
    393     /**
    394      * Notification key: the person to display for all messages sent by the user, including direct
    395      * replies to {@link MessagingStyle} notifications.
    396      */
    397     public static final String EXTRA_MESSAGING_STYLE_USER = "android.messagingStyleUser";
    398 
    399     /**
    400      * Notification key: a {@link String} to be displayed as the title to a conversation
    401      * represented by a {@link MessagingStyle}
    402      */
    403     public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
    404 
    405     /**
    406      * Notification key: an array of {@link Bundle} objects representing
    407      * {@link MessagingStyle.Message} objects for a {@link MessagingStyle} notification.
    408      */
    409     public static final String EXTRA_MESSAGES = "android.messages";
    410 
    411     /**
    412      * Notification key: whether the {@link NotificationCompat.MessagingStyle} notification
    413      * represents a group conversation.
    414      */
    415     public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
    416 
    417     /**
    418      * Keys into the {@link #getExtras} Bundle: the audio contents of this notification.
    419      *
    420      * This is for use when rendering the notification on an audio-focused interface;
    421      * the audio contents are a complete sound sample that contains the contents/body of the
    422      * notification. This may be used in substitute of a Text-to-Speech reading of the
    423      * notification. For example if the notification represents a voice message this should point
    424      * to the audio of that message.
    425      *
    426      * The data stored under this key should be a String representation of a Uri that contains the
    427      * audio contents in one of the following formats: WAV, PCM 16-bit, AMR-WB.
    428      *
    429      * This extra is unnecessary if you are using {@code MessagingStyle} since each {@code Message}
    430      * has a field for holding data URI. That field can be used for audio.
    431      * See {@code Message#setData}.
    432      *
    433      * Example usage:
    434      * <pre>
    435      * {@code
    436      * NotificationCompat.Builder myBuilder = (build your Notification as normal);
    437      * myBuilder.getExtras().putString(EXTRA_AUDIO_CONTENTS_URI, myAudioUri.toString());
    438      * }
    439      * </pre>
    440      */
    441     public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents";
    442 
    443     /**
    444      * Value of {@link Notification#color} equal to 0 (also known as
    445      * {@link android.graphics.Color#TRANSPARENT Color.TRANSPARENT}),
    446      * telling the system not to decorate this notification with any special color but instead use
    447      * default colors when presenting this notification.
    448      */
    449     @ColorInt
    450     public static final int COLOR_DEFAULT = Color.TRANSPARENT;
    451 
    452     /** @hide */
    453     @RestrictTo(LIBRARY_GROUP)
    454     @IntDef({AudioManager.STREAM_VOICE_CALL, AudioManager.STREAM_SYSTEM, AudioManager.STREAM_RING,
    455             AudioManager.STREAM_MUSIC, AudioManager.STREAM_ALARM, AudioManager.STREAM_NOTIFICATION,
    456             AudioManager.STREAM_DTMF, AudioManager.STREAM_ACCESSIBILITY})
    457     @Retention(RetentionPolicy.SOURCE)
    458     public @interface StreamType {}
    459 
    460     /** @hide */
    461     @RestrictTo(LIBRARY_GROUP)
    462     @Retention(SOURCE)
    463     @IntDef({VISIBILITY_PUBLIC, VISIBILITY_PRIVATE, VISIBILITY_SECRET})
    464     public @interface NotificationVisibility {}
    465     /**
    466      * Notification visibility: Show this notification in its entirety on all lockscreens.
    467      *
    468      * {@see android.app.Notification#visibility}
    469      */
    470     public static final int VISIBILITY_PUBLIC = Notification.VISIBILITY_PUBLIC;
    471 
    472     /**
    473      * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or
    474      * private information on secure lockscreens.
    475      *
    476      * {@see android.app.Notification#visibility}
    477      */
    478     public static final int VISIBILITY_PRIVATE = Notification.VISIBILITY_PRIVATE;
    479 
    480     /**
    481      * Notification visibility: Do not reveal any part of this notification on a secure lockscreen.
    482      *
    483      * {@see android.app.Notification#visibility}
    484      */
    485     public static final int VISIBILITY_SECRET = Notification.VISIBILITY_SECRET;
    486 
    487     /**
    488      * Notification category: incoming call (voice or video) or similar synchronous communication request.
    489      */
    490     public static final String CATEGORY_CALL = Notification.CATEGORY_CALL;
    491 
    492     /**
    493      * Notification category: incoming direct message (SMS, instant message, etc.).
    494      */
    495     public static final String CATEGORY_MESSAGE = Notification.CATEGORY_MESSAGE;
    496 
    497     /**
    498      * Notification category: asynchronous bulk message (email).
    499      */
    500     public static final String CATEGORY_EMAIL = Notification.CATEGORY_EMAIL;
    501 
    502     /**
    503      * Notification category: calendar event.
    504      */
    505     public static final String CATEGORY_EVENT = Notification.CATEGORY_EVENT;
    506 
    507     /**
    508      * Notification category: promotion or advertisement.
    509      */
    510     public static final String CATEGORY_PROMO = Notification.CATEGORY_PROMO;
    511 
    512     /**
    513      * Notification category: alarm or timer.
    514      */
    515     public static final String CATEGORY_ALARM = Notification.CATEGORY_ALARM;
    516 
    517     /**
    518      * Notification category: progress of a long-running background operation.
    519      */
    520     public static final String CATEGORY_PROGRESS = Notification.CATEGORY_PROGRESS;
    521 
    522     /**
    523      * Notification category: social network or sharing update.
    524      */
    525     public static final String CATEGORY_SOCIAL = Notification.CATEGORY_SOCIAL;
    526 
    527     /**
    528      * Notification category: error in background operation or authentication status.
    529      */
    530     public static final String CATEGORY_ERROR = Notification.CATEGORY_ERROR;
    531 
    532     /**
    533      * Notification category: media transport control for playback.
    534      */
    535     public static final String CATEGORY_TRANSPORT = Notification.CATEGORY_TRANSPORT;
    536 
    537     /**
    538      * Notification category: system or device status update.  Reserved for system use.
    539      */
    540     public static final String CATEGORY_SYSTEM = Notification.CATEGORY_SYSTEM;
    541 
    542     /**
    543      * Notification category: indication of running background service.
    544      */
    545     public static final String CATEGORY_SERVICE = Notification.CATEGORY_SERVICE;
    546 
    547     /**
    548      * Notification category: user-scheduled reminder.
    549      */
    550     public static final String CATEGORY_REMINDER = Notification.CATEGORY_REMINDER;
    551 
    552     /**
    553      * Notification category: a specific, timely recommendation for a single thing.
    554      * For example, a news app might want to recommend a news story it believes the user will
    555      * want to read next.
    556      */
    557     public static final String CATEGORY_RECOMMENDATION =
    558             Notification.CATEGORY_RECOMMENDATION;
    559 
    560     /**
    561      * Notification category: ongoing information about device or contextual status.
    562      */
    563     public static final String CATEGORY_STATUS = Notification.CATEGORY_STATUS;
    564 
    565     /** @hide */
    566     @Retention(RetentionPolicy.SOURCE)
    567     @RestrictTo(LIBRARY_GROUP)
    568     @IntDef({BADGE_ICON_NONE, BADGE_ICON_SMALL, BADGE_ICON_LARGE})
    569     public @interface BadgeIconType {}
    570     /**
    571      * If this notification is being shown as a badge, always show as a number.
    572      */
    573     public static final int BADGE_ICON_NONE = Notification.BADGE_ICON_NONE;
    574 
    575     /**
    576      * If this notification is being shown as a badge, use the icon provided to
    577      * {@link Builder#setSmallIcon(int)} to represent this notification.
    578      */
    579     public static final int BADGE_ICON_SMALL = Notification.BADGE_ICON_SMALL;
    580 
    581     /**
    582      * If this notification is being shown as a badge, use the icon provided to
    583      * {@link Builder#setLargeIcon(Bitmap) to represent this notification.
    584      */
    585     public static final int BADGE_ICON_LARGE = Notification.BADGE_ICON_LARGE;
    586 
    587     /** @hide */
    588     @Retention(RetentionPolicy.SOURCE)
    589     @RestrictTo(LIBRARY_GROUP)
    590     @IntDef({GROUP_ALERT_ALL, GROUP_ALERT_SUMMARY, GROUP_ALERT_CHILDREN})
    591     public @interface GroupAlertBehavior {}
    592 
    593     /**
    594      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all notifications in a
    595      * group with sound or vibration ought to make sound or vibrate (respectively), so this
    596      * notification will not be muted when it is in a group.
    597      */
    598     public static final int GROUP_ALERT_ALL = Notification.GROUP_ALERT_ALL;
    599 
    600     /**
    601      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all children
    602      * notification in a group should be silenced (no sound or vibration) even if they would
    603      * otherwise make sound or vibrate. Use this constant to mute this notification if this
    604      * notification is a group child. This must be applied to all children notifications you want
    605      * to mute.
    606      *
    607      * <p> For example, you might want to use this constant if you post a number of children
    608      * notifications at once (say, after a periodic sync), and only need to notify the user
    609      * audibly once.
    610      */
    611     public static final int GROUP_ALERT_SUMMARY = Notification.GROUP_ALERT_SUMMARY;
    612 
    613     /**
    614      * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that the summary
    615      * notification in a group should be silenced (no sound or vibration) even if they would
    616      * otherwise make sound or vibrate. Use this constant
    617      * to mute this notification if this notification is a group summary.
    618      *
    619      * <p>For example, you might want to use this constant if only the children notifications
    620      * in your group have content and the summary is only used to visually group notifications
    621      * rather than to alert the user that new information is available.
    622      */
    623     public static final int GROUP_ALERT_CHILDREN = Notification.GROUP_ALERT_CHILDREN;
    624 
    625     /**
    626      * Builder class for {@link NotificationCompat} objects.  Allows easier control over
    627      * all the flags, as well as help constructing the typical notification layouts.
    628      * <p>
    629      * On platform versions that don't offer expanded notifications, methods that depend on
    630      * expanded notifications have no effect.
    631      * </p>
    632      * <p>
    633      * For example, action buttons won't appear on platforms prior to Android 4.1. Action
    634      * buttons depend on expanded notifications, which are only available in Android 4.1
    635      * and later.
    636      * <p>
    637      * For this reason, you should always ensure that UI controls in a notification are also
    638      * available in an {@link android.app.Activity} in your app, and you should always start that
    639      * {@link android.app.Activity} when users click the notification. To do this, use the
    640      * {@link NotificationCompat.Builder#setContentIntent setContentIntent()}
    641      * method.
    642      * </p>
    643      *
    644      */
    645     public static class Builder {
    646         /**
    647          * Maximum length of CharSequences accepted by Builder and friends.
    648          *
    649          * <p>
    650          * Avoids spamming the system with overly large strings such as full e-mails.
    651          */
    652         private static final int MAX_CHARSEQUENCE_LENGTH = 5 * 1024;
    653 
    654         // All these variables are declared public/hidden so they can be accessed by a builder
    655         // extender.
    656 
    657         /** @hide */
    658         @RestrictTo(LIBRARY_GROUP)
    659         public Context mContext;
    660 
    661         /** @hide */
    662         @RestrictTo(LIBRARY_GROUP)
    663         public ArrayList<Action> mActions = new ArrayList<>();
    664 
    665         // Invisible actions are stored in the CarExtender bundle without actually being owned by
    666         // CarExtender. This is to comply with an optimization of the Android OS which removes
    667         // Actions from the Notification if there are no listeners for those Actions.
    668         ArrayList<Action> mInvisibleActions = new ArrayList<>();
    669 
    670         CharSequence mContentTitle;
    671         CharSequence mContentText;
    672         PendingIntent mContentIntent;
    673         PendingIntent mFullScreenIntent;
    674         RemoteViews mTickerView;
    675         Bitmap mLargeIcon;
    676         CharSequence mContentInfo;
    677         int mNumber;
    678         int mPriority;
    679         boolean mShowWhen = true;
    680         boolean mUseChronometer;
    681         Style mStyle;
    682         CharSequence mSubText;
    683         CharSequence[] mRemoteInputHistory;
    684         int mProgressMax;
    685         int mProgress;
    686         boolean mProgressIndeterminate;
    687         String mGroupKey;
    688         boolean mGroupSummary;
    689         String mSortKey;
    690         boolean mLocalOnly = false;
    691         boolean mColorized;
    692         boolean mColorizedSet;
    693         String mCategory;
    694         Bundle mExtras;
    695         int mColor = COLOR_DEFAULT;
    696         @NotificationVisibility int mVisibility = VISIBILITY_PRIVATE;
    697         Notification mPublicVersion;
    698         RemoteViews mContentView;
    699         RemoteViews mBigContentView;
    700         RemoteViews mHeadsUpContentView;
    701         String mChannelId;
    702         int mBadgeIcon = BADGE_ICON_NONE;
    703         String mShortcutId;
    704         long mTimeout;
    705         @GroupAlertBehavior int mGroupAlertBehavior = GROUP_ALERT_ALL;
    706         Notification mNotification = new Notification();
    707 
    708         /**
    709          * @deprecated This field was not meant to be public.
    710          */
    711         @Deprecated
    712         public ArrayList<String> mPeople;
    713 
    714         /**
    715          * Constructor.
    716          *
    717          * Automatically sets the when field to {@link System#currentTimeMillis()
    718          * System.currentTimeMillis()} and the audio stream to the
    719          * {@link Notification#STREAM_DEFAULT}.
    720          *
    721          * @param context A {@link Context} that will be used to construct the
    722          *      RemoteViews. The Context will not be held past the lifetime of this
    723          *      Builder object.
    724          * @param channelId The constructed Notification will be posted on this
    725          *      NotificationChannel.
    726          */
    727         public Builder(@NonNull Context context, @NonNull String channelId) {
    728             mContext = context;
    729             mChannelId = channelId;
    730 
    731             // Set defaults to match the defaults of a Notification
    732             mNotification.when = System.currentTimeMillis();
    733             mNotification.audioStreamType = Notification.STREAM_DEFAULT;
    734             mPriority = PRIORITY_DEFAULT;
    735             mPeople = new ArrayList<String>();
    736         }
    737 
    738         /**
    739          * @deprecated use {@link #NotificationCompat.Builder(Context,String)} instead.
    740          * All posted Notifications must specify a NotificationChannel Id.
    741          */
    742         @Deprecated
    743         public Builder(Context context) {
    744             this(context, null);
    745         }
    746 
    747         /**
    748          * Set the time that the event occurred.  Notifications in the panel are
    749          * sorted by this time.
    750          */
    751         public Builder setWhen(long when) {
    752             mNotification.when = when;
    753             return this;
    754         }
    755 
    756         /**
    757          * Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown
    758          * in the content view.
    759          */
    760         public Builder setShowWhen(boolean show) {
    761             mShowWhen = show;
    762             return this;
    763         }
    764 
    765         /**
    766          * Show the {@link Notification#when} field as a stopwatch.
    767          *
    768          * Instead of presenting <code>when</code> as a timestamp, the notification will show an
    769          * automatically updating display of the minutes and seconds since <code>when</code>.
    770          *
    771          * Useful when showing an elapsed time (like an ongoing phone call).
    772          *
    773          * @see android.widget.Chronometer
    774          * @see Notification#when
    775          */
    776         public Builder setUsesChronometer(boolean b) {
    777             mUseChronometer = b;
    778             return this;
    779         }
    780 
    781         /**
    782          * Set the small icon to use in the notification layouts.  Different classes of devices
    783          * may return different sizes.  See the UX guidelines for more information on how to
    784          * design these icons.
    785          *
    786          * @param icon A resource ID in the application's package of the drawable to use.
    787          */
    788         public Builder setSmallIcon(int icon) {
    789             mNotification.icon = icon;
    790             return this;
    791         }
    792 
    793         /**
    794          * A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional
    795          * level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable
    796          * LevelListDrawable}.
    797          *
    798          * @param icon A resource ID in the application's package of the drawable to use.
    799          * @param level The level to use for the icon.
    800          *
    801          * @see android.graphics.drawable.LevelListDrawable
    802          */
    803         public Builder setSmallIcon(int icon, int level) {
    804             mNotification.icon = icon;
    805             mNotification.iconLevel = level;
    806             return this;
    807         }
    808 
    809         /**
    810          * Set the title (first row) of the notification, in a standard notification.
    811          */
    812         public Builder setContentTitle(CharSequence title) {
    813             mContentTitle = limitCharSequenceLength(title);
    814             return this;
    815         }
    816 
    817         /**
    818          * Set the text (second row) of the notification, in a standard notification.
    819          */
    820         public Builder setContentText(CharSequence text) {
    821             mContentText = limitCharSequenceLength(text);
    822             return this;
    823         }
    824 
    825         /**
    826          * Set the third line of text in the platform notification template.
    827          * Don't use if you're also using {@link #setProgress(int, int, boolean)};
    828          * they occupy the same location in the standard template.
    829          * <br>
    830          * If the platform does not provide large-format notifications, this method has no effect.
    831          * The third line of text only appears in expanded view.
    832          * <br>
    833          */
    834         public Builder setSubText(CharSequence text) {
    835             mSubText = limitCharSequenceLength(text);
    836             return this;
    837         }
    838 
    839         /**
    840          * Set the remote input history.
    841          *
    842          * This should be set to the most recent inputs that have been sent
    843          * through a {@link RemoteInput} of this Notification and cleared once the it is no
    844          * longer relevant (e.g. for chat notifications once the other party has responded).
    845          *
    846          * The most recent input must be stored at the 0 index, the second most recent at the
    847          * 1 index, etc. Note that the system will limit both how far back the inputs will be shown
    848          * and how much of each individual input is shown.
    849          *
    850          * <p>Note: The reply text will only be shown on notifications that have least one action
    851          * with a {@code RemoteInput}.</p>
    852          */
    853         public Builder setRemoteInputHistory(CharSequence[] text) {
    854             mRemoteInputHistory = text;
    855             return this;
    856         }
    857 
    858         /**
    859          * Set the large number at the right-hand side of the notification.  This is
    860          * equivalent to setContentInfo, although it might show the number in a different
    861          * font size for readability.
    862          */
    863         public Builder setNumber(int number) {
    864             mNumber = number;
    865             return this;
    866         }
    867 
    868         /**
    869          * Set the large text at the right-hand side of the notification.
    870          */
    871         public Builder setContentInfo(CharSequence info) {
    872             mContentInfo = limitCharSequenceLength(info);
    873             return this;
    874         }
    875 
    876         /**
    877          * Set the progress this notification represents, which may be
    878          * represented as a {@link android.widget.ProgressBar}.
    879          */
    880         public Builder setProgress(int max, int progress, boolean indeterminate) {
    881             mProgressMax = max;
    882             mProgress = progress;
    883             mProgressIndeterminate = indeterminate;
    884             return this;
    885         }
    886 
    887         /**
    888          * Supply a custom RemoteViews to use instead of the standard one.
    889          */
    890         public Builder setContent(RemoteViews views) {
    891             mNotification.contentView = views;
    892             return this;
    893         }
    894 
    895         /**
    896          * Supply a {@link PendingIntent} to send when the notification is clicked.
    897          * If you do not supply an intent, you can now add PendingIntents to individual
    898          * views to be launched when clicked by calling {@link RemoteViews#setOnClickPendingIntent
    899          * RemoteViews.setOnClickPendingIntent(int,PendingIntent)}.  Be sure to
    900          * read {@link Notification#contentIntent Notification.contentIntent} for
    901          * how to correctly use this.
    902          */
    903         public Builder setContentIntent(PendingIntent intent) {
    904             mContentIntent = intent;
    905             return this;
    906         }
    907 
    908         /**
    909          * Supply a {@link PendingIntent} to send when the notification is cleared by the user
    910          * directly from the notification panel.  For example, this intent is sent when the user
    911          * clicks the "Clear all" button, or the individual "X" buttons on notifications.  This
    912          * intent is not sent when the application calls
    913          * {@link android.app.NotificationManager#cancel NotificationManager.cancel(int)}.
    914          */
    915         public Builder setDeleteIntent(PendingIntent intent) {
    916             mNotification.deleteIntent = intent;
    917             return this;
    918         }
    919 
    920         /**
    921          * An intent to launch instead of posting the notification to the status bar.
    922          * Only for use with extremely high-priority notifications demanding the user's
    923          * <strong>immediate</strong> attention, such as an incoming phone call or
    924          * alarm clock that the user has explicitly set to a particular time.
    925          * If this facility is used for something else, please give the user an option
    926          * to turn it off and use a normal notification, as this can be extremely
    927          * disruptive.
    928          *
    929          * <p>
    930          * On some platforms, the system UI may choose to display a heads-up notification,
    931          * instead of launching this intent, while the user is using the device.
    932          * </p>
    933          *
    934          * @param intent The pending intent to launch.
    935          * @param highPriority Passing true will cause this notification to be sent
    936          *          even if other notifications are suppressed.
    937          */
    938         public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) {
    939             mFullScreenIntent = intent;
    940             setFlag(FLAG_HIGH_PRIORITY, highPriority);
    941             return this;
    942         }
    943 
    944         /**
    945          * Sets the "ticker" text which is sent to accessibility services. Prior to
    946          * {@link Build.VERSION_CODES#LOLLIPOP}, sets the text that is displayed in the status bar
    947          * when the notification first arrives.
    948          */
    949         public Builder setTicker(CharSequence tickerText) {
    950             mNotification.tickerText = limitCharSequenceLength(tickerText);
    951             return this;
    952         }
    953 
    954         /**
    955          * Sets the "ticker" text which is sent to accessibility services. Prior to
    956          * {@link Build.VERSION_CODES#LOLLIPOP}, sets the text that is displayed in the status bar
    957          * when the notification first arrives, and also a RemoteViews object that may be displayed
    958          * instead on some devices.
    959          */
    960         public Builder setTicker(CharSequence tickerText, RemoteViews views) {
    961             mNotification.tickerText = limitCharSequenceLength(tickerText);
    962             mTickerView = views;
    963             return this;
    964         }
    965 
    966         /**
    967          * Set the large icon that is shown in the ticker and notification.
    968          */
    969         public Builder setLargeIcon(Bitmap icon) {
    970             mLargeIcon = reduceLargeIconSize(icon);
    971             return this;
    972         }
    973 
    974         /**
    975          * Reduce the size of a notification icon if it's overly large. The framework does
    976          * this automatically starting from API 27.
    977          */
    978         private Bitmap reduceLargeIconSize(Bitmap icon) {
    979             if (icon == null || Build.VERSION.SDK_INT >= 27) {
    980                 return icon;
    981             }
    982 
    983             Resources res = mContext.getResources();
    984             int maxWidth =
    985                     res.getDimensionPixelSize(R.dimen.compat_notification_large_icon_max_width);
    986             int maxHeight =
    987                     res.getDimensionPixelSize(R.dimen.compat_notification_large_icon_max_height);
    988             if (icon.getWidth() <= maxWidth && icon.getHeight() <= maxHeight) {
    989                 return icon;
    990             }
    991 
    992             double scale = Math.min(
    993                     maxWidth / (double) Math.max(1, icon.getWidth()),
    994                     maxHeight / (double) Math.max(1, icon.getHeight()));
    995             return Bitmap.createScaledBitmap(
    996                     icon,
    997                     (int) Math.ceil(icon.getWidth() * scale),
    998                     (int) Math.ceil(icon.getHeight() * scale),
    999                     true /* filtered */);
   1000         }
   1001 
   1002         /**
   1003          * Set the sound to play.  It will play on the default stream.
   1004          *
   1005          * <p>
   1006          * On some platforms, a notification that is noisy is more likely to be presented
   1007          * as a heads-up notification.
   1008          * </p>
   1009          */
   1010         public Builder setSound(Uri sound) {
   1011             mNotification.sound = sound;
   1012             mNotification.audioStreamType = Notification.STREAM_DEFAULT;
   1013             if (Build.VERSION.SDK_INT >= 21) {
   1014                 mNotification.audioAttributes = new AudioAttributes.Builder()
   1015                         .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
   1016                         .setUsage(AudioAttributes.USAGE_NOTIFICATION)
   1017                         .build();
   1018             }
   1019             return this;
   1020         }
   1021 
   1022         /**
   1023          * Set the sound to play.  It will play on the stream you supply.
   1024          *
   1025          * <p>
   1026          * On some platforms, a notification that is noisy is more likely to be presented
   1027          * as a heads-up notification.
   1028          * </p>
   1029          *
   1030          * @see Notification#STREAM_DEFAULT
   1031          * @see AudioManager for the <code>STREAM_</code> constants.
   1032          */
   1033         public Builder setSound(Uri sound, @StreamType int streamType) {
   1034             mNotification.sound = sound;
   1035             mNotification.audioStreamType = streamType;
   1036             if (Build.VERSION.SDK_INT >= 21) {
   1037                 mNotification.audioAttributes = new AudioAttributes.Builder()
   1038                         .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
   1039                         .setLegacyStreamType(streamType)
   1040                         .build();
   1041             }
   1042             return this;
   1043         }
   1044 
   1045         /**
   1046          * Set the vibration pattern to use.
   1047          *
   1048          * <p>
   1049          * On some platforms, a notification that vibrates is more likely to be presented
   1050          * as a heads-up notification.
   1051          * </p>
   1052          *
   1053          * @see android.os.Vibrator for a discussion of the <code>pattern</code>
   1054          * parameter.
   1055          */
   1056         public Builder setVibrate(long[] pattern) {
   1057             mNotification.vibrate = pattern;
   1058             return this;
   1059         }
   1060 
   1061         /**
   1062          * Set the argb value that you would like the LED on the device to blink, as well as the
   1063          * rate.  The rate is specified in terms of the number of milliseconds to be on
   1064          * and then the number of milliseconds to be off.
   1065          */
   1066         public Builder setLights(@ColorInt int argb, int onMs, int offMs) {
   1067             mNotification.ledARGB = argb;
   1068             mNotification.ledOnMS = onMs;
   1069             mNotification.ledOffMS = offMs;
   1070             boolean showLights = mNotification.ledOnMS != 0 && mNotification.ledOffMS != 0;
   1071             mNotification.flags = (mNotification.flags & ~Notification.FLAG_SHOW_LIGHTS) |
   1072                     (showLights ? Notification.FLAG_SHOW_LIGHTS : 0);
   1073             return this;
   1074         }
   1075 
   1076         /**
   1077          * Set whether this is an ongoing notification.
   1078          *
   1079          * <p>Ongoing notifications differ from regular notifications in the following ways:
   1080          * <ul>
   1081          *   <li>Ongoing notifications are sorted above the regular notifications in the
   1082          *   notification panel.</li>
   1083          *   <li>Ongoing notifications do not have an 'X' close button, and are not affected
   1084          *   by the "Clear all" button.
   1085          * </ul>
   1086          */
   1087         public Builder setOngoing(boolean ongoing) {
   1088             setFlag(Notification.FLAG_ONGOING_EVENT, ongoing);
   1089             return this;
   1090         }
   1091 
   1092         /**
   1093          * Set whether this notification should be colorized. When set, the color set with
   1094          * {@link #setColor(int)} will be used as the background color of this notification.
   1095          * <p>
   1096          * This should only be used for high priority ongoing tasks like navigation, an ongoing
   1097          * call, or other similarly high-priority events for the user.
   1098          * <p>
   1099          * For most styles, the coloring will only be applied if the notification is for a
   1100          * foreground service notification.
   1101          * <p>
   1102          * However, for MediaStyle and DecoratedMediaCustomViewStyle notifications
   1103          * that have a media session attached there is no such requirement.
   1104          * <p>
   1105          * Calling this method on any version prior to {@link android.os.Build.VERSION_CODES#O} will
   1106          * not have an effect on the notification and it won't be colorized.
   1107          *
   1108          * @see #setColor(int)
   1109          */
   1110         public Builder setColorized(boolean colorize) {
   1111             mColorized = colorize;
   1112             mColorizedSet = true;
   1113             return this;
   1114         }
   1115 
   1116         /**
   1117          * Set this flag if you would only like the sound, vibrate
   1118          * and ticker to be played if the notification is not already showing.
   1119          */
   1120         public Builder setOnlyAlertOnce(boolean onlyAlertOnce) {
   1121             setFlag(Notification.FLAG_ONLY_ALERT_ONCE, onlyAlertOnce);
   1122             return this;
   1123         }
   1124 
   1125         /**
   1126          * Setting this flag will make it so the notification is automatically
   1127          * canceled when the user clicks it in the panel.  The PendingIntent
   1128          * set with {@link #setDeleteIntent} will be broadcast when the notification
   1129          * is canceled.
   1130          */
   1131         public Builder setAutoCancel(boolean autoCancel) {
   1132             setFlag(Notification.FLAG_AUTO_CANCEL, autoCancel);
   1133             return this;
   1134         }
   1135 
   1136         /**
   1137          * Set whether or not this notification is only relevant to the current device.
   1138          *
   1139          * <p>Some notifications can be bridged to other devices for remote display.
   1140          * This hint can be set to recommend this notification not be bridged.
   1141          */
   1142         public Builder setLocalOnly(boolean b) {
   1143             mLocalOnly = b;
   1144             return this;
   1145         }
   1146 
   1147         /**
   1148          * Set the notification category.
   1149          *
   1150          * <p>Must be one of the predefined notification categories (see the <code>CATEGORY_*</code>
   1151          * constants in {@link Notification}) that best describes this notification.
   1152          * May be used by the system for ranking and filtering.
   1153          */
   1154         public Builder setCategory(String category) {
   1155             mCategory = category;
   1156             return this;
   1157         }
   1158 
   1159         /**
   1160          * Set the default notification options that will be used.
   1161          * <p>
   1162          * The value should be one or more of the following fields combined with
   1163          * bitwise-or:
   1164          * {@link Notification#DEFAULT_SOUND}, {@link Notification#DEFAULT_VIBRATE},
   1165          * {@link Notification#DEFAULT_LIGHTS}.
   1166          * <p>
   1167          * For all default values, use {@link Notification#DEFAULT_ALL}.
   1168          */
   1169         public Builder setDefaults(int defaults) {
   1170             mNotification.defaults = defaults;
   1171             if ((defaults & Notification.DEFAULT_LIGHTS) != 0) {
   1172                 mNotification.flags |= Notification.FLAG_SHOW_LIGHTS;
   1173             }
   1174             return this;
   1175         }
   1176 
   1177         private void setFlag(int mask, boolean value) {
   1178             if (value) {
   1179                 mNotification.flags |= mask;
   1180             } else {
   1181                 mNotification.flags &= ~mask;
   1182             }
   1183         }
   1184 
   1185         /**
   1186          * Set the relative priority for this notification.
   1187          *
   1188          * Priority is an indication of how much of the user's
   1189          * valuable attention should be consumed by this
   1190          * notification. Low-priority notifications may be hidden from
   1191          * the user in certain situations, while the user might be
   1192          * interrupted for a higher-priority notification.
   1193          * The system sets a notification's priority based on various factors including the
   1194          * setPriority value. The effect may differ slightly on different platforms.
   1195          *
   1196          * @param pri Relative priority for this notification. Must be one of
   1197          *     the priority constants defined by {@link NotificationCompat}.
   1198          *     Acceptable values range from {@link
   1199          *     NotificationCompat#PRIORITY_MIN} (-2) to {@link
   1200          *     NotificationCompat#PRIORITY_MAX} (2).
   1201          */
   1202         public Builder setPriority(int pri) {
   1203             mPriority = pri;
   1204             return this;
   1205         }
   1206 
   1207         /**
   1208          * Add a person that is relevant to this notification.
   1209          *
   1210          * <P>
   1211          * Depending on user preferences, this annotation may allow the notification to pass
   1212          * through interruption filters, and to appear more prominently in the user interface.
   1213          * </P>
   1214          *
   1215          * <P>
   1216          * The person should be specified by the {@code String} representation of a
   1217          * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
   1218          * </P>
   1219          *
   1220          * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema
   1221          * URIs.  The path part of these URIs must exist in the contacts database, in the
   1222          * appropriate column, or the reference will be discarded as invalid. Telephone schema
   1223          * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}.
   1224          * </P>
   1225          *
   1226          * @param uri A URI for the person.
   1227          * @see Notification#EXTRA_PEOPLE
   1228          */
   1229         public Builder addPerson(String uri) {
   1230             mPeople.add(uri);
   1231             return this;
   1232         }
   1233 
   1234         /**
   1235          * Set this notification to be part of a group of notifications sharing the same key.
   1236          * Grouped notifications may display in a cluster or stack on devices which
   1237          * support such rendering.
   1238          *
   1239          * <p>To make this notification the summary for its group, also call
   1240          * {@link #setGroupSummary}. A sort order can be specified for group members by using
   1241          * {@link #setSortKey}.
   1242          * @param groupKey The group key of the group.
   1243          * @return this object for method chaining
   1244          */
   1245         public Builder setGroup(String groupKey) {
   1246             mGroupKey = groupKey;
   1247             return this;
   1248         }
   1249 
   1250         /**
   1251          * Set this notification to be the group summary for a group of notifications.
   1252          * Grouped notifications may display in a cluster or stack on devices which
   1253          * support such rendering. Requires a group key also be set using {@link #setGroup}.
   1254          * @param isGroupSummary Whether this notification should be a group summary.
   1255          * @return this object for method chaining
   1256          */
   1257         public Builder setGroupSummary(boolean isGroupSummary) {
   1258             mGroupSummary = isGroupSummary;
   1259             return this;
   1260         }
   1261 
   1262         /**
   1263          * Set a sort key that orders this notification among other notifications from the
   1264          * same package. This can be useful if an external sort was already applied and an app
   1265          * would like to preserve this. Notifications will be sorted lexicographically using this
   1266          * value, although providing different priorities in addition to providing sort key may
   1267          * cause this value to be ignored.
   1268          *
   1269          * <p>This sort key can also be used to order members of a notification group. See
   1270          * {@link Builder#setGroup}.
   1271          *
   1272          * @see String#compareTo(String)
   1273          */
   1274         public Builder setSortKey(String sortKey) {
   1275             mSortKey = sortKey;
   1276             return this;
   1277         }
   1278 
   1279         /**
   1280          * Merge additional metadata into this notification.
   1281          *
   1282          * <p>Values within the Bundle will replace existing extras values in this Builder.
   1283          *
   1284          * @see Notification#extras
   1285          */
   1286         public Builder addExtras(Bundle extras) {
   1287             if (extras != null) {
   1288                 if (mExtras == null) {
   1289                     mExtras = new Bundle(extras);
   1290                 } else {
   1291                     mExtras.putAll(extras);
   1292                 }
   1293             }
   1294             return this;
   1295         }
   1296 
   1297         /**
   1298          * Set metadata for this notification.
   1299          *
   1300          * <p>A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's
   1301          * current contents are copied into the Notification each time {@link #build()} is
   1302          * called.
   1303          *
   1304          * <p>Replaces any existing extras values with those from the provided Bundle.
   1305          * Use {@link #addExtras} to merge in metadata instead.
   1306          *
   1307          * @see Notification#extras
   1308          */
   1309         public Builder setExtras(Bundle extras) {
   1310             mExtras = extras;
   1311             return this;
   1312         }
   1313 
   1314         /**
   1315          * Get the current metadata Bundle used by this notification Builder.
   1316          *
   1317          * <p>The returned Bundle is shared with this Builder.
   1318          *
   1319          * <p>The current contents of this Bundle are copied into the Notification each time
   1320          * {@link #build()} is called.
   1321          *
   1322          * @see Notification#extras
   1323          */
   1324         public Bundle getExtras() {
   1325             if (mExtras == null) {
   1326                 mExtras = new Bundle();
   1327             }
   1328             return mExtras;
   1329         }
   1330 
   1331         /**
   1332          * Add an action to this notification. Actions are typically displayed by
   1333          * the system as a button adjacent to the notification content.
   1334          * <br>
   1335          * Action buttons won't appear on platforms prior to Android 4.1. Action
   1336          * buttons depend on expanded notifications, which are only available in Android 4.1
   1337          * and later. To ensure that an action button's functionality is always available, first
   1338          * implement the functionality in the {@link android.app.Activity} that starts when a user
   1339          * clicks the  notification (see {@link #setContentIntent setContentIntent()}), and then
   1340          * enhance the notification by implementing the same functionality with
   1341          * {@link #addAction addAction()}.
   1342          *
   1343          * @param icon Resource ID of a drawable that represents the action.
   1344          * @param title Text describing the action.
   1345          * @param intent {@link android.app.PendingIntent} to be fired when the action is invoked.
   1346          */
   1347         public Builder addAction(int icon, CharSequence title, PendingIntent intent) {
   1348             mActions.add(new Action(icon, title, intent));
   1349             return this;
   1350         }
   1351 
   1352         /**
   1353          * Add an action to this notification. Actions are typically displayed by
   1354          * the system as a button adjacent to the notification content.
   1355          * <br>
   1356          * Action buttons won't appear on platforms prior to Android 4.1. Action
   1357          * buttons depend on expanded notifications, which are only available in Android 4.1
   1358          * and later. To ensure that an action button's functionality is always available, first
   1359          * implement the functionality in the {@link android.app.Activity} that starts when a user
   1360          * clicks the  notification (see {@link #setContentIntent setContentIntent()}), and then
   1361          * enhance the notification by implementing the same functionality with
   1362          * {@link #addAction addAction()}.
   1363          *
   1364          * @param action The action to add.
   1365          */
   1366         public Builder addAction(Action action) {
   1367             mActions.add(action);
   1368             return this;
   1369         }
   1370 
   1371         /**
   1372          * Add an invisible action to this notification. Invisible actions are never displayed by
   1373          * the system, but can be retrieved and used by other application listening to
   1374          * system notifications. Invisible actions are supported from Android 4.4.4 (API 20) and can
   1375          * be retrieved using {@link NotificationCompat#getInvisibleActions(Notification)}.
   1376          *
   1377          * @param icon Resource ID of a drawable that represents the action.
   1378          * @param title Text describing the action.
   1379          * @param intent {@link android.app.PendingIntent} to be fired when the action is invoked.
   1380          */
   1381         @RequiresApi(21)
   1382         public Builder addInvisibleAction(int icon, CharSequence title, PendingIntent intent) {
   1383             return addInvisibleAction(new Action(icon, title, intent));
   1384         }
   1385 
   1386         /**
   1387          * Add an invisible action to this notification. Invisible actions are never displayed by
   1388          * the system, but can be retrieved and used by other application listening to
   1389          * system notifications. Invisible actions are supported from Android 4.4.4 (API 20) and can
   1390          * be retrieved using {@link NotificationCompat#getInvisibleActions(Notification)}.
   1391          *
   1392          * @param action The action to add.
   1393          */
   1394         @RequiresApi(21)
   1395         public Builder addInvisibleAction(Action action) {
   1396             mInvisibleActions.add(action);
   1397             return this;
   1398         }
   1399 
   1400         /**
   1401          * Add a rich notification style to be applied at build time.
   1402          * <br>
   1403          * If the platform does not provide rich notification styles, this method has no effect. The
   1404          * user will always see the normal notification style.
   1405          *
   1406          * @param style Object responsible for modifying the notification style.
   1407          */
   1408         public Builder setStyle(Style style) {
   1409             if (mStyle != style) {
   1410                 mStyle = style;
   1411                 if (mStyle != null) {
   1412                     mStyle.setBuilder(this);
   1413                 }
   1414             }
   1415             return this;
   1416         }
   1417 
   1418         /**
   1419          * Sets {@link Notification#color}.
   1420          *
   1421          * @param argb The accent color to use
   1422          *
   1423          * @return The same Builder.
   1424          */
   1425         public Builder setColor(@ColorInt int argb) {
   1426             mColor = argb;
   1427             return this;
   1428         }
   1429 
   1430         /**
   1431          * Sets {@link Notification#visibility}.
   1432          *
   1433          * @param visibility One of {@link Notification#VISIBILITY_PRIVATE} (the default),
   1434          *                   {@link Notification#VISIBILITY_PUBLIC}, or
   1435          *                   {@link Notification#VISIBILITY_SECRET}.
   1436          */
   1437         public Builder setVisibility(@NotificationVisibility int visibility) {
   1438             mVisibility = visibility;
   1439             return this;
   1440         }
   1441 
   1442         /**
   1443          * Supply a replacement Notification whose contents should be shown in insecure contexts
   1444          * (i.e. atop the secure lockscreen). See {@link Notification#visibility} and
   1445          * {@link #VISIBILITY_PUBLIC}.
   1446          *
   1447          * @param n A replacement notification, presumably with some or all info redacted.
   1448          * @return The same Builder.
   1449          */
   1450         public Builder setPublicVersion(Notification n) {
   1451             mPublicVersion = n;
   1452             return this;
   1453         }
   1454 
   1455         /**
   1456          * Supply custom RemoteViews to use instead of the platform template.
   1457          *
   1458          * This will override the layout that would otherwise be constructed by this Builder
   1459          * object.
   1460          */
   1461         public Builder setCustomContentView(RemoteViews contentView) {
   1462             mContentView = contentView;
   1463             return this;
   1464         }
   1465 
   1466         /**
   1467          * Supply custom RemoteViews to use instead of the platform template in the expanded form.
   1468          *
   1469          * This will override the expanded layout that would otherwise be constructed by this
   1470          * Builder object.
   1471          *
   1472          * No-op on versions prior to {@link android.os.Build.VERSION_CODES#JELLY_BEAN}.
   1473          */
   1474         public Builder setCustomBigContentView(RemoteViews contentView) {
   1475             mBigContentView = contentView;
   1476             return this;
   1477         }
   1478 
   1479         /**
   1480          * Supply custom RemoteViews to use instead of the platform template in the heads up dialog.
   1481          *
   1482          * This will override the heads-up layout that would otherwise be constructed by this
   1483          * Builder object.
   1484          *
   1485          * No-op on versions prior to {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
   1486          */
   1487         public Builder setCustomHeadsUpContentView(RemoteViews contentView) {
   1488             mHeadsUpContentView = contentView;
   1489             return this;
   1490         }
   1491 
   1492         /**
   1493          * Specifies the channel the notification should be delivered on.
   1494          *
   1495          * No-op on versions prior to {@link android.os.Build.VERSION_CODES#O} .
   1496          */
   1497         public Builder setChannelId(@NonNull String channelId) {
   1498             mChannelId = channelId;
   1499             return this;
   1500         }
   1501 
   1502         /**
   1503          * Specifies the time at which this notification should be canceled, if it is not already
   1504          * canceled.
   1505          */
   1506         public Builder setTimeoutAfter(long durationMs) {
   1507             mTimeout = durationMs;
   1508             return this;
   1509         }
   1510 
   1511         /**
   1512          * If this notification is duplicative of a Launcher shortcut, sets the
   1513          * {@link androidx.core.content.pm.ShortcutInfoCompat#getId() id} of the shortcut, in
   1514          * case the Launcher wants to hide the shortcut.
   1515          *
   1516          * <p><strong>Note:</strong>This field will be ignored by Launchers that don't support
   1517          * badging or {@link androidx.core.content.pm.ShortcutManagerCompat shortcuts}.
   1518          *
   1519          * @param shortcutId the {@link androidx.core.content.pm.ShortcutInfoCompat#getId() id}
   1520          *                   of the shortcut this notification supersedes
   1521          */
   1522         public Builder setShortcutId(String shortcutId) {
   1523             mShortcutId = shortcutId;
   1524             return this;
   1525         }
   1526 
   1527         /**
   1528          * Sets which icon to display as a badge for this notification.
   1529          *
   1530          * <p>Must be one of {@link #BADGE_ICON_NONE}, {@link #BADGE_ICON_SMALL},
   1531          * {@link #BADGE_ICON_LARGE}.
   1532          *
   1533          * <p><strong>Note:</strong> This value might be ignored, for launchers that don't support
   1534          * badge icons.
   1535          */
   1536         public Builder setBadgeIconType(@BadgeIconType int icon) {
   1537             mBadgeIcon = icon;
   1538             return this;
   1539         }
   1540 
   1541         /**
   1542          * Sets the group alert behavior for this notification. Use this method to mute this
   1543          * notification if alerts for this notification's group should be handled by a different
   1544          * notification. This is only applicable for notifications that belong to a
   1545          * {@link #setGroup(String) group}. This must be called on all notifications you want to
   1546          * mute. For example, if you want only the summary of your group to make noise, all
   1547          * children in the group should have the group alert behavior {@link #GROUP_ALERT_SUMMARY}.
   1548          *
   1549          * <p> The default value is {@link #GROUP_ALERT_ALL}.</p>
   1550          */
   1551         public Builder setGroupAlertBehavior(@GroupAlertBehavior int groupAlertBehavior) {
   1552             mGroupAlertBehavior = groupAlertBehavior;
   1553             return this;
   1554         }
   1555 
   1556         /**
   1557          * Apply an extender to this notification builder. Extenders may be used to add
   1558          * metadata or change options on this builder.
   1559          */
   1560         public Builder extend(Extender extender) {
   1561             extender.extend(this);
   1562             return this;
   1563         }
   1564 
   1565         /**
   1566          * @deprecated Use {@link #build()} instead.
   1567          */
   1568         @Deprecated
   1569         public Notification getNotification() {
   1570             return build();
   1571         }
   1572 
   1573         /**
   1574          * Combine all of the options that have been set and return a new {@link Notification}
   1575          * object.
   1576          */
   1577         public Notification build() {
   1578             return new NotificationCompatBuilder(this).build();
   1579         }
   1580 
   1581         protected static CharSequence limitCharSequenceLength(CharSequence cs) {
   1582             if (cs == null) return cs;
   1583             if (cs.length() > MAX_CHARSEQUENCE_LENGTH) {
   1584                 cs = cs.subSequence(0, MAX_CHARSEQUENCE_LENGTH);
   1585             }
   1586             return cs;
   1587         }
   1588 
   1589         /**
   1590          * @hide
   1591          */
   1592         @RestrictTo(LIBRARY_GROUP)
   1593         public RemoteViews getContentView() {
   1594             return mContentView;
   1595         }
   1596 
   1597         /**
   1598          * @hide
   1599          */
   1600         @RestrictTo(LIBRARY_GROUP)
   1601         public RemoteViews getBigContentView() {
   1602             return mBigContentView;
   1603         }
   1604 
   1605         /**
   1606          * @hide
   1607          */
   1608         @RestrictTo(LIBRARY_GROUP)
   1609         public RemoteViews getHeadsUpContentView() {
   1610             return mHeadsUpContentView;
   1611         }
   1612 
   1613         /**
   1614          * return when if it is showing or 0 otherwise
   1615          *
   1616          * @hide
   1617          */
   1618         @RestrictTo(LIBRARY_GROUP)
   1619         public long getWhenIfShowing() {
   1620             return mShowWhen ? mNotification.when : 0;
   1621         }
   1622 
   1623         /**
   1624          * @return the priority set on the notification
   1625          *
   1626          * @hide
   1627          */
   1628         @RestrictTo(LIBRARY_GROUP)
   1629         public int getPriority() {
   1630             return mPriority;
   1631         }
   1632 
   1633         /**
   1634          * @return the color of the notification
   1635          *
   1636          * @hide
   1637          */
   1638         @RestrictTo(LIBRARY_GROUP)
   1639         public int getColor() {
   1640             return mColor;
   1641         }
   1642     }
   1643 
   1644     /**
   1645      * An object that can apply a rich notification style to a {@link Notification.Builder}
   1646      * object.
   1647      * <br>
   1648      * If the platform does not provide rich notification styles, methods in this class have no
   1649      * effect.
   1650      */
   1651     public static abstract class Style {
   1652         /**
   1653          * @hide
   1654          */
   1655         @RestrictTo(LIBRARY_GROUP)
   1656         protected Builder mBuilder;
   1657         CharSequence mBigContentTitle;
   1658         CharSequence mSummaryText;
   1659         boolean mSummaryTextSet = false;
   1660 
   1661         public void setBuilder(Builder builder) {
   1662             if (mBuilder != builder) {
   1663                 mBuilder = builder;
   1664                 if (mBuilder != null) {
   1665                     mBuilder.setStyle(this);
   1666                 }
   1667             }
   1668         }
   1669 
   1670         public Notification build() {
   1671             Notification notification = null;
   1672             if (mBuilder != null) {
   1673                 notification = mBuilder.build();
   1674             }
   1675             return notification;
   1676         }
   1677 
   1678         /**
   1679          * Applies the compat style data to the framework {@link Notification} in a backwards
   1680          * compatible way. All other data should be stored within the Notification's extras.
   1681          *
   1682          * @hide
   1683          */
   1684         @RestrictTo(LIBRARY_GROUP)
   1685         // TODO: implement for all styles
   1686         public void apply(NotificationBuilderWithBuilderAccessor builder) {
   1687         }
   1688 
   1689         /**
   1690          * @hide
   1691          */
   1692         @RestrictTo(LIBRARY_GROUP)
   1693         public RemoteViews makeContentView(NotificationBuilderWithBuilderAccessor builder) {
   1694             return null;
   1695         }
   1696 
   1697         /**
   1698          * @hide
   1699          */
   1700         @RestrictTo(LIBRARY_GROUP)
   1701         public RemoteViews makeBigContentView(NotificationBuilderWithBuilderAccessor builder) {
   1702             return null;
   1703         }
   1704 
   1705         /**
   1706          * @hide
   1707          */
   1708         @RestrictTo(LIBRARY_GROUP)
   1709         public RemoteViews makeHeadsUpContentView(NotificationBuilderWithBuilderAccessor builder) {
   1710             return null;
   1711         }
   1712 
   1713         /**
   1714          * @hide
   1715          */
   1716         @RestrictTo(LIBRARY_GROUP)
   1717         // TODO: implement for all styles
   1718         public void addCompatExtras(Bundle extras) {
   1719         }
   1720 
   1721         /**
   1722          * @hide
   1723          */
   1724         @RestrictTo(LIBRARY_GROUP)
   1725         // TODO: implement for all styles
   1726         protected void restoreFromCompatExtras(Bundle extras) {
   1727         }
   1728 
   1729         /**
   1730          * @hide
   1731          */
   1732         @RestrictTo(LIBRARY_GROUP)
   1733         public RemoteViews applyStandardTemplate(boolean showSmallIcon,
   1734                 int resId, boolean fitIn1U) {
   1735             Resources res = mBuilder.mContext.getResources();
   1736             RemoteViews contentView = new RemoteViews(mBuilder.mContext.getPackageName(), resId);
   1737             boolean showLine3 = false;
   1738             boolean showLine2 = false;
   1739 
   1740             boolean minPriority = mBuilder.getPriority() < NotificationCompat.PRIORITY_LOW;
   1741             if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT < 21) {
   1742                 // lets color the backgrounds
   1743                 if (minPriority) {
   1744                     contentView.setInt(R.id.notification_background,
   1745                             "setBackgroundResource", R.drawable.notification_bg_low);
   1746                     contentView.setInt(R.id.icon,
   1747                             "setBackgroundResource", R.drawable.notification_template_icon_low_bg);
   1748                 } else {
   1749                     contentView.setInt(R.id.notification_background,
   1750                             "setBackgroundResource", R.drawable.notification_bg);
   1751                     contentView.setInt(R.id.icon,
   1752                             "setBackgroundResource", R.drawable.notification_template_icon_bg);
   1753                 }
   1754             }
   1755 
   1756             if (mBuilder.mLargeIcon != null) {
   1757                 // On versions before Jellybean, the large icon was shown by SystemUI, so we need
   1758                 // to hide it here.
   1759                 if (Build.VERSION.SDK_INT >= 16) {
   1760                     contentView.setViewVisibility(R.id.icon, View.VISIBLE);
   1761                     contentView.setImageViewBitmap(R.id.icon, mBuilder.mLargeIcon);
   1762                 } else {
   1763                     contentView.setViewVisibility(R.id.icon, View.GONE);
   1764                 }
   1765                 if (showSmallIcon && mBuilder.mNotification.icon != 0) {
   1766                     int backgroundSize = res.getDimensionPixelSize(
   1767                             R.dimen.notification_right_icon_size);
   1768                     int iconSize = backgroundSize - res.getDimensionPixelSize(
   1769                             R.dimen.notification_small_icon_background_padding) * 2;
   1770                     if (Build.VERSION.SDK_INT >= 21) {
   1771                         Bitmap smallBit = createIconWithBackground(
   1772                                 mBuilder.mNotification.icon,
   1773                                 backgroundSize,
   1774                                 iconSize,
   1775                                 mBuilder.getColor());
   1776                         contentView.setImageViewBitmap(R.id.right_icon, smallBit);
   1777                     } else {
   1778                         contentView.setImageViewBitmap(R.id.right_icon, createColoredBitmap(
   1779                                 mBuilder.mNotification.icon, Color.WHITE));
   1780                     }
   1781                     contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
   1782                 }
   1783             } else if (showSmallIcon && mBuilder.mNotification.icon != 0) { // small icon at left
   1784                 contentView.setViewVisibility(R.id.icon, View.VISIBLE);
   1785                 if (Build.VERSION.SDK_INT >= 21) {
   1786                     int backgroundSize = res.getDimensionPixelSize(
   1787                             R.dimen.notification_large_icon_width)
   1788                             - res.getDimensionPixelSize(R.dimen.notification_big_circle_margin);
   1789                     int iconSize = res.getDimensionPixelSize(
   1790                             R.dimen.notification_small_icon_size_as_large);
   1791                     Bitmap smallBit = createIconWithBackground(
   1792                             mBuilder.mNotification.icon,
   1793                             backgroundSize,
   1794                             iconSize,
   1795                             mBuilder.getColor());
   1796                     contentView.setImageViewBitmap(R.id.icon, smallBit);
   1797                 } else {
   1798                     contentView.setImageViewBitmap(R.id.icon, createColoredBitmap(
   1799                             mBuilder.mNotification.icon, Color.WHITE));
   1800                 }
   1801             }
   1802             if (mBuilder.mContentTitle != null) {
   1803                 contentView.setTextViewText(R.id.title, mBuilder.mContentTitle);
   1804             }
   1805             if (mBuilder.mContentText != null) {
   1806                 contentView.setTextViewText(R.id.text, mBuilder.mContentText);
   1807                 showLine3 = true;
   1808             }
   1809             // If there is a large icon we have a right side
   1810             boolean hasRightSide = !(Build.VERSION.SDK_INT >= 21) && mBuilder.mLargeIcon != null;
   1811             if (mBuilder.mContentInfo != null) {
   1812                 contentView.setTextViewText(R.id.info, mBuilder.mContentInfo);
   1813                 contentView.setViewVisibility(R.id.info, View.VISIBLE);
   1814                 showLine3 = true;
   1815                 hasRightSide = true;
   1816             } else if (mBuilder.mNumber > 0) {
   1817                 final int tooBig = res.getInteger(
   1818                         R.integer.status_bar_notification_info_maxnum);
   1819                 if (mBuilder.mNumber > tooBig) {
   1820                     contentView.setTextViewText(R.id.info, ((Resources) res).getString(
   1821                             R.string.status_bar_notification_info_overflow));
   1822                 } else {
   1823                     NumberFormat f = NumberFormat.getIntegerInstance();
   1824                     contentView.setTextViewText(R.id.info, f.format(mBuilder.mNumber));
   1825                 }
   1826                 contentView.setViewVisibility(R.id.info, View.VISIBLE);
   1827                 showLine3 = true;
   1828                 hasRightSide = true;
   1829             } else {
   1830                 contentView.setViewVisibility(R.id.info, View.GONE);
   1831             }
   1832 
   1833             // Need to show three lines? Only allow on Jellybean+
   1834             if (mBuilder.mSubText != null && Build.VERSION.SDK_INT >= 16) {
   1835                 contentView.setTextViewText(R.id.text, mBuilder.mSubText);
   1836                 if (mBuilder.mContentText != null) {
   1837                     contentView.setTextViewText(R.id.text2, mBuilder.mContentText);
   1838                     contentView.setViewVisibility(R.id.text2, View.VISIBLE);
   1839                     showLine2 = true;
   1840                 } else {
   1841                     contentView.setViewVisibility(R.id.text2, View.GONE);
   1842                 }
   1843             }
   1844 
   1845             // RemoteViews.setViewPadding and RemoteViews.setTextViewTextSize is not available on
   1846             // ICS-
   1847             if (showLine2 && Build.VERSION.SDK_INT >= 16) {
   1848                 if (fitIn1U) {
   1849                     // need to shrink all the type to make sure everything fits
   1850                     final float subTextSize = res.getDimensionPixelSize(
   1851                             R.dimen.notification_subtext_size);
   1852                     contentView.setTextViewTextSize(R.id.text, TypedValue.COMPLEX_UNIT_PX,
   1853                             subTextSize);
   1854                 }
   1855                 // vertical centering
   1856                 contentView.setViewPadding(R.id.line1, 0, 0, 0, 0);
   1857             }
   1858 
   1859             if (mBuilder.getWhenIfShowing() != 0) {
   1860                 if (mBuilder.mUseChronometer && Build.VERSION.SDK_INT >= 16) {
   1861                     contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
   1862                     contentView.setLong(R.id.chronometer, "setBase",
   1863                             mBuilder.getWhenIfShowing()
   1864                                     + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
   1865                     contentView.setBoolean(R.id.chronometer, "setStarted", true);
   1866                 } else {
   1867                     contentView.setViewVisibility(R.id.time, View.VISIBLE);
   1868                     contentView.setLong(R.id.time, "setTime", mBuilder.getWhenIfShowing());
   1869                 }
   1870                 hasRightSide = true;
   1871             }
   1872             contentView.setViewVisibility(R.id.right_side, hasRightSide ? View.VISIBLE : View.GONE);
   1873             contentView.setViewVisibility(R.id.line3, showLine3 ? View.VISIBLE : View.GONE);
   1874             return contentView;
   1875         }
   1876 
   1877         /**
   1878          * @hide
   1879          */
   1880         @RestrictTo(LIBRARY_GROUP)
   1881         public Bitmap createColoredBitmap(int iconId, int color) {
   1882             return createColoredBitmap(iconId, color, 0);
   1883         }
   1884 
   1885         private Bitmap createColoredBitmap(int iconId, int color, int size) {
   1886             Drawable drawable = mBuilder.mContext.getResources().getDrawable(iconId);
   1887             int width = size == 0 ? drawable.getIntrinsicWidth() : size;
   1888             int height = size == 0 ? drawable.getIntrinsicHeight() : size;
   1889             Bitmap resultBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
   1890             drawable.setBounds(0, 0, width, height);
   1891             if (color != 0) {
   1892                 drawable.mutate().setColorFilter(
   1893                         new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
   1894             }
   1895             Canvas canvas = new Canvas(resultBitmap);
   1896             drawable.draw(canvas);
   1897             return resultBitmap;
   1898         }
   1899 
   1900         private Bitmap createIconWithBackground(int iconId, int size,
   1901                 int iconSize, int color) {
   1902             Bitmap coloredBitmap = createColoredBitmap(R.drawable.notification_icon_background,
   1903                     color == NotificationCompat.COLOR_DEFAULT ? 0 : color, size);
   1904             Canvas canvas = new Canvas(coloredBitmap);
   1905             Drawable icon = mBuilder.mContext.getResources().getDrawable(iconId).mutate();
   1906             icon.setFilterBitmap(true);
   1907             int inset = (size - iconSize) / 2;
   1908             icon.setBounds(inset, inset, iconSize + inset, iconSize + inset);
   1909             icon.setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP));
   1910             icon.draw(canvas);
   1911             return coloredBitmap;
   1912         }
   1913 
   1914         /**
   1915          * @hide
   1916          */
   1917         @RestrictTo(LIBRARY_GROUP)
   1918         public void buildIntoRemoteViews(RemoteViews outerView,
   1919                 RemoteViews innerView) {
   1920             // this needs to be done fore the other calls, since otherwise we might hide the wrong
   1921             // things if our ids collide.
   1922             hideNormalContent(outerView);
   1923             outerView.removeAllViews(R.id.notification_main_column);
   1924             outerView.addView(R.id.notification_main_column, innerView.clone());
   1925             outerView.setViewVisibility(R.id.notification_main_column, View.VISIBLE);
   1926             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
   1927                 // Adjust padding depending on font size.
   1928                 outerView.setViewPadding(R.id.notification_main_column_container,
   1929                         0, calculateTopPadding(), 0, 0);
   1930             }
   1931         }
   1932 
   1933         private void hideNormalContent(RemoteViews outerView) {
   1934             outerView.setViewVisibility(R.id.title, View.GONE);
   1935             outerView.setViewVisibility(R.id.text2, View.GONE);
   1936             outerView.setViewVisibility(R.id.text, View.GONE);
   1937         }
   1938 
   1939         private int calculateTopPadding() {
   1940             Resources resources = mBuilder.mContext.getResources();
   1941             int padding = resources.getDimensionPixelSize(R.dimen.notification_top_pad);
   1942             int largePadding = resources.getDimensionPixelSize(
   1943                     R.dimen.notification_top_pad_large_text);
   1944             float fontScale = resources.getConfiguration().fontScale;
   1945             float largeFactor = (constrain(fontScale, 1.0f, 1.3f) - 1f) / (1.3f - 1f);
   1946 
   1947             // Linearly interpolate the padding between large and normal with the font scale ranging
   1948             // from 1f to LARGE_TEXT_SCALE
   1949             return Math.round((1 - largeFactor) * padding + largeFactor * largePadding);
   1950         }
   1951 
   1952         private static float constrain(float amount, float low, float high) {
   1953             return amount < low ? low : (amount > high ? high : amount);
   1954         }
   1955     }
   1956 
   1957     /**
   1958      * Helper class for generating large-format notifications that include a large image attachment.
   1959      * <br>
   1960      * If the platform does not provide large-format notifications, this method has no effect. The
   1961      * user will always see the normal notification view.
   1962      * <br>
   1963      * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior, like so:
   1964      * <pre class="prettyprint">
   1965      * Notification notification = new Notification.Builder(mContext)
   1966      *     .setContentTitle(&quot;New photo from &quot; + sender.toString())
   1967      *     .setContentText(subject)
   1968      *     .setSmallIcon(R.drawable.new_post)
   1969      *     .setLargeIcon(aBitmap)
   1970      *     .setStyle(new Notification.BigPictureStyle()
   1971      *         .bigPicture(aBigBitmap))
   1972      *     .build();
   1973      * </pre>
   1974      *
   1975      * @see Notification#bigContentView
   1976      */
   1977     public static class BigPictureStyle extends Style {
   1978         private Bitmap mPicture;
   1979         private Bitmap mBigLargeIcon;
   1980         private boolean mBigLargeIconSet;
   1981 
   1982         public BigPictureStyle() {
   1983         }
   1984 
   1985         public BigPictureStyle(Builder builder) {
   1986             setBuilder(builder);
   1987         }
   1988 
   1989         /**
   1990          * Overrides ContentTitle in the big form of the template.
   1991          * This defaults to the value passed to setContentTitle().
   1992          */
   1993         public BigPictureStyle setBigContentTitle(CharSequence title) {
   1994             mBigContentTitle = Builder.limitCharSequenceLength(title);
   1995             return this;
   1996         }
   1997 
   1998         /**
   1999          * Set the first line of text after the detail section in the big form of the template.
   2000          */
   2001         public BigPictureStyle setSummaryText(CharSequence cs) {
   2002             mSummaryText = Builder.limitCharSequenceLength(cs);
   2003             mSummaryTextSet = true;
   2004             return this;
   2005         }
   2006 
   2007         /**
   2008          * Provide the bitmap to be used as the payload for the BigPicture notification.
   2009          */
   2010         public BigPictureStyle bigPicture(Bitmap b) {
   2011             mPicture = b;
   2012             return this;
   2013         }
   2014 
   2015         /**
   2016          * Override the large icon when the big notification is shown.
   2017          */
   2018         public BigPictureStyle bigLargeIcon(Bitmap b) {
   2019             mBigLargeIcon = b;
   2020             mBigLargeIconSet = true;
   2021             return this;
   2022         }
   2023 
   2024         /**
   2025          * @hide
   2026          */
   2027         @RestrictTo(LIBRARY_GROUP)
   2028         @Override
   2029         public void apply(NotificationBuilderWithBuilderAccessor builder) {
   2030             if (Build.VERSION.SDK_INT >= 16) {
   2031                 Notification.BigPictureStyle style =
   2032                         new Notification.BigPictureStyle(builder.getBuilder())
   2033                                 .setBigContentTitle(mBigContentTitle)
   2034                                 .bigPicture(mPicture);
   2035                 if (mBigLargeIconSet) {
   2036                     style.bigLargeIcon(mBigLargeIcon);
   2037                 }
   2038                 if (mSummaryTextSet) {
   2039                     style.setSummaryText(mSummaryText);
   2040                 }
   2041             }
   2042         }
   2043     }
   2044 
   2045     /**
   2046      * Helper class for generating large-format notifications that include a lot of text.
   2047      *
   2048      * <br>
   2049      * If the platform does not provide large-format notifications, this method has no effect. The
   2050      * user will always see the normal notification view.
   2051      * <br>
   2052      * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior, like so:
   2053      * <pre class="prettyprint">
   2054      * Notification notification = new Notification.Builder(mContext)
   2055      *     .setContentTitle(&quot;New mail from &quot; + sender.toString())
   2056      *     .setContentText(subject)
   2057      *     .setSmallIcon(R.drawable.new_mail)
   2058      *     .setLargeIcon(aBitmap)
   2059      *     .setStyle(new Notification.BigTextStyle()
   2060      *         .bigText(aVeryLongString))
   2061      *     .build();
   2062      * </pre>
   2063      *
   2064      * @see Notification#bigContentView
   2065      */
   2066     public static class BigTextStyle extends Style {
   2067         private CharSequence mBigText;
   2068 
   2069         public BigTextStyle() {
   2070         }
   2071 
   2072         public BigTextStyle(Builder builder) {
   2073             setBuilder(builder);
   2074         }
   2075 
   2076         /**
   2077          * Overrides ContentTitle in the big form of the template.
   2078          * This defaults to the value passed to setContentTitle().
   2079          */
   2080         public BigTextStyle setBigContentTitle(CharSequence title) {
   2081             mBigContentTitle = Builder.limitCharSequenceLength(title);
   2082             return this;
   2083         }
   2084 
   2085         /**
   2086          * Set the first line of text after the detail section in the big form of the template.
   2087          */
   2088         public BigTextStyle setSummaryText(CharSequence cs) {
   2089             mSummaryText = Builder.limitCharSequenceLength(cs);
   2090             mSummaryTextSet = true;
   2091             return this;
   2092         }
   2093 
   2094         /**
   2095          * Provide the longer text to be displayed in the big form of the
   2096          * template in place of the content text.
   2097          */
   2098         public BigTextStyle bigText(CharSequence cs) {
   2099             mBigText = Builder.limitCharSequenceLength(cs);
   2100             return this;
   2101         }
   2102 
   2103         /**
   2104          * @hide
   2105          */
   2106         @RestrictTo(LIBRARY_GROUP)
   2107         @Override
   2108         public void apply(NotificationBuilderWithBuilderAccessor builder) {
   2109             if (Build.VERSION.SDK_INT >= 16) {
   2110                 Notification.BigTextStyle style =
   2111                         new Notification.BigTextStyle(builder.getBuilder())
   2112                                 .setBigContentTitle(mBigContentTitle)
   2113                                 .bigText(mBigText);
   2114                 if (mSummaryTextSet) {
   2115                     style.setSummaryText(mSummaryText);
   2116                 }
   2117             }
   2118         }
   2119     }
   2120 
   2121     /**
   2122      * Helper class for generating large-format notifications that include multiple back-and-forth
   2123      * messages of varying types between any number of people.
   2124      *
   2125      * <br>
   2126      * In order to get a backwards compatible behavior, the app needs to use the v7 version of the
   2127      * notification builder together with this style, otherwise the user will see the normal
   2128      * notification view.
   2129      *
   2130      * <br>
   2131      * Use {@link MessagingStyle#setConversationTitle(CharSequence)} to set a conversation title for
   2132      * group chats with more than two people. This could be the user-created name of the group or,
   2133      * if it doesn't have a specific name, a list of the participants in the conversation. Do not
   2134      * set a conversation title for one-on-one chats, since platforms use the existence of this
   2135      * field as a hint that the conversation is a group.
   2136      *
   2137      * <br>
   2138      * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior, like
   2139      * so:
   2140      * <pre class="prettyprint">
   2141      *
   2142      * Notification notification = new Notification.Builder()
   2143      *     .setContentTitle(&quot;2 new messages with &quot; + sender.toString())
   2144      *     .setContentText(subject)
   2145      *     .setSmallIcon(R.drawable.new_message)
   2146      *     .setLargeIcon(aBitmap)
   2147      *     .setStyle(new Notification.MessagingStyle(resources.getString(R.string.reply_name))
   2148      *         .addMessage(messages[0].getText(), messages[0].getTime(), messages[0].getSender())
   2149      *         .addMessage(messages[1].getText(), messages[1].getTime(), messages[1].getSender()))
   2150      *     .build();
   2151      * </pre>
   2152      */
   2153     public static class MessagingStyle extends Style {
   2154 
   2155         /**
   2156          * The maximum number of messages that will be retained in the Notification itself (the
   2157          * number displayed is up to the platform).
   2158          */
   2159         public static final int MAXIMUM_RETAINED_MESSAGES = 25;
   2160 
   2161         private final List<Message> mMessages = new ArrayList<>();
   2162         private Person mUser;
   2163         private @Nullable CharSequence mConversationTitle;
   2164         private @Nullable Boolean mIsGroupConversation;
   2165 
   2166         /** Private empty constructor for {@link Style#restoreFromCompatExtras(Bundle)}. */
   2167         private MessagingStyle() {}
   2168 
   2169         /**
   2170          * @param userDisplayName Required - the name to be displayed for any replies sent by the
   2171          * user before the posting app reposts the notification with those messages after they've
   2172          * been actually sent and in previous messages sent by the user added in
   2173          * {@link #addMessage(Message)}
   2174          * @deprecated Use {@code #MessagingStyle(Person)} instead.
   2175          */
   2176         @Deprecated
   2177         public MessagingStyle(@NonNull CharSequence userDisplayName) {
   2178             mUser = new Person.Builder().setName(userDisplayName).build();
   2179         }
   2180 
   2181         /**
   2182          * Creates a new {@link MessagingStyle} object. Note that {@link Person} must have a
   2183          * non-empty name.
   2184          *
   2185          * @param user This {@link Person}'s name will be shown when this app's notification is
   2186          * being replied to. It's used temporarily so the app has time to process the send request
   2187          * and repost the notification with updates to the conversation.
   2188          */
   2189         public MessagingStyle(@NonNull Person user) {
   2190             if (TextUtils.isEmpty(user.getName())) {
   2191                 throw new IllegalArgumentException("User's name must not be empty.");
   2192             }
   2193             mUser = user;
   2194         }
   2195 
   2196         /**
   2197          * Returns the name to be displayed for any replies sent by the user.
   2198          *
   2199          * @deprecated Use {@link #getUser()} instead.
   2200          */
   2201         @Deprecated
   2202         public CharSequence getUserDisplayName() {
   2203             return mUser.getName();
   2204         }
   2205 
   2206         /** Returns the person to be used for any replies sent by the user. */
   2207         public Person getUser() {
   2208             return mUser;
   2209         }
   2210 
   2211         /**
   2212          * Sets the title to be displayed on this conversation. May be set to {@code null}.
   2213          *
   2214          * <p>This API's behavior was changed in SDK version {@link Build.VERSION_CODES#P}. If your
   2215          * application's target version is less than {@link Build.VERSION_CODES#P}, setting a
   2216          * conversation title to a non-null value will make {@link #isGroupConversation()} return
   2217          * {@code true} and passing {@code null} will make it return {@code false}. This behavior
   2218          * can be overridden by calling {@link #setGroupConversation(boolean)} regardless of SDK
   2219          * version. In {@code P} and above, this method does not affect group conversation settings.
   2220          *
   2221          * @param conversationTitle Title displayed for this conversation
   2222          * @return this object for method chaining
   2223          */
   2224         public MessagingStyle setConversationTitle(@Nullable CharSequence conversationTitle) {
   2225             mConversationTitle = conversationTitle;
   2226             return this;
   2227         }
   2228 
   2229         /**
   2230          * Return the title to be displayed on this conversation. Can be {@code null}.
   2231          */
   2232         @Nullable
   2233         public CharSequence getConversationTitle() {
   2234             return mConversationTitle;
   2235         }
   2236 
   2237         /**
   2238          * Adds a message for display by this notification. Convenience call for a simple
   2239          * {@link Message} in {@link #addMessage(Message)}
   2240          * @param text A {@link CharSequence} to be displayed as the message content
   2241          * @param timestamp Time at which the message arrived in ms since Unix epoch
   2242          * @param sender A {@link CharSequence} to be used for displaying the name of the
   2243          * sender. Should be <code>null</code> for messages by the current user, in which case
   2244          * the platform will insert {@link #getUserDisplayName()}.
   2245          * Should be unique amongst all individuals in the conversation, and should be
   2246          * consistent during re-posts of the notification.
   2247          *
   2248          * @see Message#Message(CharSequence, long, CharSequence)
   2249          *
   2250          * @return this object for method chaining
   2251          *
   2252          * @deprecated Use {@link #addMessage(CharSequence, long, Person)} or
   2253          * {@link #addMessage(Message)}
   2254          */
   2255         @Deprecated
   2256         public MessagingStyle addMessage(CharSequence text, long timestamp, CharSequence sender) {
   2257             mMessages.add(
   2258                     new Message(text, timestamp, new Person.Builder().setName(sender).build()));
   2259             if (mMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
   2260                 mMessages.remove(0);
   2261             }
   2262             return this;
   2263         }
   2264 
   2265         /**
   2266          * Adds a message for display by this notification. Convenience call for
   2267          * {@link #addMessage(Message)}.
   2268          *
   2269          * @see Message#Message(CharSequence, long, Person)
   2270          *
   2271          * @return this for method chaining
   2272          */
   2273         public MessagingStyle addMessage(CharSequence text, long timestamp, Person person) {
   2274             addMessage(new Message(text, timestamp, person));
   2275             return this;
   2276         }
   2277 
   2278         /**
   2279          * Adds a {@link Message} for display in this notification.
   2280          *
   2281          * @param message The {@link Message} to be displayed
   2282          *
   2283          * @return this object for method chaining
   2284          */
   2285         public MessagingStyle addMessage(Message message) {
   2286             mMessages.add(message);
   2287             if (mMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
   2288                 mMessages.remove(0);
   2289             }
   2290             return this;
   2291         }
   2292 
   2293         /**
   2294          * Gets the list of {@code Message} objects that represent the notification
   2295          */
   2296         public List<Message> getMessages() {
   2297             return mMessages;
   2298         }
   2299 
   2300         /**
   2301          * Sets whether this conversation notification represents a group.
   2302          * @param isGroupConversation {@code true} if the conversation represents a group,
   2303          * {@code false} otherwise.
   2304          * @return this object for method chaining
   2305          */
   2306         public MessagingStyle setGroupConversation(boolean isGroupConversation) {
   2307             mIsGroupConversation = isGroupConversation;
   2308             return this;
   2309         }
   2310 
   2311         /**
   2312          * Returns {@code true} if this notification represents a group conversation, otherwise
   2313          * {@code false}.
   2314          *
   2315          * <p> If the application that generated this {@link MessagingStyle} targets an SDK version
   2316          * less than {@link Build.VERSION_CODES#P} and {@link #setGroupConversation(boolean)}
   2317          * was not called, this method becomes dependent on whether or not the conversation title is
   2318          * set; returning {@code true} if the conversation title is a non-null value, or
   2319          * {@code false} otherwise. This is to maintain backwards compatibility. Regardless, {@link
   2320          * #setGroupConversation(boolean)} has precedence over this legacy behavior. From {@code P}
   2321          * forward, {@link #setConversationTitle(CharSequence)} has no affect on group conversation
   2322          * status.
   2323          *
   2324          * @see #setConversationTitle(CharSequence)
   2325          */
   2326         public boolean isGroupConversation() {
   2327             // When target SDK version is < P and the app didn't explicitly set isGroupConversation,
   2328             // a non-null conversation title dictates if this is a group conversation.
   2329             if (mBuilder != null
   2330                     && mBuilder.mContext.getApplicationInfo().targetSdkVersion
   2331                     < Build.VERSION_CODES.P
   2332                     && mIsGroupConversation == null) {
   2333                 return mConversationTitle != null;
   2334             }
   2335 
   2336             // Default to false if not set.
   2337             return (mIsGroupConversation != null) ? mIsGroupConversation : false;
   2338         }
   2339 
   2340         /**
   2341          * Retrieves a {@link MessagingStyle} from a {@link Notification}, enabling an application
   2342          * that has set a {@link MessagingStyle} using {@link NotificationCompat} or
   2343          * {@link android.app.Notification.Builder} to send messaging information to another
   2344          * application using {@link NotificationCompat}, regardless of the API level of the system.
   2345          * Returns {@code null} if there is no {@link MessagingStyle} set.
   2346          */
   2347         public static MessagingStyle extractMessagingStyleFromNotification(
   2348                 Notification notification) {
   2349             Bundle extras = NotificationCompat.getExtras(notification);
   2350             if (extras != null
   2351                     && !extras.containsKey(EXTRA_SELF_DISPLAY_NAME)
   2352                     && !extras.containsKey(EXTRA_MESSAGING_STYLE_USER)) {
   2353                 return null;
   2354             }
   2355 
   2356             try {
   2357                 MessagingStyle style = new MessagingStyle();
   2358                 style.restoreFromCompatExtras(extras);
   2359                 return style;
   2360             } catch (ClassCastException e) {
   2361                 return null;
   2362             }
   2363         }
   2364 
   2365         /**
   2366          * @hide
   2367          */
   2368         @RestrictTo(LIBRARY_GROUP)
   2369         @Override
   2370         public void apply(NotificationBuilderWithBuilderAccessor builder) {
   2371             // This is called because we need to apply legacy logic before writing MessagingInfo
   2372             // data into the bundle. This does nothing in >= P, but in < P this will apply the
   2373             // correct group conversation status to new fields which will then be decoded properly
   2374             // by #extractMessagingStyleFromNotification.
   2375             setGroupConversation(isGroupConversation());
   2376 
   2377             if (Build.VERSION.SDK_INT >= 24) {
   2378                 Notification.MessagingStyle style =
   2379                         new Notification.MessagingStyle(mUser.getName());
   2380 
   2381                 // In SDK < 28, base Android will assume a MessagingStyle notification is a group
   2382                 // chat if the conversation title is set. In compat, this isn't the case as we've
   2383                 // introduced #setGroupConversation. When we apply these settings to base Android
   2384                 // notifications, we should only set base Android's MessagingStyle conversation
   2385                 // title if it's a group conversation OR SDK >= 28. Otherwise we set the
   2386                 // Notification content title so Android won't think it's a group conversation.
   2387                 if (isGroupConversation() || Build.VERSION.SDK_INT >= 28) {
   2388                     // If group or non-legacy, set MessagingStyle#mConversationTitle.
   2389                     style.setConversationTitle(mConversationTitle);
   2390                 } else {
   2391                     // Otherwise set Notification#mContentTitle.
   2392                     builder.getBuilder().setContentTitle(mConversationTitle);
   2393                 }
   2394 
   2395                 // For SDK >= 28, we can simply denote the group conversation status regardless of
   2396                 // if we set the conversation title or not.
   2397                 if (Build.VERSION.SDK_INT >= 28) {
   2398                     style.setGroupConversation(mIsGroupConversation);
   2399                 }
   2400 
   2401                 for (MessagingStyle.Message message : mMessages) {
   2402                     CharSequence name = null;
   2403                     if (message.getPerson() != null) {
   2404                         name = message.getPerson().getName();
   2405                     }
   2406                     Notification.MessagingStyle.Message frameworkMessage =
   2407                             new Notification.MessagingStyle.Message(
   2408                                     message.getText(), message.getTimestamp(), name);
   2409                     if (message.getDataMimeType() != null) {
   2410                         frameworkMessage.setData(message.getDataMimeType(), message.getDataUri());
   2411                     }
   2412                     style.addMessage(frameworkMessage);
   2413                 }
   2414                 style.setBuilder(builder.getBuilder());
   2415             } else {
   2416                 MessagingStyle.Message latestIncomingMessage = findLatestIncomingMessage();
   2417                 // Set the title
   2418                 if (mConversationTitle != null) {
   2419                     builder.getBuilder().setContentTitle(mConversationTitle);
   2420                 } else if (latestIncomingMessage != null) {
   2421                     builder.getBuilder().setContentTitle("");
   2422                     if (latestIncomingMessage.getPerson() != null) {
   2423                         builder.getBuilder().setContentTitle(
   2424                                 latestIncomingMessage.getPerson().getName());
   2425                     }
   2426                 }
   2427                 // Set the text
   2428                 if (latestIncomingMessage != null) {
   2429                     builder.getBuilder().setContentText(mConversationTitle != null
   2430                             ? makeMessageLine(latestIncomingMessage)
   2431                             : latestIncomingMessage.getText());
   2432                 }
   2433                 // Build a fallback BigTextStyle for API 16-23 devices
   2434                 if (Build.VERSION.SDK_INT >= 16) {
   2435                     SpannableStringBuilder completeMessage = new SpannableStringBuilder();
   2436                     boolean showNames = mConversationTitle != null
   2437                             || hasMessagesWithoutSender();
   2438                     for (int i = mMessages.size() - 1; i >= 0; i--) {
   2439                         MessagingStyle.Message message = mMessages.get(i);
   2440                         CharSequence line;
   2441                         line = showNames ? makeMessageLine(message) : message.getText();
   2442                         if (i != mMessages.size() - 1) {
   2443                             completeMessage.insert(0, "\n");
   2444                         }
   2445                         completeMessage.insert(0, line);
   2446                     }
   2447                     new Notification.BigTextStyle(builder.getBuilder())
   2448                             .setBigContentTitle(null)
   2449                             .bigText(completeMessage);
   2450                 }
   2451             }
   2452         }
   2453 
   2454         @Nullable
   2455         private MessagingStyle.Message findLatestIncomingMessage() {
   2456             for (int i = mMessages.size() - 1; i >= 0; i--) {
   2457                 MessagingStyle.Message message = mMessages.get(i);
   2458                 // Incoming messages have a non-empty sender.
   2459                 if (message.getPerson() != null
   2460                         && !TextUtils.isEmpty(message.getPerson().getName())) {
   2461                     return message;
   2462                 }
   2463             }
   2464             if (!mMessages.isEmpty()) {
   2465                 // No incoming messages, fall back to outgoing message
   2466                 return mMessages.get(mMessages.size() - 1);
   2467             }
   2468             return null;
   2469         }
   2470 
   2471         private boolean hasMessagesWithoutSender() {
   2472             for (int i = mMessages.size() - 1; i >= 0; i--) {
   2473                 MessagingStyle.Message message = mMessages.get(i);
   2474                 if (message.getPerson() != null && message.getPerson().getName() == null) {
   2475                     return true;
   2476                 }
   2477             }
   2478             return false;
   2479         }
   2480 
   2481         private CharSequence makeMessageLine(MessagingStyle.Message message) {
   2482             BidiFormatter bidi = BidiFormatter.getInstance();
   2483             SpannableStringBuilder sb = new SpannableStringBuilder();
   2484             final boolean afterLollipop = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
   2485             int color = afterLollipop ? Color.BLACK : Color.WHITE;
   2486             CharSequence replyName =
   2487                     message.getPerson() == null ? "" : message.getPerson().getName();
   2488             if (TextUtils.isEmpty(replyName)) {
   2489                 replyName = mUser.getName();
   2490                 color = afterLollipop && mBuilder.getColor() != NotificationCompat.COLOR_DEFAULT
   2491                         ? mBuilder.getColor()
   2492                         : color;
   2493             }
   2494             CharSequence senderText = bidi.unicodeWrap(replyName);
   2495             sb.append(senderText);
   2496             sb.setSpan(makeFontColorSpan(color),
   2497                     sb.length() - senderText.length(),
   2498                     sb.length(),
   2499                     Spanned.SPAN_EXCLUSIVE_EXCLUSIVE /* flags */);
   2500             CharSequence text = message.getText() == null ? "" : message.getText();
   2501             sb.append("  ").append(bidi.unicodeWrap(text));
   2502             return sb;
   2503         }
   2504 
   2505         @NonNull
   2506         private TextAppearanceSpan makeFontColorSpan(int color) {
   2507             return new TextAppearanceSpan(null, 0, 0, ColorStateList.valueOf(color), null);
   2508         }
   2509 
   2510         @Override
   2511         public void addCompatExtras(Bundle extras) {
   2512             super.addCompatExtras(extras);
   2513             extras.putCharSequence(EXTRA_SELF_DISPLAY_NAME, mUser.getName());
   2514             extras.putBundle(EXTRA_MESSAGING_STYLE_USER, mUser.toBundle());
   2515 
   2516             if (mConversationTitle != null) {
   2517                 extras.putCharSequence(EXTRA_CONVERSATION_TITLE, mConversationTitle);
   2518             }
   2519             if (!mMessages.isEmpty()) {
   2520                 extras.putParcelableArray(
   2521                         EXTRA_MESSAGES, Message.getBundleArrayForMessages(mMessages));
   2522             }
   2523             if (mIsGroupConversation != null) {
   2524                 extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation);
   2525             }
   2526         }
   2527 
   2528         /**
   2529          * @hide
   2530          */
   2531         @RestrictTo(LIBRARY_GROUP)
   2532         @Override
   2533         protected void restoreFromCompatExtras(Bundle extras) {
   2534             mMessages.clear();
   2535             // Call to #restore requires that there either be a display name OR a user.
   2536             if (extras.containsKey(EXTRA_MESSAGING_STYLE_USER)) {
   2537                 // New path simply unpacks Person, but checks if there's a valid name.
   2538                 mUser = Person.fromBundle(extras.getBundle(EXTRA_MESSAGING_STYLE_USER));
   2539             } else {
   2540                 // Legacy extra simply builds Person with a name.
   2541                 mUser = new Person.Builder()
   2542                         .setName(extras.getString(EXTRA_SELF_DISPLAY_NAME))
   2543                         .build();
   2544             }
   2545 
   2546             mConversationTitle = extras.getString(EXTRA_CONVERSATION_TITLE);
   2547             Parcelable[] parcelables = extras.getParcelableArray(EXTRA_MESSAGES);
   2548             if (parcelables != null) {
   2549                 mMessages.addAll(Message.getMessagesFromBundleArray(parcelables));
   2550             }
   2551             if (extras.containsKey(EXTRA_IS_GROUP_CONVERSATION)) {
   2552                 mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION);
   2553             }
   2554         }
   2555 
   2556         public static final class Message {
   2557             static final String KEY_TEXT = "text";
   2558             static final String KEY_TIMESTAMP = "time";
   2559             static final String KEY_SENDER = "sender";
   2560             static final String KEY_DATA_MIME_TYPE = "type";
   2561             static final String KEY_DATA_URI= "uri";
   2562             static final String KEY_EXTRAS_BUNDLE = "extras";
   2563             static final String KEY_PERSON = "person";
   2564 
   2565             private final CharSequence mText;
   2566             private final long mTimestamp;
   2567             @Nullable private final Person mPerson;
   2568 
   2569             private Bundle mExtras = new Bundle();
   2570             @Nullable private String mDataMimeType;
   2571             @Nullable private Uri mDataUri;
   2572 
   2573             /**
   2574              * Creates a new {@link Message} with the given text, timestamp, and sender.
   2575              *
   2576              * @param text A {@link CharSequence} to be displayed as the message content
   2577              * @param timestamp Time at which the message arrived in ms since Unix epoch
   2578              * @param person A {@link Person} whose {@link Person#getName()} value is used as the
   2579              * display name for the sender. This should be {@code null} for messages by the current
   2580              * user, in which case, the platform will insert
   2581              * {@link MessagingStyle#getUserDisplayName()}. A {@link Person}'s key should be
   2582              * consistent during re-posts of the notification.
   2583              */
   2584             public Message(CharSequence text, long timestamp, @Nullable Person person) {
   2585                 mText = text;
   2586                 mTimestamp = timestamp;
   2587                 mPerson = person;
   2588             }
   2589 
   2590             /**
   2591              * Constructor
   2592              *
   2593              * @param text A {@link CharSequence} to be displayed as the message content
   2594              * @param timestamp Time at which the message arrived in ms since Unix epoch
   2595              * @param sender A {@link CharSequence} to be used for displaying the name of the
   2596              * sender. Should be <code>null</code> for messages by the current user, in which case
   2597              * the platform will insert {@link MessagingStyle#getUserDisplayName()}.
   2598              * Should be unique amongst all individuals in the conversation, and should be
   2599              * consistent during re-posts of the notification.
   2600              *
   2601              * @deprecated Use the alternative constructor instead.
   2602              */
   2603             @Deprecated
   2604             public Message(CharSequence text, long timestamp, CharSequence sender){
   2605                 this(text, timestamp, new Person.Builder().setName(sender).build());
   2606             }
   2607 
   2608             /**
   2609              * Sets a binary blob of data and an associated MIME type for a message. In the case
   2610              * where the platform doesn't support the MIME type, the original text provided in the
   2611              * constructor will be used.
   2612              *
   2613              * @param dataMimeType The MIME type of the content. See
   2614              * <a href="{@docRoot}notifications/messaging.html"> for the list of supported MIME
   2615              * types on Android and Android Wear.
   2616              * @param dataUri The uri containing the content whose type is given by the MIME type.
   2617              * <p class="note">
   2618              * <ol>
   2619              *   <li>Notification Listeners including the System UI need permission to access the
   2620              *       data the Uri points to. The recommended ways to do this are:</li>
   2621              *   <li>Store the data in your own ContentProvider, making sure that other apps have
   2622              *       the correct permission to access your provider. The preferred mechanism for
   2623              *       providing access is to use per-URI permissions which are temporary and only
   2624              *       grant access to the receiving application. An easy way to create a
   2625              *       ContentProvider like this is to use the FileProvider helper class.</li>
   2626              *   <li>Use the system MediaStore. The MediaStore is primarily aimed at video, audio
   2627              *       and image MIME types, however beginning with Android 3.0 (API level 11) it can
   2628              *       also store non-media types (see MediaStore.Files for more info). Files can be
   2629              *       inserted into the MediaStore using scanFile() after which a content:// style
   2630              *       Uri suitable for sharing is passed to the provided onScanCompleted() callback.
   2631              *       Note that once added to the system MediaStore the content is accessible to any
   2632              *       app on the device.</li>
   2633              * </ol>
   2634              *
   2635              * @return this object for method chaining
   2636              */
   2637             public Message setData(String dataMimeType, Uri dataUri) {
   2638                 mDataMimeType = dataMimeType;
   2639                 mDataUri = dataUri;
   2640                 return this;
   2641             }
   2642 
   2643             /**
   2644              * Get the text to be used for this message, or the fallback text if a type and content
   2645              * Uri have been set
   2646              */
   2647             @NonNull
   2648             public CharSequence getText() {
   2649                 return mText;
   2650             }
   2651 
   2652             /** Get the time at which this message arrived in ms since Unix epoch. */
   2653             public long getTimestamp() {
   2654                 return mTimestamp;
   2655             }
   2656 
   2657             /** Get the extras Bundle for this message. */
   2658             @NonNull
   2659             public Bundle getExtras() {
   2660                 return mExtras;
   2661             }
   2662 
   2663             /**
   2664              * Get the text used to display the contact's name in the messaging experience
   2665              *
   2666              * @deprecated Use {@link #getPerson()}
   2667              */
   2668             @Deprecated
   2669             @Nullable
   2670             public CharSequence getSender() {
   2671                 return mPerson == null ? null : mPerson.getName();
   2672             }
   2673 
   2674             /** Returns the {@link Person} sender of this message. */
   2675             @Nullable
   2676             public Person getPerson() {
   2677                 return mPerson;
   2678             }
   2679 
   2680             /** Get the MIME type of the data pointed to by the URI. */
   2681             @Nullable
   2682             public String getDataMimeType() {
   2683                 return mDataMimeType;
   2684             }
   2685 
   2686             /**
   2687              * Get the the Uri pointing to the content of the message. Can be null, in which case
   2688              * {@see #getText()} is used.
   2689              */
   2690             @Nullable
   2691             public Uri getDataUri() {
   2692                 return mDataUri;
   2693             }
   2694 
   2695             private Bundle toBundle() {
   2696                 Bundle bundle = new Bundle();
   2697                 if (mText != null) {
   2698                     bundle.putCharSequence(KEY_TEXT, mText);
   2699                 }
   2700                 bundle.putLong(KEY_TIMESTAMP, mTimestamp);
   2701                 if (mPerson != null) {
   2702                     // We must add both as Frameworks depends on this extra directly in order to
   2703                     // render properly.
   2704                     bundle.putCharSequence(KEY_SENDER, mPerson.getName());
   2705                     bundle.putBundle(KEY_PERSON, mPerson.toBundle());
   2706                 }
   2707                 if (mDataMimeType != null) {
   2708                     bundle.putString(KEY_DATA_MIME_TYPE, mDataMimeType);
   2709                 }
   2710                 if (mDataUri != null) {
   2711                     bundle.putParcelable(KEY_DATA_URI, mDataUri);
   2712                 }
   2713                 if (mExtras != null) {
   2714                     bundle.putBundle(KEY_EXTRAS_BUNDLE, mExtras);
   2715                 }
   2716                 return bundle;
   2717             }
   2718 
   2719             @NonNull
   2720             static Bundle[] getBundleArrayForMessages(List<Message> messages) {
   2721                 Bundle[] bundles = new Bundle[messages.size()];
   2722                 final int N = messages.size();
   2723                 for (int i = 0; i < N; i++) {
   2724                     bundles[i] = messages.get(i).toBundle();
   2725                 }
   2726                 return bundles;
   2727             }
   2728 
   2729             @NonNull
   2730             static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) {
   2731                 List<Message> messages = new ArrayList<>(bundles.length);
   2732                 for (int i = 0; i < bundles.length; i++) {
   2733                     if (bundles[i] instanceof Bundle) {
   2734                         Message message = getMessageFromBundle((Bundle)bundles[i]);
   2735                         if (message != null) {
   2736                             messages.add(message);
   2737                         }
   2738                     }
   2739                 }
   2740                 return messages;
   2741             }
   2742 
   2743             @Nullable
   2744             static Message getMessageFromBundle(Bundle bundle) {
   2745                 try {
   2746                     if (!bundle.containsKey(KEY_TEXT) || !bundle.containsKey(KEY_TIMESTAMP)) {
   2747                         return null;
   2748                     }
   2749 
   2750                     Person person = null;
   2751                     if (bundle.containsKey(KEY_PERSON)) {
   2752                         person = Person.fromBundle(bundle.getBundle(KEY_PERSON));
   2753                     } else if (bundle.containsKey(KEY_SENDER)) {
   2754                         // Legacy person
   2755                         person = new Person.Builder()
   2756                                 .setName(bundle.getCharSequence(KEY_SENDER))
   2757                                 .build();
   2758                     }
   2759 
   2760                     Message message = new Message(
   2761                             bundle.getCharSequence(KEY_TEXT),
   2762                             bundle.getLong(KEY_TIMESTAMP),
   2763                             person);
   2764 
   2765                     if (bundle.containsKey(KEY_DATA_MIME_TYPE)
   2766                             && bundle.containsKey(KEY_DATA_URI)) {
   2767                         message.setData(bundle.getString(KEY_DATA_MIME_TYPE),
   2768                                 (Uri) bundle.getParcelable(KEY_DATA_URI));
   2769                     }
   2770                     if (bundle.containsKey(KEY_EXTRAS_BUNDLE)) {
   2771                         message.getExtras().putAll(bundle.getBundle(KEY_EXTRAS_BUNDLE));
   2772                     }
   2773                     return message;
   2774                 } catch (ClassCastException e) {
   2775                     return null;
   2776                 }
   2777             }
   2778         }
   2779     }
   2780 
   2781     /**
   2782      * Helper class for generating large-format notifications that include a list of (up to 5) strings.
   2783      *
   2784      * <br>
   2785      * If the platform does not provide large-format notifications, this method has no effect. The
   2786      * user will always see the normal notification view.
   2787      * <br>
   2788      * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior, like so:
   2789      * <pre class="prettyprint">
   2790      * Notification notification = new Notification.Builder()
   2791      *     .setContentTitle(&quot;5 New mails from &quot; + sender.toString())
   2792      *     .setContentText(subject)
   2793      *     .setSmallIcon(R.drawable.new_mail)
   2794      *     .setLargeIcon(aBitmap)
   2795      *     .setStyle(new Notification.InboxStyle()
   2796      *         .addLine(str1)
   2797      *         .addLine(str2)
   2798      *         .setContentTitle(&quot;&quot;)
   2799      *         .setSummaryText(&quot;+3 more&quot;))
   2800      *     .build();
   2801      * </pre>
   2802      *
   2803      * @see Notification#bigContentView
   2804      */
   2805     public static class InboxStyle extends Style {
   2806         private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>();
   2807 
   2808         public InboxStyle() {
   2809         }
   2810 
   2811         public InboxStyle(Builder builder) {
   2812             setBuilder(builder);
   2813         }
   2814 
   2815         /**
   2816          * Overrides ContentTitle in the big form of the template.
   2817          * This defaults to the value passed to setContentTitle().
   2818          */
   2819         public InboxStyle setBigContentTitle(CharSequence title) {
   2820             mBigContentTitle = Builder.limitCharSequenceLength(title);
   2821             return this;
   2822         }
   2823 
   2824         /**
   2825          * Set the first line of text after the detail section in the big form of the template.
   2826          */
   2827         public InboxStyle setSummaryText(CharSequence cs) {
   2828             mSummaryText = Builder.limitCharSequenceLength(cs);
   2829             mSummaryTextSet = true;
   2830             return this;
   2831         }
   2832 
   2833         /**
   2834          * Append a line to the digest section of the Inbox notification.
   2835          */
   2836         public InboxStyle addLine(CharSequence cs) {
   2837             mTexts.add(Builder.limitCharSequenceLength(cs));
   2838             return this;
   2839         }
   2840 
   2841         /**
   2842          * @hide
   2843          */
   2844         @RestrictTo(LIBRARY_GROUP)
   2845         @Override
   2846         public void apply(NotificationBuilderWithBuilderAccessor builder) {
   2847             if (Build.VERSION.SDK_INT >= 16) {
   2848                 Notification.InboxStyle style =
   2849                         new Notification.InboxStyle(builder.getBuilder())
   2850                                 .setBigContentTitle(mBigContentTitle);
   2851                 if (mSummaryTextSet) {
   2852                     style.setSummaryText(mSummaryText);
   2853                 }
   2854                 for (CharSequence text: mTexts) {
   2855                     style.addLine(text);
   2856                 }
   2857             }
   2858         }
   2859     }
   2860 
   2861     /**
   2862      * Notification style for custom views that are decorated by the system.
   2863      *
   2864      * <p>Instead of providing a notification that is completely custom, a developer can set this
   2865      * style and still obtain system decorations like the notification header with the expand
   2866      * affordance and actions.
   2867      *
   2868      * <p>Use {@link NotificationCompat.Builder#setCustomContentView(RemoteViews)},
   2869      * {@link NotificationCompat.Builder#setCustomBigContentView(RemoteViews)} and
   2870      * {@link NotificationCompat.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the
   2871      * corresponding custom views to display.
   2872      *
   2873      * <p>To use this style with your Notification, feed it to
   2874      * {@link NotificationCompat.Builder#setStyle(Style)} like so:
   2875      * <pre class="prettyprint">
   2876      * Notification noti = new NotificationCompat.Builder()
   2877      *     .setSmallIcon(R.drawable.ic_stat_player)
   2878      *     .setLargeIcon(albumArtBitmap))
   2879      *     .setCustomContentView(contentView)
   2880      *     .setStyle(<b>new NotificationCompat.DecoratedCustomViewStyle()</b>)
   2881      *     .build();
   2882      * </pre>
   2883      *
   2884      * <p>If you are using this style, consider using the corresponding styles like
   2885      * {@link androidx.core.R.style#TextAppearance_Compat_Notification} or
   2886      * {@link androidx.core.R.style#TextAppearance_Compat_Notification_Title} in
   2887      * your custom views in order to get the correct styling on each platform version.
   2888      */
   2889     public static class DecoratedCustomViewStyle extends Style {
   2890 
   2891         private static final int MAX_ACTION_BUTTONS = 3;
   2892 
   2893         public DecoratedCustomViewStyle() {
   2894         }
   2895 
   2896         /**
   2897          * @hide
   2898          */
   2899         @RestrictTo(LIBRARY_GROUP)
   2900         @Override
   2901         public void apply(NotificationBuilderWithBuilderAccessor builder) {
   2902             if (Build.VERSION.SDK_INT >= 24) {
   2903                 builder.getBuilder().setStyle(new Notification.DecoratedCustomViewStyle());
   2904             }
   2905         }
   2906 
   2907         /**
   2908          * @hide
   2909          */
   2910         @RestrictTo(LIBRARY_GROUP)
   2911         @Override
   2912         public RemoteViews makeContentView(NotificationBuilderWithBuilderAccessor builder) {
   2913             if (Build.VERSION.SDK_INT >= 24) {
   2914                 // No custom content view required
   2915                 return null;
   2916             }
   2917             if (mBuilder.getContentView() == null) {
   2918                 // No special content view
   2919                 return null;
   2920             }
   2921             return createRemoteViews(mBuilder.getContentView(), false);
   2922         }
   2923 
   2924         /**
   2925          * @hide
   2926          */
   2927         @RestrictTo(LIBRARY_GROUP)
   2928         @Override
   2929         public RemoteViews makeBigContentView(NotificationBuilderWithBuilderAccessor builder) {
   2930             if (Build.VERSION.SDK_INT >= 24) {
   2931                 // No custom big content view required
   2932                 return null;
   2933             }
   2934             RemoteViews bigContentView = mBuilder.getBigContentView();
   2935             RemoteViews innerView = bigContentView != null
   2936                     ? bigContentView
   2937                     : mBuilder.getContentView();
   2938             if (innerView == null) {
   2939                 // No expandable notification
   2940                 return null;
   2941             }
   2942             return createRemoteViews(innerView, true);
   2943         }
   2944 
   2945         /**
   2946          * @hide
   2947          */
   2948         @RestrictTo(LIBRARY_GROUP)
   2949         @Override
   2950         public RemoteViews makeHeadsUpContentView(NotificationBuilderWithBuilderAccessor builder) {
   2951             if (Build.VERSION.SDK_INT >= 24) {
   2952                 // No custom heads up content view required
   2953                 return null;
   2954             }
   2955             RemoteViews headsUp = mBuilder.getHeadsUpContentView();
   2956             RemoteViews innerView = headsUp != null ? headsUp : mBuilder.getContentView();
   2957             if (headsUp == null) {
   2958                 // No expandable notification
   2959                 return null;
   2960             }
   2961             return createRemoteViews(innerView, true);
   2962         }
   2963 
   2964         private RemoteViews createRemoteViews(RemoteViews innerView, boolean showActions) {
   2965             RemoteViews remoteViews = applyStandardTemplate(true /* showSmallIcon */,
   2966                     R.layout.notification_template_custom_big, false /* fitIn1U */);
   2967             remoteViews.removeAllViews(R.id.actions);
   2968             boolean actionsVisible = false;
   2969             if (showActions && mBuilder.mActions != null) {
   2970                 int numActions = Math.min(mBuilder.mActions.size(), MAX_ACTION_BUTTONS);
   2971                 if (numActions > 0) {
   2972                     actionsVisible = true;
   2973                     for (int i = 0; i < numActions; i++) {
   2974                         final RemoteViews button = generateActionButton(mBuilder.mActions.get(i));
   2975                         remoteViews.addView(R.id.actions, button);
   2976                     }
   2977                 }
   2978             }
   2979             int actionVisibility = actionsVisible ? View.VISIBLE : View.GONE;
   2980             remoteViews.setViewVisibility(R.id.actions, actionVisibility);
   2981             remoteViews.setViewVisibility(R.id.action_divider, actionVisibility);
   2982             buildIntoRemoteViews(remoteViews, innerView);
   2983             return remoteViews;
   2984         }
   2985 
   2986         private RemoteViews generateActionButton(NotificationCompat.Action action) {
   2987             final boolean tombstone = (action.actionIntent == null);
   2988             RemoteViews button = new RemoteViews(mBuilder.mContext.getPackageName(),
   2989                     tombstone ? R.layout.notification_action_tombstone
   2990                             : R.layout.notification_action);
   2991             button.setImageViewBitmap(R.id.action_image,
   2992                     createColoredBitmap(action.getIcon(), mBuilder.mContext.getResources()
   2993                             .getColor(R.color.notification_action_color_filter)));
   2994             button.setTextViewText(R.id.action_text, action.title);
   2995             if (!tombstone) {
   2996                 button.setOnClickPendingIntent(R.id.action_container, action.actionIntent);
   2997             }
   2998             if (Build.VERSION.SDK_INT >= 15) {
   2999                 button.setContentDescription(R.id.action_container, action.title);
   3000             }
   3001             return button;
   3002         }
   3003     }
   3004 
   3005     /**
   3006      * Structure to encapsulate a named action that can be shown as part of this notification.
   3007      * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is
   3008      * selected by the user. Action buttons won't appear on platforms prior to Android 4.1.
   3009      * <p>
   3010      * Apps should use {@link NotificationCompat.Builder#addAction(int, CharSequence, PendingIntent)}
   3011      * or {@link NotificationCompat.Builder#addAction(NotificationCompat.Action)}
   3012      * to attach actions.
   3013      */
   3014     public static class Action {
   3015         /**
   3016          * {@link SemanticAction}: No semantic action defined.
   3017          */
   3018         public static final int SEMANTIC_ACTION_NONE = 0;
   3019 
   3020         /**
   3021          * {@link SemanticAction}: Reply to a conversation, chat, group, or wherever replies
   3022          * may be appropriate.
   3023          */
   3024         public static final int SEMANTIC_ACTION_REPLY = 1;
   3025 
   3026         /**
   3027          * {@link SemanticAction}: Mark content as read.
   3028          */
   3029         public static final int SEMANTIC_ACTION_MARK_AS_READ = 2;
   3030 
   3031         /**
   3032          * {@link SemanticAction}: Mark content as unread.
   3033          */
   3034         public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3;
   3035 
   3036         /**
   3037          * {@link SemanticAction}: Delete the content associated with the notification. This
   3038          * could mean deleting an email, message, etc.
   3039          */
   3040         public static final int SEMANTIC_ACTION_DELETE = 4;
   3041 
   3042         /**
   3043          * {@link SemanticAction}: Archive the content associated with the notification. This
   3044          * could mean archiving an email, message, etc.
   3045          */
   3046         public static final int SEMANTIC_ACTION_ARCHIVE = 5;
   3047 
   3048         /**
   3049          * {@link SemanticAction}: Mute the content associated with the notification. This could
   3050          * mean silencing a conversation or currently playing media.
   3051          */
   3052         public static final int SEMANTIC_ACTION_MUTE = 6;
   3053 
   3054         /**
   3055          * {@link SemanticAction}: Unmute the content associated with the notification. This could
   3056          * mean un-silencing a conversation or currently playing media.
   3057          */
   3058         public static final int SEMANTIC_ACTION_UNMUTE = 7;
   3059 
   3060         /**
   3061          * {@link SemanticAction}: Mark content with a thumbs up.
   3062          */
   3063         public static final int SEMANTIC_ACTION_THUMBS_UP = 8;
   3064 
   3065         /**
   3066          * {@link SemanticAction}: Mark content with a thumbs down.
   3067          */
   3068         public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9;
   3069 
   3070         /**
   3071          * {@link SemanticAction}: Call a contact, group, etc.
   3072          */
   3073         public static final int SEMANTIC_ACTION_CALL = 10;
   3074 
   3075         static final String EXTRA_SHOWS_USER_INTERFACE =
   3076                 "android.support.action.showsUserInterface";
   3077 
   3078         static final String EXTRA_SEMANTIC_ACTION = "android.support.action.semanticAction";
   3079 
   3080         final Bundle mExtras;
   3081         private final RemoteInput[] mRemoteInputs;
   3082 
   3083         /**
   3084          * Holds {@link RemoteInput}s that only accept data, meaning
   3085          * {@link RemoteInput#getAllowFreeFormInput} is false, {@link RemoteInput#getChoices}
   3086          * is null or empty, and {@link RemoteInput#getAllowedDataTypes is non-null and not
   3087          * empty. These {@link RemoteInput}s will be ignored by devices that do not
   3088          * support non-text-based {@link RemoteInput}s. See {@link Builder#build}.
   3089          *
   3090          * You can test if a RemoteInput matches these constraints using
   3091          * {@link RemoteInput#isDataOnly}.
   3092          */
   3093         private final RemoteInput[] mDataOnlyRemoteInputs;
   3094 
   3095         private boolean mAllowGeneratedReplies;
   3096         boolean mShowsUserInterface = true;
   3097 
   3098         private final @SemanticAction int mSemanticAction;
   3099 
   3100         /**
   3101          * Small icon representing the action.
   3102          */
   3103         public int icon;
   3104         /**
   3105          * Title of the action.
   3106          */
   3107         public CharSequence title;
   3108         /**
   3109          * Intent to send when the user invokes this action. May be null, in which case the action
   3110          * may be rendered in a disabled presentation.
   3111          */
   3112         public PendingIntent actionIntent;
   3113 
   3114         public Action(int icon, CharSequence title, PendingIntent intent) {
   3115             this(icon, title, intent, new Bundle(), null, null, true, SEMANTIC_ACTION_NONE, true);
   3116         }
   3117 
   3118         Action(int icon, CharSequence title, PendingIntent intent, Bundle extras,
   3119                 RemoteInput[] remoteInputs, RemoteInput[] dataOnlyRemoteInputs,
   3120                 boolean allowGeneratedReplies, @SemanticAction int semanticAction,
   3121                 boolean showsUserInterface) {
   3122             this.icon = icon;
   3123             this.title = NotificationCompat.Builder.limitCharSequenceLength(title);
   3124             this.actionIntent = intent;
   3125             this.mExtras = extras != null ? extras : new Bundle();
   3126             this.mRemoteInputs = remoteInputs;
   3127             this.mDataOnlyRemoteInputs = dataOnlyRemoteInputs;
   3128             this.mAllowGeneratedReplies = allowGeneratedReplies;
   3129             this.mSemanticAction = semanticAction;
   3130             this.mShowsUserInterface = showsUserInterface;
   3131         }
   3132 
   3133         public int getIcon() {
   3134             return icon;
   3135         }
   3136 
   3137         public CharSequence getTitle() {
   3138             return title;
   3139         }
   3140 
   3141         public PendingIntent getActionIntent() {
   3142             return actionIntent;
   3143         }
   3144 
   3145         /**
   3146          * Get additional metadata carried around with this Action.
   3147          */
   3148         public Bundle getExtras() {
   3149             return mExtras;
   3150         }
   3151 
   3152         /**
   3153          * Return whether the platform should automatically generate possible replies for this
   3154          * {@link Action}
   3155          */
   3156         public boolean getAllowGeneratedReplies() {
   3157             return mAllowGeneratedReplies;
   3158         }
   3159 
   3160         /**
   3161          * Get the list of inputs to be collected from the user when this action is sent.
   3162          * May return null if no remote inputs were added. Only returns inputs which accept
   3163          * a text input. For inputs which only accept data use {@link #getDataOnlyRemoteInputs}.
   3164          */
   3165         public RemoteInput[] getRemoteInputs() {
   3166             return mRemoteInputs;
   3167         }
   3168 
   3169         /**
   3170          * Returns the {@link SemanticAction} associated with this {@link Action}. A
   3171          * {@link SemanticAction} denotes what an {@link Action}'s {@link PendingIntent} will do
   3172          * (eg. reply, mark as read, delete, etc).
   3173          *
   3174          * @see SemanticAction
   3175          */
   3176         public @SemanticAction int getSemanticAction() {
   3177             return mSemanticAction;
   3178         }
   3179 
   3180         /**
   3181          * Get the list of inputs to be collected from the user that ONLY accept data when this
   3182          * action is sent. These remote inputs are guaranteed to return true on a call to
   3183          * {@link RemoteInput#isDataOnly}.
   3184          *
   3185          * <p>May return null if no data-only remote inputs were added.
   3186          *
   3187          * <p>This method exists so that legacy RemoteInput collectors that pre-date the addition
   3188          * of non-textual RemoteInputs do not access these remote inputs.
   3189          */
   3190         public RemoteInput[] getDataOnlyRemoteInputs() {
   3191             return mDataOnlyRemoteInputs;
   3192         }
   3193 
   3194         /**
   3195          * Return whether or not triggering this {@link Action}'s {@link PendingIntent} will open a
   3196          * user interface.
   3197          */
   3198         public boolean getShowsUserInterface() {
   3199             return mShowsUserInterface;
   3200         }
   3201 
   3202         /**
   3203          * Builder class for {@link Action} objects.
   3204          */
   3205         public static final class Builder {
   3206             private final int mIcon;
   3207             private final CharSequence mTitle;
   3208             private final PendingIntent mIntent;
   3209             private boolean mAllowGeneratedReplies = true;
   3210             private final Bundle mExtras;
   3211             private ArrayList<RemoteInput> mRemoteInputs;
   3212             private @SemanticAction int mSemanticAction;
   3213             private boolean mShowsUserInterface = true;
   3214 
   3215             /**
   3216              * Construct a new builder for {@link Action} object.
   3217              * @param icon icon to show for this action
   3218              * @param title the title of the action
   3219              * @param intent the {@link PendingIntent} to fire when users trigger this action
   3220              */
   3221             public Builder(int icon, CharSequence title, PendingIntent intent) {
   3222                 this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE, true);
   3223             }
   3224 
   3225             /**
   3226              * Construct a new builder for {@link Action} object using the fields from an
   3227              * {@link Action}.
   3228              * @param action the action to read fields from.
   3229              */
   3230             public Builder(Action action) {
   3231                 this(action.icon, action.title, action.actionIntent, new Bundle(action.mExtras),
   3232                         action.getRemoteInputs(), action.getAllowGeneratedReplies(),
   3233                         action.getSemanticAction(), action.mShowsUserInterface);
   3234             }
   3235 
   3236             private Builder(int icon, CharSequence title, PendingIntent intent, Bundle extras,
   3237                     RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
   3238                     @SemanticAction int semanticAction, boolean showsUserInterface) {
   3239                 mIcon = icon;
   3240                 mTitle = NotificationCompat.Builder.limitCharSequenceLength(title);
   3241                 mIntent = intent;
   3242                 mExtras = extras;
   3243                 mRemoteInputs = remoteInputs == null ? null : new ArrayList<>(
   3244                         Arrays.asList(remoteInputs));
   3245                 mAllowGeneratedReplies = allowGeneratedReplies;
   3246                 mSemanticAction = semanticAction;
   3247                 mShowsUserInterface = showsUserInterface;
   3248             }
   3249 
   3250             /**
   3251              * Merge additional metadata into this builder.
   3252              *
   3253              * <p>Values within the Bundle will replace existing extras values in this Builder.
   3254              *
   3255              * @see NotificationCompat.Action#getExtras
   3256              */
   3257             public Builder addExtras(Bundle extras) {
   3258                 if (extras != null) {
   3259                     mExtras.putAll(extras);
   3260                 }
   3261                 return this;
   3262             }
   3263 
   3264             /**
   3265              * Get the metadata Bundle used by this Builder.
   3266              *
   3267              * <p>The returned Bundle is shared with this Builder.
   3268              */
   3269             public Bundle getExtras() {
   3270                 return mExtras;
   3271             }
   3272 
   3273             /**
   3274              * Add an input to be collected from the user when this action is sent.
   3275              * Response values can be retrieved from the fired intent by using the
   3276              * {@link RemoteInput#getResultsFromIntent} function.
   3277              * @param remoteInput a {@link RemoteInput} to add to the action
   3278              * @return this object for method chaining
   3279              */
   3280             public Builder addRemoteInput(RemoteInput remoteInput) {
   3281                 if (mRemoteInputs == null) {
   3282                     mRemoteInputs = new ArrayList<RemoteInput>();
   3283                 }
   3284                 mRemoteInputs.add(remoteInput);
   3285                 return this;
   3286             }
   3287 
   3288             /**
   3289              * Set whether the platform should automatically generate possible replies to add to
   3290              * {@link RemoteInput#getChoices()}. If the {@link Action} doesn't have a
   3291              * {@link RemoteInput}, this has no effect.
   3292              * @param allowGeneratedReplies {@code true} to allow generated replies, {@code false}
   3293              * otherwise
   3294              * @return this object for method chaining
   3295              * The default value is {@code true}
   3296              */
   3297             public Builder setAllowGeneratedReplies(boolean allowGeneratedReplies) {
   3298                 mAllowGeneratedReplies = allowGeneratedReplies;
   3299                 return this;
   3300             }
   3301 
   3302             /**
   3303              * Sets the {@link SemanticAction} for this {@link Action}. A {@link SemanticAction}
   3304              * denotes what an {@link Action}'s {@link PendingIntent} will do (eg. reply, mark
   3305              * as read, delete, etc).
   3306              * @param semanticAction a {@link SemanticAction} defined within {@link Action} with
   3307              * {@code SEMANTIC_ACTION_} prefixes
   3308              * @return this object for method chaining
   3309              */
   3310             public Builder setSemanticAction(@SemanticAction int semanticAction) {
   3311                 mSemanticAction = semanticAction;
   3312                 return this;
   3313             }
   3314 
   3315             /**
   3316              * Set whether or not this {@link Action}'s {@link PendingIntent} will open a user
   3317              * interface.
   3318              * @param showsUserInterface {@code true} if this {@link Action}'s {@link PendingIntent}
   3319              * will open a user interface, otherwise {@code false}
   3320              * @return this object for method chaining
   3321              * The default value is {@code true}
   3322              */
   3323             public Builder setShowsUserInterface(boolean showsUserInterface) {
   3324                 mShowsUserInterface = showsUserInterface;
   3325                 return this;
   3326             }
   3327 
   3328             /**
   3329              * Apply an extender to this action builder. Extenders may be used to add
   3330              * metadata or change options on this builder.
   3331              */
   3332             public Builder extend(Extender extender) {
   3333                 extender.extend(this);
   3334                 return this;
   3335             }
   3336 
   3337             /**
   3338              * Combine all of the options that have been set and return a new {@link Action}
   3339              * object.
   3340              * @return the built action
   3341              */
   3342             public Action build() {
   3343                 List<RemoteInput> dataOnlyInputs = new ArrayList<>();
   3344                 List<RemoteInput> textInputs = new ArrayList<>();
   3345                 if (mRemoteInputs != null) {
   3346                     for (RemoteInput input : mRemoteInputs) {
   3347                         if (input.isDataOnly()) {
   3348                             dataOnlyInputs.add(input);
   3349                         } else {
   3350                             textInputs.add(input);
   3351                         }
   3352                     }
   3353                 }
   3354                 RemoteInput[] dataOnlyInputsArr = dataOnlyInputs.isEmpty()
   3355                         ? null : dataOnlyInputs.toArray(new RemoteInput[dataOnlyInputs.size()]);
   3356                 RemoteInput[] textInputsArr = textInputs.isEmpty()
   3357                         ? null : textInputs.toArray(new RemoteInput[textInputs.size()]);
   3358                 return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr,
   3359                         dataOnlyInputsArr, mAllowGeneratedReplies, mSemanticAction,
   3360                         mShowsUserInterface);
   3361             }
   3362         }
   3363 
   3364 
   3365         /**
   3366          * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
   3367          * metadata or change options on an action builder.
   3368          */
   3369         public interface Extender {
   3370             /**
   3371              * Apply this extender to a notification action builder.
   3372              * @param builder the builder to be modified.
   3373              * @return the build object for chaining.
   3374              */
   3375             Builder extend(Builder builder);
   3376         }
   3377 
   3378         /**
   3379          * Wearable extender for notification actions. To add extensions to an action,
   3380          * create a new {@link NotificationCompat.Action.WearableExtender} object using
   3381          * the {@code WearableExtender()} constructor and apply it to a
   3382          * {@link NotificationCompat.Action.Builder} using
   3383          * {@link NotificationCompat.Action.Builder#extend}.
   3384          *
   3385          * <pre class="prettyprint">
   3386          * NotificationCompat.Action action = new NotificationCompat.Action.Builder(
   3387          *         R.drawable.archive_all, "Archive all", actionIntent)
   3388          *         .extend(new NotificationCompat.Action.WearableExtender()
   3389          *                 .setAvailableOffline(false))
   3390          *         .build();</pre>
   3391          */
   3392         public static final class WearableExtender implements Extender {
   3393             /** Notification action extra which contains wearable extensions */
   3394             private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
   3395 
   3396             // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
   3397             private static final String KEY_FLAGS = "flags";
   3398             private static final String KEY_IN_PROGRESS_LABEL = "inProgressLabel";
   3399             private static final String KEY_CONFIRM_LABEL = "confirmLabel";
   3400             private static final String KEY_CANCEL_LABEL = "cancelLabel";
   3401 
   3402             // Flags bitwise-ored to mFlags
   3403             private static final int FLAG_AVAILABLE_OFFLINE = 0x1;
   3404             private static final int FLAG_HINT_LAUNCHES_ACTIVITY = 1 << 1;
   3405             private static final int FLAG_HINT_DISPLAY_INLINE = 1 << 2;
   3406 
   3407             // Default value for flags integer
   3408             private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE;
   3409 
   3410             private int mFlags = DEFAULT_FLAGS;
   3411 
   3412             private CharSequence mInProgressLabel;
   3413             private CharSequence mConfirmLabel;
   3414             private CharSequence mCancelLabel;
   3415 
   3416             /**
   3417              * Create a {@link NotificationCompat.Action.WearableExtender} with default
   3418              * options.
   3419              */
   3420             public WearableExtender() {
   3421             }
   3422 
   3423             /**
   3424              * Create a {@link NotificationCompat.Action.WearableExtender} by reading
   3425              * wearable options present in an existing notification action.
   3426              * @param action the notification action to inspect.
   3427              */
   3428             public WearableExtender(Action action) {
   3429                 Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS);
   3430                 if (wearableBundle != null) {
   3431                     mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
   3432                     mInProgressLabel = wearableBundle.getCharSequence(KEY_IN_PROGRESS_LABEL);
   3433                     mConfirmLabel = wearableBundle.getCharSequence(KEY_CONFIRM_LABEL);
   3434                     mCancelLabel = wearableBundle.getCharSequence(KEY_CANCEL_LABEL);
   3435                 }
   3436             }
   3437 
   3438             /**
   3439              * Apply wearable extensions to a notification action that is being built. This is
   3440              * typically called by the {@link NotificationCompat.Action.Builder#extend}
   3441              * method of {@link NotificationCompat.Action.Builder}.
   3442              */
   3443             @Override
   3444             public Action.Builder extend(Action.Builder builder) {
   3445                 Bundle wearableBundle = new Bundle();
   3446 
   3447                 if (mFlags != DEFAULT_FLAGS) {
   3448                     wearableBundle.putInt(KEY_FLAGS, mFlags);
   3449                 }
   3450                 if (mInProgressLabel != null) {
   3451                     wearableBundle.putCharSequence(KEY_IN_PROGRESS_LABEL, mInProgressLabel);
   3452                 }
   3453                 if (mConfirmLabel != null) {
   3454                     wearableBundle.putCharSequence(KEY_CONFIRM_LABEL, mConfirmLabel);
   3455                 }
   3456                 if (mCancelLabel != null) {
   3457                     wearableBundle.putCharSequence(KEY_CANCEL_LABEL, mCancelLabel);
   3458                 }
   3459 
   3460                 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
   3461                 return builder;
   3462             }
   3463 
   3464             @Override
   3465             public WearableExtender clone() {
   3466                 WearableExtender that = new WearableExtender();
   3467                 that.mFlags = this.mFlags;
   3468                 that.mInProgressLabel = this.mInProgressLabel;
   3469                 that.mConfirmLabel = this.mConfirmLabel;
   3470                 that.mCancelLabel = this.mCancelLabel;
   3471                 return that;
   3472             }
   3473 
   3474             /**
   3475              * Set whether this action is available when the wearable device is not connected to
   3476              * a companion device. The user can still trigger this action when the wearable device
   3477              * is offline, but a visual hint will indicate that the action may not be available.
   3478              * Defaults to true.
   3479              */
   3480             public WearableExtender setAvailableOffline(boolean availableOffline) {
   3481                 setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline);
   3482                 return this;
   3483             }
   3484 
   3485             /**
   3486              * Get whether this action is available when the wearable device is not connected to
   3487              * a companion device. The user can still trigger this action when the wearable device
   3488              * is offline, but a visual hint will indicate that the action may not be available.
   3489              * Defaults to true.
   3490              */
   3491             public boolean isAvailableOffline() {
   3492                 return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0;
   3493             }
   3494 
   3495             private void setFlag(int mask, boolean value) {
   3496                 if (value) {
   3497                     mFlags |= mask;
   3498                 } else {
   3499                     mFlags &= ~mask;
   3500                 }
   3501             }
   3502 
   3503             /**
   3504              * Set a label to display while the wearable is preparing to automatically execute the
   3505              * action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
   3506              *
   3507              * @param label the label to display while the action is being prepared to execute
   3508              * @return this object for method chaining
   3509              */
   3510             @Deprecated
   3511             public WearableExtender setInProgressLabel(CharSequence label) {
   3512                 mInProgressLabel = label;
   3513                 return this;
   3514             }
   3515 
   3516             /**
   3517              * Get the label to display while the wearable is preparing to automatically execute
   3518              * the action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
   3519              *
   3520              * @return the label to display while the action is being prepared to execute
   3521              */
   3522             @Deprecated
   3523             public CharSequence getInProgressLabel() {
   3524                 return mInProgressLabel;
   3525             }
   3526 
   3527             /**
   3528              * Set a label to display to confirm that the action should be executed.
   3529              * This is usually an imperative verb like "Send".
   3530              *
   3531              * @param label the label to confirm the action should be executed
   3532              * @return this object for method chaining
   3533              */
   3534             @Deprecated
   3535             public WearableExtender setConfirmLabel(CharSequence label) {
   3536                 mConfirmLabel = label;
   3537                 return this;
   3538             }
   3539 
   3540             /**
   3541              * Get the label to display to confirm that the action should be executed.
   3542              * This is usually an imperative verb like "Send".
   3543              *
   3544              * @return the label to confirm the action should be executed
   3545              */
   3546             @Deprecated
   3547             public CharSequence getConfirmLabel() {
   3548                 return mConfirmLabel;
   3549             }
   3550 
   3551             /**
   3552              * Set a label to display to cancel the action.
   3553              * This is usually an imperative verb, like "Cancel".
   3554              *
   3555              * @param label the label to display to cancel the action
   3556              * @return this object for method chaining
   3557              */
   3558             @Deprecated
   3559             public WearableExtender setCancelLabel(CharSequence label) {
   3560                 mCancelLabel = label;
   3561                 return this;
   3562             }
   3563 
   3564             /**
   3565              * Get the label to display to cancel the action.
   3566              * This is usually an imperative verb like "Cancel".
   3567              *
   3568              * @return the label to display to cancel the action
   3569              */
   3570             @Deprecated
   3571             public CharSequence getCancelLabel() {
   3572                 return mCancelLabel;
   3573             }
   3574 
   3575             /**
   3576              * Set a hint that this Action will launch an {@link Activity} directly, telling the
   3577              * platform that it can generate the appropriate transitions.
   3578              * @param hintLaunchesActivity {@code true} if the content intent will launch
   3579              * an activity and transitions should be generated, false otherwise.
   3580              * @return this object for method chaining
   3581              */
   3582             public WearableExtender setHintLaunchesActivity(
   3583                     boolean hintLaunchesActivity) {
   3584                 setFlag(FLAG_HINT_LAUNCHES_ACTIVITY, hintLaunchesActivity);
   3585                 return this;
   3586             }
   3587 
   3588             /**
   3589              * Get a hint that this Action will launch an {@link Activity} directly, telling the
   3590              * platform that it can generate the appropriate transitions
   3591              * @return {@code true} if the content intent will launch an activity and transitions
   3592              * should be generated, false otherwise. The default value is {@code false} if this was
   3593              * never set.
   3594              */
   3595             public boolean getHintLaunchesActivity() {
   3596                 return (mFlags & FLAG_HINT_LAUNCHES_ACTIVITY) != 0;
   3597             }
   3598 
   3599             /**
   3600              * Set a hint that this Action should be displayed inline - i.e. it will have a visual
   3601              * representation directly on the notification surface in addition to the expanded
   3602              * Notification
   3603              *
   3604              * @param hintDisplayInline {@code true} if action should be displayed inline, false
   3605              *        otherwise
   3606              * @return this object for method chaining
   3607              */
   3608             public WearableExtender setHintDisplayActionInline(
   3609                     boolean hintDisplayInline) {
   3610                 setFlag(FLAG_HINT_DISPLAY_INLINE, hintDisplayInline);
   3611                 return this;
   3612             }
   3613 
   3614             /**
   3615              * Get a hint that this Action should be displayed inline - i.e. it should have a
   3616              * visual representation directly on the notification surface in addition to the
   3617              * expanded Notification
   3618              *
   3619              * @return {@code true} if the Action should be displayed inline, {@code false}
   3620              *         otherwise. The default value is {@code false} if this was never set.
   3621              */
   3622             public boolean getHintDisplayActionInline() {
   3623                 return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0;
   3624             }
   3625         }
   3626 
   3627         /**
   3628          * Provides meaning to an {@link Action} that hints at what the associated
   3629          * {@link PendingIntent} will do. For example, an {@link Action} with a
   3630          * {@link PendingIntent} that replies to a text message notification may have the
   3631          * {@link #SEMANTIC_ACTION_REPLY} {@link SemanticAction} set within it.
   3632          */
   3633         @IntDef({
   3634                 SEMANTIC_ACTION_NONE,
   3635                 SEMANTIC_ACTION_REPLY,
   3636                 SEMANTIC_ACTION_MARK_AS_READ,
   3637                 SEMANTIC_ACTION_MARK_AS_UNREAD,
   3638                 SEMANTIC_ACTION_DELETE,
   3639                 SEMANTIC_ACTION_ARCHIVE,
   3640                 SEMANTIC_ACTION_MUTE,
   3641                 SEMANTIC_ACTION_UNMUTE,
   3642                 SEMANTIC_ACTION_THUMBS_UP,
   3643                 SEMANTIC_ACTION_THUMBS_DOWN,
   3644                 SEMANTIC_ACTION_CALL
   3645         })
   3646         @Retention(RetentionPolicy.SOURCE)
   3647         public @interface SemanticAction {}
   3648     }
   3649 
   3650 
   3651     /**
   3652      * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
   3653      * metadata or change options on a notification builder.
   3654      */
   3655     public interface Extender {
   3656         /**
   3657          * Apply this extender to a notification builder.
   3658          * @param builder the builder to be modified.
   3659          * @return the build object for chaining.
   3660          */
   3661         Builder extend(Builder builder);
   3662     }
   3663 
   3664     /**
   3665      * Helper class to add wearable extensions to notifications.
   3666      * <p class="note"> See
   3667      * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications
   3668      * for Android Wear</a> for more information on how to use this class.
   3669      * <p>
   3670      * To create a notification with wearable extensions:
   3671      * <ol>
   3672      *   <li>Create a {@link NotificationCompat.Builder}, setting any desired
   3673      *   properties.
   3674      *   <li>Create a {@link NotificationCompat.WearableExtender}.
   3675      *   <li>Set wearable-specific properties using the
   3676      *   {@code add} and {@code set} methods of {@link NotificationCompat.WearableExtender}.
   3677      *   <li>Call {@link NotificationCompat.Builder#extend} to apply the extensions to a
   3678      *   notification.
   3679      *   <li>Post the notification to the notification
   3680      *   system with the {@code NotificationManagerCompat.notify(...)} methods
   3681      *   and not the {@code NotificationManager.notify(...)} methods.
   3682      * </ol>
   3683      *
   3684      * <pre class="prettyprint">
   3685      * Notification notification = new NotificationCompat.Builder(mContext)
   3686      *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
   3687      *         .setContentText(subject)
   3688      *         .setSmallIcon(R.drawable.new_mail)
   3689      *         .extend(new NotificationCompat.WearableExtender()
   3690      *                 .setContentIcon(R.drawable.new_mail))
   3691      *         .build();
   3692      * NotificationManagerCompat.from(mContext).notify(0, notification);</pre>
   3693      *
   3694      * <p>Wearable extensions can be accessed on an existing notification by using the
   3695      * {@code WearableExtender(Notification)} constructor,
   3696      * and then using the {@code get} methods to access values.
   3697      *
   3698      * <pre class="prettyprint">
   3699      * NotificationCompat.WearableExtender wearableExtender =
   3700      *         new NotificationCompat.WearableExtender(notification);
   3701      * List&lt;Notification&gt; pages = wearableExtender.getPages();</pre>
   3702      */
   3703     public static final class WearableExtender implements Extender {
   3704         /**
   3705          * Sentinel value for an action index that is unset.
   3706          */
   3707         public static final int UNSET_ACTION_INDEX = -1;
   3708 
   3709         /**
   3710          * Size value for use with {@link #setCustomSizePreset} to show this notification with
   3711          * default sizing.
   3712          * <p>For custom display notifications created using {@link #setDisplayIntent},
   3713          * the default is {@link #SIZE_MEDIUM}. All other notifications size automatically based
   3714          * on their content.
   3715          */
   3716         public static final int SIZE_DEFAULT = 0;
   3717 
   3718         /**
   3719          * Size value for use with {@link #setCustomSizePreset} to show this notification
   3720          * with an extra small size.
   3721          * <p>This value is only applicable for custom display notifications created using
   3722          * {@link #setDisplayIntent}.
   3723          */
   3724         public static final int SIZE_XSMALL = 1;
   3725 
   3726         /**
   3727          * Size value for use with {@link #setCustomSizePreset} to show this notification
   3728          * with a small size.
   3729          * <p>This value is only applicable for custom display notifications created using
   3730          * {@link #setDisplayIntent}.
   3731          */
   3732         public static final int SIZE_SMALL = 2;
   3733 
   3734         /**
   3735          * Size value for use with {@link #setCustomSizePreset} to show this notification
   3736          * with a medium size.
   3737          * <p>This value is only applicable for custom display notifications created using
   3738          * {@link #setDisplayIntent}.
   3739          */
   3740         public static final int SIZE_MEDIUM = 3;
   3741 
   3742         /**
   3743          * Size value for use with {@link #setCustomSizePreset} to show this notification
   3744          * with a large size.
   3745          * <p>This value is only applicable for custom display notifications created using
   3746          * {@link #setDisplayIntent}.
   3747          */
   3748         public static final int SIZE_LARGE = 4;
   3749 
   3750         /**
   3751          * Size value for use with {@link #setCustomSizePreset} to show this notification
   3752          * full screen.
   3753          * <p>This value is only applicable for custom display notifications created using
   3754          * {@link #setDisplayIntent}.
   3755          */
   3756         public static final int SIZE_FULL_SCREEN = 5;
   3757 
   3758         /**
   3759          * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a
   3760          * short amount of time when this notification is displayed on the screen. This
   3761          * is the default value.
   3762          */
   3763         public static final int SCREEN_TIMEOUT_SHORT = 0;
   3764 
   3765         /**
   3766          * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on
   3767          * for a longer amount of time when this notification is displayed on the screen.
   3768          */
   3769         public static final int SCREEN_TIMEOUT_LONG = -1;
   3770 
   3771         /** Notification extra which contains wearable extensions */
   3772         private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
   3773 
   3774         // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
   3775         private static final String KEY_ACTIONS = "actions";
   3776         private static final String KEY_FLAGS = "flags";
   3777         private static final String KEY_DISPLAY_INTENT = "displayIntent";
   3778         private static final String KEY_PAGES = "pages";
   3779         private static final String KEY_BACKGROUND = "background";
   3780         private static final String KEY_CONTENT_ICON = "contentIcon";
   3781         private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity";
   3782         private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex";
   3783         private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset";
   3784         private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight";
   3785         private static final String KEY_GRAVITY = "gravity";
   3786         private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout";
   3787         private static final String KEY_DISMISSAL_ID = "dismissalId";
   3788         private static final String KEY_BRIDGE_TAG = "bridgeTag";
   3789 
   3790         // Flags bitwise-ored to mFlags
   3791         private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1;
   3792         private static final int FLAG_HINT_HIDE_ICON = 1 << 1;
   3793         private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2;
   3794         private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3;
   3795         private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4;
   3796         private static final int FLAG_BIG_PICTURE_AMBIENT = 1 << 5;
   3797         private static final int FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY = 1 << 6;
   3798 
   3799         // Default value for flags integer
   3800         private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE;
   3801 
   3802         private static final int DEFAULT_CONTENT_ICON_GRAVITY = GravityCompat.END;
   3803         private static final int DEFAULT_GRAVITY = Gravity.BOTTOM;
   3804 
   3805         private ArrayList<Action> mActions = new ArrayList<Action>();
   3806         private int mFlags = DEFAULT_FLAGS;
   3807         private PendingIntent mDisplayIntent;
   3808         private ArrayList<Notification> mPages = new ArrayList<Notification>();
   3809         private Bitmap mBackground;
   3810         private int mContentIcon;
   3811         private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY;
   3812         private int mContentActionIndex = UNSET_ACTION_INDEX;
   3813         private int mCustomSizePreset = SIZE_DEFAULT;
   3814         private int mCustomContentHeight;
   3815         private int mGravity = DEFAULT_GRAVITY;
   3816         private int mHintScreenTimeout;
   3817         private String mDismissalId;
   3818         private String mBridgeTag;
   3819 
   3820         /**
   3821          * Create a {@link NotificationCompat.WearableExtender} with default
   3822          * options.
   3823          */
   3824         public WearableExtender() {
   3825         }
   3826 
   3827         public WearableExtender(Notification notification) {
   3828             Bundle extras = getExtras(notification);
   3829             Bundle wearableBundle = extras != null ? extras.getBundle(EXTRA_WEARABLE_EXTENSIONS)
   3830                     : null;
   3831             if (wearableBundle != null) {
   3832                 final ArrayList<Parcelable> parcelables =
   3833                         wearableBundle.getParcelableArrayList(KEY_ACTIONS);
   3834                 if (Build.VERSION.SDK_INT >= 16 && parcelables != null) {
   3835                     Action[] actions = new Action[parcelables.size()];
   3836                     for (int i = 0; i < actions.length; i++) {
   3837                         if (Build.VERSION.SDK_INT >= 20) {
   3838                             actions[i] = NotificationCompat.getActionCompatFromAction(
   3839                                     (Notification.Action) parcelables.get(i));
   3840                         } else if (Build.VERSION.SDK_INT >= 16) {
   3841                             actions[i] = NotificationCompatJellybean.getActionFromBundle(
   3842                                     (Bundle) parcelables.get(i));
   3843                         }
   3844                     }
   3845                     Collections.addAll(mActions, (Action[]) actions);
   3846                 }
   3847 
   3848                 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
   3849                 mDisplayIntent = wearableBundle.getParcelable(KEY_DISPLAY_INTENT);
   3850 
   3851                 Notification[] pages = getNotificationArrayFromBundle(
   3852                         wearableBundle, KEY_PAGES);
   3853                 if (pages != null) {
   3854                     Collections.addAll(mPages, pages);
   3855                 }
   3856 
   3857                 mBackground = wearableBundle.getParcelable(KEY_BACKGROUND);
   3858                 mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON);
   3859                 mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY,
   3860                         DEFAULT_CONTENT_ICON_GRAVITY);
   3861                 mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX,
   3862                         UNSET_ACTION_INDEX);
   3863                 mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET,
   3864                         SIZE_DEFAULT);
   3865                 mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT);
   3866                 mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY);
   3867                 mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT);
   3868                 mDismissalId = wearableBundle.getString(KEY_DISMISSAL_ID);
   3869                 mBridgeTag = wearableBundle.getString(KEY_BRIDGE_TAG);
   3870             }
   3871         }
   3872 
   3873         /**
   3874          * Apply wearable extensions to a notification that is being built. This is typically
   3875          * called by the {@link NotificationCompat.Builder#extend} method of
   3876          * {@link NotificationCompat.Builder}.
   3877          */
   3878         @Override
   3879         public NotificationCompat.Builder extend(NotificationCompat.Builder builder) {
   3880             Bundle wearableBundle = new Bundle();
   3881 
   3882             if (!mActions.isEmpty()) {
   3883                 if (Build.VERSION.SDK_INT >= 16) {
   3884                     ArrayList<Parcelable> parcelables = new ArrayList<>(mActions.size());
   3885                     for (Action action : mActions) {
   3886                         if (Build.VERSION.SDK_INT >= 20) {
   3887                             parcelables.add(
   3888                                     WearableExtender.getActionFromActionCompat(action));
   3889                         } else if (Build.VERSION.SDK_INT >= 16) {
   3890                             parcelables.add(NotificationCompatJellybean.getBundleForAction(action));
   3891                         }
   3892                     }
   3893                     wearableBundle.putParcelableArrayList(KEY_ACTIONS, parcelables);
   3894                 } else {
   3895                     wearableBundle.putParcelableArrayList(KEY_ACTIONS, null);
   3896                 }
   3897             }
   3898             if (mFlags != DEFAULT_FLAGS) {
   3899                 wearableBundle.putInt(KEY_FLAGS, mFlags);
   3900             }
   3901             if (mDisplayIntent != null) {
   3902                 wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent);
   3903             }
   3904             if (!mPages.isEmpty()) {
   3905                 wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray(
   3906                         new Notification[mPages.size()]));
   3907             }
   3908             if (mBackground != null) {
   3909                 wearableBundle.putParcelable(KEY_BACKGROUND, mBackground);
   3910             }
   3911             if (mContentIcon != 0) {
   3912                 wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon);
   3913             }
   3914             if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) {
   3915                 wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity);
   3916             }
   3917             if (mContentActionIndex != UNSET_ACTION_INDEX) {
   3918                 wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX,
   3919                         mContentActionIndex);
   3920             }
   3921             if (mCustomSizePreset != SIZE_DEFAULT) {
   3922                 wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset);
   3923             }
   3924             if (mCustomContentHeight != 0) {
   3925                 wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight);
   3926             }
   3927             if (mGravity != DEFAULT_GRAVITY) {
   3928                 wearableBundle.putInt(KEY_GRAVITY, mGravity);
   3929             }
   3930             if (mHintScreenTimeout != 0) {
   3931                 wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout);
   3932             }
   3933             if (mDismissalId != null) {
   3934                 wearableBundle.putString(KEY_DISMISSAL_ID, mDismissalId);
   3935             }
   3936             if (mBridgeTag != null) {
   3937                 wearableBundle.putString(KEY_BRIDGE_TAG, mBridgeTag);
   3938             }
   3939 
   3940             builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
   3941             return builder;
   3942         }
   3943 
   3944         @RequiresApi(20)
   3945         private static Notification.Action getActionFromActionCompat(Action actionCompat) {
   3946             Notification.Action.Builder actionBuilder = new Notification.Action.Builder(
   3947                     actionCompat.getIcon(), actionCompat.getTitle(),
   3948                     actionCompat.getActionIntent());
   3949             Bundle actionExtras;
   3950             if (actionCompat.getExtras() != null) {
   3951                 actionExtras = new Bundle(actionCompat.getExtras());
   3952             } else {
   3953                 actionExtras = new Bundle();
   3954             }
   3955             actionExtras.putBoolean(NotificationCompatJellybean.EXTRA_ALLOW_GENERATED_REPLIES,
   3956                     actionCompat.getAllowGeneratedReplies());
   3957             if (Build.VERSION.SDK_INT >= 24) {
   3958                 actionBuilder.setAllowGeneratedReplies(actionCompat.getAllowGeneratedReplies());
   3959             }
   3960             actionBuilder.addExtras(actionExtras);
   3961             RemoteInput[] remoteInputCompats = actionCompat.getRemoteInputs();
   3962             if (remoteInputCompats != null) {
   3963                 android.app.RemoteInput[] remoteInputs = RemoteInput.fromCompat(remoteInputCompats);
   3964                 for (android.app.RemoteInput remoteInput : remoteInputs) {
   3965                     actionBuilder.addRemoteInput(remoteInput);
   3966                 }
   3967             }
   3968             return actionBuilder.build();
   3969         }
   3970 
   3971         @Override
   3972         public WearableExtender clone() {
   3973             WearableExtender that = new WearableExtender();
   3974             that.mActions = new ArrayList<>(this.mActions);
   3975             that.mFlags = this.mFlags;
   3976             that.mDisplayIntent = this.mDisplayIntent;
   3977             that.mPages = new ArrayList<>(this.mPages);
   3978             that.mBackground = this.mBackground;
   3979             that.mContentIcon = this.mContentIcon;
   3980             that.mContentIconGravity = this.mContentIconGravity;
   3981             that.mContentActionIndex = this.mContentActionIndex;
   3982             that.mCustomSizePreset = this.mCustomSizePreset;
   3983             that.mCustomContentHeight = this.mCustomContentHeight;
   3984             that.mGravity = this.mGravity;
   3985             that.mHintScreenTimeout = this.mHintScreenTimeout;
   3986             that.mDismissalId = this.mDismissalId;
   3987             that.mBridgeTag = this.mBridgeTag;
   3988             return that;
   3989         }
   3990 
   3991         /**
   3992          * Add a wearable action to this notification.
   3993          *
   3994          * <p>When wearable actions are added using this method, the set of actions that
   3995          * show on a wearable device splits from devices that only show actions added
   3996          * using {@link NotificationCompat.Builder#addAction}. This allows for customization
   3997          * of which actions display on different devices.
   3998          *
   3999          * @param action the action to add to this notification
   4000          * @return this object for method chaining
   4001          * @see NotificationCompat.Action
   4002          */
   4003         public WearableExtender addAction(Action action) {
   4004             mActions.add(action);
   4005             return this;
   4006         }
   4007 
   4008         /**
   4009          * Adds wearable actions to this notification.
   4010          *
   4011          * <p>When wearable actions are added using this method, the set of actions that
   4012          * show on a wearable device splits from devices that only show actions added
   4013          * using {@link NotificationCompat.Builder#addAction}. This allows for customization
   4014          * of which actions display on different devices.
   4015          *
   4016          * @param actions the actions to add to this notification
   4017          * @return this object for method chaining
   4018          * @see NotificationCompat.Action
   4019          */
   4020         public WearableExtender addActions(List<Action> actions) {
   4021             mActions.addAll(actions);
   4022             return this;
   4023         }
   4024 
   4025         /**
   4026          * Clear all wearable actions present on this builder.
   4027          * @return this object for method chaining.
   4028          * @see #addAction
   4029          */
   4030         public WearableExtender clearActions() {
   4031             mActions.clear();
   4032             return this;
   4033         }
   4034 
   4035         /**
   4036          * Get the wearable actions present on this notification.
   4037          */
   4038         public List<Action> getActions() {
   4039             return mActions;
   4040         }
   4041 
   4042         /**
   4043          * Set an intent to launch inside of an activity view when displaying
   4044          * this notification. The {@link PendingIntent} provided should be for an activity.
   4045          *
   4046          * <pre class="prettyprint">
   4047          * Intent displayIntent = new Intent(context, MyDisplayActivity.class);
   4048          * PendingIntent displayPendingIntent = PendingIntent.getActivity(context,
   4049          *         0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT);
   4050          * Notification notification = new NotificationCompat.Builder(context)
   4051          *         .extend(new NotificationCompat.WearableExtender()
   4052          *                 .setDisplayIntent(displayPendingIntent)
   4053          *                 .setCustomSizePreset(NotificationCompat.WearableExtender.SIZE_MEDIUM))
   4054          *         .build();</pre>
   4055          *
   4056          * <p>The activity to launch needs to allow embedding, must be exported, and
   4057          * should have an empty task affinity. It is also recommended to use the device
   4058          * default light theme.
   4059          *
   4060          * <p>Example AndroidManifest.xml entry:
   4061          * <pre class="prettyprint">
   4062          * &lt;activity android:name=&quot;com.example.MyDisplayActivity&quot;
   4063          *     android:exported=&quot;true&quot;
   4064          *     android:allowEmbedded=&quot;true&quot;
   4065          *     android:taskAffinity=&quot;&quot;
   4066          *     android:theme=&quot;@android:style/Theme.DeviceDefault.Light&quot; /&gt;</pre>
   4067          *
   4068          * @param intent the {@link PendingIntent} for an activity
   4069          * @return this object for method chaining
   4070          * @see NotificationCompat.WearableExtender#getDisplayIntent
   4071          */
   4072         public WearableExtender setDisplayIntent(PendingIntent intent) {
   4073             mDisplayIntent = intent;
   4074             return this;
   4075         }
   4076 
   4077         /**
   4078          * Get the intent to launch inside of an activity view when displaying this
   4079          * notification. This {@code PendingIntent} should be for an activity.
   4080          */
   4081         public PendingIntent getDisplayIntent() {
   4082             return mDisplayIntent;
   4083         }
   4084 
   4085         /**
   4086          * Add an additional page of content to display with this notification. The current
   4087          * notification forms the first page, and pages added using this function form
   4088          * subsequent pages. This field can be used to separate a notification into multiple
   4089          * sections.
   4090          *
   4091          * @param page the notification to add as another page
   4092          * @return this object for method chaining
   4093          * @see NotificationCompat.WearableExtender#getPages
   4094          */
   4095         public WearableExtender addPage(Notification page) {
   4096             mPages.add(page);
   4097             return this;
   4098         }
   4099 
   4100         /**
   4101          * Add additional pages of content to display with this notification. The current
   4102          * notification forms the first page, and pages added using this function form
   4103          * subsequent pages. This field can be used to separate a notification into multiple
   4104          * sections.
   4105          *
   4106          * @param pages a list of notifications
   4107          * @return this object for method chaining
   4108          * @see NotificationCompat.WearableExtender#getPages
   4109          */
   4110         public WearableExtender addPages(List<Notification> pages) {
   4111             mPages.addAll(pages);
   4112             return this;
   4113         }
   4114 
   4115         /**
   4116          * Clear all additional pages present on this builder.
   4117          * @return this object for method chaining.
   4118          * @see #addPage
   4119          */
   4120         public WearableExtender clearPages() {
   4121             mPages.clear();
   4122             return this;
   4123         }
   4124 
   4125         /**
   4126          * Get the array of additional pages of content for displaying this notification. The
   4127          * current notification forms the first page, and elements within this array form
   4128          * subsequent pages. This field can be used to separate a notification into multiple
   4129          * sections.
   4130          * @return the pages for this notification
   4131          */
   4132         public List<Notification> getPages() {
   4133             return mPages;
   4134         }
   4135 
   4136         /**
   4137          * Set a background image to be displayed behind the notification content.
   4138          * Contrary to the {@link NotificationCompat.BigPictureStyle}, this background
   4139          * will work with any notification style.
   4140          *
   4141          * @param background the background bitmap
   4142          * @return this object for method chaining
   4143          * @see NotificationCompat.WearableExtender#getBackground
   4144          */
   4145         public WearableExtender setBackground(Bitmap background) {
   4146             mBackground = background;
   4147             return this;
   4148         }
   4149 
   4150         /**
   4151          * Get a background image to be displayed behind the notification content.
   4152          * Contrary to the {@link NotificationCompat.BigPictureStyle}, this background
   4153          * will work with any notification style.
   4154          *
   4155          * @return the background image
   4156          * @see NotificationCompat.WearableExtender#setBackground
   4157          */
   4158         public Bitmap getBackground() {
   4159             return mBackground;
   4160         }
   4161 
   4162         /**
   4163          * Set an icon that goes with the content of this notification.
   4164          */
   4165         @Deprecated
   4166         public WearableExtender setContentIcon(int icon) {
   4167             mContentIcon = icon;
   4168             return this;
   4169         }
   4170 
   4171         /**
   4172          * Get an icon that goes with the content of this notification.
   4173          */
   4174         @Deprecated
   4175         public int getContentIcon() {
   4176             return mContentIcon;
   4177         }
   4178 
   4179         /**
   4180          * Set the gravity that the content icon should have within the notification display.
   4181          * Supported values include {@link android.view.Gravity#START} and
   4182          * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
   4183          * @see #setContentIcon
   4184          */
   4185         @Deprecated
   4186         public WearableExtender setContentIconGravity(int contentIconGravity) {
   4187             mContentIconGravity = contentIconGravity;
   4188             return this;
   4189         }
   4190 
   4191         /**
   4192          * Get the gravity that the content icon should have within the notification display.
   4193          * Supported values include {@link android.view.Gravity#START} and
   4194          * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
   4195          * @see #getContentIcon
   4196          */
   4197         @Deprecated
   4198         public int getContentIconGravity() {
   4199             return mContentIconGravity;
   4200         }
   4201 
   4202         /**
   4203          * Set an action from this notification's actions to be clickable with the content of
   4204          * this notification. This action will no longer display separately from the
   4205          * notification's content.
   4206          *
   4207          * <p>For notifications with multiple pages, child pages can also have content actions
   4208          * set, although the list of available actions comes from the main notification and not
   4209          * from the child page's notification.
   4210          *
   4211          * @param actionIndex The index of the action to hoist onto the current notification page.
   4212          *                    If wearable actions were added to the main notification, this index
   4213          *                    will apply to that list, otherwise it will apply to the regular
   4214          *                    actions list.
   4215          */
   4216         public WearableExtender setContentAction(int actionIndex) {
   4217             mContentActionIndex = actionIndex;
   4218             return this;
   4219         }
   4220 
   4221         /**
   4222          * Get the index of the notification action, if any, that was specified as being clickable
   4223          * with the content of this notification. This action will no longer display separately
   4224          * from the notification's content.
   4225          *
   4226          * <p>For notifications with multiple pages, child pages can also have content actions
   4227          * set, although the list of available actions comes from the main notification and not
   4228          * from the child page's notification.
   4229          *
   4230          * <p>If wearable specific actions were added to the main notification, this index will
   4231          * apply to that list, otherwise it will apply to the regular actions list.
   4232          *
   4233          * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected.
   4234          */
   4235         public int getContentAction() {
   4236             return mContentActionIndex;
   4237         }
   4238 
   4239         /**
   4240          * Set the gravity that this notification should have within the available viewport space.
   4241          * Supported values include {@link android.view.Gravity#TOP},
   4242          * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
   4243          * The default value is {@link android.view.Gravity#BOTTOM}.
   4244          */
   4245         @Deprecated
   4246         public WearableExtender setGravity(int gravity) {
   4247             mGravity = gravity;
   4248             return this;
   4249         }
   4250 
   4251         /**
   4252          * Get the gravity that this notification should have within the available viewport space.
   4253          * Supported values include {@link android.view.Gravity#TOP},
   4254          * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
   4255          * The default value is {@link android.view.Gravity#BOTTOM}.
   4256          */
   4257         @Deprecated
   4258         public int getGravity() {
   4259             return mGravity;
   4260         }
   4261 
   4262         /**
   4263          * Set the custom size preset for the display of this notification out of the available
   4264          * presets found in {@link NotificationCompat.WearableExtender}, e.g.
   4265          * {@link #SIZE_LARGE}.
   4266          * <p>Some custom size presets are only applicable for custom display notifications created
   4267          * using {@link NotificationCompat.WearableExtender#setDisplayIntent}. Check the
   4268          * documentation for the preset in question. See also
   4269          * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}.
   4270          */
   4271         @Deprecated
   4272         public WearableExtender setCustomSizePreset(int sizePreset) {
   4273             mCustomSizePreset = sizePreset;
   4274             return this;
   4275         }
   4276 
   4277         /**
   4278          * Get the custom size preset for the display of this notification out of the available
   4279          * presets found in {@link NotificationCompat.WearableExtender}, e.g.
   4280          * {@link #SIZE_LARGE}.
   4281          * <p>Some custom size presets are only applicable for custom display notifications created
   4282          * using {@link #setDisplayIntent}. Check the documentation for the preset in question.
   4283          * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}.
   4284          */
   4285         @Deprecated
   4286         public int getCustomSizePreset() {
   4287             return mCustomSizePreset;
   4288         }
   4289 
   4290         /**
   4291          * Set the custom height in pixels for the display of this notification's content.
   4292          * <p>This option is only available for custom display notifications created
   4293          * using {@link NotificationCompat.WearableExtender#setDisplayIntent}. See also
   4294          * {@link NotificationCompat.WearableExtender#setCustomSizePreset} and
   4295          * {@link #getCustomContentHeight}.
   4296          */
   4297         @Deprecated
   4298         public WearableExtender setCustomContentHeight(int height) {
   4299             mCustomContentHeight = height;
   4300             return this;
   4301         }
   4302 
   4303         /**
   4304          * Get the custom height in pixels for the display of this notification's content.
   4305          * <p>This option is only available for custom display notifications created
   4306          * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and
   4307          * {@link #setCustomContentHeight}.
   4308          */
   4309         @Deprecated
   4310         public int getCustomContentHeight() {
   4311             return mCustomContentHeight;
   4312         }
   4313 
   4314         /**
   4315          * Set whether the scrolling position for the contents of this notification should start
   4316          * at the bottom of the contents instead of the top when the contents are too long to
   4317          * display within the screen.  Default is false (start scroll at the top).
   4318          */
   4319         public WearableExtender setStartScrollBottom(boolean startScrollBottom) {
   4320             setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom);
   4321             return this;
   4322         }
   4323 
   4324         /**
   4325          * Get whether the scrolling position for the contents of this notification should start
   4326          * at the bottom of the contents instead of the top when the contents are too long to
   4327          * display within the screen. Default is false (start scroll at the top).
   4328          */
   4329         public boolean getStartScrollBottom() {
   4330             return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0;
   4331         }
   4332 
   4333         /**
   4334          * Set whether the content intent is available when the wearable device is not connected
   4335          * to a companion device.  The user can still trigger this intent when the wearable device
   4336          * is offline, but a visual hint will indicate that the content intent may not be available.
   4337          * Defaults to true.
   4338          */
   4339         public WearableExtender setContentIntentAvailableOffline(
   4340                 boolean contentIntentAvailableOffline) {
   4341             setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline);
   4342             return this;
   4343         }
   4344 
   4345         /**
   4346          * Get whether the content intent is available when the wearable device is not connected
   4347          * to a companion device.  The user can still trigger this intent when the wearable device
   4348          * is offline, but a visual hint will indicate that the content intent may not be available.
   4349          * Defaults to true.
   4350          */
   4351         public boolean getContentIntentAvailableOffline() {
   4352             return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0;
   4353         }
   4354 
   4355         /**
   4356          * Set a hint that this notification's icon should not be displayed.
   4357          * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise.
   4358          * @return this object for method chaining
   4359          */
   4360         @Deprecated
   4361         public WearableExtender setHintHideIcon(boolean hintHideIcon) {
   4362             setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon);
   4363             return this;
   4364         }
   4365 
   4366         /**
   4367          * Get a hint that this notification's icon should not be displayed.
   4368          * @return {@code true} if this icon should not be displayed, false otherwise.
   4369          * The default value is {@code false} if this was never set.
   4370          */
   4371         @Deprecated
   4372         public boolean getHintHideIcon() {
   4373             return (mFlags & FLAG_HINT_HIDE_ICON) != 0;
   4374         }
   4375 
   4376         /**
   4377          * Set a visual hint that only the background image of this notification should be
   4378          * displayed, and other semantic content should be hidden. This hint is only applicable
   4379          * to sub-pages added using {@link #addPage}.
   4380          */
   4381         @Deprecated
   4382         public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) {
   4383             setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly);
   4384             return this;
   4385         }
   4386 
   4387         /**
   4388          * Get a visual hint that only the background image of this notification should be
   4389          * displayed, and other semantic content should be hidden. This hint is only applicable
   4390          * to sub-pages added using {@link NotificationCompat.WearableExtender#addPage}.
   4391          */
   4392         @Deprecated
   4393         public boolean getHintShowBackgroundOnly() {
   4394             return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0;
   4395         }
   4396 
   4397         /**
   4398          * Set a hint that this notification's background should not be clipped if possible,
   4399          * and should instead be resized to fully display on the screen, retaining the aspect
   4400          * ratio of the image. This can be useful for images like barcodes or qr codes.
   4401          * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible.
   4402          * @return this object for method chaining
   4403          */
   4404         @Deprecated
   4405         public WearableExtender setHintAvoidBackgroundClipping(
   4406                 boolean hintAvoidBackgroundClipping) {
   4407             setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping);
   4408             return this;
   4409         }
   4410 
   4411         /**
   4412          * Get a hint that this notification's background should not be clipped if possible,
   4413          * and should instead be resized to fully display on the screen, retaining the aspect
   4414          * ratio of the image. This can be useful for images like barcodes or qr codes.
   4415          * @return {@code true} if it's ok if the background is clipped on the screen, false
   4416          * otherwise. The default value is {@code false} if this was never set.
   4417          */
   4418         @Deprecated
   4419         public boolean getHintAvoidBackgroundClipping() {
   4420             return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0;
   4421         }
   4422 
   4423         /**
   4424          * Set a hint that the screen should remain on for at least this duration when
   4425          * this notification is displayed on the screen.
   4426          * @param timeout The requested screen timeout in milliseconds. Can also be either
   4427          *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
   4428          * @return this object for method chaining
   4429          */
   4430         @Deprecated
   4431         public WearableExtender setHintScreenTimeout(int timeout) {
   4432             mHintScreenTimeout = timeout;
   4433             return this;
   4434         }
   4435 
   4436         /**
   4437          * Get the duration, in milliseconds, that the screen should remain on for
   4438          * when this notification is displayed.
   4439          * @return the duration in milliseconds if > 0, or either one of the sentinel values
   4440          *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
   4441          */
   4442         @Deprecated
   4443         public int getHintScreenTimeout() {
   4444             return mHintScreenTimeout;
   4445         }
   4446 
   4447         /**
   4448          * Set a hint that this notification's {@link BigPictureStyle} (if present) should be
   4449          * converted to low-bit and displayed in ambient mode, especially useful for barcodes and
   4450          * qr codes, as well as other simple black-and-white tickets.
   4451          * @param hintAmbientBigPicture {@code true} to enable converstion and ambient.
   4452          * @return this object for method chaining
   4453          */
   4454         public WearableExtender setHintAmbientBigPicture(boolean hintAmbientBigPicture) {
   4455             setFlag(FLAG_BIG_PICTURE_AMBIENT, hintAmbientBigPicture);
   4456             return this;
   4457         }
   4458 
   4459         /**
   4460          * Get a hint that this notification's {@link BigPictureStyle} (if present) should be
   4461          * converted to low-bit and displayed in ambient mode, especially useful for barcodes and
   4462          * qr codes, as well as other simple black-and-white tickets.
   4463          * @return {@code true} if it should be displayed in ambient, false otherwise
   4464          * otherwise. The default value is {@code false} if this was never set.
   4465          */
   4466         public boolean getHintAmbientBigPicture() {
   4467             return (mFlags & FLAG_BIG_PICTURE_AMBIENT) != 0;
   4468         }
   4469 
   4470         /**
   4471          * Set a hint that this notification's content intent will launch an {@link Activity}
   4472          * directly, telling the platform that it can generate the appropriate transitions.
   4473          * @param hintContentIntentLaunchesActivity {@code true} if the content intent will launch
   4474          * an activity and transitions should be generated, false otherwise.
   4475          * @return this object for method chaining
   4476          */
   4477         public WearableExtender setHintContentIntentLaunchesActivity(
   4478                 boolean hintContentIntentLaunchesActivity) {
   4479             setFlag(FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY, hintContentIntentLaunchesActivity);
   4480             return this;
   4481         }
   4482 
   4483         /**
   4484          * Get a hint that this notification's content intent will launch an {@link Activity}
   4485          * directly, telling the platform that it can generate the appropriate transitions
   4486          * @return {@code true} if the content intent will launch an activity and transitions should
   4487          * be generated, false otherwise. The default value is {@code false} if this was never set.
   4488          */
   4489         public boolean getHintContentIntentLaunchesActivity() {
   4490             return (mFlags & FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY) != 0;
   4491         }
   4492 
   4493         /**
   4494          * Sets the dismissal id for this notification. If a notification is posted with a
   4495          * dismissal id, then when that notification is canceled, notifications on other wearables
   4496          * and the paired Android phone having that same dismissal id will also be canceled. See
   4497          * <a href="{@docRoot}wear/notifications/index.html">Adding Wearable Features to
   4498          * Notifications</a> for more information.
   4499          * @param dismissalId the dismissal id of the notification.
   4500          * @return this object for method chaining
   4501          */
   4502         public WearableExtender setDismissalId(String dismissalId) {
   4503             mDismissalId = dismissalId;
   4504             return this;
   4505         }
   4506 
   4507         /**
   4508          * Returns the dismissal id of the notification.
   4509          * @return the dismissal id of the notification or null if it has not been set.
   4510          */
   4511         public String getDismissalId() {
   4512             return mDismissalId;
   4513         }
   4514 
   4515         /**
   4516          * Sets a bridge tag for this notification. A bridge tag can be set for notifications
   4517          * posted from a phone to provide finer-grained control on what notifications are bridged
   4518          * to wearables. See <a href="{@docRoot}wear/notifications/index.html">Adding Wearable
   4519          * Features to Notifications</a> for more information.
   4520          * @param bridgeTag the bridge tag of the notification.
   4521          * @return this object for method chaining
   4522          */
   4523         public WearableExtender setBridgeTag(String bridgeTag) {
   4524             mBridgeTag = bridgeTag;
   4525             return this;
   4526         }
   4527 
   4528         /**
   4529          * Returns the bridge tag of the notification.
   4530          * @return the bridge tag or null if not present.
   4531          */
   4532         public String getBridgeTag() {
   4533             return mBridgeTag;
   4534         }
   4535 
   4536         private void setFlag(int mask, boolean value) {
   4537             if (value) {
   4538                 mFlags |= mask;
   4539             } else {
   4540                 mFlags &= ~mask;
   4541             }
   4542         }
   4543     }
   4544 
   4545     /**
   4546      * <p>Helper class to add Android Auto extensions to notifications. To create a notification
   4547      * with car extensions:
   4548      *
   4549      * <ol>
   4550      *  <li>Create an {@link NotificationCompat.Builder}, setting any desired
   4551      *  properties.
   4552      *  <li>Create a {@link CarExtender}.
   4553      *  <li>Set car-specific properties using the {@code add} and {@code set} methods of
   4554      *  {@link CarExtender}.
   4555      *  <li>Call {@link androidx.core.app.NotificationCompat.Builder#extend(NotificationCompat.Extender)}
   4556      *  to apply the extensions to a notification.
   4557      *  <li>Post the notification to the notification system with the
   4558      *  {@code NotificationManagerCompat.notify(...)} methods and not the
   4559      *  {@code NotificationManager.notify(...)} methods.
   4560      * </ol>
   4561      *
   4562      * <pre class="prettyprint">
   4563      * Notification notification = new NotificationCompat.Builder(context)
   4564      *         ...
   4565      *         .extend(new CarExtender()
   4566      *                 .set*(...))
   4567      *         .build();
   4568      * </pre>
   4569      *
   4570      * <p>Car extensions can be accessed on an existing notification by using the
   4571      * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods
   4572      * to access values.
   4573      */
   4574     public static final class CarExtender implements Extender {
   4575         /** @hide **/
   4576         @RestrictTo(LIBRARY_GROUP)
   4577         static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS";
   4578         private static final String EXTRA_LARGE_ICON = "large_icon";
   4579         private static final String EXTRA_CONVERSATION = "car_conversation";
   4580         private static final String EXTRA_COLOR = "app_color";
   4581         /** @hide **/
   4582         @RestrictTo(LIBRARY_GROUP)
   4583         static final String EXTRA_INVISIBLE_ACTIONS = "invisible_actions";
   4584 
   4585         private static final String KEY_AUTHOR = "author";
   4586         private static final String KEY_TEXT = "text";
   4587         private static final String KEY_MESSAGES = "messages";
   4588         private static final String KEY_REMOTE_INPUT = "remote_input";
   4589         private static final String KEY_ON_REPLY = "on_reply";
   4590         private static final String KEY_ON_READ = "on_read";
   4591         private static final String KEY_PARTICIPANTS = "participants";
   4592         private static final String KEY_TIMESTAMP = "timestamp";
   4593 
   4594         private Bitmap mLargeIcon;
   4595         private UnreadConversation mUnreadConversation;
   4596         private int mColor = NotificationCompat.COLOR_DEFAULT;
   4597 
   4598         /**
   4599          * Create a {@link CarExtender} with default options.
   4600          */
   4601         public CarExtender() {
   4602         }
   4603 
   4604         /**
   4605          * Create a {@link CarExtender} from the CarExtender options of an existing Notification.
   4606          *
   4607          * @param notification The notification from which to copy options.
   4608          */
   4609         public CarExtender(Notification notification) {
   4610             if (Build.VERSION.SDK_INT < 21) {
   4611                 return;
   4612             }
   4613 
   4614             Bundle carBundle = getExtras(notification) == null
   4615                     ? null : getExtras(notification).getBundle(EXTRA_CAR_EXTENDER);
   4616             if (carBundle != null) {
   4617                 mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON);
   4618                 mColor = carBundle.getInt(EXTRA_COLOR, NotificationCompat.COLOR_DEFAULT);
   4619 
   4620                 Bundle b = carBundle.getBundle(EXTRA_CONVERSATION);
   4621                 mUnreadConversation = getUnreadConversationFromBundle(b);
   4622             }
   4623         }
   4624 
   4625         @RequiresApi(21)
   4626         private static UnreadConversation getUnreadConversationFromBundle(@Nullable Bundle b) {
   4627             if (b == null) {
   4628                 return null;
   4629             }
   4630             Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES);
   4631             String[] messages = null;
   4632             if (parcelableMessages != null) {
   4633                 String[] tmp = new String[parcelableMessages.length];
   4634                 boolean success = true;
   4635                 for (int i = 0; i < tmp.length; i++) {
   4636                     if (!(parcelableMessages[i] instanceof Bundle)) {
   4637                         success = false;
   4638                         break;
   4639                     }
   4640                     tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT);
   4641                     if (tmp[i] == null) {
   4642                         success = false;
   4643                         break;
   4644                     }
   4645                 }
   4646                 if (success) {
   4647                     messages = tmp;
   4648                 } else {
   4649                     return null;
   4650                 }
   4651             }
   4652 
   4653             PendingIntent onRead = b.getParcelable(KEY_ON_READ);
   4654             PendingIntent onReply = b.getParcelable(KEY_ON_REPLY);
   4655 
   4656             android.app.RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT);
   4657 
   4658             String[] participants = b.getStringArray(KEY_PARTICIPANTS);
   4659             if (participants == null || participants.length != 1) {
   4660                 return null;
   4661             }
   4662 
   4663             RemoteInput remoteInputCompat = remoteInput != null
   4664                     ? new RemoteInput(remoteInput.getResultKey(),
   4665                     remoteInput.getLabel(),
   4666                     remoteInput.getChoices(),
   4667                     remoteInput.getAllowFreeFormInput(),
   4668                     remoteInput.getExtras(),
   4669                     null /* allowedDataTypes */)
   4670                     : null;
   4671 
   4672             return new UnreadConversation(messages, remoteInputCompat, onReply,
   4673                     onRead, participants, b.getLong(KEY_TIMESTAMP));
   4674         }
   4675 
   4676         @RequiresApi(21)
   4677         private static Bundle getBundleForUnreadConversation(@NonNull UnreadConversation uc) {
   4678             Bundle b = new Bundle();
   4679             String author = null;
   4680             if (uc.getParticipants() != null && uc.getParticipants().length > 1) {
   4681                 author = uc.getParticipants()[0];
   4682             }
   4683             Parcelable[] messages = new Parcelable[uc.getMessages().length];
   4684             for (int i = 0; i < messages.length; i++) {
   4685                 Bundle m = new Bundle();
   4686                 m.putString(KEY_TEXT, uc.getMessages()[i]);
   4687                 m.putString(KEY_AUTHOR, author);
   4688                 messages[i] = m;
   4689             }
   4690             b.putParcelableArray(KEY_MESSAGES, messages);
   4691             RemoteInput remoteInputCompat = uc.getRemoteInput();
   4692             if (remoteInputCompat != null) {
   4693                 android.app.RemoteInput remoteInput =
   4694                         new android.app.RemoteInput.Builder(remoteInputCompat.getResultKey())
   4695                                 .setLabel(remoteInputCompat.getLabel())
   4696                                 .setChoices(remoteInputCompat.getChoices())
   4697                                 .setAllowFreeFormInput(remoteInputCompat.getAllowFreeFormInput())
   4698                                 .addExtras(remoteInputCompat.getExtras())
   4699                                 .build();
   4700                 b.putParcelable(KEY_REMOTE_INPUT, remoteInput);
   4701             }
   4702             b.putParcelable(KEY_ON_REPLY, uc.getReplyPendingIntent());
   4703             b.putParcelable(KEY_ON_READ, uc.getReadPendingIntent());
   4704             b.putStringArray(KEY_PARTICIPANTS, uc.getParticipants());
   4705             b.putLong(KEY_TIMESTAMP, uc.getLatestTimestamp());
   4706             return b;
   4707         }
   4708 
   4709         /**
   4710          * Apply car extensions to a notification that is being built. This is typically called by
   4711          * the {@link androidx.core.app.NotificationCompat.Builder#extend(NotificationCompat.Extender)}
   4712          * method of {@link NotificationCompat.Builder}.
   4713          */
   4714         @Override
   4715         public NotificationCompat.Builder extend(NotificationCompat.Builder builder) {
   4716             if (Build.VERSION.SDK_INT < 21) {
   4717                 return builder;
   4718             }
   4719 
   4720             Bundle carExtensions = new Bundle();
   4721 
   4722             if (mLargeIcon != null) {
   4723                 carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
   4724             }
   4725             if (mColor != NotificationCompat.COLOR_DEFAULT) {
   4726                 carExtensions.putInt(EXTRA_COLOR, mColor);
   4727             }
   4728 
   4729             if (mUnreadConversation != null) {
   4730                 Bundle b = getBundleForUnreadConversation(mUnreadConversation);
   4731                 carExtensions.putBundle(EXTRA_CONVERSATION, b);
   4732             }
   4733 
   4734             builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions);
   4735             return builder;
   4736         }
   4737 
   4738         /**
   4739          * Sets the accent color to use when Android Auto presents the notification.
   4740          *
   4741          * Android Auto uses the color set with {@link androidx.core.app.NotificationCompat.Builder#setColor(int)}
   4742          * to accent the displayed notification. However, not all colors are acceptable in an
   4743          * automotive setting. This method can be used to override the color provided in the
   4744          * notification in such a situation.
   4745          */
   4746         public CarExtender setColor(@ColorInt int color) {
   4747             mColor = color;
   4748             return this;
   4749         }
   4750 
   4751         /**
   4752          * Gets the accent color.
   4753          *
   4754          * @see #setColor
   4755          */
   4756         @ColorInt
   4757         public int getColor() {
   4758             return mColor;
   4759         }
   4760 
   4761         /**
   4762          * Sets the large icon of the car notification.
   4763          *
   4764          * If no large icon is set in the extender, Android Auto will display the icon
   4765          * specified by {@link androidx.core.app.NotificationCompat.Builder#setLargeIcon(android.graphics.Bitmap)}
   4766          *
   4767          * @param largeIcon The large icon to use in the car notification.
   4768          * @return This object for method chaining.
   4769          */
   4770         public CarExtender setLargeIcon(Bitmap largeIcon) {
   4771             mLargeIcon = largeIcon;
   4772             return this;
   4773         }
   4774 
   4775         /**
   4776          * Gets the large icon used in this car notification, or null if no icon has been set.
   4777          *
   4778          * @return The large icon for the car notification.
   4779          * @see CarExtender#setLargeIcon
   4780          */
   4781         public Bitmap getLargeIcon() {
   4782             return mLargeIcon;
   4783         }
   4784 
   4785         /**
   4786          * Sets the unread conversation in a message notification.
   4787          *
   4788          * @param unreadConversation The unread part of the conversation this notification conveys.
   4789          * @return This object for method chaining.
   4790          */
   4791         public CarExtender setUnreadConversation(UnreadConversation unreadConversation) {
   4792             mUnreadConversation = unreadConversation;
   4793             return this;
   4794         }
   4795 
   4796         /**
   4797          * Returns the unread conversation conveyed by this notification.
   4798          * @see #setUnreadConversation(UnreadConversation)
   4799          */
   4800         public UnreadConversation getUnreadConversation() {
   4801             return mUnreadConversation;
   4802         }
   4803 
   4804         /**
   4805          * A class which holds the unread messages from a conversation.
   4806          */
   4807         public static class UnreadConversation {
   4808             private final String[] mMessages;
   4809             private final RemoteInput mRemoteInput;
   4810             private final PendingIntent mReplyPendingIntent;
   4811             private final PendingIntent mReadPendingIntent;
   4812             private final String[] mParticipants;
   4813             private final long mLatestTimestamp;
   4814 
   4815             UnreadConversation(String[] messages, RemoteInput remoteInput,
   4816                     PendingIntent replyPendingIntent, PendingIntent readPendingIntent,
   4817                     String[] participants, long latestTimestamp) {
   4818                 mMessages = messages;
   4819                 mRemoteInput = remoteInput;
   4820                 mReadPendingIntent = readPendingIntent;
   4821                 mReplyPendingIntent = replyPendingIntent;
   4822                 mParticipants = participants;
   4823                 mLatestTimestamp = latestTimestamp;
   4824             }
   4825 
   4826             /**
   4827              * Gets the list of messages conveyed by this notification.
   4828              */
   4829             public String[] getMessages() {
   4830                 return mMessages;
   4831             }
   4832 
   4833             /**
   4834              * Gets the remote input that will be used to convey the response to a message list, or
   4835              * null if no such remote input exists.
   4836              */
   4837             public RemoteInput getRemoteInput() {
   4838                 return mRemoteInput;
   4839             }
   4840 
   4841             /**
   4842              * Gets the pending intent that will be triggered when the user replies to this
   4843              * notification.
   4844              */
   4845             public PendingIntent getReplyPendingIntent() {
   4846                 return mReplyPendingIntent;
   4847             }
   4848 
   4849             /**
   4850              * Gets the pending intent that Android Auto will send after it reads aloud all messages
   4851              * in this object's message list.
   4852              */
   4853             public PendingIntent getReadPendingIntent() {
   4854                 return mReadPendingIntent;
   4855             }
   4856 
   4857             /**
   4858              * Gets the participants in the conversation.
   4859              */
   4860             public String[] getParticipants() {
   4861                 return mParticipants;
   4862             }
   4863 
   4864             /**
   4865              * Gets the firs participant in the conversation.
   4866              */
   4867             public String getParticipant() {
   4868                 return mParticipants.length > 0 ? mParticipants[0] : null;
   4869             }
   4870 
   4871             /**
   4872              * Gets the timestamp of the conversation.
   4873              */
   4874             public long getLatestTimestamp() {
   4875                 return mLatestTimestamp;
   4876             }
   4877 
   4878             /**
   4879              * Builder class for {@link CarExtender.UnreadConversation} objects.
   4880              */
   4881             public static class Builder {
   4882                 private final List<String> mMessages = new ArrayList<String>();
   4883                 private final String mParticipant;
   4884                 private RemoteInput mRemoteInput;
   4885                 private PendingIntent mReadPendingIntent;
   4886                 private PendingIntent mReplyPendingIntent;
   4887                 private long mLatestTimestamp;
   4888 
   4889                 /**
   4890                  * Constructs a new builder for {@link CarExtender.UnreadConversation}.
   4891                  *
   4892                  * @param name The name of the other participant in the conversation.
   4893                  */
   4894                 public Builder(String name) {
   4895                     mParticipant = name;
   4896                 }
   4897 
   4898                 /**
   4899                  * Appends a new unread message to the list of messages for this conversation.
   4900                  *
   4901                  * The messages should be added from oldest to newest.
   4902                  *
   4903                  * @param message The text of the new unread message.
   4904                  * @return This object for method chaining.
   4905                  */
   4906                 public Builder addMessage(String message) {
   4907                     mMessages.add(message);
   4908                     return this;
   4909                 }
   4910 
   4911                 /**
   4912                  * Sets the pending intent and remote input which will convey the reply to this
   4913                  * notification.
   4914                  *
   4915                  * @param pendingIntent The pending intent which will be triggered on a reply.
   4916                  * @param remoteInput The remote input parcelable which will carry the reply.
   4917                  * @return This object for method chaining.
   4918                  *
   4919                  * @see CarExtender.UnreadConversation#getRemoteInput
   4920                  * @see CarExtender.UnreadConversation#getReplyPendingIntent
   4921                  */
   4922                 public Builder setReplyAction(
   4923                         PendingIntent pendingIntent, RemoteInput remoteInput) {
   4924                     mRemoteInput = remoteInput;
   4925                     mReplyPendingIntent = pendingIntent;
   4926 
   4927                     return this;
   4928                 }
   4929 
   4930                 /**
   4931                  * Sets the pending intent that will be sent once the messages in this notification
   4932                  * are read.
   4933                  *
   4934                  * @param pendingIntent The pending intent to use.
   4935                  * @return This object for method chaining.
   4936                  */
   4937                 public Builder setReadPendingIntent(PendingIntent pendingIntent) {
   4938                     mReadPendingIntent = pendingIntent;
   4939                     return this;
   4940                 }
   4941 
   4942                 /**
   4943                  * Sets the timestamp of the most recent message in an unread conversation.
   4944                  *
   4945                  * If a messaging notification has been posted by your application and has not
   4946                  * yet been cancelled, posting a later notification with the same id and tag
   4947                  * but without a newer timestamp may result in Android Auto not displaying a
   4948                  * heads up notification for the later notification.
   4949                  *
   4950                  * @param timestamp The timestamp of the most recent message in the conversation.
   4951                  * @return This object for method chaining.
   4952                  */
   4953                 public Builder setLatestTimestamp(long timestamp) {
   4954                     mLatestTimestamp = timestamp;
   4955                     return this;
   4956                 }
   4957 
   4958                 /**
   4959                  * Builds a new unread conversation object.
   4960                  *
   4961                  * @return The new unread conversation object.
   4962                  */
   4963                 public UnreadConversation build() {
   4964                     String[] messages = mMessages.toArray(new String[mMessages.size()]);
   4965                     String[] participants = { mParticipant };
   4966                     return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent,
   4967                             mReadPendingIntent, participants, mLatestTimestamp);
   4968                 }
   4969             }
   4970         }
   4971     }
   4972 
   4973 
   4974     /**
   4975      * Get an array of Notification objects from a parcelable array bundle field.
   4976      * Update the bundle to have a typed array so fetches in the future don't need
   4977      * to do an array copy.
   4978      */
   4979     static Notification[] getNotificationArrayFromBundle(Bundle bundle, String key) {
   4980         Parcelable[] array = bundle.getParcelableArray(key);
   4981         if (array instanceof Notification[] || array == null) {
   4982             return (Notification[]) array;
   4983         }
   4984         Notification[] typedArray = new Notification[array.length];
   4985         for (int i = 0; i < array.length; i++) {
   4986             typedArray[i] = (Notification) array[i];
   4987         }
   4988         bundle.putParcelableArray(key, typedArray);
   4989         return typedArray;
   4990     }
   4991 
   4992     /**
   4993      * Gets the {@link Notification#extras} field from a notification in a backwards
   4994      * compatible manner. Extras field was supported from JellyBean (Api level 16)
   4995      * forwards. This function will return null on older api levels.
   4996      */
   4997     public static Bundle getExtras(Notification notification) {
   4998         if (Build.VERSION.SDK_INT >= 19) {
   4999             return notification.extras;
   5000         } else if (Build.VERSION.SDK_INT >= 16) {
   5001             return NotificationCompatJellybean.getExtras(notification);
   5002         } else {
   5003             return null;
   5004         }
   5005     }
   5006 
   5007     /**
   5008      * Get the number of actions in this notification in a backwards compatible
   5009      * manner. Actions were supported from JellyBean (Api level 16) forwards.
   5010      */
   5011     public static int getActionCount(Notification notification) {
   5012         if (Build.VERSION.SDK_INT >= 19) {
   5013             return notification.actions != null ? notification.actions.length : 0;
   5014         } else if (Build.VERSION.SDK_INT >= 16) {
   5015             return NotificationCompatJellybean.getActionCount(notification);
   5016         } else {
   5017             return 0;
   5018         }
   5019     }
   5020 
   5021     /**
   5022      * Get an action on this notification in a backwards compatible
   5023      * manner. Actions were supported from JellyBean (Api level 16) forwards.
   5024      * @param notification The notification to inspect.
   5025      * @param actionIndex The index of the action to retrieve.
   5026      */
   5027     public static Action getAction(Notification notification, int actionIndex) {
   5028         if (Build.VERSION.SDK_INT >= 20) {
   5029             return getActionCompatFromAction(notification.actions[actionIndex]);
   5030         } else if (Build.VERSION.SDK_INT >= 19) {
   5031             Notification.Action action = notification.actions[actionIndex];
   5032             Bundle actionExtras = null;
   5033             SparseArray<Bundle> actionExtrasMap = notification.extras.getSparseParcelableArray(
   5034                     NotificationCompatExtras.EXTRA_ACTION_EXTRAS);
   5035             if (actionExtrasMap != null) {
   5036                 actionExtras = actionExtrasMap.get(actionIndex);
   5037             }
   5038             return NotificationCompatJellybean.readAction(action.icon, action.title,
   5039                     action.actionIntent, actionExtras);
   5040         } else if (Build.VERSION.SDK_INT >= 16) {
   5041             return NotificationCompatJellybean.getAction(notification, actionIndex);
   5042         } else {
   5043             return null;
   5044         }
   5045     }
   5046 
   5047     @RequiresApi(20)
   5048     static Action getActionCompatFromAction(Notification.Action action) {
   5049         final RemoteInput[] remoteInputs;
   5050         final android.app.RemoteInput[] srcArray = action.getRemoteInputs();
   5051         if (srcArray == null) {
   5052             remoteInputs = null;
   5053         } else {
   5054             remoteInputs = new RemoteInput[srcArray.length];
   5055             for (int i = 0; i < srcArray.length; i++) {
   5056                 android.app.RemoteInput src = srcArray[i];
   5057                 remoteInputs[i] = new RemoteInput(src.getResultKey(), src.getLabel(),
   5058                         src.getChoices(), src.getAllowFreeFormInput(), src.getExtras(), null);
   5059             }
   5060         }
   5061 
   5062         final boolean allowGeneratedReplies;
   5063         if (Build.VERSION.SDK_INT >= 24) {
   5064             allowGeneratedReplies = action.getExtras().getBoolean(
   5065                     NotificationCompatJellybean.EXTRA_ALLOW_GENERATED_REPLIES)
   5066                     || action.getAllowGeneratedReplies();
   5067         } else {
   5068             allowGeneratedReplies = action.getExtras().getBoolean(
   5069                     NotificationCompatJellybean.EXTRA_ALLOW_GENERATED_REPLIES);
   5070         }
   5071 
   5072         final boolean showsUserInterface =
   5073                 action.getExtras().getBoolean(Action.EXTRA_SHOWS_USER_INTERFACE, true);
   5074 
   5075         final @Action.SemanticAction int semanticAction;
   5076         if (Build.VERSION.SDK_INT >= 28) {
   5077             semanticAction = action.getSemanticAction();
   5078         } else {
   5079             semanticAction = action.getExtras().getInt(
   5080                     Action.EXTRA_SEMANTIC_ACTION, Action.SEMANTIC_ACTION_NONE);
   5081         }
   5082 
   5083         return new Action(action.icon, action.title, action.actionIntent,
   5084                 action.getExtras(), remoteInputs, null, allowGeneratedReplies,
   5085                 semanticAction, showsUserInterface);
   5086     }
   5087 
   5088     /** Returns the invisible actions contained within the given notification. */
   5089     @RequiresApi(21)
   5090     public static List<Action> getInvisibleActions(Notification notification) {
   5091         ArrayList<Action> result = new ArrayList<>();
   5092 
   5093         Bundle carExtenderBundle = notification.extras.getBundle(CarExtender.EXTRA_CAR_EXTENDER);
   5094         if (carExtenderBundle == null) {
   5095             return result;
   5096         }
   5097 
   5098         Bundle listBundle = carExtenderBundle.getBundle(CarExtender.EXTRA_INVISIBLE_ACTIONS);
   5099         if (listBundle != null) {
   5100             for (int i = 0; i < listBundle.size(); i++) {
   5101                 result.add(NotificationCompatJellybean.getActionFromBundle(
   5102                         listBundle.getBundle(Integer.toString(i))));
   5103             }
   5104         }
   5105         return result;
   5106     }
   5107 
   5108     /** Returns the content title of a {@link Notification}. **/
   5109     @RequiresApi(19)
   5110     public static CharSequence getContentTitle(Notification notification) {
   5111         return notification.extras.getCharSequence(Notification.EXTRA_TITLE);
   5112     }
   5113 
   5114     /**
   5115      * Get the category of this notification in a backwards compatible
   5116      * manner.
   5117      * @param notification The notification to inspect.
   5118      */
   5119     public static String getCategory(Notification notification) {
   5120         if (Build.VERSION.SDK_INT >= 21) {
   5121             return notification.category;
   5122         } else {
   5123             return null;
   5124         }
   5125     }
   5126 
   5127     /**
   5128      * Get whether or not this notification is only relevant to the current device.
   5129      *
   5130      * <p>Some notifications can be bridged to other devices for remote display.
   5131      * If this hint is set, it is recommend that this notification not be bridged.
   5132      */
   5133     public static boolean getLocalOnly(Notification notification) {
   5134         if (Build.VERSION.SDK_INT >= 20) {
   5135             return (notification.flags & Notification.FLAG_LOCAL_ONLY) != 0;
   5136         } else if (Build.VERSION.SDK_INT >= 19) {
   5137             return notification.extras.getBoolean(NotificationCompatExtras.EXTRA_LOCAL_ONLY);
   5138         } else if (Build.VERSION.SDK_INT >= 16) {
   5139             return NotificationCompatJellybean.getExtras(notification).getBoolean(
   5140                     NotificationCompatExtras.EXTRA_LOCAL_ONLY);
   5141         } else {
   5142             return false;
   5143         }
   5144     }
   5145 
   5146     /**
   5147      * Get the key used to group this notification into a cluster or stack
   5148      * with other notifications on devices which support such rendering.
   5149      */
   5150     public static String getGroup(Notification notification) {
   5151         if (Build.VERSION.SDK_INT >= 20) {
   5152             return notification.getGroup();
   5153         } else if (Build.VERSION.SDK_INT >= 19) {
   5154             return notification.extras.getString(NotificationCompatExtras.EXTRA_GROUP_KEY);
   5155         } else if (Build.VERSION.SDK_INT >= 16) {
   5156             return NotificationCompatJellybean.getExtras(notification).getString(
   5157                     NotificationCompatExtras.EXTRA_GROUP_KEY);
   5158         } else {
   5159             return null;
   5160         }
   5161     }
   5162 
   5163     /**
   5164      * Get whether this notification to be the group summary for a group of notifications.
   5165      * Grouped notifications may display in a cluster or stack on devices which
   5166      * support such rendering. Requires a group key also be set using {@link Builder#setGroup}.
   5167      * @return Whether this notification is a group summary.
   5168      */
   5169     public static boolean isGroupSummary(Notification notification) {
   5170         if (Build.VERSION.SDK_INT >= 20) {
   5171             return (notification.flags & Notification.FLAG_GROUP_SUMMARY) != 0;
   5172         } else if (Build.VERSION.SDK_INT >= 19) {
   5173             return notification.extras.getBoolean(NotificationCompatExtras.EXTRA_GROUP_SUMMARY);
   5174         } else if (Build.VERSION.SDK_INT >= 16) {
   5175             return NotificationCompatJellybean.getExtras(notification).getBoolean(
   5176                     NotificationCompatExtras.EXTRA_GROUP_SUMMARY);
   5177         } else {
   5178             return false;
   5179         }
   5180     }
   5181 
   5182     /**
   5183      * Get a sort key that orders this notification among other notifications from the
   5184      * same package. This can be useful if an external sort was already applied and an app
   5185      * would like to preserve this. Notifications will be sorted lexicographically using this
   5186      * value, although providing different priorities in addition to providing sort key may
   5187      * cause this value to be ignored.
   5188      *
   5189      * <p>This sort key can also be used to order members of a notification group. See
   5190      * {@link Builder#setGroup}.
   5191      *
   5192      * @see String#compareTo(String)
   5193      */
   5194     public static String getSortKey(Notification notification) {
   5195         if (Build.VERSION.SDK_INT >= 20) {
   5196             return notification.getSortKey();
   5197         } else if (Build.VERSION.SDK_INT >= 19) {
   5198             return notification.extras.getString(NotificationCompatExtras.EXTRA_SORT_KEY);
   5199         } else if (Build.VERSION.SDK_INT >= 16) {
   5200             return NotificationCompatJellybean.getExtras(notification).getString(
   5201                     NotificationCompatExtras.EXTRA_SORT_KEY);
   5202         } else {
   5203             return null;
   5204         }
   5205     }
   5206 
   5207     /**
   5208      * @return the ID of the channel this notification posts to.
   5209      */
   5210     public static String getChannelId(Notification notification) {
   5211         if (Build.VERSION.SDK_INT >= 26) {
   5212             return notification.getChannelId();
   5213         } else {
   5214             return null;
   5215         }
   5216     }
   5217 
   5218     /**
   5219      * Returns the time at which this notification should be canceled by the system, if it's not
   5220      * canceled already.
   5221      */
   5222     public static long getTimeoutAfter(Notification notification) {
   5223         if (Build.VERSION.SDK_INT >= 26) {
   5224             return notification.getTimeoutAfter();
   5225         } else {
   5226             return 0;
   5227         }
   5228     }
   5229 
   5230     /**
   5231      * Returns what icon should be shown for this notification if it is being displayed in a
   5232      * Launcher that supports badging. Will be one of {@link #BADGE_ICON_NONE},
   5233      * {@link #BADGE_ICON_SMALL}, or {@link #BADGE_ICON_LARGE}.
   5234      */
   5235     public static int getBadgeIconType(Notification notification) {
   5236         if (Build.VERSION.SDK_INT >= 26) {
   5237             return notification.getBadgeIconType();
   5238         } else {
   5239             return BADGE_ICON_NONE;
   5240         }
   5241     }
   5242 
   5243     /**
   5244      * Returns the {@link androidx.core.content.pm.ShortcutInfoCompat#getId() id} that this
   5245      * notification supersedes, if any.
   5246      */
   5247     public static String getShortcutId(Notification notification) {
   5248         if (Build.VERSION.SDK_INT >= 26) {
   5249             return notification.getShortcutId();
   5250         } else {
   5251             return null;
   5252         }
   5253     }
   5254 
   5255     /**
   5256      * Returns which type of notifications in a group are responsible for audibly alerting the
   5257      * user. See {@link #GROUP_ALERT_ALL}, {@link #GROUP_ALERT_CHILDREN},
   5258      * {@link #GROUP_ALERT_SUMMARY}.
   5259      */
   5260     @GroupAlertBehavior
   5261     public static int getGroupAlertBehavior(Notification notification) {
   5262         if (Build.VERSION.SDK_INT >= 26) {
   5263             return notification.getGroupAlertBehavior();
   5264         } else {
   5265             return GROUP_ALERT_ALL;
   5266         }
   5267     }
   5268 
   5269     /** @deprecated This type should not be instantiated as it contains only static methods. */
   5270     @Deprecated
   5271     @SuppressWarnings("PrivateConstructorForUtilityClass")
   5272     public NotificationCompat() {
   5273     }
   5274 }
   5275