1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 package com.android.launcher3.ui; 17 18 import android.content.BroadcastReceiver; 19 import android.content.ComponentName; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.IntentFilter; 23 import android.content.pm.ActivityInfo; 24 import android.content.pm.PackageManager; 25 import android.content.pm.ResolveInfo; 26 import android.graphics.Point; 27 import android.os.ParcelFileDescriptor; 28 import android.os.Process; 29 import android.os.RemoteException; 30 import android.os.SystemClock; 31 import android.support.test.uiautomator.By; 32 import android.support.test.uiautomator.BySelector; 33 import android.support.test.uiautomator.Direction; 34 import android.support.test.uiautomator.UiDevice; 35 import android.support.test.uiautomator.UiObject2; 36 import android.support.test.uiautomator.Until; 37 import android.test.InstrumentationTestCase; 38 import android.view.MotionEvent; 39 40 import com.android.launcher3.Launcher; 41 import com.android.launcher3.LauncherAppState; 42 import com.android.launcher3.LauncherAppWidgetProviderInfo; 43 import com.android.launcher3.LauncherSettings; 44 import com.android.launcher3.MainThreadExecutor; 45 import com.android.launcher3.R; 46 import com.android.launcher3.Utilities; 47 import com.android.launcher3.compat.AppWidgetManagerCompat; 48 import com.android.launcher3.config.FeatureFlags; 49 import com.android.launcher3.testcomponent.AppWidgetNoConfig; 50 import com.android.launcher3.testcomponent.AppWidgetWithConfig; 51 import com.android.launcher3.util.ManagedProfileHeuristic; 52 53 import java.io.BufferedReader; 54 import java.io.FileInputStream; 55 import java.io.IOException; 56 import java.io.InputStreamReader; 57 import java.util.ArrayList; 58 import java.util.Locale; 59 import java.util.concurrent.Callable; 60 import java.util.concurrent.CountDownLatch; 61 import java.util.concurrent.TimeUnit; 62 63 /** 64 * Base class for all instrumentation tests providing various utility methods. 65 */ 66 public class LauncherInstrumentationTestCase extends InstrumentationTestCase { 67 68 public static final long DEFAULT_ACTIVITY_TIMEOUT = TimeUnit.SECONDS.toMillis(10); 69 public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5; 70 71 public static final long DEFAULT_UI_TIMEOUT = 3000; 72 public static final long DEFAULT_WORKER_TIMEOUT_SECS = 5; 73 74 protected UiDevice mDevice; 75 protected Context mTargetContext; 76 protected String mTargetPackage; 77 78 @Override 79 protected void setUp() throws Exception { 80 super.setUp(); 81 82 mDevice = UiDevice.getInstance(getInstrumentation()); 83 mTargetContext = getInstrumentation().getTargetContext(); 84 mTargetPackage = mTargetContext.getPackageName(); 85 } 86 87 protected void lockRotation(boolean naturalOrientation) throws RemoteException { 88 Utilities.getPrefs(mTargetContext) 89 .edit() 90 .putBoolean(Utilities.ALLOW_ROTATION_PREFERENCE_KEY, !naturalOrientation) 91 .commit(); 92 93 if (naturalOrientation) { 94 mDevice.setOrientationNatural(); 95 } else { 96 mDevice.setOrientationRight(); 97 } 98 } 99 100 /** 101 * Starts the launcher activity in the target package and returns the Launcher instance. 102 */ 103 protected Launcher startLauncher() { 104 return (Launcher) getInstrumentation().startActivitySync(getHomeIntent()); 105 } 106 107 protected Intent getHomeIntent() { 108 return new Intent(Intent.ACTION_MAIN) 109 .addCategory(Intent.CATEGORY_HOME) 110 .setPackage(mTargetPackage) 111 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 112 } 113 114 /** 115 * Grants the launcher permission to bind widgets. 116 */ 117 protected void grantWidgetPermission() throws IOException { 118 // Check bind widget permission 119 if (mTargetContext.getPackageManager().checkPermission( 120 mTargetPackage, android.Manifest.permission.BIND_APPWIDGET) 121 != PackageManager.PERMISSION_GRANTED) { 122 runShellCommand("appwidget grantbind --package " + mTargetPackage); 123 } 124 } 125 126 /** 127 * Sets the target launcher as default launcher. 128 */ 129 protected void setDefaultLauncher() throws IOException { 130 ActivityInfo launcher = mTargetContext.getPackageManager() 131 .queryIntentActivities(getHomeIntent(), 0).get(0).activityInfo; 132 runShellCommand("cmd package set-home-activity " + 133 new ComponentName(launcher.packageName, launcher.name).flattenToString()); 134 } 135 136 protected void runShellCommand(String command) throws IOException { 137 ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation() 138 .executeShellCommand(command); 139 140 // Read the input stream fully. 141 FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd); 142 while (fis.read() != -1); 143 fis.close(); 144 } 145 146 /** 147 * Opens all apps and returns the recycler view 148 */ 149 protected UiObject2 openAllApps() { 150 if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) { 151 // clicking on the page indicator brings up all apps tray on non tablets. 152 findViewById(R.id.page_indicator).click(); 153 } else { 154 mDevice.wait(Until.findObject( 155 By.desc(mTargetContext.getString(R.string.all_apps_button_label))), 156 DEFAULT_UI_TIMEOUT).click(); 157 } 158 return findViewById(R.id.apps_list_view); 159 } 160 161 /** 162 * Opens widget tray and returns the recycler view. 163 */ 164 protected UiObject2 openWidgetsTray() { 165 mDevice.pressMenu(); // Enter overview mode. 166 mDevice.wait(Until.findObject( 167 By.text(mTargetContext.getString(R.string.widget_button_text) 168 .toUpperCase(Locale.getDefault()))), DEFAULT_UI_TIMEOUT).click(); 169 return findViewById(R.id.widgets_list_view); 170 } 171 172 /** 173 * Scrolls the {@param container} until it finds an object matching {@param condition}. 174 * @return the matching object. 175 */ 176 protected UiObject2 scrollAndFind(UiObject2 container, BySelector condition) { 177 do { 178 UiObject2 widget = container.findObject(condition); 179 if (widget != null) { 180 return widget; 181 } 182 } while (container.scroll(Direction.DOWN, 1f)); 183 return container.findObject(condition); 184 } 185 186 /** 187 * Drags an icon to the center of homescreen. 188 */ 189 protected void dragToWorkspace(UiObject2 icon, boolean expectedToShowShortcuts) { 190 Point center = icon.getVisibleCenter(); 191 192 // Action Down 193 sendPointer(MotionEvent.ACTION_DOWN, center); 194 195 UiObject2 dragLayer = findViewById(R.id.drag_layer); 196 197 if (expectedToShowShortcuts) { 198 // Make sure shortcuts show up, and then move a bit to hide them. 199 assertNotNull(findViewById(R.id.deep_shortcuts_container)); 200 201 Point moveLocation = new Point(center); 202 int distanceToMove = mTargetContext.getResources().getDimensionPixelSize( 203 R.dimen.deep_shortcuts_start_drag_threshold) + 50; 204 if (moveLocation.y - distanceToMove >= dragLayer.getVisibleBounds().top) { 205 moveLocation.y -= distanceToMove; 206 } else { 207 moveLocation.y += distanceToMove; 208 } 209 movePointer(center, moveLocation); 210 211 assertNull(findViewById(R.id.deep_shortcuts_container)); 212 } 213 214 // Wait until Remove/Delete target is visible 215 assertNotNull(findViewById(R.id.delete_target_text)); 216 217 Point moveLocation = dragLayer.getVisibleCenter(); 218 219 // Move to center 220 movePointer(center, moveLocation); 221 sendPointer(MotionEvent.ACTION_UP, center); 222 223 // Wait until remove target is gone. 224 mDevice.wait(Until.gone(getSelectorForId(R.id.delete_target_text)), DEFAULT_UI_TIMEOUT); 225 } 226 227 private void movePointer(Point from, Point to) { 228 while(!from.equals(to)) { 229 from.x = getNextMoveValue(to.x, from.x); 230 from.y = getNextMoveValue(to.y, from.y); 231 sendPointer(MotionEvent.ACTION_MOVE, from); 232 } 233 } 234 235 private int getNextMoveValue(int targetValue, int oldValue) { 236 if (targetValue - oldValue > 10) { 237 return oldValue + 10; 238 } else if (targetValue - oldValue < -10) { 239 return oldValue - 10; 240 } else { 241 return targetValue; 242 } 243 } 244 245 protected void sendPointer(int action, Point point) { 246 MotionEvent event = MotionEvent.obtain(SystemClock.uptimeMillis(), 247 SystemClock.uptimeMillis(), action, point.x, point.y, 0); 248 getInstrumentation().sendPointerSync(event); 249 event.recycle(); 250 } 251 252 /** 253 * Removes all icons from homescreen and hotseat. 254 */ 255 public void clearHomescreen() throws Throwable { 256 LauncherSettings.Settings.call(mTargetContext.getContentResolver(), 257 LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB); 258 LauncherSettings.Settings.call(mTargetContext.getContentResolver(), 259 LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG); 260 resetLoaderState(); 261 } 262 263 protected void resetLoaderState() { 264 try { 265 runTestOnUiThread(new Runnable() { 266 @Override 267 public void run() { 268 ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext); 269 LauncherAppState.getInstance(mTargetContext).getModel().forceReload(); 270 } 271 }); 272 } catch (Throwable t) { 273 throw new IllegalArgumentException(t); 274 } 275 } 276 277 /** 278 * Runs the callback on the UI thread and returns the result. 279 */ 280 protected <T> T getOnUiThread(final Callable<T> callback) { 281 try { 282 return new MainThreadExecutor().submit(callback).get(); 283 } catch (Exception e) { 284 throw new RuntimeException(e); 285 } 286 } 287 288 /** 289 * Finds a widget provider which can fit on the home screen. 290 * @param hasConfigureScreen if true, a provider with a config screen is returned. 291 */ 292 protected LauncherAppWidgetProviderInfo findWidgetProvider(final boolean hasConfigureScreen) { 293 LauncherAppWidgetProviderInfo info = 294 getOnUiThread(new Callable<LauncherAppWidgetProviderInfo>() { 295 @Override 296 public LauncherAppWidgetProviderInfo call() throws Exception { 297 ComponentName cn = new ComponentName(getInstrumentation().getContext(), 298 hasConfigureScreen ? AppWidgetWithConfig.class : AppWidgetNoConfig.class); 299 return AppWidgetManagerCompat.getInstance(mTargetContext) 300 .findProvider(cn, Process.myUserHandle()); 301 } 302 }); 303 if (info == null) { 304 throw new IllegalArgumentException("No valid widget provider"); 305 } 306 return info; 307 } 308 309 protected UiObject2 findViewById(int id) { 310 return mDevice.wait(Until.findObject(getSelectorForId(id)), DEFAULT_UI_TIMEOUT); 311 } 312 313 protected BySelector getSelectorForId(int id) { 314 String name = mTargetContext.getResources().getResourceEntryName(id); 315 return By.res(mTargetPackage, name); 316 } 317 318 319 /** 320 * Broadcast receiver which blocks until the result is received. 321 */ 322 public class BlockingBroadcastReceiver extends BroadcastReceiver { 323 324 private final CountDownLatch latch = new CountDownLatch(1); 325 private Intent mIntent; 326 327 public BlockingBroadcastReceiver(String action) { 328 mTargetContext.registerReceiver(this, new IntentFilter(action)); 329 } 330 331 @Override 332 public void onReceive(Context context, Intent intent) { 333 mIntent = intent; 334 latch.countDown(); 335 } 336 337 public Intent blockingGetIntent() throws InterruptedException { 338 latch.await(DEFAULT_BROADCAST_TIMEOUT_SECS, TimeUnit.SECONDS); 339 mTargetContext.unregisterReceiver(this); 340 return mIntent; 341 } 342 343 public Intent blockingGetExtraIntent() throws InterruptedException { 344 Intent intent = blockingGetIntent(); 345 return intent == null ? null : (Intent) intent.getParcelableExtra(Intent.EXTRA_INTENT); 346 } 347 } 348 } 349