• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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