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