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