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.documentsui; 18 19 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 20 21 import static org.junit.Assume.assumeNotNull; 22 23 import android.app.Activity; 24 import android.app.ActivityManager; 25 import android.app.Instrumentation; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.pm.ActivityInfo; 30 import android.content.pm.PackageManager; 31 import android.content.pm.ResolveInfo; 32 import android.os.Bundle; 33 import android.os.SystemClock; 34 import android.provider.DocumentsContract; 35 import android.support.test.uiautomator.UiDevice; 36 import android.util.Log; 37 38 import org.junit.After; 39 import org.junit.Before; 40 import org.junit.Test; 41 42 import java.io.IOException; 43 import java.util.Arrays; 44 import java.util.List; 45 46 public class FilesAppPerfTest { 47 private static final String TAG = "FilesAppPerfTest"; 48 49 // Keys used to report metrics to APCT. 50 private static final String KEY_FILES_COLD_START_PERFORMANCE_MEDIAN = 51 "files-cold-start-performance-median"; 52 private static final String KEY_FILES_WARM_START_PERFORMANCE_MEDIAN = 53 "files-warm-start-performance-median"; 54 55 private static final int NUM_MEASUREMENTS = 10; 56 private static final long REMOVAL_TIMEOUT_MS = 3000; 57 private static final long TIMEOUT_INTERVAL_MS = 200; 58 59 private Instrumentation mInstrumentation; 60 private Context mContext; 61 private LauncherActivity mLauncherActivity; 62 private ActivityInfo mDocumentsUiActivityInfo; 63 64 @Before setUp()65 public void setUp() { 66 mInstrumentation = getInstrumentation(); 67 mContext = mInstrumentation.getContext(); 68 final ResolveInfo info = mContext.getPackageManager().resolveActivity( 69 LauncherActivity.OPEN_DOCUMENT_INTENT, PackageManager.ResolveInfoFlags.of(0)); 70 assumeNotNull(info); 71 mDocumentsUiActivityInfo = info.activityInfo; 72 mLauncherActivity = (LauncherActivity) mInstrumentation.startActivitySync( 73 new Intent(mContext, LauncherActivity.class).addFlags( 74 Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION)); 75 } 76 77 @After tearDown()78 public void tearDown() { 79 mLauncherActivity.finishAndRemoveTask(); 80 } 81 82 @Test testFilesColdStartPerformance()83 public void testFilesColdStartPerformance() throws Exception { 84 runFilesStartPerformanceTest(true); 85 } 86 87 @Test testFilesWarmStartPerformance()88 public void testFilesWarmStartPerformance() throws Exception { 89 runFilesStartPerformanceTest(false); 90 } 91 runFilesStartPerformanceTest(boolean cold)92 public void runFilesStartPerformanceTest(boolean cold) throws Exception { 93 final String documentsUiPackageName = mDocumentsUiActivityInfo.packageName; 94 String[] providerPackageNames = null; 95 if (cold) { 96 providerPackageNames = getDocumentsProviderPackageNames(); 97 } 98 final ActivityManager am = mContext.getSystemService(ActivityManager.class); 99 long[] measurements = new long[NUM_MEASUREMENTS]; 100 for (int i = 0; i < NUM_MEASUREMENTS; i++) { 101 if (cold) { 102 // Kill all providers, as well as DocumentsUI to measure a cold start. 103 for (String pkgName : providerPackageNames) { 104 // Use kill-bg to avoid affecting other important services. 105 Log.i(TAG, "killBackgroundProcesses " + pkgName); 106 am.killBackgroundProcesses(pkgName); 107 } 108 Log.i(TAG, "forceStopPackage " + documentsUiPackageName); 109 am.forceStopPackage(documentsUiPackageName); 110 // Wait for any closing animations to finish. 111 mInstrumentation.getUiAutomation().syncInputTransactions(); 112 } 113 114 measurements[i] = mLauncherActivity.startAndWaitDocumentsUi(); 115 // The DocumentUi will finish automatically according to the request code for testing, 116 // so wait until it is completely removed to avoid affecting next iteration. 117 waitUntilDocumentsUiActivityRemoved(); 118 } 119 120 reportMetrics(cold ? KEY_FILES_COLD_START_PERFORMANCE_MEDIAN 121 : KEY_FILES_WARM_START_PERFORMANCE_MEDIAN, measurements); 122 } 123 reportMetrics(String key, long[] measurements)124 private void reportMetrics(String key, long[] measurements) { 125 final Bundle status = new Bundle(); 126 Arrays.sort(measurements); 127 final long median = measurements[NUM_MEASUREMENTS / 2 - 1]; 128 status.putDouble(key + "(ms)", median); 129 130 mInstrumentation.sendStatus(Activity.RESULT_OK, status); 131 } 132 getDocumentsProviderPackageNames()133 private String[] getDocumentsProviderPackageNames() { 134 final Intent intent = new Intent(DocumentsContract.PROVIDER_INTERFACE); 135 final List<ResolveInfo> providers = mContext.getPackageManager() 136 .queryIntentContentProviders(intent, PackageManager.ResolveInfoFlags.of(0)); 137 final String[] pkgNames = new String[providers.size()]; 138 for (int i = 0; i < providers.size(); i++) { 139 pkgNames[i] = providers.get(i).providerInfo.packageName; 140 } 141 return pkgNames; 142 } 143 waitUntilDocumentsUiActivityRemoved()144 private void waitUntilDocumentsUiActivityRemoved() { 145 final UiDevice uiDevice = UiDevice.getInstance(mInstrumentation); 146 final String classPattern = new ComponentName(mDocumentsUiActivityInfo.packageName, 147 mDocumentsUiActivityInfo.name).flattenToShortString(); 148 final long startTime = SystemClock.uptimeMillis(); 149 while (SystemClock.uptimeMillis() - startTime <= REMOVAL_TIMEOUT_MS) { 150 SystemClock.sleep(TIMEOUT_INTERVAL_MS); 151 final String windowTokenDump; 152 try { 153 windowTokenDump = uiDevice.executeShellCommand("dumpsys window tokens"); 154 } catch (IOException e) { 155 throw new RuntimeException(e); 156 } 157 if (!windowTokenDump.contains(classPattern)) { 158 return; 159 } 160 } 161 Log.i(TAG, "Removal timeout of " + classPattern); 162 } 163 } 164