[go: nahoru, domu]

blob: f2af9a3bd4cb04a6ba9e6b8b95d956e5e33e59d7 [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.wear.ongoingactivity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.graphics.drawable.Icon;
import android.os.Build;
import android.os.SystemClock;
import android.service.notification.StatusBarNotification;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import androidx.core.content.LocusIdCompat;
import java.util.function.Predicate;
/**
* Main class to access the Ongoing Activities API.
*
* It's created with the {@link Builder}. After it's created (and before building and
* posting the {@link Notification}) {@link OngoingActivity#apply(Context)} apply} needs to be
* called:
* {@code
* NotificationCompat.Builder builder = new NotificationCompat.Builder(context)....
*
* OngoingActivity OngoingActivity.Builder(context, notificationId, builder);
* ....
* ongoingActivity.apply(context);
*
* notificationManager.notify(notificationId, builder.build());
* }
*
* Afterward, {@link OngoingActivity#update(Context, OngoingActivityStatus) update} can be used to
* update the status.
*
* If saving the {@link OngoingActivity} instance is not convenient, it can be recovered (after the
* notification is posted) with {@link OngoingActivity#fromExistingOngoingActivity(Context)}
*/
@RequiresApi(24)
public final class OngoingActivity {
private final int mNotificationId;
private final NotificationCompat.Builder mNotificationBuilder;
private final OngoingActivityData mData;
private OngoingActivity(int notificationId,
@NonNull NotificationCompat.Builder notificationBuilder,
@NonNull OngoingActivityData data) {
this.mNotificationId = notificationId;
this.mNotificationBuilder = notificationBuilder;
this.mData = data;
}
/**
* Builder used to build an {@link OngoingActivity}
*/
public static final class Builder {
private final Context mContext;
private final int mNotificationId;
private final NotificationCompat.Builder mNotificationBuilder;
// Ongoing Activity Data
private Icon mAnimatedIcon;
private Icon mStaticIcon;
private OngoingActivityStatus mStatus;
private PendingIntent mTouchIntent;
private LocusIdCompat mLocusId;
private int mOngoingActivityId = OngoingActivityData.DEFAULT_ID;
private String mCategory;
/**
* Construct a new empty {@link Builder}, associated with the given notification.
*
* @param context to be used during the life of this {@link Builder}, will
* NOT pass a reference into the built {@link OngoingActivity}
* @param notificationId id that will be used to post the notification associated
* with this Ongoing Activity
* @param notificationBuilder builder for the notification associated with this Ongoing
* Activity
*/
public Builder(@NonNull Context context, int notificationId,
@NonNull NotificationCompat.Builder notificationBuilder) {
this.mContext = context;
this.mNotificationId = notificationId;
this.mNotificationBuilder = notificationBuilder;
}
/**
* Set the animated icon that can be used on some surfaces to represent this
* {@link OngoingActivity}. For example, in the WatchFace.
* Should be white with a transparent background, preferably an AnimatedVectorDrawable.
*/
@SuppressWarnings("MissingGetterMatchingBuilder")
// No getters needed on OngoingActivity - receiver will consume from OngoingActivityData.
@NonNull
public Builder setAnimatedIcon(@NonNull Icon animatedIcon) {
mAnimatedIcon = animatedIcon;
return this;
}
/**
* Set the animated icon that can be used on some surfaces to represent this
* {@link OngoingActivity}. For example, in the WatchFace.
* Should be white with a transparent background, preferably an AnimatedVectorDrawable.
*/
@SuppressWarnings("MissingGetterMatchingBuilder")
// No getters needed on OngoingActivity - receiver will consume from OngoingActivityData.
@NonNull
public Builder setAnimatedIcon(@DrawableRes int animatedIcon) {
mAnimatedIcon = Icon.createWithResource(mContext, animatedIcon);
return this;
}
/**
* Set the animated icon that can be used on some surfaces to represent this
* {@link OngoingActivity}, for example in the WatchFace in ambient mode.
* Should be white with a transparent background, preferably an VectorDrawable.
*/
@SuppressWarnings("MissingGetterMatchingBuilder")
// No getters needed on OngoingActivity - receiver will consume from OngoingActivityData.
@NonNull
public Builder setStaticIcon(@NonNull Icon staticIcon) {
mStaticIcon = staticIcon;
return this;
}
/**
* Set the animated icon that can be used on some surfaces to represent this
* {@link OngoingActivity}, for example in the WatchFace in ambient mode.
* Should be white with a transparent background, preferably an VectorDrawable.
*/
@SuppressWarnings("MissingGetterMatchingBuilder")
// No getters needed on OngoingActivity - receiver will consume from OngoingActivityData.
@NonNull
public Builder setStaticIcon(@DrawableRes int staticIcon) {
mStaticIcon = Icon.createWithResource(mContext, staticIcon);
return this;
}
/**
* Set the initial status of this ongoing activity, the status may be displayed on the UI to
* show progress of the Ongoing Activity.
*/
@SuppressWarnings("MissingGetterMatchingBuilder")
// No getters needed on OngoingActivity - receiver will consume from OngoingActivityData.
@NonNull
public Builder setStatus(@NonNull OngoingActivityStatus status) {
mStatus = status;
return this;
}
/**
* Set the intent to be used to go back to the activity when the user interacts with the
* Ongoing Activity in other surfaces (for example, taps the Icon on the WatchFace)
*/
@SuppressWarnings("MissingGetterMatchingBuilder")
// No getters needed on OngoingActivity - receiver will consume from OngoingActivityData.
@NonNull
public Builder setTouchIntent(@NonNull PendingIntent touchIntent) {
mTouchIntent = touchIntent;
return this;
}
/**
* Set the corresponding LocusId of this {@link OngoingActivity}, this will be used by the
* launcher to identify the corresponding launcher item and display it accordingly.
*/
@SuppressWarnings("MissingGetterMatchingBuilder")
// No getters needed on OngoingActivity - receiver will consume from OngoingActivityData.
@NonNull
public Builder setLocusId(@NonNull LocusIdCompat locusId) {
mLocusId = locusId;
return this;
}
/**
* Give an id to this {@link OngoingActivity}, as a way to reference it in
* {@link OngoingActivity#fromExistingOngoingActivity(Context, int)}
*/
@SuppressWarnings("MissingGetterMatchingBuilder")
// No getters needed on OngoingActivity - receiver will consume from OngoingActivityData.
@NonNull
public Builder setOngoingActivityId(int ongoingActivityId) {
mOngoingActivityId = ongoingActivityId;
return this;
}
/**
* Set the category of this {@link OngoingActivity}, this may be used by the system to
* prioritize it.
*/
@SuppressWarnings("MissingGetterMatchingBuilder")
// No getters needed on OngoingActivity - receiver will consume from OngoingActivityData.
@NonNull
public Builder setCategory(@NonNull String category) {
mCategory = category;
return this;
}
/**
* Combine all options provided and the information in the notification if needed,
* return a new {@link OngoingActivity} object.
*
* @throws IllegalArgumentException if the static icon or the touch intent are not provided.
*/
@SuppressWarnings("SyntheticAccessor")
@NonNull
public OngoingActivity build() {
Notification notification = mNotificationBuilder.build();
Icon staticIcon = mStaticIcon == null ? notification.getSmallIcon() : mStaticIcon;
if (staticIcon == null) {
throw new IllegalArgumentException("Static icon should be specified.");
}
PendingIntent touchIntent = mTouchIntent == null
? notification.contentIntent
: mTouchIntent;
if (touchIntent == null) {
throw new IllegalArgumentException("Touch intent should be specified.");
}
OngoingActivityStatus status = mStatus;
if (status == null) {
String text = notification.extras.getString(Notification.EXTRA_TEXT);
if (text != null) {
status = new TextOngoingActivityStatus(text);
}
}
LocusIdCompat locusId = mLocusId;
if (locusId == null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
locusId = Api29Impl.getLocusId(notification);
}
String category = mCategory == null ? notification.category : mCategory;
return new OngoingActivity(mNotificationId, mNotificationBuilder,
new OngoingActivityData(
mAnimatedIcon,
staticIcon,
status,
touchIntent,
locusId == null ? null : locusId.getId(),
mOngoingActivityId,
category,
SystemClock.elapsedRealtime()
));
}
}
/**
* Notify the system that this activity should be shown as an Ongoing Activity.
*
* This will modify the notification builder associated with this Ongoing Activity, so needs
* to be called before building and posting that notification.
*
* @param context May be used to access system services. A reference will not be kept after
* this call returns.
*/
public void apply(@NonNull @SuppressWarnings("unused") Context context) {
mData.extend(mNotificationBuilder);
}
/**
* Update the status of this Ongoing Activity.
*
* Note that this may post the notification updated with the new information.
*
* @param context May be used to access system services. A reference will not be kept after
* this call returns.
* @param status The new status of this Ongoing Activity.
*/
public void update(@NonNull Context context, @NonNull OngoingActivityStatus status) {
mData.setStatus(status);
Notification notification = mData.extendAndBuild(mNotificationBuilder);
context.getSystemService(NotificationManager.class).notify(mNotificationId, notification);
}
/**
* Convenience method for clients that don’t want to / can’t store the OngoingActivity
* instance.
*
* @param context May be used to access system services. A reference will not be kept after
* this call returns.
* @param filter used to find the required {@link OngoingActivity}.
* @return the Ongoing Activity or null if not found
*/
@Nullable
public static OngoingActivity fromExistingOngoingActivity(
@NonNull Context context,
@NonNull Predicate<OngoingActivityData> filter
) {
StatusBarNotification[] notifications =
context.getSystemService(NotificationManager.class).getActiveNotifications();
for (StatusBarNotification statusBarNotification : notifications) {
OngoingActivityData data =
OngoingActivityData.create(statusBarNotification.getNotification());
if (data != null && filter.test(data)) {
return new OngoingActivity(statusBarNotification.getId(),
new NotificationCompat.Builder(context,
statusBarNotification.getNotification()),
data);
}
}
return null;
}
/**
* Convenience method for clients that don’t want to / can’t store the OngoingActivity
* instance.
*
* Note that if there is more than one Ongoing Activity active you have not guarantee
* over which one you get, you need to use one of the other variations of this method.
*
* @param context May be used to access system services. A reference will not be kept after
* this call returns.
* @return the Ongoing Activity or null if not found
*/
@Nullable
public static OngoingActivity fromExistingOngoingActivity(@NonNull Context context) {
return fromExistingOngoingActivity(context, (data) -> true);
}
/**
* Convenience method for clients that don’t want to / can’t store the OngoingActivity
* instance.
*
* @param context May be used to access system services. A reference will not be kept
* after this call returns.
* @param ongoingActivityId the id of the Ongoing Activity to retrieve, set in
* {@link OngoingActivity.Builder#setOngoingActivityId(int)}
* @return the Ongoing Activity or null if not found
*/
@Nullable
public static OngoingActivity fromExistingOngoingActivity(@NonNull Context context,
int ongoingActivityId) {
return fromExistingOngoingActivity(context,
(data) -> data.getOngoingActivityId() == ongoingActivityId);
}
// Inner class required to avoid VFY errors during class init.
@RequiresApi(29)
static class Api29Impl {
// Avoid instantiation.
private Api29Impl() {
}
@Nullable
private static LocusIdCompat getLocusId(@NonNull Notification notification) {
return notification.getLocusId() != null
? LocusIdCompat.toLocusIdCompat(notification.getLocusId()) : null;
}
}
}