1package com.android.launcher3.ui; 2 3import android.app.SearchManager; 4import android.appwidget.AppWidgetProviderInfo; 5import android.content.ComponentName; 6import android.content.Context; 7import android.content.Intent; 8import android.content.pm.PackageManager; 9import android.graphics.Point; 10import android.os.ParcelFileDescriptor; 11import android.os.RemoteException; 12import android.os.SystemClock; 13import android.support.test.uiautomator.By; 14import android.support.test.uiautomator.BySelector; 15import android.support.test.uiautomator.Direction; 16import android.support.test.uiautomator.UiDevice; 17import android.support.test.uiautomator.UiObject2; 18import android.support.test.uiautomator.Until; 19import android.test.InstrumentationTestCase; 20import android.view.MotionEvent; 21 22import com.android.launcher3.InvariantDeviceProfile; 23import com.android.launcher3.Launcher; 24import com.android.launcher3.LauncherAppState; 25import com.android.launcher3.LauncherAppWidgetProviderInfo; 26import com.android.launcher3.LauncherClings; 27import com.android.launcher3.LauncherSettings; 28import com.android.launcher3.R; 29import com.android.launcher3.Utilities; 30import com.android.launcher3.compat.AppWidgetManagerCompat; 31import com.android.launcher3.config.FeatureFlags; 32import com.android.launcher3.util.ManagedProfileHeuristic; 33 34import java.io.FileInputStream; 35import java.io.IOException; 36import java.util.Locale; 37import java.util.concurrent.Callable; 38import java.util.concurrent.atomic.AtomicReference; 39 40/** 41 * Base class for all instrumentation tests providing various utility methods. 42 */ 43public class LauncherInstrumentationTestCase extends InstrumentationTestCase { 44 45 public static final long DEFAULT_UI_TIMEOUT = 3000; 46 47 protected UiDevice mDevice; 48 protected Context mTargetContext; 49 protected String mTargetPackage; 50 51 @Override 52 protected void setUp() throws Exception { 53 super.setUp(); 54 55 mDevice = UiDevice.getInstance(getInstrumentation()); 56 mTargetContext = getInstrumentation().getTargetContext(); 57 mTargetPackage = mTargetContext.getPackageName(); 58 } 59 60 protected void lockRotation(boolean naturalOrientation) throws RemoteException { 61 Utilities.getPrefs(mTargetContext) 62 .edit() 63 .putBoolean(Utilities.ALLOW_ROTATION_PREFERENCE_KEY, !naturalOrientation) 64 .commit(); 65 66 if (naturalOrientation) { 67 mDevice.setOrientationNatural(); 68 } else { 69 mDevice.setOrientationRight(); 70 } 71 } 72 73 /** 74 * Starts the launcher activity in the target package and returns the Launcher instance. 75 */ 76 protected Launcher startLauncher() { 77 Intent homeIntent = new Intent(Intent.ACTION_MAIN) 78 .addCategory(Intent.CATEGORY_HOME) 79 .setPackage(mTargetPackage) 80 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 81 return (Launcher) getInstrumentation().startActivitySync(homeIntent); 82 } 83 84 /** 85 * Grants the launcher permission to bind widgets. 86 */ 87 protected void grantWidgetPermission() throws IOException { 88 // Check bind widget permission 89 if (mTargetContext.getPackageManager().checkPermission( 90 mTargetPackage, android.Manifest.permission.BIND_APPWIDGET) 91 != PackageManager.PERMISSION_GRANTED) { 92 ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation().executeShellCommand( 93 "appwidget grantbind --package " + mTargetPackage); 94 // Read the input stream fully. 95 FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd); 96 while (fis.read() != -1); 97 fis.close(); 98 } 99 } 100 101 /** 102 * Opens all apps and returns the recycler view 103 */ 104 protected UiObject2 openAllApps() { 105 if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) { 106 // clicking on the page indicator brings up all apps tray on non tablets. 107 findViewById(R.id.page_indicator).click(); 108 } else { 109 mDevice.wait(Until.findObject( 110 By.desc(mTargetContext.getString(R.string.all_apps_button_label))), 111 DEFAULT_UI_TIMEOUT).click(); 112 } 113 return findViewById(R.id.apps_list_view); 114 } 115 116 /** 117 * Opens widget tray and returns the recycler view. 118 */ 119 protected UiObject2 openWidgetsTray() { 120 mDevice.pressMenu(); // Enter overview mode. 121 mDevice.wait(Until.findObject( 122 By.text(mTargetContext.getString(R.string.widget_button_text) 123 .toUpperCase(Locale.getDefault()))), DEFAULT_UI_TIMEOUT).click(); 124 return findViewById(R.id.widgets_list_view); 125 } 126 127 /** 128 * Scrolls the {@param container} until it finds an object matching {@param condition}. 129 * @return the matching object. 130 */ 131 protected UiObject2 scrollAndFind(UiObject2 container, BySelector condition) { 132 do { 133 UiObject2 widget = container.findObject(condition); 134 if (widget != null) { 135 return widget; 136 } 137 } while (container.scroll(Direction.DOWN, 1f)); 138 return container.findObject(condition); 139 } 140 141 /** 142 * Drags an icon to the center of homescreen. 143 */ 144 protected void dragToWorkspace(UiObject2 icon) { 145 Point center = icon.getVisibleCenter(); 146 147 // Action Down 148 sendPointer(MotionEvent.ACTION_DOWN, center); 149 150 // Wait until "Remove/Delete target is visible 151 assertNotNull(findViewById(R.id.delete_target_text)); 152 153 Point moveLocation = findViewById(R.id.drag_layer).getVisibleCenter(); 154 155 // Move to center 156 while(!moveLocation.equals(center)) { 157 center.x = getNextMoveValue(moveLocation.x, center.x); 158 center.y = getNextMoveValue(moveLocation.y, center.y); 159 sendPointer(MotionEvent.ACTION_MOVE, center); 160 } 161 sendPointer(MotionEvent.ACTION_UP, center); 162 163 // Wait until remove target is gone. 164 mDevice.wait(Until.gone(getSelectorForId(R.id.delete_target_text)), DEFAULT_UI_TIMEOUT); 165 } 166 167 private int getNextMoveValue(int targetValue, int oldValue) { 168 if (targetValue - oldValue > 10) { 169 return oldValue + 10; 170 } else if (targetValue - oldValue < -10) { 171 return oldValue - 10; 172 } else { 173 return targetValue; 174 } 175 } 176 177 private void sendPointer(int action, Point point) { 178 MotionEvent event = MotionEvent.obtain(SystemClock.uptimeMillis(), 179 SystemClock.uptimeMillis(), action, point.x, point.y, 0); 180 getInstrumentation().sendPointerSync(event); 181 event.recycle(); 182 } 183 184 /** 185 * Removes all icons from homescreen and hotseat. 186 */ 187 public void clearHomescreen() throws Throwable { 188 LauncherSettings.Settings.call(mTargetContext.getContentResolver(), 189 LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB); 190 LauncherSettings.Settings.call(mTargetContext.getContentResolver(), 191 LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG); 192 LauncherClings.markFirstRunClingDismissed(mTargetContext); 193 ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext); 194 195 runTestOnUiThread(new Runnable() { 196 @Override 197 public void run() { 198 // Reset the loader state 199 LauncherAppState.getInstance().getModel().resetLoadedState(true, true); 200 } 201 }); 202 } 203 204 /** 205 * Runs the callback on the UI thread and returns the result. 206 */ 207 protected <T> T getOnUiThread(final Callable<T> callback) { 208 final AtomicReference<T> result = new AtomicReference<>(null); 209 try { 210 runTestOnUiThread(new Runnable() { 211 @Override 212 public void run() { 213 try { 214 result.set(callback.call()); 215 } catch (Exception e) { } 216 } 217 }); 218 } catch (Throwable t) { } 219 return result.get(); 220 } 221 222 /** 223 * Finds a widget provider which can fit on the home screen. 224 * @param hasConfigureScreen if true, a provider with a config screen is returned. 225 */ 226 protected LauncherAppWidgetProviderInfo findWidgetProvider(final boolean hasConfigureScreen) { 227 LauncherAppWidgetProviderInfo info = getOnUiThread(new Callable<LauncherAppWidgetProviderInfo>() { 228 @Override 229 public LauncherAppWidgetProviderInfo call() throws Exception { 230 InvariantDeviceProfile idv = 231 LauncherAppState.getInstance().getInvariantDeviceProfile(); 232 233 ComponentName searchComponent = ((SearchManager) mTargetContext 234 .getSystemService(Context.SEARCH_SERVICE)).getGlobalSearchActivity(); 235 String searchPackage = searchComponent == null 236 ? null : searchComponent.getPackageName(); 237 238 for (AppWidgetProviderInfo info : 239 AppWidgetManagerCompat.getInstance(mTargetContext).getAllProviders()) { 240 if ((info.configure != null) ^ hasConfigureScreen) { 241 continue; 242 } 243 // Exclude the widgets in search package, as Launcher already binds them in 244 // QSB, so they can cause conflicts. 245 if (info.provider.getPackageName().equals(searchPackage)) { 246 continue; 247 } 248 LauncherAppWidgetProviderInfo widgetInfo = LauncherAppWidgetProviderInfo 249 .fromProviderInfo(mTargetContext, info); 250 if (widgetInfo.minSpanX >= idv.numColumns 251 || widgetInfo.minSpanY >= idv.numRows) { 252 continue; 253 } 254 return widgetInfo; 255 } 256 return null; 257 } 258 }); 259 if (info == null) { 260 throw new IllegalArgumentException("No valid widget provider"); 261 } 262 return info; 263 } 264 265 protected UiObject2 findViewById(int id) { 266 return mDevice.wait(Until.findObject(getSelectorForId(id)), DEFAULT_UI_TIMEOUT); 267 } 268 269 protected BySelector getSelectorForId(int id) { 270 String name = mTargetContext.getResources().getResourceEntryName(id); 271 return By.res(mTargetPackage, name); 272 } 273} 274