| package com.android.launcher3.ui; |
| |
| import android.app.SearchManager; |
| import android.appwidget.AppWidgetProviderInfo; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.PackageManager; |
| import android.graphics.Point; |
| import android.os.ParcelFileDescriptor; |
| import android.os.RemoteException; |
| import android.os.SystemClock; |
| import android.support.test.uiautomator.By; |
| import android.support.test.uiautomator.BySelector; |
| import android.support.test.uiautomator.Direction; |
| import android.support.test.uiautomator.UiDevice; |
| import android.support.test.uiautomator.UiObject2; |
| import android.support.test.uiautomator.Until; |
| import android.test.InstrumentationTestCase; |
| import android.view.MotionEvent; |
| |
| import com.android.launcher3.InvariantDeviceProfile; |
| import com.android.launcher3.Launcher; |
| import com.android.launcher3.LauncherAppState; |
| import com.android.launcher3.LauncherAppWidgetProviderInfo; |
| import com.android.launcher3.LauncherClings; |
| import com.android.launcher3.LauncherSettings; |
| import com.android.launcher3.R; |
| import com.android.launcher3.Utilities; |
| import com.android.launcher3.compat.AppWidgetManagerCompat; |
| import com.android.launcher3.config.FeatureFlags; |
| import com.android.launcher3.util.ManagedProfileHeuristic; |
| |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.util.Locale; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| /** |
| * Base class for all instrumentation tests providing various utility methods. |
| */ |
| public class LauncherInstrumentationTestCase extends InstrumentationTestCase { |
| |
| public static final long DEFAULT_UI_TIMEOUT = 3000; |
| |
| protected UiDevice mDevice; |
| protected Context mTargetContext; |
| protected String mTargetPackage; |
| |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| |
| mDevice = UiDevice.getInstance(getInstrumentation()); |
| mTargetContext = getInstrumentation().getTargetContext(); |
| mTargetPackage = mTargetContext.getPackageName(); |
| } |
| |
| protected void lockRotation(boolean naturalOrientation) throws RemoteException { |
| Utilities.getPrefs(mTargetContext) |
| .edit() |
| .putBoolean(Utilities.ALLOW_ROTATION_PREFERENCE_KEY, !naturalOrientation) |
| .commit(); |
| |
| if (naturalOrientation) { |
| mDevice.setOrientationNatural(); |
| } else { |
| mDevice.setOrientationRight(); |
| } |
| } |
| |
| /** |
| * Starts the launcher activity in the target package and returns the Launcher instance. |
| */ |
| protected Launcher startLauncher() { |
| Intent homeIntent = new Intent(Intent.ACTION_MAIN) |
| .addCategory(Intent.CATEGORY_HOME) |
| .setPackage(mTargetPackage) |
| .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| return (Launcher) getInstrumentation().startActivitySync(homeIntent); |
| } |
| |
| /** |
| * Grants the launcher permission to bind widgets. |
| */ |
| protected void grantWidgetPermission() throws IOException { |
| // Check bind widget permission |
| if (mTargetContext.getPackageManager().checkPermission( |
| mTargetPackage, android.Manifest.permission.BIND_APPWIDGET) |
| != PackageManager.PERMISSION_GRANTED) { |
| ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation().executeShellCommand( |
| "appwidget grantbind --package " + mTargetPackage); |
| // Read the input stream fully. |
| FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd); |
| while (fis.read() != -1); |
| fis.close(); |
| } |
| } |
| |
| /** |
| * Opens all apps and returns the recycler view |
| */ |
| protected UiObject2 openAllApps() { |
| if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) { |
| // clicking on the page indicator brings up all apps tray on non tablets. |
| findViewById(R.id.page_indicator).click(); |
| } else { |
| mDevice.wait(Until.findObject( |
| By.desc(mTargetContext.getString(R.string.all_apps_button_label))), |
| DEFAULT_UI_TIMEOUT).click(); |
| } |
| return findViewById(R.id.apps_list_view); |
| } |
| |
| /** |
| * Opens widget tray and returns the recycler view. |
| */ |
| protected UiObject2 openWidgetsTray() { |
| mDevice.pressMenu(); // Enter overview mode. |
| mDevice.wait(Until.findObject( |
| By.text(mTargetContext.getString(R.string.widget_button_text) |
| .toUpperCase(Locale.getDefault()))), DEFAULT_UI_TIMEOUT).click(); |
| return findViewById(R.id.widgets_list_view); |
| } |
| |
| /** |
| * Scrolls the {@param container} until it finds an object matching {@param condition}. |
| * @return the matching object. |
| */ |
| protected UiObject2 scrollAndFind(UiObject2 container, BySelector condition) { |
| do { |
| UiObject2 widget = container.findObject(condition); |
| if (widget != null) { |
| return widget; |
| } |
| } while (container.scroll(Direction.DOWN, 1f)); |
| return container.findObject(condition); |
| } |
| |
| /** |
| * Drags an icon to the center of homescreen. |
| */ |
| protected void dragToWorkspace(UiObject2 icon) { |
| Point center = icon.getVisibleCenter(); |
| |
| // Action Down |
| sendPointer(MotionEvent.ACTION_DOWN, center); |
| |
| // Wait until "Remove/Delete target is visible |
| assertNotNull(findViewById(R.id.delete_target_text)); |
| |
| Point moveLocation = findViewById(R.id.drag_layer).getVisibleCenter(); |
| |
| // Move to center |
| while(!moveLocation.equals(center)) { |
| center.x = getNextMoveValue(moveLocation.x, center.x); |
| center.y = getNextMoveValue(moveLocation.y, center.y); |
| sendPointer(MotionEvent.ACTION_MOVE, center); |
| } |
| sendPointer(MotionEvent.ACTION_UP, center); |
| |
| // Wait until remove target is gone. |
| mDevice.wait(Until.gone(getSelectorForId(R.id.delete_target_text)), DEFAULT_UI_TIMEOUT); |
| } |
| |
| private int getNextMoveValue(int targetValue, int oldValue) { |
| if (targetValue - oldValue > 10) { |
| return oldValue + 10; |
| } else if (targetValue - oldValue < -10) { |
| return oldValue - 10; |
| } else { |
| return targetValue; |
| } |
| } |
| |
| private void sendPointer(int action, Point point) { |
| MotionEvent event = MotionEvent.obtain(SystemClock.uptimeMillis(), |
| SystemClock.uptimeMillis(), action, point.x, point.y, 0); |
| getInstrumentation().sendPointerSync(event); |
| event.recycle(); |
| } |
| |
| /** |
| * Removes all icons from homescreen and hotseat. |
| */ |
| public void clearHomescreen() throws Throwable { |
| LauncherSettings.Settings.call(mTargetContext.getContentResolver(), |
| LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB); |
| LauncherSettings.Settings.call(mTargetContext.getContentResolver(), |
| LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG); |
| LauncherClings.markFirstRunClingDismissed(mTargetContext); |
| ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext); |
| |
| runTestOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| // Reset the loader state |
| LauncherAppState.getInstance().getModel().resetLoadedState(true, true); |
| } |
| }); |
| } |
| |
| /** |
| * Runs the callback on the UI thread and returns the result. |
| */ |
| protected <T> T getOnUiThread(final Callable<T> callback) { |
| final AtomicReference<T> result = new AtomicReference<>(null); |
| try { |
| runTestOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| result.set(callback.call()); |
| } catch (Exception e) { } |
| } |
| }); |
| } catch (Throwable t) { } |
| return result.get(); |
| } |
| |
| /** |
| * Finds a widget provider which can fit on the home screen. |
| * @param hasConfigureScreen if true, a provider with a config screen is returned. |
| */ |
| protected LauncherAppWidgetProviderInfo findWidgetProvider(final boolean hasConfigureScreen) { |
| LauncherAppWidgetProviderInfo info = getOnUiThread(new Callable<LauncherAppWidgetProviderInfo>() { |
| @Override |
| public LauncherAppWidgetProviderInfo call() throws Exception { |
| InvariantDeviceProfile idv = |
| LauncherAppState.getInstance().getInvariantDeviceProfile(); |
| |
| ComponentName searchComponent = ((SearchManager) mTargetContext |
| .getSystemService(Context.SEARCH_SERVICE)).getGlobalSearchActivity(); |
| String searchPackage = searchComponent == null |
| ? null : searchComponent.getPackageName(); |
| |
| for (AppWidgetProviderInfo info : |
| AppWidgetManagerCompat.getInstance(mTargetContext).getAllProviders()) { |
| if ((info.configure != null) ^ hasConfigureScreen) { |
| continue; |
| } |
| // Exclude the widgets in search package, as Launcher already binds them in |
| // QSB, so they can cause conflicts. |
| if (info.provider.getPackageName().equals(searchPackage)) { |
| continue; |
| } |
| LauncherAppWidgetProviderInfo widgetInfo = LauncherAppWidgetProviderInfo |
| .fromProviderInfo(mTargetContext, info); |
| if (widgetInfo.minSpanX >= idv.numColumns |
| || widgetInfo.minSpanY >= idv.numRows) { |
| continue; |
| } |
| return widgetInfo; |
| } |
| return null; |
| } |
| }); |
| if (info == null) { |
| throw new IllegalArgumentException("No valid widget provider"); |
| } |
| return info; |
| } |
| |
| protected UiObject2 findViewById(int id) { |
| return mDevice.wait(Until.findObject(getSelectorForId(id)), DEFAULT_UI_TIMEOUT); |
| } |
| |
| protected BySelector getSelectorForId(int id) { |
| String name = mTargetContext.getResources().getResourceEntryName(id); |
| return By.res(mTargetPackage, name); |
| } |
| } |