1 /*
2  * Copyright (C) 2016 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.apptransition.tests;
18 
19 import android.app.Instrumentation;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.pm.PackageManager;
23 import android.content.pm.ResolveInfo;
24 import android.os.Bundle;
25 import android.os.ParcelFileDescriptor;
26 import android.os.RemoteException;
27 import android.support.test.InstrumentationRegistry;
28 import android.support.test.launcherhelper.ILauncherStrategy;
29 import android.support.test.launcherhelper.LauncherStrategyFactory;
30 import android.support.test.rule.logging.AtraceLogger;
31 import android.support.test.uiautomator.UiDevice;
32 import android.util.Log;
33 
34 import com.android.launcher3.tapl.LauncherInstrumentation;
35 import com.android.launcher3.tapl.Workspace;
36 
37 import org.junit.After;
38 import org.junit.Assume;
39 import org.junit.Before;
40 import org.junit.Test;
41 
42 import java.io.BufferedReader;
43 import java.io.File;
44 import java.io.FileInputStream;
45 import java.io.IOException;
46 import java.io.InputStream;
47 import java.io.InputStreamReader;
48 import java.util.HashMap;
49 import java.util.HashSet;
50 import java.util.LinkedHashMap;
51 import java.util.List;
52 import java.util.Map;
53 import java.util.Set;
54 
55 public class AppTransitionTests extends Instrumentation {
56 
57     private static final String TAG = AppTransitionTests.class.getSimpleName();
58     private static final int JOIN_TIMEOUT = 10000;
59     private static final int DEFAULT_DROP_CACHE_DELAY = 2000;
60     private static final String DEFAULT_POST_LAUNCH_TIMEOUT = "5000";
61     private static final String DEFAULT_LAUNCH_COUNT = "10";
62     private static final String SUCCESS_MESSAGE = "Status: ok";
63     private static final String HOT_LAUNCH_MESSAGE = "LaunchState: HOT";
64     private static final String TOTAL_TIME_MESSAGE = "TotalTime:";
65     private static final String DROP_CACHE_SCRIPT = "/data/local/tmp/dropCache.sh";
66     private static final String APP_LAUNCH_CMD = "am start -W -n";
67     private static final String FORCE_STOP = "am force-stop ";
68     private static final String PRE_LAUNCH_APPS = "pre_launch_apps";
69     private static final String LAUNCH_APPS = "launch_apps";
70     private static final String KEY_LAUNCH_ITERATIONS = "launch_iteration";
71     private static final String KEY_POST_LAUNCH_TIMEOUT = "postlaunch_timeout";
72     private static final String COLD_LAUNCH = "cold_launch";
73     private static final String HOT_LAUNCH = "hot_launch";
74     private static final String NOT_SURE = "not_sure";
75     private static final String ACTIVITY = "Activity:";
76     private static final String KEY_TRACE_DIRECTORY = "trace_directory";
77     private static final String KEY_TRACE_CATEGORY = "trace_categories";
78     private static final String KEY_TRACE_BUFFERSIZE = "trace_bufferSize";
79     private static final String KEY_TRACE_DUMPINTERVAL = "tracedump_interval";
80     private static final String DEFAULT_TRACE_CATEGORIES = "sched,freq,gfx,view,dalvik,webview,"
81             + "input,wm,disk,am,wm";
82     private static final String DEFAULT_TRACE_BUFFER_SIZE = "20000";
83     private static final String DEFAULT_TRACE_DUMP_INTERVAL = "10";
84     private static final String DELIMITER = ",";
85     private UiDevice mDevice;
86     private LauncherInstrumentation mLauncher;
87     private ILauncherStrategy mLauncherStrategy = null;
88     private Map<String, Intent> mAppLaunchIntentsMapping = null;
89     private String mTraceDirectoryStr = null;
90     private Bundle mResult = new Bundle();
91     private Bundle mArgs;
92     private String mPreAppsList;
93     private int mLaunchIterations;
94     private int mPostLaunchTimeout;
95     private String[] mAppListArray;
96     private String[] mPreAppsListArray;
97     private File mRootTrace = null;
98     private File mRootTraceSubDir = null;
99     private int mTraceBufferSize = 0;
100     private int mTraceDumpInterval = 0;
101     private Set<String> mTraceCategoriesSet = null;
102     private AtraceLogger mAtraceLogger = null;
103     private String mComponentName = null;
104     private Map<String,String> mPreAppsComponentName = new HashMap<String, String>();
105     private boolean mHasLeanback = false;
106 
107     @Before
setUp()108     public void setUp() throws Exception {
109         androidx.test.InstrumentationRegistry.registerInstance(this, new Bundle());
110         mArgs = InstrumentationRegistry.getArguments();
111         mDevice = UiDevice.getInstance(getInstrumentation());
112         LauncherStrategyFactory factory = LauncherStrategyFactory.getInstance(mDevice);
113         mLauncherStrategy = factory.getLauncherStrategy();
114         mLauncher = new LauncherInstrumentation(getInstrumentation());
115         mHasLeanback = hasLeanback(getInstrumentation().getTargetContext());
116 
117         // Inject an instance of instrumentation only if leanback. This enables to launch any app
118         // in the Apps and Games row on leanback launcher.
119         if (mHasLeanback) {
120             factory.getLeanbackLauncherStrategy().setInstrumentation(getInstrumentation());
121         }
122 
123         createLaunchIntentMappings();
124 
125         String appsList = mArgs.getString(LAUNCH_APPS, "");
126         mPreAppsList = mArgs.getString(PRE_LAUNCH_APPS, "");
127         mLaunchIterations = Integer.parseInt(mArgs.getString(KEY_LAUNCH_ITERATIONS,
128                 DEFAULT_LAUNCH_COUNT));
129         mPostLaunchTimeout = Integer.parseInt(mArgs.getString(KEY_POST_LAUNCH_TIMEOUT,
130                 DEFAULT_POST_LAUNCH_TIMEOUT));
131         if (null == appsList || appsList.isEmpty()) {
132             throw new IllegalArgumentException("Need atleast one app to do the"
133                     + " app transition from launcher");
134         }
135         mAppListArray = appsList.split(DELIMITER);
136 
137         // Parse the trace parameters
138         mTraceDirectoryStr = mArgs.getString(KEY_TRACE_DIRECTORY);
139         if (isTracesEnabled()) {
140             String traceCategoriesStr = mArgs
141                     .getString(KEY_TRACE_CATEGORY, DEFAULT_TRACE_CATEGORIES);
142             mTraceBufferSize = Integer.parseInt(mArgs.getString(KEY_TRACE_BUFFERSIZE,
143                     DEFAULT_TRACE_BUFFER_SIZE));
144             mTraceDumpInterval = Integer.parseInt(mArgs.getString(KEY_TRACE_DUMPINTERVAL,
145                     DEFAULT_TRACE_DUMP_INTERVAL));
146             mTraceCategoriesSet = new HashSet<String>();
147             if (!traceCategoriesStr.isEmpty()) {
148                 String[] traceCategoriesSplit = traceCategoriesStr.split(DELIMITER);
149                 for (int i = 0; i < traceCategoriesSplit.length; i++) {
150                     mTraceCategoriesSet.add(traceCategoriesSplit[i]);
151                 }
152             }
153         }
154         mDevice.setOrientationNatural();
155         sleep(mPostLaunchTimeout);
156         cleanTestApps();
157     }
158 
159     @After
tearDown()160     public void tearDown() throws Exception{
161         cleanTestApps();
162         getInstrumentation().sendStatus(0, mResult);
163     }
164 
165     /**
166      * Cold launch given list of apps for given launch count from the launcher screen.
167      * @throws IOException if there are issues in writing atrace file
168      * @throws InterruptedException if there are interrupt during the sleep
169      * @throws RemoteException if press home is not successful
170      */
171     @Test
testColdLaunchFromLauncher()172     public void testColdLaunchFromLauncher() throws IOException, InterruptedException,
173             RemoteException {
174         if (isTracesEnabled()) {
175             createTraceDirectory("testColdLaunchFromLauncher");
176         }
177         // Perform cold app launch from launcher screen
178         for (int appCount = 0; appCount < mAppListArray.length; appCount++) {
179             String appName = mAppListArray[appCount];
180             // Additional launch to account for cold launch
181             if (setupAppLaunch(appName) == ILauncherStrategy.LAUNCH_FAILED_TIMESTAMP) {
182                 continue;
183             }
184             closeApps(new String[] {
185                     appName
186             });
187             getInstrumentation().getUiAutomation()
188                     .executeShellCommand(DROP_CACHE_SCRIPT);
189             sleep(DEFAULT_DROP_CACHE_DELAY);
190             for (int launchCount = 0; launchCount <= mLaunchIterations; launchCount++) {
191                 if (null != mAtraceLogger) {
192                     mAtraceLogger.atraceStart(mTraceCategoriesSet, mTraceBufferSize,
193                             mTraceDumpInterval, mRootTraceSubDir,
194                             String.format("%s-%d", appName, launchCount));
195                 }
196                 mLauncherStrategy.launch(appName, mComponentName.split("\\/")[0]);
197                 if (null != mAtraceLogger) {
198                     mAtraceLogger.atraceStop();
199                 }
200                 sleep(mPostLaunchTimeout);
201                 mDevice.pressHome();
202                 mDevice.waitForIdle();
203                 closeApps(new String[] {
204                         appName
205                 });
206                 sleep(mPostLaunchTimeout);
207                 getInstrumentation().getUiAutomation()
208                         .executeShellCommand(DROP_CACHE_SCRIPT);
209                 sleep(DEFAULT_DROP_CACHE_DELAY);
210             }
211             mComponentName = null;
212             // Update the result with the component name
213             updateResult(appName);
214         }
215     }
216 
217     /**
218      * Hot launch given list of apps for given launch count from the launcher screen. Same method can be
219      * used to test app to home transition delay information as well.
220      * @throws IOException if there are issues in writing atrace file
221      * @throws InterruptedException if there are interrupt during the sleep
222      * @throws RemoteException if press home is not successful
223      */
224     @Test
testHotLaunchFromLauncher()225     public void testHotLaunchFromLauncher() throws IOException, InterruptedException,
226             RemoteException {
227         if (isTracesEnabled()) {
228             createTraceDirectory("testHotLaunchFromLauncher");
229         }
230         for (int appCount = 0; appCount < mAppListArray.length; appCount++) {
231             String appName = mAppListArray[appCount];
232             // Additional launch to account for cold launch
233             if (setupAppLaunch(appName) == ILauncherStrategy.LAUNCH_FAILED_TIMESTAMP) {
234                 continue;
235             }
236             // Hot app launch for given (launch iterations + 1) times.
237             for (int launchCount = 0; launchCount <= (mLaunchIterations); launchCount++) {
238                 if (null != mAtraceLogger) {
239                     mAtraceLogger.atraceStart(mTraceCategoriesSet, mTraceBufferSize,
240                             mTraceDumpInterval, mRootTraceSubDir,
241                             String.format("%s-%d", appName, (launchCount)));
242                 }
243                 mLauncherStrategy.launch(appName, mComponentName.split("\\/")[0]);
244                 sleep(mPostLaunchTimeout);
245                 mDevice.pressHome();
246                 sleep(mPostLaunchTimeout);
247                 if (null != mAtraceLogger) {
248                     mAtraceLogger.atraceStop();
249                 }
250             }
251             mComponentName = null;
252             // Update the result with the component name
253             updateResult(appName);
254         }
255     }
256 
257     /**
258      * Launch an app and press recents for given list of apps for given launch counts.
259      * @throws IOException if there are issues in writing atrace file
260      * @throws InterruptedException if there are interrupt during the sleep
261      * @throws RemoteException if press recent apps is not successful
262      */
263     @Test
testAppToRecents()264     public void testAppToRecents() throws IOException, InterruptedException, RemoteException {
265         Assume.assumeFalse(mHasLeanback);
266         if (isTracesEnabled()) {
267             createTraceDirectory("testAppToRecents");
268         }
269         if (null == mPreAppsList || mPreAppsList.isEmpty()) {
270             throw new IllegalArgumentException("Need atleast few apps in the "
271                     + "recents before starting the test");
272         }
273         mPreAppsListArray = mPreAppsList.split(DELIMITER);
274         mPreAppsComponentName.clear();
275         populateRecentsList();
276         for (int appCount = 0; appCount < mAppListArray.length; appCount++) {
277             String appName = mAppListArray[appCount];
278             long appLaunchTime = -1L;
279             for (int launchCount = 0; launchCount <= mLaunchIterations; launchCount++) {
280                 mLauncherStrategy.launch(appName, mPreAppsComponentName.get(appName).split(
281                         "\\/")[0]);
282                 sleep(mPostLaunchTimeout);
283                 if (null != mAtraceLogger && launchCount > 0) {
284                     mAtraceLogger.atraceStart(mTraceCategoriesSet, mTraceBufferSize,
285                             mTraceDumpInterval, mRootTraceSubDir,
286                             String.format("%s-%d", appName, launchCount - 1));
287                 }
288                 mLauncher.getBackground().switchToOverview();
289                 sleep(mPostLaunchTimeout);
290                 if (null != mAtraceLogger && launchCount > 0) {
291                     mAtraceLogger.atraceStop();
292                 }
293                 mDevice.pressHome();
294                 sleep(mPostLaunchTimeout);
295             }
296             updateResult(appName);
297         }
298     }
299 
300     /**
301      * Hot launch an app from recents for given list of apps for given launch counts.
302      * @throws IOException if there are issues in writing atrace file
303      * @throws InterruptedException if there are interrupt during the sleep
304      * @throws RemoteException if press recent apps is not successful
305      */
306     @Test
testHotLaunchFromRecents()307     public void testHotLaunchFromRecents() throws IOException, InterruptedException,
308             RemoteException {
309         Assume.assumeFalse(mHasLeanback);
310         if (isTracesEnabled()) {
311             createTraceDirectory("testHotLaunchFromRecents");
312         }
313         if (null == mPreAppsList || mPreAppsList.isEmpty()) {
314             throw new IllegalArgumentException("Need atleast few apps in the"
315                     + " recents before starting the test");
316         }
317         mPreAppsListArray = mPreAppsList.split(DELIMITER);
318         mPreAppsComponentName.clear();
319         populateRecentsList();
320         for (int appCount = 0; appCount < mAppListArray.length; appCount++) {
321             String appName = mAppListArray[appCount];
322             // To bring the app to launch as first item from recents task.
323             mLauncherStrategy.launch(appName, mPreAppsComponentName.get(appName).split(
324                     "\\/")[0]);
325             for (int launchCount = 0; launchCount <= mLaunchIterations; launchCount++) {
326                 sleep(mPostLaunchTimeout);
327                 final Workspace workspace = mLauncher.pressHome();
328                 if (null != mAtraceLogger) {
329                     mAtraceLogger.atraceStart(mTraceCategoriesSet, mTraceBufferSize,
330                             mTraceDumpInterval, mRootTraceSubDir,
331                             String.format("%s-%d", appName, (launchCount)));
332                 }
333                 workspace.switchToOverview().getCurrentTask().open();
334                 sleep(mPostLaunchTimeout);
335                 if (null != mAtraceLogger) {
336                     mAtraceLogger.atraceStop();
337                 }
338             }
339             updateResult(appName);
340         }
341     }
342 
343     /**
344      * Launch given app to account for the cold launch and track
345      * component name associated with the app.
346      * @throws RemoteException if press home is not successful
347      * @param appName
348      * @return
349      */
setupAppLaunch(String appName)350     public long setupAppLaunch(String appName) throws RemoteException {
351         long appLaunchTime = startApp(appName, NOT_SURE);
352         if (appLaunchTime == ILauncherStrategy.LAUNCH_FAILED_TIMESTAMP) {
353             return appLaunchTime;
354         }
355         sleep(mPostLaunchTimeout);
356         mDevice.pressHome();
357         sleep(mPostLaunchTimeout);
358         return appLaunchTime;
359     }
360 
361     /**
362      * Create sub directory under the trace root directory to store the trace files captured during
363      * the app transition.
364      * @param subDirectoryName
365      */
createTraceDirectory(String subDirectoryName)366     private void createTraceDirectory(String subDirectoryName) throws IOException {
367         mRootTrace = new File(mTraceDirectoryStr);
368         if (!mRootTrace.exists() && !mRootTrace.mkdirs()) {
369             throw new IOException("Unable to create the trace directory");
370         }
371         mRootTraceSubDir = new File(mRootTrace, subDirectoryName);
372         if (!mRootTraceSubDir.exists() && !mRootTraceSubDir.mkdirs()) {
373             throw new IOException("Unable to create the trace sub directory");
374         }
375         mAtraceLogger = AtraceLogger.getAtraceLoggerInstance(getInstrumentation());
376     }
377 
378     /**
379      * Force stop the given list of apps, clear the cache and return to home screen.
380      * @throws RemoteException if press home is not successful
381      */
cleanTestApps()382     private void cleanTestApps() throws RemoteException {
383         if (null != mPreAppsListArray && mPreAppsListArray.length > 0) {
384             closeApps(mPreAppsListArray);
385         }
386         closeApps(mAppListArray);
387         getInstrumentation().getUiAutomation()
388                         .executeShellCommand(DROP_CACHE_SCRIPT);
389         sleep(DEFAULT_DROP_CACHE_DELAY);
390         mDevice.pressHome();
391         sleep(mPostLaunchTimeout);
392     }
393 
394     /**
395      * Populate the recents list with given list of apps.
396      * @throws RemoteException if press home is not successful
397      */
populateRecentsList()398     private void populateRecentsList() throws RemoteException {
399         for (int preAppCount = 0; preAppCount < mPreAppsListArray.length; preAppCount++) {
400             startApp(mPreAppsListArray[preAppCount], NOT_SURE);
401             mPreAppsComponentName.put(mPreAppsListArray[preAppCount], mComponentName);
402             sleep(mPostLaunchTimeout);
403             mDevice.pressHome();
404             sleep(mPostLaunchTimeout);
405         }
406         mComponentName = null;
407     }
408 
409 
410     /**
411      * To obtain the app name and corresponding intent to launch the app.
412      */
createLaunchIntentMappings()413     private void createLaunchIntentMappings() {
414         mAppLaunchIntentsMapping = new LinkedHashMap<String, Intent>();
415         PackageManager pm = getInstrumentation().getContext()
416                 .getPackageManager();
417         Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
418         intentToResolve.addCategory(mHasLeanback ?
419                         Intent.CATEGORY_LEANBACK_LAUNCHER :
420                         Intent.CATEGORY_LAUNCHER);
421         List<ResolveInfo> ris = pm.queryIntentActivities(intentToResolve, 0);
422         resolveLoop(ris, intentToResolve, pm);
423     }
424 
resolveLoop(List<ResolveInfo> ris, Intent intentToResolve, PackageManager pm)425     private void resolveLoop(List<ResolveInfo> ris, Intent intentToResolve, PackageManager pm) {
426         if (ris == null || ris.isEmpty()) {
427             Log.i(TAG, "Could not find any apps");
428         } else {
429             for (ResolveInfo ri : ris) {
430                 Intent startIntent = new Intent(intentToResolve);
431                 startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
432                         | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
433                 startIntent.setClassName(ri.activityInfo.packageName,
434                         ri.activityInfo.name);
435                 String appName = ri.loadLabel(pm).toString();
436                 if (appName != null) {
437                     mAppLaunchIntentsMapping.put(appName, startIntent);
438                 }
439             }
440         }
441     }
442 
443     /**
444      * Launch an app using the app name and return the app launch time. If app launch time is -1
445      * then app launch is not successful.
446      * @param appName Name of an app as listed in the launcher
447      * @param launchMode Cold or Hot launch
448      * @return
449      */
startApp(String appName, String launchMode)450     private long startApp(String appName, String launchMode) {
451         Log.i(TAG, "Starting " + appName);
452         Intent startIntent = mAppLaunchIntentsMapping.get(appName);
453         if (startIntent == null) {
454             return -1L;
455         }
456         AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent, launchMode);
457         Thread t = new Thread(runnable);
458         t.start();
459         try {
460             t.join(JOIN_TIMEOUT);
461         } catch (InterruptedException e) {
462             // ignore
463         }
464         mComponentName = runnable.getCmpName();
465         return runnable.getResult();
466     }
467 
468     private class AppLaunchRunnable implements Runnable {
469         private Intent mLaunchIntent;
470         private String mLaunchMode;
471         private Long mResult = -1L;
472         private String mCmpName;
473 
AppLaunchRunnable(Intent intent, String launchMode)474         public AppLaunchRunnable(Intent intent, String launchMode) {
475             mLaunchIntent = intent;
476             mLaunchMode = launchMode;
477         }
478 
getResult()479         public Long getResult() {
480             return mResult;
481         }
482 
getCmpName()483         public String getCmpName() {
484             return mCmpName;
485         }
486 
487         @Override
run()488         public void run() {
489             String packageName = mLaunchIntent.getComponent().getPackageName();
490             String componentName = mLaunchIntent.getComponent().flattenToString();
491             String launchCmd = String.format("%s %s", APP_LAUNCH_CMD, componentName);
492             ParcelFileDescriptor parcelDesc = getInstrumentation().getUiAutomation()
493                     .executeShellCommand(launchCmd);
494             mResult = Long.parseLong(parseLaunchTime(parcelDesc));
495         }
496 
497         /**
498          * Returns launch time if app launch is successful otherwise "-1"
499          * @param parcelDesc
500          * @return
501          */
parseLaunchTime(ParcelFileDescriptor parcelDesc)502         private String parseLaunchTime(ParcelFileDescriptor parcelDesc) {
503             String launchTime = "-1";
504             boolean launchSuccess = false;
505             mCmpName = null;
506             try {
507                 InputStream inputStream = new FileInputStream(parcelDesc.getFileDescriptor());
508                 /* SAMPLE OUTPUT : Cold launch
509                 Starting: Intent { cmp=com.google.android.calculator/com.android.calculator2.Calculator }
510                 Status: ok
511                 LaunchState: COLD
512                 Activity: com.google.android.calculator/com.android.calculator2.Calculator
513                 TotalTime: 357
514                 WaitTime: 377
515                 Complete*/
516                 /* SAMPLE OUTPUT : Hot launch
517                 Starting: Intent { cmp=com.google.android.calculator/com.android.calculator2.Calculator }
518                 Warning: Activity not started, its current task has been brought to the front
519                 Status: ok
520                 LaunchState: HOT
521                 Activity: com.google.android.calculator/com.android.calculator2.CalculatorGoogle
522                 TotalTime: 60
523                 WaitTime: 67
524                 Complete*/
525                 StringBuilder appLaunchOuput = new StringBuilder();
526                 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
527                         inputStream));
528                 String line = null;
529                 final boolean maybeHotLaunch = mLaunchMode.contains(HOT_LAUNCH) ||
530                         mLaunchMode.contains(NOT_SURE);
531                 while ((line = bufferedReader.readLine()) != null) {
532                     if (line.startsWith(SUCCESS_MESSAGE)) {
533                         launchSuccess = true;
534                     }
535                     if (!launchSuccess) {
536                         continue;
537                     }
538 
539                     if (line.startsWith(HOT_LAUNCH_MESSAGE) && (!maybeHotLaunch)){
540                         Log.w(TAG, "Error did not expect a hot launch");
541                         break;
542                     }
543 
544                     if (line.startsWith(TOTAL_TIME_MESSAGE)) {
545                         String launchSplit[] = line.split(":");
546                         launchTime = launchSplit[1].trim();
547                     }
548                     // Needed to update the component name if the very first launch activity
549                     // is different from hot launch activity (i.e YouTube)
550                     if (maybeHotLaunch && line.startsWith(ACTIVITY)) {
551                         String activitySplit[] = line.split(":");
552                         mCmpName = activitySplit[1].trim();
553                     }
554                 }
555                 inputStream.close();
556             } catch (IOException e) {
557                 Log.w(TAG, "Error writing the launch file", e);
558             }
559             return launchTime;
560         }
561     }
562 
563     /**
564      * To force stop the given list of apps based on the app name.
565      * @param appNames
566      */
closeApps(String[] appNames)567     private void closeApps(String[] appNames) {
568         int length = appNames == null ? 0 : appNames.length;
569         for (int i = 0; i < length; i++) {
570             Intent startIntent = mAppLaunchIntentsMapping.get(appNames[i]);
571             if (startIntent != null) {
572                 String packageName = startIntent.getComponent().getPackageName();
573 
574                 getInstrumentation().getUiAutomation().executeShellCommand(
575                         FORCE_STOP + packageName);
576             }
577             sleep(1000);
578         }
579         sleep(mPostLaunchTimeout);
580     }
581 
582     /**
583      * @return
584      */
isTracesEnabled()585     private boolean isTracesEnabled(){
586         return (null != mTraceDirectoryStr && !mTraceDirectoryStr.isEmpty());
587     }
588 
589     /**
590      * Update the result status
591      * @param appName
592      */
updateResult(String appName)593     private void updateResult(String appName) {
594             // Component name needed for parsing the events log
595             if (null != mComponentName) {
596                 mResult.putString(appName, mComponentName);
597             } else {
598                 // Component name needed for parsing the events log
599                 mResult.putString(appName, mAppLaunchIntentsMapping.get(appName).
600                         getComponent().flattenToString());
601             }
602     }
603 
604 
605     /**
606      * To sleep for given millisecs.
607      * @param time
608      */
sleep(int time)609     private void sleep(int time) {
610         try {
611             Thread.sleep(time);
612         } catch (InterruptedException e) {
613             // ignore
614         }
615     }
616 
617     /**
618      * Return the instrumentation from the registry.
619      * @return
620      */
getInstrumentation()621     private Instrumentation getInstrumentation() {
622         return InstrumentationRegistry.getInstrumentation();
623     }
624 
625     /**
626      * @return True if we're running on Android TV.
627      */
hasLeanback(Context context)628     private boolean hasLeanback(Context context) {
629         return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
630     }
631 }
632 
633