1 /* 2 * Copyright (C) 2013 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 package com.android.cts.verifier.notifications; 17 18 import android.app.Activity; 19 import android.app.Notification; 20 import android.content.BroadcastReceiver; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.IntentFilter; 25 import android.os.Bundle; 26 import android.os.Parcelable; 27 import android.service.notification.NotificationListenerService; 28 import android.service.notification.StatusBarNotification; 29 import android.util.ArrayMap; 30 import android.util.Log; 31 32 import org.json.JSONException; 33 import org.json.JSONObject; 34 35 import java.util.ArrayList; 36 import java.util.HashSet; 37 import java.util.List; 38 import java.util.Set; 39 40 public class MockListener extends NotificationListenerService { 41 static final String TAG = "MockListener"; 42 43 public static final ComponentName COMPONENT_NAME = 44 new ComponentName("com.android.cts.verifier", MockListener.class.getName()); 45 46 static final String SERVICE_BASE = "android.service.notification.cts."; 47 static final String SERVICE_CHECK = SERVICE_BASE + "SERVICE_CHECK"; 48 static final String SERVICE_POSTED = SERVICE_BASE + "SERVICE_POSTED"; 49 static final String SERVICE_PAYLOADS = SERVICE_BASE + "SERVICE_PAYLOADS"; 50 static final String SERVICE_REMOVED = SERVICE_BASE + "SERVICE_REMOVED"; 51 static final String SERVICE_REMOVED_REASON = SERVICE_BASE + "SERVICE_REMOVED"; 52 static final String SERVICE_RESET = SERVICE_BASE + "SERVICE_RESET"; 53 static final String SERVICE_CLEAR_ONE = SERVICE_BASE + "SERVICE_CLEAR_ONE"; 54 static final String SERVICE_CLEAR_ALL = SERVICE_BASE + "SERVICE_CLEAR_ALL"; 55 static final String SERVICE_SNOOZE = SERVICE_BASE + "SERVICE_SNOOZE"; 56 static final String SERVICE_HINTS = SERVICE_BASE + "SERVICE_HINTS"; 57 static final String SERVICE_PROBE_HINTS = SERVICE_BASE + "SERVICE_PROBE_HINTS"; 58 static final String SERVICE_ORDER = SERVICE_BASE + "SERVICE_ORDER"; 59 static final String SERVICE_DND = SERVICE_BASE + "SERVICE_DND"; 60 static final String SERVICE_SNOOZE_DURATION = SERVICE_BASE + "SERVICE_SNOOZE_DURATION"; 61 static final String SERVICE_GET_SNOOZED = SERVICE_BASE + "GET_SNOOZED"; 62 63 static final String EXTRA_PAYLOAD = "PAYLOAD"; 64 static final String EXTRA_POSTED_NOTIFICATIONS = "NOTIFICATION_PAYLOAD"; 65 static final String EXTRA_INT = "INT"; 66 static final String EXTRA_TAG = "TAG"; 67 static final String EXTRA_CODE = "CODE"; 68 static final String EXTRA_LONG = "LONG"; 69 70 static final int RESULT_NO_SERVER = Activity.RESULT_FIRST_USER + 1; 71 72 public static final String JSON_FLAGS = "flag"; 73 public static final String JSON_ICON = "icon"; 74 public static final String JSON_ID = "id"; 75 public static final String JSON_PACKAGE = "pkg"; 76 public static final String JSON_WHEN = "when"; 77 public static final String JSON_TAG = "tag"; 78 public static final String JSON_RANK = "rank"; 79 public static final String JSON_AMBIENT = "ambient"; 80 public static final String JSON_MATCHES_ZEN_FILTER = "matches_zen_filter"; 81 public static final String JSON_REASON = "reason"; 82 public static final String JSON_HINTS = "hints"; 83 84 private ArrayList<String> mPosted = new ArrayList<String>(); 85 private ArrayMap<String, JSONObject> mNotifications = new ArrayMap<>(); 86 private ArrayMap<String, String> mNotificationKeys = new ArrayMap<>(); 87 private ArrayList<String> mRemoved = new ArrayList<String>(); 88 private ArrayMap<String, JSONObject> mRemovedReason = new ArrayMap<>(); 89 private ArrayList<String> mSnoozed = new ArrayList<>(); 90 private ArrayList<String> mOrder = new ArrayList<>(); 91 private Set<String> mTestPackages = new HashSet<>(); 92 private BroadcastReceiver mReceiver; 93 private int mDND = -1; 94 private ArrayList<Notification> mPostedNotifications = new ArrayList<Notification>(); 95 96 @Override 97 public void onCreate() { 98 super.onCreate(); 99 Log.d(TAG, "created"); 100 101 mTestPackages.add("com.android.cts.verifier"); 102 mTestPackages.add("com.android.cts.robot"); 103 104 mPosted = new ArrayList<String>(); 105 mRemoved = new ArrayList<String>(); 106 mSnoozed = new ArrayList<String>(); 107 mPostedNotifications = new ArrayList<Notification>(); 108 109 mReceiver = new BroadcastReceiver() { 110 @Override 111 public void onReceive(Context context, Intent intent) { 112 final String action = intent.getAction(); 113 final Bundle bundle = new Bundle(); 114 Log.d(TAG, action); 115 if (SERVICE_CHECK.equals(action)) { 116 setResultCode(Activity.RESULT_OK); 117 } else if (SERVICE_POSTED.equals(action)) { 118 bundle.putStringArrayList(EXTRA_PAYLOAD, mPosted); 119 bundle.putParcelableArrayList(EXTRA_POSTED_NOTIFICATIONS, mPostedNotifications); 120 setResultExtras(bundle); 121 setResultCode(Activity.RESULT_OK); 122 } else if (SERVICE_DND.equals(action)) { 123 bundle.putInt(EXTRA_INT, mDND); 124 setResultExtras(bundle); 125 setResultCode(Activity.RESULT_OK); 126 } else if (SERVICE_ORDER.equals(action)) { 127 bundle.putStringArrayList(EXTRA_PAYLOAD, mOrder); 128 setResultExtras(bundle); 129 setResultCode(Activity.RESULT_OK); 130 } else if (SERVICE_PAYLOADS.equals(action)) { 131 ArrayList<String> payloadData = new ArrayList<>(mNotifications.size()); 132 for (JSONObject payload: mNotifications.values()) { 133 payloadData.add(payload.toString()); 134 } 135 bundle.putStringArrayList(EXTRA_PAYLOAD, payloadData); 136 setResultExtras(bundle); 137 setResultCode(Activity.RESULT_OK); 138 } else if (SERVICE_REMOVED.equals(action)) { 139 bundle.putStringArrayList(EXTRA_PAYLOAD, mRemoved); 140 setResultExtras(bundle); 141 setResultCode(Activity.RESULT_OK); 142 } else if (SERVICE_REMOVED_REASON.equals(action)) { 143 ArrayList<String> payloadData = new ArrayList<>(mRemovedReason.size()); 144 for (JSONObject payload: mRemovedReason.values()) { 145 payloadData.add(payload.toString()); 146 } 147 bundle.putStringArrayList(EXTRA_PAYLOAD, payloadData); 148 setResultExtras(bundle); 149 setResultCode(Activity.RESULT_OK); 150 } else if (SERVICE_CLEAR_ONE.equals(action)) { 151 String tag = intent.getStringExtra(EXTRA_TAG); 152 String key = mNotificationKeys.get(tag); 153 if (key != null) { 154 MockListener.this.cancelNotification(key); 155 } else { 156 Log.w(TAG, "Notification does not exist: " + tag); 157 } 158 } else if (SERVICE_CLEAR_ALL.equals(action)) { 159 MockListener.this.cancelAllNotifications(); 160 } else if (SERVICE_RESET.equals(action)) { 161 resetData(); 162 } else if (SERVICE_SNOOZE.equals(action)) { 163 MockListener.this.requestUnbind(); 164 } else if (SERVICE_HINTS.equals(action)) { 165 MockListener.this.requestListenerHints(intent.getIntExtra(EXTRA_CODE, 0)); 166 } else if (SERVICE_PROBE_HINTS.equals(action)) { 167 bundle.putInt(EXTRA_INT, MockListener.this.getCurrentListenerHints()); 168 setResultExtras(bundle); 169 setResultCode(Activity.RESULT_OK); 170 } else if (SERVICE_SNOOZE_DURATION.equals(action)) { 171 String tag = intent.getStringExtra(EXTRA_TAG); 172 String key = mNotificationKeys.get(tag); 173 if (key != null) { 174 MockListener.this.snoozeNotification(key, 175 intent.getLongExtra(EXTRA_LONG, (long) 0)); 176 } 177 } else if (SERVICE_GET_SNOOZED.equals(action)) { 178 mSnoozed.clear(); 179 StatusBarNotification[] snoozed = MockListener.this.getSnoozedNotifications(); 180 for (StatusBarNotification sbn : snoozed) { 181 mSnoozed.add(sbn.getTag()); 182 } 183 bundle.putStringArrayList(EXTRA_PAYLOAD, mSnoozed); 184 setResultExtras(bundle); 185 setResultCode(Activity.RESULT_OK); 186 } else { 187 Log.w(TAG, "unknown action"); 188 setResultCode(Activity.RESULT_CANCELED); 189 } 190 } 191 }; 192 IntentFilter filter = new IntentFilter(); 193 filter.addAction(SERVICE_CHECK); 194 filter.addAction(SERVICE_DND); 195 filter.addAction(SERVICE_POSTED); 196 filter.addAction(SERVICE_ORDER); 197 filter.addAction(SERVICE_PAYLOADS); 198 filter.addAction(SERVICE_REMOVED); 199 filter.addAction(SERVICE_REMOVED_REASON); 200 filter.addAction(SERVICE_CLEAR_ONE); 201 filter.addAction(SERVICE_CLEAR_ALL); 202 filter.addAction(SERVICE_RESET); 203 filter.addAction(SERVICE_SNOOZE); 204 filter.addAction(SERVICE_HINTS); 205 filter.addAction(SERVICE_PROBE_HINTS); 206 filter.addAction(SERVICE_SNOOZE_DURATION); 207 filter.addAction(SERVICE_GET_SNOOZED); 208 registerReceiver(mReceiver, filter); 209 } 210 211 @Override 212 public void onDestroy() { 213 super.onDestroy(); 214 unregisterReceiver(mReceiver); 215 mReceiver = null; 216 Log.d(TAG, "destroyed"); 217 } 218 219 @Override 220 public void onListenerConnected() { 221 super.onListenerConnected(); 222 mDND = getCurrentInterruptionFilter(); 223 Log.d(TAG, "initial value of CurrentInterruptionFilter is " + mDND); 224 } 225 226 @Override 227 public void onInterruptionFilterChanged(int interruptionFilter) { 228 super.onInterruptionFilterChanged(interruptionFilter); 229 mDND = interruptionFilter; 230 Log.d(TAG, "value of CurrentInterruptionFilter changed to " + mDND); 231 } 232 233 public void resetData() { 234 mPosted.clear(); 235 mNotifications.clear(); 236 mRemoved.clear(); 237 mOrder.clear(); 238 mRemovedReason.clear(); 239 mSnoozed.clear(); 240 mPostedNotifications.clear(); 241 } 242 243 @Override 244 public void onNotificationRankingUpdate(RankingMap rankingMap) { 245 String[] orderedKeys = rankingMap.getOrderedKeys(); 246 mOrder.clear(); 247 Ranking rank = new Ranking(); 248 for( int i = 0; i < orderedKeys.length; i++) { 249 String key = orderedKeys[i]; 250 mOrder.add(key); 251 rankingMap.getRanking(key, rank); 252 JSONObject note = mNotifications.get(key); 253 if (note != null) { 254 try { 255 note.put(JSON_RANK, rank.getRank()); 256 note.put(JSON_AMBIENT, rank.isAmbient()); 257 note.put(JSON_MATCHES_ZEN_FILTER, rank.matchesInterruptionFilter()); 258 } catch (JSONException e) { 259 Log.e(TAG, "failed to pack up notification payload", e); 260 } 261 } 262 } 263 } 264 265 @Override 266 public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) { 267 if (!mTestPackages.contains(sbn.getPackageName())) { return; } 268 Log.d(TAG, "posted: " + sbn.getTag()); 269 mPosted.add(sbn.getTag()); 270 mPostedNotifications.add(sbn.getNotification()); 271 JSONObject notification = new JSONObject(); 272 try { 273 notification.put(JSON_TAG, sbn.getTag()); 274 notification.put(JSON_ID, sbn.getId()); 275 notification.put(JSON_PACKAGE, sbn.getPackageName()); 276 notification.put(JSON_WHEN, sbn.getNotification().when); 277 notification.put(JSON_ICON, sbn.getNotification().icon); 278 notification.put(JSON_FLAGS, sbn.getNotification().flags); 279 mNotifications.put(sbn.getKey(), notification); 280 mNotificationKeys.put(sbn.getTag(), sbn.getKey()); 281 } catch (JSONException e) { 282 Log.e(TAG, "failed to pack up notification payload", e); 283 } 284 onNotificationRankingUpdate(rankingMap); 285 } 286 287 @Override 288 public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) { 289 Log.d(TAG, "removed: " + sbn.getTag()); 290 mRemoved.add(sbn.getTag()); 291 mNotifications.remove(sbn.getKey()); 292 mNotificationKeys.remove(sbn.getTag()); 293 onNotificationRankingUpdate(rankingMap); 294 } 295 296 @Override 297 public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap, 298 int reason) { 299 Log.d(TAG, "removed: " + sbn.getTag()); 300 mRemoved.add(sbn.getTag()); 301 JSONObject removed = new JSONObject(); 302 try { 303 removed.put(JSON_TAG, sbn.getTag()); 304 removed.put(JSON_REASON, reason); 305 } catch (JSONException e) { 306 Log.e(TAG, "failed to pack up notification payload", e); 307 } 308 mNotifications.remove(sbn.getKey()); 309 mNotificationKeys.remove(sbn.getTag()); 310 mRemovedReason.put(sbn.getTag(), removed); 311 onNotificationRankingUpdate(rankingMap); 312 } 313 314 public static void resetListenerData(Context context) { 315 sendCommand(context, SERVICE_RESET, null, 0); 316 } 317 318 public static void probeListenerStatus(Context context, StatusCatcher catcher) { 319 requestStatus(context, SERVICE_CHECK, catcher); 320 } 321 322 public static void probeFilter(Context context, IntegerResultCatcher catcher) { 323 requestIntegerResult(context, SERVICE_DND, catcher); 324 } 325 326 public static void probeListenerPosted(Context context, StringListResultCatcher catcher) { 327 requestStringListResult(context, SERVICE_POSTED, catcher); 328 } 329 330 public static void probeListenerPosted(Context context, NotificationResultCatcher catcher) { 331 requestNotificationResult(context, SERVICE_POSTED, catcher); 332 } 333 334 public static void probeListenerSnoozed(Context context, StringListResultCatcher catcher) { 335 requestStringListResult(context, SERVICE_GET_SNOOZED, catcher); 336 } 337 338 public static void probeListenerOrder(Context context, StringListResultCatcher catcher) { 339 requestStringListResult(context, SERVICE_ORDER, catcher); 340 } 341 342 public static void probeListenerPayloads(Context context, StringListResultCatcher catcher) { 343 requestStringListResult(context, SERVICE_PAYLOADS, catcher); 344 } 345 346 public static void probeListenerRemoved(Context context, StringListResultCatcher catcher) { 347 requestStringListResult(context, SERVICE_REMOVED, catcher); 348 } 349 350 public static void probeListenerRemovedWithReason(Context context, 351 StringListResultCatcher catcher) { 352 requestStringListResult(context, SERVICE_REMOVED_REASON, catcher); 353 } 354 355 public static void probeListenerHints(Context context, IntegerResultCatcher catcher) { 356 requestIntegerResult(context, SERVICE_PROBE_HINTS, catcher); 357 } 358 359 public static void setHints(Context context, int hints) { 360 Intent broadcast = new Intent(SERVICE_HINTS); 361 broadcast.putExtra(EXTRA_CODE, hints); 362 context.sendBroadcast(broadcast); 363 } 364 365 public static void snooze(Context context) { 366 sendCommand(context, SERVICE_SNOOZE, null, 0); 367 } 368 369 public static void clearOne(Context context, String tag, int code) { 370 sendCommand(context, SERVICE_CLEAR_ONE, tag, code); 371 } 372 373 public static void snoozeOneFor(Context context, String tag, long duration) { 374 Intent broadcast = new Intent(SERVICE_SNOOZE_DURATION); 375 if (tag != null) { 376 broadcast.putExtra(EXTRA_TAG, tag); 377 broadcast.putExtra(EXTRA_LONG, duration); 378 } 379 context.sendBroadcast(broadcast); 380 } 381 382 public static void clearAll(Context context) { 383 sendCommand(context, SERVICE_CLEAR_ALL, null, 0); 384 } 385 386 private static void sendCommand(Context context, String action, String tag, int code) { 387 Intent broadcast = new Intent(action); 388 if (tag != null) { 389 broadcast.putExtra(EXTRA_TAG, tag); 390 broadcast.putExtra(EXTRA_CODE, code); 391 } 392 context.sendBroadcast(broadcast); 393 } 394 395 public abstract static class StatusCatcher extends BroadcastReceiver { 396 @Override 397 public void onReceive(Context context, Intent intent) { 398 accept(Integer.valueOf(getResultCode())); 399 } 400 401 abstract public void accept(int result); 402 } 403 404 private static void requestStatus(Context context, String action, 405 StatusCatcher catcher) { 406 Intent broadcast = new Intent(action); 407 context.sendOrderedBroadcast(broadcast, null, catcher, null, RESULT_NO_SERVER, null, null); 408 } 409 410 public abstract static class IntegerResultCatcher extends BroadcastReceiver { 411 @Override 412 public void onReceive(Context context, Intent intent) { 413 accept(getResultExtras(true).getInt(EXTRA_INT, -1)); 414 } 415 416 abstract public void accept(int result); 417 } 418 419 private static void requestIntegerResult(Context context, String action, 420 IntegerResultCatcher catcher) { 421 Intent broadcast = new Intent(action); 422 context.sendOrderedBroadcast(broadcast, null, catcher, null, RESULT_NO_SERVER, null, null); 423 } 424 425 public abstract static class StringListResultCatcher extends BroadcastReceiver { 426 @Override 427 public void onReceive(Context context, Intent intent) { 428 accept(getResultExtras(true).getStringArrayList(EXTRA_PAYLOAD)); 429 } 430 431 abstract public void accept(List<String> result); 432 } 433 434 public abstract static class NotificationResultCatcher extends BroadcastReceiver { 435 @Override 436 public void onReceive(Context context, Intent intent) { 437 ArrayList<Parcelable> parcels = 438 getResultExtras(true).getParcelableArrayList(EXTRA_POSTED_NOTIFICATIONS); 439 if (parcels == null) { 440 parcels = new ArrayList<Parcelable>(); 441 } 442 List<Notification> notifications = new ArrayList<Notification>(parcels.size()); 443 for (Parcelable parcel : parcels) { 444 if (parcel instanceof Notification) { 445 notifications.add((Notification) parcel); 446 } 447 } 448 accept(notifications); 449 } 450 451 abstract public void accept(List<Notification> result); 452 } 453 454 private static void requestStringListResult(Context context, String action, 455 StringListResultCatcher catcher) { 456 Intent broadcast = new Intent(action); 457 context.sendOrderedBroadcast(broadcast, null, catcher, null, RESULT_NO_SERVER, null, null); 458 } 459 460 private static void requestNotificationResult(Context context, String action, 461 NotificationResultCatcher catcher) { 462 Intent broadcast = new Intent(action); 463 context.sendOrderedBroadcast(broadcast, null, catcher, null, RESULT_NO_SERVER, null, null); 464 } 465 } 466