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