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.app.Instrumentation; 19 import android.content.BroadcastReceiver; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.content.pm.LauncherActivityInfo; 25 import android.graphics.Point; 26 import android.os.Process; 27 import android.os.RemoteException; 28 import android.os.SystemClock; 29 import android.support.test.InstrumentationRegistry; 30 import android.support.test.uiautomator.By; 31 import android.support.test.uiautomator.BySelector; 32 import android.support.test.uiautomator.Direction; 33 import android.support.test.uiautomator.UiDevice; 34 import android.support.test.uiautomator.UiObject2; 35 import android.support.test.uiautomator.Until; 36 import android.view.MotionEvent; 37 38 import com.android.launcher3.LauncherAppState; 39 import com.android.launcher3.LauncherAppWidgetProviderInfo; 40 import com.android.launcher3.LauncherSettings; 41 import com.android.launcher3.MainThreadExecutor; 42 import com.android.launcher3.R; 43 import com.android.launcher3.Utilities; 44 import com.android.launcher3.compat.AppWidgetManagerCompat; 45 import com.android.launcher3.compat.LauncherAppsCompat; 46 import com.android.launcher3.config.FeatureFlags; 47 import com.android.launcher3.testcomponent.AppWidgetNoConfig; 48 import com.android.launcher3.testcomponent.AppWidgetWithConfig; 49 import com.android.launcher3.util.ManagedProfileHeuristic; 50 51 import org.junit.Before; 52 53 import java.util.Locale; 54 import java.util.concurrent.Callable; 55 import java.util.concurrent.CountDownLatch; 56 import java.util.concurrent.TimeUnit; 57 58 import static org.junit.Assert.assertNotNull; 59 import static org.junit.Assert.assertNull; 60 61 /** 62 * Base class for all instrumentation tests providing various utility methods. 63 */ 64 public abstract class AbstractLauncherUiTest { 65 66 public static final long DEFAULT_ACTIVITY_TIMEOUT = TimeUnit.SECONDS.toMillis(10); 67 public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5; 68 69 public static final long DEFAULT_UI_TIMEOUT = 3000; 70 public static final long DEFAULT_WORKER_TIMEOUT_SECS = 5; 71 72 protected MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor(); 73 protected UiDevice mDevice; 74 protected Context mTargetContext; 75 protected String mTargetPackage; 76 77 @Before setUp()78 public void setUp() throws Exception { 79 mDevice = UiDevice.getInstance(getInstrumentation()); 80 mTargetContext = InstrumentationRegistry.getTargetContext(); 81 mTargetPackage = mTargetContext.getPackageName(); 82 } 83 lockRotation(boolean naturalOrientation)84 protected void lockRotation(boolean naturalOrientation) throws RemoteException { 85 Utilities.getPrefs(mTargetContext) 86 .edit() 87 .putBoolean(Utilities.ALLOW_ROTATION_PREFERENCE_KEY, !naturalOrientation) 88 .commit(); 89 90 if (naturalOrientation) { 91 mDevice.setOrientationNatural(); 92 } else { 93 mDevice.setOrientationRight(); 94 } 95 } 96 getInstrumentation()97 protected Instrumentation getInstrumentation() { 98 return InstrumentationRegistry.getInstrumentation(); 99 } 100 101 /** 102 * Opens all apps and returns the recycler view 103 */ openAllApps()104 protected UiObject2 openAllApps() { 105 mDevice.waitForIdle(); 106 if (FeatureFlags.NO_ALL_APPS_ICON) { 107 // clicking on the page indicator brings up all apps tray on non tablets. 108 findViewById(R.id.page_indicator).click(); 109 } else { 110 mDevice.wait(Until.findObject( 111 By.desc(mTargetContext.getString(R.string.all_apps_button_label))), 112 DEFAULT_UI_TIMEOUT).click(); 113 } 114 return findViewById(R.id.apps_list_view); 115 } 116 117 /** 118 * Opens widget tray and returns the recycler view. 119 */ openWidgetsTray()120 protected UiObject2 openWidgetsTray() { 121 mDevice.pressMenu(); // Enter overview mode. 122 mDevice.wait(Until.findObject( 123 By.text(mTargetContext.getString(R.string.widget_button_text) 124 .toUpperCase(Locale.getDefault()))), DEFAULT_UI_TIMEOUT).click(); 125 return findViewById(R.id.widgets_list_view); 126 } 127 128 /** 129 * Scrolls the {@param container} until it finds an object matching {@param condition}. 130 * @return the matching object. 131 */ scrollAndFind(UiObject2 container, BySelector condition)132 protected UiObject2 scrollAndFind(UiObject2 container, BySelector condition) { 133 do { 134 UiObject2 widget = container.findObject(condition); 135 if (widget != null) { 136 return widget; 137 } 138 } while (container.scroll(Direction.DOWN, 1f)); 139 return container.findObject(condition); 140 } 141 142 /** 143 * Drags an icon to the center of homescreen. 144 */ dragToWorkspace(UiObject2 icon, boolean expectedToShowShortcuts)145 protected void dragToWorkspace(UiObject2 icon, boolean expectedToShowShortcuts) { 146 Point center = icon.getVisibleCenter(); 147 148 // Action Down 149 sendPointer(MotionEvent.ACTION_DOWN, center); 150 151 UiObject2 dragLayer = findViewById(R.id.drag_layer); 152 153 if (expectedToShowShortcuts) { 154 // Make sure shortcuts show up, and then move a bit to hide them. 155 assertNotNull(findViewById(R.id.deep_shortcuts_container)); 156 157 Point moveLocation = new Point(center); 158 int distanceToMove = mTargetContext.getResources().getDimensionPixelSize( 159 R.dimen.deep_shortcuts_start_drag_threshold) + 50; 160 if (moveLocation.y - distanceToMove >= dragLayer.getVisibleBounds().top) { 161 moveLocation.y -= distanceToMove; 162 } else { 163 moveLocation.y += distanceToMove; 164 } 165 movePointer(center, moveLocation); 166 167 assertNull(findViewById(R.id.deep_shortcuts_container)); 168 } 169 170 // Wait until Remove/Delete target is visible 171 assertNotNull(findViewById(R.id.delete_target_text)); 172 173 Point moveLocation = dragLayer.getVisibleCenter(); 174 175 // Move to center 176 movePointer(center, moveLocation); 177 sendPointer(MotionEvent.ACTION_UP, center); 178 179 // Wait until remove target is gone. 180 mDevice.wait(Until.gone(getSelectorForId(R.id.delete_target_text)), DEFAULT_UI_TIMEOUT); 181 } 182 movePointer(Point from, Point to)183 private void movePointer(Point from, Point to) { 184 while(!from.equals(to)) { 185 from.x = getNextMoveValue(to.x, from.x); 186 from.y = getNextMoveValue(to.y, from.y); 187 sendPointer(MotionEvent.ACTION_MOVE, from); 188 } 189 } 190 getNextMoveValue(int targetValue, int oldValue)191 private int getNextMoveValue(int targetValue, int oldValue) { 192 if (targetValue - oldValue > 10) { 193 return oldValue + 10; 194 } else if (targetValue - oldValue < -10) { 195 return oldValue - 10; 196 } else { 197 return targetValue; 198 } 199 } 200 sendPointer(int action, Point point)201 protected void sendPointer(int action, Point point) { 202 MotionEvent event = MotionEvent.obtain(SystemClock.uptimeMillis(), 203 SystemClock.uptimeMillis(), action, point.x, point.y, 0); 204 getInstrumentation().sendPointerSync(event); 205 event.recycle(); 206 } 207 208 /** 209 * Removes all icons from homescreen and hotseat. 210 */ clearHomescreen()211 public void clearHomescreen() throws Throwable { 212 LauncherSettings.Settings.call(mTargetContext.getContentResolver(), 213 LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB); 214 LauncherSettings.Settings.call(mTargetContext.getContentResolver(), 215 LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG); 216 resetLoaderState(); 217 } 218 resetLoaderState()219 protected void resetLoaderState() { 220 try { 221 mMainThreadExecutor.execute(new Runnable() { 222 @Override 223 public void run() { 224 ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext); 225 LauncherAppState.getInstance(mTargetContext).getModel().forceReload(); 226 } 227 }); 228 } catch (Throwable t) { 229 throw new IllegalArgumentException(t); 230 } 231 } 232 233 /** 234 * Runs the callback on the UI thread and returns the result. 235 */ getOnUiThread(final Callable<T> callback)236 protected <T> T getOnUiThread(final Callable<T> callback) { 237 try { 238 return mMainThreadExecutor.submit(callback).get(); 239 } catch (Exception e) { 240 throw new RuntimeException(e); 241 } 242 } 243 244 /** 245 * Finds a widget provider which can fit on the home screen. 246 * @param hasConfigureScreen if true, a provider with a config screen is returned. 247 */ findWidgetProvider(final boolean hasConfigureScreen)248 protected LauncherAppWidgetProviderInfo findWidgetProvider(final boolean hasConfigureScreen) { 249 LauncherAppWidgetProviderInfo info = 250 getOnUiThread(new Callable<LauncherAppWidgetProviderInfo>() { 251 @Override 252 public LauncherAppWidgetProviderInfo call() throws Exception { 253 ComponentName cn = new ComponentName(getInstrumentation().getContext(), 254 hasConfigureScreen ? AppWidgetWithConfig.class : AppWidgetNoConfig.class); 255 return AppWidgetManagerCompat.getInstance(mTargetContext) 256 .findProvider(cn, Process.myUserHandle()); 257 } 258 }); 259 if (info == null) { 260 throw new IllegalArgumentException("No valid widget provider"); 261 } 262 return info; 263 } 264 findViewById(int id)265 protected UiObject2 findViewById(int id) { 266 return mDevice.wait(Until.findObject(getSelectorForId(id)), DEFAULT_UI_TIMEOUT); 267 } 268 getSelectorForId(int id)269 protected BySelector getSelectorForId(int id) { 270 String name = mTargetContext.getResources().getResourceEntryName(id); 271 return By.res(mTargetPackage, name); 272 } 273 getSettingsApp()274 protected LauncherActivityInfo getSettingsApp() { 275 return LauncherAppsCompat.getInstance(mTargetContext) 276 .getActivityList("com.android.settings", Process.myUserHandle()).get(0); 277 } 278 279 /** 280 * Broadcast receiver which blocks until the result is received. 281 */ 282 public class BlockingBroadcastReceiver extends BroadcastReceiver { 283 284 private final CountDownLatch latch = new CountDownLatch(1); 285 private Intent mIntent; 286 BlockingBroadcastReceiver(String action)287 public BlockingBroadcastReceiver(String action) { 288 mTargetContext.registerReceiver(this, new IntentFilter(action)); 289 } 290 291 @Override onReceive(Context context, Intent intent)292 public void onReceive(Context context, Intent intent) { 293 mIntent = intent; 294 latch.countDown(); 295 } 296 blockingGetIntent()297 public Intent blockingGetIntent() throws InterruptedException { 298 latch.await(DEFAULT_BROADCAST_TIMEOUT_SECS, TimeUnit.SECONDS); 299 mTargetContext.unregisterReceiver(this); 300 return mIntent; 301 } 302 blockingGetExtraIntent()303 public Intent blockingGetExtraIntent() throws InterruptedException { 304 Intent intent = blockingGetIntent(); 305 return intent == null ? null : (Intent) intent.getParcelableExtra(Intent.EXTRA_INTENT); 306 } 307 } 308 } 309