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 17 package com.android.cts.verifier.notifications; 18 19 import static com.android.cts.verifier.notifications.MockListener.JSON_FLAGS; 20 import static com.android.cts.verifier.notifications.MockListener.JSON_ICON; 21 import static com.android.cts.verifier.notifications.MockListener.JSON_ID; 22 import static com.android.cts.verifier.notifications.MockListener.JSON_PACKAGE; 23 import static com.android.cts.verifier.notifications.MockListener.JSON_TAG; 24 import static com.android.cts.verifier.notifications.MockListener.JSON_WHEN; 25 26 import android.annotation.SuppressLint; 27 import android.app.Activity; 28 import android.app.Notification; 29 import android.app.NotificationManager; 30 import android.app.PendingIntent; 31 import android.app.Service; 32 import android.content.ComponentName; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.content.pm.PackageManager; 36 import android.os.Bundle; 37 import android.os.IBinder; 38 import android.provider.Settings.Secure; 39 import android.util.Log; 40 import android.view.LayoutInflater; 41 import android.view.View; 42 import android.view.ViewGroup; 43 import android.widget.Button; 44 import android.widget.ImageView; 45 import android.widget.TextView; 46 47 import com.android.cts.verifier.PassFailButtons; 48 import com.android.cts.verifier.R; 49 import com.android.cts.verifier.nfc.TagVerifierActivity; 50 51 import org.json.JSONException; 52 import org.json.JSONObject; 53 54 import java.util.HashSet; 55 import java.util.List; 56 import java.util.Set; 57 import java.util.UUID; 58 import java.util.concurrent.LinkedBlockingQueue; 59 60 public class NotificationListenerVerifierActivity extends PassFailButtons.Activity 61 implements Runnable { 62 private static final String TAG = TagVerifierActivity.class.getSimpleName(); 63 private static final String STATE = "state"; 64 private static LinkedBlockingQueue<String> sDeletedQueue = new LinkedBlockingQueue<String>(); 65 66 protected static final String LISTENER_PATH = "com.android.cts.verifier/" + 67 "com.android.cts.verifier.notifications.MockListener"; 68 protected static final int SETUP = 0; 69 protected static final int PASS = 1; 70 protected static final int FAIL = 2; 71 protected static final int WAIT_FOR_USER = 3; 72 protected static final int CLEARED = 4; 73 protected static final int READY = 5; 74 protected static final int RETRY = 6; 75 76 protected static final int NOTIFICATION_ID = 1001; 77 78 protected int mState; 79 protected int[] mStatus; 80 protected PackageManager mPackageManager; 81 protected NotificationManager mNm; 82 protected Context mContext; 83 protected Runnable mRunner; 84 protected View mHandler; 85 protected String mPackageString; 86 87 private LayoutInflater mInflater; 88 private ViewGroup mItemList; 89 90 private String mTag1; 91 private String mTag2; 92 private String mTag3; 93 private int mIcon1; 94 private int mIcon2; 95 private int mIcon3; 96 private int mId1; 97 private int mId2; 98 private int mId3; 99 private long mWhen1; 100 private long mWhen2; 101 private long mWhen3; 102 private int mFlag1; 103 private int mFlag2; 104 private int mFlag3; 105 106 public static class DismissService extends Service { 107 @Override 108 public IBinder onBind(Intent intent) { 109 return null; 110 } 111 112 @Override 113 public void onStart(Intent intent, int startId) { 114 sDeletedQueue.offer(intent.getAction()); 115 } 116 } 117 118 @Override 119 protected void onCreate(Bundle savedInstanceState) { 120 onCreate(savedInstanceState, R.layout.nls_main); 121 setInfoResources(R.string.nls_test, R.string.nls_info, -1); 122 } 123 124 protected void onCreate(Bundle savedInstanceState, int layoutId) { 125 super.onCreate(savedInstanceState); 126 127 if (savedInstanceState != null) { 128 mState = savedInstanceState.getInt(STATE, 0); 129 } 130 mContext = this; 131 mRunner = this; 132 mNm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 133 mPackageManager = getPackageManager(); 134 mInflater = getLayoutInflater(); 135 View view = mInflater.inflate(layoutId, null); 136 mItemList = (ViewGroup) view.findViewById(R.id.nls_test_items); 137 mHandler = mItemList; 138 createTestItems(); 139 mStatus = new int[mItemList.getChildCount()]; 140 setContentView(view); 141 142 setPassFailButtonClickListeners(); 143 getPassButton().setEnabled(false); 144 } 145 146 @Override 147 protected void onSaveInstanceState (Bundle outState) { 148 outState.putInt(STATE, mState); 149 } 150 151 @Override 152 protected void onResume() { 153 super.onResume(); 154 next(); 155 } 156 157 // Interface Utilities 158 159 protected void createTestItems() { 160 createNlsSettingsItem(R.string.nls_enable_service); 161 createAutoItem(R.string.nls_service_started); 162 createAutoItem(R.string.nls_note_received); 163 createAutoItem(R.string.nls_payload_intact); 164 createAutoItem(R.string.nls_clear_one); 165 createAutoItem(R.string.nls_clear_all); 166 createNlsSettingsItem(R.string.nls_disable_service); 167 createAutoItem(R.string.nls_service_stopped); 168 createAutoItem(R.string.nls_note_missed); 169 } 170 171 protected void setItemState(int index, boolean passed) { 172 ViewGroup item = (ViewGroup) mItemList.getChildAt(index); 173 ImageView status = (ImageView) item.findViewById(R.id.nls_status); 174 status.setImageResource(passed ? R.drawable.fs_good : R.drawable.fs_error); 175 View button = item.findViewById(R.id.nls_action_button); 176 button.setClickable(false); 177 button.setEnabled(false); 178 status.invalidate(); 179 } 180 181 protected void markItemWaiting(int index) { 182 ViewGroup item = (ViewGroup) mItemList.getChildAt(index); 183 ImageView status = (ImageView) item.findViewById(R.id.nls_status); 184 status.setImageResource(R.drawable.fs_warning); 185 status.invalidate(); 186 } 187 188 protected View createNlsSettingsItem(int messageId) { 189 return createUserItem(messageId, R.string.nls_start_settings); 190 } 191 192 protected View createRetryItem(int messageId) { 193 return createUserItem(messageId, R.string.attention_ready); 194 } 195 196 protected View createUserItem(int messageId, int actionId) { 197 View item = mInflater.inflate(R.layout.nls_item, mItemList, false); 198 TextView instructions = (TextView) item.findViewById(R.id.nls_instructions); 199 instructions.setText(messageId); 200 Button button = (Button) item.findViewById(R.id.nls_action_button); 201 button.setText(actionId); 202 mItemList.addView(item); 203 button.setTag(actionId); 204 return item; 205 } 206 207 protected View createAutoItem(int stringId) { 208 View item = mInflater.inflate(R.layout.nls_item, mItemList, false); 209 TextView instructions = (TextView) item.findViewById(R.id.nls_instructions); 210 instructions.setText(stringId); 211 View button = item.findViewById(R.id.nls_action_button); 212 button.setVisibility(View.GONE); 213 mItemList.addView(item); 214 return item; 215 } 216 217 // Test management 218 219 public void run() { 220 while (mState < mStatus.length && mStatus[mState] != WAIT_FOR_USER) { 221 if (mStatus[mState] == PASS) { 222 setItemState(mState, true); 223 mState++; 224 } else if (mStatus[mState] == FAIL) { 225 setItemState(mState, false); 226 return; 227 } else { 228 break; 229 } 230 } 231 232 if (mState < mStatus.length && mStatus[mState] == WAIT_FOR_USER) { 233 markItemWaiting(mState); 234 } 235 236 updateStateMachine(); 237 } 238 239 protected void updateStateMachine() { 240 switch (mState) { 241 case 0: 242 testIsEnabled(mState); 243 break; 244 case 1: 245 testIsStarted(mState); 246 break; 247 case 2: 248 testNotificationRecieved(mState); 249 break; 250 case 3: 251 testDataIntact(mState); 252 break; 253 case 4: 254 testDismissOne(mState); 255 break; 256 case 5: 257 testDismissAll(mState); 258 break; 259 case 6: 260 testIsDisabled(mState); 261 break; 262 case 7: 263 testIsStopped(mState); 264 break; 265 case 8: 266 testNotificationNotRecieved(mState); 267 break; 268 case 9: 269 getPassButton().setEnabled(true); 270 mNm.cancelAll(); 271 break; 272 } 273 } 274 275 public void launchSettings() { 276 startActivity( 277 new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS")); 278 } 279 280 public void actionPressed(View v) { 281 Object tag = v.getTag(); 282 if (tag instanceof Integer) { 283 int id = ((Integer) tag).intValue(); 284 if (id == R.string.nls_start_settings) { 285 launchSettings(); 286 } else if (id == R.string.attention_ready) { 287 mStatus[mState] = READY; 288 next(); 289 } 290 } 291 } 292 293 protected PendingIntent makeIntent(int code, String tag) { 294 Intent intent = new Intent(tag); 295 intent.setComponent(new ComponentName(mContext, DismissService.class)); 296 PendingIntent pi = PendingIntent.getService(mContext, code, intent, 297 PendingIntent.FLAG_UPDATE_CURRENT); 298 return pi; 299 } 300 301 @SuppressLint("NewApi") 302 private void sendNotifications() { 303 mTag1 = UUID.randomUUID().toString(); 304 mTag2 = UUID.randomUUID().toString(); 305 mTag3 = UUID.randomUUID().toString(); 306 307 mNm.cancelAll(); 308 309 mWhen1 = System.currentTimeMillis() + 1; 310 mWhen2 = System.currentTimeMillis() + 2; 311 mWhen3 = System.currentTimeMillis() + 3; 312 313 mIcon1 = R.drawable.fs_good; 314 mIcon2 = R.drawable.fs_error; 315 mIcon3 = R.drawable.fs_warning; 316 317 mId1 = NOTIFICATION_ID + 1; 318 mId2 = NOTIFICATION_ID + 2; 319 mId3 = NOTIFICATION_ID + 3; 320 321 mPackageString = "com.android.cts.verifier"; 322 323 Notification n1 = new Notification.Builder(mContext) 324 .setContentTitle("ClearTest 1") 325 .setContentText(mTag1.toString()) 326 .setPriority(Notification.PRIORITY_LOW) 327 .setSmallIcon(mIcon1) 328 .setWhen(mWhen1) 329 .setDeleteIntent(makeIntent(1, mTag1)) 330 .setOnlyAlertOnce(true) 331 .build(); 332 mNm.notify(mTag1, mId1, n1); 333 mFlag1 = Notification.FLAG_ONLY_ALERT_ONCE; 334 335 Notification n2 = new Notification.Builder(mContext) 336 .setContentTitle("ClearTest 2") 337 .setContentText(mTag2.toString()) 338 .setPriority(Notification.PRIORITY_HIGH) 339 .setSmallIcon(mIcon2) 340 .setWhen(mWhen2) 341 .setDeleteIntent(makeIntent(2, mTag2)) 342 .setAutoCancel(true) 343 .build(); 344 mNm.notify(mTag2, mId2, n2); 345 mFlag2 = Notification.FLAG_AUTO_CANCEL; 346 347 Notification n3 = new Notification.Builder(mContext) 348 .setContentTitle("ClearTest 3") 349 .setContentText(mTag3.toString()) 350 .setPriority(Notification.PRIORITY_LOW) 351 .setSmallIcon(mIcon3) 352 .setWhen(mWhen3) 353 .setDeleteIntent(makeIntent(3, mTag3)) 354 .setAutoCancel(true) 355 .setOnlyAlertOnce(true) 356 .build(); 357 mNm.notify(mTag3, mId3, n3); 358 mFlag3 = Notification.FLAG_ONLY_ALERT_ONCE | Notification.FLAG_AUTO_CANCEL; 359 } 360 361 /** 362 * Return to the state machine to progress through the tests. 363 */ 364 protected void next() { 365 mHandler.removeCallbacks(mRunner); 366 mHandler.post(mRunner); 367 } 368 369 /** 370 * Wait for things to settle before returning to the state machine. 371 */ 372 protected void delay() { 373 delay(2000); 374 } 375 376 /** 377 * Wait for some time. 378 */ 379 protected void delay(long waitTime) { 380 mHandler.removeCallbacks(mRunner); 381 mHandler.postDelayed(mRunner, waitTime); 382 } 383 384 protected boolean checkEquals(long expected, long actual, String message) { 385 if (expected == actual) { 386 return true; 387 } 388 logWithStack(String.format(message, expected, actual)); 389 return false; 390 } 391 392 protected boolean checkEquals(String expected, String actual, String message) { 393 if (expected.equals(actual)) { 394 return true; 395 } 396 logWithStack(String.format(message, expected, actual)); 397 return false; 398 } 399 400 protected boolean checkFlagSet(int expected, int actual, String message) { 401 if ((expected & actual) != 0) { 402 return true; 403 } 404 logWithStack(String.format(message, expected, actual)); 405 return false; 406 }; 407 408 protected void logWithStack(String message) { 409 Throwable stackTrace = new Throwable(); 410 stackTrace.fillInStackTrace(); 411 Log.e(TAG, message, stackTrace); 412 } 413 414 // Tests 415 416 private void testIsEnabled(int i) { 417 // no setup required 418 Intent settings = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"); 419 if (settings.resolveActivity(mPackageManager) == null) { 420 logWithStack("failed testIsEnabled: no settings activity"); 421 mStatus[i] = FAIL; 422 } else { 423 // TODO: find out why Secure.ENABLED_NOTIFICATION_LISTENERS is hidden 424 String listeners = Secure.getString(getContentResolver(), 425 "enabled_notification_listeners"); 426 if (listeners != null && listeners.contains(LISTENER_PATH)) { 427 mStatus[i] = PASS; 428 } else { 429 mStatus[i] = WAIT_FOR_USER; 430 } 431 } 432 next(); 433 } 434 435 private void testIsStarted(final int i) { 436 if (mStatus[i] == SETUP) { 437 mStatus[i] = READY; 438 // wait for the service to start 439 delay(); 440 } else { 441 MockListener.probeListenerStatus(mContext, 442 new MockListener.StatusCatcher() { 443 @Override 444 public void accept(int result) { 445 if (result == Activity.RESULT_OK) { 446 mStatus[i] = PASS; 447 } else { 448 logWithStack("failed testIsStarted: " + result); 449 mStatus[i] = FAIL; 450 } 451 next(); 452 } 453 }); 454 } 455 } 456 457 private void testNotificationRecieved(final int i) { 458 if (mStatus[i] == SETUP) { 459 MockListener.resetListenerData(this); 460 mStatus[i] = CLEARED; 461 // wait for intent to move through the system 462 delay(); 463 } else if (mStatus[i] == CLEARED) { 464 sendNotifications(); 465 mStatus[i] = READY; 466 // wait for notifications to move through the system 467 delay(); 468 } else { 469 MockListener.probeListenerPosted(mContext, 470 new MockListener.StringListResultCatcher() { 471 @Override 472 public void accept(List<String> result) { 473 if (result != null && result.size() > 0 && result.contains(mTag1)) { 474 mStatus[i] = PASS; 475 } else { 476 logWithStack("failed testNotificationRecieved"); 477 mStatus[i] = FAIL; 478 } 479 next(); 480 }}); 481 } 482 } 483 484 private void testDataIntact(final int i) { 485 // no setup required 486 MockListener.probeListenerPayloads(mContext, 487 new MockListener.StringListResultCatcher() { 488 @Override 489 public void accept(List<String> result) { 490 boolean pass = false; 491 Set<String> found = new HashSet<String>(); 492 if (result != null && result.size() > 0) { 493 pass = true; 494 for(String payloadData : result) { 495 try { 496 JSONObject payload = new JSONObject(payloadData); 497 pass &= checkEquals(mPackageString, payload.getString(JSON_PACKAGE), 498 "data integrity test fail: notification package (%s, %s)"); 499 String tag = payload.getString(JSON_TAG); 500 if (mTag1.equals(tag)) { 501 found.add(mTag1); 502 pass &= checkEquals(mIcon1, payload.getInt(JSON_ICON), 503 "data integrity test fail: notification icon (%d, %d)"); 504 pass &= checkFlagSet(mFlag1, payload.getInt(JSON_FLAGS), 505 "data integrity test fail: notification flags (%d, %d)"); 506 pass &= checkEquals(mId1, payload.getInt(JSON_ID), 507 "data integrity test fail: notification ID (%d, %d)"); 508 pass &= checkEquals(mWhen1, payload.getLong(JSON_WHEN), 509 "data integrity test fail: notification when (%d, %d)"); 510 } else if (mTag2.equals(tag)) { 511 found.add(mTag2); 512 pass &= checkEquals(mIcon2, payload.getInt(JSON_ICON), 513 "data integrity test fail: notification icon (%d, %d)"); 514 pass &= checkFlagSet(mFlag2, payload.getInt(JSON_FLAGS), 515 "data integrity test fail: notification flags (%d, %d)"); 516 pass &= checkEquals(mId2, payload.getInt(JSON_ID), 517 "data integrity test fail: notification ID (%d, %d)"); 518 pass &= checkEquals(mWhen2, payload.getLong(JSON_WHEN), 519 "data integrity test fail: notification when (%d, %d)"); 520 } else if (mTag3.equals(tag)) { 521 found.add(mTag3); 522 pass &= checkEquals(mIcon3, payload.getInt(JSON_ICON), 523 "data integrity test fail: notification icon (%d, %d)"); 524 pass &= checkFlagSet(mFlag3, payload.getInt(JSON_FLAGS), 525 "data integrity test fail: notification flags (%d, %d)"); 526 pass &= checkEquals(mId3, payload.getInt(JSON_ID), 527 "data integrity test fail: notification ID (%d, %d)"); 528 pass &= checkEquals(mWhen3, payload.getLong(JSON_WHEN), 529 "data integrity test fail: notification when (%d, %d)"); 530 } else { 531 pass = false; 532 logWithStack("failed on unexpected notification tag: " + tag); 533 } 534 } catch (JSONException e) { 535 pass = false; 536 Log.e(TAG, "failed to unpack data from mocklistener", e); 537 } 538 } 539 } 540 pass &= found.size() == 3; 541 mStatus[i] = pass ? PASS : FAIL; 542 next(); 543 }}); 544 } 545 546 private void testDismissOne(final int i) { 547 if (mStatus[i] == SETUP) { 548 MockListener.resetListenerData(this); 549 mStatus[i] = CLEARED; 550 // wait for intent to move through the system 551 delay(); 552 } else if (mStatus[i] == CLEARED) { 553 MockListener.clearOne(mContext, mTag1, NOTIFICATION_ID + 1); 554 mStatus[i] = READY; 555 delay(); 556 } else { 557 MockListener.probeListenerRemoved(mContext, 558 new MockListener.StringListResultCatcher() { 559 @Override 560 public void accept(List<String> result) { 561 if (result != null && result.size() > 0 && result.contains(mTag1)) { 562 mStatus[i] = PASS; 563 next(); 564 } else { 565 if (mStatus[i] == RETRY) { 566 logWithStack("failed testDismissOne"); 567 mStatus[i] = FAIL; 568 next(); 569 } else { 570 logWithStack("failed testDismissOne, once: retrying"); 571 mStatus[i] = RETRY; 572 delay(); 573 } 574 } 575 }}); 576 } 577 } 578 579 private void testDismissAll(final int i) { 580 if (mStatus[i] == SETUP) { 581 MockListener.resetListenerData(this); 582 mStatus[i] = CLEARED; 583 // wait for intent to move through the system 584 delay(); 585 } else if (mStatus[i] == CLEARED) { 586 MockListener.clearAll(mContext); 587 mStatus[i] = READY; 588 delay(); 589 } else { 590 MockListener.probeListenerRemoved(mContext, 591 new MockListener.StringListResultCatcher() { 592 @Override 593 public void accept(List<String> result) { 594 if (result != null && result.size() == 2 595 && result.contains(mTag2) && result.contains(mTag3)) { 596 mStatus[i] = PASS; 597 next(); 598 } else { 599 if (mStatus[i] == RETRY) { 600 logWithStack("failed testDismissAll"); 601 mStatus[i] = FAIL; 602 next(); 603 } else { 604 logWithStack("failed testDismissAll, once: retrying"); 605 mStatus[i] = RETRY; 606 delay(); 607 } 608 } 609 } 610 }); 611 } 612 } 613 614 private void testIsDisabled(int i) { 615 // no setup required 616 // TODO: find out why Secure.ENABLED_NOTIFICATION_LISTENERS is hidden 617 String listeners = Secure.getString(getContentResolver(), 618 "enabled_notification_listeners"); 619 if (listeners == null || !listeners.contains(LISTENER_PATH)) { 620 mStatus[i] = PASS; 621 next(); 622 } else { 623 mStatus[i] = WAIT_FOR_USER; 624 delay(); 625 } 626 } 627 628 private void testIsStopped(final int i) { 629 if (mStatus[i] == SETUP) { 630 mStatus[i] = READY; 631 // wait for the service to start 632 delay(); 633 } else { 634 MockListener.probeListenerStatus(mContext, 635 new MockListener.StatusCatcher() { 636 @Override 637 public void accept(int result) { 638 if (result == Activity.RESULT_OK) { 639 logWithStack("failed testIsStopped"); 640 mStatus[i] = FAIL; 641 } else { 642 mStatus[i] = PASS; 643 } 644 next(); 645 } 646 }); 647 } 648 } 649 650 private void testNotificationNotRecieved(final int i) { 651 if (mStatus[i] == SETUP) { 652 MockListener.resetListenerData(this); 653 mStatus[i] = CLEARED; 654 // wait for intent to move through the system 655 delay(); 656 } else if (mStatus[i] == CLEARED) { 657 // setup for testNotificationRecieved 658 sendNotifications(); 659 mStatus[i] = READY; 660 delay(); 661 } else { 662 MockListener.probeListenerPosted(mContext, 663 new MockListener.StringListResultCatcher() { 664 @Override 665 public void accept(List<String> result) { 666 if (result == null || result.size() == 0) { 667 mStatus[i] = PASS; 668 } else { 669 logWithStack("failed testNotificationNotRecieved"); 670 mStatus[i] = FAIL; 671 } 672 next(); 673 }}); 674 } 675 } 676 } 677