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