[go: nahoru, domu]

blob: 05fc5a1da18f0bee117fcbae304543ace2587727 [file] [log] [blame]
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.car.app.notification;
import static java.util.Objects.requireNonNull;
import android.app.Notification;
import android.app.Notification.Action;
import android.app.PendingIntent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.os.Bundle;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import java.util.ArrayList;
import java.util.List;
/**
* Helper class to add car app extensions to notifications.
*
* <p>By default, notifications in a car screen have the properties provided by
* {@link NotificationCompat.Builder}. This helper class provides methods to
* override those properties for the car screen. However, notifications only show up in the car
* screen if it is extended with {@link CarAppExtender}, even if the extender does not override any
* properties. To create a notification with car extensions:
*
* <ol>
* <li>Create a {@link NotificationCompat.Builder}, setting any desired properties.
* <li>Create a {@link CarAppExtender.Builder} with {@link CarAppExtender#builder()}.
* <li>Set car-specific properties using the {@code set} methods of {@link
* CarAppExtender.Builder}.
* <li>Create a {@link CarAppExtender} by calling {@link Builder#build()}.
* <li>Call {@link NotificationCompat.Builder#extend} to apply the extensions to a notification.
* <li>Post the notification to the notification system with the {@code
* NotificationManagerCompat.notify(...)} methods and not the {@code
* NotificationManager.notify(...)} methods.
* </ol>
*
* <pre class="prettyprint">
* Notification notification = new NotificationCompat.Builder(context)
* ...
* .extend(CarAppExtender.builder()
* .set*(...)
* .build())
* .build();
* </pre>
*
* <p>Car extensions can be accessed on an existing notification by using the {@code
* CarAppExtender(Notification)} constructor, and then using the {@code get} methods to access
* values.
*
* <p>The car screen UI is affected by the notification channel importance (Android O and above) or
* notification priority (below Android O) in the following ways:
*
* <ul>
* <li>A heads-up-notification (HUN) will show if the importance is set to
* {@link NotificationManagerCompat#IMPORTANCE_HIGH}, or the priority is set
* to {@link NotificationCompat#PRIORITY_HIGH} or above.
* <li>The notification center icon, which opens a screen with all posted notifications when
* tapped, will show a badge for a new notification if the importance is set to
* {@link NotificationManagerCompat#IMPORTANCE_DEFAULT} or above, or the
* priority is set to {@link NotificationCompat#PRIORITY_DEFAULT} or above.
* <li>The notification entry will show in the notification center for all priority levels.
* </ul>
*
* Calling {@link Builder#setImportance(int)} will override the importance for the notification in
* the car screen.
*
* <p>Calling {@code NotificationCompat.Builder#setOnlyAlertOnce(true)} will alert a high-priority
* notification only once in the HUN. Updating the same notification will not trigger another HUN
* event.
*
* <h4>Navigation</h4>
*
* <p>For a navigation app's turn-by-turn (TBT) notifications, which update the same notification
* frequently with navigation information, the notification UI has a slightly different behavior.
* The app can post a TBT notification by calling {@code
* NotificationCompat.Builder#setOngoing(true)} and {@code
* NotificationCompat.Builder#setCategory(NotificationCompat.CATEGORY_NAVIGATION)}. The car screen
* UI is affected in the following ways:
*
* <ul>
* <li>The same heads-up-notification (HUN) behavior as regular notifications.
* <li>A rail widget at the bottom of the screen will show when the navigation app is in the
* background.
* </ul>
*
* Note that frequent HUNs distract the driver. The recommended practice is to update the TBT
* notification regularly on distance changes, which updates the rail widget, but call {@code
* NotificationCompat.Builder#setOnlyAlertOnce(true)} unless there is a significant navigation turn
* event.
*/
public class CarAppExtender implements NotificationCompat.Extender {
private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS";
private static final String EXTRA_IS_EXTENDED = "android.car.app.EXTENDED";
private static final String EXTRA_CONTENT_TITLE = "content_title";
private static final String EXTRA_CONTENT_TEXT = "content_text";
private static final String EXTRA_SMALL_RES_ID = "small_res_id";
private static final String EXTRA_LARGE_BITMAP = "large_bitmap";
private static final String EXTRA_CONTENT_INTENT = "content_intent";
private static final String EXTRA_DELETE_INTENT = "delete_intent";
private static final String EXTRA_ACTIONS = "actions";
private static final String EXTRA_IMPORTANCE = "importance";
private boolean mIsExtended;
@Nullable
private CharSequence mContentTitle;
@Nullable
private CharSequence mContentText;
private int mSmallIconResId;
@Nullable
private Bitmap mLargeIconBitmap;
@Nullable
private PendingIntent mContentIntent;
@Nullable
private PendingIntent mDeleteIntent;
private ArrayList<Action> mActions;
private int mImportance;
/** Creates a {@link CarAppExtender.Builder}. */
@NonNull
public static Builder builder() {
return new Builder();
}
/**
* Creates a {@link CarAppExtender} from the {@link CarAppExtender} of an existing notification.
*/
public CarAppExtender(@NonNull Notification notification) {
Bundle extras = NotificationCompat.getExtras(notification);
if (extras == null) {
return;
}
Bundle carBundle = extras.getBundle(EXTRA_CAR_EXTENDER);
if (carBundle == null) {
return;
}
mIsExtended = carBundle.getBoolean(EXTRA_IS_EXTENDED);
mContentTitle = carBundle.getCharSequence(EXTRA_CONTENT_TITLE);
mContentText = carBundle.getCharSequence(EXTRA_CONTENT_TEXT);
mSmallIconResId = carBundle.getInt(EXTRA_SMALL_RES_ID);
mLargeIconBitmap = carBundle.getParcelable(EXTRA_LARGE_BITMAP);
mContentIntent = carBundle.getParcelable(EXTRA_CONTENT_INTENT);
mDeleteIntent = carBundle.getParcelable(EXTRA_DELETE_INTENT);
ArrayList<Action> actions = carBundle.getParcelableArrayList(EXTRA_ACTIONS);
this.mActions = actions == null ? new ArrayList<>() : actions;
mImportance =
carBundle.getInt(EXTRA_IMPORTANCE,
NotificationManagerCompat.IMPORTANCE_UNSPECIFIED);
}
private CarAppExtender(Builder builder) {
this.mContentTitle = builder.mContentTitle;
this.mContentText = builder.mContentText;
this.mSmallIconResId = builder.mSmallIconResId;
this.mLargeIconBitmap = builder.mLargeIconBitmap;
this.mContentIntent = builder.mContentIntent;
this.mDeleteIntent = builder.mDeleteIntent;
this.mActions = builder.mActions;
this.mImportance = builder.mImportance;
}
/**
* Applies car extensions to a notification that is being built. This is typically called by
* {@link NotificationCompat.Builder#extend(NotificationCompat.Extender)}.
*
* @throws NullPointerException if {@code builder} is {@code null}.
*/
@NonNull
@Override
public NotificationCompat.Builder extend(@NonNull NotificationCompat.Builder builder) {
requireNonNull(builder);
Bundle carExtensions = new Bundle();
carExtensions.putBoolean(EXTRA_IS_EXTENDED, true);
if (mContentTitle != null) {
carExtensions.putCharSequence(EXTRA_CONTENT_TITLE, mContentTitle);
}
if (mContentText != null) {
carExtensions.putCharSequence(EXTRA_CONTENT_TEXT, mContentText);
}
if (mSmallIconResId != Resources.ID_NULL) {
carExtensions.putInt(EXTRA_SMALL_RES_ID, mSmallIconResId);
}
if (mLargeIconBitmap != null) {
carExtensions.putParcelable(EXTRA_LARGE_BITMAP, mLargeIconBitmap);
}
if (mContentIntent != null) {
carExtensions.putParcelable(EXTRA_CONTENT_INTENT, mContentIntent);
}
if (mDeleteIntent != null) {
carExtensions.putParcelable(EXTRA_DELETE_INTENT, mDeleteIntent);
}
if (!mActions.isEmpty()) {
carExtensions.putParcelableArrayList(EXTRA_ACTIONS, mActions);
}
carExtensions.putInt(EXTRA_IMPORTANCE, mImportance);
builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions);
return builder;
}
/**
* Returns {@code true} if the notification was extended with {@link CarAppExtender}, {@code
* false} otherwise.
*/
public boolean isExtended() {
return mIsExtended;
}
/**
* Returns {@code true} if the notification was extended with {@link CarAppExtender}, {@code
* false} otherwise.
*
* @throws NullPointerException if {@code notification} is {@code null}.
*/
public static boolean isExtended(@NonNull Notification notification) {
Bundle extras = NotificationCompat.getExtras(requireNonNull(notification));
if (extras == null) {
return false;
}
extras = extras.getBundle(EXTRA_CAR_EXTENDER);
return extras != null && extras.getBoolean(EXTRA_IS_EXTENDED);
}
/**
* Gets the content title for the notification.
*
* @see Builder#setContentTitle
*/
@Nullable
public CharSequence getContentTitle() {
return mContentTitle;
}
/**
* Returns the content text of the notification.
*
* @see Builder#setContentText
*/
@Nullable
public CharSequence getContentText() {
return mContentText;
}
/**
* Gets the resource ID of the small icon drawable to use.
*
* @see Builder#setSmallIcon(int)
*/
public int getSmallIconResId() {
return mSmallIconResId;
}
/**
* Gets the large icon bitmap to display in the notification.
*
* @see Builder#setLargeIcon(Bitmap)
*/
@Nullable
public Bitmap getLargeIconBitmap() {
return mLargeIconBitmap;
}
/**
* Gets the {@link PendingIntent} to send when the notification is clicked in the car, or {@code
* null} if one is not set.
*
* @see Builder#setContentIntent(PendingIntent)
*/
@Nullable
public PendingIntent getContentIntent() {
return mContentIntent;
}
/**
* Gets the {@link PendingIntent} to send when the notification is cleared by the user, or
* {@code null} if one is not set.
*
* @see Builder#setDeleteIntent(PendingIntent)
*/
@Nullable
public PendingIntent getDeleteIntent() {
return mDeleteIntent;
}
/**
* Gets the list of {@link Action} present on this car notification.
*
* @see Builder#addAction(int, CharSequence, PendingIntent)
*/
@NonNull
public List<Action> getActions() {
return mActions;
}
/**
* Gets the importance of the notification in the car screen.
*
* @see Builder#setImportance(int)
*/
public int getImportance() {
return mImportance;
}
/** A builder of {@link CarAppExtender}. */
public static final class Builder {
@Nullable
private CharSequence mContentTitle;
@Nullable
private CharSequence mContentText;
private int mSmallIconResId;
@Nullable
private Bitmap mLargeIconBitmap;
@Nullable
private PendingIntent mContentIntent;
@Nullable
private PendingIntent mDeleteIntent;
private final ArrayList<Action> mActions = new ArrayList<>();
private int mImportance = NotificationManagerCompat.IMPORTANCE_UNSPECIFIED;
/**
* Sets the title of the notification in the car screen, or {@code null} to not override the
* notification title.
*
* <p>This will be the most prominently displayed text in the car notification.
*
* <p>This method is equivalent to
* {@link NotificationCompat.Builder#setContentTitle(CharSequence)} for the car
* screen.
*/
@NonNull
public Builder setContentTitle(@Nullable CharSequence contentTitle) {
this.mContentTitle = contentTitle;
return this;
}
/**
* Sets the content text of the notification in the car screen, or {@code null} to not
* override the content text.
*
* <p>This method is equivalent to
* {@link NotificationCompat.Builder#setContentText(CharSequence)} for the car screen.
*
* @param contentText override for the notification's content text. If set to an empty
* string, it will be treated as if there is no context text.
* @throws NullPointerException if {@code contentText} is {@code null}
*/
@NonNull
public Builder setContentText(@Nullable CharSequence contentText) {
this.mContentText = contentText;
return this;
}
/**
* Sets the small icon of the notification in the car screen, or
* {@link Resources#ID_NULL} to not override the notification small icon.
*
* <p>This is used as the primary icon to represent the notification.
*
* <p>This method is equivalent to {@link NotificationCompat.Builder#setSmallIcon(int)} for
* the car screen.
*/
// TODO(rampara): Revisit small icon getter API
@SuppressWarnings("MissingGetterMatchingBuilder")
@NonNull
public Builder setSmallIcon(int iconResId) {
this.mSmallIconResId = iconResId;
return this;
}
/**
* Sets the large icon of the notification in the car screen, or {@code null} to not
* override the large icon of the notification.
*
* <p>This is used as the secondary icon to represent the notification in the notification
* center.
*
* <p>This method is equivalent to {@link NotificationCompat.Builder#setLargeIcon(Bitmap)}
* for the car screen.
*
* <p>The large icon will be shown in the notification badge. If the large icon is not
* set in the {@link CarAppExtender} or the notification, the small icon will show instead.
*/
// TODO(rampara): Revisit small icon getter API
@SuppressWarnings("MissingGetterMatchingBuilder")
@NonNull
public Builder setLargeIcon(@Nullable Bitmap bitmap) {
this.mLargeIconBitmap = bitmap;
return this;
}
/**
* Supplies a {@link PendingIntent} to send when the notification is clicked in the car.
*
* <p>If set to {@code null}, the notification's content intent will be used.
*
* <p>In the case of navigation notifications in the rail widget, this intent will be
* sent when the user taps on the rail widget.
*
* <p>This method is equivalent to
* {@link NotificationCompat.Builder#setContentIntent(PendingIntent)} for the car screen.
*
* @param contentIntent override for the notification's content intent.
*/
@NonNull
public Builder setContentIntent(@Nullable PendingIntent contentIntent) {
this.mContentIntent = contentIntent;
return this;
}
/**
* Supplies a {@link PendingIntent} to send when the user clears the notification by either
* using the "clear all" functionality in the notification center, or tapping the individual
* "close" buttons on the notification in the car screen.
*
* <p>If set to {@code null}, the notification's content intent will be used.
*
* <p>This method is equivalent to
* {@link NotificationCompat.Builder#setDeleteIntent(PendingIntent)} for the car screen.
*
* @param deleteIntent override for the notification's delete intent.
*/
@NonNull
public Builder setDeleteIntent(@Nullable PendingIntent deleteIntent) {
this.mDeleteIntent = deleteIntent;
return this;
}
/**
* Adds an action to this notification.
*
* <p>Actions are typically displayed by the system as a button adjacent to the notification
* content.
*
* <p>A notification may offer up to 2 actions. The system may not display some actions
* in the compact notification UI (e.g. heads-up-notifications).
*
* <p>If one or more action is added with this method, any action added by
* {@link NotificationCompat.Builder#addAction(int, CharSequence, PendingIntent)} will be
* ignored.
*
* <p>This method is equivalent to
* {@link NotificationCompat.Builder#addAction(int, CharSequence, PendingIntent)} for the
* car screen.
*
* @param icon resource ID of a drawable that represents the action. In order to
* display the
* actions properly, a valid resource id for the icon must be provided.
* @param title text describing the action.
* @param intent {@link PendingIntent} to send when the action is invoked. In the case of
* navigation notifications in the rail widget, this intent will be sent
* when the user taps on the action icon in the rail
* widget.
* @throws NullPointerException if {@code title} is {@code null}.
* @throws NullPointerException if {@code intent} is {@code null}.
*/
// TODO(rampara): Update to remove use of deprecated Action API
@SuppressWarnings("deprecation")
@NonNull
public Builder addAction(
@DrawableRes int icon, @NonNull CharSequence title, @NonNull PendingIntent intent) {
this.mActions.add(new Action(icon, requireNonNull(title), requireNonNull(intent)));
return this;
}
/** Clears any actions that may have been added with {@link #addAction} up to this point. */
@NonNull
public Builder clearActions() {
this.mActions.clear();
return this;
}
/**
* Sets the importance of the notification in the car screen.
*
* <p>The default value is {@link NotificationManagerCompat#IMPORTANCE_UNSPECIFIED}.
*
* <p>The importance is used to determine whether the notification will show as a HUN on
* the car screen. See the class description for more details.
*
* <p>See {@link NotificationManagerCompat} for all supported importance
* values.
*/
@NonNull
public Builder setImportance(int importance) {
this.mImportance = importance;
return this;
}
/**
* Constructs the {@link CarAppExtender} defined by this builder.
*/
@NonNull
public CarAppExtender build() {
return new CarAppExtender(this);
}
}
}