1 /* 2 * Copyright (C) 2024 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.app.cts; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertFalse; 21 import static org.junit.Assert.assertNotNull; 22 import static org.junit.Assert.assertTrue; 23 import static org.junit.Assert.fail; 24 25 import android.app.ActivityManager; 26 import android.app.ApplicationStartInfo; 27 import android.app.Flags; 28 import android.app.Instrumentation; 29 import android.content.BroadcastReceiver; 30 import android.content.ComponentName; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.IntentFilter; 34 import android.content.pm.PackageManager; 35 import android.os.Bundle; 36 import android.os.Process; 37 import android.os.UserHandle; 38 import android.platform.test.annotations.RequiresFlagsEnabled; 39 import android.util.Log; 40 41 import androidx.test.ext.junit.runners.AndroidJUnit4; 42 import androidx.test.platform.app.InstrumentationRegistry; 43 44 import com.android.compatibility.common.util.AmUtils; 45 import com.android.compatibility.common.util.ShellIdentityUtils; 46 import com.android.compatibility.common.util.SystemUtil; 47 48 import com.google.errorprone.annotations.FormatMethod; 49 50 import org.junit.After; 51 import org.junit.Before; 52 import org.junit.Test; 53 import org.junit.runner.RunWith; 54 55 import java.util.ArrayList; 56 import java.util.List; 57 import java.util.Map; 58 59 @RunWith(AndroidJUnit4.class) 60 public final class ActivityManagerAppStartInfoTest { 61 private static final String TAG = ActivityManagerAppStartInfoTest.class.getSimpleName(); 62 63 // Begin section: keep in sync with {@link ApiTestActivity} 64 private static final String REQUEST_KEY_ACTION = "action"; 65 private static final String REQUEST_KEY_TIMESTAMP_KEY_FIRST = "timestamp_key_first"; 66 private static final String REQUEST_KEY_TIMESTAMP_VALUE_FIRST = "timestamp_value_first"; 67 private static final String REQUEST_KEY_TIMESTAMP_KEY_LAST = "timestamp_key_last"; 68 private static final String REQUEST_KEY_TIMESTAMP_VALUE_LAST = "timestamp_value_last"; 69 70 private static final int REQUEST_VALUE_QUERY_START = 1; 71 private static final int REQUEST_VALUE_ADD_TIMESTAMP = 2; 72 private static final int REQUEST_VALUE_LISTENER_ADD_ONE = 3; 73 private static final int REQUEST_VALUE_LISTENER_ADD_MULTIPLE = 4; 74 private static final int REQUEST_VALUE_LISTENER_ADD_REMOVE = 5; 75 private static final int REQUEST_VALUE_CRASH = 6; 76 77 private static final String REPLY_ACTION_COMPLETE = 78 "com.android.cts.startinfoapp.ACTION_COMPLETE"; 79 80 private static final String REPLY_EXTRA_STATUS_KEY = "status"; 81 82 private static final int REPLY_EXTRA_SUCCESS_VALUE = 1; 83 //private static final int REPLY_EXTRA_FAILURE_VALUE = 2; 84 85 private static final int REPLY_STATUS_NONE = -1; 86 // End section: keep in sync with {@link ApiTestActivity} 87 88 private static final String STUB_APK = 89 "/data/local/tmp/cts/content/CtsAppStartInfoApp.apk"; 90 private static final String STUB_PACKAGE_NAME = "com.android.cts.startinfoapp"; 91 private static final String SIMPLE_ACTIVITY = ".ApiTestActivity"; 92 93 private static final int FIRST_TIMESTAMP_KEY = 94 ApplicationStartInfo.START_TIMESTAMP_RESERVED_RANGE_DEVELOPER_START; 95 private static final int LAST_TIMESTAMP_KEY = 96 ApplicationStartInfo.START_TIMESTAMP_RESERVED_RANGE_DEVELOPER; 97 98 private static final int MAX_WAITS_FOR_START = 20; 99 private static final int WAIT_FOR_START_MS = 400; 100 101 // Return states of the ResultReceiverFilter. 102 public static final int RESULT_PASS = 1; 103 public static final int RESULT_FAIL = 2; 104 public static final int RESULT_TIMEOUT = 3; 105 106 private Context mContext; 107 private Instrumentation mInstrumentation; 108 private ActivityManager mActivityManager; 109 private PackageManager mPackageManager; 110 111 private int mStubPackageUid; 112 private int mTestRunningUserId; 113 114 @Before setUp()115 public void setUp() throws Exception { 116 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 117 mContext = mInstrumentation.getContext(); 118 mActivityManager = mContext.getSystemService(ActivityManager.class); 119 mPackageManager = mContext.getPackageManager(); 120 mTestRunningUserId = UserHandle.getUserId(Process.myUid()); 121 122 executeShellCmd("pm install --user %d -r --force-queryable " + STUB_APK, 123 mTestRunningUserId); 124 125 mStubPackageUid = mPackageManager.getPackageUid(STUB_PACKAGE_NAME, 0); 126 } 127 128 @After tearDown()129 public void tearDown() throws Exception { 130 executeShellCmd("am force-stop --user %d " + STUB_PACKAGE_NAME, mTestRunningUserId); 131 mInstrumentation.getUiAutomation().dropShellPermissionIdentity(); 132 } 133 134 @Test 135 @RequiresFlagsEnabled(Flags.FLAG_APP_START_INFO) testLauncherStart()136 public void testLauncherStart() throws Exception { 137 clearHistoricalStartInfo(); 138 139 Intent intent = 140 mPackageManager.getLaunchIntentForPackage(STUB_PACKAGE_NAME); 141 mContext.startActivity(intent); 142 143 ApplicationStartInfo info = waitForAppStart(); 144 145 verify(info, STUB_PACKAGE_NAME, STUB_PACKAGE_NAME, intent, 146 ApplicationStartInfo.START_REASON_LAUNCHER, 147 ApplicationStartInfo.START_TYPE_COLD, 148 ApplicationStartInfo.LAUNCH_MODE_STANDARD, 149 ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN, 150 ApplicationStartInfo.START_COMPONENT_ACTIVITY); 151 152 verifyIds(info, 0, mStubPackageUid, mStubPackageUid, mStubPackageUid); 153 } 154 155 @Test 156 @RequiresFlagsEnabled(Flags.FLAG_APP_START_INFO) testActivityStart()157 public void testActivityStart() throws Exception { 158 clearHistoricalStartInfo(); 159 160 executeShellCmd("am start --user %d -n " + STUB_PACKAGE_NAME + "/" + STUB_PACKAGE_NAME 161 + SIMPLE_ACTIVITY, mTestRunningUserId); 162 163 ApplicationStartInfo info = waitForAppStart(); 164 165 Intent intent = new Intent(); 166 intent.setComponent(ComponentName.createRelative(STUB_PACKAGE_NAME, 167 SIMPLE_ACTIVITY)); 168 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 169 170 verify(info, STUB_PACKAGE_NAME, STUB_PACKAGE_NAME, intent, 171 ApplicationStartInfo.START_REASON_START_ACTIVITY, 172 ApplicationStartInfo.START_TYPE_COLD, 173 ApplicationStartInfo.LAUNCH_MODE_STANDARD, 174 ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN, 175 ApplicationStartInfo.START_COMPONENT_ACTIVITY); 176 177 verifyIds(info, 0, mStubPackageUid, mStubPackageUid, mStubPackageUid); 178 } 179 180 /** Test that the wasForceStopped state is accurate in force stopped case. */ 181 @Test 182 @RequiresFlagsEnabled(Flags.FLAG_APP_START_INFO) testWasForceStopped()183 public void testWasForceStopped() throws Exception { 184 clearHistoricalStartInfo(); 185 186 // Start the test app and wait for it to complete 187 executeShellCmd("am start --user %d -n " + STUB_PACKAGE_NAME + "/" + STUB_PACKAGE_NAME 188 + SIMPLE_ACTIVITY, mTestRunningUserId); 189 waitForAppStart(); 190 191 // Now force stop the app 192 executeShellCmd("am force-stop --user %d " + STUB_PACKAGE_NAME, mTestRunningUserId); 193 194 // Clear records again, we don't want to check the previous one. 195 clearHistoricalStartInfo(); 196 197 // Start the app again 198 executeShellCmd("am start --user %d -n " + STUB_PACKAGE_NAME + "/" + STUB_PACKAGE_NAME 199 + SIMPLE_ACTIVITY, mTestRunningUserId); 200 201 // Obtain the start record and confirm it shows having been force stopped 202 ApplicationStartInfo info = waitForAppStart(); 203 assertTrue(info.wasForceStopped()); 204 } 205 206 /** Test that the wasForceStopped state is accurate in not force stopped case. */ 207 @Test 208 @RequiresFlagsEnabled(Flags.FLAG_APP_START_INFO) testWasNotForceStopped()209 public void testWasNotForceStopped() throws Exception { 210 clearHistoricalStartInfo(); 211 212 // Start the test app and wait for it to complete 213 executeShellCmd("am start --user %d -n " + STUB_PACKAGE_NAME + "/" + STUB_PACKAGE_NAME 214 + SIMPLE_ACTIVITY, mTestRunningUserId); 215 waitForAppStart(); 216 217 // Now force stop the app 218 executeShellCmd("am force-stop --user %d " + STUB_PACKAGE_NAME, mTestRunningUserId); 219 220 // Clear records again, we don't want to check the previous one here. 221 clearHistoricalStartInfo(); 222 223 // Start the app with flag to immediately exit 224 executeShellCmd("am start --user %d -n %s/%s%s --ei %s %d", 225 mTestRunningUserId, // test running user ID 226 STUB_PACKAGE_NAME, STUB_PACKAGE_NAME, SIMPLE_ACTIVITY, // package/activity to start 227 REQUEST_KEY_ACTION, REQUEST_VALUE_CRASH); // action to perform 228 sleep(1000); 229 230 // Clear records again, we don't want to check the previous one. 231 clearHistoricalStartInfo(); 232 233 // Start the app again 234 executeShellCmd("am start --user %d -n " + STUB_PACKAGE_NAME + "/" + STUB_PACKAGE_NAME 235 + SIMPLE_ACTIVITY, mTestRunningUserId); 236 237 // Obtain the start record and confirm it shows having not been force stopped 238 ApplicationStartInfo info = waitForAppStart(); 239 assertFalse(info.wasForceStopped()); 240 } 241 242 /** 243 * Start an app and make sure its record exists, then verify 244 * the record is removed when the app is uninstalled. 245 */ 246 @Test 247 @RequiresFlagsEnabled(Flags.FLAG_APP_START_INFO) testAppRemoved()248 public void testAppRemoved() throws Exception { 249 testActivityStart(); 250 251 executeShellCmd("pm uninstall --user %d " + STUB_PACKAGE_NAME, mTestRunningUserId); 252 253 List<ApplicationStartInfo> list = 254 ShellIdentityUtils.invokeMethodWithShellPermissions( 255 STUB_PACKAGE_NAME, 1, 256 mActivityManager::getExternalHistoricalProcessStartReasons, 257 android.Manifest.permission.DUMP); 258 259 assertTrue(list != null && list.size() == 0); 260 } 261 262 /** 263 * Test querying the startup of the process we're currently in. 264 */ 265 @Test 266 @RequiresFlagsEnabled(Flags.FLAG_APP_START_INFO) testQueryThisProcess()267 public void testQueryThisProcess() throws Exception { 268 clearHistoricalStartInfo(); 269 270 ResultReceiverFilter receiver = new ResultReceiverFilter(REPLY_ACTION_COMPLETE, 1); 271 272 // Start the app and have it query its own start info record. 273 executeShellCmd("am start --user %d -n %s/%s%s --ei %s %d", 274 mTestRunningUserId, // test running user ID 275 STUB_PACKAGE_NAME, STUB_PACKAGE_NAME, SIMPLE_ACTIVITY, // package/activity to start 276 REQUEST_KEY_ACTION, REQUEST_VALUE_QUERY_START); // action to perform 277 278 // Wait for complete callback 279 assertEquals(RESULT_PASS, receiver.waitForActivity()); 280 receiver.close(); 281 282 // Confirm that the app confirmed that it successfully obtained record. 283 assertEquals(1, receiver.mIntents.size()); 284 285 Bundle extras = receiver.mIntents.get(0).getExtras(); 286 assertNotNull(extras); 287 288 int status = extras.getInt(REPLY_EXTRA_STATUS_KEY, -1); 289 assertEquals(REPLY_EXTRA_SUCCESS_VALUE, status); 290 } 291 292 /** 293 * Test adding timestamps and verify that the timestamps that were added are still there on a 294 * subsequent query. 295 * 296 * Timestamp is created by test runner process and provided to test app to add to start record 297 * as apps can only add timestamps to their own starts. The subsequent query is performed here 298 * in the test app as querying records can be done from other processes and querying the process 299 * itself is not being tested here. 300 */ 301 @Test 302 @RequiresFlagsEnabled(Flags.FLAG_APP_START_INFO) testAddingTimestamps()303 public void testAddingTimestamps() throws Exception { 304 clearHistoricalStartInfo(); 305 306 final long timestampFirst = System.nanoTime(); 307 final long timestampLast = timestampFirst + 1000L; 308 ResultReceiverFilter receiver = new ResultReceiverFilter(REPLY_ACTION_COMPLETE, 1); 309 310 // Start the app and have it add the provided timestamp to its start record. 311 executeShellCmd("am start --user %d -n %s/%s%s " 312 + "--ei %s %d --ei %s %d --el %s %d --ei %s %d --el %s %d", 313 mTestRunningUserId, // test running user ID 314 STUB_PACKAGE_NAME, STUB_PACKAGE_NAME, SIMPLE_ACTIVITY, // package/activity to start 315 REQUEST_KEY_ACTION, REQUEST_VALUE_ADD_TIMESTAMP, // action to perform 316 REQUEST_KEY_TIMESTAMP_KEY_FIRST, FIRST_TIMESTAMP_KEY, // first timestamp key 317 REQUEST_KEY_TIMESTAMP_VALUE_FIRST, timestampFirst, // first timestamp value 318 REQUEST_KEY_TIMESTAMP_KEY_LAST, LAST_TIMESTAMP_KEY, // last timestamp key 319 REQUEST_KEY_TIMESTAMP_VALUE_LAST, timestampLast); // last timestamp value 320 321 // Wait for complete callback 322 assertEquals(RESULT_PASS, receiver.waitForActivity()); 323 receiver.close(); 324 325 // Get the most recent app start 326 ApplicationStartInfo info = waitForAppStart(); 327 assertNotNull(info); 328 329 // Verify that the timestamps are retrievable and they're the same 330 // when we pull them back out. 331 Map<Integer, Long> timestamps = info.getStartupTimestamps(); 332 long timestampFirstFromInfo = timestamps.get(FIRST_TIMESTAMP_KEY); 333 long timestampLastFromInfo = timestamps.get(LAST_TIMESTAMP_KEY); 334 335 assertEquals(timestampFirst, timestampFirstFromInfo); 336 assertEquals(timestampLast, timestampLastFromInfo); 337 } 338 339 /** 340 * Test that registered listeners are triggered when AppStartInfo is complete. 341 */ 342 @Test 343 @RequiresFlagsEnabled(Flags.FLAG_APP_START_INFO) testTriggerListeners()344 public void testTriggerListeners() throws Exception { 345 clearHistoricalStartInfo(); 346 347 ResultReceiverFilter receiver = new ResultReceiverFilter(REPLY_ACTION_COMPLETE, 1); 348 349 executeShellCmd("am start --user %d -n %s/%s%s --ei %s %d", 350 mTestRunningUserId, // test running user ID 351 STUB_PACKAGE_NAME, STUB_PACKAGE_NAME, SIMPLE_ACTIVITY, // package/activity to start 352 REQUEST_KEY_ACTION, REQUEST_VALUE_LISTENER_ADD_ONE); // action to perform 353 354 // Wait for complete callback 355 assertEquals(RESULT_PASS, receiver.waitForActivity()); 356 receiver.close(); 357 358 // Confirm that the app confirmed that it successfully received a callback. 359 assertEquals(1, receiver.mIntents.size()); 360 361 Bundle extras = receiver.mIntents.get(0).getExtras(); 362 assertNotNull(extras); 363 364 int status = extras.getInt(REPLY_EXTRA_STATUS_KEY, REPLY_STATUS_NONE); 365 assertEquals(REPLY_EXTRA_SUCCESS_VALUE, status); 366 } 367 368 /** 369 * Test that multiple registered listeners are triggered when AppStartInfo is complete. 370 */ 371 @Test 372 @RequiresFlagsEnabled(Flags.FLAG_APP_START_INFO) testTriggerMultipleListeners()373 public void testTriggerMultipleListeners() throws Exception { 374 clearHistoricalStartInfo(); 375 376 ResultReceiverFilter receiver = new ResultReceiverFilter(REPLY_ACTION_COMPLETE, 2); 377 378 executeShellCmd("am start --user %d -n %s/%s%s --ei %s %d", 379 mTestRunningUserId, // test running user ID 380 STUB_PACKAGE_NAME, STUB_PACKAGE_NAME, SIMPLE_ACTIVITY, // package/activity to start 381 REQUEST_KEY_ACTION, 382 REQUEST_VALUE_LISTENER_ADD_MULTIPLE); // action to perform 383 384 // Wait for complete callback 385 assertEquals(RESULT_PASS, receiver.waitForActivity()); 386 receiver.close(); 387 388 // Confirm that the app confirmed that it successfully received a callback. 389 assertEquals(2, receiver.mIntents.size()); 390 391 Bundle extras = receiver.mIntents.get(0).getExtras(); 392 assertNotNull(extras); 393 394 int status = extras.getInt(REPLY_EXTRA_STATUS_KEY, REPLY_STATUS_NONE); 395 assertEquals(REPLY_EXTRA_SUCCESS_VALUE, status); 396 397 extras = receiver.mIntents.get(1).getExtras(); 398 assertNotNull(extras); 399 400 status = extras.getInt(REPLY_EXTRA_STATUS_KEY, REPLY_STATUS_NONE); 401 assertEquals(REPLY_EXTRA_SUCCESS_VALUE, status); 402 } 403 404 /** 405 * Test that a removed listener is not triggered when AppStartInfo is complete. 406 */ 407 @Test 408 @RequiresFlagsEnabled(Flags.FLAG_APP_START_INFO) testRemoveListener()409 public void testRemoveListener() throws Exception { 410 clearHistoricalStartInfo(); 411 412 ResultReceiverFilter receiver = new ResultReceiverFilter(REPLY_ACTION_COMPLETE, 2); 413 414 executeShellCmd("am start --user %d -n %s/%s%s --ei %s %d", 415 mTestRunningUserId, // test running user ID 416 STUB_PACKAGE_NAME, STUB_PACKAGE_NAME, SIMPLE_ACTIVITY, // package/activity to start 417 REQUEST_KEY_ACTION, REQUEST_VALUE_LISTENER_ADD_REMOVE); // action to perform 418 419 // Wait for timeout callback to ensure the broadcast was only sent once for the remaining 420 // listener. If we get a complete result this means that the removed listener was triggered. 421 assertEquals(RESULT_TIMEOUT, receiver.waitForActivity()); 422 receiver.close(); 423 424 // Confirm that the app confirmed that it successfully received a callback on the not 425 // removed listener, and did not receive one on the removed listener. 426 assertEquals(1, receiver.mIntents.size()); 427 428 Bundle extras = receiver.mIntents.get(0).getExtras(); 429 assertNotNull(extras); 430 431 int status = extras.getInt(REPLY_EXTRA_STATUS_KEY, REPLY_STATUS_NONE); 432 assertEquals(REPLY_EXTRA_SUCCESS_VALUE, status); 433 } 434 clearHistoricalStartInfo()435 private void clearHistoricalStartInfo() throws Exception { 436 executeShellCmd("am clear-start-info --user all " + STUB_PACKAGE_NAME); 437 } 438 439 /** Query the app start info object until it indicates the startup is complete. */ waitForAppStart()440 private ApplicationStartInfo waitForAppStart() { 441 List<ApplicationStartInfo> list; 442 443 for (int i = 0; i < MAX_WAITS_FOR_START; i++) { 444 list = ShellIdentityUtils.invokeMethodWithShellPermissions( 445 STUB_PACKAGE_NAME, 1, 446 mActivityManager::getExternalHistoricalProcessStartReasons, 447 android.Manifest.permission.DUMP); 448 449 if (list != null && list.size() == 1 450 && list.get(0).getStartupState() 451 == ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN) { 452 return list.get(0); 453 } 454 sleep(WAIT_FOR_START_MS); 455 } 456 457 fail("The app didn't finish starting in time."); 458 return null; 459 } 460 sleep(long timeout)461 private void sleep(long timeout) { 462 try { 463 Thread.sleep(timeout); 464 } catch (InterruptedException e) { 465 } 466 } 467 468 @FormatMethod executeShellCmd(String cmdFormat, Object... args)469 private String executeShellCmd(String cmdFormat, Object... args) throws Exception { 470 String cmd = String.format(cmdFormat, args); 471 String result = SystemUtil.runShellCommand(mInstrumentation, cmd); 472 Log.d(TAG, String.format("Output for '%s': %s", cmd, result)); 473 return result; 474 } 475 verifyIds(ApplicationStartInfo info, int pid, int realUid, int packageUid, int definingUid)476 private void verifyIds(ApplicationStartInfo info, 477 int pid, int realUid, int packageUid, int definingUid) { 478 assertNotNull(info); 479 480 assertEquals(pid, info.getPid()); 481 assertEquals(realUid, info.getRealUid()); 482 assertEquals(definingUid, info.getDefiningUid()); 483 assertEquals(packageUid, info.getPackageUid()); 484 } 485 486 /** 487 * Verify that the info matches the passed state. 488 * Null arguments are skipped in verification. 489 */ verify(ApplicationStartInfo info, String packageName, String processName, Intent intent, int reason, int startType, int launchMode, int startupState, int startComponent)490 private void verify(ApplicationStartInfo info, 491 String packageName, String processName, Intent intent, 492 int reason, int startType, int launchMode, int startupState, int startComponent) { 493 assertNotNull(info); 494 495 if (packageName != null) { 496 assertTrue(packageName.equals(info.getPackageName())); 497 } 498 499 if (processName != null) { 500 assertTrue(processName.equals(info.getProcessName())); 501 } 502 503 if (intent != null) { 504 assertTrue(intent.filterEquals(info.getIntent())); 505 } 506 507 assertEquals(reason, info.getReason()); 508 assertEquals(startType, info.getStartType()); 509 assertEquals(launchMode, info.getLaunchMode()); 510 assertEquals(startupState, info.getStartupState()); 511 512 if (android.app.Flags.appStartInfoComponent()) { 513 assertEquals(startComponent, info.getStartComponent()); 514 } 515 516 // Check that the appropriate timestamps exist based on the startup state 517 // and that they're in the right order. 518 Map<Integer, Long> timestamps = info.getStartupTimestamps(); 519 if (startupState == ApplicationStartInfo.STARTUP_STATE_STARTED) { 520 Long launchTimestamp = timestamps.get(ApplicationStartInfo.START_TIMESTAMP_LAUNCH); 521 assertTrue(launchTimestamp != null); 522 assertTrue(launchTimestamp > 0); 523 } 524 525 if (startupState == ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN) { 526 Long launchTimestamp = timestamps.get(ApplicationStartInfo.START_TIMESTAMP_LAUNCH); 527 assertTrue(launchTimestamp != null); 528 assertTrue(launchTimestamp > 0); 529 530 Long bindApplicationTimestamp = timestamps.get( 531 ApplicationStartInfo.START_TIMESTAMP_BIND_APPLICATION); 532 assertTrue(bindApplicationTimestamp != null); 533 assertTrue(bindApplicationTimestamp > 0); 534 535 assertTrue(launchTimestamp < bindApplicationTimestamp); 536 537 // TODO(287153617): Add support for START_TIMESTAMP_APPLICATION_ONCREATE 538 // and START_TIMESTAMP_FIRST_FRAME 539 } 540 } 541 542 private class ResultReceiverFilter extends BroadcastReceiver { 543 private String mActivityToFilter; 544 private int mResult = RESULT_TIMEOUT; 545 private int mResultsToWaitFor; 546 private static final int TIMEOUT_IN_MS = 5000; 547 List<Intent> mIntents = new ArrayList<Intent>(); 548 549 // Create the filter with the intent to look for. 550 ResultReceiverFilter(String activityToFilter, int resultsToWaitFor) { 551 mActivityToFilter = activityToFilter; 552 mResultsToWaitFor = resultsToWaitFor; 553 IntentFilter filter = new IntentFilter(); 554 filter.addAction(mActivityToFilter); 555 mInstrumentation.getTargetContext().registerReceiver(this, filter, 556 Context.RECEIVER_EXPORTED); 557 } 558 559 // Turn off the filter. 560 public void close() { 561 mInstrumentation.getTargetContext().unregisterReceiver(this); 562 } 563 564 @Override 565 public void onReceive(Context context, Intent intent) { 566 if (intent.getAction().equals(mActivityToFilter)) { 567 synchronized (this) { 568 mIntents.add(intent); 569 if (mIntents.size() >= mResultsToWaitFor) { 570 mResult = RESULT_PASS; 571 notifyAll(); 572 } 573 } 574 } 575 } 576 waitForActivity()577 public int waitForActivity() throws Exception { 578 AmUtils.waitForBroadcastBarrier(); 579 synchronized (this) { 580 try { 581 wait(TIMEOUT_IN_MS); 582 } catch (InterruptedException e) { 583 } 584 } 585 return mResult; 586 } 587 } 588 } 589