Home | History | Annotate | Download | only in notification
      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 
     17 package android.service.notification;
     18 
     19 import android.annotation.SdkConstant;
     20 import android.annotation.SystemApi;
     21 import android.annotation.TestApi;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.os.Handler;
     25 import android.os.IBinder;
     26 import android.os.Looper;
     27 import android.os.Message;
     28 import android.os.RemoteException;
     29 import android.util.Log;
     30 
     31 import com.android.internal.os.SomeArgs;
     32 
     33 import java.util.List;
     34 
     35 /**
     36  * A service that helps the user manage notifications.
     37  * @hide
     38  */
     39 @SystemApi
     40 @TestApi
     41 public abstract class NotificationAssistantService extends NotificationListenerService {
     42     private static final String TAG = "NotificationAssistants";
     43 
     44     /**
     45      * The {@link Intent} that must be declared as handled by the service.
     46      */
     47     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
     48     public static final String SERVICE_INTERFACE
     49             = "android.service.notification.NotificationAssistantService";
     50 
     51     /**
     52      * @hide
     53      */
     54     protected Handler mHandler;
     55 
     56     @Override
     57     protected void attachBaseContext(Context base) {
     58         super.attachBaseContext(base);
     59         mHandler = new MyHandler(getContext().getMainLooper());
     60     }
     61 
     62     @Override
     63     public final IBinder onBind(Intent intent) {
     64         if (mWrapper == null) {
     65             mWrapper = new NotificationAssistantServiceWrapper();
     66         }
     67         return mWrapper;
     68     }
     69 
     70     /**
     71      * A notification was snoozed until a context. For use with
     72      * {@link Adjustment#KEY_SNOOZE_CRITERIA}. When the device reaches the given context, the
     73      * assistant should restore the notification with {@link #unsnoozeNotification(String)}.
     74      *
     75      * @param sbn the notification to snooze
     76      * @param snoozeCriterionId the {@link SnoozeCriterion#getId()} representing a device context.
     77      */
     78     abstract public void onNotificationSnoozedUntilContext(StatusBarNotification sbn,
     79             String snoozeCriterionId);
     80 
     81     /**
     82      * A notification was posted by an app. Called before post.
     83      *
     84      * @param sbn the new notification
     85      * @return an adjustment or null to take no action, within 100ms.
     86      */
     87     abstract public Adjustment onNotificationEnqueued(StatusBarNotification sbn);
     88 
     89     /**
     90      * Implement this method to learn when notifications are removed, how they were interacted with
     91      * before removal, and why they were removed.
     92      * <p>
     93      * This might occur because the user has dismissed the notification using system UI (or another
     94      * notification listener) or because the app has withdrawn the notification.
     95      * <p>
     96      * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the
     97      * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight
     98      * fields such as {@link android.app.Notification#contentView} and
     99      * {@link android.app.Notification#largeIcon}. However, all other fields on
    100      * {@link StatusBarNotification}, sufficient to match this call with a prior call to
    101      * {@link #onNotificationPosted(StatusBarNotification)}, will be intact.
    102      *
    103      ** @param sbn A data structure encapsulating at least the original information (tag and id)
    104      *            and source (package name) used to post the {@link android.app.Notification} that
    105      *            was just removed.
    106      * @param rankingMap The current ranking map that can be used to retrieve ranking information
    107      *                   for active notifications.
    108      * @param stats Stats about how the user interacted with the notification before it was removed.
    109      * @param reason see {@link #REASON_LISTENER_CANCEL}, etc.
    110      */
    111     @Override
    112     public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
    113             NotificationStats stats, int reason) {
    114         onNotificationRemoved(sbn, rankingMap, reason);
    115     }
    116 
    117     /**
    118      * Updates a notification.  N.B. this wont cause
    119      * an existing notification to alert, but might allow a future update to
    120      * this notification to alert.
    121      *
    122      * @param adjustment the adjustment with an explanation
    123      */
    124     public final void adjustNotification(Adjustment adjustment) {
    125         if (!isBound()) return;
    126         try {
    127             getNotificationInterface().applyAdjustmentFromAssistant(mWrapper, adjustment);
    128         } catch (android.os.RemoteException ex) {
    129             Log.v(TAG, "Unable to contact notification manager", ex);
    130             throw ex.rethrowFromSystemServer();
    131         }
    132     }
    133 
    134     /**
    135      * Updates existing notifications. Re-ranking won't occur until all adjustments are applied.
    136      * N.B. this wont cause an existing notification to alert, but might allow a future update to
    137      * these notifications to alert.
    138      *
    139      * @param adjustments a list of adjustments with explanations
    140      */
    141     public final void adjustNotifications(List<Adjustment> adjustments) {
    142         if (!isBound()) return;
    143         try {
    144             getNotificationInterface().applyAdjustmentsFromAssistant(mWrapper, adjustments);
    145         } catch (android.os.RemoteException ex) {
    146             Log.v(TAG, "Unable to contact notification manager", ex);
    147             throw ex.rethrowFromSystemServer();
    148         }
    149     }
    150 
    151     /**
    152      * Inform the notification manager about un-snoozing a specific notification.
    153      * <p>
    154      * This should only be used for notifications snoozed by this listener using
    155      * {@link #snoozeNotification(String, String)}. Once un-snoozed, you will get a
    156      * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the
    157      * notification.
    158      * @param key The key of the notification to snooze
    159      */
    160     public final void unsnoozeNotification(String key) {
    161         if (!isBound()) return;
    162         try {
    163             getNotificationInterface().unsnoozeNotificationFromAssistant(mWrapper, key);
    164         } catch (android.os.RemoteException ex) {
    165             Log.v(TAG, "Unable to contact notification manager", ex);
    166         }
    167     }
    168 
    169     private class NotificationAssistantServiceWrapper extends NotificationListenerWrapper {
    170         @Override
    171         public void onNotificationEnqueued(IStatusBarNotificationHolder sbnHolder) {
    172             StatusBarNotification sbn;
    173             try {
    174                 sbn = sbnHolder.get();
    175             } catch (RemoteException e) {
    176                 Log.w(TAG, "onNotificationEnqueued: Error receiving StatusBarNotification", e);
    177                 return;
    178             }
    179 
    180             SomeArgs args = SomeArgs.obtain();
    181             args.arg1 = sbn;
    182             mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_ENQUEUED,
    183                     args).sendToTarget();
    184         }
    185 
    186         @Override
    187         public void onNotificationSnoozedUntilContext(
    188                 IStatusBarNotificationHolder sbnHolder, String snoozeCriterionId)
    189                 throws RemoteException {
    190             StatusBarNotification sbn;
    191             try {
    192                 sbn = sbnHolder.get();
    193             } catch (RemoteException e) {
    194                 Log.w(TAG, "onNotificationSnoozed: Error receiving StatusBarNotification", e);
    195                 return;
    196             }
    197 
    198             SomeArgs args = SomeArgs.obtain();
    199             args.arg1 = sbn;
    200             args.arg2 = snoozeCriterionId;
    201             mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_SNOOZED,
    202                     args).sendToTarget();
    203         }
    204     }
    205 
    206     private final class MyHandler extends Handler {
    207         public static final int MSG_ON_NOTIFICATION_ENQUEUED = 1;
    208         public static final int MSG_ON_NOTIFICATION_SNOOZED = 2;
    209 
    210         public MyHandler(Looper looper) {
    211             super(looper, null, false);
    212         }
    213 
    214         @Override
    215         public void handleMessage(Message msg) {
    216             switch (msg.what) {
    217                 case MSG_ON_NOTIFICATION_ENQUEUED: {
    218                     SomeArgs args = (SomeArgs) msg.obj;
    219                     StatusBarNotification sbn = (StatusBarNotification) args.arg1;
    220                     args.recycle();
    221                     Adjustment adjustment = onNotificationEnqueued(sbn);
    222                     if (adjustment != null) {
    223                         if (!isBound()) return;
    224                         try {
    225                             getNotificationInterface().applyEnqueuedAdjustmentFromAssistant(
    226                                     mWrapper, adjustment);
    227                         } catch (android.os.RemoteException ex) {
    228                             Log.v(TAG, "Unable to contact notification manager", ex);
    229                             throw ex.rethrowFromSystemServer();
    230                         }
    231                     }
    232                     break;
    233                 }
    234                 case MSG_ON_NOTIFICATION_SNOOZED: {
    235                     SomeArgs args = (SomeArgs) msg.obj;
    236                     StatusBarNotification sbn = (StatusBarNotification) args.arg1;
    237                     String snoozeCriterionId = (String) args.arg2;
    238                     args.recycle();
    239                     onNotificationSnoozedUntilContext(sbn, snoozeCriterionId);
    240                     break;
    241                 }
    242             }
    243         }
    244     }
    245 }
    246