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.content.ComponentName;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.net.Uri;
     25 import android.os.Bundle;
     26 import android.os.Handler;
     27 import android.os.IBinder;
     28 import android.os.Looper;
     29 import android.os.Message;
     30 import android.os.Parcel;
     31 import android.os.Parcelable;
     32 import android.os.RemoteException;
     33 import android.util.Log;
     34 import com.android.internal.os.SomeArgs;
     35 
     36 import java.util.List;
     37 
     38 /**
     39  * A service that helps the user manage notifications. This class is only used to
     40  * extend the framework service and may not be implemented by non-framework components.
     41  * @hide
     42  */
     43 @SystemApi
     44 public abstract class NotificationRankerService extends NotificationListenerService {
     45     private static final String TAG = "NotificationRankers";
     46 
     47     /**
     48      * The {@link Intent} that must be declared as handled by the service.
     49      */
     50     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
     51     public static final String SERVICE_INTERFACE
     52             = "android.service.notification.NotificationRankerService";
     53 
     54     /** Notification was canceled by the status bar reporting a click. */
     55     public static final int REASON_DELEGATE_CLICK = 1;
     56 
     57     /** Notification was canceled by the status bar reporting a user dismissal. */
     58     public static final int REASON_DELEGATE_CANCEL = 2;
     59 
     60     /** Notification was canceled by the status bar reporting a user dismiss all. */
     61     public static final int REASON_DELEGATE_CANCEL_ALL = 3;
     62 
     63     /** Notification was canceled by the status bar reporting an inflation error. */
     64     public static final int REASON_DELEGATE_ERROR = 4;
     65 
     66     /** Notification was canceled by the package manager modifying the package. */
     67     public static final int REASON_PACKAGE_CHANGED = 5;
     68 
     69     /** Notification was canceled by the owning user context being stopped. */
     70     public static final int REASON_USER_STOPPED = 6;
     71 
     72     /** Notification was canceled by the user banning the package. */
     73     public static final int REASON_PACKAGE_BANNED = 7;
     74 
     75     /** Notification was canceled by the app canceling this specific notification. */
     76     public static final int REASON_APP_CANCEL = 8;
     77 
     78     /** Notification was canceled by the app cancelling all its notifications. */
     79     public static final int REASON_APP_CANCEL_ALL = 9;
     80 
     81     /** Notification was canceled by a listener reporting a user dismissal. */
     82     public static final int REASON_LISTENER_CANCEL = 10;
     83 
     84     /** Notification was canceled by a listener reporting a user dismiss all. */
     85     public static final int REASON_LISTENER_CANCEL_ALL = 11;
     86 
     87     /** Notification was canceled because it was a member of a canceled group. */
     88     public static final int REASON_GROUP_SUMMARY_CANCELED = 12;
     89 
     90     /** Notification was canceled because it was an invisible member of a group. */
     91     public static final int REASON_GROUP_OPTIMIZATION = 13;
     92 
     93     /** Notification was canceled by the device administrator suspending the package. */
     94     public static final int REASON_PACKAGE_SUSPENDED = 14;
     95 
     96     /** Notification was canceled by the owning managed profile being turned off. */
     97     public static final int REASON_PROFILE_TURNED_OFF = 15;
     98 
     99     /** Autobundled summary notification was canceled because its group was unbundled */
    100     public static final int REASON_UNAUTOBUNDLED = 16;
    101 
    102     private Handler mHandler;
    103 
    104     /** @hide */
    105     @Override
    106     public void registerAsSystemService(Context context, ComponentName componentName,
    107             int currentUser)  {
    108         throw new UnsupportedOperationException("the ranker lifecycle is managed by the system.");
    109     }
    110 
    111     /** @hide */
    112     @Override
    113     public void unregisterAsSystemService()  {
    114         throw new UnsupportedOperationException("the ranker lifecycle is managed by the system.");
    115     }
    116 
    117     @Override
    118     protected void attachBaseContext(Context base) {
    119         super.attachBaseContext(base);
    120         mHandler = new MyHandler(getContext().getMainLooper());
    121     }
    122 
    123     @Override
    124     public final IBinder onBind(Intent intent) {
    125         if (mWrapper == null) {
    126             mWrapper = new NotificationRankingServiceWrapper();
    127         }
    128         return mWrapper;
    129     }
    130 
    131     /**
    132      * A notification was posted by an app. Called before alert.
    133      *
    134      * @param sbn the new notification
    135      * @param importance the initial importance of the notification.
    136      * @param user true if the initial importance reflects an explicit user preference.
    137      * @return an adjustment or null to take no action, within 100ms.
    138      */
    139     abstract public Adjustment onNotificationEnqueued(StatusBarNotification sbn,
    140           int importance, boolean user);
    141 
    142     /**
    143      * The visibility of a notification has changed.
    144      *
    145      * @param key the notification key
    146      * @param time milliseconds since midnight, January 1, 1970 UTC.
    147      * @param visible true if the notification became visible, false if hidden.
    148      */
    149     public void onNotificationVisibilityChanged(String key, long time, boolean visible)
    150     {
    151         // Do nothing, Override this to collect visibility statistics.
    152     }
    153 
    154     /**
    155      * The user clicked on a notification.
    156      *
    157      * @param key the notification key
    158      * @param time milliseconds since midnight, January 1, 1970 UTC.
    159      */
    160     public void onNotificationClick(String key, long time)
    161     {
    162         // Do nothing, Override this to collect click statistics
    163     }
    164 
    165     /**
    166      * The user clicked on a notification action.
    167      *
    168      * @param key the notification key
    169      * @param time milliseconds since midnight, January 1, 1970 UTC.
    170      * @param actionIndex the index of the action button that was pressed.
    171      */
    172     public void onNotificationActionClick(String key, long time, int actionIndex)
    173     {
    174         // Do nothing, Override this to collect action button click statistics
    175     }
    176 
    177     /**
    178      * A notification was removed.
    179 
    180      * @param key the notification key
    181      * @param time milliseconds since midnight, January 1, 1970 UTC.
    182      * @param reason see {@link #REASON_LISTENER_CANCEL}, etc.
    183      */
    184     public void onNotificationRemoved(String key, long time, int reason) {
    185         // Do nothing, Override this to collect dismissal statistics
    186     }
    187 
    188     /**
    189      * Updates a notification.  N.B. this wont cause
    190      * an existing notification to alert, but might allow a future update to
    191      * this notification to alert.
    192      *
    193      * @param adjustment the adjustment with an explanation
    194      */
    195     public final void adjustNotification(Adjustment adjustment) {
    196         if (!isBound()) return;
    197         try {
    198             getNotificationInterface().applyAdjustmentFromRankerService(mWrapper, adjustment);
    199         } catch (android.os.RemoteException ex) {
    200             Log.v(TAG, "Unable to contact notification manager", ex);
    201         }
    202     }
    203 
    204     /**
    205      * Updates existing notifications. Re-ranking won't occur until all adjustments are applied.
    206      * N.B. this wont cause an existing notification to alert, but might allow a future update to
    207      * these notifications to alert.
    208      *
    209      * @param adjustments a list of adjustments with explanations
    210      */
    211     public final void adjustNotifications(List<Adjustment> adjustments) {
    212         if (!isBound()) return;
    213         try {
    214             getNotificationInterface().applyAdjustmentsFromRankerService(mWrapper, adjustments);
    215         } catch (android.os.RemoteException ex) {
    216             Log.v(TAG, "Unable to contact notification manager", ex);
    217         }
    218     }
    219 
    220     private class NotificationRankingServiceWrapper extends NotificationListenerWrapper {
    221         @Override
    222         public void onNotificationEnqueued(IStatusBarNotificationHolder sbnHolder,
    223                 int importance, boolean user) {
    224             StatusBarNotification sbn;
    225             try {
    226                 sbn = sbnHolder.get();
    227             } catch (RemoteException e) {
    228                 Log.w(TAG, "onNotificationEnqueued: Error receiving StatusBarNotification", e);
    229                 return;
    230             }
    231 
    232             SomeArgs args = SomeArgs.obtain();
    233             args.arg1 = sbn;
    234             args.argi1 = importance;
    235             args.argi2 = user ? 1 : 0;
    236             mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_ENQUEUED,
    237                     args).sendToTarget();
    238         }
    239 
    240         @Override
    241         public void onNotificationVisibilityChanged(String key, long time, boolean visible) {
    242             SomeArgs args = SomeArgs.obtain();
    243             args.arg1 = key;
    244             args.arg2 = time;
    245             args.argi1 = visible ? 1 : 0;
    246             mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_VISIBILITY_CHANGED,
    247                     args).sendToTarget();
    248         }
    249 
    250         @Override
    251         public void onNotificationClick(String key, long time) {
    252             SomeArgs args = SomeArgs.obtain();
    253             args.arg1 = key;
    254             args.arg2 = time;
    255             mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_CLICK,
    256                     args).sendToTarget();
    257         }
    258 
    259         @Override
    260         public void onNotificationActionClick(String key, long time, int actionIndex) {
    261             SomeArgs args = SomeArgs.obtain();
    262             args.arg1 = key;
    263             args.arg2 = time;
    264             args.argi1 = actionIndex;
    265             mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_ACTION_CLICK,
    266                     args).sendToTarget();
    267         }
    268 
    269         @Override
    270         public void onNotificationRemovedReason(String key, long time, int reason) {
    271             SomeArgs args = SomeArgs.obtain();
    272             args.arg1 = key;
    273             args.arg2 = time;
    274             args.argi1 = reason;
    275             mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_REMOVED_REASON,
    276                     args).sendToTarget();
    277         }
    278     }
    279 
    280     private final class MyHandler extends Handler {
    281         public static final int MSG_ON_NOTIFICATION_ENQUEUED = 1;
    282         public static final int MSG_ON_NOTIFICATION_VISIBILITY_CHANGED = 2;
    283         public static final int MSG_ON_NOTIFICATION_CLICK = 3;
    284         public static final int MSG_ON_NOTIFICATION_ACTION_CLICK = 4;
    285         public static final int MSG_ON_NOTIFICATION_REMOVED_REASON = 5;
    286 
    287         public MyHandler(Looper looper) {
    288             super(looper, null, false);
    289         }
    290 
    291         @Override
    292         public void handleMessage(Message msg) {
    293             switch (msg.what) {
    294                 case MSG_ON_NOTIFICATION_ENQUEUED: {
    295                     SomeArgs args = (SomeArgs) msg.obj;
    296                     StatusBarNotification sbn = (StatusBarNotification) args.arg1;
    297                     final int importance = args.argi1;
    298                     final boolean user = args.argi2 == 1;
    299                     args.recycle();
    300                     Adjustment adjustment = onNotificationEnqueued(sbn, importance, user);
    301                     if (adjustment != null) {
    302                         adjustNotification(adjustment);
    303                     }
    304                 } break;
    305 
    306                 case MSG_ON_NOTIFICATION_VISIBILITY_CHANGED: {
    307                     SomeArgs args = (SomeArgs) msg.obj;
    308                     final String key = (String) args.arg1;
    309                     final long time = (long) args.arg2;
    310                     final boolean visible = args.argi1 == 1;
    311                     args.recycle();
    312                     onNotificationVisibilityChanged(key, time, visible);
    313                 } break;
    314 
    315                 case MSG_ON_NOTIFICATION_CLICK: {
    316                     SomeArgs args = (SomeArgs) msg.obj;
    317                     final String key = (String) args.arg1;
    318                     final long time = (long) args.arg2;
    319                     args.recycle();
    320                     onNotificationClick(key, time);
    321                 } break;
    322 
    323                 case MSG_ON_NOTIFICATION_ACTION_CLICK: {
    324                     SomeArgs args = (SomeArgs) msg.obj;
    325                     final String key = (String) args.arg1;
    326                     final long time = (long) args.arg2;
    327                     final int actionIndex = args.argi1;
    328                     args.recycle();
    329                     onNotificationActionClick(key, time, actionIndex);
    330                 } break;
    331 
    332                 case MSG_ON_NOTIFICATION_REMOVED_REASON: {
    333                     SomeArgs args = (SomeArgs) msg.obj;
    334                     final String key = (String) args.arg1;
    335                     final long time = (long) args.arg2;
    336                     final int reason = args.argi1;
    337                     args.recycle();
    338                     onNotificationRemoved(key, time, reason);
    339                 } break;
    340             }
    341         }
    342     }
    343 }
    344