Home | History | Annotate | Download | only in ui
      1 /*
      2  * Copyright (C) 2015 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 package android.support.car.ui;
     17 
     18 import android.app.Notification;
     19 import android.content.Intent;
     20 import android.graphics.Bitmap;
     21 import android.os.Bundle;
     22 import android.support.annotation.DrawableRes;
     23 import android.support.annotation.IntDef;
     24 import android.support.annotation.NonNull;
     25 import android.support.annotation.Nullable;
     26 import android.support.v4.app.NotificationCompat;
     27 
     28 import java.lang.annotation.Retention;
     29 import java.lang.annotation.RetentionPolicy;
     30 
     31 /**
     32  * Helper class to add navigation extensions to notifications for use in Android Auto.
     33  * <p>
     34  * To create a notification with navigation extensions:
     35  * <ol>
     36  *   <li>Create a {@link android.app.Notification.Builder}, setting any desired
     37  *   properties.
     38  *   <li>Create a {@link CarNavExtender}.
     39  *   <li>Set car-specific properties using the
     40  *   {@code add} and {@code set} methods of {@link CarNavExtender}.
     41  *   <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a
     42  *   notification.
     43  *   <li>Post the notification to the notification system with the
     44  *   {@code NotificationManager.notify(...)} methods.
     45  * </ol>
     46  *
     47  * <pre class="prettyprint">
     48  * Notification notif = new Notification.Builder(mContext)
     49  *         .setContentTitle("Turn right in 2.0 miles on to US 101-N")
     50  *         .setContentText("43 mins (32 mi) to Home")
     51  *         .setSmallIcon(R.drawable.ic_nav)
     52  *         .extend(new CarNavExtender()
     53  *                 .setContentTitle("US 101-N")
     54  *                 .setContentText("400 ft")
     55  *                 .setSubText("43 mins to Home")
     56  *         .build();
     57  * NotificationManager notificationManger =
     58  *         (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
     59  * notificationManger.notify(0, notif);</pre>
     60  *
     61  * <p>CarNavExtender fields can be accessed on an existing notification by using the
     62  * {@code CarNavExtender(Notification)} constructor,
     63  * and then using the {@code get} methods to access values.
     64  * @hide
     65  */
     66 public class CarNavExtender implements NotificationCompat.Extender {
     67     /** This value must remain unchanged for compatibility. **/
     68     private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS";
     69     private static final String EXTRA_IS_EXTENDED =
     70             "com.google.android.gms.car.support.CarNavExtender.EXTENDED";
     71     private static final String EXTRA_CONTENT_ID = "content_id";
     72     private static final String EXTRA_TYPE = "type";
     73     private static final String EXTRA_SUB_TEXT = "sub_text";
     74     private static final String EXTRA_ACTION_ICON = "action_icon";
     75     /** This value must remain unchanged for compatibility. **/
     76     private static final String EXTRA_CONTENT_INTENT = "content_intent";
     77     /** This value must remain unchanged for compatibility. **/
     78     private static final String EXTRA_COLOR = "app_color";
     79     private static final String EXTRA_NIGHT_COLOR = "app_night_color";
     80     /** This value must remain unchanged for compatibility. **/
     81     private static final String EXTRA_STREAM_VISIBILITY = "stream_visibility";
     82     /** This value must remain unchanged for compatibility. **/
     83     private static final String EXTRA_HEADS_UP_VISIBILITY = "heads_up_visibility";
     84     private static final String EXTRA_IGNORE_IN_STREAM = "ignore_in_stream";
     85 
     86     @IntDef({TYPE_HERO, TYPE_NORMAL})
     87     @Retention(RetentionPolicy.SOURCE)
     88     private @interface Type {}
     89     public static final int TYPE_HERO = 0;
     90     public static final int TYPE_NORMAL = 1;
     91 
     92     private boolean mIsExtended;
     93     /** <code>null</code> if not explicitly set. **/
     94     private Long mContentId;
     95     private int mType = TYPE_NORMAL;
     96     private CharSequence mContentTitle;
     97     private CharSequence mContentText;
     98     private CharSequence mSubText;
     99     private Bitmap mLargeIcon;
    100     private @DrawableRes int mActionIcon;
    101     private Intent mContentIntent;
    102     private int mColor = Notification.COLOR_DEFAULT;
    103     private int mNightColor = Notification.COLOR_DEFAULT;
    104     private boolean mShowInStream = true;
    105     private boolean mShowAsHeadsUp;
    106     private boolean mIgnoreInStream;
    107 
    108     /**
    109      * Create a new CarNavExtender to extend a new notification.
    110      */
    111     public CarNavExtender() {
    112     }
    113 
    114     /**
    115      * Reconstruct a CarNavExtender from an existing notification. Can be used to retrieve values.
    116      *
    117      * @param notification The notification to retrieve the values from.
    118      */
    119     public CarNavExtender(@NonNull Notification notification) {
    120         Bundle extras = NotificationCompat.getExtras(notification);
    121         if (extras == null) {
    122             return;
    123         }
    124         Bundle b = extras.getBundle(EXTRA_CAR_EXTENDER);
    125         if (b == null) {
    126             return;
    127         }
    128 
    129         mIsExtended = b.getBoolean(EXTRA_IS_EXTENDED);
    130         mContentId = (Long) b.getSerializable(EXTRA_CONTENT_ID);
    131         // The ternary guarantees that we return either TYPE_HERO or TYPE_NORMAL.
    132         mType = (b.getInt(EXTRA_TYPE, TYPE_NORMAL) == TYPE_HERO) ? TYPE_HERO : TYPE_NORMAL;
    133         mContentTitle = b.getCharSequence(Notification.EXTRA_TITLE);
    134         mContentText = b.getCharSequence(Notification.EXTRA_TEXT);
    135         mSubText = b.getCharSequence(EXTRA_SUB_TEXT);
    136         mLargeIcon = b.getParcelable(Notification.EXTRA_LARGE_ICON);
    137         mActionIcon = b.getInt(EXTRA_ACTION_ICON);
    138         mContentIntent = b.getParcelable(EXTRA_CONTENT_INTENT);
    139         mColor = b.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT);
    140         mNightColor = b.getInt(EXTRA_NIGHT_COLOR, Notification.COLOR_DEFAULT);
    141         mShowInStream = b.getBoolean(EXTRA_STREAM_VISIBILITY, true);
    142         mShowAsHeadsUp = b.getBoolean(EXTRA_HEADS_UP_VISIBILITY);
    143         mIgnoreInStream = b.getBoolean(EXTRA_IGNORE_IN_STREAM);
    144     }
    145 
    146     @Override
    147     public NotificationCompat.Builder extend(NotificationCompat.Builder builder) {
    148         Bundle b = new Bundle();
    149         b.putBoolean(EXTRA_IS_EXTENDED, true);
    150         b.putSerializable(EXTRA_CONTENT_ID, mContentId);
    151         b.putInt(EXTRA_TYPE, mType);
    152         b.putCharSequence(Notification.EXTRA_TITLE, mContentTitle);
    153         b.putCharSequence(Notification.EXTRA_TEXT, mContentText);
    154         b.putCharSequence(EXTRA_SUB_TEXT, mSubText);
    155         b.putParcelable(Notification.EXTRA_LARGE_ICON, mLargeIcon);
    156         b.putInt(EXTRA_ACTION_ICON, mActionIcon);
    157         b.putParcelable(EXTRA_CONTENT_INTENT, mContentIntent);
    158         b.putInt(EXTRA_COLOR, mColor);
    159         b.putInt(EXTRA_NIGHT_COLOR, mNightColor);
    160         b.putBoolean(EXTRA_STREAM_VISIBILITY, mShowInStream);
    161         b.putBoolean(EXTRA_HEADS_UP_VISIBILITY, mShowAsHeadsUp);
    162         b.putBoolean(EXTRA_IGNORE_IN_STREAM, mIgnoreInStream);
    163         builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, b);
    164         return builder;
    165     }
    166 
    167     /**
    168      * @return <code>true</code> if the notification was extended with {@link CarNavExtender}.
    169      */
    170     public boolean isExtended() {
    171         return mIsExtended;
    172     }
    173 
    174     /**
    175      * Static version of {@link #isExtended()}.
    176      */
    177     public static boolean isExtended(Notification notification) {
    178         Bundle extras = NotificationCompat.getExtras(notification);
    179         if (extras == null) {
    180             return false;
    181         }
    182 
    183         extras = extras.getBundle(EXTRA_CAR_EXTENDER);
    184         return extras != null && extras.getBoolean(EXTRA_IS_EXTENDED);
    185     }
    186 
    187     /**
    188      * Sets an id for the content of this notification. If the content id matches an existing
    189      * notification, any timers that control ranking and heads up notification will remain
    190      * unchanged. However, if it differs from the previous notification with the same id then
    191      * this notification will be treated as a new notification with respect to heads up
    192      * notifications and ranking.
    193      *
    194      * If no content id is specified, it will be treated like a new content id.
    195      *
    196      * A content id will only be compared to the existing notification, not the entire history of
    197      * content ids.
    198      *
    199      * @param contentId The content id that represents this notification.
    200      * @return This object for method chaining.
    201      */
    202     public CarNavExtender setContentId(long contentId) {
    203         mContentId = contentId;
    204         return this;
    205     }
    206 
    207     /**
    208      * @return The content id for this notification or <code>null</code> if it was not specified.
    209      */
    210     @Nullable
    211     public Long getContentId() {
    212         return mContentId;
    213     }
    214 
    215     /**
    216      * @param type The type of notification that this will be displayed as in the Android Auto.
    217      * @return This object for method chaining.
    218      *
    219      * @see #TYPE_NORMAL
    220      * @see #TYPE_HERO
    221      */
    222     public CarNavExtender setType(@Type int type) {
    223         mType = type;
    224         return this;
    225     }
    226 
    227     /**
    228      * @return The type of notification
    229      *
    230      * @see #TYPE_NORMAL
    231      * @see #TYPE_HERO
    232      */
    233     @Type
    234     public int getType() {
    235         return mType;
    236     }
    237 
    238     /**
    239      * @return The type without having to construct an entire {@link CarNavExtender} object.
    240      */
    241     @Type
    242     public static int getType(Notification notification) {
    243         Bundle extras = NotificationCompat.getExtras(notification);
    244         if (extras == null) {
    245             return TYPE_NORMAL;
    246         }
    247         Bundle b = extras.getBundle(EXTRA_CAR_EXTENDER);
    248         if (b == null) {
    249             return TYPE_NORMAL;
    250         }
    251 
    252         // The ternary guarantees that we return either TYPE_HERO or TYPE_NORMAL.
    253         return (b.getInt(EXTRA_TYPE, TYPE_NORMAL) == TYPE_HERO) ? TYPE_HERO : TYPE_NORMAL;
    254     }
    255 
    256     /**
    257      * @param contentTitle Override for the notification's content title.
    258      * @return This object for method chaining.
    259      */
    260     public CarNavExtender setContentTitle(CharSequence contentTitle) {
    261         mContentTitle = contentTitle;
    262         return this;
    263     }
    264 
    265     /**
    266      * @return The content title for the notification if one was explicitly set with
    267      *         {@link #setContentTitle(CharSequence)}.
    268      */
    269     public CharSequence getContentTitle() {
    270         return mContentTitle;
    271     }
    272 
    273     /**
    274      * @param contentText Override for the notification's content text. If set to an empty string,
    275      *                    it will be treated as if there is no context text by the UI.
    276      * @return This object for method chaining.
    277      */
    278     public CarNavExtender setContentText(CharSequence contentText) {
    279         mContentText = contentText;
    280         return this;
    281     }
    282 
    283     /**
    284      * @return The content text for the notification if one was explicitly set with
    285      *         {@link #setContentText(CharSequence)}.
    286      */
    287     @Nullable
    288     public CharSequence getContentText() {
    289         return mContentText;
    290     }
    291 
    292     /**
    293      * @param subText A third text field that will be displayed on hero cards.
    294      * @return This object for method chaining.
    295      */
    296     public CarNavExtender setSubText(CharSequence subText) {
    297         mSubText = subText;
    298         return this;
    299     }
    300 
    301     /**
    302      * @return The secondary content text for the notification or null if it wasn't set.
    303      */
    304     @Nullable
    305     public CharSequence getSubText() {
    306         return mSubText;
    307     }
    308 
    309     /**
    310      * @param largeIcon Override for the notification's large icon.
    311      * @return This object for method chaining.
    312      */
    313     public CarNavExtender setLargeIcon(Bitmap largeIcon) {
    314         mLargeIcon = largeIcon;
    315         return this;
    316     }
    317 
    318     /**
    319      * @return The large icon for the notification if one was explicitly set with
    320      *         {@link #setLargeIcon(android.graphics.Bitmap)}.
    321      */
    322     public Bitmap getLargeIcon() {
    323         return mLargeIcon;
    324     }
    325 
    326     /**
    327      * By default, Android Auto will show a navigation chevron on cards. However, a separate icon
    328      * can be set here to override it.
    329      *
    330      * @param actionIcon The action icon resource id from your package that you would like to
    331      *                   use instead of the navigation chevron.
    332      * @return This object for method chaining.
    333      */
    334     public CarNavExtender setActionIcon(@DrawableRes int actionIcon) {
    335         mActionIcon = actionIcon;
    336         return this;
    337     }
    338 
    339     /**
    340      * @return The overridden action icon or 0 if one wasn't set.
    341      */
    342     @DrawableRes
    343     public int getActionIcon() {
    344         return mActionIcon;
    345     }
    346 
    347     /**
    348      * @param contentIntent The content intent that will be sent using
    349      *                      {@link com.google.android.gms.car.CarActivity#startCarProjectionActivity(android.content.Intent)}
    350      *                      It is STRONGLY suggested that you set a content intent or else the
    351      *                      notification will have no action when tapped.
    352      * @return This object for method chaining.
    353      */
    354     public CarNavExtender setContentIntent(Intent contentIntent) {
    355         mContentIntent = contentIntent;
    356         return this;
    357     }
    358 
    359     /**
    360      * @return The content intent that will be sent using
    361      *         {@link com.google.android.gms.car.CarActivity#startCarProjectionActivity(android.content.Intent)}
    362      */
    363     public Intent getContentIntent() {
    364         return mContentIntent;
    365     }
    366 
    367     /**
    368      * @param color Override for the notification color.
    369      * @return This object for method chaining.
    370      *
    371      * @see android.app.Notification.Builder#setColor(int)
    372      */
    373     public CarNavExtender setColor(int color) {
    374         mColor = color;
    375         return this;
    376     }
    377 
    378     /**
    379      * @return The color specified by the notification or {@link android.app.Notification#COLOR_DEFAULT} if
    380      *         one wasn't explicitly set with {@link #setColor(int)}.
    381      */
    382     public int getColor() {
    383         return mColor;
    384     }
    385 
    386     /**
    387      * @param nightColor Override for the notification color at night.
    388      * @return This object for method chaining.
    389      *
    390      * @see android.app.Notification.Builder#setColor(int)
    391      */
    392     public CarNavExtender setNightColor(int nightColor) {
    393         mNightColor = nightColor;
    394         return this;
    395     }
    396 
    397     /**
    398      * @return The night color specified by the notification or {@link android.app.Notification#COLOR_DEFAULT}
    399      *         if one wasn't explicitly set with {@link #setNightColor(int)}.
    400      */
    401     public int getNightColor() {
    402         return mNightColor;
    403     }
    404 
    405     /**
    406      * @param show Whether or not to show the notification in the stream.
    407      * @return This object for method chaining.
    408      */
    409     public CarNavExtender setShowInStream(boolean show) {
    410         mShowInStream = show;
    411         return this;
    412     }
    413 
    414     /**
    415      * @return Whether or not to show the notification in the stream.
    416      */
    417     public boolean getShowInStream() {
    418         return mShowInStream;
    419     }
    420 
    421     /**
    422      * @param show Whether or not to show the notification as a heads up notification.
    423      * @return This object for method chaining.
    424      */
    425     public CarNavExtender setShowAsHeadsUp(boolean show) {
    426         mShowAsHeadsUp = show;
    427         return this;
    428     }
    429 
    430     /**
    431      * @return Whether or not to show the notification as a heads up notification.
    432      */
    433     public boolean getShowAsHeadsUp() {
    434         return mShowAsHeadsUp;
    435     }
    436 
    437     /**
    438      * @param ignore Whether or not this notification can be shown as a heads-up notification if
    439      *               the user is already on the stream.
    440      * @return This object for method chaining.
    441      */
    442     public CarNavExtender setIgnoreInStream(boolean ignore) {
    443         mIgnoreInStream = ignore;
    444         return this;
    445     }
    446 
    447     /**
    448      * @return Whether or not the stream item can be shown as a heads-up notification if ther user
    449      *         already is on the stream.
    450      */
    451     public boolean getIgnoreInStream() {
    452         return mIgnoreInStream;
    453     }
    454 }