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 android.provider.Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS; 20 21 import android.app.NotificationManager; 22 import android.app.PendingIntent; 23 import android.app.Service; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.pm.PackageManager; 28 import android.os.Bundle; 29 import android.os.IBinder; 30 import android.os.Parcelable; 31 import android.provider.Settings.Secure; 32 import android.util.Log; 33 import android.view.LayoutInflater; 34 import android.view.View; 35 import android.view.ViewGroup; 36 import android.widget.Button; 37 import android.widget.ImageView; 38 import android.widget.TextView; 39 import com.android.cts.verifier.PassFailButtons; 40 import com.android.cts.verifier.R; 41 42 import java.util.ArrayList; 43 import java.util.Arrays; 44 import java.util.Iterator; 45 import java.util.List; 46 import java.util.Objects; 47 import java.util.concurrent.LinkedBlockingQueue; 48 49 public abstract class InteractiveVerifierActivity extends PassFailButtons.Activity 50 implements Runnable { 51 private static final String TAG = "InteractiveVerifier"; 52 private static final String STATE = "state"; 53 private static final String STATUS = "status"; 54 private static LinkedBlockingQueue<String> sDeletedQueue = new LinkedBlockingQueue<String>(); 55 protected static final String LISTENER_PATH = "com.android.cts.verifier/" + 56 "com.android.cts.verifier.notifications.MockListener"; 57 protected static final int SETUP = 0; 58 protected static final int READY = 1; 59 protected static final int RETEST = 2; 60 protected static final int PASS = 3; 61 protected static final int FAIL = 4; 62 protected static final int WAIT_FOR_USER = 5; 63 protected static final int RETEST_AFTER_LONG_DELAY = 6; 64 protected static final int READY_AFTER_LONG_DELAY = 7; 65 66 protected static final int NOTIFICATION_ID = 1001; 67 68 // TODO remove these once b/10023397 is fixed 69 public static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners"; 70 71 protected InteractiveTestCase mCurrentTest; 72 protected PackageManager mPackageManager; 73 protected NotificationManager mNm; 74 protected Context mContext; 75 protected Runnable mRunner; 76 protected View mHandler; 77 protected String mPackageString; 78 79 private LayoutInflater mInflater; 80 private ViewGroup mItemList; 81 private List<InteractiveTestCase> mTestList; 82 private Iterator<InteractiveTestCase> mTestOrder; 83 84 public static class DismissService extends Service { 85 @Override onBind(Intent intent)86 public IBinder onBind(Intent intent) { 87 return null; 88 } 89 90 @Override onStart(Intent intent, int startId)91 public void onStart(Intent intent, int startId) { 92 if(intent != null) { sDeletedQueue.offer(intent.getAction()); } 93 } 94 } 95 96 protected abstract class InteractiveTestCase { 97 protected boolean mUserVerified; 98 protected int status; 99 private View view; 100 protected long delayTime = 3000; 101 inflate(ViewGroup parent)102 protected abstract View inflate(ViewGroup parent); getView(ViewGroup parent)103 View getView(ViewGroup parent) { 104 if (view == null) { 105 view = inflate(parent); 106 } 107 return view; 108 } 109 110 /** @return true if the test should re-run when the test activity starts. */ autoStart()111 boolean autoStart() { 112 return false; 113 } 114 115 /** Set status to {@link #READY} to proceed, or {@link #SETUP} to try again. */ setUp()116 protected void setUp() { status = READY; next(); }; 117 118 /** Set status to {@link #PASS} or @{link #FAIL} to proceed, or {@link #READY} to retry. */ test()119 protected void test() { status = FAIL; next(); }; 120 121 /** Do not modify status. */ tearDown()122 protected void tearDown() { next(); }; 123 setFailed()124 protected void setFailed() { 125 status = FAIL; 126 logFail(); 127 } 128 logFail()129 protected void logFail() { 130 logFail(null); 131 } 132 logFail(String message)133 protected void logFail(String message) { 134 logWithStack("failed " + this.getClass().getSimpleName() + 135 ((message == null) ? "" : ": " + message)); 136 } 137 logFail(String message, Throwable e)138 protected void logFail(String message, Throwable e) { 139 Log.e(TAG, "failed " + this.getClass().getSimpleName() + 140 ((message == null) ? "" : ": " + message), e); 141 } 142 143 // If this test contains a button that launches another activity, override this 144 // method to provide the intent to launch. getIntent()145 protected Intent getIntent() { 146 return null; 147 } 148 } 149 getTitleResource()150 protected abstract int getTitleResource(); getInstructionsResource()151 protected abstract int getInstructionsResource(); 152 onCreate(Bundle savedState)153 protected void onCreate(Bundle savedState) { 154 super.onCreate(savedState); 155 int savedStateIndex = (savedState == null) ? 0 : savedState.getInt(STATE, 0); 156 int savedStatus = (savedState == null) ? SETUP : savedState.getInt(STATUS, SETUP); 157 Log.i(TAG, "restored state(" + savedStateIndex + "}, status(" + savedStatus + ")"); 158 mContext = this; 159 mRunner = this; 160 mNm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 161 mPackageManager = getPackageManager(); 162 mInflater = getLayoutInflater(); 163 View view = mInflater.inflate(R.layout.nls_main, null); 164 mItemList = (ViewGroup) view.findViewById(R.id.nls_test_items); 165 mHandler = mItemList; 166 mTestList = new ArrayList<>(); 167 mTestList.addAll(createTestItems()); 168 for (InteractiveTestCase test: mTestList) { 169 mItemList.addView(test.getView(mItemList)); 170 } 171 mTestOrder = mTestList.iterator(); 172 for (int i = 0; i < savedStateIndex; i++) { 173 mCurrentTest = mTestOrder.next(); 174 mCurrentTest.status = PASS; 175 } 176 mCurrentTest = mTestOrder.next(); 177 mCurrentTest.status = savedStatus; 178 179 setContentView(view); 180 setPassFailButtonClickListeners(); 181 getPassButton().setEnabled(false); 182 183 setInfoResources(getTitleResource(), getInstructionsResource(), -1); 184 } 185 186 @Override onSaveInstanceState(Bundle outState)187 protected void onSaveInstanceState (Bundle outState) { 188 final int stateIndex = mTestList.indexOf(mCurrentTest); 189 outState.putInt(STATE, stateIndex); 190 final int status = mCurrentTest == null ? SETUP : mCurrentTest.status; 191 outState.putInt(STATUS, status); 192 Log.i(TAG, "saved state(" + stateIndex + "}, status(" + status + ")"); 193 } 194 195 @Override onResume()196 protected void onResume() { 197 super.onResume(); 198 //To avoid NPE during onResume,before start to iterate next test order 199 if (mCurrentTest!= null && mCurrentTest.autoStart()) { 200 mCurrentTest.status = READY; 201 } 202 next(); 203 } 204 205 // Interface Utilities 206 markItem(InteractiveTestCase test)207 protected void markItem(InteractiveTestCase test) { 208 if (test == null) { return; } 209 View item = test.view; 210 ImageView status = (ImageView) item.findViewById(R.id.nls_status); 211 View button = item.findViewById(R.id.nls_action_button); 212 switch (test.status) { 213 case WAIT_FOR_USER: 214 status.setImageResource(R.drawable.fs_warning); 215 break; 216 217 case SETUP: 218 case READY: 219 case RETEST: 220 status.setImageResource(R.drawable.fs_clock); 221 break; 222 223 case FAIL: 224 status.setImageResource(R.drawable.fs_error); 225 button.setClickable(false); 226 button.setEnabled(false); 227 break; 228 229 case PASS: 230 status.setImageResource(R.drawable.fs_good); 231 button.setClickable(false); 232 button.setEnabled(false); 233 break; 234 235 } 236 status.invalidate(); 237 } 238 createNlsSettingsItem(ViewGroup parent, int messageId)239 protected View createNlsSettingsItem(ViewGroup parent, int messageId) { 240 return createUserItem(parent, R.string.nls_start_settings, messageId); 241 } 242 createRetryItem(ViewGroup parent, int messageId, Object... messageFormatArgs)243 protected View createRetryItem(ViewGroup parent, int messageId, Object... messageFormatArgs) { 244 return createUserItem(parent, R.string.attention_ready, messageId, messageFormatArgs); 245 } 246 createUserItem(ViewGroup parent, int actionId, int messageId, Object... messageFormatArgs)247 protected View createUserItem(ViewGroup parent, int actionId, int messageId, 248 Object... messageFormatArgs) { 249 View item = mInflater.inflate(R.layout.nls_item, parent, false); 250 TextView instructions = (TextView) item.findViewById(R.id.nls_instructions); 251 instructions.setText(getString(messageId, messageFormatArgs)); 252 Button button = (Button) item.findViewById(R.id.nls_action_button); 253 button.setText(actionId); 254 button.setTag(actionId); 255 return item; 256 } 257 createAutoItem(ViewGroup parent, int stringId)258 protected View createAutoItem(ViewGroup parent, int stringId) { 259 View item = mInflater.inflate(R.layout.nls_item, parent, false); 260 TextView instructions = (TextView) item.findViewById(R.id.nls_instructions); 261 instructions.setText(stringId); 262 View button = item.findViewById(R.id.nls_action_button); 263 button.setVisibility(View.GONE); 264 return item; 265 } 266 267 // Test management 268 createTestItems()269 abstract protected List<InteractiveTestCase> createTestItems(); 270 run()271 public void run() { 272 if (mCurrentTest == null) { return; } 273 markItem(mCurrentTest); 274 switch (mCurrentTest.status) { 275 case SETUP: 276 Log.i(TAG, "running setup for: " + mCurrentTest.getClass().getSimpleName()); 277 mCurrentTest.setUp(); 278 if (mCurrentTest.status == READY_AFTER_LONG_DELAY) { 279 delay(mCurrentTest.delayTime); 280 } else { 281 delay(); 282 } 283 break; 284 285 case WAIT_FOR_USER: 286 Log.i(TAG, "waiting for user: " + mCurrentTest.getClass().getSimpleName()); 287 break; 288 289 case READY_AFTER_LONG_DELAY: 290 case RETEST_AFTER_LONG_DELAY: 291 case READY: 292 case RETEST: 293 Log.i(TAG, "running test for: " + mCurrentTest.getClass().getSimpleName()); 294 try { 295 mCurrentTest.test(); 296 if (mCurrentTest.status == RETEST_AFTER_LONG_DELAY) { 297 delay(mCurrentTest.delayTime); 298 } else { 299 delay(); 300 } 301 } catch (Throwable t) { 302 mCurrentTest.status = FAIL; 303 markItem(mCurrentTest); 304 Log.e(TAG, "FAIL: " + mCurrentTest.getClass().getSimpleName(), t); 305 mCurrentTest.tearDown(); 306 mCurrentTest = null; 307 delay(); 308 } 309 310 break; 311 312 case FAIL: 313 Log.i(TAG, "FAIL: " + mCurrentTest.getClass().getSimpleName()); 314 mCurrentTest.tearDown(); 315 mCurrentTest = null; 316 delay(); 317 break; 318 319 case PASS: 320 Log.i(TAG, "pass for: " + mCurrentTest.getClass().getSimpleName()); 321 mCurrentTest.tearDown(); 322 if (mTestOrder.hasNext()) { 323 mCurrentTest = mTestOrder.next(); 324 Log.i(TAG, "next test is: " + mCurrentTest.getClass().getSimpleName()); 325 next(); 326 } else { 327 Log.i(TAG, "no more tests"); 328 mCurrentTest = null; 329 getPassButton().setEnabled(true); 330 mNm.cancelAll(); 331 } 332 break; 333 } 334 markItem(mCurrentTest); 335 } 336 337 /** 338 * Return to the state machine to progress through the tests. 339 */ next()340 protected void next() { 341 mHandler.removeCallbacks(mRunner); 342 mHandler.post(mRunner); 343 } 344 345 /** 346 * Wait for things to settle before returning to the state machine. 347 */ delay()348 protected void delay() { 349 delay(3000); 350 } 351 sleep(long time)352 protected void sleep(long time) { 353 try { 354 Thread.sleep(time); 355 } catch (InterruptedException e) { 356 e.printStackTrace(); 357 } 358 } 359 360 /** 361 * Wait for some time. 362 */ delay(long waitTime)363 protected void delay(long waitTime) { 364 mHandler.removeCallbacks(mRunner); 365 mHandler.postDelayed(mRunner, waitTime); 366 } 367 368 // UI callbacks 369 actionPressed(View v)370 public void actionPressed(View v) { 371 Object tag = v.getTag(); 372 if (tag instanceof Integer) { 373 int id = ((Integer) tag).intValue(); 374 if (mCurrentTest != null && mCurrentTest.getIntent() != null) { 375 startActivity(mCurrentTest.getIntent()); 376 } else if (id == R.string.attention_ready) { 377 if (mCurrentTest != null) { 378 mCurrentTest.status = READY; 379 next(); 380 } 381 } 382 if (mCurrentTest != null) { 383 mCurrentTest.mUserVerified = true; 384 } 385 } 386 } 387 388 // Utilities 389 makeIntent(int code, String tag)390 protected PendingIntent makeIntent(int code, String tag) { 391 Intent intent = new Intent(tag); 392 intent.setComponent(new ComponentName(mContext, DismissService.class)); 393 PendingIntent pi = PendingIntent.getService(mContext, code, intent, 394 PendingIntent.FLAG_UPDATE_CURRENT); 395 return pi; 396 } 397 checkEquals(long[] expected, long[] actual, String message)398 protected boolean checkEquals(long[] expected, long[] actual, String message) { 399 if (Arrays.equals(expected, actual)) { 400 return true; 401 } 402 logWithStack(String.format(message, expected, actual)); 403 return false; 404 } 405 checkEquals(Object[] expected, Object[] actual, String message)406 protected boolean checkEquals(Object[] expected, Object[] actual, String message) { 407 if (Arrays.equals(expected, actual)) { 408 return true; 409 } 410 logWithStack(String.format(message, expected, actual)); 411 return false; 412 } 413 checkEquals(Parcelable expected, Parcelable actual, String message)414 protected boolean checkEquals(Parcelable expected, Parcelable actual, String message) { 415 if (Objects.equals(expected, actual)) { 416 return true; 417 } 418 logWithStack(String.format(message, expected, actual)); 419 return false; 420 } 421 checkEquals(boolean expected, boolean actual, String message)422 protected boolean checkEquals(boolean expected, boolean actual, String message) { 423 if (expected == actual) { 424 return true; 425 } 426 logWithStack(String.format(message, expected, actual)); 427 return false; 428 } 429 checkEquals(long expected, long actual, String message)430 protected boolean checkEquals(long expected, long actual, String message) { 431 if (expected == actual) { 432 return true; 433 } 434 logWithStack(String.format(message, expected, actual)); 435 return false; 436 } 437 checkEquals(CharSequence expected, CharSequence actual, String message)438 protected boolean checkEquals(CharSequence expected, CharSequence actual, String message) { 439 if (expected.equals(actual)) { 440 return true; 441 } 442 logWithStack(String.format(message, expected, actual)); 443 return false; 444 } 445 checkFlagSet(int expected, int actual, String message)446 protected boolean checkFlagSet(int expected, int actual, String message) { 447 if ((expected & actual) != 0) { 448 return true; 449 } 450 logWithStack(String.format(message, expected, actual)); 451 return false; 452 }; 453 logWithStack(String message)454 protected void logWithStack(String message) { 455 Throwable stackTrace = new Throwable(); 456 stackTrace.fillInStackTrace(); 457 Log.e(TAG, message, stackTrace); 458 } 459 460 // Common Tests: useful for the side-effects they generate 461 462 protected class IsEnabledTest extends InteractiveTestCase { 463 @Override inflate(ViewGroup parent)464 protected View inflate(ViewGroup parent) { 465 return createNlsSettingsItem(parent, R.string.nls_enable_service); 466 } 467 468 @Override autoStart()469 boolean autoStart() { 470 return true; 471 } 472 473 @Override test()474 protected void test() { 475 mNm.cancelAll(); 476 Intent settings = new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS); 477 if (settings.resolveActivity(mPackageManager) == null) { 478 logFail("no settings activity"); 479 status = FAIL; 480 } else { 481 String listeners = Secure.getString(getContentResolver(), 482 ENABLED_NOTIFICATION_LISTENERS); 483 if (listeners != null && listeners.contains(LISTENER_PATH)) { 484 status = PASS; 485 } else { 486 status = WAIT_FOR_USER; 487 } 488 next(); 489 } 490 } 491 492 @Override tearDown()493 protected void tearDown() { 494 // wait for the service to start 495 delay(); 496 } 497 498 @Override getIntent()499 protected Intent getIntent() { 500 return new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS); 501 } 502 } 503 504 protected class CannotBeEnabledTest extends InteractiveTestCase { 505 @Override inflate(ViewGroup parent)506 protected View inflate(ViewGroup parent) { 507 return createNlsSettingsItem(parent, R.string.nls_cannot_enable_service); 508 } 509 510 @Override autoStart()511 boolean autoStart() { 512 return true; 513 } 514 515 @Override test()516 protected void test() { 517 mNm.cancelAll(); 518 Intent settings = new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS); 519 if (settings.resolveActivity(mPackageManager) == null) { 520 logFail("no settings activity"); 521 status = FAIL; 522 } else { 523 String listeners = Secure.getString(getContentResolver(), 524 ENABLED_NOTIFICATION_LISTENERS); 525 if (listeners != null && listeners.contains(LISTENER_PATH)) { 526 status = FAIL; 527 } else { 528 status = PASS; 529 } 530 next(); 531 } 532 } 533 tearDown()534 protected void tearDown() { 535 // wait for the service to start 536 delay(); 537 } 538 539 @Override getIntent()540 protected Intent getIntent() { 541 return new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS); 542 } 543 } 544 545 protected class ServiceStartedTest extends InteractiveTestCase { 546 @Override inflate(ViewGroup parent)547 protected View inflate(ViewGroup parent) { 548 return createAutoItem(parent, R.string.nls_service_started); 549 } 550 551 @Override test()552 protected void test() { 553 if (MockListener.getInstance() != null && MockListener.getInstance().isConnected) { 554 status = PASS; 555 next(); 556 } else { 557 logFail(); 558 status = RETEST; 559 delay(); 560 } 561 } 562 } 563 } 564