/* * Copyright (C) 2015 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 android.service.notification; import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.util.Log; import com.android.internal.os.SomeArgs; import java.lang.annotation.Retention; import java.util.List; /** * A service that helps the user manage notifications. *
* Only one notification assistant can be active at a time. Unlike notification listener services, * assistant services can additionally modify certain aspects about notifications * (see {@link Adjustment}) before they are posted. *
* A note about managed profiles: Unlike {@link NotificationListenerService listener services}, * NotificationAssistantServices are allowed to run in managed profiles * (see {@link DevicePolicyManager#isManagedProfile(ComponentName)}), so they can access the * information they need to create good {@link Adjustment adjustments}. To maintain the contract * with {@link NotificationListenerService}, an assistant service will receive all of the * callbacks from {@link NotificationListenerService} for the current user, managed profiles of * that user, and ones that affect all users. However, * {@link #onNotificationEnqueued(StatusBarNotification)} will only be called for notifications * sent to the current user, and {@link Adjustment adjuments} will only be accepted for the * current user. *
* All callbacks are called on the main thread. *
* @hide */ @SystemApi public abstract class NotificationAssistantService extends NotificationListenerService { private static final String TAG = "NotificationAssistants"; /** @hide */ @Retention(SOURCE) @IntDef({SOURCE_FROM_APP, SOURCE_FROM_ASSISTANT}) public @interface Source {} /** * To indicate an adjustment is from an app. */ public static final int SOURCE_FROM_APP = 0; /** * To indicate an adjustment is from a {@link NotificationAssistantService}. */ public static final int SOURCE_FROM_ASSISTANT = 1; /** * The {@link Intent} that must be declared as handled by the service. */ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; /** * Activity Action: Show notification assistant detail setting page in NAS app. ** In some cases, a matching Activity may not exist, so ensure you * safeguard against this. *
* Input: Nothing. *
* Output: Nothing. */ @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_NOTIFICATION_ASSISTANT_DETAIL_SETTINGS = "android.service.notification.action.NOTIFICATION_ASSISTANT_DETAIL_SETTINGS"; /** * Data type: int, the feedback rating score provided by user. The score can be any integer * value depends on the experimental and feedback UX design. */ public static final String FEEDBACK_RATING = "feedback.rating"; /** * @hide */ protected Handler mHandler; @SuppressLint("OnNameExpected") @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); mHandler = new MyHandler(getContext().getMainLooper()); } @Override public final @NonNull IBinder onBind(@Nullable Intent intent) { if (mWrapper == null) { mWrapper = new NotificationAssistantServiceWrapper(); } return mWrapper; } /** * A notification was snoozed until a context. For use with * {@link Adjustment#KEY_SNOOZE_CRITERIA}. When the device reaches the given context, the * assistant should restore the notification with {@link #unsnoozeNotification(String)}. * * @param sbn the notification to snooze * @param snoozeCriterionId the {@link SnoozeCriterion#getId()} representing a device context. */ abstract public void onNotificationSnoozedUntilContext(@NonNull StatusBarNotification sbn, @NonNull String snoozeCriterionId); /** * A notification was posted by an app. Called before post. * *
Note: this method is only called if you don't override * {@link #onNotificationEnqueued(StatusBarNotification, NotificationChannel)} or * {@link #onNotificationEnqueued(StatusBarNotification, NotificationChannel, RankingMap)}.
* * @param sbn the new notification * @return an adjustment or null to take no action, within 200ms. */ abstract public @Nullable Adjustment onNotificationEnqueued(@NonNull StatusBarNotification sbn); /** * A notification was posted by an app. Called before post. * *Note: this method is only called if you don't override * {@link #onNotificationEnqueued(StatusBarNotification, NotificationChannel, RankingMap)}.
* * @param sbn the new notification * @param channel the channel the notification was posted to * @return an adjustment or null to take no action, within 200ms. */ public @Nullable Adjustment onNotificationEnqueued(@NonNull StatusBarNotification sbn, @NonNull NotificationChannel channel) { return onNotificationEnqueued(sbn); } /** * A notification was posted by an app. Called before post. * * @param sbn the new notification * @param channel the channel the notification was posted to * @param rankingMap The current ranking map that can be used to retrieve ranking information * for active notifications. * @return an adjustment or null to take no action, within 200ms. */ public @Nullable Adjustment onNotificationEnqueued(@NonNull StatusBarNotification sbn, @NonNull NotificationChannel channel, @NonNull RankingMap rankingMap) { return onNotificationEnqueued(sbn, channel); } /** * Implement this method to learn when notifications are removed, how they were interacted with * before removal, and why they were removed. ** This might occur because the user has dismissed the notification using system UI (or another * notification listener) or because the app has withdrawn the notification. *
* NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the
* result from {@link StatusBarNotification#getNotification} may be missing some heavyweight
* fields such as {@link android.app.Notification#contentView} and
* {@link android.app.Notification#largeIcon}. However, all other fields on
* {@link StatusBarNotification}, sufficient to match this call with a prior call to
* {@link #onNotificationPosted(StatusBarNotification)}, will be intact.
*
** @param sbn A data structure encapsulating at least the original information (tag and id)
* and source (package name) used to post the {@link android.app.Notification} that
* was just removed.
* @param rankingMap The current ranking map that can be used to retrieve ranking information
* for active notifications.
* @param stats Stats about how the user interacted with the notification before it was removed.
* @param reason see {@link #REASON_LISTENER_CANCEL}, etc.
*/
@Override
public void onNotificationRemoved(@NonNull StatusBarNotification sbn,
@NonNull RankingMap rankingMap,
@NonNull NotificationStats stats, int reason) {
onNotificationRemoved(sbn, rankingMap, reason);
}
/**
* Implement this to know when a user has seen notifications, as triggered by
* {@link #setNotificationsShown(String[])}.
*/
public void onNotificationsSeen(@NonNull List Query {@link NotificationManager#getAllowedAssistantAdjustments()} to see what
* {@link Adjustment adjustments} you are currently allowed to make.
* This should only be used for notifications snoozed because of a contextual snooze suggestion
* you provided via {@link Adjustment#KEY_SNOOZE_CRITERIA}. Once un-snoozed, you will get a
* {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the
* notification.
* @param key The key of the notification to snooze
*/
public final void unsnoozeNotification(@NonNull String key) {
if (!isBound()) return;
try {
getNotificationInterface().unsnoozeNotificationFromAssistant(mWrapper, key);
} catch (android.os.RemoteException ex) {
Log.v(TAG, "Unable to contact notification manager", ex);
}
}
private class NotificationAssistantServiceWrapper extends NotificationListenerWrapper {
@Override
public void onNotificationEnqueuedWithChannel(IStatusBarNotificationHolder sbnHolder,
NotificationChannel channel, NotificationRankingUpdate update) {
StatusBarNotification sbn;
try {
sbn = sbnHolder.get();
} catch (RemoteException e) {
Log.w(TAG, "onNotificationEnqueued: Error receiving StatusBarNotification", e);
return;
}
if (sbn == null) {
Log.w(TAG, "onNotificationEnqueuedWithChannel: "
+ "Error receiving StatusBarNotification");
return;
}
applyUpdateLocked(update);
SomeArgs args = SomeArgs.obtain();
args.arg1 = sbn;
args.arg2 = channel;
args.arg3 = getCurrentRanking();
mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_ENQUEUED,
args).sendToTarget();
}
@Override
public void onNotificationSnoozedUntilContext(
IStatusBarNotificationHolder sbnHolder, String snoozeCriterionId) {
StatusBarNotification sbn;
try {
sbn = sbnHolder.get();
} catch (RemoteException e) {
Log.w(TAG, "onNotificationSnoozed: Error receiving StatusBarNotification", e);
return;
}
if (sbn == null) {
Log.w(TAG, "onNotificationSnoozed: Error receiving StatusBarNotification");
return;
}
SomeArgs args = SomeArgs.obtain();
args.arg1 = sbn;
args.arg2 = snoozeCriterionId;
mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_SNOOZED,
args).sendToTarget();
}
@Override
public void onNotificationsSeen(List