CarNavExtender.java revision e54ac276796c6535558f8444d882adecd19ce2bd
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 */
16package android.support.car.ui;
17
18import android.app.Notification;
19import android.content.Intent;
20import android.graphics.Bitmap;
21import android.os.Bundle;
22import android.support.annotation.DrawableRes;
23import android.support.annotation.IntDef;
24import android.support.annotation.NonNull;
25import android.support.annotation.Nullable;
26import android.support.v4.app.NotificationCompat;
27
28import java.lang.annotation.Retention;
29import 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 */
65public 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     */
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     */
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
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     */
169    public boolean isExtended() {
170        return mIsExtended;
171    }
172
173    /**
174     * Static version of {@link #isExtended()}.
175     */
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     */
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
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     */
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
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
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     */
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     */
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     */
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
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     */
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
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     */
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     */
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     */
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
450    public boolean getIgnoreInStream() {
451        return mIgnoreInStream;
452    }
453}