Home | History | Annotate | Download | only in app
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.app;
     18 
     19 import android.annotation.ColorInt;
     20 import android.annotation.DrawableRes;
     21 import android.annotation.IntDef;
     22 import android.annotation.SdkConstant;
     23 import android.annotation.SdkConstant.SdkConstantType;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.pm.ApplicationInfo;
     27 import android.content.pm.PackageManager.NameNotFoundException;
     28 import android.content.res.ColorStateList;
     29 import android.graphics.Bitmap;
     30 import android.graphics.Canvas;
     31 import android.graphics.PorterDuff;
     32 import android.graphics.drawable.Drawable;
     33 import android.graphics.drawable.Icon;
     34 import android.media.AudioAttributes;
     35 import android.media.AudioManager;
     36 import android.media.session.MediaSession;
     37 import android.net.Uri;
     38 import android.os.BadParcelableException;
     39 import android.os.Build;
     40 import android.os.Bundle;
     41 import android.os.Parcel;
     42 import android.os.Parcelable;
     43 import android.os.SystemClock;
     44 import android.os.UserHandle;
     45 import android.text.TextUtils;
     46 import android.util.Log;
     47 import android.util.MathUtils;
     48 import android.util.TypedValue;
     49 import android.view.Gravity;
     50 import android.view.View;
     51 import android.widget.ProgressBar;
     52 import android.widget.RemoteViews;
     53 
     54 import com.android.internal.R;
     55 import com.android.internal.util.NotificationColorUtil;
     56 
     57 import java.lang.annotation.Retention;
     58 import java.lang.annotation.RetentionPolicy;
     59 import java.lang.reflect.Constructor;
     60 import java.text.NumberFormat;
     61 import java.util.ArrayList;
     62 import java.util.Arrays;
     63 import java.util.Collections;
     64 import java.util.List;
     65 
     66 /**
     67  * A class that represents how a persistent notification is to be presented to
     68  * the user using the {@link android.app.NotificationManager}.
     69  *
     70  * <p>The {@link Notification.Builder Notification.Builder} has been added to make it
     71  * easier to construct Notifications.</p>
     72  *
     73  * <div class="special reference">
     74  * <h3>Developer Guides</h3>
     75  * <p>For a guide to creating notifications, read the
     76  * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Status Bar Notifications</a>
     77  * developer guide.</p>
     78  * </div>
     79  */
     80 public class Notification implements Parcelable
     81 {
     82     private static final String TAG = "Notification";
     83 
     84     /**
     85      * An activity that provides a user interface for adjusting notification preferences for its
     86      * containing application. Optional but recommended for apps that post
     87      * {@link android.app.Notification Notifications}.
     88      */
     89     @SdkConstant(SdkConstantType.INTENT_CATEGORY)
     90     public static final String INTENT_CATEGORY_NOTIFICATION_PREFERENCES
     91             = "android.intent.category.NOTIFICATION_PREFERENCES";
     92 
     93     /**
     94      * Use all default values (where applicable).
     95      */
     96     public static final int DEFAULT_ALL = ~0;
     97 
     98     /**
     99      * Use the default notification sound. This will ignore any given
    100      * {@link #sound}.
    101      *
    102      * <p>
    103      * A notification that is noisy is more likely to be presented as a heads-up notification.
    104      * </p>
    105      *
    106      * @see #defaults
    107      */
    108 
    109     public static final int DEFAULT_SOUND = 1;
    110 
    111     /**
    112      * Use the default notification vibrate. This will ignore any given
    113      * {@link #vibrate}. Using phone vibration requires the
    114      * {@link android.Manifest.permission#VIBRATE VIBRATE} permission.
    115      *
    116      * <p>
    117      * A notification that vibrates is more likely to be presented as a heads-up notification.
    118      * </p>
    119      *
    120      * @see #defaults
    121      */
    122 
    123     public static final int DEFAULT_VIBRATE = 2;
    124 
    125     /**
    126      * Use the default notification lights. This will ignore the
    127      * {@link #FLAG_SHOW_LIGHTS} bit, and {@link #ledARGB}, {@link #ledOffMS}, or
    128      * {@link #ledOnMS}.
    129      *
    130      * @see #defaults
    131      */
    132 
    133     public static final int DEFAULT_LIGHTS = 4;
    134 
    135     /**
    136      * Maximum length of CharSequences accepted by Builder and friends.
    137      *
    138      * <p>
    139      * Avoids spamming the system with overly large strings such as full e-mails.
    140      */
    141     private static final int MAX_CHARSEQUENCE_LENGTH = 5 * 1024;
    142 
    143     /**
    144      * A timestamp related to this notification, in milliseconds since the epoch.
    145      *
    146      * Default value: {@link System#currentTimeMillis() Now}.
    147      *
    148      * Choose a timestamp that will be most relevant to the user. For most finite events, this
    149      * corresponds to the time the event happened (or will happen, in the case of events that have
    150      * yet to occur but about which the user is being informed). Indefinite events should be
    151      * timestamped according to when the activity began.
    152      *
    153      * Some examples:
    154      *
    155      * <ul>
    156      *   <li>Notification of a new chat message should be stamped when the message was received.</li>
    157      *   <li>Notification of an ongoing file download (with a progress bar, for example) should be stamped when the download started.</li>
    158      *   <li>Notification of a completed file download should be stamped when the download finished.</li>
    159      *   <li>Notification of an upcoming meeting should be stamped with the time the meeting will begin (that is, in the future).</li>
    160      *   <li>Notification of an ongoing stopwatch (increasing timer) should be stamped with the watch's start time.
    161      *   <li>Notification of an ongoing countdown timer should be stamped with the timer's end time.
    162      * </ul>
    163      *
    164      */
    165     public long when;
    166 
    167     /**
    168      * The resource id of a drawable to use as the icon in the status bar.
    169      *
    170      * @deprecated Use {@link Builder#setSmallIcon(Icon)} instead.
    171      */
    172     @Deprecated
    173     @DrawableRes
    174     public int icon;
    175 
    176     /**
    177      * If the icon in the status bar is to have more than one level, you can set this.  Otherwise,
    178      * leave it at its default value of 0.
    179      *
    180      * @see android.widget.ImageView#setImageLevel
    181      * @see android.graphics.drawable.Drawable#setLevel
    182      */
    183     public int iconLevel;
    184 
    185     /**
    186      * The number of events that this notification represents. For example, in a new mail
    187      * notification, this could be the number of unread messages.
    188      *
    189      * The system may or may not use this field to modify the appearance of the notification. For
    190      * example, before {@link android.os.Build.VERSION_CODES#HONEYCOMB}, this number was
    191      * superimposed over the icon in the status bar. Starting with
    192      * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, the template used by
    193      * {@link Notification.Builder} has displayed the number in the expanded notification view.
    194      *
    195      * If the number is 0 or negative, it is never shown.
    196      */
    197     public int number;
    198 
    199     /**
    200      * The intent to execute when the expanded status entry is clicked.  If
    201      * this is an activity, it must include the
    202      * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
    203      * that you take care of task management as described in the
    204      * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
    205      * Stack</a> document.  In particular, make sure to read the notification section
    206      * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html#HandlingNotifications">Handling
    207      * Notifications</a> for the correct ways to launch an application from a
    208      * notification.
    209      */
    210     public PendingIntent contentIntent;
    211 
    212     /**
    213      * The intent to execute when the notification is explicitly dismissed by the user, either with
    214      * the "Clear All" button or by swiping it away individually.
    215      *
    216      * This probably shouldn't be launching an activity since several of those will be sent
    217      * at the same time.
    218      */
    219     public PendingIntent deleteIntent;
    220 
    221     /**
    222      * An intent to launch instead of posting the notification to the status bar.
    223      *
    224      * <p>
    225      * The system UI may choose to display a heads-up notification, instead of
    226      * launching this intent, while the user is using the device.
    227      * </p>
    228      *
    229      * @see Notification.Builder#setFullScreenIntent
    230      */
    231     public PendingIntent fullScreenIntent;
    232 
    233     /**
    234      * Text that summarizes this notification for accessibility services.
    235      *
    236      * As of the L release, this text is no longer shown on screen, but it is still useful to
    237      * accessibility services (where it serves as an audible announcement of the notification's
    238      * appearance).
    239      *
    240      * @see #tickerView
    241      */
    242     public CharSequence tickerText;
    243 
    244     /**
    245      * Formerly, a view showing the {@link #tickerText}.
    246      *
    247      * No longer displayed in the status bar as of API 21.
    248      */
    249     @Deprecated
    250     public RemoteViews tickerView;
    251 
    252     /**
    253      * The view that will represent this notification in the expanded status bar.
    254      */
    255     public RemoteViews contentView;
    256 
    257     /**
    258      * A large-format version of {@link #contentView}, giving the Notification an
    259      * opportunity to show more detail. The system UI may choose to show this
    260      * instead of the normal content view at its discretion.
    261      */
    262     public RemoteViews bigContentView;
    263 
    264 
    265     /**
    266      * A medium-format version of {@link #contentView}, providing the Notification an
    267      * opportunity to add action buttons to contentView. At its discretion, the system UI may
    268      * choose to show this as a heads-up notification, which will pop up so the user can see
    269      * it without leaving their current activity.
    270      */
    271     public RemoteViews headsUpContentView;
    272 
    273     /**
    274      * A large bitmap to be shown in the notification content area.
    275      *
    276      * @deprecated Use {@link Builder#setLargeIcon(Icon)} instead.
    277      */
    278     @Deprecated
    279     public Bitmap largeIcon;
    280 
    281     /**
    282      * The sound to play.
    283      *
    284      * <p>
    285      * A notification that is noisy is more likely to be presented as a heads-up notification.
    286      * </p>
    287      *
    288      * <p>
    289      * To play the default notification sound, see {@link #defaults}.
    290      * </p>
    291      */
    292     public Uri sound;
    293 
    294     /**
    295      * Use this constant as the value for audioStreamType to request that
    296      * the default stream type for notifications be used.  Currently the
    297      * default stream type is {@link AudioManager#STREAM_NOTIFICATION}.
    298      *
    299      * @deprecated Use {@link #audioAttributes} instead.
    300      */
    301     @Deprecated
    302     public static final int STREAM_DEFAULT = -1;
    303 
    304     /**
    305      * The audio stream type to use when playing the sound.
    306      * Should be one of the STREAM_ constants from
    307      * {@link android.media.AudioManager}.
    308      *
    309      * @deprecated Use {@link #audioAttributes} instead.
    310      */
    311     @Deprecated
    312     public int audioStreamType = STREAM_DEFAULT;
    313 
    314     /**
    315      * The default value of {@link #audioAttributes}.
    316      */
    317     public static final AudioAttributes AUDIO_ATTRIBUTES_DEFAULT = new AudioAttributes.Builder()
    318             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
    319             .setUsage(AudioAttributes.USAGE_NOTIFICATION)
    320             .build();
    321 
    322     /**
    323      * The {@link AudioAttributes audio attributes} to use when playing the sound.
    324      */
    325     public AudioAttributes audioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
    326 
    327     /**
    328      * The pattern with which to vibrate.
    329      *
    330      * <p>
    331      * To vibrate the default pattern, see {@link #defaults}.
    332      * </p>
    333      *
    334      * <p>
    335      * A notification that vibrates is more likely to be presented as a heads-up notification.
    336      * </p>
    337      *
    338      * @see android.os.Vibrator#vibrate(long[],int)
    339      */
    340     public long[] vibrate;
    341 
    342     /**
    343      * The color of the led.  The hardware will do its best approximation.
    344      *
    345      * @see #FLAG_SHOW_LIGHTS
    346      * @see #flags
    347      */
    348     @ColorInt
    349     public int ledARGB;
    350 
    351     /**
    352      * The number of milliseconds for the LED to be on while it's flashing.
    353      * The hardware will do its best approximation.
    354      *
    355      * @see #FLAG_SHOW_LIGHTS
    356      * @see #flags
    357      */
    358     public int ledOnMS;
    359 
    360     /**
    361      * The number of milliseconds for the LED to be off while it's flashing.
    362      * The hardware will do its best approximation.
    363      *
    364      * @see #FLAG_SHOW_LIGHTS
    365      * @see #flags
    366      */
    367     public int ledOffMS;
    368 
    369     /**
    370      * Specifies which values should be taken from the defaults.
    371      * <p>
    372      * To set, OR the desired from {@link #DEFAULT_SOUND},
    373      * {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. For all default
    374      * values, use {@link #DEFAULT_ALL}.
    375      * </p>
    376      */
    377     public int defaults;
    378 
    379     /**
    380      * Bit to be bitwise-ored into the {@link #flags} field that should be
    381      * set if you want the LED on for this notification.
    382      * <ul>
    383      * <li>To turn the LED off, pass 0 in the alpha channel for colorARGB
    384      *      or 0 for both ledOnMS and ledOffMS.</li>
    385      * <li>To turn the LED on, pass 1 for ledOnMS and 0 for ledOffMS.</li>
    386      * <li>To flash the LED, pass the number of milliseconds that it should
    387      *      be on and off to ledOnMS and ledOffMS.</li>
    388      * </ul>
    389      * <p>
    390      * Since hardware varies, you are not guaranteed that any of the values
    391      * you pass are honored exactly.  Use the system defaults (TODO) if possible
    392      * because they will be set to values that work on any given hardware.
    393      * <p>
    394      * The alpha channel must be set for forward compatibility.
    395      *
    396      */
    397     public static final int FLAG_SHOW_LIGHTS        = 0x00000001;
    398 
    399     /**
    400      * Bit to be bitwise-ored into the {@link #flags} field that should be
    401      * set if this notification is in reference to something that is ongoing,
    402      * like a phone call.  It should not be set if this notification is in
    403      * reference to something that happened at a particular point in time,
    404      * like a missed phone call.
    405      */
    406     public static final int FLAG_ONGOING_EVENT      = 0x00000002;
    407 
    408     /**
    409      * Bit to be bitwise-ored into the {@link #flags} field that if set,
    410      * the audio will be repeated until the notification is
    411      * cancelled or the notification window is opened.
    412      */
    413     public static final int FLAG_INSISTENT          = 0x00000004;
    414 
    415     /**
    416      * Bit to be bitwise-ored into the {@link #flags} field that should be
    417      * set if you would only like the sound, vibrate and ticker to be played
    418      * if the notification was not already showing.
    419      */
    420     public static final int FLAG_ONLY_ALERT_ONCE    = 0x00000008;
    421 
    422     /**
    423      * Bit to be bitwise-ored into the {@link #flags} field that should be
    424      * set if the notification should be canceled when it is clicked by the
    425      * user.
    426      */
    427     public static final int FLAG_AUTO_CANCEL        = 0x00000010;
    428 
    429     /**
    430      * Bit to be bitwise-ored into the {@link #flags} field that should be
    431      * set if the notification should not be canceled when the user clicks
    432      * the Clear all button.
    433      */
    434     public static final int FLAG_NO_CLEAR           = 0x00000020;
    435 
    436     /**
    437      * Bit to be bitwise-ored into the {@link #flags} field that should be
    438      * set if this notification represents a currently running service.  This
    439      * will normally be set for you by {@link Service#startForeground}.
    440      */
    441     public static final int FLAG_FOREGROUND_SERVICE = 0x00000040;
    442 
    443     /**
    444      * Obsolete flag indicating high-priority notifications; use the priority field instead.
    445      *
    446      * @deprecated Use {@link #priority} with a positive value.
    447      */
    448     public static final int FLAG_HIGH_PRIORITY      = 0x00000080;
    449 
    450     /**
    451      * Bit to be bitswise-ored into the {@link #flags} field that should be
    452      * set if this notification is relevant to the current device only
    453      * and it is not recommended that it bridge to other devices.
    454      */
    455     public static final int FLAG_LOCAL_ONLY         = 0x00000100;
    456 
    457     /**
    458      * Bit to be bitswise-ored into the {@link #flags} field that should be
    459      * set if this notification is the group summary for a group of notifications.
    460      * Grouped notifications may display in a cluster or stack on devices which
    461      * support such rendering. Requires a group key also be set using {@link Builder#setGroup}.
    462      */
    463     public static final int FLAG_GROUP_SUMMARY      = 0x00000200;
    464 
    465     public int flags;
    466 
    467     /** @hide */
    468     @IntDef({PRIORITY_DEFAULT,PRIORITY_LOW,PRIORITY_MIN,PRIORITY_HIGH,PRIORITY_MAX})
    469     @Retention(RetentionPolicy.SOURCE)
    470     public @interface Priority {}
    471 
    472     /**
    473      * Default notification {@link #priority}. If your application does not prioritize its own
    474      * notifications, use this value for all notifications.
    475      */
    476     public static final int PRIORITY_DEFAULT = 0;
    477 
    478     /**
    479      * Lower {@link #priority}, for items that are less important. The UI may choose to show these
    480      * items smaller, or at a different position in the list, compared with your app's
    481      * {@link #PRIORITY_DEFAULT} items.
    482      */
    483     public static final int PRIORITY_LOW = -1;
    484 
    485     /**
    486      * Lowest {@link #priority}; these items might not be shown to the user except under special
    487      * circumstances, such as detailed notification logs.
    488      */
    489     public static final int PRIORITY_MIN = -2;
    490 
    491     /**
    492      * Higher {@link #priority}, for more important notifications or alerts. The UI may choose to
    493      * show these items larger, or at a different position in notification lists, compared with
    494      * your app's {@link #PRIORITY_DEFAULT} items.
    495      */
    496     public static final int PRIORITY_HIGH = 1;
    497 
    498     /**
    499      * Highest {@link #priority}, for your application's most important items that require the
    500      * user's prompt attention or input.
    501      */
    502     public static final int PRIORITY_MAX = 2;
    503 
    504     /**
    505      * Relative priority for this notification.
    506      *
    507      * Priority is an indication of how much of the user's valuable attention should be consumed by
    508      * this notification. Low-priority notifications may be hidden from the user in certain
    509      * situations, while the user might be interrupted for a higher-priority notification. The
    510      * system will make a determination about how to interpret this priority when presenting
    511      * the notification.
    512      *
    513      * <p>
    514      * A notification that is at least {@link #PRIORITY_HIGH} is more likely to be presented
    515      * as a heads-up notification.
    516      * </p>
    517      *
    518      */
    519     @Priority
    520     public int priority;
    521 
    522     /**
    523      * Accent color (an ARGB integer like the constants in {@link android.graphics.Color})
    524      * to be applied by the standard Style templates when presenting this notification.
    525      *
    526      * The current template design constructs a colorful header image by overlaying the
    527      * {@link #icon} image (stenciled in white) atop a field of this color. Alpha components are
    528      * ignored.
    529      */
    530     @ColorInt
    531     public int color = COLOR_DEFAULT;
    532 
    533     /**
    534      * Special value of {@link #color} telling the system not to decorate this notification with
    535      * any special color but instead use default colors when presenting this notification.
    536      */
    537     @ColorInt
    538     public static final int COLOR_DEFAULT = 0; // AKA Color.TRANSPARENT
    539 
    540     /**
    541      * Sphere of visibility of this notification, which affects how and when the SystemUI reveals
    542      * the notification's presence and contents in untrusted situations (namely, on the secure
    543      * lockscreen).
    544      *
    545      * The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always
    546      * done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are
    547      * shown in all situations, but the contents are only available if the device is unlocked for
    548      * the appropriate user.
    549      *
    550      * A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification
    551      * can be read even in an "insecure" context (that is, above a secure lockscreen).
    552      * To modify the public version of this notificationfor example, to redact some portionssee
    553      * {@link Builder#setPublicVersion(Notification)}.
    554      *
    555      * Finally, a notification can be made {@link #VISIBILITY_SECRET}, which will suppress its icon
    556      * and ticker until the user has bypassed the lockscreen.
    557      */
    558     public int visibility;
    559 
    560     /**
    561      * Notification visibility: Show this notification in its entirety on all lockscreens.
    562      *
    563      * {@see #visibility}
    564      */
    565     public static final int VISIBILITY_PUBLIC = 1;
    566 
    567     /**
    568      * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or
    569      * private information on secure lockscreens.
    570      *
    571      * {@see #visibility}
    572      */
    573     public static final int VISIBILITY_PRIVATE = 0;
    574 
    575     /**
    576      * Notification visibility: Do not reveal any part of this notification on a secure lockscreen.
    577      *
    578      * {@see #visibility}
    579      */
    580     public static final int VISIBILITY_SECRET = -1;
    581 
    582     /**
    583      * Notification category: incoming call (voice or video) or similar synchronous communication request.
    584      */
    585     public static final String CATEGORY_CALL = "call";
    586 
    587     /**
    588      * Notification category: incoming direct message (SMS, instant message, etc.).
    589      */
    590     public static final String CATEGORY_MESSAGE = "msg";
    591 
    592     /**
    593      * Notification category: asynchronous bulk message (email).
    594      */
    595     public static final String CATEGORY_EMAIL = "email";
    596 
    597     /**
    598      * Notification category: calendar event.
    599      */
    600     public static final String CATEGORY_EVENT = "event";
    601 
    602     /**
    603      * Notification category: promotion or advertisement.
    604      */
    605     public static final String CATEGORY_PROMO = "promo";
    606 
    607     /**
    608      * Notification category: alarm or timer.
    609      */
    610     public static final String CATEGORY_ALARM = "alarm";
    611 
    612     /**
    613      * Notification category: progress of a long-running background operation.
    614      */
    615     public static final String CATEGORY_PROGRESS = "progress";
    616 
    617     /**
    618      * Notification category: social network or sharing update.
    619      */
    620     public static final String CATEGORY_SOCIAL = "social";
    621 
    622     /**
    623      * Notification category: error in background operation or authentication status.
    624      */
    625     public static final String CATEGORY_ERROR = "err";
    626 
    627     /**
    628      * Notification category: media transport control for playback.
    629      */
    630     public static final String CATEGORY_TRANSPORT = "transport";
    631 
    632     /**
    633      * Notification category: system or device status update.  Reserved for system use.
    634      */
    635     public static final String CATEGORY_SYSTEM = "sys";
    636 
    637     /**
    638      * Notification category: indication of running background service.
    639      */
    640     public static final String CATEGORY_SERVICE = "service";
    641 
    642     /**
    643      * Notification category: a specific, timely recommendation for a single thing.
    644      * For example, a news app might want to recommend a news story it believes the user will
    645      * want to read next.
    646      */
    647     public static final String CATEGORY_RECOMMENDATION = "recommendation";
    648 
    649     /**
    650      * Notification category: ongoing information about device or contextual status.
    651      */
    652     public static final String CATEGORY_STATUS = "status";
    653 
    654     /**
    655      * Notification category: user-scheduled reminder.
    656      */
    657     public static final String CATEGORY_REMINDER = "reminder";
    658 
    659     /**
    660      * One of the predefined notification categories (see the <code>CATEGORY_*</code> constants)
    661      * that best describes this Notification.  May be used by the system for ranking and filtering.
    662      */
    663     public String category;
    664 
    665     private String mGroupKey;
    666 
    667     /**
    668      * Get the key used to group this notification into a cluster or stack
    669      * with other notifications on devices which support such rendering.
    670      */
    671     public String getGroup() {
    672         return mGroupKey;
    673     }
    674 
    675     private String mSortKey;
    676 
    677     /**
    678      * Get a sort key that orders this notification among other notifications from the
    679      * same package. This can be useful if an external sort was already applied and an app
    680      * would like to preserve this. Notifications will be sorted lexicographically using this
    681      * value, although providing different priorities in addition to providing sort key may
    682      * cause this value to be ignored.
    683      *
    684      * <p>This sort key can also be used to order members of a notification group. See
    685      * {@link Builder#setGroup}.
    686      *
    687      * @see String#compareTo(String)
    688      */
    689     public String getSortKey() {
    690         return mSortKey;
    691     }
    692 
    693     /**
    694      * Additional semantic data to be carried around with this Notification.
    695      * <p>
    696      * The extras keys defined here are intended to capture the original inputs to {@link Builder}
    697      * APIs, and are intended to be used by
    698      * {@link android.service.notification.NotificationListenerService} implementations to extract
    699      * detailed information from notification objects.
    700      */
    701     public Bundle extras = new Bundle();
    702 
    703     /**
    704      * {@link #extras} key: this is the title of the notification,
    705      * as supplied to {@link Builder#setContentTitle(CharSequence)}.
    706      */
    707     public static final String EXTRA_TITLE = "android.title";
    708 
    709     /**
    710      * {@link #extras} key: this is the title of the notification when shown in expanded form,
    711      * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}.
    712      */
    713     public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big";
    714 
    715     /**
    716      * {@link #extras} key: this is the main text payload, as supplied to
    717      * {@link Builder#setContentText(CharSequence)}.
    718      */
    719     public static final String EXTRA_TEXT = "android.text";
    720 
    721     /**
    722      * {@link #extras} key: this is a third line of text, as supplied to
    723      * {@link Builder#setSubText(CharSequence)}.
    724      */
    725     public static final String EXTRA_SUB_TEXT = "android.subText";
    726 
    727     /**
    728      * {@link #extras} key: this is a small piece of additional text as supplied to
    729      * {@link Builder#setContentInfo(CharSequence)}.
    730      */
    731     public static final String EXTRA_INFO_TEXT = "android.infoText";
    732 
    733     /**
    734      * {@link #extras} key: this is a line of summary information intended to be shown
    735      * alongside expanded notifications, as supplied to (e.g.)
    736      * {@link BigTextStyle#setSummaryText(CharSequence)}.
    737      */
    738     public static final String EXTRA_SUMMARY_TEXT = "android.summaryText";
    739 
    740     /**
    741      * {@link #extras} key: this is the longer text shown in the big form of a
    742      * {@link BigTextStyle} notification, as supplied to
    743      * {@link BigTextStyle#bigText(CharSequence)}.
    744      */
    745     public static final String EXTRA_BIG_TEXT = "android.bigText";
    746 
    747     /**
    748      * {@link #extras} key: this is the resource ID of the notification's main small icon, as
    749      * supplied to {@link Builder#setSmallIcon(int)}.
    750      */
    751     public static final String EXTRA_SMALL_ICON = "android.icon";
    752 
    753     /**
    754      * {@link #extras} key: this is a bitmap to be used instead of the small icon when showing the
    755      * notification payload, as
    756      * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}.
    757      */
    758     public static final String EXTRA_LARGE_ICON = "android.largeIcon";
    759 
    760     /**
    761      * {@link #extras} key: this is a bitmap to be used instead of the one from
    762      * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is
    763      * shown in its expanded form, as supplied to
    764      * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}.
    765      */
    766     public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big";
    767 
    768     /**
    769      * {@link #extras} key: this is the progress value supplied to
    770      * {@link Builder#setProgress(int, int, boolean)}.
    771      */
    772     public static final String EXTRA_PROGRESS = "android.progress";
    773 
    774     /**
    775      * {@link #extras} key: this is the maximum value supplied to
    776      * {@link Builder#setProgress(int, int, boolean)}.
    777      */
    778     public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
    779 
    780     /**
    781      * {@link #extras} key: whether the progress bar is indeterminate, supplied to
    782      * {@link Builder#setProgress(int, int, boolean)}.
    783      */
    784     public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
    785 
    786     /**
    787      * {@link #extras} key: whether {@link #when} should be shown as a count-up timer (specifically
    788      * a {@link android.widget.Chronometer}) instead of a timestamp, as supplied to
    789      * {@link Builder#setUsesChronometer(boolean)}.
    790      */
    791     public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
    792 
    793     /**
    794      * {@link #extras} key: whether {@link #when} should be shown,
    795      * as supplied to {@link Builder#setShowWhen(boolean)}.
    796      */
    797     public static final String EXTRA_SHOW_WHEN = "android.showWhen";
    798 
    799     /**
    800      * {@link #extras} key: this is a bitmap to be shown in {@link BigPictureStyle} expanded
    801      * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}.
    802      */
    803     public static final String EXTRA_PICTURE = "android.picture";
    804 
    805     /**
    806      * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded
    807      * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}.
    808      */
    809     public static final String EXTRA_TEXT_LINES = "android.textLines";
    810 
    811     /**
    812      * {@link #extras} key: A string representing the name of the specific
    813      * {@link android.app.Notification.Style} used to create this notification.
    814      */
    815     public static final String EXTRA_TEMPLATE = "android.template";
    816 
    817     /**
    818      * {@link #extras} key: A String array containing the people that this notification relates to,
    819      * each of which was supplied to {@link Builder#addPerson(String)}.
    820      */
    821     public static final String EXTRA_PEOPLE = "android.people";
    822 
    823     /**
    824      * {@link #extras} key: used to provide hints about the appropriateness of
    825      * displaying this notification as a heads-up notification.
    826      * @hide
    827      */
    828     public static final String EXTRA_AS_HEADS_UP = "headsup";
    829 
    830     /**
    831      * Allow certain system-generated notifications to appear before the device is provisioned.
    832      * Only available to notifications coming from the android package.
    833      * @hide
    834      */
    835     public static final String EXTRA_ALLOW_DURING_SETUP = "android.allowDuringSetup";
    836 
    837     /**
    838      * {@link #extras} key: A
    839      * {@link android.content.ContentUris content URI} pointing to an image that can be displayed
    840      * in the background when the notification is selected. The URI must point to an image stream
    841      * suitable for passing into
    842      * {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream)
    843      * BitmapFactory.decodeStream}; all other content types will be ignored. The content provider
    844      * URI used for this purpose must require no permissions to read the image data.
    845      */
    846     public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
    847 
    848     /**
    849      * {@link #extras} key: A
    850      * {@link android.media.session.MediaSession.Token} associated with a
    851      * {@link android.app.Notification.MediaStyle} notification.
    852      */
    853     public static final String EXTRA_MEDIA_SESSION = "android.mediaSession";
    854 
    855     /**
    856      * {@link #extras} key: the indices of actions to be shown in the compact view,
    857      * as supplied to (e.g.) {@link MediaStyle#setShowActionsInCompactView(int...)}.
    858      */
    859     public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions";
    860 
    861     /**
    862      * {@link #extras} key: the user that built the notification.
    863      *
    864      * @hide
    865      */
    866     public static final String EXTRA_ORIGINATING_USERID = "android.originatingUserId";
    867 
    868     /**
    869      * Value for {@link #EXTRA_AS_HEADS_UP} that indicates this notification should not be
    870      * displayed in the heads up space.
    871      *
    872      * <p>
    873      * If this notification has a {@link #fullScreenIntent}, then it will always launch the
    874      * full-screen intent when posted.
    875      * </p>
    876      * @hide
    877      */
    878     public static final int HEADS_UP_NEVER = 0;
    879 
    880     /**
    881      * Default value for {@link #EXTRA_AS_HEADS_UP} that indicates this notification may be
    882      * displayed as a heads up.
    883      * @hide
    884      */
    885     public static final int HEADS_UP_ALLOWED = 1;
    886 
    887     /**
    888      * Value for {@link #EXTRA_AS_HEADS_UP} that indicates this notification is a
    889      * good candidate for display as a heads up.
    890      * @hide
    891      */
    892     public static final int HEADS_UP_REQUESTED = 2;
    893 
    894     private Icon mSmallIcon;
    895     private Icon mLargeIcon;
    896 
    897     /**
    898      * Structure to encapsulate a named action that can be shown as part of this notification.
    899      * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is
    900      * selected by the user.
    901      * <p>
    902      * Apps should use {@link Notification.Builder#addAction(int, CharSequence, PendingIntent)}
    903      * or {@link Notification.Builder#addAction(Notification.Action)}
    904      * to attach actions.
    905      */
    906     public static class Action implements Parcelable {
    907         private final Bundle mExtras;
    908         private Icon mIcon;
    909         private final RemoteInput[] mRemoteInputs;
    910 
    911         /**
    912          * Small icon representing the action.
    913          *
    914          * @deprecated Use {@link Action#getIcon()} instead.
    915          */
    916         @Deprecated
    917         public int icon;
    918 
    919         /**
    920          * Title of the action.
    921          */
    922         public CharSequence title;
    923 
    924         /**
    925          * Intent to send when the user invokes this action. May be null, in which case the action
    926          * may be rendered in a disabled presentation by the system UI.
    927          */
    928         public PendingIntent actionIntent;
    929 
    930         private Action(Parcel in) {
    931             if (in.readInt() != 0) {
    932                 mIcon = Icon.CREATOR.createFromParcel(in);
    933                 if (mIcon.getType() == Icon.TYPE_RESOURCE) {
    934                     icon = mIcon.getResId();
    935                 }
    936             }
    937             title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
    938             if (in.readInt() == 1) {
    939                 actionIntent = PendingIntent.CREATOR.createFromParcel(in);
    940             }
    941             mExtras = in.readBundle();
    942             mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR);
    943         }
    944 
    945         /**
    946          * @deprecated Use {@link android.app.Notification.Action.Builder}.
    947          */
    948         @Deprecated
    949         public Action(int icon, CharSequence title, PendingIntent intent) {
    950             this(Icon.createWithResource("", icon), title, intent, new Bundle(), null);
    951         }
    952 
    953         private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
    954                 RemoteInput[] remoteInputs) {
    955             this.mIcon = icon;
    956             this.title = title;
    957             this.actionIntent = intent;
    958             this.mExtras = extras != null ? extras : new Bundle();
    959             this.mRemoteInputs = remoteInputs;
    960         }
    961 
    962         /**
    963          * Return an icon representing the action.
    964          */
    965         public Icon getIcon() {
    966             if (mIcon == null && icon != 0) {
    967                 // you snuck an icon in here without using the builder; let's try to keep it
    968                 mIcon = Icon.createWithResource("", icon);
    969             }
    970             return mIcon;
    971         }
    972 
    973         /**
    974          * Get additional metadata carried around with this Action.
    975          */
    976         public Bundle getExtras() {
    977             return mExtras;
    978         }
    979 
    980         /**
    981          * Get the list of inputs to be collected from the user when this action is sent.
    982          * May return null if no remote inputs were added.
    983          */
    984         public RemoteInput[] getRemoteInputs() {
    985             return mRemoteInputs;
    986         }
    987 
    988         /**
    989          * Builder class for {@link Action} objects.
    990          */
    991         public static final class Builder {
    992             private final Icon mIcon;
    993             private final CharSequence mTitle;
    994             private final PendingIntent mIntent;
    995             private final Bundle mExtras;
    996             private ArrayList<RemoteInput> mRemoteInputs;
    997 
    998             /**
    999              * Construct a new builder for {@link Action} object.
   1000              * @param icon icon to show for this action
   1001              * @param title the title of the action
   1002              * @param intent the {@link PendingIntent} to fire when users trigger this action
   1003              */
   1004             @Deprecated
   1005             public Builder(int icon, CharSequence title, PendingIntent intent) {
   1006                 this(Icon.createWithResource("", icon), title, intent, new Bundle(), null);
   1007             }
   1008 
   1009             /**
   1010              * Construct a new builder for {@link Action} object.
   1011              * @param icon icon to show for this action
   1012              * @param title the title of the action
   1013              * @param intent the {@link PendingIntent} to fire when users trigger this action
   1014              */
   1015             public Builder(Icon icon, CharSequence title, PendingIntent intent) {
   1016                 this(icon, title, intent, new Bundle(), null);
   1017             }
   1018 
   1019             /**
   1020              * Construct a new builder for {@link Action} object using the fields from an
   1021              * {@link Action}.
   1022              * @param action the action to read fields from.
   1023              */
   1024             public Builder(Action action) {
   1025                 this(action.getIcon(), action.title, action.actionIntent, new Bundle(action.mExtras),
   1026                         action.getRemoteInputs());
   1027             }
   1028 
   1029             private Builder(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
   1030                     RemoteInput[] remoteInputs) {
   1031                 mIcon = icon;
   1032                 mTitle = title;
   1033                 mIntent = intent;
   1034                 mExtras = extras;
   1035                 if (remoteInputs != null) {
   1036                     mRemoteInputs = new ArrayList<RemoteInput>(remoteInputs.length);
   1037                     Collections.addAll(mRemoteInputs, remoteInputs);
   1038                 }
   1039             }
   1040 
   1041             /**
   1042              * Merge additional metadata into this builder.
   1043              *
   1044              * <p>Values within the Bundle will replace existing extras values in this Builder.
   1045              *
   1046              * @see Notification.Action#extras
   1047              */
   1048             public Builder addExtras(Bundle extras) {
   1049                 if (extras != null) {
   1050                     mExtras.putAll(extras);
   1051                 }
   1052                 return this;
   1053             }
   1054 
   1055             /**
   1056              * Get the metadata Bundle used by this Builder.
   1057              *
   1058              * <p>The returned Bundle is shared with this Builder.
   1059              */
   1060             public Bundle getExtras() {
   1061                 return mExtras;
   1062             }
   1063 
   1064             /**
   1065              * Add an input to be collected from the user when this action is sent.
   1066              * Response values can be retrieved from the fired intent by using the
   1067              * {@link RemoteInput#getResultsFromIntent} function.
   1068              * @param remoteInput a {@link RemoteInput} to add to the action
   1069              * @return this object for method chaining
   1070              */
   1071             public Builder addRemoteInput(RemoteInput remoteInput) {
   1072                 if (mRemoteInputs == null) {
   1073                     mRemoteInputs = new ArrayList<RemoteInput>();
   1074                 }
   1075                 mRemoteInputs.add(remoteInput);
   1076                 return this;
   1077             }
   1078 
   1079             /**
   1080              * Apply an extender to this action builder. Extenders may be used to add
   1081              * metadata or change options on this builder.
   1082              */
   1083             public Builder extend(Extender extender) {
   1084                 extender.extend(this);
   1085                 return this;
   1086             }
   1087 
   1088             /**
   1089              * Combine all of the options that have been set and return a new {@link Action}
   1090              * object.
   1091              * @return the built action
   1092              */
   1093             public Action build() {
   1094                 RemoteInput[] remoteInputs = mRemoteInputs != null
   1095                         ? mRemoteInputs.toArray(new RemoteInput[mRemoteInputs.size()]) : null;
   1096                 return new Action(mIcon, mTitle, mIntent, mExtras, remoteInputs);
   1097             }
   1098         }
   1099 
   1100         @Override
   1101         public Action clone() {
   1102             return new Action(
   1103                     getIcon(),
   1104                     title,
   1105                     actionIntent, // safe to alias
   1106                     new Bundle(mExtras),
   1107                     getRemoteInputs());
   1108         }
   1109         @Override
   1110         public int describeContents() {
   1111             return 0;
   1112         }
   1113         @Override
   1114         public void writeToParcel(Parcel out, int flags) {
   1115             final Icon ic = getIcon();
   1116             if (ic != null) {
   1117                 out.writeInt(1);
   1118                 ic.writeToParcel(out, 0);
   1119             } else {
   1120                 out.writeInt(0);
   1121             }
   1122             TextUtils.writeToParcel(title, out, flags);
   1123             if (actionIntent != null) {
   1124                 out.writeInt(1);
   1125                 actionIntent.writeToParcel(out, flags);
   1126             } else {
   1127                 out.writeInt(0);
   1128             }
   1129             out.writeBundle(mExtras);
   1130             out.writeTypedArray(mRemoteInputs, flags);
   1131         }
   1132         public static final Parcelable.Creator<Action> CREATOR =
   1133                 new Parcelable.Creator<Action>() {
   1134             public Action createFromParcel(Parcel in) {
   1135                 return new Action(in);
   1136             }
   1137             public Action[] newArray(int size) {
   1138                 return new Action[size];
   1139             }
   1140         };
   1141 
   1142         /**
   1143          * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
   1144          * metadata or change options on an action builder.
   1145          */
   1146         public interface Extender {
   1147             /**
   1148              * Apply this extender to a notification action builder.
   1149              * @param builder the builder to be modified.
   1150              * @return the build object for chaining.
   1151              */
   1152             public Builder extend(Builder builder);
   1153         }
   1154 
   1155         /**
   1156          * Wearable extender for notification actions. To add extensions to an action,
   1157          * create a new {@link android.app.Notification.Action.WearableExtender} object using
   1158          * the {@code WearableExtender()} constructor and apply it to a
   1159          * {@link android.app.Notification.Action.Builder} using
   1160          * {@link android.app.Notification.Action.Builder#extend}.
   1161          *
   1162          * <pre class="prettyprint">
   1163          * Notification.Action action = new Notification.Action.Builder(
   1164          *         R.drawable.archive_all, "Archive all", actionIntent)
   1165          *         .extend(new Notification.Action.WearableExtender()
   1166          *                 .setAvailableOffline(false))
   1167          *         .build();</pre>
   1168          */
   1169         public static final class WearableExtender implements Extender {
   1170             /** Notification action extra which contains wearable extensions */
   1171             private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
   1172 
   1173             // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
   1174             private static final String KEY_FLAGS = "flags";
   1175             private static final String KEY_IN_PROGRESS_LABEL = "inProgressLabel";
   1176             private static final String KEY_CONFIRM_LABEL = "confirmLabel";
   1177             private static final String KEY_CANCEL_LABEL = "cancelLabel";
   1178 
   1179             // Flags bitwise-ored to mFlags
   1180             private static final int FLAG_AVAILABLE_OFFLINE = 0x1;
   1181 
   1182             // Default value for flags integer
   1183             private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE;
   1184 
   1185             private int mFlags = DEFAULT_FLAGS;
   1186 
   1187             private CharSequence mInProgressLabel;
   1188             private CharSequence mConfirmLabel;
   1189             private CharSequence mCancelLabel;
   1190 
   1191             /**
   1192              * Create a {@link android.app.Notification.Action.WearableExtender} with default
   1193              * options.
   1194              */
   1195             public WearableExtender() {
   1196             }
   1197 
   1198             /**
   1199              * Create a {@link android.app.Notification.Action.WearableExtender} by reading
   1200              * wearable options present in an existing notification action.
   1201              * @param action the notification action to inspect.
   1202              */
   1203             public WearableExtender(Action action) {
   1204                 Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS);
   1205                 if (wearableBundle != null) {
   1206                     mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
   1207                     mInProgressLabel = wearableBundle.getCharSequence(KEY_IN_PROGRESS_LABEL);
   1208                     mConfirmLabel = wearableBundle.getCharSequence(KEY_CONFIRM_LABEL);
   1209                     mCancelLabel = wearableBundle.getCharSequence(KEY_CANCEL_LABEL);
   1210                 }
   1211             }
   1212 
   1213             /**
   1214              * Apply wearable extensions to a notification action that is being built. This is
   1215              * typically called by the {@link android.app.Notification.Action.Builder#extend}
   1216              * method of {@link android.app.Notification.Action.Builder}.
   1217              */
   1218             @Override
   1219             public Action.Builder extend(Action.Builder builder) {
   1220                 Bundle wearableBundle = new Bundle();
   1221 
   1222                 if (mFlags != DEFAULT_FLAGS) {
   1223                     wearableBundle.putInt(KEY_FLAGS, mFlags);
   1224                 }
   1225                 if (mInProgressLabel != null) {
   1226                     wearableBundle.putCharSequence(KEY_IN_PROGRESS_LABEL, mInProgressLabel);
   1227                 }
   1228                 if (mConfirmLabel != null) {
   1229                     wearableBundle.putCharSequence(KEY_CONFIRM_LABEL, mConfirmLabel);
   1230                 }
   1231                 if (mCancelLabel != null) {
   1232                     wearableBundle.putCharSequence(KEY_CANCEL_LABEL, mCancelLabel);
   1233                 }
   1234 
   1235                 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
   1236                 return builder;
   1237             }
   1238 
   1239             @Override
   1240             public WearableExtender clone() {
   1241                 WearableExtender that = new WearableExtender();
   1242                 that.mFlags = this.mFlags;
   1243                 that.mInProgressLabel = this.mInProgressLabel;
   1244                 that.mConfirmLabel = this.mConfirmLabel;
   1245                 that.mCancelLabel = this.mCancelLabel;
   1246                 return that;
   1247             }
   1248 
   1249             /**
   1250              * Set whether this action is available when the wearable device is not connected to
   1251              * a companion device. The user can still trigger this action when the wearable device is
   1252              * offline, but a visual hint will indicate that the action may not be available.
   1253              * Defaults to true.
   1254              */
   1255             public WearableExtender setAvailableOffline(boolean availableOffline) {
   1256                 setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline);
   1257                 return this;
   1258             }
   1259 
   1260             /**
   1261              * Get whether this action is available when the wearable device is not connected to
   1262              * a companion device. The user can still trigger this action when the wearable device is
   1263              * offline, but a visual hint will indicate that the action may not be available.
   1264              * Defaults to true.
   1265              */
   1266             public boolean isAvailableOffline() {
   1267                 return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0;
   1268             }
   1269 
   1270             private void setFlag(int mask, boolean value) {
   1271                 if (value) {
   1272                     mFlags |= mask;
   1273                 } else {
   1274                     mFlags &= ~mask;
   1275                 }
   1276             }
   1277 
   1278             /**
   1279              * Set a label to display while the wearable is preparing to automatically execute the
   1280              * action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
   1281              *
   1282              * @param label the label to display while the action is being prepared to execute
   1283              * @return this object for method chaining
   1284              */
   1285             public WearableExtender setInProgressLabel(CharSequence label) {
   1286                 mInProgressLabel = label;
   1287                 return this;
   1288             }
   1289 
   1290             /**
   1291              * Get the label to display while the wearable is preparing to automatically execute
   1292              * the action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
   1293              *
   1294              * @return the label to display while the action is being prepared to execute
   1295              */
   1296             public CharSequence getInProgressLabel() {
   1297                 return mInProgressLabel;
   1298             }
   1299 
   1300             /**
   1301              * Set a label to display to confirm that the action should be executed.
   1302              * This is usually an imperative verb like "Send".
   1303              *
   1304              * @param label the label to confirm the action should be executed
   1305              * @return this object for method chaining
   1306              */
   1307             public WearableExtender setConfirmLabel(CharSequence label) {
   1308                 mConfirmLabel = label;
   1309                 return this;
   1310             }
   1311 
   1312             /**
   1313              * Get the label to display to confirm that the action should be executed.
   1314              * This is usually an imperative verb like "Send".
   1315              *
   1316              * @return the label to confirm the action should be executed
   1317              */
   1318             public CharSequence getConfirmLabel() {
   1319                 return mConfirmLabel;
   1320             }
   1321 
   1322             /**
   1323              * Set a label to display to cancel the action.
   1324              * This is usually an imperative verb, like "Cancel".
   1325              *
   1326              * @param label the label to display to cancel the action
   1327              * @return this object for method chaining
   1328              */
   1329             public WearableExtender setCancelLabel(CharSequence label) {
   1330                 mCancelLabel = label;
   1331                 return this;
   1332             }
   1333 
   1334             /**
   1335              * Get the label to display to cancel the action.
   1336              * This is usually an imperative verb like "Cancel".
   1337              *
   1338              * @return the label to display to cancel the action
   1339              */
   1340             public CharSequence getCancelLabel() {
   1341                 return mCancelLabel;
   1342             }
   1343         }
   1344     }
   1345 
   1346     /**
   1347      * Array of all {@link Action} structures attached to this notification by
   1348      * {@link Builder#addAction(int, CharSequence, PendingIntent)}. Mostly useful for instances of
   1349      * {@link android.service.notification.NotificationListenerService} that provide an alternative
   1350      * interface for invoking actions.
   1351      */
   1352     public Action[] actions;
   1353 
   1354     /**
   1355      * Replacement version of this notification whose content will be shown
   1356      * in an insecure context such as atop a secure keyguard. See {@link #visibility}
   1357      * and {@link #VISIBILITY_PUBLIC}.
   1358      */
   1359     public Notification publicVersion;
   1360 
   1361     /**
   1362      * Constructs a Notification object with default values.
   1363      * You might want to consider using {@link Builder} instead.
   1364      */
   1365     public Notification()
   1366     {
   1367         this.when = System.currentTimeMillis();
   1368         this.priority = PRIORITY_DEFAULT;
   1369     }
   1370 
   1371     /**
   1372      * @hide
   1373      */
   1374     public Notification(Context context, int icon, CharSequence tickerText, long when,
   1375             CharSequence contentTitle, CharSequence contentText, Intent contentIntent)
   1376     {
   1377         new Builder(context)
   1378                 .setWhen(when)
   1379                 .setSmallIcon(icon)
   1380                 .setTicker(tickerText)
   1381                 .setContentTitle(contentTitle)
   1382                 .setContentText(contentText)
   1383                 .setContentIntent(PendingIntent.getActivity(context, 0, contentIntent, 0))
   1384                 .buildInto(this);
   1385     }
   1386 
   1387     /**
   1388      * Constructs a Notification object with the information needed to
   1389      * have a status bar icon without the standard expanded view.
   1390      *
   1391      * @param icon          The resource id of the icon to put in the status bar.
   1392      * @param tickerText    The text that flows by in the status bar when the notification first
   1393      *                      activates.
   1394      * @param when          The time to show in the time field.  In the System.currentTimeMillis
   1395      *                      timebase.
   1396      *
   1397      * @deprecated Use {@link Builder} instead.
   1398      */
   1399     @Deprecated
   1400     public Notification(int icon, CharSequence tickerText, long when)
   1401     {
   1402         this.icon = icon;
   1403         this.tickerText = tickerText;
   1404         this.when = when;
   1405     }
   1406 
   1407     /**
   1408      * Unflatten the notification from a parcel.
   1409      */
   1410     public Notification(Parcel parcel)
   1411     {
   1412         int version = parcel.readInt();
   1413 
   1414         when = parcel.readLong();
   1415         if (parcel.readInt() != 0) {
   1416             mSmallIcon = Icon.CREATOR.createFromParcel(parcel);
   1417             if (mSmallIcon.getType() == Icon.TYPE_RESOURCE) {
   1418                 icon = mSmallIcon.getResId();
   1419             }
   1420         }
   1421         number = parcel.readInt();
   1422         if (parcel.readInt() != 0) {
   1423             contentIntent = PendingIntent.CREATOR.createFromParcel(parcel);
   1424         }
   1425         if (parcel.readInt() != 0) {
   1426             deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel);
   1427         }
   1428         if (parcel.readInt() != 0) {
   1429             tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
   1430         }
   1431         if (parcel.readInt() != 0) {
   1432             tickerView = RemoteViews.CREATOR.createFromParcel(parcel);
   1433         }
   1434         if (parcel.readInt() != 0) {
   1435             contentView = RemoteViews.CREATOR.createFromParcel(parcel);
   1436         }
   1437         if (parcel.readInt() != 0) {
   1438             mLargeIcon = Icon.CREATOR.createFromParcel(parcel);
   1439         }
   1440         defaults = parcel.readInt();
   1441         flags = parcel.readInt();
   1442         if (parcel.readInt() != 0) {
   1443             sound = Uri.CREATOR.createFromParcel(parcel);
   1444         }
   1445 
   1446         audioStreamType = parcel.readInt();
   1447         if (parcel.readInt() != 0) {
   1448             audioAttributes = AudioAttributes.CREATOR.createFromParcel(parcel);
   1449         }
   1450         vibrate = parcel.createLongArray();
   1451         ledARGB = parcel.readInt();
   1452         ledOnMS = parcel.readInt();
   1453         ledOffMS = parcel.readInt();
   1454         iconLevel = parcel.readInt();
   1455 
   1456         if (parcel.readInt() != 0) {
   1457             fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel);
   1458         }
   1459 
   1460         priority = parcel.readInt();
   1461 
   1462         category = parcel.readString();
   1463 
   1464         mGroupKey = parcel.readString();
   1465 
   1466         mSortKey = parcel.readString();
   1467 
   1468         extras = parcel.readBundle(); // may be null
   1469 
   1470         actions = parcel.createTypedArray(Action.CREATOR); // may be null
   1471 
   1472         if (parcel.readInt() != 0) {
   1473             bigContentView = RemoteViews.CREATOR.createFromParcel(parcel);
   1474         }
   1475 
   1476         if (parcel.readInt() != 0) {
   1477             headsUpContentView = RemoteViews.CREATOR.createFromParcel(parcel);
   1478         }
   1479 
   1480         visibility = parcel.readInt();
   1481 
   1482         if (parcel.readInt() != 0) {
   1483             publicVersion = Notification.CREATOR.createFromParcel(parcel);
   1484         }
   1485 
   1486         color = parcel.readInt();
   1487     }
   1488 
   1489     @Override
   1490     public Notification clone() {
   1491         Notification that = new Notification();
   1492         cloneInto(that, true);
   1493         return that;
   1494     }
   1495 
   1496     /**
   1497      * Copy all (or if heavy is false, all except Bitmaps and RemoteViews) members
   1498      * of this into that.
   1499      * @hide
   1500      */
   1501     public void cloneInto(Notification that, boolean heavy) {
   1502         that.when = this.when;
   1503         that.mSmallIcon = this.mSmallIcon;
   1504         that.number = this.number;
   1505 
   1506         // PendingIntents are global, so there's no reason (or way) to clone them.
   1507         that.contentIntent = this.contentIntent;
   1508         that.deleteIntent = this.deleteIntent;
   1509         that.fullScreenIntent = this.fullScreenIntent;
   1510 
   1511         if (this.tickerText != null) {
   1512             that.tickerText = this.tickerText.toString();
   1513         }
   1514         if (heavy && this.tickerView != null) {
   1515             that.tickerView = this.tickerView.clone();
   1516         }
   1517         if (heavy && this.contentView != null) {
   1518             that.contentView = this.contentView.clone();
   1519         }
   1520         if (heavy && this.mLargeIcon != null) {
   1521             that.mLargeIcon = this.mLargeIcon;
   1522         }
   1523         that.iconLevel = this.iconLevel;
   1524         that.sound = this.sound; // android.net.Uri is immutable
   1525         that.audioStreamType = this.audioStreamType;
   1526         if (this.audioAttributes != null) {
   1527             that.audioAttributes = new AudioAttributes.Builder(this.audioAttributes).build();
   1528         }
   1529 
   1530         final long[] vibrate = this.vibrate;
   1531         if (vibrate != null) {
   1532             final int N = vibrate.length;
   1533             final long[] vib = that.vibrate = new long[N];
   1534             System.arraycopy(vibrate, 0, vib, 0, N);
   1535         }
   1536 
   1537         that.ledARGB = this.ledARGB;
   1538         that.ledOnMS = this.ledOnMS;
   1539         that.ledOffMS = this.ledOffMS;
   1540         that.defaults = this.defaults;
   1541 
   1542         that.flags = this.flags;
   1543 
   1544         that.priority = this.priority;
   1545 
   1546         that.category = this.category;
   1547 
   1548         that.mGroupKey = this.mGroupKey;
   1549 
   1550         that.mSortKey = this.mSortKey;
   1551 
   1552         if (this.extras != null) {
   1553             try {
   1554                 that.extras = new Bundle(this.extras);
   1555                 // will unparcel
   1556                 that.extras.size();
   1557             } catch (BadParcelableException e) {
   1558                 Log.e(TAG, "could not unparcel extras from notification: " + this, e);
   1559                 that.extras = null;
   1560             }
   1561         }
   1562 
   1563         if (this.actions != null) {
   1564             that.actions = new Action[this.actions.length];
   1565             for(int i=0; i<this.actions.length; i++) {
   1566                 that.actions[i] = this.actions[i].clone();
   1567             }
   1568         }
   1569 
   1570         if (heavy && this.bigContentView != null) {
   1571             that.bigContentView = this.bigContentView.clone();
   1572         }
   1573 
   1574         if (heavy && this.headsUpContentView != null) {
   1575             that.headsUpContentView = this.headsUpContentView.clone();
   1576         }
   1577 
   1578         that.visibility = this.visibility;
   1579 
   1580         if (this.publicVersion != null) {
   1581             that.publicVersion = new Notification();
   1582             this.publicVersion.cloneInto(that.publicVersion, heavy);
   1583         }
   1584 
   1585         that.color = this.color;
   1586 
   1587         if (!heavy) {
   1588             that.lightenPayload(); // will clean out extras
   1589         }
   1590     }
   1591 
   1592     /**
   1593      * Removes heavyweight parts of the Notification object for archival or for sending to
   1594      * listeners when the full contents are not necessary.
   1595      * @hide
   1596      */
   1597     public final void lightenPayload() {
   1598         tickerView = null;
   1599         contentView = null;
   1600         bigContentView = null;
   1601         headsUpContentView = null;
   1602         mLargeIcon = null;
   1603         if (extras != null) {
   1604             extras.remove(Notification.EXTRA_LARGE_ICON);
   1605             extras.remove(Notification.EXTRA_LARGE_ICON_BIG);
   1606             extras.remove(Notification.EXTRA_PICTURE);
   1607             extras.remove(Notification.EXTRA_BIG_TEXT);
   1608             // Prevent light notifications from being rebuilt.
   1609             extras.remove(Builder.EXTRA_NEEDS_REBUILD);
   1610         }
   1611     }
   1612 
   1613     /**
   1614      * Make sure this CharSequence is safe to put into a bundle, which basically
   1615      * means it had better not be some custom Parcelable implementation.
   1616      * @hide
   1617      */
   1618     public static CharSequence safeCharSequence(CharSequence cs) {
   1619         if (cs == null) return cs;
   1620         if (cs.length() > MAX_CHARSEQUENCE_LENGTH) {
   1621             cs = cs.subSequence(0, MAX_CHARSEQUENCE_LENGTH);
   1622         }
   1623         if (cs instanceof Parcelable) {
   1624             Log.e(TAG, "warning: " + cs.getClass().getCanonicalName()
   1625                     + " instance is a custom Parcelable and not allowed in Notification");
   1626             return cs.toString();
   1627         }
   1628 
   1629         return cs;
   1630     }
   1631 
   1632     public int describeContents() {
   1633         return 0;
   1634     }
   1635 
   1636     /**
   1637      * Flatten this notification into a parcel.
   1638      */
   1639     public void writeToParcel(Parcel parcel, int flags)
   1640     {
   1641         parcel.writeInt(1);
   1642 
   1643         parcel.writeLong(when);
   1644         if (mSmallIcon == null && icon != 0) {
   1645             // you snuck an icon in here without using the builder; let's try to keep it
   1646             mSmallIcon = Icon.createWithResource("", icon);
   1647         }
   1648         if (mSmallIcon != null) {
   1649             parcel.writeInt(1);
   1650             mSmallIcon.writeToParcel(parcel, 0);
   1651         } else {
   1652             parcel.writeInt(0);
   1653         }
   1654         parcel.writeInt(number);
   1655         if (contentIntent != null) {
   1656             parcel.writeInt(1);
   1657             contentIntent.writeToParcel(parcel, 0);
   1658         } else {
   1659             parcel.writeInt(0);
   1660         }
   1661         if (deleteIntent != null) {
   1662             parcel.writeInt(1);
   1663             deleteIntent.writeToParcel(parcel, 0);
   1664         } else {
   1665             parcel.writeInt(0);
   1666         }
   1667         if (tickerText != null) {
   1668             parcel.writeInt(1);
   1669             TextUtils.writeToParcel(tickerText, parcel, flags);
   1670         } else {
   1671             parcel.writeInt(0);
   1672         }
   1673         if (tickerView != null) {
   1674             parcel.writeInt(1);
   1675             tickerView.writeToParcel(parcel, 0);
   1676         } else {
   1677             parcel.writeInt(0);
   1678         }
   1679         if (contentView != null) {
   1680             parcel.writeInt(1);
   1681             contentView.writeToParcel(parcel, 0);
   1682         } else {
   1683             parcel.writeInt(0);
   1684         }
   1685         if (mLargeIcon != null) {
   1686             parcel.writeInt(1);
   1687             mLargeIcon.writeToParcel(parcel, 0);
   1688         } else {
   1689             parcel.writeInt(0);
   1690         }
   1691 
   1692         parcel.writeInt(defaults);
   1693         parcel.writeInt(this.flags);
   1694 
   1695         if (sound != null) {
   1696             parcel.writeInt(1);
   1697             sound.writeToParcel(parcel, 0);
   1698         } else {
   1699             parcel.writeInt(0);
   1700         }
   1701         parcel.writeInt(audioStreamType);
   1702 
   1703         if (audioAttributes != null) {
   1704             parcel.writeInt(1);
   1705             audioAttributes.writeToParcel(parcel, 0);
   1706         } else {
   1707             parcel.writeInt(0);
   1708         }
   1709 
   1710         parcel.writeLongArray(vibrate);
   1711         parcel.writeInt(ledARGB);
   1712         parcel.writeInt(ledOnMS);
   1713         parcel.writeInt(ledOffMS);
   1714         parcel.writeInt(iconLevel);
   1715 
   1716         if (fullScreenIntent != null) {
   1717             parcel.writeInt(1);
   1718             fullScreenIntent.writeToParcel(parcel, 0);
   1719         } else {
   1720             parcel.writeInt(0);
   1721         }
   1722 
   1723         parcel.writeInt(priority);
   1724 
   1725         parcel.writeString(category);
   1726 
   1727         parcel.writeString(mGroupKey);
   1728 
   1729         parcel.writeString(mSortKey);
   1730 
   1731         parcel.writeBundle(extras); // null ok
   1732 
   1733         parcel.writeTypedArray(actions, 0); // null ok
   1734 
   1735         if (bigContentView != null) {
   1736             parcel.writeInt(1);
   1737             bigContentView.writeToParcel(parcel, 0);
   1738         } else {
   1739             parcel.writeInt(0);
   1740         }
   1741 
   1742         if (headsUpContentView != null) {
   1743             parcel.writeInt(1);
   1744             headsUpContentView.writeToParcel(parcel, 0);
   1745         } else {
   1746             parcel.writeInt(0);
   1747         }
   1748 
   1749         parcel.writeInt(visibility);
   1750 
   1751         if (publicVersion != null) {
   1752             parcel.writeInt(1);
   1753             publicVersion.writeToParcel(parcel, 0);
   1754         } else {
   1755             parcel.writeInt(0);
   1756         }
   1757 
   1758         parcel.writeInt(color);
   1759     }
   1760 
   1761     /**
   1762      * Parcelable.Creator that instantiates Notification objects
   1763      */
   1764     public static final Parcelable.Creator<Notification> CREATOR
   1765             = new Parcelable.Creator<Notification>()
   1766     {
   1767         public Notification createFromParcel(Parcel parcel)
   1768         {
   1769             return new Notification(parcel);
   1770         }
   1771 
   1772         public Notification[] newArray(int size)
   1773         {
   1774             return new Notification[size];
   1775         }
   1776     };
   1777 
   1778     /**
   1779      * Sets the {@link #contentView} field to be a view with the standard "Latest Event"
   1780      * layout.
   1781      *
   1782      * <p>Uses the {@link #icon} and {@link #when} fields to set the icon and time fields
   1783      * in the view.</p>
   1784      * @param context       The context for your application / activity.
   1785      * @param contentTitle The title that goes in the expanded entry.
   1786      * @param contentText  The text that goes in the expanded entry.
   1787      * @param contentIntent The intent to launch when the user clicks the expanded notification.
   1788      * If this is an activity, it must include the
   1789      * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
   1790      * that you take care of task management as described in the
   1791      * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
   1792      * Stack</a> document.
   1793      *
   1794      * @deprecated Use {@link Builder} instead.
   1795      * @removed
   1796      */
   1797     @Deprecated
   1798     public void setLatestEventInfo(Context context,
   1799             CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) {
   1800         Notification.Builder builder = new Notification.Builder(context);
   1801 
   1802         // First, ensure that key pieces of information that may have been set directly
   1803         // are preserved
   1804         builder.setWhen(this.when);
   1805         builder.setSmallIcon(this.icon);
   1806         builder.setPriority(this.priority);
   1807         builder.setTicker(this.tickerText);
   1808         builder.setNumber(this.number);
   1809         builder.setColor(this.color);
   1810         builder.mFlags = this.flags;
   1811         builder.setSound(this.sound, this.audioStreamType);
   1812         builder.setDefaults(this.defaults);
   1813         builder.setVibrate(this.vibrate);
   1814         builder.setDeleteIntent(this.deleteIntent);
   1815 
   1816         // now apply the latestEventInfo fields
   1817         if (contentTitle != null) {
   1818             builder.setContentTitle(contentTitle);
   1819         }
   1820         if (contentText != null) {
   1821             builder.setContentText(contentText);
   1822         }
   1823         builder.setContentIntent(contentIntent);
   1824         builder.buildInto(this);
   1825     }
   1826 
   1827     @Override
   1828     public String toString() {
   1829         StringBuilder sb = new StringBuilder();
   1830         sb.append("Notification(pri=");
   1831         sb.append(priority);
   1832         sb.append(" contentView=");
   1833         if (contentView != null) {
   1834             sb.append(contentView.getPackage());
   1835             sb.append("/0x");
   1836             sb.append(Integer.toHexString(contentView.getLayoutId()));
   1837         } else {
   1838             sb.append("null");
   1839         }
   1840         sb.append(" vibrate=");
   1841         if ((this.defaults & DEFAULT_VIBRATE) != 0) {
   1842             sb.append("default");
   1843         } else if (this.vibrate != null) {
   1844             int N = this.vibrate.length-1;
   1845             sb.append("[");
   1846             for (int i=0; i<N; i++) {
   1847                 sb.append(this.vibrate[i]);
   1848                 sb.append(',');
   1849             }
   1850             if (N != -1) {
   1851                 sb.append(this.vibrate[N]);
   1852             }
   1853             sb.append("]");
   1854         } else {
   1855             sb.append("null");
   1856         }
   1857         sb.append(" sound=");
   1858         if ((this.defaults & DEFAULT_SOUND) != 0) {
   1859             sb.append("default");
   1860         } else if (this.sound != null) {
   1861             sb.append(this.sound.toString());
   1862         } else {
   1863             sb.append("null");
   1864         }
   1865         if (this.tickerText != null) {
   1866             sb.append(" tick");
   1867         }
   1868         sb.append(" defaults=0x");
   1869         sb.append(Integer.toHexString(this.defaults));
   1870         sb.append(" flags=0x");
   1871         sb.append(Integer.toHexString(this.flags));
   1872         sb.append(String.format(" color=0x%08x", this.color));
   1873         if (this.category != null) {
   1874             sb.append(" category=");
   1875             sb.append(this.category);
   1876         }
   1877         if (this.mGroupKey != null) {
   1878             sb.append(" groupKey=");
   1879             sb.append(this.mGroupKey);
   1880         }
   1881         if (this.mSortKey != null) {
   1882             sb.append(" sortKey=");
   1883             sb.append(this.mSortKey);
   1884         }
   1885         if (actions != null) {
   1886             sb.append(" actions=");
   1887             sb.append(actions.length);
   1888         }
   1889         sb.append(" vis=");
   1890         sb.append(visibilityToString(this.visibility));
   1891         if (this.publicVersion != null) {
   1892             sb.append(" publicVersion=");
   1893             sb.append(publicVersion.toString());
   1894         }
   1895         sb.append(")");
   1896         return sb.toString();
   1897     }
   1898 
   1899     /**
   1900      * {@hide}
   1901      */
   1902     public static String visibilityToString(int vis) {
   1903         switch (vis) {
   1904             case VISIBILITY_PRIVATE:
   1905                 return "PRIVATE";
   1906             case VISIBILITY_PUBLIC:
   1907                 return "PUBLIC";
   1908             case VISIBILITY_SECRET:
   1909                 return "SECRET";
   1910             default:
   1911                 return "UNKNOWN(" + String.valueOf(vis) + ")";
   1912         }
   1913     }
   1914 
   1915     /**
   1916      * {@hide}
   1917      */
   1918     public static String priorityToString(@Priority int pri) {
   1919         switch (pri) {
   1920             case PRIORITY_MIN:
   1921                 return "MIN";
   1922             case PRIORITY_LOW:
   1923                 return "LOW";
   1924             case PRIORITY_DEFAULT:
   1925                 return "DEFAULT";
   1926             case PRIORITY_HIGH:
   1927                 return "HIGH";
   1928             case PRIORITY_MAX:
   1929                 return "MAX";
   1930             default:
   1931                 return "UNKNOWN(" + String.valueOf(pri) + ")";
   1932         }
   1933     }
   1934 
   1935     /**
   1936      * The small icon representing this notification in the status bar and content view.
   1937      *
   1938      * @return the small icon representing this notification.
   1939      *
   1940      * @see Builder#getSmallIcon()
   1941      * @see Builder#setSmallIcon(Icon)
   1942      */
   1943     public Icon getSmallIcon() {
   1944         return mSmallIcon;
   1945     }
   1946 
   1947     /**
   1948      * Used when notifying to clean up legacy small icons.
   1949      * @hide
   1950      */
   1951     public void setSmallIcon(Icon icon) {
   1952         mSmallIcon = icon;
   1953     }
   1954 
   1955     /**
   1956      * The large icon shown in this notification's content view.
   1957      * @see Builder#getLargeIcon()
   1958      * @see Builder#setLargeIcon(Icon)
   1959      */
   1960     public Icon getLargeIcon() {
   1961         return mLargeIcon;
   1962     }
   1963 
   1964     /**
   1965      * @hide
   1966      */
   1967     public boolean isValid() {
   1968         // Would like to check for icon!=0 here, too, but NotificationManagerService accepts that
   1969         // for legacy reasons.
   1970         return contentView != null || extras.getBoolean(Builder.EXTRA_REBUILD_CONTENT_VIEW);
   1971     }
   1972 
   1973     /**
   1974      * @hide
   1975      */
   1976     public boolean isGroupSummary() {
   1977         return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0;
   1978     }
   1979 
   1980     /**
   1981      * @hide
   1982      */
   1983     public boolean isGroupChild() {
   1984         return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) == 0;
   1985     }
   1986 
   1987     /**
   1988      * Builder class for {@link Notification} objects.
   1989      *
   1990      * Provides a convenient way to set the various fields of a {@link Notification} and generate
   1991      * content views using the platform's notification layout template. If your app supports
   1992      * versions of Android as old as API level 4, you can instead use
   1993      * {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder},
   1994      * available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support
   1995      * library</a>.
   1996      *
   1997      * <p>Example:
   1998      *
   1999      * <pre class="prettyprint">
   2000      * Notification noti = new Notification.Builder(mContext)
   2001      *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
   2002      *         .setContentText(subject)
   2003      *         .setSmallIcon(R.drawable.new_mail)
   2004      *         .setLargeIcon(aBitmap)
   2005      *         .build();
   2006      * </pre>
   2007      */
   2008     public static class Builder {
   2009         private static final int MAX_ACTION_BUTTONS = 3;
   2010         private static final float LARGE_TEXT_SCALE = 1.3f;
   2011 
   2012         /**
   2013          * @hide
   2014          */
   2015         public static final String EXTRA_NEEDS_REBUILD = "android.rebuild";
   2016 
   2017         /**
   2018          * @hide
   2019          */
   2020         public static final String EXTRA_REBUILD_LARGE_ICON = "android.rebuild.largeIcon";
   2021         /**
   2022          * @hide
   2023          */
   2024         public static final String EXTRA_REBUILD_CONTENT_VIEW = "android.rebuild.contentView";
   2025         /**
   2026          * @hide
   2027          */
   2028         public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT =
   2029                 "android.rebuild.contentViewActionCount";
   2030         /**
   2031          * @hide
   2032          */
   2033         public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW
   2034                 = "android.rebuild.bigView";
   2035         /**
   2036          * @hide
   2037          */
   2038         public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT
   2039                 = "android.rebuild.bigViewActionCount";
   2040         /**
   2041          * @hide
   2042          */
   2043         public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW
   2044                 = "android.rebuild.hudView";
   2045         /**
   2046          * @hide
   2047          */
   2048         public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT
   2049                 = "android.rebuild.hudViewActionCount";
   2050 
   2051         /**
   2052          * The ApplicationInfo of the package that created the notification, used to create
   2053          * a context to rebuild the notification via a Builder.
   2054          * @hide
   2055          */
   2056         private static final String EXTRA_REBUILD_CONTEXT_APPLICATION_INFO =
   2057                 "android.rebuild.applicationInfo";
   2058 
   2059         // Whether to enable stripping (at post time) & rebuilding (at listener receive time) of
   2060         // memory intensive resources.
   2061         private static final boolean STRIP_AND_REBUILD = true;
   2062 
   2063         private Context mContext;
   2064 
   2065         private long mWhen;
   2066         private Icon mSmallIcon, mLargeIcon;
   2067         private int mSmallIconLevel;
   2068         private int mNumber;
   2069         private CharSequence mContentTitle;
   2070         private CharSequence mContentText;
   2071         private CharSequence mContentInfo;
   2072         private CharSequence mSubText;
   2073         private PendingIntent mContentIntent;
   2074         private RemoteViews mContentView;
   2075         private PendingIntent mDeleteIntent;
   2076         private PendingIntent mFullScreenIntent;
   2077         private CharSequence mTickerText;
   2078         private RemoteViews mTickerView;
   2079         private Uri mSound;
   2080         private int mAudioStreamType;
   2081         private AudioAttributes mAudioAttributes;
   2082         private long[] mVibrate;
   2083         private int mLedArgb;
   2084         private int mLedOnMs;
   2085         private int mLedOffMs;
   2086         private int mDefaults;
   2087         private int mFlags;
   2088         private int mProgressMax;
   2089         private int mProgress;
   2090         private boolean mProgressIndeterminate;
   2091         private String mCategory;
   2092         private String mGroupKey;
   2093         private String mSortKey;
   2094         private Bundle mExtras;
   2095         private int mPriority;
   2096         private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS);
   2097         private boolean mUseChronometer;
   2098         private Style mStyle;
   2099         private boolean mShowWhen = true;
   2100         private int mVisibility = VISIBILITY_PRIVATE;
   2101         private Notification mPublicVersion = null;
   2102         private final NotificationColorUtil mColorUtil;
   2103         private ArrayList<String> mPeople;
   2104         private int mColor = COLOR_DEFAULT;
   2105 
   2106         /**
   2107          * The user that built the notification originally.
   2108          */
   2109         private int mOriginatingUserId;
   2110 
   2111         /**
   2112          * Contains extras related to rebuilding during the build phase.
   2113          */
   2114         private Bundle mRebuildBundle = new Bundle();
   2115         /**
   2116          * Contains the notification to rebuild when this Builder is in "rebuild" mode.
   2117          * Null otherwise.
   2118          */
   2119         private Notification mRebuildNotification = null;
   2120 
   2121         /**
   2122          * Whether the build notification has three lines. This is used to make the top padding for
   2123          * both the contracted and expanded layout consistent.
   2124          *
   2125          * <p>
   2126          * This field is only valid during the build phase.
   2127          */
   2128         private boolean mHasThreeLines;
   2129 
   2130         /**
   2131          * Constructs a new Builder with the defaults:
   2132          *
   2133 
   2134          * <table>
   2135          * <tr><th align=right>priority</th>
   2136          *     <td>{@link #PRIORITY_DEFAULT}</td></tr>
   2137          * <tr><th align=right>when</th>
   2138          *     <td>now ({@link System#currentTimeMillis()})</td></tr>
   2139          * <tr><th align=right>audio stream</th>
   2140          *     <td>{@link #STREAM_DEFAULT}</td></tr>
   2141          * </table>
   2142          *
   2143 
   2144          * @param context
   2145          *            A {@link Context} that will be used by the Builder to construct the
   2146          *            RemoteViews. The Context will not be held past the lifetime of this Builder
   2147          *            object.
   2148          */
   2149         public Builder(Context context) {
   2150             /*
   2151              * Important compatibility note!
   2152              * Some apps out in the wild create a Notification.Builder in their Activity subclass
   2153              * constructor for later use. At this point Activities - themselves subclasses of
   2154              * ContextWrapper - do not have their inner Context populated yet. This means that
   2155              * any calls to Context methods from within this constructor can cause NPEs in existing
   2156              * apps. Any data populated from mContext should therefore be populated lazily to
   2157              * preserve compatibility.
   2158              */
   2159             mContext = context;
   2160 
   2161             // Set defaults to match the defaults of a Notification
   2162             mWhen = System.currentTimeMillis();
   2163             mAudioStreamType = STREAM_DEFAULT;
   2164             mAudioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
   2165             mPriority = PRIORITY_DEFAULT;
   2166             mPeople = new ArrayList<String>();
   2167 
   2168             mColorUtil = context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.LOLLIPOP ?
   2169                     NotificationColorUtil.getInstance(mContext) : null;
   2170         }
   2171 
   2172         /**
   2173          * Creates a Builder for rebuilding the given Notification.
   2174          * <p>
   2175          * Call {@link #rebuild()} to retrieve the rebuilt version of 'n'.
   2176          */
   2177         private Builder(Context context, Notification n) {
   2178             this(context);
   2179             mRebuildNotification = n;
   2180             restoreFromNotification(n);
   2181 
   2182             Style style = null;
   2183             Bundle extras = n.extras;
   2184             String templateClass = extras.getString(EXTRA_TEMPLATE);
   2185             if (!TextUtils.isEmpty(templateClass)) {
   2186                 Class<? extends Style> styleClass = getNotificationStyleClass(templateClass);
   2187                 if (styleClass == null) {
   2188                     Log.d(TAG, "Unknown style class: " + styleClass);
   2189                     return;
   2190                 }
   2191 
   2192                 try {
   2193                     Constructor<? extends Style> constructor = styleClass.getConstructor();
   2194                     constructor.setAccessible(true);
   2195                     style = constructor.newInstance();
   2196                     style.restoreFromExtras(extras);
   2197                 } catch (Throwable t) {
   2198                     Log.e(TAG, "Could not create Style", t);
   2199                     return;
   2200                 }
   2201             }
   2202             if (style != null) {
   2203                 setStyle(style);
   2204             }
   2205         }
   2206 
   2207         /**
   2208          * Add a timestamp pertaining to the notification (usually the time the event occurred).
   2209          * It will be shown in the notification content view by default; use
   2210          * {@link #setShowWhen(boolean) setShowWhen} to control this.
   2211          *
   2212          * @see Notification#when
   2213          */
   2214         public Builder setWhen(long when) {
   2215             mWhen = when;
   2216             return this;
   2217         }
   2218 
   2219         /**
   2220          * Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown
   2221          * in the content view.
   2222          */
   2223         public Builder setShowWhen(boolean show) {
   2224             mShowWhen = show;
   2225             return this;
   2226         }
   2227 
   2228         /**
   2229          * Show the {@link Notification#when} field as a stopwatch.
   2230          *
   2231          * Instead of presenting <code>when</code> as a timestamp, the notification will show an
   2232          * automatically updating display of the minutes and seconds since <code>when</code>.
   2233          *
   2234          * Useful when showing an elapsed time (like an ongoing phone call).
   2235          *
   2236          * @see android.widget.Chronometer
   2237          * @see Notification#when
   2238          */
   2239         public Builder setUsesChronometer(boolean b) {
   2240             mUseChronometer = b;
   2241             return this;
   2242         }
   2243 
   2244         /**
   2245          * Set the small icon resource, which will be used to represent the notification in the
   2246          * status bar.
   2247          *
   2248 
   2249          * The platform template for the expanded view will draw this icon in the left, unless a
   2250          * {@link #setLargeIcon(Bitmap) large icon} has also been specified, in which case the small
   2251          * icon will be moved to the right-hand side.
   2252          *
   2253 
   2254          * @param icon
   2255          *            A resource ID in the application's package of the drawable to use.
   2256          * @see Notification#icon
   2257          */
   2258         public Builder setSmallIcon(@DrawableRes int icon) {
   2259             return setSmallIcon(icon != 0
   2260                     ? Icon.createWithResource(mContext, icon)
   2261                     : null);
   2262         }
   2263 
   2264         /**
   2265          * A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional
   2266          * level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable
   2267          * LevelListDrawable}.
   2268          *
   2269          * @param icon A resource ID in the application's package of the drawable to use.
   2270          * @param level The level to use for the icon.
   2271          *
   2272          * @see Notification#icon
   2273          * @see Notification#iconLevel
   2274          */
   2275         public Builder setSmallIcon(@DrawableRes int icon, int level) {
   2276             mSmallIconLevel = level;
   2277             return setSmallIcon(icon);
   2278         }
   2279 
   2280         /**
   2281          * Set the small icon, which will be used to represent the notification in the
   2282          * status bar and content view (unless overriden there by a
   2283          * {@link #setLargeIcon(Bitmap) large icon}).
   2284          *
   2285          * @param icon An Icon object to use.
   2286          * @see Notification#icon
   2287          */
   2288         public Builder setSmallIcon(Icon icon) {
   2289             mSmallIcon = icon;
   2290             return this;
   2291         }
   2292 
   2293         /**
   2294          * Set the first line of text in the platform notification template.
   2295          */
   2296         public Builder setContentTitle(CharSequence title) {
   2297             mContentTitle = safeCharSequence(title);
   2298             return this;
   2299         }
   2300 
   2301         /**
   2302          * Set the second line of text in the platform notification template.
   2303          */
   2304         public Builder setContentText(CharSequence text) {
   2305             mContentText = safeCharSequence(text);
   2306             return this;
   2307         }
   2308 
   2309         /**
   2310          * Set the third line of text in the platform notification template.
   2311          * Don't use if you're also using {@link #setProgress(int, int, boolean)}; they occupy the
   2312          * same location in the standard template.
   2313          */
   2314         public Builder setSubText(CharSequence text) {
   2315             mSubText = safeCharSequence(text);
   2316             return this;
   2317         }
   2318 
   2319         /**
   2320          * Set the large number at the right-hand side of the notification.  This is
   2321          * equivalent to setContentInfo, although it might show the number in a different
   2322          * font size for readability.
   2323          */
   2324         public Builder setNumber(int number) {
   2325             mNumber = number;
   2326             return this;
   2327         }
   2328 
   2329         /**
   2330          * A small piece of additional information pertaining to this notification.
   2331          *
   2332          * The platform template will draw this on the last line of the notification, at the far
   2333          * right (to the right of a smallIcon if it has been placed there).
   2334          */
   2335         public Builder setContentInfo(CharSequence info) {
   2336             mContentInfo = safeCharSequence(info);
   2337             return this;
   2338         }
   2339 
   2340         /**
   2341          * Set the progress this notification represents.
   2342          *
   2343          * The platform template will represent this using a {@link ProgressBar}.
   2344          */
   2345         public Builder setProgress(int max, int progress, boolean indeterminate) {
   2346             mProgressMax = max;
   2347             mProgress = progress;
   2348             mProgressIndeterminate = indeterminate;
   2349             return this;
   2350         }
   2351 
   2352         /**
   2353          * Supply a custom RemoteViews to use instead of the platform template.
   2354          *
   2355          * @see Notification#contentView
   2356          */
   2357         public Builder setContent(RemoteViews views) {
   2358             mContentView = views;
   2359             return this;
   2360         }
   2361 
   2362         /**
   2363          * Supply a {@link PendingIntent} to be sent when the notification is clicked.
   2364          *
   2365          * As of {@link android.os.Build.VERSION_CODES#HONEYCOMB}, if this field is unset and you
   2366          * have specified a custom RemoteViews with {@link #setContent(RemoteViews)}, you can use
   2367          * {@link RemoteViews#setOnClickPendingIntent RemoteViews.setOnClickPendingIntent(int,PendingIntent)}
   2368          * to assign PendingIntents to individual views in that custom layout (i.e., to create
   2369          * clickable buttons inside the notification view).
   2370          *
   2371          * @see Notification#contentIntent Notification.contentIntent
   2372          */
   2373         public Builder setContentIntent(PendingIntent intent) {
   2374             mContentIntent = intent;
   2375             return this;
   2376         }
   2377 
   2378         /**
   2379          * Supply a {@link PendingIntent} to send when the notification is cleared explicitly by the user.
   2380          *
   2381          * @see Notification#deleteIntent
   2382          */
   2383         public Builder setDeleteIntent(PendingIntent intent) {
   2384             mDeleteIntent = intent;
   2385             return this;
   2386         }
   2387 
   2388         /**
   2389          * An intent to launch instead of posting the notification to the status bar.
   2390          * Only for use with extremely high-priority notifications demanding the user's
   2391          * <strong>immediate</strong> attention, such as an incoming phone call or
   2392          * alarm clock that the user has explicitly set to a particular time.
   2393          * If this facility is used for something else, please give the user an option
   2394          * to turn it off and use a normal notification, as this can be extremely
   2395          * disruptive.
   2396          *
   2397          * <p>
   2398          * The system UI may choose to display a heads-up notification, instead of
   2399          * launching this intent, while the user is using the device.
   2400          * </p>
   2401          *
   2402          * @param intent The pending intent to launch.
   2403          * @param highPriority Passing true will cause this notification to be sent
   2404          *          even if other notifications are suppressed.
   2405          *
   2406          * @see Notification#fullScreenIntent
   2407          */
   2408         public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) {
   2409             mFullScreenIntent = intent;
   2410             setFlag(FLAG_HIGH_PRIORITY, highPriority);
   2411             return this;
   2412         }
   2413 
   2414         /**
   2415          * Set the "ticker" text which is sent to accessibility services.
   2416          *
   2417          * @see Notification#tickerText
   2418          */
   2419         public Builder setTicker(CharSequence tickerText) {
   2420             mTickerText = safeCharSequence(tickerText);
   2421             return this;
   2422         }
   2423 
   2424         /**
   2425          * Obsolete version of {@link #setTicker(CharSequence)}.
   2426          *
   2427          */
   2428         @Deprecated
   2429         public Builder setTicker(CharSequence tickerText, RemoteViews views) {
   2430             mTickerText = safeCharSequence(tickerText);
   2431             mTickerView = views; // we'll save it for you anyway
   2432             return this;
   2433         }
   2434 
   2435         /**
   2436          * Add a large icon to the notification content view.
   2437          *
   2438          * In the platform template, this image will be shown on the left of the notification view
   2439          * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small
   2440          * badge atop the large icon).
   2441          */
   2442         public Builder setLargeIcon(Bitmap b) {
   2443             return setLargeIcon(b != null ? Icon.createWithBitmap(b) : null);
   2444         }
   2445 
   2446         /**
   2447          * Add a large icon to the notification content view.
   2448          *
   2449          * In the platform template, this image will be shown on the left of the notification view
   2450          * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small
   2451          * badge atop the large icon).
   2452          */
   2453         public Builder setLargeIcon(Icon icon) {
   2454             mLargeIcon = icon;
   2455             return this;
   2456         }
   2457 
   2458         /**
   2459          * Set the sound to play.
   2460          *
   2461          * It will be played using the {@link #AUDIO_ATTRIBUTES_DEFAULT default audio attributes}
   2462          * for notifications.
   2463          *
   2464          * <p>
   2465          * A notification that is noisy is more likely to be presented as a heads-up notification.
   2466          * </p>
   2467          *
   2468          * @see Notification#sound
   2469          */
   2470         public Builder setSound(Uri sound) {
   2471             mSound = sound;
   2472             mAudioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
   2473             return this;
   2474         }
   2475 
   2476         /**
   2477          * Set the sound to play, along with a specific stream on which to play it.
   2478          *
   2479          * See {@link android.media.AudioManager} for the <code>STREAM_</code> constants.
   2480          *
   2481          * <p>
   2482          * A notification that is noisy is more likely to be presented as a heads-up notification.
   2483          * </p>
   2484          * @deprecated use {@link #setSound(Uri, AudioAttributes)} instead.
   2485          * @see Notification#sound
   2486          */
   2487         @Deprecated
   2488         public Builder setSound(Uri sound, int streamType) {
   2489             mSound = sound;
   2490             mAudioStreamType = streamType;
   2491             return this;
   2492         }
   2493 
   2494         /**
   2495          * Set the sound to play, along with specific {@link AudioAttributes audio attributes} to
   2496          * use during playback.
   2497          *
   2498          * <p>
   2499          * A notification that is noisy is more likely to be presented as a heads-up notification.
   2500          * </p>
   2501          *
   2502          * @see Notification#sound
   2503          */
   2504         public Builder setSound(Uri sound, AudioAttributes audioAttributes) {
   2505             mSound = sound;
   2506             mAudioAttributes = audioAttributes;
   2507             return this;
   2508         }
   2509 
   2510         /**
   2511          * Set the vibration pattern to use.
   2512          *
   2513          * See {@link android.os.Vibrator#vibrate(long[], int)} for a discussion of the
   2514          * <code>pattern</code> parameter.
   2515          *
   2516          * <p>
   2517          * A notification that vibrates is more likely to be presented as a heads-up notification.
   2518          * </p>
   2519          *
   2520          * @see Notification#vibrate
   2521          */
   2522         public Builder setVibrate(long[] pattern) {
   2523             mVibrate = pattern;
   2524             return this;
   2525         }
   2526 
   2527         /**
   2528          * Set the desired color for the indicator LED on the device, as well as the
   2529          * blink duty cycle (specified in milliseconds).
   2530          *
   2531 
   2532          * Not all devices will honor all (or even any) of these values.
   2533          *
   2534 
   2535          * @see Notification#ledARGB
   2536          * @see Notification#ledOnMS
   2537          * @see Notification#ledOffMS
   2538          */
   2539         public Builder setLights(@ColorInt int argb, int onMs, int offMs) {
   2540             mLedArgb = argb;
   2541             mLedOnMs = onMs;
   2542             mLedOffMs = offMs;
   2543             return this;
   2544         }
   2545 
   2546         /**
   2547          * Set whether this is an "ongoing" notification.
   2548          *
   2549 
   2550          * Ongoing notifications cannot be dismissed by the user, so your application or service
   2551          * must take care of canceling them.
   2552          *
   2553 
   2554          * They are typically used to indicate a background task that the user is actively engaged
   2555          * with (e.g., playing music) or is pending in some way and therefore occupying the device
   2556          * (e.g., a file download, sync operation, active network connection).
   2557          *
   2558 
   2559          * @see Notification#FLAG_ONGOING_EVENT
   2560          * @see Service#setForeground(boolean)
   2561          */
   2562         public Builder setOngoing(boolean ongoing) {
   2563             setFlag(FLAG_ONGOING_EVENT, ongoing);
   2564             return this;
   2565         }
   2566 
   2567         /**
   2568          * Set this flag if you would only like the sound, vibrate
   2569          * and ticker to be played if the notification is not already showing.
   2570          *
   2571          * @see Notification#FLAG_ONLY_ALERT_ONCE
   2572          */
   2573         public Builder setOnlyAlertOnce(boolean onlyAlertOnce) {
   2574             setFlag(FLAG_ONLY_ALERT_ONCE, onlyAlertOnce);
   2575             return this;
   2576         }
   2577 
   2578         /**
   2579          * Make this notification automatically dismissed when the user touches it. The
   2580          * PendingIntent set with {@link #setDeleteIntent} will be sent when this happens.
   2581          *
   2582          * @see Notification#FLAG_AUTO_CANCEL
   2583          */
   2584         public Builder setAutoCancel(boolean autoCancel) {
   2585             setFlag(FLAG_AUTO_CANCEL, autoCancel);
   2586             return this;
   2587         }
   2588 
   2589         /**
   2590          * Set whether or not this notification should not bridge to other devices.
   2591          *
   2592          * <p>Some notifications can be bridged to other devices for remote display.
   2593          * This hint can be set to recommend this notification not be bridged.
   2594          */
   2595         public Builder setLocalOnly(boolean localOnly) {
   2596             setFlag(FLAG_LOCAL_ONLY, localOnly);
   2597             return this;
   2598         }
   2599 
   2600         /**
   2601          * Set which notification properties will be inherited from system defaults.
   2602          * <p>
   2603          * The value should be one or more of the following fields combined with
   2604          * bitwise-or:
   2605          * {@link #DEFAULT_SOUND}, {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}.
   2606          * <p>
   2607          * For all default values, use {@link #DEFAULT_ALL}.
   2608          */
   2609         public Builder setDefaults(int defaults) {
   2610             mDefaults = defaults;
   2611             return this;
   2612         }
   2613 
   2614         /**
   2615          * Set the priority of this notification.
   2616          *
   2617          * @see Notification#priority
   2618          */
   2619         public Builder setPriority(@Priority int pri) {
   2620             mPriority = pri;
   2621             return this;
   2622         }
   2623 
   2624         /**
   2625          * Set the notification category.
   2626          *
   2627          * @see Notification#category
   2628          */
   2629         public Builder setCategory(String category) {
   2630             mCategory = category;
   2631             return this;
   2632         }
   2633 
   2634         /**
   2635          * Add a person that is relevant to this notification.
   2636          *
   2637          * <P>
   2638          * Depending on user preferences, this annotation may allow the notification to pass
   2639          * through interruption filters, and to appear more prominently in the user interface.
   2640          * </P>
   2641          *
   2642          * <P>
   2643          * The person should be specified by the {@code String} representation of a
   2644          * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
   2645          * </P>
   2646          *
   2647          * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema
   2648          * URIs.  The path part of these URIs must exist in the contacts database, in the
   2649          * appropriate column, or the reference will be discarded as invalid. Telephone schema
   2650          * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}.
   2651          * </P>
   2652          *
   2653          * @param uri A URI for the person.
   2654          * @see Notification#EXTRA_PEOPLE
   2655          */
   2656         public Builder addPerson(String uri) {
   2657             mPeople.add(uri);
   2658             return this;
   2659         }
   2660 
   2661         /**
   2662          * Set this notification to be part of a group of notifications sharing the same key.
   2663          * Grouped notifications may display in a cluster or stack on devices which
   2664          * support such rendering.
   2665          *
   2666          * <p>To make this notification the summary for its group, also call
   2667          * {@link #setGroupSummary}. A sort order can be specified for group members by using
   2668          * {@link #setSortKey}.
   2669          * @param groupKey The group key of the group.
   2670          * @return this object for method chaining
   2671          */
   2672         public Builder setGroup(String groupKey) {
   2673             mGroupKey = groupKey;
   2674             return this;
   2675         }
   2676 
   2677         /**
   2678          * Set this notification to be the group summary for a group of notifications.
   2679          * Grouped notifications may display in a cluster or stack on devices which
   2680          * support such rendering. Requires a group key also be set using {@link #setGroup}.
   2681          * @param isGroupSummary Whether this notification should be a group summary.
   2682          * @return this object for method chaining
   2683          */
   2684         public Builder setGroupSummary(boolean isGroupSummary) {
   2685             setFlag(FLAG_GROUP_SUMMARY, isGroupSummary);
   2686             return this;
   2687         }
   2688 
   2689         /**
   2690          * Set a sort key that orders this notification among other notifications from the
   2691          * same package. This can be useful if an external sort was already applied and an app
   2692          * would like to preserve this. Notifications will be sorted lexicographically using this
   2693          * value, although providing different priorities in addition to providing sort key may
   2694          * cause this value to be ignored.
   2695          *
   2696          * <p>This sort key can also be used to order members of a notification group. See
   2697          * {@link #setGroup}.
   2698          *
   2699          * @see String#compareTo(String)
   2700          */
   2701         public Builder setSortKey(String sortKey) {
   2702             mSortKey = sortKey;
   2703             return this;
   2704         }
   2705 
   2706         /**
   2707          * Merge additional metadata into this notification.
   2708          *
   2709          * <p>Values within the Bundle will replace existing extras values in this Builder.
   2710          *
   2711          * @see Notification#extras
   2712          */
   2713         public Builder addExtras(Bundle extras) {
   2714             if (extras != null) {
   2715                 if (mExtras == null) {
   2716                     mExtras = new Bundle(extras);
   2717                 } else {
   2718                     mExtras.putAll(extras);
   2719                 }
   2720             }
   2721             return this;
   2722         }
   2723 
   2724         /**
   2725          * Set metadata for this notification.
   2726          *
   2727          * <p>A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's
   2728          * current contents are copied into the Notification each time {@link #build()} is
   2729          * called.
   2730          *
   2731          * <p>Replaces any existing extras values with those from the provided Bundle.
   2732          * Use {@link #addExtras} to merge in metadata instead.
   2733          *
   2734          * @see Notification#extras
   2735          */
   2736         public Builder setExtras(Bundle extras) {
   2737             mExtras = extras;
   2738             return this;
   2739         }
   2740 
   2741         /**
   2742          * Get the current metadata Bundle used by this notification Builder.
   2743          *
   2744          * <p>The returned Bundle is shared with this Builder.
   2745          *
   2746          * <p>The current contents of this Bundle are copied into the Notification each time
   2747          * {@link #build()} is called.
   2748          *
   2749          * @see Notification#extras
   2750          */
   2751         public Bundle getExtras() {
   2752             if (mExtras == null) {
   2753                 mExtras = new Bundle();
   2754             }
   2755             return mExtras;
   2756         }
   2757 
   2758         /**
   2759          * Add an action to this notification. Actions are typically displayed by
   2760          * the system as a button adjacent to the notification content.
   2761          * <p>
   2762          * Every action must have an icon (32dp square and matching the
   2763          * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
   2764          * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
   2765          * <p>
   2766          * A notification in its expanded form can display up to 3 actions, from left to right in
   2767          * the order they were added. Actions will not be displayed when the notification is
   2768          * collapsed, however, so be sure that any essential functions may be accessed by the user
   2769          * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
   2770          *
   2771          * @param icon Resource ID of a drawable that represents the action.
   2772          * @param title Text describing the action.
   2773          * @param intent PendingIntent to be fired when the action is invoked.
   2774          *
   2775          * @deprecated Use {@link #addAction(Action)} instead.
   2776          */
   2777         @Deprecated
   2778         public Builder addAction(int icon, CharSequence title, PendingIntent intent) {
   2779             mActions.add(new Action(icon, safeCharSequence(title), intent));
   2780             return this;
   2781         }
   2782 
   2783         /**
   2784          * Add an action to this notification. Actions are typically displayed by
   2785          * the system as a button adjacent to the notification content.
   2786          * <p>
   2787          * Every action must have an icon (32dp square and matching the
   2788          * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
   2789          * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
   2790          * <p>
   2791          * A notification in its expanded form can display up to 3 actions, from left to right in
   2792          * the order they were added. Actions will not be displayed when the notification is
   2793          * collapsed, however, so be sure that any essential functions may be accessed by the user
   2794          * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
   2795          *
   2796          * @param action The action to add.
   2797          */
   2798         public Builder addAction(Action action) {
   2799             mActions.add(action);
   2800             return this;
   2801         }
   2802 
   2803         /**
   2804          * Add a rich notification style to be applied at build time.
   2805          *
   2806          * @param style Object responsible for modifying the notification style.
   2807          */
   2808         public Builder setStyle(Style style) {
   2809             if (mStyle != style) {
   2810                 mStyle = style;
   2811                 if (mStyle != null) {
   2812                     mStyle.setBuilder(this);
   2813                 }
   2814             }
   2815             return this;
   2816         }
   2817 
   2818         /**
   2819          * Specify the value of {@link #visibility}.
   2820          *
   2821          * @param visibility One of {@link #VISIBILITY_PRIVATE} (the default),
   2822          * {@link #VISIBILITY_SECRET}, or {@link #VISIBILITY_PUBLIC}.
   2823          *
   2824          * @return The same Builder.
   2825          */
   2826         public Builder setVisibility(int visibility) {
   2827             mVisibility = visibility;
   2828             return this;
   2829         }
   2830 
   2831         /**
   2832          * Supply a replacement Notification whose contents should be shown in insecure contexts
   2833          * (i.e. atop the secure lockscreen). See {@link #visibility} and {@link #VISIBILITY_PUBLIC}.
   2834          * @param n A replacement notification, presumably with some or all info redacted.
   2835          * @return The same Builder.
   2836          */
   2837         public Builder setPublicVersion(Notification n) {
   2838             mPublicVersion = n;
   2839             return this;
   2840         }
   2841 
   2842         /**
   2843          * Apply an extender to this notification builder. Extenders may be used to add
   2844          * metadata or change options on this builder.
   2845          */
   2846         public Builder extend(Extender extender) {
   2847             extender.extend(this);
   2848             return this;
   2849         }
   2850 
   2851         /**
   2852          * @hide
   2853          */
   2854         public void setFlag(int mask, boolean value) {
   2855             if (value) {
   2856                 mFlags |= mask;
   2857             } else {
   2858                 mFlags &= ~mask;
   2859             }
   2860         }
   2861 
   2862         /**
   2863          * Sets {@link Notification#color}.
   2864          *
   2865          * @param argb The accent color to use
   2866          *
   2867          * @return The same Builder.
   2868          */
   2869         public Builder setColor(@ColorInt int argb) {
   2870             mColor = argb;
   2871             return this;
   2872         }
   2873 
   2874         private Drawable getProfileBadgeDrawable() {
   2875             // Note: This assumes that the current user can read the profile badge of the
   2876             // originating user.
   2877             return mContext.getPackageManager().getUserBadgeForDensity(
   2878                     new UserHandle(mOriginatingUserId), 0);
   2879         }
   2880 
   2881         private Bitmap getProfileBadge() {
   2882             Drawable badge = getProfileBadgeDrawable();
   2883             if (badge == null) {
   2884                 return null;
   2885             }
   2886             final int size = mContext.getResources().getDimensionPixelSize(
   2887                     R.dimen.notification_badge_size);
   2888             Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
   2889             Canvas canvas = new Canvas(bitmap);
   2890             badge.setBounds(0, 0, size, size);
   2891             badge.draw(canvas);
   2892             return bitmap;
   2893         }
   2894 
   2895         private boolean addProfileBadge(RemoteViews contentView, int resId) {
   2896             Bitmap profileBadge = getProfileBadge();
   2897 
   2898             contentView.setViewVisibility(R.id.profile_badge_large_template, View.GONE);
   2899             contentView.setViewVisibility(R.id.profile_badge_line2, View.GONE);
   2900             contentView.setViewVisibility(R.id.profile_badge_line3, View.GONE);
   2901 
   2902             if (profileBadge != null) {
   2903                 contentView.setImageViewBitmap(resId, profileBadge);
   2904                 contentView.setViewVisibility(resId, View.VISIBLE);
   2905 
   2906                 // Make sure Line 3 is visible. As badge will be here if there
   2907                 // is no text to display.
   2908                 if (resId == R.id.profile_badge_line3) {
   2909                     contentView.setViewVisibility(R.id.line3, View.VISIBLE);
   2910                 }
   2911                 return true;
   2912             }
   2913             return false;
   2914         }
   2915 
   2916         private void shrinkLine3Text(RemoteViews contentView) {
   2917             float subTextSize = mContext.getResources().getDimensionPixelSize(
   2918                     R.dimen.notification_subtext_size);
   2919             contentView.setTextViewTextSize(R.id.text, TypedValue.COMPLEX_UNIT_PX, subTextSize);
   2920         }
   2921 
   2922         private void unshrinkLine3Text(RemoteViews contentView) {
   2923             float regularTextSize = mContext.getResources().getDimensionPixelSize(
   2924                     com.android.internal.R.dimen.notification_text_size);
   2925             contentView.setTextViewTextSize(R.id.text, TypedValue.COMPLEX_UNIT_PX, regularTextSize);
   2926         }
   2927 
   2928         private void resetStandardTemplate(RemoteViews contentView) {
   2929             removeLargeIconBackground(contentView);
   2930             contentView.setViewPadding(R.id.icon, 0, 0, 0, 0);
   2931             contentView.setImageViewResource(R.id.icon, 0);
   2932             contentView.setInt(R.id.icon, "setBackgroundResource", 0);
   2933             contentView.setViewVisibility(R.id.right_icon, View.GONE);
   2934             contentView.setInt(R.id.right_icon, "setBackgroundResource", 0);
   2935             contentView.setImageViewResource(R.id.right_icon, 0);
   2936             contentView.setImageViewResource(R.id.icon, 0);
   2937             contentView.setTextViewText(R.id.title, null);
   2938             contentView.setTextViewText(R.id.text, null);
   2939             unshrinkLine3Text(contentView);
   2940             contentView.setTextViewText(R.id.text2, null);
   2941             contentView.setViewVisibility(R.id.text2, View.GONE);
   2942             contentView.setViewVisibility(R.id.info, View.GONE);
   2943             contentView.setViewVisibility(R.id.time, View.GONE);
   2944             contentView.setViewVisibility(R.id.line3, View.GONE);
   2945             contentView.setViewVisibility(R.id.overflow_divider, View.GONE);
   2946             contentView.setViewVisibility(R.id.progress, View.GONE);
   2947             contentView.setViewVisibility(R.id.chronometer, View.GONE);
   2948             contentView.setViewVisibility(R.id.time, View.GONE);
   2949         }
   2950 
   2951         private RemoteViews applyStandardTemplate(int resId) {
   2952             return applyStandardTemplate(resId, true /* hasProgress */);
   2953         }
   2954 
   2955         /**
   2956          * @param hasProgress whether the progress bar should be shown and set
   2957          */
   2958         private RemoteViews applyStandardTemplate(int resId, boolean hasProgress) {
   2959             RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
   2960 
   2961             resetStandardTemplate(contentView);
   2962 
   2963             boolean showLine3 = false;
   2964             boolean showLine2 = false;
   2965             boolean contentTextInLine2 = false;
   2966 
   2967             if (mLargeIcon != null) {
   2968                 contentView.setImageViewIcon(R.id.icon, mLargeIcon);
   2969                 processLargeLegacyIcon(mLargeIcon, contentView);
   2970                 contentView.setImageViewIcon(R.id.right_icon, mSmallIcon);
   2971                 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
   2972                 processSmallRightIcon(mSmallIcon, contentView);
   2973             } else { // small icon at left
   2974                 contentView.setImageViewIcon(R.id.icon, mSmallIcon);
   2975                 contentView.setViewVisibility(R.id.icon, View.VISIBLE);
   2976                 processSmallIconAsLarge(mSmallIcon, contentView);
   2977             }
   2978             if (mContentTitle != null) {
   2979                 contentView.setTextViewText(R.id.title, processLegacyText(mContentTitle));
   2980             }
   2981             if (mContentText != null) {
   2982                 contentView.setTextViewText(R.id.text, processLegacyText(mContentText));
   2983                 showLine3 = true;
   2984             }
   2985             if (mContentInfo != null) {
   2986                 contentView.setTextViewText(R.id.info, processLegacyText(mContentInfo));
   2987                 contentView.setViewVisibility(R.id.info, View.VISIBLE);
   2988                 showLine3 = true;
   2989             } else if (mNumber > 0) {
   2990                 final int tooBig = mContext.getResources().getInteger(
   2991                         R.integer.status_bar_notification_info_maxnum);
   2992                 if (mNumber > tooBig) {
   2993                     contentView.setTextViewText(R.id.info, processLegacyText(
   2994                             mContext.getResources().getString(
   2995                                     R.string.status_bar_notification_info_overflow)));
   2996                 } else {
   2997                     NumberFormat f = NumberFormat.getIntegerInstance();
   2998                     contentView.setTextViewText(R.id.info, processLegacyText(f.format(mNumber)));
   2999                 }
   3000                 contentView.setViewVisibility(R.id.info, View.VISIBLE);
   3001                 showLine3 = true;
   3002             } else {
   3003                 contentView.setViewVisibility(R.id.info, View.GONE);
   3004             }
   3005 
   3006             // Need to show three lines?
   3007             if (mSubText != null) {
   3008                 contentView.setTextViewText(R.id.text, processLegacyText(mSubText));
   3009                 if (mContentText != null) {
   3010                     contentView.setTextViewText(R.id.text2, processLegacyText(mContentText));
   3011                     contentView.setViewVisibility(R.id.text2, View.VISIBLE);
   3012                     showLine2 = true;
   3013                     contentTextInLine2 = true;
   3014                 } else {
   3015                     contentView.setViewVisibility(R.id.text2, View.GONE);
   3016                 }
   3017             } else {
   3018                 contentView.setViewVisibility(R.id.text2, View.GONE);
   3019                 if (hasProgress && (mProgressMax != 0 || mProgressIndeterminate)) {
   3020                     contentView.setViewVisibility(R.id.progress, View.VISIBLE);
   3021                     contentView.setProgressBar(
   3022                             R.id.progress, mProgressMax, mProgress, mProgressIndeterminate);
   3023                     contentView.setProgressBackgroundTintList(
   3024                             R.id.progress, ColorStateList.valueOf(mContext.getColor(
   3025                                     R.color.notification_progress_background_color)));
   3026                     if (mColor != COLOR_DEFAULT) {
   3027                         ColorStateList colorStateList = ColorStateList.valueOf(mColor);
   3028                         contentView.setProgressTintList(R.id.progress, colorStateList);
   3029                         contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList);
   3030                     }
   3031                     showLine2 = true;
   3032                 } else {
   3033                     contentView.setViewVisibility(R.id.progress, View.GONE);
   3034                 }
   3035             }
   3036             if (showLine2) {
   3037 
   3038                 // need to shrink all the type to make sure everything fits
   3039                 shrinkLine3Text(contentView);
   3040             }
   3041 
   3042             if (showsTimeOrChronometer()) {
   3043                 if (mUseChronometer) {
   3044                     contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
   3045                     contentView.setLong(R.id.chronometer, "setBase",
   3046                             mWhen + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
   3047                     contentView.setBoolean(R.id.chronometer, "setStarted", true);
   3048                 } else {
   3049                     contentView.setViewVisibility(R.id.time, View.VISIBLE);
   3050                     contentView.setLong(R.id.time, "setTime", mWhen);
   3051                 }
   3052             }
   3053 
   3054             // Adjust padding depending on line count and font size.
   3055             contentView.setViewPadding(R.id.line1, 0, calculateTopPadding(mContext,
   3056                     mHasThreeLines, mContext.getResources().getConfiguration().fontScale),
   3057                     0, 0);
   3058 
   3059             // We want to add badge to first line of text.
   3060             boolean addedBadge = addProfileBadge(contentView,
   3061                     contentTextInLine2 ? R.id.profile_badge_line2 : R.id.profile_badge_line3);
   3062             // If we added the badge to line 3 then we should show line 3.
   3063             if (addedBadge && !contentTextInLine2) {
   3064                 showLine3 = true;
   3065             }
   3066 
   3067             // Note getStandardView may hide line 3 again.
   3068             contentView.setViewVisibility(R.id.line3, showLine3 ? View.VISIBLE : View.GONE);
   3069             contentView.setViewVisibility(R.id.overflow_divider, showLine3 ? View.VISIBLE : View.GONE);
   3070             return contentView;
   3071         }
   3072 
   3073         /**
   3074          * @return true if the built notification will show the time or the chronometer; false
   3075          *         otherwise
   3076          */
   3077         private boolean showsTimeOrChronometer() {
   3078             return mWhen != 0 && mShowWhen;
   3079         }
   3080 
   3081         /**
   3082          * Logic to find out whether the notification is going to have three lines in the contracted
   3083          * layout. This is used to adjust the top padding.
   3084          *
   3085          * @return true if the notification is going to have three lines; false if the notification
   3086          *         is going to have one or two lines
   3087          */
   3088         private boolean hasThreeLines() {
   3089             boolean contentTextInLine2 = mSubText != null && mContentText != null;
   3090             boolean hasProgress = mStyle == null || mStyle.hasProgress();
   3091             // If we have content text in line 2, badge goes into line 2, or line 3 otherwise
   3092             boolean badgeInLine3 = getProfileBadgeDrawable() != null && !contentTextInLine2;
   3093             boolean hasLine3 = mContentText != null || mContentInfo != null || mNumber > 0
   3094                     || badgeInLine3;
   3095             boolean hasLine2 = (mSubText != null && mContentText != null) ||
   3096                     (hasProgress && mSubText == null
   3097                             && (mProgressMax != 0 || mProgressIndeterminate));
   3098             return hasLine2 && hasLine3;
   3099         }
   3100 
   3101         /**
   3102          * @hide
   3103          */
   3104         public static int calculateTopPadding(Context ctx, boolean hasThreeLines,
   3105                 float fontScale) {
   3106             int padding = ctx.getResources().getDimensionPixelSize(hasThreeLines
   3107                     ? R.dimen.notification_top_pad_narrow
   3108                     : R.dimen.notification_top_pad);
   3109             int largePadding = ctx.getResources().getDimensionPixelSize(hasThreeLines
   3110                     ? R.dimen.notification_top_pad_large_text_narrow
   3111                     : R.dimen.notification_top_pad_large_text);
   3112             float largeFactor = (MathUtils.constrain(fontScale, 1.0f, LARGE_TEXT_SCALE) - 1f)
   3113                     / (LARGE_TEXT_SCALE - 1f);
   3114 
   3115             // Linearly interpolate the padding between large and normal with the font scale ranging
   3116             // from 1f to LARGE_TEXT_SCALE
   3117             return Math.round((1 - largeFactor) * padding + largeFactor * largePadding);
   3118         }
   3119 
   3120         private void resetStandardTemplateWithActions(RemoteViews big) {
   3121             big.setViewVisibility(R.id.actions, View.GONE);
   3122             big.setViewVisibility(R.id.action_divider, View.GONE);
   3123             big.removeAllViews(R.id.actions);
   3124         }
   3125 
   3126         private RemoteViews applyStandardTemplateWithActions(int layoutId) {
   3127             RemoteViews big = applyStandardTemplate(layoutId);
   3128 
   3129             resetStandardTemplateWithActions(big);
   3130 
   3131             int N = mActions.size();
   3132             if (N > 0) {
   3133                 big.setViewVisibility(R.id.actions, View.VISIBLE);
   3134                 big.setViewVisibility(R.id.action_divider, View.VISIBLE);
   3135                 if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS;
   3136                 for (int i=0; i<N; i++) {
   3137                     final RemoteViews button = generateActionButton(mActions.get(i));
   3138                     big.addView(R.id.actions, button);
   3139                 }
   3140             }
   3141             return big;
   3142         }
   3143 
   3144         private RemoteViews makeContentView() {
   3145             if (mContentView != null) {
   3146                 return mContentView;
   3147             } else {
   3148                 return applyStandardTemplate(getBaseLayoutResource());
   3149             }
   3150         }
   3151 
   3152         private RemoteViews makeTickerView() {
   3153             if (mTickerView != null) {
   3154                 return mTickerView;
   3155             }
   3156             return null; // tickers are not created by default anymore
   3157         }
   3158 
   3159         private RemoteViews makeBigContentView() {
   3160             if (mActions.size() == 0) return null;
   3161 
   3162             return applyStandardTemplateWithActions(getBigBaseLayoutResource());
   3163         }
   3164 
   3165         private RemoteViews makeHeadsUpContentView() {
   3166             if (mActions.size() == 0) return null;
   3167 
   3168             return applyStandardTemplateWithActions(getBigBaseLayoutResource());
   3169         }
   3170 
   3171 
   3172         private RemoteViews generateActionButton(Action action) {
   3173             final boolean tombstone = (action.actionIntent == null);
   3174             RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(),
   3175                     tombstone ? getActionTombstoneLayoutResource()
   3176                               : getActionLayoutResource());
   3177             final Icon ai = action.getIcon();
   3178             button.setTextViewCompoundDrawablesRelative(R.id.action0, ai, null, null, null);
   3179             button.setTextViewText(R.id.action0, processLegacyText(action.title));
   3180             if (!tombstone) {
   3181                 button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
   3182             }
   3183             button.setContentDescription(R.id.action0, action.title);
   3184             processLegacyAction(action, button);
   3185             return button;
   3186         }
   3187 
   3188         /**
   3189          * @return Whether we are currently building a notification from a legacy (an app that
   3190          *         doesn't create material notifications by itself) app.
   3191          */
   3192         private boolean isLegacy() {
   3193             return mColorUtil != null;
   3194         }
   3195 
   3196         private void processLegacyAction(Action action, RemoteViews button) {
   3197             if (!isLegacy() || mColorUtil.isGrayscaleIcon(mContext, action.getIcon())) {
   3198                 button.setTextViewCompoundDrawablesRelativeColorFilter(R.id.action0, 0,
   3199                         mContext.getColor(R.color.notification_action_color_filter),
   3200                         PorterDuff.Mode.MULTIPLY);
   3201             }
   3202         }
   3203 
   3204         private CharSequence processLegacyText(CharSequence charSequence) {
   3205             if (isLegacy()) {
   3206                 return mColorUtil.invertCharSequenceColors(charSequence);
   3207             } else {
   3208                 return charSequence;
   3209             }
   3210         }
   3211 
   3212         /**
   3213          * Apply any necessary background to smallIcons being used in the largeIcon spot.
   3214          */
   3215         private void processSmallIconAsLarge(Icon largeIcon, RemoteViews contentView) {
   3216             if (!isLegacy()) {
   3217                 contentView.setDrawableParameters(R.id.icon, false, -1,
   3218                         0xFFFFFFFF,
   3219                         PorterDuff.Mode.SRC_ATOP, -1);
   3220                 applyLargeIconBackground(contentView);
   3221             } else {
   3222                 if (mColorUtil.isGrayscaleIcon(mContext, largeIcon)) {
   3223                     applyLargeIconBackground(contentView);
   3224                 }
   3225             }
   3226         }
   3227 
   3228         /**
   3229          * Apply any necessary background to a largeIcon if it's a fake smallIcon (that is,
   3230          * if it's grayscale).
   3231          */
   3232         // TODO: also check bounds, transparency, that sort of thing.
   3233         private void processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView) {
   3234             if (largeIcon != null && isLegacy()
   3235                     && mColorUtil.isGrayscaleIcon(mContext, largeIcon)) {
   3236                 applyLargeIconBackground(contentView);
   3237             } else {
   3238                 removeLargeIconBackground(contentView);
   3239             }
   3240         }
   3241 
   3242         /**
   3243          * Add a colored circle behind the largeIcon slot.
   3244          */
   3245         private void applyLargeIconBackground(RemoteViews contentView) {
   3246             contentView.setInt(R.id.icon, "setBackgroundResource",
   3247                     R.drawable.notification_icon_legacy_bg);
   3248 
   3249             contentView.setDrawableParameters(
   3250                     R.id.icon,
   3251                     true,
   3252                     -1,
   3253                     resolveColor(),
   3254                     PorterDuff.Mode.SRC_ATOP,
   3255                     -1);
   3256 
   3257             int padding = mContext.getResources().getDimensionPixelSize(
   3258                     R.dimen.notification_large_icon_circle_padding);
   3259             contentView.setViewPadding(R.id.icon, padding, padding, padding, padding);
   3260         }
   3261 
   3262         private void removeLargeIconBackground(RemoteViews contentView) {
   3263             contentView.setInt(R.id.icon, "setBackgroundResource", 0);
   3264         }
   3265 
   3266         /**
   3267          * Recolor small icons when used in the R.id.right_icon slot.
   3268          */
   3269         private void processSmallRightIcon(Icon smallIcon, RemoteViews contentView) {
   3270             if (!isLegacy()) {
   3271                 contentView.setDrawableParameters(R.id.right_icon, false, -1,
   3272                         0xFFFFFFFF,
   3273                         PorterDuff.Mode.SRC_ATOP, -1);
   3274             }
   3275             final boolean gray = isLegacy()
   3276                     && smallIcon.getType() == Icon.TYPE_RESOURCE
   3277                     && mColorUtil.isGrayscaleIcon(mContext, smallIcon.getResId());
   3278             if (!isLegacy() || gray) {
   3279                 contentView.setInt(R.id.right_icon,
   3280                         "setBackgroundResource",
   3281                         R.drawable.notification_icon_legacy_bg);
   3282 
   3283                 contentView.setDrawableParameters(
   3284                         R.id.right_icon,
   3285                         true,
   3286                         -1,
   3287                         resolveColor(),
   3288                         PorterDuff.Mode.SRC_ATOP,
   3289                         -1);
   3290             }
   3291         }
   3292 
   3293         private int sanitizeColor() {
   3294             if (mColor != COLOR_DEFAULT) {
   3295                 mColor |= 0xFF000000; // no alpha for custom colors
   3296             }
   3297             return mColor;
   3298         }
   3299 
   3300         private int resolveColor() {
   3301             if (mColor == COLOR_DEFAULT) {
   3302                 return mContext.getColor(R.color.notification_icon_bg_color);
   3303             }
   3304             return mColor;
   3305         }
   3306 
   3307         /**
   3308          * Apply the unstyled operations and return a new {@link Notification} object.
   3309          * @hide
   3310          */
   3311         public Notification buildUnstyled() {
   3312             Notification n = new Notification();
   3313             n.when = mWhen;
   3314             n.mSmallIcon = mSmallIcon;
   3315             if (mSmallIcon != null && mSmallIcon.getType() == Icon.TYPE_RESOURCE) {
   3316                 n.icon = mSmallIcon.getResId();
   3317             }
   3318             n.iconLevel = mSmallIconLevel;
   3319             n.number = mNumber;
   3320 
   3321             n.color = sanitizeColor();
   3322 
   3323             setBuilderContentView(n, makeContentView());
   3324             n.contentIntent = mContentIntent;
   3325             n.deleteIntent = mDeleteIntent;
   3326             n.fullScreenIntent = mFullScreenIntent;
   3327             n.tickerText = mTickerText;
   3328             n.tickerView = makeTickerView();
   3329             n.mLargeIcon = mLargeIcon;
   3330             if (mLargeIcon != null && mLargeIcon.getType() == Icon.TYPE_BITMAP) {
   3331                 n.largeIcon = mLargeIcon.getBitmap();
   3332             }
   3333             n.sound = mSound;
   3334             n.audioStreamType = mAudioStreamType;
   3335             n.audioAttributes = mAudioAttributes;
   3336             n.vibrate = mVibrate;
   3337             n.ledARGB = mLedArgb;
   3338             n.ledOnMS = mLedOnMs;
   3339             n.ledOffMS = mLedOffMs;
   3340             n.defaults = mDefaults;
   3341             n.flags = mFlags;
   3342             setBuilderBigContentView(n, makeBigContentView());
   3343             setBuilderHeadsUpContentView(n, makeHeadsUpContentView());
   3344             if (mLedOnMs != 0 || mLedOffMs != 0) {
   3345                 n.flags |= FLAG_SHOW_LIGHTS;
   3346             }
   3347             if ((mDefaults & DEFAULT_LIGHTS) != 0) {
   3348                 n.flags |= FLAG_SHOW_LIGHTS;
   3349             }
   3350             n.category = mCategory;
   3351             n.mGroupKey = mGroupKey;
   3352             n.mSortKey = mSortKey;
   3353             n.priority = mPriority;
   3354             if (mActions.size() > 0) {
   3355                 n.actions = new Action[mActions.size()];
   3356                 mActions.toArray(n.actions);
   3357             }
   3358             n.visibility = mVisibility;
   3359 
   3360             if (mPublicVersion != null) {
   3361                 n.publicVersion = new Notification();
   3362                 mPublicVersion.cloneInto(n.publicVersion, true);
   3363             }
   3364             // Note: If you're adding new fields, also update restoreFromNotitification().
   3365             return n;
   3366         }
   3367 
   3368         /**
   3369          * Capture, in the provided bundle, semantic information used in the construction of
   3370          * this Notification object.
   3371          * @hide
   3372          */
   3373         public void populateExtras(Bundle extras) {
   3374             // Store original information used in the construction of this object
   3375             extras.putInt(EXTRA_ORIGINATING_USERID, mOriginatingUserId);
   3376             extras.putParcelable(EXTRA_REBUILD_CONTEXT_APPLICATION_INFO,
   3377                     mContext.getApplicationInfo());
   3378             extras.putCharSequence(EXTRA_TITLE, mContentTitle);
   3379             extras.putCharSequence(EXTRA_TEXT, mContentText);
   3380             extras.putCharSequence(EXTRA_SUB_TEXT, mSubText);
   3381             extras.putCharSequence(EXTRA_INFO_TEXT, mContentInfo);
   3382             extras.putParcelable(EXTRA_SMALL_ICON, mSmallIcon);
   3383             extras.putInt(EXTRA_PROGRESS, mProgress);
   3384             extras.putInt(EXTRA_PROGRESS_MAX, mProgressMax);
   3385             extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, mProgressIndeterminate);
   3386             extras.putBoolean(EXTRA_SHOW_CHRONOMETER, mUseChronometer);
   3387             extras.putBoolean(EXTRA_SHOW_WHEN, mShowWhen);
   3388             if (mLargeIcon != null) {
   3389                 extras.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
   3390             }
   3391             if (!mPeople.isEmpty()) {
   3392                 extras.putStringArray(EXTRA_PEOPLE, mPeople.toArray(new String[mPeople.size()]));
   3393             }
   3394             // NOTE: If you're adding new extras also update restoreFromNotification().
   3395         }
   3396 
   3397 
   3398         /**
   3399          * @hide
   3400          */
   3401         public static void stripForDelivery(Notification n) {
   3402             if (!STRIP_AND_REBUILD) {
   3403                 return;
   3404             }
   3405 
   3406             String templateClass = n.extras.getString(EXTRA_TEMPLATE);
   3407             // Only strip views for known Styles because we won't know how to
   3408             // re-create them otherwise.
   3409             boolean stripViews = TextUtils.isEmpty(templateClass) ||
   3410                     getNotificationStyleClass(templateClass) != null;
   3411 
   3412             boolean isStripped = false;
   3413 
   3414             if (n.largeIcon != null && n.extras.containsKey(EXTRA_LARGE_ICON)) {
   3415                 // TODO: Would like to check for equality here, but if the notification
   3416                 // has been cloned, we can't.
   3417                 n.largeIcon = null;
   3418                 n.extras.putBoolean(EXTRA_REBUILD_LARGE_ICON, true);
   3419                 isStripped = true;
   3420             }
   3421             // Get rid of unmodified BuilderRemoteViews.
   3422 
   3423             if (stripViews &&
   3424                     n.contentView instanceof BuilderRemoteViews &&
   3425                     n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) ==
   3426                             n.contentView.getSequenceNumber()) {
   3427                 n.contentView = null;
   3428                 n.extras.putBoolean(EXTRA_REBUILD_CONTENT_VIEW, true);
   3429                 n.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT);
   3430                 isStripped = true;
   3431             }
   3432             if (stripViews &&
   3433                     n.bigContentView instanceof BuilderRemoteViews &&
   3434                     n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) ==
   3435                             n.bigContentView.getSequenceNumber()) {
   3436                 n.bigContentView = null;
   3437                 n.extras.putBoolean(EXTRA_REBUILD_BIG_CONTENT_VIEW, true);
   3438                 n.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT);
   3439                 isStripped = true;
   3440             }
   3441             if (stripViews &&
   3442                     n.headsUpContentView instanceof BuilderRemoteViews &&
   3443                     n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) ==
   3444                             n.headsUpContentView.getSequenceNumber()) {
   3445                 n.headsUpContentView = null;
   3446                 n.extras.putBoolean(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW, true);
   3447                 n.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT);
   3448                 isStripped = true;
   3449             }
   3450 
   3451             if (isStripped) {
   3452                 n.extras.putBoolean(EXTRA_NEEDS_REBUILD, true);
   3453             }
   3454         }
   3455 
   3456         /**
   3457          * @hide
   3458          */
   3459         public static Notification rebuild(Context context, Notification n) {
   3460             Bundle extras = n.extras;
   3461             if (!extras.getBoolean(EXTRA_NEEDS_REBUILD)) return n;
   3462             extras.remove(EXTRA_NEEDS_REBUILD);
   3463 
   3464             // Re-create notification context so we can access app resources.
   3465             ApplicationInfo applicationInfo = extras.getParcelable(
   3466                     EXTRA_REBUILD_CONTEXT_APPLICATION_INFO);
   3467             Context builderContext;
   3468             try {
   3469                 builderContext = context.createApplicationContext(applicationInfo,
   3470                         Context.CONTEXT_RESTRICTED);
   3471             } catch (NameNotFoundException e) {
   3472                 Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found");
   3473                 builderContext = context;  // try with our context
   3474             }
   3475 
   3476             Builder b = new Builder(builderContext, n);
   3477             return b.rebuild();
   3478         }
   3479 
   3480         /**
   3481          * Rebuilds the notification passed in to the rebuild-constructor
   3482          * {@link #Builder(Context, Notification)}.
   3483          *
   3484          * <p>
   3485          * Throws IllegalStateException when invoked on a Builder that isn't in rebuild mode.
   3486          *
   3487          * @hide
   3488          */
   3489         private Notification rebuild() {
   3490             if (mRebuildNotification == null) {
   3491                 throw new IllegalStateException("rebuild() only valid when in 'rebuild' mode.");
   3492             }
   3493             mHasThreeLines = hasThreeLines();
   3494 
   3495             Bundle extras = mRebuildNotification.extras;
   3496 
   3497             if (extras.getBoolean(EXTRA_REBUILD_LARGE_ICON)) {
   3498                 mRebuildNotification.largeIcon = extras.getParcelable(EXTRA_LARGE_ICON);
   3499             }
   3500             extras.remove(EXTRA_REBUILD_LARGE_ICON);
   3501 
   3502             if (extras.getBoolean(EXTRA_REBUILD_CONTENT_VIEW)) {
   3503                 setBuilderContentView(mRebuildNotification, makeContentView());
   3504                 if (mStyle != null) {
   3505                     mStyle.populateContentView(mRebuildNotification);
   3506                 }
   3507             }
   3508             extras.remove(EXTRA_REBUILD_CONTENT_VIEW);
   3509 
   3510             if (extras.getBoolean(EXTRA_REBUILD_BIG_CONTENT_VIEW)) {
   3511                 setBuilderBigContentView(mRebuildNotification, makeBigContentView());
   3512                 if (mStyle != null) {
   3513                     mStyle.populateBigContentView(mRebuildNotification);
   3514                 }
   3515             }
   3516             extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW);
   3517 
   3518             if (extras.getBoolean(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW)) {
   3519                 setBuilderHeadsUpContentView(mRebuildNotification, makeHeadsUpContentView());
   3520                 if (mStyle != null) {
   3521                     mStyle.populateHeadsUpContentView(mRebuildNotification);
   3522                 }
   3523             }
   3524             extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW);
   3525 
   3526             mHasThreeLines = false;
   3527             return mRebuildNotification;
   3528         }
   3529 
   3530         private static Class<? extends Style> getNotificationStyleClass(String templateClass) {
   3531             Class<? extends Style>[] classes = new Class[]{
   3532                     BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class};
   3533             for (Class<? extends Style> innerClass : classes) {
   3534                 if (templateClass.equals(innerClass.getName())) {
   3535                     return innerClass;
   3536                 }
   3537             }
   3538             return null;
   3539         }
   3540 
   3541         private void setBuilderContentView(Notification n, RemoteViews contentView) {
   3542             n.contentView = contentView;
   3543             if (contentView instanceof BuilderRemoteViews) {
   3544                 mRebuildBundle.putInt(Builder.EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT,
   3545                         contentView.getSequenceNumber());
   3546             }
   3547         }
   3548 
   3549         private void setBuilderBigContentView(Notification n, RemoteViews bigContentView) {
   3550             n.bigContentView = bigContentView;
   3551             if (bigContentView instanceof BuilderRemoteViews) {
   3552                 mRebuildBundle.putInt(Builder.EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT,
   3553                         bigContentView.getSequenceNumber());
   3554             }
   3555         }
   3556 
   3557         private void setBuilderHeadsUpContentView(Notification n,
   3558                 RemoteViews headsUpContentView) {
   3559             n.headsUpContentView = headsUpContentView;
   3560             if (headsUpContentView instanceof BuilderRemoteViews) {
   3561                 mRebuildBundle.putInt(Builder.EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT,
   3562                         headsUpContentView.getSequenceNumber());
   3563             }
   3564         }
   3565 
   3566         private void restoreFromNotification(Notification n) {
   3567 
   3568             // Notification fields.
   3569             mWhen = n.when;
   3570             mSmallIcon = n.mSmallIcon;
   3571             mSmallIconLevel = n.iconLevel;
   3572             mNumber = n.number;
   3573 
   3574             mColor = n.color;
   3575 
   3576             mContentView = n.contentView;
   3577             mDeleteIntent = n.deleteIntent;
   3578             mFullScreenIntent = n.fullScreenIntent;
   3579             mTickerText = n.tickerText;
   3580             mTickerView = n.tickerView;
   3581             mLargeIcon = n.mLargeIcon;
   3582             mSound = n.sound;
   3583             mAudioStreamType = n.audioStreamType;
   3584             mAudioAttributes = n.audioAttributes;
   3585 
   3586             mVibrate = n.vibrate;
   3587             mLedArgb = n.ledARGB;
   3588             mLedOnMs = n.ledOnMS;
   3589             mLedOffMs = n.ledOffMS;
   3590             mDefaults = n.defaults;
   3591             mFlags = n.flags;
   3592 
   3593             mCategory = n.category;
   3594             mGroupKey = n.mGroupKey;
   3595             mSortKey = n.mSortKey;
   3596             mPriority = n.priority;
   3597             mActions.clear();
   3598             if (n.actions != null) {
   3599                 Collections.addAll(mActions, n.actions);
   3600             }
   3601             mVisibility = n.visibility;
   3602 
   3603             mPublicVersion = n.publicVersion;
   3604 
   3605             // Extras.
   3606             Bundle extras = n.extras;
   3607             mOriginatingUserId = extras.getInt(EXTRA_ORIGINATING_USERID);
   3608             mContentTitle = extras.getCharSequence(EXTRA_TITLE);
   3609             mContentText = extras.getCharSequence(EXTRA_TEXT);
   3610             mSubText = extras.getCharSequence(EXTRA_SUB_TEXT);
   3611             mContentInfo = extras.getCharSequence(EXTRA_INFO_TEXT);
   3612             mProgress = extras.getInt(EXTRA_PROGRESS);
   3613             mProgressMax = extras.getInt(EXTRA_PROGRESS_MAX);
   3614             mProgressIndeterminate = extras.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
   3615             mUseChronometer = extras.getBoolean(EXTRA_SHOW_CHRONOMETER);
   3616             mShowWhen = extras.getBoolean(EXTRA_SHOW_WHEN);
   3617             if (extras.containsKey(EXTRA_LARGE_ICON)) {
   3618                 mLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON);
   3619             }
   3620             if (extras.containsKey(EXTRA_PEOPLE)) {
   3621                 mPeople.clear();
   3622                 Collections.addAll(mPeople, extras.getStringArray(EXTRA_PEOPLE));
   3623             }
   3624         }
   3625 
   3626         /**
   3627          * @deprecated Use {@link #build()} instead.
   3628          */
   3629         @Deprecated
   3630         public Notification getNotification() {
   3631             return build();
   3632         }
   3633 
   3634         /**
   3635          * Combine all of the options that have been set and return a new {@link Notification}
   3636          * object.
   3637          */
   3638         public Notification build() {
   3639             if (mSmallIcon != null) {
   3640                 mSmallIcon.convertToAshmem();
   3641             }
   3642             if (mLargeIcon != null) {
   3643                 mLargeIcon.convertToAshmem();
   3644             }
   3645             mOriginatingUserId = mContext.getUserId();
   3646             mHasThreeLines = hasThreeLines();
   3647 
   3648             Notification n = buildUnstyled();
   3649 
   3650             if (mStyle != null) {
   3651                 mStyle.purgeResources();
   3652                 n = mStyle.buildStyled(n);
   3653             }
   3654 
   3655             if (mExtras != null) {
   3656                 n.extras.putAll(mExtras);
   3657             }
   3658 
   3659             if (mRebuildBundle.size() > 0) {
   3660                 n.extras.putAll(mRebuildBundle);
   3661                 mRebuildBundle.clear();
   3662             }
   3663 
   3664             populateExtras(n.extras);
   3665             if (mStyle != null) {
   3666                 mStyle.addExtras(n.extras);
   3667             }
   3668 
   3669             mHasThreeLines = false;
   3670             return n;
   3671         }
   3672 
   3673         /**
   3674          * Apply this Builder to an existing {@link Notification} object.
   3675          *
   3676          * @hide
   3677          */
   3678         public Notification buildInto(Notification n) {
   3679             build().cloneInto(n, true);
   3680             return n;
   3681         }
   3682 
   3683         private int getBaseLayoutResource() {
   3684             return R.layout.notification_template_material_base;
   3685         }
   3686 
   3687         private int getBigBaseLayoutResource() {
   3688             return R.layout.notification_template_material_big_base;
   3689         }
   3690 
   3691         private int getBigPictureLayoutResource() {
   3692             return R.layout.notification_template_material_big_picture;
   3693         }
   3694 
   3695         private int getBigTextLayoutResource() {
   3696             return R.layout.notification_template_material_big_text;
   3697         }
   3698 
   3699         private int getInboxLayoutResource() {
   3700             return R.layout.notification_template_material_inbox;
   3701         }
   3702 
   3703         private int getActionLayoutResource() {
   3704             return R.layout.notification_material_action;
   3705         }
   3706 
   3707         private int getActionTombstoneLayoutResource() {
   3708             return R.layout.notification_material_action_tombstone;
   3709         }
   3710     }
   3711 
   3712     /**
   3713      * An object that can apply a rich notification style to a {@link Notification.Builder}
   3714      * object.
   3715      */
   3716     public static abstract class Style {
   3717         private CharSequence mBigContentTitle;
   3718 
   3719         /**
   3720          * @hide
   3721          */
   3722         protected CharSequence mSummaryText = null;
   3723 
   3724         /**
   3725          * @hide
   3726          */
   3727         protected boolean mSummaryTextSet = false;
   3728 
   3729         protected Builder mBuilder;
   3730 
   3731         /**
   3732          * Overrides ContentTitle in the big form of the template.
   3733          * This defaults to the value passed to setContentTitle().
   3734          */
   3735         protected void internalSetBigContentTitle(CharSequence title) {
   3736             mBigContentTitle = title;
   3737         }
   3738 
   3739         /**
   3740          * Set the first line of text after the detail section in the big form of the template.
   3741          */
   3742         protected void internalSetSummaryText(CharSequence cs) {
   3743             mSummaryText = cs;
   3744             mSummaryTextSet = true;
   3745         }
   3746 
   3747         public void setBuilder(Builder builder) {
   3748             if (mBuilder != builder) {
   3749                 mBuilder = builder;
   3750                 if (mBuilder != null) {
   3751                     mBuilder.setStyle(this);
   3752                 }
   3753             }
   3754         }
   3755 
   3756         protected void checkBuilder() {
   3757             if (mBuilder == null) {
   3758                 throw new IllegalArgumentException("Style requires a valid Builder object");
   3759             }
   3760         }
   3761 
   3762         protected RemoteViews getStandardView(int layoutId) {
   3763             checkBuilder();
   3764 
   3765             // Nasty.
   3766             CharSequence oldBuilderContentTitle = mBuilder.mContentTitle;
   3767             if (mBigContentTitle != null) {
   3768                 mBuilder.setContentTitle(mBigContentTitle);
   3769             }
   3770 
   3771             RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(layoutId);
   3772 
   3773             mBuilder.mContentTitle = oldBuilderContentTitle;
   3774 
   3775             if (mBigContentTitle != null && mBigContentTitle.equals("")) {
   3776                 contentView.setViewVisibility(R.id.line1, View.GONE);
   3777             } else {
   3778                 contentView.setViewVisibility(R.id.line1, View.VISIBLE);
   3779             }
   3780 
   3781             // The last line defaults to the subtext, but can be replaced by mSummaryText
   3782             final CharSequence overflowText =
   3783                     mSummaryTextSet ? mSummaryText
   3784                                     : mBuilder.mSubText;
   3785             if (overflowText != null) {
   3786                 contentView.setTextViewText(R.id.text, mBuilder.processLegacyText(overflowText));
   3787                 contentView.setViewVisibility(R.id.overflow_divider, View.VISIBLE);
   3788                 contentView.setViewVisibility(R.id.line3, View.VISIBLE);
   3789             } else {
   3790                 // Clear text in case we use the line to show the profile badge.
   3791                 contentView.setTextViewText(R.id.text, "");
   3792                 contentView.setViewVisibility(R.id.overflow_divider, View.GONE);
   3793                 contentView.setViewVisibility(R.id.line3, View.GONE);
   3794             }
   3795 
   3796             return contentView;
   3797         }
   3798 
   3799         /**
   3800          * Changes the padding of the first line such that the big and small content view have the
   3801          * same top padding.
   3802          *
   3803          * @hide
   3804          */
   3805         protected void applyTopPadding(RemoteViews contentView) {
   3806             int topPadding = Builder.calculateTopPadding(mBuilder.mContext,
   3807                     mBuilder.mHasThreeLines,
   3808                     mBuilder.mContext.getResources().getConfiguration().fontScale);
   3809             contentView.setViewPadding(R.id.line1, 0, topPadding, 0, 0);
   3810         }
   3811 
   3812         /**
   3813          * @hide
   3814          */
   3815         public void addExtras(Bundle extras) {
   3816             if (mSummaryTextSet) {
   3817                 extras.putCharSequence(EXTRA_SUMMARY_TEXT, mSummaryText);
   3818             }
   3819             if (mBigContentTitle != null) {
   3820                 extras.putCharSequence(EXTRA_TITLE_BIG, mBigContentTitle);
   3821             }
   3822             extras.putString(EXTRA_TEMPLATE, this.getClass().getName());
   3823         }
   3824 
   3825         /**
   3826          * @hide
   3827          */
   3828         protected void restoreFromExtras(Bundle extras) {
   3829             if (extras.containsKey(EXTRA_SUMMARY_TEXT)) {
   3830                 mSummaryText = extras.getCharSequence(EXTRA_SUMMARY_TEXT);
   3831                 mSummaryTextSet = true;
   3832             }
   3833             if (extras.containsKey(EXTRA_TITLE_BIG)) {
   3834                 mBigContentTitle = extras.getCharSequence(EXTRA_TITLE_BIG);
   3835             }
   3836         }
   3837 
   3838 
   3839         /**
   3840          * @hide
   3841          */
   3842         public Notification buildStyled(Notification wip) {
   3843             populateTickerView(wip);
   3844             populateContentView(wip);
   3845             populateBigContentView(wip);
   3846             populateHeadsUpContentView(wip);
   3847             return wip;
   3848         }
   3849 
   3850         /**
   3851          * @hide
   3852          */
   3853         public void purgeResources() {}
   3854 
   3855         // The following methods are split out so we can re-create notification partially.
   3856         /**
   3857          * @hide
   3858          */
   3859         protected void populateTickerView(Notification wip) {}
   3860         /**
   3861          * @hide
   3862          */
   3863         protected void populateContentView(Notification wip) {}
   3864 
   3865         /**
   3866          * @hide
   3867          */
   3868         protected void populateBigContentView(Notification wip) {}
   3869 
   3870         /**
   3871          * @hide
   3872          */
   3873         protected void populateHeadsUpContentView(Notification wip) {}
   3874 
   3875         /**
   3876          * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is
   3877          * attached to.
   3878          *
   3879          * @return the fully constructed Notification.
   3880          */
   3881         public Notification build() {
   3882             checkBuilder();
   3883             return mBuilder.build();
   3884         }
   3885 
   3886         /**
   3887          * @hide
   3888          * @return true if the style positions the progress bar on the second line; false if the
   3889          *         style hides the progress bar
   3890          */
   3891         protected boolean hasProgress() {
   3892             return true;
   3893         }
   3894     }
   3895 
   3896     /**
   3897      * Helper class for generating large-format notifications that include a large image attachment.
   3898      *
   3899      * Here's how you'd set the <code>BigPictureStyle</code> on a notification:
   3900      * <pre class="prettyprint">
   3901      * Notification notif = new Notification.Builder(mContext)
   3902      *     .setContentTitle(&quot;New photo from &quot; + sender.toString())
   3903      *     .setContentText(subject)
   3904      *     .setSmallIcon(R.drawable.new_post)
   3905      *     .setLargeIcon(aBitmap)
   3906      *     .setStyle(new Notification.BigPictureStyle()
   3907      *         .bigPicture(aBigBitmap))
   3908      *     .build();
   3909      * </pre>
   3910      *
   3911      * @see Notification#bigContentView
   3912      */
   3913     public static class BigPictureStyle extends Style {
   3914         private Bitmap mPicture;
   3915         private Icon mBigLargeIcon;
   3916         private boolean mBigLargeIconSet = false;
   3917 
   3918         public BigPictureStyle() {
   3919         }
   3920 
   3921         public BigPictureStyle(Builder builder) {
   3922             setBuilder(builder);
   3923         }
   3924 
   3925         /**
   3926          * Overrides ContentTitle in the big form of the template.
   3927          * This defaults to the value passed to setContentTitle().
   3928          */
   3929         public BigPictureStyle setBigContentTitle(CharSequence title) {
   3930             internalSetBigContentTitle(safeCharSequence(title));
   3931             return this;
   3932         }
   3933 
   3934         /**
   3935          * Set the first line of text after the detail section in the big form of the template.
   3936          */
   3937         public BigPictureStyle setSummaryText(CharSequence cs) {
   3938             internalSetSummaryText(safeCharSequence(cs));
   3939             return this;
   3940         }
   3941 
   3942         /**
   3943          * Provide the bitmap to be used as the payload for the BigPicture notification.
   3944          */
   3945         public BigPictureStyle bigPicture(Bitmap b) {
   3946             mPicture = b;
   3947             return this;
   3948         }
   3949 
   3950         /**
   3951          * Override the large icon when the big notification is shown.
   3952          */
   3953         public BigPictureStyle bigLargeIcon(Bitmap b) {
   3954             return bigLargeIcon(b != null ? Icon.createWithBitmap(b) : null);
   3955         }
   3956 
   3957         /**
   3958          * Override the large icon when the big notification is shown.
   3959          */
   3960         public BigPictureStyle bigLargeIcon(Icon icon) {
   3961             mBigLargeIconSet = true;
   3962             mBigLargeIcon = icon;
   3963             return this;
   3964         }
   3965 
   3966         /**
   3967          * @hide
   3968          */
   3969         @Override
   3970         public void purgeResources() {
   3971             super.purgeResources();
   3972             if (mPicture != null && mPicture.isMutable()) {
   3973                 mPicture = mPicture.createAshmemBitmap();
   3974             }
   3975             if (mBigLargeIcon != null) {
   3976                 mBigLargeIcon.convertToAshmem();
   3977             }
   3978         }
   3979 
   3980         private RemoteViews makeBigContentView() {
   3981             // Replace mLargeIcon with mBigLargeIcon if mBigLargeIconSet
   3982             // This covers the following cases:
   3983             //   1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides
   3984             //          mLargeIcon
   3985             //   2. !mBigLargeIconSet -> mLargeIcon applies
   3986             Icon oldLargeIcon = null;
   3987             if (mBigLargeIconSet) {
   3988                 oldLargeIcon = mBuilder.mLargeIcon;
   3989                 mBuilder.mLargeIcon = mBigLargeIcon;
   3990             }
   3991 
   3992             RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource());
   3993 
   3994             if (mBigLargeIconSet) {
   3995                 mBuilder.mLargeIcon = oldLargeIcon;
   3996             }
   3997 
   3998             contentView.setImageViewBitmap(R.id.big_picture, mPicture);
   3999 
   4000             applyTopPadding(contentView);
   4001 
   4002             boolean twoTextLines = mBuilder.mSubText != null && mBuilder.mContentText != null;
   4003             mBuilder.addProfileBadge(contentView,
   4004                     twoTextLines ? R.id.profile_badge_line2 : R.id.profile_badge_line3);
   4005             return contentView;
   4006         }
   4007 
   4008         /**
   4009          * @hide
   4010          */
   4011         public void addExtras(Bundle extras) {
   4012             super.addExtras(extras);
   4013 
   4014             if (mBigLargeIconSet) {
   4015                 extras.putParcelable(EXTRA_LARGE_ICON_BIG, mBigLargeIcon);
   4016             }
   4017             extras.putParcelable(EXTRA_PICTURE, mPicture);
   4018         }
   4019 
   4020         /**
   4021          * @hide
   4022          */
   4023         @Override
   4024         protected void restoreFromExtras(Bundle extras) {
   4025             super.restoreFromExtras(extras);
   4026 
   4027             if (extras.containsKey(EXTRA_LARGE_ICON_BIG)) {
   4028                 mBigLargeIconSet = true;
   4029                 mBigLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON_BIG);
   4030             }
   4031             mPicture = extras.getParcelable(EXTRA_PICTURE);
   4032         }
   4033 
   4034         /**
   4035          * @hide
   4036          */
   4037         @Override
   4038         public void populateBigContentView(Notification wip) {
   4039             mBuilder.setBuilderBigContentView(wip, makeBigContentView());
   4040         }
   4041     }
   4042 
   4043     /**
   4044      * Helper class for generating large-format notifications that include a lot of text.
   4045      *
   4046      * Here's how you'd set the <code>BigTextStyle</code> on a notification:
   4047      * <pre class="prettyprint">
   4048      * Notification notif = new Notification.Builder(mContext)
   4049      *     .setContentTitle(&quot;New mail from &quot; + sender.toString())
   4050      *     .setContentText(subject)
   4051      *     .setSmallIcon(R.drawable.new_mail)
   4052      *     .setLargeIcon(aBitmap)
   4053      *     .setStyle(new Notification.BigTextStyle()
   4054      *         .bigText(aVeryLongString))
   4055      *     .build();
   4056      * </pre>
   4057      *
   4058      * @see Notification#bigContentView
   4059      */
   4060     public static class BigTextStyle extends Style {
   4061 
   4062         private static final int MAX_LINES = 13;
   4063         private static final int LINES_CONSUMED_BY_ACTIONS = 3;
   4064         private static final int LINES_CONSUMED_BY_SUMMARY = 2;
   4065 
   4066         private CharSequence mBigText;
   4067 
   4068         public BigTextStyle() {
   4069         }
   4070 
   4071         public BigTextStyle(Builder builder) {
   4072             setBuilder(builder);
   4073         }
   4074 
   4075         /**
   4076          * Overrides ContentTitle in the big form of the template.
   4077          * This defaults to the value passed to setContentTitle().
   4078          */
   4079         public BigTextStyle setBigContentTitle(CharSequence title) {
   4080             internalSetBigContentTitle(safeCharSequence(title));
   4081             return this;
   4082         }
   4083 
   4084         /**
   4085          * Set the first line of text after the detail section in the big form of the template.
   4086          */
   4087         public BigTextStyle setSummaryText(CharSequence cs) {
   4088             internalSetSummaryText(safeCharSequence(cs));
   4089             return this;
   4090         }
   4091 
   4092         /**
   4093          * Provide the longer text to be displayed in the big form of the
   4094          * template in place of the content text.
   4095          */
   4096         public BigTextStyle bigText(CharSequence cs) {
   4097             mBigText = safeCharSequence(cs);
   4098             return this;
   4099         }
   4100 
   4101         /**
   4102          * @hide
   4103          */
   4104         public void addExtras(Bundle extras) {
   4105             super.addExtras(extras);
   4106 
   4107             extras.putCharSequence(EXTRA_BIG_TEXT, mBigText);
   4108         }
   4109 
   4110         /**
   4111          * @hide
   4112          */
   4113         @Override
   4114         protected void restoreFromExtras(Bundle extras) {
   4115             super.restoreFromExtras(extras);
   4116 
   4117             mBigText = extras.getCharSequence(EXTRA_BIG_TEXT);
   4118         }
   4119 
   4120         private RemoteViews makeBigContentView() {
   4121 
   4122             // Nasty
   4123             CharSequence oldBuilderContentText = mBuilder.mContentText;
   4124             mBuilder.mContentText = null;
   4125 
   4126             RemoteViews contentView = getStandardView(mBuilder.getBigTextLayoutResource());
   4127 
   4128             mBuilder.mContentText = oldBuilderContentText;
   4129 
   4130             contentView.setTextViewText(R.id.big_text, mBuilder.processLegacyText(mBigText));
   4131             contentView.setViewVisibility(R.id.big_text, View.VISIBLE);
   4132             contentView.setInt(R.id.big_text, "setMaxLines", calculateMaxLines());
   4133             contentView.setViewVisibility(R.id.text2, View.GONE);
   4134 
   4135             applyTopPadding(contentView);
   4136 
   4137             mBuilder.shrinkLine3Text(contentView);
   4138 
   4139             mBuilder.addProfileBadge(contentView, R.id.profile_badge_large_template);
   4140 
   4141             return contentView;
   4142         }
   4143 
   4144         private int calculateMaxLines() {
   4145             int lineCount = MAX_LINES;
   4146             boolean hasActions = mBuilder.mActions.size() > 0;
   4147             boolean hasSummary = (mSummaryTextSet ? mSummaryText : mBuilder.mSubText) != null;
   4148             if (hasActions) {
   4149                 lineCount -= LINES_CONSUMED_BY_ACTIONS;
   4150             }
   4151             if (hasSummary) {
   4152                 lineCount -= LINES_CONSUMED_BY_SUMMARY;
   4153             }
   4154 
   4155             // If we have less top padding at the top, we can fit less lines.
   4156             if (!mBuilder.mHasThreeLines) {
   4157                 lineCount--;
   4158             }
   4159             return lineCount;
   4160         }
   4161 
   4162         /**
   4163          * @hide
   4164          */
   4165         @Override
   4166         public void populateBigContentView(Notification wip) {
   4167             mBuilder.setBuilderBigContentView(wip, makeBigContentView());
   4168         }
   4169     }
   4170 
   4171     /**
   4172      * Helper class for generating large-format notifications that include a list of (up to 5) strings.
   4173      *
   4174      * Here's how you'd set the <code>InboxStyle</code> on a notification:
   4175      * <pre class="prettyprint">
   4176      * Notification notif = new Notification.Builder(mContext)
   4177      *     .setContentTitle(&quot;5 New mails from &quot; + sender.toString())
   4178      *     .setContentText(subject)
   4179      *     .setSmallIcon(R.drawable.new_mail)
   4180      *     .setLargeIcon(aBitmap)
   4181      *     .setStyle(new Notification.InboxStyle()
   4182      *         .addLine(str1)
   4183      *         .addLine(str2)
   4184      *         .setContentTitle(&quot;&quot;)
   4185      *         .setSummaryText(&quot;+3 more&quot;))
   4186      *     .build();
   4187      * </pre>
   4188      *
   4189      * @see Notification#bigContentView
   4190      */
   4191     public static class InboxStyle extends Style {
   4192         private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(5);
   4193 
   4194         public InboxStyle() {
   4195         }
   4196 
   4197         public InboxStyle(Builder builder) {
   4198             setBuilder(builder);
   4199         }
   4200 
   4201         /**
   4202          * Overrides ContentTitle in the big form of the template.
   4203          * This defaults to the value passed to setContentTitle().
   4204          */
   4205         public InboxStyle setBigContentTitle(CharSequence title) {
   4206             internalSetBigContentTitle(safeCharSequence(title));
   4207             return this;
   4208         }
   4209 
   4210         /**
   4211          * Set the first line of text after the detail section in the big form of the template.
   4212          */
   4213         public InboxStyle setSummaryText(CharSequence cs) {
   4214             internalSetSummaryText(safeCharSequence(cs));
   4215             return this;
   4216         }
   4217 
   4218         /**
   4219          * Append a line to the digest section of the Inbox notification.
   4220          */
   4221         public InboxStyle addLine(CharSequence cs) {
   4222             mTexts.add(safeCharSequence(cs));
   4223             return this;
   4224         }
   4225 
   4226         /**
   4227          * @hide
   4228          */
   4229         public void addExtras(Bundle extras) {
   4230             super.addExtras(extras);
   4231 
   4232             CharSequence[] a = new CharSequence[mTexts.size()];
   4233             extras.putCharSequenceArray(EXTRA_TEXT_LINES, mTexts.toArray(a));
   4234         }
   4235 
   4236         /**
   4237          * @hide
   4238          */
   4239         @Override
   4240         protected void restoreFromExtras(Bundle extras) {
   4241             super.restoreFromExtras(extras);
   4242 
   4243             mTexts.clear();
   4244             if (extras.containsKey(EXTRA_TEXT_LINES)) {
   4245                 Collections.addAll(mTexts, extras.getCharSequenceArray(EXTRA_TEXT_LINES));
   4246             }
   4247         }
   4248 
   4249         private RemoteViews makeBigContentView() {
   4250             // Remove the content text so line3 disappears unless you have a summary
   4251 
   4252             // Nasty
   4253             CharSequence oldBuilderContentText = mBuilder.mContentText;
   4254             mBuilder.mContentText = null;
   4255 
   4256             RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource());
   4257 
   4258             mBuilder.mContentText = oldBuilderContentText;
   4259 
   4260             contentView.setViewVisibility(R.id.text2, View.GONE);
   4261 
   4262             int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
   4263                     R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};
   4264 
   4265             // Make sure all rows are gone in case we reuse a view.
   4266             for (int rowId : rowIds) {
   4267                 contentView.setViewVisibility(rowId, View.GONE);
   4268             }
   4269 
   4270             final boolean largeText =
   4271                     mBuilder.mContext.getResources().getConfiguration().fontScale > 1f;
   4272             final float subTextSize = mBuilder.mContext.getResources().getDimensionPixelSize(
   4273                     R.dimen.notification_subtext_size);
   4274             int i=0;
   4275             while (i < mTexts.size() && i < rowIds.length) {
   4276                 CharSequence str = mTexts.get(i);
   4277                 if (str != null && !str.equals("")) {
   4278                     contentView.setViewVisibility(rowIds[i], View.VISIBLE);
   4279                     contentView.setTextViewText(rowIds[i], mBuilder.processLegacyText(str));
   4280                     if (largeText) {
   4281                         contentView.setTextViewTextSize(rowIds[i], TypedValue.COMPLEX_UNIT_PX,
   4282                                 subTextSize);
   4283                     }
   4284                 }
   4285                 i++;
   4286             }
   4287 
   4288             contentView.setViewVisibility(R.id.inbox_end_pad,
   4289                     mTexts.size() > 0 ? View.VISIBLE : View.GONE);
   4290 
   4291             contentView.setViewVisibility(R.id.inbox_more,
   4292                     mTexts.size() > rowIds.length ? View.VISIBLE : View.GONE);
   4293 
   4294             applyTopPadding(contentView);
   4295 
   4296             mBuilder.shrinkLine3Text(contentView);
   4297 
   4298             mBuilder.addProfileBadge(contentView, R.id.profile_badge_large_template);
   4299 
   4300             return contentView;
   4301         }
   4302 
   4303         /**
   4304          * @hide
   4305          */
   4306         @Override
   4307         public void populateBigContentView(Notification wip) {
   4308             mBuilder.setBuilderBigContentView(wip, makeBigContentView());
   4309         }
   4310     }
   4311 
   4312     /**
   4313      * Notification style for media playback notifications.
   4314      *
   4315      * In the expanded form, {@link Notification#bigContentView}, up to 5
   4316      * {@link Notification.Action}s specified with
   4317      * {@link Notification.Builder#addAction(Action) addAction} will be
   4318      * shown as icon-only pushbuttons, suitable for transport controls. The Bitmap given to
   4319      * {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap) setLargeIcon()} will be
   4320      * treated as album artwork.
   4321      *
   4322      * Unlike the other styles provided here, MediaStyle can also modify the standard-size
   4323      * {@link Notification#contentView}; by providing action indices to
   4324      * {@link #setShowActionsInCompactView(int...)} you can promote up to 3 actions to be displayed
   4325      * in the standard view alongside the usual content.
   4326      *
   4327      * Notifications created with MediaStyle will have their category set to
   4328      * {@link Notification#CATEGORY_TRANSPORT CATEGORY_TRANSPORT} unless you set a different
   4329      * category using {@link Notification.Builder#setCategory(String) setCategory()}.
   4330      *
   4331      * Finally, if you attach a {@link android.media.session.MediaSession.Token} using
   4332      * {@link android.app.Notification.MediaStyle#setMediaSession(MediaSession.Token)},
   4333      * the System UI can identify this as a notification representing an active media session
   4334      * and respond accordingly (by showing album artwork in the lockscreen, for example).
   4335      *
   4336      * To use this style with your Notification, feed it to
   4337      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
   4338      * <pre class="prettyprint">
   4339      * Notification noti = new Notification.Builder()
   4340      *     .setSmallIcon(R.drawable.ic_stat_player)
   4341      *     .setContentTitle(&quot;Track title&quot;)
   4342      *     .setContentText(&quot;Artist - Album&quot;)
   4343      *     .setLargeIcon(albumArtBitmap))
   4344      *     .setStyle(<b>new Notification.MediaStyle()</b>
   4345      *         .setMediaSession(mySession))
   4346      *     .build();
   4347      * </pre>
   4348      *
   4349      * @see Notification#bigContentView
   4350      */
   4351     public static class MediaStyle extends Style {
   4352         static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3;
   4353         static final int MAX_MEDIA_BUTTONS = 5;
   4354 
   4355         private int[] mActionsToShowInCompact = null;
   4356         private MediaSession.Token mToken;
   4357 
   4358         public MediaStyle() {
   4359         }
   4360 
   4361         public MediaStyle(Builder builder) {
   4362             setBuilder(builder);
   4363         }
   4364 
   4365         /**
   4366          * Request up to 3 actions (by index in the order of addition) to be shown in the compact
   4367          * notification view.
   4368          *
   4369          * @param actions the indices of the actions to show in the compact notification view
   4370          */
   4371         public MediaStyle setShowActionsInCompactView(int...actions) {
   4372             mActionsToShowInCompact = actions;
   4373             return this;
   4374         }
   4375 
   4376         /**
   4377          * Attach a {@link android.media.session.MediaSession.Token} to this Notification
   4378          * to provide additional playback information and control to the SystemUI.
   4379          */
   4380         public MediaStyle setMediaSession(MediaSession.Token token) {
   4381             mToken = token;
   4382             return this;
   4383         }
   4384 
   4385         /**
   4386          * @hide
   4387          */
   4388         @Override
   4389         public Notification buildStyled(Notification wip) {
   4390             super.buildStyled(wip);
   4391             if (wip.category == null) {
   4392                 wip.category = Notification.CATEGORY_TRANSPORT;
   4393             }
   4394             return wip;
   4395         }
   4396 
   4397         /**
   4398          * @hide
   4399          */
   4400         @Override
   4401         public void populateContentView(Notification wip) {
   4402             mBuilder.setBuilderContentView(wip, makeMediaContentView());
   4403         }
   4404 
   4405         /**
   4406          * @hide
   4407          */
   4408         @Override
   4409         public void populateBigContentView(Notification wip) {
   4410             mBuilder.setBuilderBigContentView(wip, makeMediaBigContentView());
   4411         }
   4412 
   4413         /** @hide */
   4414         @Override
   4415         public void addExtras(Bundle extras) {
   4416             super.addExtras(extras);
   4417 
   4418             if (mToken != null) {
   4419                 extras.putParcelable(EXTRA_MEDIA_SESSION, mToken);
   4420             }
   4421             if (mActionsToShowInCompact != null) {
   4422                 extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact);
   4423             }
   4424         }
   4425 
   4426         /**
   4427          * @hide
   4428          */
   4429         @Override
   4430         protected void restoreFromExtras(Bundle extras) {
   4431             super.restoreFromExtras(extras);
   4432 
   4433             if (extras.containsKey(EXTRA_MEDIA_SESSION)) {
   4434                 mToken = extras.getParcelable(EXTRA_MEDIA_SESSION);
   4435             }
   4436             if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) {
   4437                 mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS);
   4438             }
   4439         }
   4440 
   4441         private RemoteViews generateMediaActionButton(Action action) {
   4442             final boolean tombstone = (action.actionIntent == null);
   4443             RemoteViews button = new BuilderRemoteViews(mBuilder.mContext.getApplicationInfo(),
   4444                     R.layout.notification_material_media_action);
   4445             button.setImageViewIcon(R.id.action0, action.getIcon());
   4446             button.setDrawableParameters(R.id.action0, false, -1,
   4447                     0xFFFFFFFF,
   4448                     PorterDuff.Mode.SRC_ATOP, -1);
   4449             if (!tombstone) {
   4450                 button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
   4451             }
   4452             button.setContentDescription(R.id.action0, action.title);
   4453             return button;
   4454         }
   4455 
   4456         private RemoteViews makeMediaContentView() {
   4457             RemoteViews view = mBuilder.applyStandardTemplate(
   4458                     R.layout.notification_template_material_media, false /* hasProgress */);
   4459 
   4460             final int numActions = mBuilder.mActions.size();
   4461             final int N = mActionsToShowInCompact == null
   4462                     ? 0
   4463                     : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
   4464             if (N > 0) {
   4465                 view.removeAllViews(com.android.internal.R.id.media_actions);
   4466                 for (int i = 0; i < N; i++) {
   4467                     if (i >= numActions) {
   4468                         throw new IllegalArgumentException(String.format(
   4469                                 "setShowActionsInCompactView: action %d out of bounds (max %d)",
   4470                                 i, numActions - 1));
   4471                     }
   4472 
   4473                     final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]);
   4474                     final RemoteViews button = generateMediaActionButton(action);
   4475                     view.addView(com.android.internal.R.id.media_actions, button);
   4476                 }
   4477             }
   4478             styleText(view);
   4479             hideRightIcon(view);
   4480             return view;
   4481         }
   4482 
   4483         private RemoteViews makeMediaBigContentView() {
   4484             final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
   4485             RemoteViews big = mBuilder.applyStandardTemplate(getBigLayoutResource(actionCount),
   4486                     false /* hasProgress */);
   4487 
   4488             if (actionCount > 0) {
   4489                 big.removeAllViews(com.android.internal.R.id.media_actions);
   4490                 for (int i = 0; i < actionCount; i++) {
   4491                     final RemoteViews button = generateMediaActionButton(mBuilder.mActions.get(i));
   4492                     big.addView(com.android.internal.R.id.media_actions, button);
   4493                 }
   4494             }
   4495             styleText(big);
   4496             hideRightIcon(big);
   4497             applyTopPadding(big);
   4498             big.setViewVisibility(android.R.id.progress, View.GONE);
   4499             return big;
   4500         }
   4501 
   4502         private int getBigLayoutResource(int actionCount) {
   4503             if (actionCount <= 3) {
   4504                 return R.layout.notification_template_material_big_media_narrow;
   4505             } else {
   4506                 return R.layout.notification_template_material_big_media;
   4507             }
   4508         }
   4509 
   4510         private void hideRightIcon(RemoteViews contentView) {
   4511             contentView.setViewVisibility(R.id.right_icon, View.GONE);
   4512         }
   4513 
   4514         /**
   4515          * Applies the special text colors for media notifications to all text views.
   4516          */
   4517         private void styleText(RemoteViews contentView) {
   4518             int primaryColor = mBuilder.mContext.getColor(
   4519                     R.color.notification_media_primary_color);
   4520             int secondaryColor = mBuilder.mContext.getColor(
   4521                     R.color.notification_media_secondary_color);
   4522             contentView.setTextColor(R.id.title, primaryColor);
   4523             if (mBuilder.showsTimeOrChronometer()) {
   4524                 if (mBuilder.mUseChronometer) {
   4525                     contentView.setTextColor(R.id.chronometer, secondaryColor);
   4526                 } else {
   4527                     contentView.setTextColor(R.id.time, secondaryColor);
   4528                 }
   4529             }
   4530             contentView.setTextColor(R.id.text2, secondaryColor);
   4531             contentView.setTextColor(R.id.text, secondaryColor);
   4532             contentView.setTextColor(R.id.info, secondaryColor);
   4533         }
   4534 
   4535         /**
   4536          * @hide
   4537          */
   4538         @Override
   4539         protected boolean hasProgress() {
   4540             return false;
   4541         }
   4542     }
   4543 
   4544     // When adding a new Style subclass here, don't forget to update
   4545     // Builder.getNotificationStyleClass.
   4546 
   4547     /**
   4548      * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
   4549      * metadata or change options on a notification builder.
   4550      */
   4551     public interface Extender {
   4552         /**
   4553          * Apply this extender to a notification builder.
   4554          * @param builder the builder to be modified.
   4555          * @return the build object for chaining.
   4556          */
   4557         public Builder extend(Builder builder);
   4558     }
   4559 
   4560     /**
   4561      * Helper class to add wearable extensions to notifications.
   4562      * <p class="note"> See
   4563      * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications
   4564      * for Android Wear</a> for more information on how to use this class.
   4565      * <p>
   4566      * To create a notification with wearable extensions:
   4567      * <ol>
   4568      *   <li>Create a {@link android.app.Notification.Builder}, setting any desired
   4569      *   properties.
   4570      *   <li>Create a {@link android.app.Notification.WearableExtender}.
   4571      *   <li>Set wearable-specific properties using the
   4572      *   {@code add} and {@code set} methods of {@link android.app.Notification.WearableExtender}.
   4573      *   <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a
   4574      *   notification.
   4575      *   <li>Post the notification to the notification system with the
   4576      *   {@code NotificationManager.notify(...)} methods.
   4577      * </ol>
   4578      *
   4579      * <pre class="prettyprint">
   4580      * Notification notif = new Notification.Builder(mContext)
   4581      *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
   4582      *         .setContentText(subject)
   4583      *         .setSmallIcon(R.drawable.new_mail)
   4584      *         .extend(new Notification.WearableExtender()
   4585      *                 .setContentIcon(R.drawable.new_mail))
   4586      *         .build();
   4587      * NotificationManager notificationManger =
   4588      *         (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
   4589      * notificationManger.notify(0, notif);</pre>
   4590      *
   4591      * <p>Wearable extensions can be accessed on an existing notification by using the
   4592      * {@code WearableExtender(Notification)} constructor,
   4593      * and then using the {@code get} methods to access values.
   4594      *
   4595      * <pre class="prettyprint">
   4596      * Notification.WearableExtender wearableExtender = new Notification.WearableExtender(
   4597      *         notification);
   4598      * List&lt;Notification&gt; pages = wearableExtender.getPages();</pre>
   4599      */
   4600     public static final class WearableExtender implements Extender {
   4601         /**
   4602          * Sentinel value for an action index that is unset.
   4603          */
   4604         public static final int UNSET_ACTION_INDEX = -1;
   4605 
   4606         /**
   4607          * Size value for use with {@link #setCustomSizePreset} to show this notification with
   4608          * default sizing.
   4609          * <p>For custom display notifications created using {@link #setDisplayIntent},
   4610          * the default is {@link #SIZE_LARGE}. All other notifications size automatically based
   4611          * on their content.
   4612          */
   4613         public static final int SIZE_DEFAULT = 0;
   4614 
   4615         /**
   4616          * Size value for use with {@link #setCustomSizePreset} to show this notification
   4617          * with an extra small size.
   4618          * <p>This value is only applicable for custom display notifications created using
   4619          * {@link #setDisplayIntent}.
   4620          */
   4621         public static final int SIZE_XSMALL = 1;
   4622 
   4623         /**
   4624          * Size value for use with {@link #setCustomSizePreset} to show this notification
   4625          * with a small size.
   4626          * <p>This value is only applicable for custom display notifications created using
   4627          * {@link #setDisplayIntent}.
   4628          */
   4629         public static final int SIZE_SMALL = 2;
   4630 
   4631         /**
   4632          * Size value for use with {@link #setCustomSizePreset} to show this notification
   4633          * with a medium size.
   4634          * <p>This value is only applicable for custom display notifications created using
   4635          * {@link #setDisplayIntent}.
   4636          */
   4637         public static final int SIZE_MEDIUM = 3;
   4638 
   4639         /**
   4640          * Size value for use with {@link #setCustomSizePreset} to show this notification
   4641          * with a large size.
   4642          * <p>This value is only applicable for custom display notifications created using
   4643          * {@link #setDisplayIntent}.
   4644          */
   4645         public static final int SIZE_LARGE = 4;
   4646 
   4647         /**
   4648          * Size value for use with {@link #setCustomSizePreset} to show this notification
   4649          * full screen.
   4650          * <p>This value is only applicable for custom display notifications created using
   4651          * {@link #setDisplayIntent}.
   4652          */
   4653         public static final int SIZE_FULL_SCREEN = 5;
   4654 
   4655         /**
   4656          * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a
   4657          * short amount of time when this notification is displayed on the screen. This
   4658          * is the default value.
   4659          */
   4660         public static final int SCREEN_TIMEOUT_SHORT = 0;
   4661 
   4662         /**
   4663          * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on
   4664          * for a longer amount of time when this notification is displayed on the screen.
   4665          */
   4666         public static final int SCREEN_TIMEOUT_LONG = -1;
   4667 
   4668         /** Notification extra which contains wearable extensions */
   4669         private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
   4670 
   4671         // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
   4672         private static final String KEY_ACTIONS = "actions";
   4673         private static final String KEY_FLAGS = "flags";
   4674         private static final String KEY_DISPLAY_INTENT = "displayIntent";
   4675         private static final String KEY_PAGES = "pages";
   4676         private static final String KEY_BACKGROUND = "background";
   4677         private static final String KEY_CONTENT_ICON = "contentIcon";
   4678         private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity";
   4679         private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex";
   4680         private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset";
   4681         private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight";
   4682         private static final String KEY_GRAVITY = "gravity";
   4683         private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout";
   4684 
   4685         // Flags bitwise-ored to mFlags
   4686         private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1;
   4687         private static final int FLAG_HINT_HIDE_ICON = 1 << 1;
   4688         private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2;
   4689         private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3;
   4690         private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4;
   4691 
   4692         // Default value for flags integer
   4693         private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE;
   4694 
   4695         private static final int DEFAULT_CONTENT_ICON_GRAVITY = Gravity.END;
   4696         private static final int DEFAULT_GRAVITY = Gravity.BOTTOM;
   4697 
   4698         private ArrayList<Action> mActions = new ArrayList<Action>();
   4699         private int mFlags = DEFAULT_FLAGS;
   4700         private PendingIntent mDisplayIntent;
   4701         private ArrayList<Notification> mPages = new ArrayList<Notification>();
   4702         private Bitmap mBackground;
   4703         private int mContentIcon;
   4704         private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY;
   4705         private int mContentActionIndex = UNSET_ACTION_INDEX;
   4706         private int mCustomSizePreset = SIZE_DEFAULT;
   4707         private int mCustomContentHeight;
   4708         private int mGravity = DEFAULT_GRAVITY;
   4709         private int mHintScreenTimeout;
   4710 
   4711         /**
   4712          * Create a {@link android.app.Notification.WearableExtender} with default
   4713          * options.
   4714          */
   4715         public WearableExtender() {
   4716         }
   4717 
   4718         public WearableExtender(Notification notif) {
   4719             Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS);
   4720             if (wearableBundle != null) {
   4721                 List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS);
   4722                 if (actions != null) {
   4723                     mActions.addAll(actions);
   4724                 }
   4725 
   4726                 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
   4727                 mDisplayIntent = wearableBundle.getParcelable(KEY_DISPLAY_INTENT);
   4728 
   4729                 Notification[] pages = getNotificationArrayFromBundle(
   4730                         wearableBundle, KEY_PAGES);
   4731                 if (pages != null) {
   4732                     Collections.addAll(mPages, pages);
   4733                 }
   4734 
   4735                 mBackground = wearableBundle.getParcelable(KEY_BACKGROUND);
   4736                 mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON);
   4737                 mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY,
   4738                         DEFAULT_CONTENT_ICON_GRAVITY);
   4739                 mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX,
   4740                         UNSET_ACTION_INDEX);
   4741                 mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET,
   4742                         SIZE_DEFAULT);
   4743                 mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT);
   4744                 mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY);
   4745                 mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT);
   4746             }
   4747         }
   4748 
   4749         /**
   4750          * Apply wearable extensions to a notification that is being built. This is typically
   4751          * called by the {@link android.app.Notification.Builder#extend} method of
   4752          * {@link android.app.Notification.Builder}.
   4753          */
   4754         @Override
   4755         public Notification.Builder extend(Notification.Builder builder) {
   4756             Bundle wearableBundle = new Bundle();
   4757 
   4758             if (!mActions.isEmpty()) {
   4759                 wearableBundle.putParcelableArrayList(KEY_ACTIONS, mActions);
   4760             }
   4761             if (mFlags != DEFAULT_FLAGS) {
   4762                 wearableBundle.putInt(KEY_FLAGS, mFlags);
   4763             }
   4764             if (mDisplayIntent != null) {
   4765                 wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent);
   4766             }
   4767             if (!mPages.isEmpty()) {
   4768                 wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray(
   4769                         new Notification[mPages.size()]));
   4770             }
   4771             if (mBackground != null) {
   4772                 wearableBundle.putParcelable(KEY_BACKGROUND, mBackground);
   4773             }
   4774             if (mContentIcon != 0) {
   4775                 wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon);
   4776             }
   4777             if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) {
   4778                 wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity);
   4779             }
   4780             if (mContentActionIndex != UNSET_ACTION_INDEX) {
   4781                 wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX,
   4782                         mContentActionIndex);
   4783             }
   4784             if (mCustomSizePreset != SIZE_DEFAULT) {
   4785                 wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset);
   4786             }
   4787             if (mCustomContentHeight != 0) {
   4788                 wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight);
   4789             }
   4790             if (mGravity != DEFAULT_GRAVITY) {
   4791                 wearableBundle.putInt(KEY_GRAVITY, mGravity);
   4792             }
   4793             if (mHintScreenTimeout != 0) {
   4794                 wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout);
   4795             }
   4796 
   4797             builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
   4798             return builder;
   4799         }
   4800 
   4801         @Override
   4802         public WearableExtender clone() {
   4803             WearableExtender that = new WearableExtender();
   4804             that.mActions = new ArrayList<Action>(this.mActions);
   4805             that.mFlags = this.mFlags;
   4806             that.mDisplayIntent = this.mDisplayIntent;
   4807             that.mPages = new ArrayList<Notification>(this.mPages);
   4808             that.mBackground = this.mBackground;
   4809             that.mContentIcon = this.mContentIcon;
   4810             that.mContentIconGravity = this.mContentIconGravity;
   4811             that.mContentActionIndex = this.mContentActionIndex;
   4812             that.mCustomSizePreset = this.mCustomSizePreset;
   4813             that.mCustomContentHeight = this.mCustomContentHeight;
   4814             that.mGravity = this.mGravity;
   4815             that.mHintScreenTimeout = this.mHintScreenTimeout;
   4816             return that;
   4817         }
   4818 
   4819         /**
   4820          * Add a wearable action to this notification.
   4821          *
   4822          * <p>When wearable actions are added using this method, the set of actions that
   4823          * show on a wearable device splits from devices that only show actions added
   4824          * using {@link android.app.Notification.Builder#addAction}. This allows for customization
   4825          * of which actions display on different devices.
   4826          *
   4827          * @param action the action to add to this notification
   4828          * @return this object for method chaining
   4829          * @see android.app.Notification.Action
   4830          */
   4831         public WearableExtender addAction(Action action) {
   4832             mActions.add(action);
   4833             return this;
   4834         }
   4835 
   4836         /**
   4837          * Adds wearable actions to this notification.
   4838          *
   4839          * <p>When wearable actions are added using this method, the set of actions that
   4840          * show on a wearable device splits from devices that only show actions added
   4841          * using {@link android.app.Notification.Builder#addAction}. This allows for customization
   4842          * of which actions display on different devices.
   4843          *
   4844          * @param actions the actions to add to this notification
   4845          * @return this object for method chaining
   4846          * @see android.app.Notification.Action
   4847          */
   4848         public WearableExtender addActions(List<Action> actions) {
   4849             mActions.addAll(actions);
   4850             return this;
   4851         }
   4852 
   4853         /**
   4854          * Clear all wearable actions present on this builder.
   4855          * @return this object for method chaining.
   4856          * @see #addAction
   4857          */
   4858         public WearableExtender clearActions() {
   4859             mActions.clear();
   4860             return this;
   4861         }
   4862 
   4863         /**
   4864          * Get the wearable actions present on this notification.
   4865          */
   4866         public List<Action> getActions() {
   4867             return mActions;
   4868         }
   4869 
   4870         /**
   4871          * Set an intent to launch inside of an activity view when displaying
   4872          * this notification. The {@link PendingIntent} provided should be for an activity.
   4873          *
   4874          * <pre class="prettyprint">
   4875          * Intent displayIntent = new Intent(context, MyDisplayActivity.class);
   4876          * PendingIntent displayPendingIntent = PendingIntent.getActivity(context,
   4877          *         0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT);
   4878          * Notification notif = new Notification.Builder(context)
   4879          *         .extend(new Notification.WearableExtender()
   4880          *                 .setDisplayIntent(displayPendingIntent)
   4881          *                 .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM))
   4882          *         .build();</pre>
   4883          *
   4884          * <p>The activity to launch needs to allow embedding, must be exported, and
   4885          * should have an empty task affinity. It is also recommended to use the device
   4886          * default light theme.
   4887          *
   4888          * <p>Example AndroidManifest.xml entry:
   4889          * <pre class="prettyprint">
   4890          * &lt;activity android:name=&quot;com.example.MyDisplayActivity&quot;
   4891          *     android:exported=&quot;true&quot;
   4892          *     android:allowEmbedded=&quot;true&quot;
   4893          *     android:taskAffinity=&quot;&quot;
   4894          *     android:theme=&quot;@android:style/Theme.DeviceDefault.Light&quot; /&gt;</pre>
   4895          *
   4896          * @param intent the {@link PendingIntent} for an activity
   4897          * @return this object for method chaining
   4898          * @see android.app.Notification.WearableExtender#getDisplayIntent
   4899          */
   4900         public WearableExtender setDisplayIntent(PendingIntent intent) {
   4901             mDisplayIntent = intent;
   4902             return this;
   4903         }
   4904 
   4905         /**
   4906          * Get the intent to launch inside of an activity view when displaying this
   4907          * notification. This {@code PendingIntent} should be for an activity.
   4908          */
   4909         public PendingIntent getDisplayIntent() {
   4910             return mDisplayIntent;
   4911         }
   4912 
   4913         /**
   4914          * Add an additional page of content to display with this notification. The current
   4915          * notification forms the first page, and pages added using this function form
   4916          * subsequent pages. This field can be used to separate a notification into multiple
   4917          * sections.
   4918          *
   4919          * @param page the notification to add as another page
   4920          * @return this object for method chaining
   4921          * @see android.app.Notification.WearableExtender#getPages
   4922          */
   4923         public WearableExtender addPage(Notification page) {
   4924             mPages.add(page);
   4925             return this;
   4926         }
   4927 
   4928         /**
   4929          * Add additional pages of content to display with this notification. The current
   4930          * notification forms the first page, and pages added using this function form
   4931          * subsequent pages. This field can be used to separate a notification into multiple
   4932          * sections.
   4933          *
   4934          * @param pages a list of notifications
   4935          * @return this object for method chaining
   4936          * @see android.app.Notification.WearableExtender#getPages
   4937          */
   4938         public WearableExtender addPages(List<Notification> pages) {
   4939             mPages.addAll(pages);
   4940             return this;
   4941         }
   4942 
   4943         /**
   4944          * Clear all additional pages present on this builder.
   4945          * @return this object for method chaining.
   4946          * @see #addPage
   4947          */
   4948         public WearableExtender clearPages() {
   4949             mPages.clear();
   4950             return this;
   4951         }
   4952 
   4953         /**
   4954          * Get the array of additional pages of content for displaying this notification. The
   4955          * current notification forms the first page, and elements within this array form
   4956          * subsequent pages. This field can be used to separate a notification into multiple
   4957          * sections.
   4958          * @return the pages for this notification
   4959          */
   4960         public List<Notification> getPages() {
   4961             return mPages;
   4962         }
   4963 
   4964         /**
   4965          * Set a background image to be displayed behind the notification content.
   4966          * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
   4967          * will work with any notification style.
   4968          *
   4969          * @param background the background bitmap
   4970          * @return this object for method chaining
   4971          * @see android.app.Notification.WearableExtender#getBackground
   4972          */
   4973         public WearableExtender setBackground(Bitmap background) {
   4974             mBackground = background;
   4975             return this;
   4976         }
   4977 
   4978         /**
   4979          * Get a background image to be displayed behind the notification content.
   4980          * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
   4981          * will work with any notification style.
   4982          *
   4983          * @return the background image
   4984          * @see android.app.Notification.WearableExtender#setBackground
   4985          */
   4986         public Bitmap getBackground() {
   4987             return mBackground;
   4988         }
   4989 
   4990         /**
   4991          * Set an icon that goes with the content of this notification.
   4992          */
   4993         public WearableExtender setContentIcon(int icon) {
   4994             mContentIcon = icon;
   4995             return this;
   4996         }
   4997 
   4998         /**
   4999          * Get an icon that goes with the content of this notification.
   5000          */
   5001         public int getContentIcon() {
   5002             return mContentIcon;
   5003         }
   5004 
   5005         /**
   5006          * Set the gravity that the content icon should have within the notification display.
   5007          * Supported values include {@link android.view.Gravity#START} and
   5008          * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
   5009          * @see #setContentIcon
   5010          */
   5011         public WearableExtender setContentIconGravity(int contentIconGravity) {
   5012             mContentIconGravity = contentIconGravity;
   5013             return this;
   5014         }
   5015 
   5016         /**
   5017          * Get the gravity that the content icon should have within the notification display.
   5018          * Supported values include {@link android.view.Gravity#START} and
   5019          * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
   5020          * @see #getContentIcon
   5021          */
   5022         public int getContentIconGravity() {
   5023             return mContentIconGravity;
   5024         }
   5025 
   5026         /**
   5027          * Set an action from this notification's actions to be clickable with the content of
   5028          * this notification. This action will no longer display separately from the
   5029          * notification's content.
   5030          *
   5031          * <p>For notifications with multiple pages, child pages can also have content actions
   5032          * set, although the list of available actions comes from the main notification and not
   5033          * from the child page's notification.
   5034          *
   5035          * @param actionIndex The index of the action to hoist onto the current notification page.
   5036          *                    If wearable actions were added to the main notification, this index
   5037          *                    will apply to that list, otherwise it will apply to the regular
   5038          *                    actions list.
   5039          */
   5040         public WearableExtender setContentAction(int actionIndex) {
   5041             mContentActionIndex = actionIndex;
   5042             return this;
   5043         }
   5044 
   5045         /**
   5046          * Get the index of the notification action, if any, that was specified as being clickable
   5047          * with the content of this notification. This action will no longer display separately
   5048          * from the notification's content.
   5049          *
   5050          * <p>For notifications with multiple pages, child pages can also have content actions
   5051          * set, although the list of available actions comes from the main notification and not
   5052          * from the child page's notification.
   5053          *
   5054          * <p>If wearable specific actions were added to the main notification, this index will
   5055          * apply to that list, otherwise it will apply to the regular actions list.
   5056          *
   5057          * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected.
   5058          */
   5059         public int getContentAction() {
   5060             return mContentActionIndex;
   5061         }
   5062 
   5063         /**
   5064          * Set the gravity that this notification should have within the available viewport space.
   5065          * Supported values include {@link android.view.Gravity#TOP},
   5066          * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
   5067          * The default value is {@link android.view.Gravity#BOTTOM}.
   5068          */
   5069         public WearableExtender setGravity(int gravity) {
   5070             mGravity = gravity;
   5071             return this;
   5072         }
   5073 
   5074         /**
   5075          * Get the gravity that this notification should have within the available viewport space.
   5076          * Supported values include {@link android.view.Gravity#TOP},
   5077          * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
   5078          * The default value is {@link android.view.Gravity#BOTTOM}.
   5079          */
   5080         public int getGravity() {
   5081             return mGravity;
   5082         }
   5083 
   5084         /**
   5085          * Set the custom size preset for the display of this notification out of the available
   5086          * presets found in {@link android.app.Notification.WearableExtender}, e.g.
   5087          * {@link #SIZE_LARGE}.
   5088          * <p>Some custom size presets are only applicable for custom display notifications created
   5089          * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. Check the
   5090          * documentation for the preset in question. See also
   5091          * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}.
   5092          */
   5093         public WearableExtender setCustomSizePreset(int sizePreset) {
   5094             mCustomSizePreset = sizePreset;
   5095             return this;
   5096         }
   5097 
   5098         /**
   5099          * Get the custom size preset for the display of this notification out of the available
   5100          * presets found in {@link android.app.Notification.WearableExtender}, e.g.
   5101          * {@link #SIZE_LARGE}.
   5102          * <p>Some custom size presets are only applicable for custom display notifications created
   5103          * using {@link #setDisplayIntent}. Check the documentation for the preset in question.
   5104          * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}.
   5105          */
   5106         public int getCustomSizePreset() {
   5107             return mCustomSizePreset;
   5108         }
   5109 
   5110         /**
   5111          * Set the custom height in pixels for the display of this notification's content.
   5112          * <p>This option is only available for custom display notifications created
   5113          * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. See also
   5114          * {@link android.app.Notification.WearableExtender#setCustomSizePreset} and
   5115          * {@link #getCustomContentHeight}.
   5116          */
   5117         public WearableExtender setCustomContentHeight(int height) {
   5118             mCustomContentHeight = height;
   5119             return this;
   5120         }
   5121 
   5122         /**
   5123          * Get the custom height in pixels for the display of this notification's content.
   5124          * <p>This option is only available for custom display notifications created
   5125          * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and
   5126          * {@link #setCustomContentHeight}.
   5127          */
   5128         public int getCustomContentHeight() {
   5129             return mCustomContentHeight;
   5130         }
   5131 
   5132         /**
   5133          * Set whether the scrolling position for the contents of this notification should start
   5134          * at the bottom of the contents instead of the top when the contents are too long to
   5135          * display within the screen.  Default is false (start scroll at the top).
   5136          */
   5137         public WearableExtender setStartScrollBottom(boolean startScrollBottom) {
   5138             setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom);
   5139             return this;
   5140         }
   5141 
   5142         /**
   5143          * Get whether the scrolling position for the contents of this notification should start
   5144          * at the bottom of the contents instead of the top when the contents are too long to
   5145          * display within the screen. Default is false (start scroll at the top).
   5146          */
   5147         public boolean getStartScrollBottom() {
   5148             return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0;
   5149         }
   5150 
   5151         /**
   5152          * Set whether the content intent is available when the wearable device is not connected
   5153          * to a companion device.  The user can still trigger this intent when the wearable device
   5154          * is offline, but a visual hint will indicate that the content intent may not be available.
   5155          * Defaults to true.
   5156          */
   5157         public WearableExtender setContentIntentAvailableOffline(
   5158                 boolean contentIntentAvailableOffline) {
   5159             setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline);
   5160             return this;
   5161         }
   5162 
   5163         /**
   5164          * Get whether the content intent is available when the wearable device is not connected
   5165          * to a companion device.  The user can still trigger this intent when the wearable device
   5166          * is offline, but a visual hint will indicate that the content intent may not be available.
   5167          * Defaults to true.
   5168          */
   5169         public boolean getContentIntentAvailableOffline() {
   5170             return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0;
   5171         }
   5172 
   5173         /**
   5174          * Set a hint that this notification's icon should not be displayed.
   5175          * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise.
   5176          * @return this object for method chaining
   5177          */
   5178         public WearableExtender setHintHideIcon(boolean hintHideIcon) {
   5179             setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon);
   5180             return this;
   5181         }
   5182 
   5183         /**
   5184          * Get a hint that this notification's icon should not be displayed.
   5185          * @return {@code true} if this icon should not be displayed, false otherwise.
   5186          * The default value is {@code false} if this was never set.
   5187          */
   5188         public boolean getHintHideIcon() {
   5189             return (mFlags & FLAG_HINT_HIDE_ICON) != 0;
   5190         }
   5191 
   5192         /**
   5193          * Set a visual hint that only the background image of this notification should be
   5194          * displayed, and other semantic content should be hidden. This hint is only applicable
   5195          * to sub-pages added using {@link #addPage}.
   5196          */
   5197         public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) {
   5198             setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly);
   5199             return this;
   5200         }
   5201 
   5202         /**
   5203          * Get a visual hint that only the background image of this notification should be
   5204          * displayed, and other semantic content should be hidden. This hint is only applicable
   5205          * to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}.
   5206          */
   5207         public boolean getHintShowBackgroundOnly() {
   5208             return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0;
   5209         }
   5210 
   5211         /**
   5212          * Set a hint that this notification's background should not be clipped if possible,
   5213          * and should instead be resized to fully display on the screen, retaining the aspect
   5214          * ratio of the image. This can be useful for images like barcodes or qr codes.
   5215          * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible.
   5216          * @return this object for method chaining
   5217          */
   5218         public WearableExtender setHintAvoidBackgroundClipping(
   5219                 boolean hintAvoidBackgroundClipping) {
   5220             setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping);
   5221             return this;
   5222         }
   5223 
   5224         /**
   5225          * Get a hint that this notification's background should not be clipped if possible,
   5226          * and should instead be resized to fully display on the screen, retaining the aspect
   5227          * ratio of the image. This can be useful for images like barcodes or qr codes.
   5228          * @return {@code true} if it's ok if the background is clipped on the screen, false
   5229          * otherwise. The default value is {@code false} if this was never set.
   5230          */
   5231         public boolean getHintAvoidBackgroundClipping() {
   5232             return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0;
   5233         }
   5234 
   5235         /**
   5236          * Set a hint that the screen should remain on for at least this duration when
   5237          * this notification is displayed on the screen.
   5238          * @param timeout The requested screen timeout in milliseconds. Can also be either
   5239          *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
   5240          * @return this object for method chaining
   5241          */
   5242         public WearableExtender setHintScreenTimeout(int timeout) {
   5243             mHintScreenTimeout = timeout;
   5244             return this;
   5245         }
   5246 
   5247         /**
   5248          * Get the duration, in milliseconds, that the screen should remain on for
   5249          * when this notification is displayed.
   5250          * @return the duration in milliseconds if > 0, or either one of the sentinel values
   5251          *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
   5252          */
   5253         public int getHintScreenTimeout() {
   5254             return mHintScreenTimeout;
   5255         }
   5256 
   5257         private void setFlag(int mask, boolean value) {
   5258             if (value) {
   5259                 mFlags |= mask;
   5260             } else {
   5261                 mFlags &= ~mask;
   5262             }
   5263         }
   5264     }
   5265 
   5266     /**
   5267      * <p>Helper class to add Android Auto extensions to notifications. To create a notification
   5268      * with car extensions:
   5269      *
   5270      * <ol>
   5271      *  <li>Create an {@link Notification.Builder}, setting any desired
   5272      *  properties.
   5273      *  <li>Create a {@link CarExtender}.
   5274      *  <li>Set car-specific properties using the {@code add} and {@code set} methods of
   5275      *  {@link CarExtender}.
   5276      *  <li>Call {@link Notification.Builder#extend(Notification.Extender)}
   5277      *  to apply the extensions to a notification.
   5278      * </ol>
   5279      *
   5280      * <pre class="prettyprint">
   5281      * Notification notification = new Notification.Builder(context)
   5282      *         ...
   5283      *         .extend(new CarExtender()
   5284      *                 .set*(...))
   5285      *         .build();
   5286      * </pre>
   5287      *
   5288      * <p>Car extensions can be accessed on an existing notification by using the
   5289      * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods
   5290      * to access values.
   5291      */
   5292     public static final class CarExtender implements Extender {
   5293         private static final String TAG = "CarExtender";
   5294 
   5295         private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS";
   5296         private static final String EXTRA_LARGE_ICON = "large_icon";
   5297         private static final String EXTRA_CONVERSATION = "car_conversation";
   5298         private static final String EXTRA_COLOR = "app_color";
   5299 
   5300         private Bitmap mLargeIcon;
   5301         private UnreadConversation mUnreadConversation;
   5302         private int mColor = Notification.COLOR_DEFAULT;
   5303 
   5304         /**
   5305          * Create a {@link CarExtender} with default options.
   5306          */
   5307         public CarExtender() {
   5308         }
   5309 
   5310         /**
   5311          * Create a {@link CarExtender} from the CarExtender options of an existing Notification.
   5312          *
   5313          * @param notif The notification from which to copy options.
   5314          */
   5315         public CarExtender(Notification notif) {
   5316             Bundle carBundle = notif.extras == null ?
   5317                     null : notif.extras.getBundle(EXTRA_CAR_EXTENDER);
   5318             if (carBundle != null) {
   5319                 mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON);
   5320                 mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT);
   5321 
   5322                 Bundle b = carBundle.getBundle(EXTRA_CONVERSATION);
   5323                 mUnreadConversation = UnreadConversation.getUnreadConversationFromBundle(b);
   5324             }
   5325         }
   5326 
   5327         /**
   5328          * Apply car extensions to a notification that is being built. This is typically called by
   5329          * the {@link Notification.Builder#extend(Notification.Extender)}
   5330          * method of {@link Notification.Builder}.
   5331          */
   5332         @Override
   5333         public Notification.Builder extend(Notification.Builder builder) {
   5334             Bundle carExtensions = new Bundle();
   5335 
   5336             if (mLargeIcon != null) {
   5337                 carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
   5338             }
   5339             if (mColor != Notification.COLOR_DEFAULT) {
   5340                 carExtensions.putInt(EXTRA_COLOR, mColor);
   5341             }
   5342 
   5343             if (mUnreadConversation != null) {
   5344                 Bundle b = mUnreadConversation.getBundleForUnreadConversation();
   5345                 carExtensions.putBundle(EXTRA_CONVERSATION, b);
   5346             }
   5347 
   5348             builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions);
   5349             return builder;
   5350         }
   5351 
   5352         /**
   5353          * Sets the accent color to use when Android Auto presents the notification.
   5354          *
   5355          * Android Auto uses the color set with {@link Notification.Builder#setColor(int)}
   5356          * to accent the displayed notification. However, not all colors are acceptable in an
   5357          * automotive setting. This method can be used to override the color provided in the
   5358          * notification in such a situation.
   5359          */
   5360         public CarExtender setColor(@ColorInt int color) {
   5361             mColor = color;
   5362             return this;
   5363         }
   5364 
   5365         /**
   5366          * Gets the accent color.
   5367          *
   5368          * @see setColor
   5369          */
   5370         @ColorInt
   5371         public int getColor() {
   5372             return mColor;
   5373         }
   5374 
   5375         /**
   5376          * Sets the large icon of the car notification.
   5377          *
   5378          * If no large icon is set in the extender, Android Auto will display the icon
   5379          * specified by {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap)}
   5380          *
   5381          * @param largeIcon The large icon to use in the car notification.
   5382          * @return This object for method chaining.
   5383          */
   5384         public CarExtender setLargeIcon(Bitmap largeIcon) {
   5385             mLargeIcon = largeIcon;
   5386             return this;
   5387         }
   5388 
   5389         /**
   5390          * Gets the large icon used in this car notification, or null if no icon has been set.
   5391          *
   5392          * @return The large icon for the car notification.
   5393          * @see CarExtender#setLargeIcon
   5394          */
   5395         public Bitmap getLargeIcon() {
   5396             return mLargeIcon;
   5397         }
   5398 
   5399         /**
   5400          * Sets the unread conversation in a message notification.
   5401          *
   5402          * @param unreadConversation The unread part of the conversation this notification conveys.
   5403          * @return This object for method chaining.
   5404          */
   5405         public CarExtender setUnreadConversation(UnreadConversation unreadConversation) {
   5406             mUnreadConversation = unreadConversation;
   5407             return this;
   5408         }
   5409 
   5410         /**
   5411          * Returns the unread conversation conveyed by this notification.
   5412          * @see #setUnreadConversation(UnreadConversation)
   5413          */
   5414         public UnreadConversation getUnreadConversation() {
   5415             return mUnreadConversation;
   5416         }
   5417 
   5418         /**
   5419          * A class which holds the unread messages from a conversation.
   5420          */
   5421         public static class UnreadConversation {
   5422             private static final String KEY_AUTHOR = "author";
   5423             private static final String KEY_TEXT = "text";
   5424             private static final String KEY_MESSAGES = "messages";
   5425             private static final String KEY_REMOTE_INPUT = "remote_input";
   5426             private static final String KEY_ON_REPLY = "on_reply";
   5427             private static final String KEY_ON_READ = "on_read";
   5428             private static final String KEY_PARTICIPANTS = "participants";
   5429             private static final String KEY_TIMESTAMP = "timestamp";
   5430 
   5431             private final String[] mMessages;
   5432             private final RemoteInput mRemoteInput;
   5433             private final PendingIntent mReplyPendingIntent;
   5434             private final PendingIntent mReadPendingIntent;
   5435             private final String[] mParticipants;
   5436             private final long mLatestTimestamp;
   5437 
   5438             UnreadConversation(String[] messages, RemoteInput remoteInput,
   5439                     PendingIntent replyPendingIntent, PendingIntent readPendingIntent,
   5440                     String[] participants, long latestTimestamp) {
   5441                 mMessages = messages;
   5442                 mRemoteInput = remoteInput;
   5443                 mReadPendingIntent = readPendingIntent;
   5444                 mReplyPendingIntent = replyPendingIntent;
   5445                 mParticipants = participants;
   5446                 mLatestTimestamp = latestTimestamp;
   5447             }
   5448 
   5449             /**
   5450              * Gets the list of messages conveyed by this notification.
   5451              */
   5452             public String[] getMessages() {
   5453                 return mMessages;
   5454             }
   5455 
   5456             /**
   5457              * Gets the remote input that will be used to convey the response to a message list, or
   5458              * null if no such remote input exists.
   5459              */
   5460             public RemoteInput getRemoteInput() {
   5461                 return mRemoteInput;
   5462             }
   5463 
   5464             /**
   5465              * Gets the pending intent that will be triggered when the user replies to this
   5466              * notification.
   5467              */
   5468             public PendingIntent getReplyPendingIntent() {
   5469                 return mReplyPendingIntent;
   5470             }
   5471 
   5472             /**
   5473              * Gets the pending intent that Android Auto will send after it reads aloud all messages
   5474              * in this object's message list.
   5475              */
   5476             public PendingIntent getReadPendingIntent() {
   5477                 return mReadPendingIntent;
   5478             }
   5479 
   5480             /**
   5481              * Gets the participants in the conversation.
   5482              */
   5483             public String[] getParticipants() {
   5484                 return mParticipants;
   5485             }
   5486 
   5487             /**
   5488              * Gets the firs participant in the conversation.
   5489              */
   5490             public String getParticipant() {
   5491                 return mParticipants.length > 0 ? mParticipants[0] : null;
   5492             }
   5493 
   5494             /**
   5495              * Gets the timestamp of the conversation.
   5496              */
   5497             public long getLatestTimestamp() {
   5498                 return mLatestTimestamp;
   5499             }
   5500 
   5501             Bundle getBundleForUnreadConversation() {
   5502                 Bundle b = new Bundle();
   5503                 String author = null;
   5504                 if (mParticipants != null && mParticipants.length > 1) {
   5505                     author = mParticipants[0];
   5506                 }
   5507                 Parcelable[] messages = new Parcelable[mMessages.length];
   5508                 for (int i = 0; i < messages.length; i++) {
   5509                     Bundle m = new Bundle();
   5510                     m.putString(KEY_TEXT, mMessages[i]);
   5511                     m.putString(KEY_AUTHOR, author);
   5512                     messages[i] = m;
   5513                 }
   5514                 b.putParcelableArray(KEY_MESSAGES, messages);
   5515                 if (mRemoteInput != null) {
   5516                     b.putParcelable(KEY_REMOTE_INPUT, mRemoteInput);
   5517                 }
   5518                 b.putParcelable(KEY_ON_REPLY, mReplyPendingIntent);
   5519                 b.putParcelable(KEY_ON_READ, mReadPendingIntent);
   5520                 b.putStringArray(KEY_PARTICIPANTS, mParticipants);
   5521                 b.putLong(KEY_TIMESTAMP, mLatestTimestamp);
   5522                 return b;
   5523             }
   5524 
   5525             static UnreadConversation getUnreadConversationFromBundle(Bundle b) {
   5526                 if (b == null) {
   5527                     return null;
   5528                 }
   5529                 Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES);
   5530                 String[] messages = null;
   5531                 if (parcelableMessages != null) {
   5532                     String[] tmp = new String[parcelableMessages.length];
   5533                     boolean success = true;
   5534                     for (int i = 0; i < tmp.length; i++) {
   5535                         if (!(parcelableMessages[i] instanceof Bundle)) {
   5536                             success = false;
   5537                             break;
   5538                         }
   5539                         tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT);
   5540                         if (tmp[i] == null) {
   5541                             success = false;
   5542                             break;
   5543                         }
   5544                     }
   5545                     if (success) {
   5546                         messages = tmp;
   5547                     } else {
   5548                         return null;
   5549                     }
   5550                 }
   5551 
   5552                 PendingIntent onRead = b.getParcelable(KEY_ON_READ);
   5553                 PendingIntent onReply = b.getParcelable(KEY_ON_REPLY);
   5554 
   5555                 RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT);
   5556 
   5557                 String[] participants = b.getStringArray(KEY_PARTICIPANTS);
   5558                 if (participants == null || participants.length != 1) {
   5559                     return null;
   5560                 }
   5561 
   5562                 return new UnreadConversation(messages,
   5563                         remoteInput,
   5564                         onReply,
   5565                         onRead,
   5566                         participants, b.getLong(KEY_TIMESTAMP));
   5567             }
   5568         };
   5569 
   5570         /**
   5571          * Builder class for {@link CarExtender.UnreadConversation} objects.
   5572          */
   5573         public static class Builder {
   5574             private final List<String> mMessages = new ArrayList<String>();
   5575             private final String mParticipant;
   5576             private RemoteInput mRemoteInput;
   5577             private PendingIntent mReadPendingIntent;
   5578             private PendingIntent mReplyPendingIntent;
   5579             private long mLatestTimestamp;
   5580 
   5581             /**
   5582              * Constructs a new builder for {@link CarExtender.UnreadConversation}.
   5583              *
   5584              * @param name The name of the other participant in the conversation.
   5585              */
   5586             public Builder(String name) {
   5587                 mParticipant = name;
   5588             }
   5589 
   5590             /**
   5591              * Appends a new unread message to the list of messages for this conversation.
   5592              *
   5593              * The messages should be added from oldest to newest.
   5594              *
   5595              * @param message The text of the new unread message.
   5596              * @return This object for method chaining.
   5597              */
   5598             public Builder addMessage(String message) {
   5599                 mMessages.add(message);
   5600                 return this;
   5601             }
   5602 
   5603             /**
   5604              * Sets the pending intent and remote input which will convey the reply to this
   5605              * notification.
   5606              *
   5607              * @param pendingIntent The pending intent which will be triggered on a reply.
   5608              * @param remoteInput The remote input parcelable which will carry the reply.
   5609              * @return This object for method chaining.
   5610              *
   5611              * @see CarExtender.UnreadConversation#getRemoteInput
   5612              * @see CarExtender.UnreadConversation#getReplyPendingIntent
   5613              */
   5614             public Builder setReplyAction(
   5615                     PendingIntent pendingIntent, RemoteInput remoteInput) {
   5616                 mRemoteInput = remoteInput;
   5617                 mReplyPendingIntent = pendingIntent;
   5618 
   5619                 return this;
   5620             }
   5621 
   5622             /**
   5623              * Sets the pending intent that will be sent once the messages in this notification
   5624              * are read.
   5625              *
   5626              * @param pendingIntent The pending intent to use.
   5627              * @return This object for method chaining.
   5628              */
   5629             public Builder setReadPendingIntent(PendingIntent pendingIntent) {
   5630                 mReadPendingIntent = pendingIntent;
   5631                 return this;
   5632             }
   5633 
   5634             /**
   5635              * Sets the timestamp of the most recent message in an unread conversation.
   5636              *
   5637              * If a messaging notification has been posted by your application and has not
   5638              * yet been cancelled, posting a later notification with the same id and tag
   5639              * but without a newer timestamp may result in Android Auto not displaying a
   5640              * heads up notification for the later notification.
   5641              *
   5642              * @param timestamp The timestamp of the most recent message in the conversation.
   5643              * @return This object for method chaining.
   5644              */
   5645             public Builder setLatestTimestamp(long timestamp) {
   5646                 mLatestTimestamp = timestamp;
   5647                 return this;
   5648             }
   5649 
   5650             /**
   5651              * Builds a new unread conversation object.
   5652              *
   5653              * @return The new unread conversation object.
   5654              */
   5655             public UnreadConversation build() {
   5656                 String[] messages = mMessages.toArray(new String[mMessages.size()]);
   5657                 String[] participants = { mParticipant };
   5658                 return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent,
   5659                         mReadPendingIntent, participants, mLatestTimestamp);
   5660             }
   5661         }
   5662     }
   5663 
   5664     /**
   5665      * Get an array of Notification objects from a parcelable array bundle field.
   5666      * Update the bundle to have a typed array so fetches in the future don't need
   5667      * to do an array copy.
   5668      */
   5669     private static Notification[] getNotificationArrayFromBundle(Bundle bundle, String key) {
   5670         Parcelable[] array = bundle.getParcelableArray(key);
   5671         if (array instanceof Notification[] || array == null) {
   5672             return (Notification[]) array;
   5673         }
   5674         Notification[] typedArray = Arrays.copyOf(array, array.length,
   5675                 Notification[].class);
   5676         bundle.putParcelableArray(key, typedArray);
   5677         return typedArray;
   5678     }
   5679 
   5680     private static class BuilderRemoteViews extends RemoteViews {
   5681         public BuilderRemoteViews(Parcel parcel) {
   5682             super(parcel);
   5683         }
   5684 
   5685         public BuilderRemoteViews(ApplicationInfo appInfo, int layoutId) {
   5686             super(appInfo, layoutId);
   5687         }
   5688 
   5689         @Override
   5690         public BuilderRemoteViews clone() {
   5691             Parcel p = Parcel.obtain();
   5692             writeToParcel(p, 0);
   5693             p.setDataPosition(0);
   5694             BuilderRemoteViews brv = new BuilderRemoteViews(p);
   5695             p.recycle();
   5696             return brv;
   5697         }
   5698     }
   5699 }
   5700