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