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.performance.tests;
18 
19 import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner;
20 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
21 import com.android.loganalysis.item.LatencyItem;
22 import com.android.loganalysis.item.TransitionDelayItem;
23 import com.android.loganalysis.parser.EventsLogParser;
24 import com.android.tradefed.config.Option;
25 import com.android.tradefed.device.DeviceNotAvailableException;
26 import com.android.tradefed.device.IFileEntry;
27 import com.android.tradefed.device.ITestDevice;
28 import com.android.tradefed.device.LogcatReceiver;
29 import com.android.tradefed.log.LogUtil.CLog;
30 import com.android.tradefed.result.CollectingTestListener;
31 import com.android.tradefed.result.FileInputStreamSource;
32 import com.android.tradefed.result.ITestInvocationListener;
33 import com.android.tradefed.result.InputStreamSource;
34 import com.android.tradefed.result.LogDataType;
35 import com.android.tradefed.result.TestResult;
36 import com.android.tradefed.result.TestRunResult;
37 import com.android.tradefed.testtype.IDeviceTest;
38 import com.android.tradefed.testtype.IRemoteTest;
39 import com.android.tradefed.util.FileUtil;
40 import com.android.tradefed.util.ListInstrumentationParser;
41 import com.android.tradefed.util.ListInstrumentationParser.InstrumentationTarget;
42 import com.android.tradefed.util.SimpleStats;
43 import com.android.tradefed.util.StreamUtil;
44 import com.android.tradefed.util.ZipUtil;
45 import com.android.tradefed.util.proto.TfMetricProtoUtil;
46 
47 import java.io.BufferedReader;
48 import java.io.File;
49 import java.io.IOException;
50 import java.io.InputStream;
51 import java.io.InputStreamReader;
52 import java.util.ArrayList;
53 import java.util.Collection;
54 import java.util.HashMap;
55 import java.util.LinkedHashMap;
56 import java.util.List;
57 import java.util.Map;
58 import java.util.concurrent.TimeUnit;
59 
60 /**
61  * Test that drives the transition delays during different user behavior like cold launch from
62  * launcher, hot launch from recent etc.This class invokes the instrumentation test apk that does
63  * the transition and captures the events logs during the transition and parse them and report in
64  * the dashboard.
65  */
66 public class AppTransitionTests implements IRemoteTest, IDeviceTest {
67 
68     private static final String PACKAGE_NAME = "com.android.apptransition.tests";
69     private static final String CLASS_NAME = "com.android.apptransition.tests.AppTransitionTests";
70     private static final String TEST_COLD_LAUNCH = "testColdLaunchFromLauncher";
71     private static final String TEST_HOT_LAUNCH = "testHotLaunchFromLauncher";
72     private static final String TEST_APP_TO_HOME = "testAppToHome";
73     private static final String TEST_APP_TO_RECENT = "testAppToRecents";
74     private static final String TEST_LATENCY = "testLatency";
75     private static final String TEST_HOT_LAUNCH_FROM_RECENTS = "testHotLaunchFromRecents";
76     private static final String DROP_CACHE_SCRIPT_FILE = "dropCache";
77     private static final String SCRIPT_EXTENSION = ".sh";
78     private static final String DROP_CACHE_CMD = "echo 3 > /proc/sys/vm/drop_caches";
79     private static final String DEVICE_TEMPORARY_DIR_PATH = "/data/local/tmp/";
80     private static final String REMOVE_CMD = "rm -rf %s/%s";
81     private static final String EVENTS_CMD = "logcat -v threadtime -b events";
82     private static final String EVENTS_CLEAR_CMD = "logcat -v threadtime -b events -c";
83     private static final String EVENTS_LOG = "events_log";
84     private static final long EVENTS_LOGCAT_SIZE = 80 * 1024 * 1024;
85 
86     @Option(
87             name = "cold-apps",
88             description =
89                     "Apps used for cold app launch"
90                             + " transition delay testing from launcher screen.")
91     private String mColdLaunchApps = null;
92 
93     @Option(
94             name = "hot-apps",
95             description =
96                     "Apps used for hot app launch"
97                             + " transition delay testing from launcher screen.")
98     private String mHotLaunchApps = null;
99 
100     @Option(
101             name = "pre-launch-apps",
102             description =
103                     "Apps used for populating the"
104                             + " recents apps list before starting app_to_recents or hot_app_from_recents"
105                             + " testing.")
106     private String mPreLaunchApps = null;
107 
108     @Option(
109             name = "apps-to-recents",
110             description = "Apps used for app to recents" + " transition delay testing.")
111     private String mAppToRecents = null;
112 
113     @Option(
114             name = "hot-apps-from-recents",
115             description =
116                     "Apps used for hot" + " launch app from recents list transition delay testing.")
117     private String mRecentsToApp = null;
118 
119     @Option(
120             name = "launch-iteration",
121             description = "Iterations for launching each app to" + "test the transition delay.")
122     private int mLaunchIteration = 10;
123 
124     @Option(
125             name = "trace-directory",
126             description =
127                     "Directory to store the trace files"
128                             + "while testing ther app transition delay.")
129     private String mTraceDirectory = null;
130 
131     @Option(name = "runner", description = "The instrumentation test runner class name to use.")
132     private String mRunnerName = "";
133 
134     @Option(name = "run-arg", description = "Additional test specific arguments to provide.")
135     private Map<String, String> mArgMap = new LinkedHashMap<String, String>();
136 
137     @Option(name = "launcher-activity", description = "Home activity name")
138     private String mLauncherActivity = ".NexusLauncherActivity";
139 
140     @Option(
141             name = "class",
142             description =
143                     "test class to run, may be repeated; multiple classess will be run"
144                             + " in the same order as provided in command line")
145     private List<String> mClasses = new ArrayList<String>();
146 
147     @Option(name = "package", description = "The manifest package name of the UI test package")
148     private String mPackage = "com.android.apptransition.tests";
149 
150     @Option(name = "latency-iteration", description = "Iterations to be used in the latency tests.")
151     private int mLatencyIteration = 10;
152 
153     @Option(
154             name = "timeout",
155             description =
156                     "Aborts the test run if any test takes longer than the specified number "
157                             + "of milliseconds. For no timeout, set to 0.",
158             isTimeVal = true)
159     private long mTestTimeout = 45 * 60 * 1000; // default to 45 minutes
160 
161     @Option(
162             name = "isolated-storage",
163             description =
164                     "If set to false, the '--no-isolated-storage' flag will be passed to the am "
165                             + "instrument command. Only works for Q or later."
166         )
167     private boolean mIsolatedStorage = true;
168 
169     private ITestDevice mDevice = null;
170     private IRemoteAndroidTestRunner mRunner = null;
171     private CollectingTestListener mLaunchListener = null;
172     private LogcatReceiver mLaunchEventsLogs = null;
173     private EventsLogParser mEventsLogParser = new EventsLogParser();
174     private ITestInvocationListener mListener = null;
175     private ListInstrumentationParser mListInstrumentationParser = null;
176 
177     @Override
run(ITestInvocationListener listener)178     public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
179 
180         addDropCacheScriptFile();
181         mListener = listener;
182 
183         if (null != mColdLaunchApps && !mColdLaunchApps.isEmpty()) {
184             try {
185                 mRunner = createRemoteAndroidTestRunner(TEST_COLD_LAUNCH, mColdLaunchApps, null);
186                 mLaunchListener = new CollectingTestListener();
187                 mLaunchEventsLogs =
188                         new LogcatReceiver(getDevice(), EVENTS_CMD, EVENTS_LOGCAT_SIZE, 0);
189                 startEventsLogs(mLaunchEventsLogs, TEST_COLD_LAUNCH);
190                 runTests();
191                 analyzeColdLaunchDelay(parseTransitionDelayInfo());
192             } finally {
193                 stopEventsLogs(mLaunchEventsLogs, TEST_COLD_LAUNCH);
194                 if (isTraceDirEnabled()) {
195                     uploadTraceFiles(listener, TEST_COLD_LAUNCH);
196                 }
197             }
198         }
199 
200         if (null != mHotLaunchApps && !mHotLaunchApps.isEmpty()) {
201             try {
202                 mRunner = createRemoteAndroidTestRunner(TEST_HOT_LAUNCH, mHotLaunchApps, null);
203                 mLaunchListener = new CollectingTestListener();
204                 mLaunchEventsLogs =
205                         new LogcatReceiver(getDevice(), EVENTS_CMD, EVENTS_LOGCAT_SIZE, 0);
206                 startEventsLogs(mLaunchEventsLogs, TEST_HOT_LAUNCH);
207                 runTests();
208                 analyzeHotLaunchDelay(parseTransitionDelayInfo());
209             } finally {
210                 stopEventsLogs(mLaunchEventsLogs, TEST_HOT_LAUNCH);
211                 if (isTraceDirEnabled()) {
212                     uploadTraceFiles(listener, TEST_HOT_LAUNCH);
213                 }
214             }
215         }
216 
217         if ((null != mAppToRecents && !mAppToRecents.isEmpty())
218                 && (null != mPreLaunchApps && !mPreLaunchApps.isEmpty())) {
219             try {
220                 mRunner =
221                         createRemoteAndroidTestRunner(
222                                 TEST_APP_TO_RECENT, mAppToRecents, mPreLaunchApps);
223                 mLaunchListener = new CollectingTestListener();
224                 mLaunchEventsLogs =
225                         new LogcatReceiver(getDevice(), EVENTS_CMD, EVENTS_LOGCAT_SIZE, 0);
226                 startEventsLogs(mLaunchEventsLogs, TEST_APP_TO_RECENT);
227                 runTests();
228                 analyzeAppToRecentsDelay(parseTransitionDelayInfo());
229             } finally {
230                 stopEventsLogs(mLaunchEventsLogs, TEST_APP_TO_RECENT);
231                 if (isTraceDirEnabled()) {
232                     uploadTraceFiles(listener, TEST_APP_TO_RECENT);
233                 }
234             }
235         }
236 
237         if ((null != mRecentsToApp && !mRecentsToApp.isEmpty())
238                 && (null != mPreLaunchApps && !mPreLaunchApps.isEmpty())) {
239             try {
240                 mRunner =
241                         createRemoteAndroidTestRunner(
242                                 TEST_HOT_LAUNCH_FROM_RECENTS, mRecentsToApp, mPreLaunchApps);
243                 mLaunchListener = new CollectingTestListener();
244                 mLaunchEventsLogs =
245                         new LogcatReceiver(getDevice(), EVENTS_CMD, EVENTS_LOGCAT_SIZE, 0);
246                 startEventsLogs(mLaunchEventsLogs, TEST_HOT_LAUNCH_FROM_RECENTS);
247                 runTests();
248                 analyzeRecentsToAppDelay(parseTransitionDelayInfo());
249             } finally {
250                 stopEventsLogs(mLaunchEventsLogs, TEST_HOT_LAUNCH_FROM_RECENTS);
251                 if (isTraceDirEnabled()) {
252                     uploadTraceFiles(listener, TEST_HOT_LAUNCH_FROM_RECENTS);
253                 }
254             }
255         }
256 
257         if (!mClasses.isEmpty()) {
258             try {
259                 mRunner = createTestRunner();
260                 mLaunchListener = new CollectingTestListener();
261                 mLaunchEventsLogs =
262                         new LogcatReceiver(getDevice(), EVENTS_CMD, EVENTS_LOGCAT_SIZE, 0);
263                 startEventsLogs(mLaunchEventsLogs, TEST_LATENCY);
264                 runTests();
265                 analyzeLatencyInfo(parseLatencyInfo());
266             } finally {
267                 stopEventsLogs(mLaunchEventsLogs, TEST_LATENCY);
268                 if (isTraceDirEnabled()) {
269                     uploadTraceFiles(listener, TEST_LATENCY);
270                 }
271             }
272         }
273     }
274 
runTests()275     private void runTests() throws DeviceNotAvailableException {
276         mDevice.runInstrumentationTests(mRunner, mLaunchListener);
277         final TestRunResult runResults = mLaunchListener.getCurrentRunResults();
278         if (runResults.isRunFailure()) {
279             throw new RuntimeException("Error: test run failed!");
280         }
281         if (runResults.hasFailedTests()) {
282             throw new RuntimeException("Error: some tests failed!");
283         }
284     }
285 
286     /**
287      * Push drop cache script file to test device used for clearing the cache between the app
288      * launches.
289      *
290      * @throws DeviceNotAvailableException
291      */
addDropCacheScriptFile()292     private void addDropCacheScriptFile() throws DeviceNotAvailableException {
293         File scriptFile = null;
294         try {
295             scriptFile = FileUtil.createTempFile(DROP_CACHE_SCRIPT_FILE, SCRIPT_EXTENSION);
296             FileUtil.writeToFile(DROP_CACHE_CMD, scriptFile);
297             getDevice()
298                     .pushFile(
299                             scriptFile,
300                             String.format(
301                                     "%s%s.sh", DEVICE_TEMPORARY_DIR_PATH, DROP_CACHE_SCRIPT_FILE));
302         } catch (IOException ioe) {
303             CLog.e("Unable to create the Script file");
304             CLog.e(ioe);
305         }
306         getDevice()
307                 .executeShellCommand(
308                         String.format(
309                                 "chmod 755 %s%s.sh",
310                                 DEVICE_TEMPORARY_DIR_PATH, DROP_CACHE_SCRIPT_FILE));
311         scriptFile.delete();
312     }
313 
314     /**
315      * Method to create the runner with given list of arguments
316      *
317      * @return the {@link IRemoteAndroidTestRunner} to use.
318      * @throws DeviceNotAvailableException
319      */
createRemoteAndroidTestRunner( String testName, String launchApps, String preLaunchApps)320     IRemoteAndroidTestRunner createRemoteAndroidTestRunner(
321             String testName, String launchApps, String preLaunchApps)
322             throws DeviceNotAvailableException {
323         if(mRunnerName.isEmpty()) {
324             mRunnerName = queryRunnerName();
325         }
326         RemoteAndroidTestRunner runner =
327                 new RemoteAndroidTestRunner(PACKAGE_NAME, mRunnerName, mDevice.getIDevice());
328         runner.setMethodName(CLASS_NAME, testName);
329         runner.addInstrumentationArg("launch_apps", launchApps);
330         runner.setMaxTimeout(mTestTimeout, TimeUnit.MILLISECONDS);
331         if (null != preLaunchApps && !preLaunchApps.isEmpty()) {
332             runner.addInstrumentationArg("pre_launch_apps", preLaunchApps);
333         }
334         runner.addInstrumentationArg("launch_iteration", Integer.toString(mLaunchIteration));
335         for (Map.Entry<String, String> entry : getTestRunArgMap().entrySet()) {
336             runner.addInstrumentationArg(entry.getKey(), entry.getValue());
337         }
338         if (isTraceDirEnabled()) {
339             mDevice.executeShellCommand(String.format("rm -rf %s/%s", mTraceDirectory, testName));
340             runner.addInstrumentationArg("trace_directory", mTraceDirectory);
341         }
342 
343         String runOptions = "";
344 
345         // isolated-storage flag only exists in Q and after.
346         if (!mIsolatedStorage && getDevice().checkApiLevelAgainstNextRelease(29)) {
347             runOptions += "--no-isolated-storage ";
348         }
349 
350         runner.setRunOptions(runOptions);
351 
352         return runner;
353     }
354 
355     /**
356      * Get the {@link ListInstrumentationParser} used to parse 'pm list instrumentation' queries.
357      */
getListInstrumentationParser()358     protected ListInstrumentationParser getListInstrumentationParser() {
359         if (mListInstrumentationParser == null) {
360             mListInstrumentationParser = new ListInstrumentationParser();
361         }
362         return mListInstrumentationParser;
363     }
364 
365     /**
366      * Query the device for a test runner to use.
367      *
368      * @return the first test runner name that matches the package or null if we don't find any.
369      * @throws DeviceNotAvailableException
370      */
queryRunnerName()371     protected String queryRunnerName() throws DeviceNotAvailableException {
372         ListInstrumentationParser parser = getListInstrumentationParser();
373         getDevice().executeShellCommand("pm list instrumentation", parser);
374 
375         for (InstrumentationTarget target : parser.getInstrumentationTargets()) {
376             if (PACKAGE_NAME.equals(target.packageName)) {
377                 return target.runnerName;
378             }
379         }
380         throw new RuntimeException(
381                 String.format("Unable to determine runner name for package: %s", PACKAGE_NAME));
382     }
383 
384     /**
385      * Method to create the runner with given runner name, package and list of classes.
386      *
387      * @return the {@link IRemoteAndroidTestRunner} to use.
388      * @throws DeviceNotAvailableException
389      */
createTestRunner()390     IRemoteAndroidTestRunner createTestRunner() throws DeviceNotAvailableException {
391         IRemoteAndroidTestRunner runner =
392                 new RemoteAndroidTestRunner(mPackage, mRunnerName, getDevice().getIDevice());
393         if (!mClasses.isEmpty()) {
394             runner.setClassNames(mClasses.toArray(new String[] {}));
395         }
396         runner.addInstrumentationArg("iteration_count", Integer.toString(mLatencyIteration));
397         for (Map.Entry<String, String> entry : getTestRunArgMap().entrySet()) {
398             runner.addInstrumentationArg(entry.getKey(), entry.getValue());
399         }
400         if (isTraceDirEnabled()) {
401             mDevice.executeShellCommand(
402                     String.format("rm -rf %s/%s", mTraceDirectory, TEST_LATENCY));
403             runner.addInstrumentationArg(
404                     "trace_directory", String.format("%s/%s", mTraceDirectory, TEST_LATENCY));
405         }
406         return runner;
407     }
408 
409     /**
410      * Start the events logcat
411      *
412      * @param logReceiver
413      * @param testName
414      * @throws DeviceNotAvailableException
415      */
startEventsLogs(LogcatReceiver logReceiver, String testName)416     private void startEventsLogs(LogcatReceiver logReceiver, String testName)
417             throws DeviceNotAvailableException {
418         getDevice().clearLogcat();
419         getDevice().executeShellCommand(EVENTS_CLEAR_CMD);
420         logReceiver.start();
421     }
422 
423     /**
424      * Stop the events logcat and upload the data to sponge
425      *
426      * @param logReceiver
427      */
stopEventsLogs(LogcatReceiver logReceiver, String launchDesc)428     private void stopEventsLogs(LogcatReceiver logReceiver, String launchDesc) {
429         try (InputStreamSource logcatData = logReceiver.getLogcatData()) {
430             mListener.testLog(
431                     String.format("%s-%s", EVENTS_LOG, launchDesc), LogDataType.TEXT, logcatData);
432         } finally {
433             logReceiver.stop();
434         }
435     }
436 
437     /**
438      * Pull the trace files if exist under destDirectory and log it.
439      *
440      * @param listener test result listener
441      * @param srcDirectory source directory in the device where the files are copied to the local
442      *     tmp directory
443      * @param subFolderName to store the files corresponding to the test
444      * @throws DeviceNotAvailableException
445      * @throws IOException
446      */
logTraceFiles( ITestInvocationListener listener, String srcDirectory, String subFolderName)447     private void logTraceFiles(
448             ITestInvocationListener listener, String srcDirectory, String subFolderName)
449             throws DeviceNotAvailableException, IOException {
450         File tmpDestDir = null;
451         FileInputStreamSource streamSource = null;
452         File zipFile = null;
453         try {
454             tmpDestDir = FileUtil.createTempDir(subFolderName);
455             IFileEntry srcDir =
456                     mDevice.getFileEntry(String.format("%s/%s", srcDirectory, subFolderName));
457             // Files are retrieved from source directory in device
458             if (srcDir != null) {
459                 for (IFileEntry file : srcDir.getChildren(false)) {
460                     File pulledFile = new File(tmpDestDir, file.getName());
461                     if (!mDevice.pullFile(file.getFullPath(), pulledFile)) {
462                         throw new IOException("Not able to pull the file from test device");
463                     }
464                 }
465                 zipFile = ZipUtil.createZip(tmpDestDir);
466                 streamSource = new FileInputStreamSource(zipFile);
467                 listener.testLog(tmpDestDir.getName(), LogDataType.ZIP, streamSource);
468             }
469         } finally {
470             FileUtil.recursiveDelete(tmpDestDir);
471             StreamUtil.cancel(streamSource);
472             FileUtil.deleteFile(zipFile);
473         }
474     }
475 
476     /**
477      * To upload the trace files stored in the traceDirectory in device to sponge.
478      *
479      * @param listener
480      * @param subFolderName
481      * @throws DeviceNotAvailableException
482      */
uploadTraceFiles(ITestInvocationListener listener, String subFolderName)483     private void uploadTraceFiles(ITestInvocationListener listener, String subFolderName)
484             throws DeviceNotAvailableException {
485         try {
486             logTraceFiles(listener, mTraceDirectory, subFolderName);
487         } catch (IOException ioe) {
488             CLog.e("Problem in uploading the log files.");
489             CLog.e(ioe);
490         }
491         mDevice.executeShellCommand(String.format(REMOVE_CMD, mTraceDirectory, subFolderName));
492     }
493 
494     /** Returns false if the traceDirectory is not set. */
isTraceDirEnabled()495     private boolean isTraceDirEnabled() {
496         return (null != mTraceDirectory && !mTraceDirectory.isEmpty());
497     }
498 
499     /** To parse the transition delay info from the events log. */
parseTransitionDelayInfo()500     private List<TransitionDelayItem> parseTransitionDelayInfo() {
501         List<TransitionDelayItem> transitionDelayItems = null;
502         try (InputStreamSource logcatData = mLaunchEventsLogs.getLogcatData();
503                 InputStream logcatStream = logcatData.createInputStream();
504                 InputStreamReader streamReader = new InputStreamReader(logcatStream);
505                 BufferedReader reader = new BufferedReader(streamReader)) {
506             transitionDelayItems = mEventsLogParser.parseTransitionDelayInfo(reader);
507         } catch (IOException e) {
508             CLog.e("Problem in parsing the transition delay items from events log");
509             CLog.e(e);
510         }
511         return transitionDelayItems;
512     }
513 
514     /** To parse the latency info from the events log. */
parseLatencyInfo()515     private List<LatencyItem> parseLatencyInfo() {
516         List<LatencyItem> latencyItems = null;
517         try (InputStreamSource logcatData = mLaunchEventsLogs.getLogcatData();
518                 InputStream logcatStream = logcatData.createInputStream();
519                 InputStreamReader streamReader = new InputStreamReader(logcatStream);
520                 BufferedReader reader = new BufferedReader(streamReader)) {
521             latencyItems = mEventsLogParser.parseLatencyInfo(reader);
522         } catch (IOException e) {
523             CLog.e("Problem in parsing the latency items from events log");
524             CLog.e(e);
525         }
526         return latencyItems;
527     }
528 
529     /**
530      * Analyze and report the cold launch transition delay from launcher screen.
531      *
532      * @param transitionDelayItems
533      */
analyzeColdLaunchDelay(List<TransitionDelayItem> transitionDelayItems)534     private void analyzeColdLaunchDelay(List<TransitionDelayItem> transitionDelayItems) {
535         Map<String, String> cmpNameAppMap = reverseAppCmpInfoMap(getAppComponentInfoMap());
536         Map<String, List<Long>> appKeyTransitionDelayMap = new HashMap<>();
537         // Handle launcher to cold app launch transition
538         for (TransitionDelayItem delayItem : transitionDelayItems) {
539             if (cmpNameAppMap.containsKey(delayItem.getComponentName())) {
540                 String appName = cmpNameAppMap.get(delayItem.getComponentName());
541                 if (delayItem.getStartingWindowDelay() != null) {
542                     if (appKeyTransitionDelayMap.containsKey(appName)) {
543                         appKeyTransitionDelayMap
544                                 .get(appName)
545                                 .add(delayItem.getStartingWindowDelay());
546                     } else {
547                         List<Long> delayTimeList = new ArrayList<Long>();
548                         delayTimeList.add(delayItem.getStartingWindowDelay());
549                         appKeyTransitionDelayMap.put(appName, delayTimeList);
550                     }
551                 }
552             }
553         }
554         removeAdditionalLaunchInfo(appKeyTransitionDelayMap);
555         computeAndUploadResults(TEST_COLD_LAUNCH, appKeyTransitionDelayMap);
556     }
557 
558     /**
559      * Analyze and report the hot launch transition delay from launcher and app to home transition
560      * delay. Keep track of launcher to app transition delay which immediately followed by app to
561      * home transition. Skip the initial cold launch on the apps.
562      *
563      * @param transitionDelayItems
564      */
analyzeHotLaunchDelay(List<TransitionDelayItem> transitionDelayItems)565     private void analyzeHotLaunchDelay(List<TransitionDelayItem> transitionDelayItems) {
566         Map<String, String> cmpNameAppMap = reverseAppCmpInfoMap(getAppComponentInfoMap());
567         Map<String, List<Long>> appKeyTransitionDelayMap = new HashMap<>();
568         Map<String, List<Long>> appToHomeKeyTransitionDelayMap = new HashMap<>();
569         String prevAppName = null;
570         for (TransitionDelayItem delayItem : transitionDelayItems) {
571             // Handle app to home transition
572             if (null != prevAppName) {
573                 if (delayItem.getComponentName().contains(mLauncherActivity)) {
574                     if (appToHomeKeyTransitionDelayMap.containsKey(prevAppName)) {
575                         appToHomeKeyTransitionDelayMap
576                                 .get(prevAppName)
577                                 .add(delayItem.getWindowDrawnDelay());
578                     } else {
579                         List<Long> delayTimeList = new ArrayList<Long>();
580                         delayTimeList.add(delayItem.getWindowDrawnDelay());
581                         appToHomeKeyTransitionDelayMap.put(prevAppName, delayTimeList);
582                     }
583                     prevAppName = null;
584                 }
585                 continue;
586             }
587             // Handle launcher to hot app launch transition
588             if (cmpNameAppMap.containsKey(delayItem.getComponentName())) {
589                 // Not to consider the first cold launch for the app.
590                 if (delayItem.getStartingWindowDelay() != null) {
591                     continue;
592                 }
593                 String appName = cmpNameAppMap.get(delayItem.getComponentName());
594                 if (appKeyTransitionDelayMap.containsKey(appName)) {
595                     appKeyTransitionDelayMap.get(appName).add(delayItem.getTransitionDelay());
596                 } else {
597                     List<Long> delayTimeList = new ArrayList<Long>();
598                     delayTimeList.add(delayItem.getTransitionDelay());
599                     appKeyTransitionDelayMap.put(appName, delayTimeList);
600                 }
601                 prevAppName = appName;
602             }
603         }
604         // Remove the first hot launch info through intents
605         removeAdditionalLaunchInfo(appKeyTransitionDelayMap);
606         computeAndUploadResults(TEST_HOT_LAUNCH, appKeyTransitionDelayMap);
607         removeAdditionalLaunchInfo(appToHomeKeyTransitionDelayMap);
608         computeAndUploadResults(TEST_APP_TO_HOME, appToHomeKeyTransitionDelayMap);
609     }
610 
611     /**
612      * Analyze and report app to recents transition delay info.
613      *
614      * @param transitionDelayItems
615      */
analyzeAppToRecentsDelay(List<TransitionDelayItem> transitionDelayItems)616     private void analyzeAppToRecentsDelay(List<TransitionDelayItem> transitionDelayItems) {
617         Map<String, String> cmpNameAppMap = reverseAppCmpInfoMap(getAppComponentInfoMap());
618         Map<String, List<Long>> appKeyTransitionDelayMap = new HashMap<>();
619         String prevAppName = null;
620         for (TransitionDelayItem delayItem : transitionDelayItems) {
621             if (delayItem.getComponentName().contains(mLauncherActivity)) {
622                 if (appKeyTransitionDelayMap.containsKey(prevAppName)) {
623                     appKeyTransitionDelayMap.get(prevAppName).add(delayItem.getWindowDrawnDelay());
624                 } else {
625                     if (null != prevAppName) {
626                         List<Long> delayTimeList = new ArrayList<Long>();
627                         delayTimeList.add(delayItem.getWindowDrawnDelay());
628                         appKeyTransitionDelayMap.put(prevAppName, delayTimeList);
629                     }
630                 }
631                 prevAppName = null;
632                 continue;
633             }
634 
635             if (cmpNameAppMap.containsKey(delayItem.getComponentName())) {
636                 prevAppName = cmpNameAppMap.get(delayItem.getComponentName());
637             }
638         }
639         // Removing the first cold launch to recents transition delay.
640         removeAdditionalLaunchInfo(appKeyTransitionDelayMap);
641         computeAndUploadResults(TEST_APP_TO_RECENT, appKeyTransitionDelayMap);
642     }
643 
644     /**
645      * Analyze and report recents to hot app launch delay info. Skip the initial cold launch
646      * transition delay on the apps. Also the first launch cannot always be the cold launch because
647      * the apps could be part of preapps list. The transition delay is tracked based on recents to
648      * apps transition delay items.
649      *
650      * @param transitionDelayItems
651      */
analyzeRecentsToAppDelay(List<TransitionDelayItem> transitionDelayItems)652     private void analyzeRecentsToAppDelay(List<TransitionDelayItem> transitionDelayItems) {
653         Map<String, String> cmpNameAppMap = reverseAppCmpInfoMap(getAppComponentInfoMap());
654         Map<String, List<Long>> appKeyTransitionDelayMap = new HashMap<>();
655         boolean isRecentsBefore = false;
656         for (TransitionDelayItem delayItem : transitionDelayItems) {
657             if (delayItem.getComponentName().contains(mLauncherActivity)) {
658                 isRecentsBefore = true;
659                 continue;
660             }
661             if (isRecentsBefore && cmpNameAppMap.containsKey(delayItem.getComponentName())) {
662                 if (delayItem.getStartingWindowDelay() != null) {
663                     continue;
664                 }
665                 String appName = cmpNameAppMap.get(delayItem.getComponentName());
666                 if (appKeyTransitionDelayMap.containsKey(appName)) {
667                     appKeyTransitionDelayMap.get(appName).add(delayItem.getTransitionDelay());
668                 } else {
669                     List<Long> delayTimeList = new ArrayList<Long>();
670                     delayTimeList.add(delayItem.getTransitionDelay());
671                     appKeyTransitionDelayMap.put(appName, delayTimeList);
672                 }
673             }
674             isRecentsBefore = false;
675         }
676         removeAdditionalLaunchInfo(appKeyTransitionDelayMap);
677         computeAndUploadResults(TEST_HOT_LAUNCH_FROM_RECENTS, appKeyTransitionDelayMap);
678     }
679 
680     /**
681      * Analyze and report different latency delay items captured from the events log file while
682      * running the LatencyTests
683      *
684      * @param latencyItemsList
685      */
analyzeLatencyInfo(List<LatencyItem> latencyItemsList)686     private void analyzeLatencyInfo(List<LatencyItem> latencyItemsList) {
687         Map<String, List<Long>> actionDelayListMap = new HashMap<>();
688         for (LatencyItem delayItem : latencyItemsList) {
689             if (actionDelayListMap.containsKey(Integer.toString(delayItem.getActionId()))) {
690                 actionDelayListMap
691                         .get(Integer.toString(delayItem.getActionId()))
692                         .add(delayItem.getDelay());
693             } else {
694                 List<Long> delayList = new ArrayList<Long>();
695                 delayList.add(delayItem.getDelay());
696                 actionDelayListMap.put(Integer.toString(delayItem.getActionId()), delayList);
697             }
698         }
699         computeAndUploadResults(TEST_LATENCY, actionDelayListMap);
700     }
701 
702     /**
703      * To remove the additional launch info at the beginning of the test.
704      *
705      * @param appKeyTransitionDelayMap
706      */
removeAdditionalLaunchInfo(Map<String, List<Long>> appKeyTransitionDelayMap)707     private void removeAdditionalLaunchInfo(Map<String, List<Long>> appKeyTransitionDelayMap) {
708         for (List<Long> delayList : appKeyTransitionDelayMap.values()) {
709             while (delayList.size() > mLaunchIteration) {
710                 delayList.remove(0);
711             }
712         }
713     }
714 
715     /**
716      * To compute the min,max,avg,median and std_dev on the transition delay for each app and upload
717      * the metrics
718      *
719      * @param reportingKey
720      * @param appKeyTransitionDelayMap
721      */
computeAndUploadResults( String reportingKey, Map<String, List<Long>> appKeyTransitionDelayMap)722     private void computeAndUploadResults(
723             String reportingKey, Map<String, List<Long>> appKeyTransitionDelayMap) {
724         CLog.i(String.format("Testing : %s", reportingKey));
725         Map<String, String> activityMetrics = new HashMap<String, String>();
726         for (String appNameKey : appKeyTransitionDelayMap.keySet()) {
727             List<Long> delayList = appKeyTransitionDelayMap.get(appNameKey);
728             StringBuffer delayListStr = new StringBuffer();
729             for (Long delayItem : delayList) {
730                 delayListStr.append(delayItem);
731                 delayListStr.append(",");
732             }
733             CLog.i("%s : %s", appNameKey, delayListStr);
734             SimpleStats stats = new SimpleStats();
735             for (Long delay : delayList) {
736                 stats.add(Double.parseDouble(delay.toString()));
737             }
738             activityMetrics.put(appNameKey + "_min", stats.min().toString());
739             activityMetrics.put(appNameKey + "_max", stats.max().toString());
740             activityMetrics.put(appNameKey + "_avg", stats.mean().toString());
741             activityMetrics.put(appNameKey + "_median", stats.median().toString());
742             activityMetrics.put(appNameKey + "_std_dev", stats.stdev().toString());
743         }
744         mListener.testRunStarted(reportingKey, 0);
745         mListener.testRunEnded(0, TfMetricProtoUtil.upgradeConvert(activityMetrics));
746     }
747 
748     /** Retrieve the map of appname,componenetname from the results. */
getAppComponentInfoMap()749     private Map<String, String> getAppComponentInfoMap() {
750         Collection<TestResult> testResultsCollection =
751                 mLaunchListener.getCurrentRunResults().getTestResults().values();
752         List<TestResult> testResults = new ArrayList<>(testResultsCollection);
753         return testResults.get(0).getMetrics();
754     }
755 
756     /**
757      * Reverse and return the given appName,componentName info map to componenetName,appName info
758      * map.
759      */
reverseAppCmpInfoMap(Map<String, String> appNameCmpNameMap)760     private Map<String, String> reverseAppCmpInfoMap(Map<String, String> appNameCmpNameMap) {
761         Map<String, String> cmpNameAppNameMap = new HashMap<String, String>();
762         for (Map.Entry<String, String> entry : appNameCmpNameMap.entrySet()) {
763             cmpNameAppNameMap.put(entry.getValue(), entry.getKey());
764         }
765         return cmpNameAppNameMap;
766     }
767 
768     @Override
setDevice(ITestDevice device)769     public void setDevice(ITestDevice device) {
770         mDevice = device;
771     }
772 
773     @Override
getDevice()774     public ITestDevice getDevice() {
775         return mDevice;
776     }
777 
778     /** @return the arguments map to pass to the test runner. */
getTestRunArgMap()779     public Map<String, String> getTestRunArgMap() {
780         return mArgMap;
781     }
782 
783     /** @param runArgMap the arguments to pass to the test runner. */
setTestRunArgMap(Map<String, String> runArgMap)784     public void setTestRunArgMap(Map<String, String> runArgMap) {
785         mArgMap = runArgMap;
786     }
787 }