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.compat.AppWidgetManagerCompat; 44 import com.android.launcher3.compat.LauncherAppsCompat; 45 import com.android.launcher3.config.FeatureFlags; 46 import com.android.launcher3.testcomponent.AppWidgetNoConfig; 47 import com.android.launcher3.testcomponent.AppWidgetWithConfig; 48 49 import org.junit.Before; 50 51 import java.util.Locale; 52 import java.util.concurrent.Callable; 53 import java.util.concurrent.CountDownLatch; 54 import java.util.concurrent.TimeUnit; 55 56 import static org.junit.Assert.assertNotNull; 57 import static org.junit.Assert.assertNull; 58 59 /** 60 * Base class for all instrumentation tests providing various utility methods. 61 */ 62 public abstract class AbstractLauncherUiTest { 63 64 public static final long DEFAULT_ACTIVITY_TIMEOUT = TimeUnit.SECONDS.toMillis(10); 65 public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5; 66 67 public static final long DEFAULT_UI_TIMEOUT = 3000; 68 public static final long LARGE_UI_TIMEOUT = 10000; 69 public static final long DEFAULT_WORKER_TIMEOUT_SECS = 5; 70 71 protected MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor(); 72 protected UiDevice mDevice; 73 protected Context mTargetContext; 74 protected String mTargetPackage; 75 76 @Before 77 public void setUp() throws Exception { 78 mDevice = UiDevice.getInstance(getInstrumentation()); 79 mTargetContext = InstrumentationRegistry.getTargetContext(); 80 mTargetPackage = mTargetContext.getPackageName(); 81 } 82 83 protected void lockRotation(boolean naturalOrientation) throws RemoteException { 84 if (naturalOrientation) { 85 mDevice.setOrientationNatural(); 86 } else { 87 mDevice.setOrientationRight(); 88 } 89 } 90 91 protected Instrumentation getInstrumentation() { 92 return InstrumentationRegistry.getInstrumentation(); 93 } 94 95 /** 96 * Opens all apps and returns the recycler view 97 */ 98 protected UiObject2 openAllApps() { 99 mDevice.waitForIdle(); 100 if (FeatureFlags.NO_ALL_APPS_ICON) { 101 UiObject2 hotseat = mDevice.wait( 102 Until.findObject(getSelectorForId(R.id.hotseat)), 2500); 103 Point start = hotseat.getVisibleCenter(); 104 int endY = (int) (mDevice.getDisplayHeight() * 0.1f); 105 // 100 px/step 106 mDevice.swipe(start.x, start.y, start.x, endY, (start.y - endY) / 100); 107 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, boolean expectedToShowShortcuts) { 145 Point center = icon.getVisibleCenter(); 146 147 // Action Down 148 sendPointer(MotionEvent.ACTION_DOWN, center); 149 150 UiObject2 dragLayer = findViewById(R.id.drag_layer); 151 152 if (expectedToShowShortcuts) { 153 // Make sure shortcuts show up, and then move a bit to hide them. 154 assertNotNull(findViewById(R.id.deep_shortcuts_container)); 155 156 Point moveLocation = new Point(center); 157 int distanceToMove = mTargetContext.getResources().getDimensionPixelSize( 158 R.dimen.deep_shortcuts_start_drag_threshold) + 50; 159 if (moveLocation.y - distanceToMove >= dragLayer.getVisibleBounds().top) { 160 moveLocation.y -= distanceToMove; 161 } else { 162 moveLocation.y += distanceToMove; 163 } 164 movePointer(center, moveLocation); 165 166 assertNull(findViewById(R.id.deep_shortcuts_container)); 167 } 168 169 // Wait until Remove/Delete target is visible 170 assertNotNull(findViewById(R.id.delete_target_text)); 171 172 Point moveLocation = dragLayer.getVisibleCenter(); 173 174 // Move to center 175 movePointer(center, moveLocation); 176 sendPointer(MotionEvent.ACTION_UP, center); 177 178 // Wait until remove target is gone. 179 mDevice.wait(Until.gone(getSelectorForId(R.id.delete_target_text)), DEFAULT_UI_TIMEOUT); 180 } 181 182 private void movePointer(Point from, Point to) { 183 while(!from.equals(to)) { 184 from.x = getNextMoveValue(to.x, from.x); 185 from.y = getNextMoveValue(to.y, from.y); 186 sendPointer(MotionEvent.ACTION_MOVE, from); 187 } 188 } 189 190 private int getNextMoveValue(int targetValue, int oldValue) { 191 if (targetValue - oldValue > 10) { 192 return oldValue + 10; 193 } else if (targetValue - oldValue < -10) { 194 return oldValue - 10; 195 } else { 196 return targetValue; 197 } 198 } 199 200 protected void sendPointer(int action, Point point) { 201 MotionEvent event = MotionEvent.obtain(SystemClock.uptimeMillis(), 202 SystemClock.uptimeMillis(), action, point.x, point.y, 0); 203 getInstrumentation().sendPointerSync(event); 204 event.recycle(); 205 } 206 207 /** 208 * Removes all icons from homescreen and hotseat. 209 */ 210 public void clearHomescreen() throws Throwable { 211 LauncherSettings.Settings.call(mTargetContext.getContentResolver(), 212 LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB); 213 LauncherSettings.Settings.call(mTargetContext.getContentResolver(), 214 LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG); 215 resetLoaderState(); 216 } 217 218 protected void resetLoaderState() { 219 try { 220 mMainThreadExecutor.execute(new Runnable() { 221 @Override 222 public void run() { 223 LauncherAppState.getInstance(mTargetContext).getModel().forceReload(); 224 } 225 }); 226 } catch (Throwable t) { 227 throw new IllegalArgumentException(t); 228 } 229 } 230 231 /** 232 * Runs the callback on the UI thread and returns the result. 233 */ 234 protected <T> T getOnUiThread(final Callable<T> callback) { 235 try { 236 return mMainThreadExecutor.submit(callback).get(); 237 } catch (Exception e) { 238 throw new RuntimeException(e); 239 } 240 } 241 242 /** 243 * Finds a widget provider which can fit on the home screen. 244 * @param hasConfigureScreen if true, a provider with a config screen is returned. 245 */ 246 protected LauncherAppWidgetProviderInfo findWidgetProvider(final boolean hasConfigureScreen) { 247 LauncherAppWidgetProviderInfo info = 248 getOnUiThread(new Callable<LauncherAppWidgetProviderInfo>() { 249 @Override 250 public LauncherAppWidgetProviderInfo call() throws Exception { 251 ComponentName cn = new ComponentName(getInstrumentation().getContext(), 252 hasConfigureScreen ? AppWidgetWithConfig.class : AppWidgetNoConfig.class); 253 return AppWidgetManagerCompat.getInstance(mTargetContext) 254 .findProvider(cn, Process.myUserHandle()); 255 } 256 }); 257 if (info == null) { 258 throw new IllegalArgumentException("No valid widget provider"); 259 } 260 return info; 261 } 262 263 protected UiObject2 findViewById(int id) { 264 return mDevice.wait(Until.findObject(getSelectorForId(id)), DEFAULT_UI_TIMEOUT); 265 } 266 267 protected BySelector getSelectorForId(int id) { 268 String name = mTargetContext.getResources().getResourceEntryName(id); 269 return By.res(mTargetPackage, name); 270 } 271 272 protected LauncherActivityInfo getSettingsApp() { 273 return LauncherAppsCompat.getInstance(mTargetContext) 274 .getActivityList("com.android.settings", Process.myUserHandle()).get(0); 275 } 276 277 /** 278 * Broadcast receiver which blocks until the result is received. 279 */ 280 public class BlockingBroadcastReceiver extends BroadcastReceiver { 281 282 private final CountDownLatch latch = new CountDownLatch(1); 283 private Intent mIntent; 284 285 public BlockingBroadcastReceiver(String action) { 286 mTargetContext.registerReceiver(this, new IntentFilter(action)); 287 } 288 289 @Override 290 public void onReceive(Context context, Intent intent) { 291 mIntent = intent; 292 latch.countDown(); 293 } 294 295 public Intent blockingGetIntent() throws InterruptedException { 296 latch.await(DEFAULT_BROADCAST_TIMEOUT_SECS, TimeUnit.SECONDS); 297 mTargetContext.unregisterReceiver(this); 298 return mIntent; 299 } 300 301 public Intent blockingGetExtraIntent() throws InterruptedException { 302 Intent intent = blockingGetIntent(); 303 return intent == null ? null : (Intent) intent.getParcelableExtra(Intent.EXTRA_INTENT); 304 } 305 } 306 } 307