OSDN Git Service

ed7351f85a7a35fa0e973208b35b28e7b553312d
[android-x86/frameworks-base.git] / tests / AppLaunch / src / com / android / tests / applaunch / AppLaunch.java
1 /*
2  * Copyright (C) 2013 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 package com.android.tests.applaunch;
17
18 import android.accounts.Account;
19 import android.accounts.AccountManager;
20 import android.app.ActivityManager;
21 import android.app.ActivityManager.ProcessErrorStateInfo;
22 import android.app.ActivityManagerNative;
23 import android.app.IActivityManager;
24 import android.app.IActivityManager.WaitResult;
25 import android.app.UiAutomation;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.pm.PackageManager;
29 import android.content.pm.PackageManager.NameNotFoundException;
30 import android.content.pm.ResolveInfo;
31 import android.os.Bundle;
32 import android.os.RemoteException;
33 import android.os.UserHandle;
34 import android.test.InstrumentationTestCase;
35 import android.test.InstrumentationTestRunner;
36 import android.util.Log;
37
38 import java.util.HashMap;
39 import java.util.HashSet;
40 import java.util.LinkedHashMap;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.Set;
44
45 /**
46  * This test is intended to measure the time it takes for the apps to start.
47  * Names of the applications are passed in command line, and the
48  * test starts each application, and reports the start up time in milliseconds.
49  * The instrumentation expects the following key to be passed on the command line:
50  * apps - A list of applications to start and their corresponding result keys
51  * in the following format:
52  * -e apps <app name>^<result key>|<app name>^<result key>
53  */
54 public class AppLaunch extends InstrumentationTestCase {
55
56     private static final int JOIN_TIMEOUT = 10000;
57     private static final String TAG = AppLaunch.class.getSimpleName();
58     private static final String KEY_APPS = "apps";
59     private static final String KEY_LAUNCH_ITERATIONS = "launch_iterations";
60     // optional parameter: comma separated list of required account types before proceeding
61     // with the app launch
62     private static final String KEY_REQUIRED_ACCOUNTS = "required_accounts";
63     private static final String WEARABLE_ACTION_GOOGLE =
64             "com.google.android.wearable.action.GOOGLE";
65     private static final int INITIAL_LAUNCH_IDLE_TIMEOUT = 7500; //7.5s to allow app to idle
66     private static final int POST_LAUNCH_IDLE_TIMEOUT = 750; //750ms idle for non initial launches
67     private static final int BETWEEN_LAUNCH_SLEEP_TIMEOUT = 2000; //2s between launching apps
68
69     private Map<String, Intent> mNameToIntent;
70     private Map<String, String> mNameToProcess;
71     private Map<String, String> mNameToResultKey;
72     private Map<String, Long> mNameToLaunchTime;
73     private IActivityManager mAm;
74     private int mLaunchIterations = 10;
75     private Bundle mResult = new Bundle();
76     private Set<String> mRequiredAccounts;
77
78     @Override
79     protected void setUp() throws Exception {
80         super.setUp();
81         getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_0);
82     }
83
84     @Override
85     protected void tearDown() throws Exception {
86         getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_UNFREEZE);
87         super.tearDown();
88     }
89
90     public void testMeasureStartUpTime() throws RemoteException, NameNotFoundException {
91         InstrumentationTestRunner instrumentation =
92                 (InstrumentationTestRunner)getInstrumentation();
93         Bundle args = instrumentation.getArguments();
94         mAm = ActivityManagerNative.getDefault();
95
96         createMappings();
97         parseArgs(args);
98         checkAccountSignIn();
99
100         // do initial app launch, without force stopping
101         for (String app : mNameToResultKey.keySet()) {
102             long launchTime = startApp(app, false);
103             if (launchTime <= 0) {
104                 mNameToLaunchTime.put(app, -1L);
105                 // simply pass the app if launch isn't successful
106                 // error should have already been logged by startApp
107                 continue;
108             } else {
109                 mNameToLaunchTime.put(app, launchTime);
110             }
111             sleep(INITIAL_LAUNCH_IDLE_TIMEOUT);
112             closeApp(app, false);
113             sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT);
114         }
115         // do the real app launch now
116         for (int i = 0; i < mLaunchIterations; i++) {
117             for (String app : mNameToResultKey.keySet()) {
118                 long prevLaunchTime = mNameToLaunchTime.get(app);
119                 long launchTime = 0;
120                 if (prevLaunchTime < 0) {
121                     // skip if the app has previous failures
122                     continue;
123                 }
124                 launchTime = startApp(app, true);
125                 if (launchTime <= 0) {
126                     // if it fails once, skip the rest of the launches
127                     mNameToLaunchTime.put(app, -1L);
128                     continue;
129                 }
130                 // keep the min launch time
131                 if (launchTime < prevLaunchTime) {
132                     mNameToLaunchTime.put(app, launchTime);
133                 }
134                 sleep(POST_LAUNCH_IDLE_TIMEOUT);
135                 closeApp(app, true);
136                 sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT);
137             }
138         }
139         for (String app : mNameToResultKey.keySet()) {
140             long launchTime = mNameToLaunchTime.get(app);
141             if (launchTime != -1) {
142                 mResult.putLong(mNameToResultKey.get(app), launchTime);
143             }
144         }
145         instrumentation.sendStatus(0, mResult);
146     }
147
148     private void parseArgs(Bundle args) {
149         mNameToResultKey = new LinkedHashMap<String, String>();
150         mNameToLaunchTime = new HashMap<String, Long>();
151         String launchIterations = args.getString(KEY_LAUNCH_ITERATIONS);
152         if (launchIterations != null) {
153             mLaunchIterations = Integer.parseInt(launchIterations);
154         }
155         String appList = args.getString(KEY_APPS);
156         if (appList == null)
157             return;
158
159         String appNames[] = appList.split("\\|");
160         for (String pair : appNames) {
161             String[] parts = pair.split("\\^");
162             if (parts.length != 2) {
163                 Log.e(TAG, "The apps key is incorrectly formatted");
164                 fail();
165             }
166
167             mNameToResultKey.put(parts[0], parts[1]);
168             mNameToLaunchTime.put(parts[0], 0L);
169         }
170         String requiredAccounts = args.getString(KEY_REQUIRED_ACCOUNTS);
171         if (requiredAccounts != null) {
172             mRequiredAccounts = new HashSet<String>();
173             for (String accountType : requiredAccounts.split(",")) {
174                 mRequiredAccounts.add(accountType);
175             }
176         }
177     }
178
179     private boolean hasLeanback(Context context) {
180         return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
181     }
182
183     private void createMappings() {
184         mNameToIntent = new LinkedHashMap<String, Intent>();
185         mNameToProcess = new LinkedHashMap<String, String>();
186
187         PackageManager pm = getInstrumentation().getContext()
188                 .getPackageManager();
189         Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
190         intentToResolve.addCategory(hasLeanback(getInstrumentation().getContext()) ?
191                 Intent.CATEGORY_LEANBACK_LAUNCHER :
192                 Intent.CATEGORY_LAUNCHER);
193         List<ResolveInfo> ris = pm.queryIntentActivities(intentToResolve, 0);
194         resolveLoop(ris, intentToResolve, pm);
195         // For Wear
196         intentToResolve = new Intent(WEARABLE_ACTION_GOOGLE);
197         ris = pm.queryIntentActivities(intentToResolve, 0);
198         resolveLoop(ris, intentToResolve, pm);
199     }
200
201     private void resolveLoop(List<ResolveInfo> ris, Intent intentToResolve, PackageManager pm) {
202         if (ris == null || ris.isEmpty()) {
203             Log.i(TAG, "Could not find any apps");
204         } else {
205             for (ResolveInfo ri : ris) {
206                 Intent startIntent = new Intent(intentToResolve);
207                 startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
208                         | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
209                 startIntent.setClassName(ri.activityInfo.packageName,
210                         ri.activityInfo.name);
211                 String appName = ri.loadLabel(pm).toString();
212                 if (appName != null) {
213                     mNameToIntent.put(appName, startIntent);
214                     mNameToProcess.put(appName, ri.activityInfo.processName);
215                 }
216             }
217         }
218     }
219
220     private long startApp(String appName, boolean forceStopBeforeLaunch)
221             throws NameNotFoundException, RemoteException {
222         Log.i(TAG, "Starting " + appName);
223
224         Intent startIntent = mNameToIntent.get(appName);
225         if (startIntent == null) {
226             Log.w(TAG, "App does not exist: " + appName);
227             mResult.putString(mNameToResultKey.get(appName), "App does not exist");
228             return -1;
229         }
230         AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent, forceStopBeforeLaunch);
231         Thread t = new Thread(runnable);
232         t.start();
233         try {
234             t.join(JOIN_TIMEOUT);
235         } catch (InterruptedException e) {
236             // ignore
237         }
238         WaitResult result = runnable.getResult();
239         // report error if any of the following is true:
240         // * launch thread is alive
241         // * result is not null, but:
242         //   * result is not START_SUCCESS
243         //   * or in case of no force stop, result is not TASK_TO_FRONT either
244         if (t.isAlive() || (result != null
245                 && ((result.result != ActivityManager.START_SUCCESS)
246                         && (!forceStopBeforeLaunch
247                                 && result.result != ActivityManager.START_TASK_TO_FRONT)))) {
248             Log.w(TAG, "Assuming app " + appName + " crashed.");
249             reportError(appName, mNameToProcess.get(appName));
250             return -1;
251         }
252         return result.thisTime;
253     }
254
255     private void checkAccountSignIn() {
256         // ensure that the device has the required account types before starting test
257         // e.g. device must have a valid Google account sign in to measure a meaningful launch time
258         // for Gmail
259         if (mRequiredAccounts == null || mRequiredAccounts.isEmpty()) {
260             return;
261         }
262         final AccountManager am =
263                 (AccountManager) getInstrumentation().getTargetContext().getSystemService(
264                         Context.ACCOUNT_SERVICE);
265         Account[] accounts = am.getAccounts();
266         // use set here in case device has multiple accounts of the same type
267         Set<String> foundAccounts = new HashSet<String>();
268         for (Account account : accounts) {
269             if (mRequiredAccounts.contains(account.type)) {
270                 foundAccounts.add(account.type);
271             }
272         }
273         // check if account type matches, if not, fail test with message on what account types
274         // are missing
275         if (mRequiredAccounts.size() != foundAccounts.size()) {
276             mRequiredAccounts.removeAll(foundAccounts);
277             StringBuilder sb = new StringBuilder("Device missing these accounts:");
278             for (String account : mRequiredAccounts) {
279                 sb.append(' ');
280                 sb.append(account);
281             }
282             fail(sb.toString());
283         }
284     }
285
286     private void closeApp(String appName, boolean forceStopApp) {
287         Intent homeIntent = new Intent(Intent.ACTION_MAIN);
288         homeIntent.addCategory(Intent.CATEGORY_HOME);
289         homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
290                 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
291         getInstrumentation().getContext().startActivity(homeIntent);
292         sleep(POST_LAUNCH_IDLE_TIMEOUT);
293         if (forceStopApp) {
294             Intent startIntent = mNameToIntent.get(appName);
295             if (startIntent != null) {
296                 String packageName = startIntent.getComponent().getPackageName();
297                 try {
298                     mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT);
299                 } catch (RemoteException e) {
300                     Log.w(TAG, "Error closing app", e);
301                 }
302             }
303         }
304     }
305
306     private void sleep(int time) {
307         try {
308             Thread.sleep(time);
309         } catch (InterruptedException e) {
310             // ignore
311         }
312     }
313
314     private void reportError(String appName, String processName) {
315         ActivityManager am = (ActivityManager) getInstrumentation()
316                 .getContext().getSystemService(Context.ACTIVITY_SERVICE);
317         List<ProcessErrorStateInfo> crashes = am.getProcessesInErrorState();
318         if (crashes != null) {
319             for (ProcessErrorStateInfo crash : crashes) {
320                 if (!crash.processName.equals(processName))
321                     continue;
322
323                 Log.w(TAG, appName + " crashed: " + crash.shortMsg);
324                 mResult.putString(mNameToResultKey.get(appName), crash.shortMsg);
325                 return;
326             }
327         }
328
329         mResult.putString(mNameToResultKey.get(appName),
330                 "Crashed for unknown reason");
331         Log.w(TAG, appName
332                 + " not found in process list, most likely it is crashed");
333     }
334
335     private class AppLaunchRunnable implements Runnable {
336         private Intent mLaunchIntent;
337         private IActivityManager.WaitResult mResult;
338         private boolean mForceStopBeforeLaunch;
339
340         public AppLaunchRunnable(Intent intent, boolean forceStopBeforeLaunch) {
341             mLaunchIntent = intent;
342             mForceStopBeforeLaunch = forceStopBeforeLaunch;
343         }
344
345         public IActivityManager.WaitResult getResult() {
346             return mResult;
347         }
348
349         public void run() {
350             try {
351                 String packageName = mLaunchIntent.getComponent().getPackageName();
352                 if (mForceStopBeforeLaunch) {
353                     mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT);
354                 }
355                 String mimeType = mLaunchIntent.getType();
356                 if (mimeType == null && mLaunchIntent.getData() != null
357                         && "content".equals(mLaunchIntent.getData().getScheme())) {
358                     mimeType = mAm.getProviderMimeType(mLaunchIntent.getData(),
359                             UserHandle.USER_CURRENT);
360                 }
361
362                 mResult = mAm.startActivityAndWait(null, null, mLaunchIntent, mimeType,
363                         null, null, 0, mLaunchIntent.getFlags(), null, null,
364                         UserHandle.USER_CURRENT);
365             } catch (RemoteException e) {
366                 Log.w(TAG, "Error launching app", e);
367             }
368         }
369     }
370 }