• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.test.util.dismissdialogs;
18 
19 import android.app.Activity;
20 import android.app.Instrumentation;
21 import android.os.Bundle;
22 import android.os.Environment;
23 import android.os.RemoteException;
24 import android.os.SystemClock;
25 import android.support.test.aupt.UiWatchers;
26 import android.support.test.uiautomator.UiDevice;
27 import android.support.test.uiautomator.Until;
28 import android.util.Log;
29 
30 import android.platform.test.helpers.IStandardAppHelper;
31 import android.platform.test.helpers.ChromeHelperImpl;
32 import android.platform.test.helpers.GoogleCameraHelperImpl;
33 import android.platform.test.helpers.GoogleKeyboardHelperImpl;
34 import android.platform.test.helpers.GmailHelperImpl;
35 import android.platform.test.helpers.MapsHelperImpl;
36 import android.platform.test.helpers.PhotosHelperImpl;
37 import android.platform.test.helpers.PlayMoviesHelperImpl;
38 import android.platform.test.helpers.PlayMusicHelperImpl;
39 import android.platform.test.helpers.PlayStoreHelperImpl;
40 import android.platform.test.helpers.YouTubeHelperImpl;
41 import android.support.test.launcherhelper.ILauncherStrategy;
42 import android.support.test.launcherhelper.LauncherStrategyFactory;
43 
44 import java.io.File;
45 import java.io.IOException;
46 import java.lang.NoSuchMethodException;
47 import java.lang.InstantiationException;
48 import java.lang.IllegalAccessException;
49 import java.lang.ReflectiveOperationException;
50 import java.lang.reflect.InvocationTargetException;
51 import java.util.HashMap;
52 import java.util.Map;
53 
54 /**
55  * A utility to dismiss all predictable, relevant one-time dialogs
56  */
57 public class DismissDialogsInstrumentation extends Instrumentation {
58     private static final String LOG_TAG = DismissDialogsInstrumentation.class.getSimpleName();
59     private static final String IMAGE_SUBFOLDER = "dialog-dismissal";
60 
61     private static final long INIT_TIMEOUT = 20000;
62     private static final long MAX_INIT_RETRIES = 5;
63 
64     // Comma-separated value indicating for which apps to dismiss dialogs
65     private static final String PARAM_APP = "apps";
66     // Boolean to indicate if this should take screenshots to document dismissal
67     private static final String PARAM_SCREENSHOTS = "screenshots";
68     // Boolean to indicate if this should quit if any failure occurs
69     private static final String PARAM_QUIT_ON_ERROR = "quitOnError";
70 
71     // Key for status bundles provided when running the preparer
72     private static final String BUNDLE_DISMISSED_APP_KEY = "dismissedApp";
73     private static final String BUNDLE_APP_ERROR_KEY = "appError";
74 
75     private Map<String, Class<? extends IStandardAppHelper>> mKeyHelperMap;
76     private String[] mApps;
77     private boolean mScreenshots;
78     private boolean mQuitOnError;
79     private UiDevice mDevice;
80 
81     @Override
onCreate(Bundle arguments)82     public void onCreate(Bundle arguments) {
83         super.onCreate(arguments);
84 
85         mKeyHelperMap = new HashMap<String, Class<? extends IStandardAppHelper>>();
86         mKeyHelperMap.put("Chrome", ChromeHelperImpl.class);
87         mKeyHelperMap.put("GoogleCamera", GoogleCameraHelperImpl.class);
88         mKeyHelperMap.put("GoogleKeyboard", GoogleKeyboardHelperImpl.class);
89         mKeyHelperMap.put("Gmail", GmailHelperImpl.class);
90         mKeyHelperMap.put("Maps", MapsHelperImpl.class);
91         mKeyHelperMap.put("Photos", PhotosHelperImpl.class);
92         mKeyHelperMap.put("PlayMovies", PlayMoviesHelperImpl.class);
93         mKeyHelperMap.put("PlayMusic", PlayMusicHelperImpl.class);
94         mKeyHelperMap.put("PlayStore", PlayStoreHelperImpl.class);
95         //mKeyHelperMap.put("Settings", SettingsHelperImpl.class);
96         mKeyHelperMap.put("YouTube", YouTubeHelperImpl.class);
97 
98         String appsString = arguments.getString(PARAM_APP);
99         if (appsString == null) {
100             throw new IllegalArgumentException("Missing 'apps' parameter.");
101         }
102         mApps = appsString.split(",");
103 
104         String screenshotsString = arguments.getString(PARAM_SCREENSHOTS);
105         if (screenshotsString == null) {
106             Log.i(LOG_TAG, "No 'screenshots' parameter. Defaulting to true.");
107             mScreenshots = true;
108         } else {
109             mScreenshots = "true".equals(screenshotsString);
110         }
111 
112         String quitString = arguments.getString(PARAM_QUIT_ON_ERROR);
113         if (quitString == null) {
114             Log.i(LOG_TAG, "No 'quitOnError' parameter. Defaulting to quit on error.");
115             mQuitOnError = true;
116         } else {
117             mQuitOnError = "true".equals(quitString);
118         }
119 
120         start();
121     }
122 
123     @Override
onStart()124     public void onStart() {
125         super.onStart();
126 
127         mDevice = UiDevice.getInstance(this);
128 
129         UiWatchers watcherManager = new UiWatchers();
130         watcherManager.registerAnrAndCrashWatchers(this);
131 
132         takeScreenDump("init", "pre-setup");
133 
134         try {
135             mDevice.setOrientationNatural();
136         } catch (RemoteException e) {
137             Log.e(LOG_TAG, "Unable to set device orientation.", e);
138         }
139 
140         for (int retry = 1; retry <= MAX_INIT_RETRIES; retry++) {
141             ILauncherStrategy launcherStrategy =
142                     LauncherStrategyFactory.getInstance(mDevice).getLauncherStrategy();
143             boolean foundHome = mDevice.wait(Until.hasObject(
144                     launcherStrategy.getWorkspaceSelector()), INIT_TIMEOUT);
145             if (foundHome) {
146                 sendStatusUpdate(Activity.RESULT_OK, "launcher");
147                 break;
148             } else {
149                 takeScreenDump("init", String.format("launcher-selection-failure-%d", retry));
150                 if (retry == MAX_INIT_RETRIES && mQuitOnError) {
151                     throw new RuntimeException("Unable to select launcher workspace. Quitting.");
152                 } else {
153                     sendStatusUpdate(Activity.RESULT_CANCELED, "launcher");
154                     Log.e(LOG_TAG, "Failed to find home selector; try #" + retry);
155                     // HACK: Try to poke at UI to fix accessibility issue (b/21448825)
156                     try {
157                         mDevice.sleep();
158                         SystemClock.sleep(1000);
159                         mDevice.wakeUp();
160                         mDevice.pressMenu();
161                         UiDevice.getInstance(this).pressHome();
162                     } catch (RemoteException e) {
163                         Log.e(LOG_TAG, "Failed to avoid UI bug b/21448825.", e);
164                     }
165                 }
166             }
167         }
168 
169         for (String app : mApps) {
170             Log.i(LOG_TAG, String.format("Dismissing dialogs for app, %s.", app));
171             try {
172                 if (!dismissDialogs(app)) {
173                     throw new IllegalArgumentException(
174                             String.format("Unrecognized app \"%s\"", mApps));
175                 } else {
176                     sendStatusUpdate(Activity.RESULT_OK, app);
177                 }
178             } catch (ReflectiveOperationException e) {
179                 if (mQuitOnError) {
180                     quitWithError(app, e);
181                 } else {
182                     sendStatusUpdate(Activity.RESULT_CANCELED, app);
183                     Log.w(LOG_TAG, "ReflectiveOperationException. Continuing with dismissal.", e);
184                 }
185             } catch (RuntimeException e) {
186                 if (mQuitOnError) {
187                     quitWithError(app, e);
188                 } else {
189                     sendStatusUpdate(Activity.RESULT_CANCELED, app);
190                     Log.w(LOG_TAG, "RuntimeException. Continuing with dismissal.", e);
191                 }
192             } catch (AssertionError e) {
193                 if (mQuitOnError) {
194                     quitWithError(app, new Exception(e));
195                 } else {
196                     sendStatusUpdate(Activity.RESULT_CANCELED, app);
197                     Log.w(LOG_TAG, "AssertionError. Continuing with dismissal.", e);
198                 }
199             }
200 
201             // Always return to the home page after dismissal
202             UiDevice.getInstance(this).pressHome();
203         }
204 
205         watcherManager.removeAnrAndCrashWatchers(this);
206 
207         finish(Activity.RESULT_OK, new Bundle());
208     }
209 
dismissDialogs(String app)210     private boolean dismissDialogs(String app) throws NoSuchMethodException, InstantiationException,
211             IllegalAccessException, InvocationTargetException {
212         try {
213             if (mKeyHelperMap.containsKey(app)) {
214                 Class<? extends IStandardAppHelper> appHelperClass = mKeyHelperMap.get(app);
215                 IStandardAppHelper helper =
216                         appHelperClass.getDeclaredConstructor(Instrumentation.class).newInstance(this);
217                 takeScreenDump(app, "-dialog1-pre-open");
218                 helper.open();
219                 takeScreenDump(app, "-dialog2-pre-dismissal");
220                 helper.dismissInitialDialogs();
221                 takeScreenDump(app, "-dialog3-post-dismissal");
222                 helper.exit();
223                 takeScreenDump(app, "-dialog4-post-exit");
224                 return true;
225             } else {
226                 return false;
227             }
228         } catch (Exception | AssertionError e) {
229             takeScreenDump(app, "-exception");
230             throw e;
231         }
232     }
233 
sendStatusUpdate(int code, String app)234     private void sendStatusUpdate(int code, String app) {
235         Bundle result = new Bundle();
236         result.putString(BUNDLE_DISMISSED_APP_KEY, app);
237         sendStatus(code, result);
238     }
239 
quitWithError(String app, Exception exception)240     private void quitWithError(String app, Exception exception) {
241         Log.e(LOG_TAG, "Quitting with exception.", exception);
242         // Pass Bundle with debugging information to TF
243         Bundle result = new Bundle();
244         result.putString(BUNDLE_DISMISSED_APP_KEY, app);
245         result.putString(BUNDLE_APP_ERROR_KEY, exception.toString());
246         finish(Activity.RESULT_CANCELED, result);
247     }
248 
takeScreenDump(String app, String suffix)249     private void takeScreenDump(String app, String suffix) {
250         if (!mScreenshots) {
251             return;
252         }
253 
254         try {
255             File dir = new File(Environment.getExternalStorageDirectory(), IMAGE_SUBFOLDER);
256             if (!dir.exists() && !dir.mkdirs()) {
257                     throw new RuntimeException(String.format(
258                             "Unable to create or find directory, %s.", dir.getPath()));
259             }
260             File scr = new File(dir, "dd-" + app + suffix + ".png");
261             File uix = new File(dir, "dd-" + app + suffix + ".uix");
262             Log.v(LOG_TAG, String.format("Screen file path: %s", scr.getPath()));
263             Log.v(LOG_TAG, String.format("UI XML file path: %s", uix.getPath()));
264             scr.createNewFile();
265             uix.createNewFile();
266             UiDevice.getInstance(this).takeScreenshot(scr);
267             UiDevice.getInstance(this).dumpWindowHierarchy(uix);
268         } catch (IOException e) {
269             Log.e(LOG_TAG, "Failed screen dump.", e);
270         }
271     }
272 }
273