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 }