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 */ CarNavExtender()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 */ CarNavExtender(@onNull Notification notification)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 extend(NotificationCompat.Builder builder)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 */ isExtended()170 public boolean isExtended() { 171 return mIsExtended; 172 } 173 174 /** 175 * Static version of {@link #isExtended()}. 176 */ isExtended(Notification notification)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 */ setContentId(long contentId)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 getContentId()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 */ setType(@ype int type)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 getType()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 getType(Notification notification)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 */ setContentTitle(CharSequence contentTitle)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 */ getContentTitle()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 */ setContentText(CharSequence contentText)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 getContentText()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 */ setSubText(CharSequence subText)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 getSubText()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 */ setLargeIcon(Bitmap largeIcon)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 */ getLargeIcon()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 */ setActionIcon(@rawableRes int actionIcon)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 getActionIcon()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 */ setContentIntent(Intent contentIntent)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 */ getContentIntent()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 */ setColor(int color)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 */ getColor()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 */ setNightColor(int nightColor)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 */ getNightColor()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 */ setShowInStream(boolean show)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 */ getShowInStream()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 */ setShowAsHeadsUp(boolean show)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 */ getShowAsHeadsUp()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 */ setIgnoreInStream(boolean ignore)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 */ getIgnoreInStream()451 public boolean getIgnoreInStream() { 452 return mIgnoreInStream; 453 } 454 }