summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk3
-rw-r--r--quickstep/libs/sysui_shared.jarbin133150 -> 136447 bytes
-rw-r--r--quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java3
-rw-r--r--quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java2
-rw-r--r--quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java2
-rw-r--r--quickstep/src/com/android/launcher3/uioverrides/OverviewState.java13
-rw-r--r--quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java2
-rw-r--r--quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java2
-rw-r--r--quickstep/src/com/android/launcher3/uioverrides/UiFactory.java26
-rw-r--r--quickstep/src/com/android/quickstep/ActivityControlHelper.java3
-rw-r--r--quickstep/src/com/android/quickstep/OverviewCommandHelper.java2
-rw-r--r--quickstep/src/com/android/quickstep/OverviewInteractionState.java58
-rw-r--r--quickstep/src/com/android/quickstep/RecentsActivity.java6
-rw-r--r--quickstep/src/com/android/quickstep/RecentsModel.java22
-rw-r--r--quickstep/src/com/android/quickstep/SwipeUpSetting.java50
-rw-r--r--quickstep/src/com/android/quickstep/TaskSystemShortcut.java4
-rw-r--r--quickstep/src/com/android/quickstep/TaskUtils.java8
-rw-r--r--quickstep/src/com/android/quickstep/TouchInteractionService.java16
-rw-r--r--quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java25
-rw-r--r--quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java4
-rw-r--r--quickstep/src/com/android/quickstep/views/LauncherRecentsView.java2
-rw-r--r--quickstep/src/com/android/quickstep/views/RecentsView.java24
-rw-r--r--quickstep/src/com/android/quickstep/views/TaskMenuView.java25
-rw-r--r--quickstep/src/com/android/quickstep/views/TaskView.java35
-rw-r--r--res/drawable-hdpi/ic_allapps.pngbin2956 -> 0 bytes
-rw-r--r--res/drawable-hdpi/ic_allapps_pressed.pngbin3231 -> 0 bytes
-rw-r--r--res/drawable-mdpi/ic_allapps.pngbin1488 -> 0 bytes
-rw-r--r--res/drawable-mdpi/ic_allapps_pressed.pngbin1997 -> 0 bytes
-rw-r--r--res/drawable-xhdpi/ic_allapps.pngbin3598 -> 0 bytes
-rw-r--r--res/drawable-xhdpi/ic_allapps_pressed.pngbin3961 -> 0 bytes
-rw-r--r--res/drawable-xxhdpi/ic_allapps.pngbin6398 -> 0 bytes
-rw-r--r--res/drawable-xxhdpi/ic_allapps_pressed.pngbin7039 -> 0 bytes
-rw-r--r--res/drawable/all_apps_button_icon.xml21
-rw-r--r--res/layout/all_apps_button.xml17
-rw-r--r--res/values-sw720dp/dimens.xml1
-rw-r--r--res/values/config.xml2
-rw-r--r--res/values/dimens.xml1
-rw-r--r--res/xml/device_profiles.xml8
-rw-r--r--res/xml/dw_phone_hotseat.xml11
-rw-r--r--res/xml/dw_tablet_hotseat.xml10
-rw-r--r--src/com/android/launcher3/AbstractFloatingView.java6
-rw-r--r--src/com/android/launcher3/AutoInstallsLayout.java13
-rw-r--r--src/com/android/launcher3/DeviceProfile.java7
-rw-r--r--src/com/android/launcher3/Hotseat.java47
-rw-r--r--src/com/android/launcher3/InvariantDeviceProfile.java12
-rw-r--r--src/com/android/launcher3/Launcher.java18
-rw-r--r--src/com/android/launcher3/LauncherState.java11
-rw-r--r--src/com/android/launcher3/LauncherStateManager.java6
-rw-r--r--src/com/android/launcher3/PagedView.java64
-rw-r--r--src/com/android/launcher3/Utilities.java8
-rw-r--r--src/com/android/launcher3/Workspace.java60
-rw-r--r--src/com/android/launcher3/allapps/AllAppsContainerView.java14
-rw-r--r--src/com/android/launcher3/allapps/WorkModeSwitch.java66
-rw-r--r--src/com/android/launcher3/compat/AccessibilityManagerCompat.java56
-rw-r--r--src/com/android/launcher3/config/BaseFlags.java4
-rw-r--r--src/com/android/launcher3/dragndrop/DragController.java3
-rw-r--r--src/com/android/launcher3/dragndrop/DragView.java8
-rw-r--r--src/com/android/launcher3/folder/Folder.java19
-rw-r--r--src/com/android/launcher3/folder/FolderIcon.java7
-rw-r--r--src/com/android/launcher3/graphics/NinePatchDrawHelper.java4
-rw-r--r--src/com/android/launcher3/graphics/ShadowDrawable.java3
-rw-r--r--src/com/android/launcher3/graphics/ShadowGenerator.java6
-rw-r--r--src/com/android/launcher3/model/GridSizeMigrationTask.java8
-rw-r--r--src/com/android/launcher3/model/LoaderCursor.java10
-rw-r--r--src/com/android/launcher3/notification/NotificationFooterLayout.java2
-rw-r--r--src/com/android/launcher3/popup/PopupContainerWithArrow.java2
-rw-r--r--src/com/android/launcher3/provider/ImportDataTask.java3
-rw-r--r--src/com/android/launcher3/shortcuts/DeepShortcutView.java4
-rw-r--r--src/com/android/launcher3/touch/AbstractStateChangeTouchController.java3
-rw-r--r--src/com/android/launcher3/touch/ItemClickHandler.java2
-rw-r--r--src/com/android/launcher3/touch/WorkspaceTouchListener.java2
-rw-r--r--src/com/android/launcher3/util/FocusLogic.java29
-rw-r--r--src/com/android/launcher3/util/OverScroller.java963
-rw-r--r--src/com/android/launcher3/views/AbstractSlideInView.java13
-rw-r--r--src/com/android/launcher3/views/OptionsPopupView.java6
-rw-r--r--src/com/android/launcher3/views/RecyclerViewFastScroller.java3
-rw-r--r--src/com/android/launcher3/widget/BaseWidgetSheet.java13
-rw-r--r--src/com/android/launcher3/widget/WidgetsBottomSheet.java4
-rw-r--r--src/com/android/launcher3/widget/WidgetsFullSheet.java16
-rw-r--r--tests/Android.mk14
-rw-r--r--tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java19
-rw-r--r--tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java21
-rw-r--r--tests/tapl/com/android/launcher3/tapl/AllApps.java (renamed from tests/tapl/com/android/launcher3/tapl/AllAppsFromHome.java)69
-rw-r--r--tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java24
-rw-r--r--tests/tapl/com/android/launcher3/tapl/AppIcon.java16
-rw-r--r--tests/tapl/com/android/launcher3/tapl/Background.java32
-rw-r--r--tests/tapl/com/android/launcher3/tapl/Home.java152
-rw-r--r--tests/tapl/com/android/launcher3/tapl/Launcher.java303
-rw-r--r--tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java340
-rw-r--r--tests/tapl/com/android/launcher3/tapl/Overview.java43
-rw-r--r--tests/tapl/com/android/launcher3/tapl/OverviewTask.java31
-rw-r--r--tests/tapl/com/android/launcher3/tapl/Widgets.java33
-rw-r--r--tests/tapl/com/android/launcher3/tapl/Workspace.java155
93 files changed, 2134 insertions, 1051 deletions
diff --git a/Android.mk b/Android.mk
index 394574655..2a3285748 100644
--- a/Android.mk
+++ b/Android.mk
@@ -193,7 +193,8 @@ LOCAL_RESOURCE_DIR := \
$(LOCAL_PATH)/quickstep/res \
$(LOCAL_PATH)/go/res
-LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+LOCAL_PROGUARD_ENABLED := full
LOCAL_SDK_VERSION := system_current
LOCAL_MIN_SDK_VERSION := 26
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index 27de1e907..9d91d7ecc 100644
--- a/quickstep/libs/sysui_shared.jar
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 14633afa5..37d0b12bd 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -537,6 +537,9 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
@Override
public void onAnimationEnd(Animator animation) {
// Reset launcher to normal state
+ if (isBubbleTextView) {
+ ((BubbleTextView) v).setStayPressed(false);
+ }
v.setVisibility(View.VISIBLE);
((ViewGroup) mDragLayer.getParent()).removeView(mFloatingView);
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java b/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
index 722f51ba7..693ae60f2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
@@ -35,7 +35,7 @@ public class BackButtonAlphaHandler implements LauncherStateManager.StateHandler
public BackButtonAlphaHandler(Launcher launcher) {
mLauncher = launcher;
- mOverviewInteractionState = OverviewInteractionState.getInstance(mLauncher);
+ mOverviewInteractionState = OverviewInteractionState.INSTANCE.get(mLauncher);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
index 6d1061990..fd4bf9b8f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
@@ -73,7 +73,7 @@ public class LandscapeEdgeSwipeController extends AbstractStateChangeTouchContro
protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
super.onSwipeInteractionCompleted(targetState, logAction);
if (mStartState == NORMAL && targetState == OVERVIEW) {
- RecentsModel.getInstance(mLauncher).onOverviewShown(true, TAG);
+ RecentsModel.INSTANCE.get(mLauncher).onOverviewShown(true, TAG);
}
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index 7f956f8a2..25b5f5711 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -31,6 +31,7 @@ import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
/**
* Definition for overview state
@@ -77,7 +78,7 @@ public class OverviewState extends LauncherState {
public void onStateDisabled(Launcher launcher) {
RecentsView rv = launcher.getOverviewPanel();
rv.setOverviewStateEnabled(false);
- RecentsModel.getInstance(launcher).resetAssistCache();
+ RecentsModel.INSTANCE.get(launcher).resetAssistCache();
}
@Override
@@ -130,4 +131,14 @@ public class OverviewState extends LauncherState {
DeviceProfile dp = launcher.getDeviceProfile();
return dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
}
+
+ @Override
+ public void onBackPressed(Launcher launcher) {
+ TaskView taskView = launcher.<RecentsView>getOverviewPanel().getRunningTaskView();
+ if (taskView != null) {
+ taskView.launchTask(true);
+ } else {
+ super.onBackPressed(launcher);
+ }
+ }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
index 1d1b7dac9..a1ae99eae 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
@@ -256,7 +256,7 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
super.onSwipeInteractionCompleted(targetState, logAction);
if (mStartState == NORMAL && targetState == OVERVIEW) {
- RecentsModel.getInstance(mLauncher).onOverviewShown(true, TAG);
+ RecentsModel.INSTANCE.get(mLauncher).onOverviewShown(true, TAG);
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
index 9a920c843..88f231575 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
@@ -121,7 +121,7 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
if (mRecentsView.isTaskViewVisible(view) && mActivity.getDragLayer()
.isEventOverView(view, ev)) {
mTaskBeingDragged = view;
- if (!OverviewInteractionState.getInstance(mActivity)
+ if (!OverviewInteractionState.INSTANCE.get(mActivity)
.isSwipeUpGestureEnabled()) {
// Don't allow swipe down to open if we don't support swipe up
// to enter overview.
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index 2d0946b89..c9393306f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -57,7 +57,7 @@ import java.util.zip.Deflater;
public class UiFactory {
public static TouchController[] createTouchControllers(Launcher launcher) {
- boolean swipeUpEnabled = OverviewInteractionState.getInstance(launcher)
+ boolean swipeUpEnabled = OverviewInteractionState.INSTANCE.get(launcher)
.isSwipeUpGestureEnabled();
if (!swipeUpEnabled) {
return new TouchController[] {
@@ -80,7 +80,7 @@ public class UiFactory {
}
public static void setOnTouchControllersChangedListener(Context context, Runnable listener) {
- OverviewInteractionState.getInstance(context).setOnSwipeUpSettingChangedListener(listener);
+ OverviewInteractionState.INSTANCE.get(context).setOnSwipeUpSettingChangedListener(listener);
}
public static StateHandler[] getStateHandler(Launcher launcher) {
@@ -100,7 +100,7 @@ public class UiFactory {
shouldBackButtonBeHidden = AbstractFloatingView.getTopOpenViewWithType(launcher,
TYPE_ALL & ~TYPE_HIDE_BACK_BUTTON) == null;
}
- OverviewInteractionState.getInstance(launcher)
+ OverviewInteractionState.INSTANCE.get(launcher)
.setBackButtonAlpha(shouldBackButtonBeHidden ? 0 : 1, true /* animate */);
}
@@ -122,7 +122,7 @@ public class UiFactory {
@Override
public void onStateTransitionComplete(LauncherState finalState) {
- boolean swipeUpEnabled = OverviewInteractionState.getInstance(launcher)
+ boolean swipeUpEnabled = OverviewInteractionState.INSTANCE.get(launcher)
.isSwipeUpGestureEnabled();
LauncherState prevState = launcher.getStateManager().getLastState();
@@ -159,7 +159,7 @@ public class UiFactory {
}
public static void onStart(Context context) {
- RecentsModel model = RecentsModel.getInstance(context);
+ RecentsModel model = RecentsModel.INSTANCE.get(context);
if (model != null) {
model.onStart();
}
@@ -169,17 +169,19 @@ public class UiFactory {
// After the transition to home, enable the high-res thumbnail loader if it wasn't enabled
// as a part of quickstep/scrub, so that high-res thumbnails can load the next time we
// enter overview
- RecentsModel.getInstance(context).getRecentsTaskLoader()
+ RecentsModel.INSTANCE.get(context).getRecentsTaskLoader()
.getHighResThumbnailLoader().setVisible(true);
}
public static void onLauncherStateOrResumeChanged(Launcher launcher) {
LauncherState state = launcher.getStateManager().getState();
- DeviceProfile profile = launcher.getDeviceProfile();
- WindowManagerWrapper.getInstance().setShelfHeight(
- (state == NORMAL || state == OVERVIEW) && launcher.isUserActive()
- && !profile.isVerticalBarLayout(),
- profile.hotseatBarSizePx);
+ if (!OverviewInteractionState.INSTANCE.get(launcher).swipeGestureInitializing()) {
+ DeviceProfile profile = launcher.getDeviceProfile();
+ WindowManagerWrapper.getInstance().setShelfHeight(
+ (state == NORMAL || state == OVERVIEW) && launcher.isUserActive()
+ && !profile.isVerticalBarLayout(),
+ profile.hotseatBarSizePx);
+ }
if (state == NORMAL) {
launcher.<RecentsView>getOverviewPanel().setSwipeDownShouldLaunchApp(false);
@@ -187,7 +189,7 @@ public class UiFactory {
}
public static void onTrimMemory(Context context, int level) {
- RecentsModel model = RecentsModel.getInstance(context);
+ RecentsModel model = RecentsModel.INSTANCE.get(context);
if (model != null) {
model.onTrimMemory(level);
}
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index 3c69973aa..bb5bf6e1c 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -57,6 +57,7 @@ import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.uioverrides.FastOverviewState;
import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -230,6 +231,8 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
// Optimization, hide the all apps view to prevent layout while initializing
activity.getAppsView().getContentView().setVisibility(View.GONE);
+
+ AccessibilityManagerCompat.sendEventToTest(activity, "TAPL_WENT_TO_STATE");
}
return new AnimationFactory() {
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 7c6eb3259..3d54b82d1 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -111,7 +111,7 @@ public class OverviewCommandHelper {
mContext = context;
mAM = ActivityManagerWrapper.getInstance();
mMainThreadExecutor = new MainThreadExecutor();
- mRecentsModel = RecentsModel.getInstance(mContext);
+ mRecentsModel = RecentsModel.INSTANCE.get(mContext);
Intent myHomeIntent = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME)
diff --git a/quickstep/src/com/android/quickstep/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
index 922a7ff29..a5ff68113 100644
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
@@ -22,24 +22,20 @@ import static com.android.systemui.shared.system.SettingsCompat.SWIPE_UP_SETTING
import android.content.ContentResolver;
import android.content.Context;
-import android.content.res.Resources;
import android.database.ContentObserver;
import android.os.Handler;
-import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.provider.Settings;
import android.support.annotation.WorkerThread;
import android.util.Log;
-import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.DiscoveryBounce;
+import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.UiThreadHelper;
import com.android.systemui.shared.recents.ISystemUiProxy;
-import java.util.concurrent.ExecutionException;
-
/**
* Sets overview interaction flags, such as:
*
@@ -54,29 +50,10 @@ public class OverviewInteractionState {
private static final String TAG = "OverviewFlags";
private static final String HAS_ENABLED_QUICKSTEP_ONCE = "launcher.has_enabled_quickstep_once";
- private static final String SWIPE_UP_SETTING_AVAILABLE_RES_NAME =
- "config_swipe_up_gesture_setting_available";
- private static final String SWIPE_UP_ENABLED_DEFAULT_RES_NAME =
- "config_swipe_up_gesture_default";
// We do not need any synchronization for this variable as its only written on UI thread.
- private static OverviewInteractionState INSTANCE;
-
- public static OverviewInteractionState getInstance(final Context context) {
- if (INSTANCE == null) {
- if (Looper.myLooper() == Looper.getMainLooper()) {
- INSTANCE = new OverviewInteractionState(context.getApplicationContext());
- } else {
- try {
- return new MainThreadExecutor().submit(
- () -> OverviewInteractionState.getInstance(context)).get();
- } catch (InterruptedException|ExecutionException e) {
- throw new RuntimeException(e);
- }
- }
- }
- return INSTANCE;
- }
+ public static final MainThreadInitializedObject<OverviewInteractionState> INSTANCE =
+ new MainThreadInitializedObject<>((c) -> new OverviewInteractionState(c));
private static final int MSG_SET_PROXY = 200;
private static final int MSG_SET_BACK_BUTTON_ALPHA = 201;
@@ -88,6 +65,8 @@ public class OverviewInteractionState {
private final Handler mUiHandler;
private final Handler mBgHandler;
+ private boolean mSwipeGestureInitializing = false;
+
// These are updated on the background thread
private ISystemUiProxy mISystemUiProxy;
private boolean mSwipeUpEnabled = true;
@@ -104,13 +83,13 @@ public class OverviewInteractionState {
mUiHandler = new Handler(this::handleUiMessage);
mBgHandler = new Handler(UiThreadHelper.getBackgroundLooper(), this::handleBgMessage);
- if (getSystemBooleanRes(SWIPE_UP_SETTING_AVAILABLE_RES_NAME)) {
+ if (SwipeUpSetting.isSwipeUpSettingAvailable()) {
mSwipeUpSettingObserver = new SwipeUpGestureEnabledSettingObserver(mUiHandler,
context.getContentResolver());
mSwipeUpSettingObserver.register();
} else {
mSwipeUpSettingObserver = null;
- mSwipeUpEnabled = getSystemBooleanRes(SWIPE_UP_ENABLED_DEFAULT_RES_NAME);
+ mSwipeUpEnabled = SwipeUpSetting.isSwipeUpEnabledDefaultValue();
}
}
@@ -197,6 +176,15 @@ public class OverviewInteractionState {
}
}
+ @WorkerThread
+ public void setSwipeGestureInitializing(boolean swipeGestureInitializing) {
+ mSwipeGestureInitializing = swipeGestureInitializing;
+ }
+
+ public boolean swipeGestureInitializing() {
+ return mSwipeGestureInitializing;
+ }
+
private class SwipeUpGestureEnabledSettingObserver extends ContentObserver {
private Handler mHandler;
private ContentResolver mResolver;
@@ -206,7 +194,7 @@ public class OverviewInteractionState {
super(handler);
mHandler = handler;
mResolver = resolver;
- defaultValue = getSystemBooleanRes(SWIPE_UP_ENABLED_DEFAULT_RES_NAME) ? 1 : 0;
+ defaultValue = SwipeUpSetting.isSwipeUpEnabledDefaultValue() ? 1 : 0;
}
public void register() {
@@ -228,18 +216,6 @@ public class OverviewInteractionState {
}
}
- private boolean getSystemBooleanRes(String resName) {
- Resources res = Resources.getSystem();
- int resId = res.getIdentifier(resName, "bool", "android");
-
- if (resId != 0) {
- return res.getBoolean(resId);
- } else {
- Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
- return false;
- }
- }
-
private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
if (mSwipeUpEnabled && !Utilities.getPrefs(mContext).getBoolean(
HAS_ENABLED_QUICKSTEP_ONCE, true)) {
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 32079bf13..b93a54b89 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -232,6 +232,12 @@ public class RecentsActivity extends BaseDraggingActivity {
}
@Override
+ public void onEnterAnimationComplete() {
+ super.onEnterAnimationComplete();
+ UiFactory.onEnterAnimationComplete(this);
+ }
+
+ @Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
UiFactory.onTrimMemory(this, level);
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 0b97f0119..fa4e016db 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -27,7 +27,6 @@ import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.support.annotation.WorkerThread;
@@ -38,6 +37,7 @@ import android.view.accessibility.AccessibilityManager;
import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.R;
+import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Preconditions;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.model.IconLoader;
@@ -50,7 +50,6 @@ import com.android.systemui.shared.system.BackgroundExecutor;
import com.android.systemui.shared.system.TaskStackChangeListener;
import java.util.ArrayList;
-import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
/**
@@ -59,23 +58,8 @@ import java.util.function.Consumer;
@TargetApi(Build.VERSION_CODES.O)
public class RecentsModel extends TaskStackChangeListener {
// We do not need any synchronization for this variable as its only written on UI thread.
- private static RecentsModel INSTANCE;
-
- public static RecentsModel getInstance(final Context context) {
- if (INSTANCE == null) {
- if (Looper.myLooper() == Looper.getMainLooper()) {
- INSTANCE = new RecentsModel(context.getApplicationContext());
- } else {
- try {
- return new MainThreadExecutor().submit(
- () -> RecentsModel.getInstance(context)).get();
- } catch (InterruptedException|ExecutionException e) {
- throw new RuntimeException(e);
- }
- }
- }
- return INSTANCE;
- }
+ public static final MainThreadInitializedObject<RecentsModel> INSTANCE =
+ new MainThreadInitializedObject<>(c -> new RecentsModel(c));
private final SparseArray<Bundle> mCachedAssistData = new SparseArray<>(1);
private final ArrayList<AssistDataListener> mAssistDataListeners = new ArrayList<>();
diff --git a/quickstep/src/com/android/quickstep/SwipeUpSetting.java b/quickstep/src/com/android/quickstep/SwipeUpSetting.java
new file mode 100644
index 000000000..0f91f9775
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/SwipeUpSetting.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep;
+
+import android.content.res.Resources;
+import android.util.Log;
+
+public final class SwipeUpSetting {
+ private static final String TAG = "SwipeUpSetting";
+
+ private static final String SWIPE_UP_SETTING_AVAILABLE_RES_NAME =
+ "config_swipe_up_gesture_setting_available";
+
+ private static final String SWIPE_UP_ENABLED_DEFAULT_RES_NAME =
+ "config_swipe_up_gesture_default";
+
+ private static boolean getSystemBooleanRes(String resName) {
+ Resources res = Resources.getSystem();
+ int resId = res.getIdentifier(resName, "bool", "android");
+
+ if (resId != 0) {
+ return res.getBoolean(resId);
+ } else {
+ Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
+ return false;
+ }
+ }
+
+ public static boolean isSwipeUpSettingAvailable() {
+ return getSystemBooleanRes(SWIPE_UP_SETTING_AVAILABLE_RES_NAME);
+ }
+
+ public static boolean isSwipeUpEnabledDefaultValue() {
+ return getSystemBooleanRes(SWIPE_UP_ENABLED_DEFAULT_RES_NAME);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
index 24e199b97..e64d04afd 100644
--- a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
+++ b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
@@ -160,7 +160,7 @@ public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut
boolean dockTopOrLeft = navBarPosition != WindowManagerWrapper.NAV_BAR_POS_LEFT;
if (ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId,
ActivityOptionsCompat.makeSplitScreenOptions(dockTopOrLeft))) {
- ISystemUiProxy sysUiProxy = RecentsModel.getInstance(activity).getSystemUiProxy();
+ ISystemUiProxy sysUiProxy = RecentsModel.INSTANCE.get(activity).getSystemUiProxy();
try {
sysUiProxy.onSplitScreenInvoked();
} catch (RemoteException e) {
@@ -225,7 +225,7 @@ public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut
@Override
public View.OnClickListener getOnClickListener(
BaseDraggingActivity activity, TaskView taskView) {
- ISystemUiProxy sysUiProxy = RecentsModel.getInstance(activity).getSystemUiProxy();
+ ISystemUiProxy sysUiProxy = RecentsModel.INSTANCE.get(activity).getSystemUiProxy();
if (sysUiProxy == null) {
return null;
}
diff --git a/quickstep/src/com/android/quickstep/TaskUtils.java b/quickstep/src/com/android/quickstep/TaskUtils.java
index f9b5e30e1..5cae2b919 100644
--- a/quickstep/src/com/android/quickstep/TaskUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskUtils.java
@@ -18,8 +18,6 @@ package com.android.quickstep;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
-import static com.android.systemui.shared.recents.utilities.Utilities.getNextFrameNumber;
-import static com.android.systemui.shared.recents.utilities.Utilities.getSurface;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
import android.animation.ValueAnimator;
@@ -30,7 +28,6 @@ import android.content.pm.PackageManager;
import android.graphics.RectF;
import android.os.UserHandle;
import android.util.Log;
-import android.view.Surface;
import android.view.View;
import com.android.launcher3.BaseDraggingActivity;
@@ -92,10 +89,11 @@ public class TaskUtils {
*/
public static TaskView findTaskViewToLaunch(
BaseDraggingActivity activity, View v, RemoteAnimationTargetCompat[] targets) {
+ RecentsView recentsView = activity.getOverviewPanel();
if (v instanceof TaskView) {
- return (TaskView) v;
+ TaskView taskView = (TaskView) v;
+ return recentsView.isTaskViewVisible(taskView) ? taskView : null;
}
- RecentsView recentsView = activity.getOverviewPanel();
// It's possible that the launched view can still be resolved to a visible task view, check
// the task id of the opening task and see if we can find a match.
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 5a1f52339..bd7930111 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -90,7 +90,14 @@ public class TouchInteractionService extends Service {
public void onMotionEvent(MotionEvent ev) {
mEventQueue.queue(ev);
- String name = sMotionEventNames.get(ev.getActionMasked());
+ int action = ev.getActionMasked();
+ if (action == ACTION_DOWN) {
+ mOverviewInteractionState.setSwipeGestureInitializing(true);
+ } else if (action == ACTION_UP || action == ACTION_CANCEL) {
+ mOverviewInteractionState.setSwipeGestureInitializing(false);
+ }
+
+ String name = sMotionEventNames.get(action);
if (name != null){
TraceHelper.partitionSection("SysUiBinder", name);
}
@@ -106,6 +113,7 @@ public class TouchInteractionService extends Service {
@Override
public void onQuickScrubStart() {
mEventQueue.onQuickScrubStart();
+ mOverviewInteractionState.setSwipeGestureInitializing(false);
TraceHelper.partitionSection("SysUiBinder", "onQuickScrubStart");
}
@@ -146,8 +154,8 @@ public class TouchInteractionService extends Service {
@Override
public void onQuickStep(MotionEvent motionEvent) {
mEventQueue.onQuickStep(motionEvent);
+ mOverviewInteractionState.setSwipeGestureInitializing(false);
TraceHelper.endSection("SysUiBinder", "onQuickStep");
-
}
@Override
@@ -179,13 +187,13 @@ public class TouchInteractionService extends Service {
public void onCreate() {
super.onCreate();
mAM = ActivityManagerWrapper.getInstance();
- mRecentsModel = RecentsModel.getInstance(this);
+ mRecentsModel = RecentsModel.INSTANCE.get(this);
mRecentsModel.setPreloadTasksInBackground(true);
mMainThreadExecutor = new MainThreadExecutor();
mOverviewCommandHelper = new OverviewCommandHelper(this);
mMainThreadChoreographer = Choreographer.getInstance();
mEventQueue = new MotionEventQueue(mMainThreadChoreographer, TouchConsumer.NO_OP);
- mOverviewInteractionState = OverviewInteractionState.getInstance(this);
+ mOverviewInteractionState = OverviewInteractionState.INSTANCE.get(this);
mOverviewCallbacks = OverviewCallbacks.get(this);
mTaskOverlayFactory = TaskOverlayFactory.get(this);
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 8e3449093..f8ef49b31 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -256,6 +256,11 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
}
};
+ // Re-setup the recents UI when gesture starts, as the state could have been changed during
+ // that time by a previous window transition.
+ mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_GESTURE_STARTED_QUICKSTEP,
+ this::setupRecentsViewUi);
+
mStateCallback.addCallback(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED_QUICKSCRUB,
this::initializeLauncherAnimationController);
mStateCallback.addCallback(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED_QUICKSTEP,
@@ -429,11 +434,15 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
});
}
+ setupRecentsViewUi();
+ mLayoutListener.open();
+ mStateCallback.setState(STATE_LAUNCHER_STARTED);
+ }
+
+ private void setupRecentsViewUi() {
mRecentsView.showTask(mRunningTaskId);
mRecentsView.setRunningTaskHidden(true);
mRecentsView.setRunningTaskIconScaledDown(true);
- mLayoutListener.open();
- mStateCallback.setState(STATE_LAUNCHER_STARTED);
}
public void setLauncherOnDrawCallback(Runnable callback) {
@@ -481,7 +490,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
// This method is only called when STATE_GESTURE_STARTED_QUICKSTEP/
// STATE_GESTURE_STARTED_QUICKSCRUB is set, so we can enable the high-res thumbnail loader
// here once we are sure that we will end up in an overview state
- RecentsModel.getInstance(mContext).getRecentsTaskLoader()
+ RecentsModel.INSTANCE.get(mContext).getRecentsTaskLoader()
.getHighResThumbnailLoader().setVisible(true);
}
@@ -632,10 +641,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
overviewStackBounds = new Rect(0, 0, dp.widthPx, dp.heightPx);
}
// If we are not in multi-window mode, home insets should be same as system insets.
- Rect insets = new Rect();
- WindowManagerWrapper.getInstance().getStableInsets(insets);
dp = dp.copy(mContext);
- dp.updateInsets(insets);
+ dp.updateInsets(homeContentInsets);
}
dp.updateIsSeascape(mContext.getSystemService(WindowManager.class));
@@ -940,7 +947,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
mRecentsView.animateUpRunningTaskIconScale();
mRecentsView.setSwipeDownShouldLaunchApp(true);
- RecentsModel.getInstance(mContext).onOverviewShown(false, TAG);
+ RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG);
doLogGesture(true /* toLauncher */);
reset();
@@ -970,7 +977,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
mQuickScrubController.onFinishedTransitionToQuickScrub();
mRecentsView.animateUpRunningTaskIconScale();
- RecentsModel.getInstance(mContext).onOverviewShown(false, TAG);
+ RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG);
}
public void onQuickScrubProgress(float progress) {
@@ -1104,7 +1111,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
}
private void preloadAssistData() {
- RecentsModel.getInstance(mContext).preloadAssistData(mRunningTaskId, mAssistData);
+ RecentsModel.INSTANCE.get(mContext).preloadAssistData(mRunningTaskId, mAssistData);
}
public static float getHiddenTargetAlpha(RemoteAnimationTargetCompat app, Float expectedAlpha) {
diff --git a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
index df70a8a39..0597a2ebd 100644
--- a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
+++ b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
@@ -30,7 +30,6 @@ import android.graphics.RectF;
import android.os.Build;
import android.os.RemoteException;
import android.support.annotation.Nullable;
-import android.view.Surface;
import android.view.animation.Interpolator;
import com.android.launcher3.BaseDraggingActivity;
@@ -50,7 +49,6 @@ import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplier.Surfac
import com.android.systemui.shared.system.TransactionCompat;
import com.android.systemui.shared.system.WindowManagerWrapper;
-import java.util.function.BiConsumer;
import java.util.function.BiFunction;
/**
@@ -260,7 +258,7 @@ public class ClipAnimationHelper {
}
private void updateStackBoundsToMultiWindowTaskSize(BaseDraggingActivity activity) {
- ISystemUiProxy sysUiProxy = RecentsModel.getInstance(activity).getSystemUiProxy();
+ ISystemUiProxy sysUiProxy = RecentsModel.INSTANCE.get(activity).getSystemUiProxy();
if (sysUiProxy != null) {
try {
mSourceStackBounds.set(sysUiProxy.getNonMinimizedSplitScreenSecondaryBounds());
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 7c5828b44..697bb4f1c 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -124,7 +124,7 @@ public class LauncherRecentsView extends RecentsView<Launcher> {
ClipAnimationHelper helper) {
AnimatorSet anim = super.createAdjacentPageAnimForTaskLaunch(tv, helper);
- if (!OverviewInteractionState.getInstance(mActivity).isSwipeUpGestureEnabled()) {
+ if (!OverviewInteractionState.INSTANCE.get(mActivity).isSwipeUpGestureEnabled()) {
// Hotseat doesn't move when opening recents with the button,
// so don't animate it here either.
return anim;
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 2680a2695..814d02d5a 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -277,7 +277,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
.getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
mActivity = (T) BaseActivity.fromContext(context);
mQuickScrubController = new QuickScrubController(mActivity, this);
- mModel = RecentsModel.getInstance(context);
+ mModel = RecentsModel.INSTANCE.get(context);
mClearAllButton = (ClearAllButton) LayoutInflater.from(context)
.inflate(R.layout.overview_clear_all_button, this, false);
@@ -415,7 +415,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
case MotionEvent.ACTION_DOWN:
// Touch down anywhere but the deadzone around the visible clear all button and
// between the task views will start home on touch up
- if (mTouchState == TOUCH_STATE_REST) {
+ if (!isHandlingTouch()) {
updateDeadZoneRects();
final boolean clearAllButtonDeadZoneConsumed = mClearAllButton.getAlpha() == 1
&& mClearAllButtonDeadZoneRect.contains(x, y);
@@ -560,7 +560,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
boolean scrolling = super.computeScrollHelper();
boolean isFlingingFast = false;
updateCurveProperties();
- if (scrolling || (mTouchState == TOUCH_STATE_SCROLLING)) {
+ if (scrolling || isHandlingTouch()) {
if (scrolling) {
// Check if we are flinging quickly to disable high res thumbnail loading
isFlingingFast = mScroller.getCurrVelocity() > mFastFlingVelocity;
@@ -714,12 +714,16 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
setCurrentTask(runningTaskId);
}
+ public TaskView getRunningTaskView() {
+ return getTaskView(mRunningTaskId);
+ }
+
/**
* Hides the tile associated with {@link #mRunningTaskId}
*/
public void setRunningTaskHidden(boolean isHidden) {
mRunningTaskTileHidden = isHidden;
- TaskView runningTask = getTaskView(mRunningTaskId);
+ TaskView runningTask = getRunningTaskView();
if (runningTask != null) {
runningTask.setAlpha(isHidden ? 0 : mContentAlpha);
}
@@ -745,7 +749,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
}
public void showNextTask() {
- TaskView runningTaskView = getTaskView(mRunningTaskId);
+ TaskView runningTaskView = getRunningTaskView();
if (runningTaskView == null) {
// Launch the first task
if (getTaskViewCount() > 0) {
@@ -773,7 +777,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
}
private void applyRunningTaskIconScale() {
- TaskView firstTask = getTaskView(mRunningTaskId);
+ TaskView firstTask = getRunningTaskView();
if (firstTask != null) {
firstTask.setIconScaleAndDim(mRunningTaskIconScaledDown ? 0 : 1);
}
@@ -781,7 +785,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
public void animateUpRunningTaskIconScale() {
mRunningTaskIconScaledDown = false;
- TaskView firstTask = getTaskView(mRunningTaskId);
+ TaskView firstTask = getRunningTaskView();
if (firstTask != null) {
firstTask.animateIconScaleAndDimIntoView();
}
@@ -990,10 +994,8 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
mPendingAnimation = pendingAnimation;
mPendingAnimation.addEndListener((onEndListener) -> {
if (onEndListener.isSuccess) {
- int taskViewCount = getTaskViewCount();
- for (int i = 0; i < taskViewCount; i++) {
- removeTask(getTaskViewAt(i).getTask(), -1, onEndListener, false);
- }
+ // Remove all the task views now
+ ActivityManagerWrapper.getInstance().removeAllRecentTasks();
removeAllViews();
onAllTasksRemoved();
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index e609a404f..41626c6be 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -87,6 +87,7 @@ public class TaskMenuView extends AbstractFloatingView {
private static final int REVEAL_OPEN_DURATION = 150;
private static final int REVEAL_CLOSE_DURATION = 100;
+ private final float mThumbnailTopMargin;
private BaseDraggingActivity mActivity;
private TextView mTaskName;
private IconView mTaskIcon;
@@ -103,6 +104,7 @@ public class TaskMenuView extends AbstractFloatingView {
super(context, attrs, defStyleAttr);
mActivity = BaseDraggingActivity.fromContext(context);
+ mThumbnailTopMargin = getResources().getDimension(R.dimen.task_thumbnail_top_margin);
}
@Override
@@ -154,11 +156,16 @@ public class TaskMenuView extends AbstractFloatingView {
return (type & TYPE_TASK_MENU) != 0;
}
- public static boolean showForTask(TaskView taskView) {
+ public void setPosition(float x, float y) {
+ setX(x);
+ setY(y + mThumbnailTopMargin);
+ }
+
+ public static TaskMenuView showForTask(TaskView taskView) {
BaseDraggingActivity activity = BaseDraggingActivity.fromContext(taskView.getContext());
final TaskMenuView taskMenuView = (TaskMenuView) activity.getLayoutInflater().inflate(
R.layout.task_menu, activity.getDragLayer(), false);
- return taskMenuView.populateAndShowForTask(taskView);
+ return taskMenuView.populateAndShowForTask(taskView) ? taskMenuView : null;
}
private boolean populateAndShowForTask(TaskView taskView) {
@@ -188,7 +195,7 @@ public class TaskMenuView extends AbstractFloatingView {
// Move the icon and text up half an icon size to lay over the TaskView
LinearLayout.LayoutParams params =
(LinearLayout.LayoutParams) mTaskIcon.getLayoutParams();
- params.topMargin = (int) -getResources().getDimension(R.dimen.task_thumbnail_top_margin);
+ params.topMargin = (int) -mThumbnailTopMargin;
mTaskIcon.setLayoutParams(params);
for (TaskSystemShortcut menuOption : MENU_OPTIONS) {
@@ -213,12 +220,12 @@ public class TaskMenuView extends AbstractFloatingView {
mActivity.getDragLayer().getDescendantRectRelativeToSelf(taskView, sTempRect);
Rect insets = mActivity.getDragLayer().getInsets();
BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) getLayoutParams();
- params.width = sTempRect.width();
- params.gravity = Gravity.LEFT;
+ params.width = taskView.getMeasuredWidth();
+ params.gravity = Gravity.START;
setLayoutParams(params);
- setX(Math.round(sTempRect.left - insets.left));
- setY(Math.round(sTempRect.top - insets.top
- + getResources().getDimension(R.dimen.task_thumbnail_top_margin)));
+ setScaleX(taskView.getScaleX());
+ setScaleY(taskView.getScaleY());
+ setPosition(sTempRect.left - insets.left, sTempRect.top - insets.top);
}
private void animateOpen() {
@@ -232,7 +239,7 @@ public class TaskMenuView extends AbstractFloatingView {
private void animateOpenOrClosed(boolean closing) {
if (mOpenCloseAnimator != null && mOpenCloseAnimator.isRunning()) {
- return;
+ mOpenCloseAnimator.end();
}
mOpenCloseAnimator = LauncherAnimUtils.createAnimatorSet();
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 165303880..ee542d57e 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -110,8 +110,24 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback
}
};
+ private final OnAttachStateChangeListener mTaskMenuStateListener =
+ new OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View view) {
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View view) {
+ if (mMenuView != null) {
+ mMenuView.removeOnAttachStateChangeListener(this);
+ mMenuView = null;
+ }
+ }
+ };
+
private Task mTask;
private TaskThumbnailView mSnapshotView;
+ private TaskMenuView mMenuView;
private IconView mIconView;
private float mCurveScale;
private float mZoomScale;
@@ -200,13 +216,22 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback
public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData) {
mSnapshotView.setThumbnail(task, thumbnailData);
mIconView.setDrawable(task.icon);
- mIconView.setOnClickListener(icon -> TaskMenuView.showForTask(this));
+ mIconView.setOnClickListener(icon -> showTaskMenu());
mIconView.setOnLongClickListener(icon -> {
requestDisallowInterceptTouchEvent(true);
- return TaskMenuView.showForTask(this);
+ return showTaskMenu();
});
}
+ private boolean showTaskMenu() {
+ getRecentsView().snapToPage(getRecentsView().indexOfChild(this));
+ mMenuView = TaskMenuView.showForTask(this);
+ if (mMenuView != null) {
+ mMenuView.addOnAttachStateChangeListener(mTaskMenuStateListener);
+ }
+ return mMenuView != null;
+ }
+
@Override
public void onTaskDataUnloaded() {
mSnapshotView.setThumbnail(null, null);
@@ -266,6 +291,12 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback
mSnapshotView.setDimAlpha(curveInterpolation * MAX_PAGE_SCRIM_ALPHA);
setCurveScale(getCurveScaleForCurveInterpolation(curveInterpolation));
+
+ if (mMenuView != null) {
+ mMenuView.setPosition(getX() - getRecentsView().getScrollX(), getY());
+ mMenuView.setScaleX(getScaleX());
+ mMenuView.setScaleY(getScaleY());
+ }
}
@Override
diff --git a/res/drawable-hdpi/ic_allapps.png b/res/drawable-hdpi/ic_allapps.png
deleted file mode 100644
index 253755ffe..000000000
--- a/res/drawable-hdpi/ic_allapps.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_allapps_pressed.png b/res/drawable-hdpi/ic_allapps_pressed.png
deleted file mode 100644
index 1e644c5d0..000000000
--- a/res/drawable-hdpi/ic_allapps_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_allapps.png b/res/drawable-mdpi/ic_allapps.png
deleted file mode 100644
index 6936b2079..000000000
--- a/res/drawable-mdpi/ic_allapps.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_allapps_pressed.png b/res/drawable-mdpi/ic_allapps_pressed.png
deleted file mode 100644
index 850ded6fe..000000000
--- a/res/drawable-mdpi/ic_allapps_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_allapps.png b/res/drawable-xhdpi/ic_allapps.png
deleted file mode 100644
index c11c1038b..000000000
--- a/res/drawable-xhdpi/ic_allapps.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_allapps_pressed.png b/res/drawable-xhdpi/ic_allapps_pressed.png
deleted file mode 100644
index f319bf1e6..000000000
--- a/res/drawable-xhdpi/ic_allapps_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_allapps.png b/res/drawable-xxhdpi/ic_allapps.png
deleted file mode 100644
index cf6a2cbc0..000000000
--- a/res/drawable-xxhdpi/ic_allapps.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_allapps_pressed.png b/res/drawable-xxhdpi/ic_allapps_pressed.png
deleted file mode 100644
index 379389ac7..000000000
--- a/res/drawable-xxhdpi/ic_allapps_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/all_apps_button_icon.xml b/res/drawable/all_apps_button_icon.xml
deleted file mode 100644
index 7c69cad3a..000000000
--- a/res/drawable/all_apps_button_icon.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_focused="true" android:drawable="@drawable/ic_allapps_pressed" />
- <item android:state_pressed="true" android:drawable="@drawable/ic_allapps_pressed" />
- <item android:drawable="@drawable/ic_allapps" />
-</selector>
diff --git a/res/layout/all_apps_button.xml b/res/layout/all_apps_button.xml
deleted file mode 100644
index 4bc780a70..000000000
--- a/res/layout/all_apps_button.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<TextView style="@style/BaseIcon" />
diff --git a/res/values-sw720dp/dimens.xml b/res/values-sw720dp/dimens.xml
index b21120758..691219af6 100644
--- a/res/values-sw720dp/dimens.xml
+++ b/res/values-sw720dp/dimens.xml
@@ -16,7 +16,6 @@
<resources>
<!-- All Apps -->
- <dimen name="all_apps_button_scale_down">8dp</dimen>
<dimen name="all_apps_empty_search_message_top_offset">64dp</dimen>
<dimen name="all_apps_empty_search_bg_top_offset">180dp</dimen>
diff --git a/res/values/config.xml b/res/values/config.xml
index f462b9c53..8d31bd271 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -148,6 +148,4 @@
<item type="id" name="overview_panel"/>
<integer name="config_recentsMaxThumbnailCacheSize">6</integer>
<integer name="config_recentsMaxIconCacheSize">12</integer>
-
- <item name="workspace_page_container" type="id" />
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 3bb7a797a..58fce3482 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -76,7 +76,6 @@
<dimen name="fastscroll_end_margin">-26dp</dimen>
<!-- All Apps -->
- <dimen name="all_apps_button_scale_down">0dp</dimen>
<dimen name="all_apps_search_bar_field_height">48dp</dimen>
<dimen name="all_apps_search_bar_bottom_padding">30dp</dimen>
<dimen name="all_apps_empty_search_message_top_offset">40dp</dimen>
diff --git a/res/xml/device_profiles.xml b/res/xml/device_profiles.xml
index a34f22515..ef6e14506 100644
--- a/res/xml/device_profiles.xml
+++ b/res/xml/device_profiles.xml
@@ -133,13 +133,13 @@
launcher:name="Nexus 7"
launcher:minWidthDps="575"
launcher:minHeightDps="904"
- launcher:numRows="5"
+ launcher:numRows="6"
launcher:numColumns="6"
launcher:numFolderRows="4"
launcher:numFolderColumns="5"
launcher:iconSize="64"
launcher:iconTextSize="14.4"
- launcher:numHotseatIcons="7"
+ launcher:numHotseatIcons="6"
launcher:defaultLayoutId="@xml/default_workspace_5x6"
/>
@@ -147,8 +147,8 @@
launcher:name="Nexus 10"
launcher:minWidthDps="727"
launcher:minHeightDps="1207"
- launcher:numRows="5"
- launcher:numColumns="6"
+ launcher:numRows="6"
+ launcher:numColumns="7"
launcher:numFolderRows="4"
launcher:numFolderColumns="5"
launcher:iconSize="76"
diff --git a/res/xml/dw_phone_hotseat.xml b/res/xml/dw_phone_hotseat.xml
index b58994d1d..c691ebc3d 100644
--- a/res/xml/dw_phone_hotseat.xml
+++ b/res/xml/dw_phone_hotseat.xml
@@ -16,7 +16,7 @@
<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
<!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
- <!-- Dialer, Messaging, [All Apps], Browser, Camera -->
+ <!-- Dialer, Messaging, [Maps/Music], Browser, Camera -->
<resolve
launcher:container="-101"
launcher:screen="0"
@@ -39,7 +39,14 @@
<favorite launcher:uri="mmsto:" />
</resolve>
- <!-- All Apps -->
+ <resolve
+ launcher:container="-101"
+ launcher:screen="2"
+ launcher:x="2"
+ launcher:y="0" >
+ <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MAPS;end" />
+ <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MUSIC;end" />
+ </resolve>
<resolve
launcher:container="-101"
diff --git a/res/xml/dw_tablet_hotseat.xml b/res/xml/dw_tablet_hotseat.xml
index 671ccba3c..6fe7f93d8 100644
--- a/res/xml/dw_tablet_hotseat.xml
+++ b/res/xml/dw_tablet_hotseat.xml
@@ -16,7 +16,7 @@
<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
<!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
- <!-- Messaging, Email, Browser, [All Apps], Music, Gallery, Camera -->
+ <!-- Messaging, Email, Browser, Maps, Music, Gallery, Camera -->
<resolve
launcher:container="-101"
launcher:screen="0"
@@ -48,7 +48,13 @@
<favorite launcher:uri="http://www.example.com/" />
</resolve>
- <!-- All Apps -->
+ <resolve
+ launcher:container="-101"
+ launcher:screen="3"
+ launcher:x="3"
+ launcher:y="0" >
+ <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MAPS;end" />
+ </resolve>
<favorite
launcher:container="-101"
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 322383778..e58e73d28 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -69,9 +69,9 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch
public static final int TYPE_DISCOVERY_BOUNCE = 1 << 6;
// Popups related to quickstep UI
- public static final int TYPE_QUICKSTEP_PREVIEW = 1 << 6;
- public static final int TYPE_TASK_MENU = 1 << 7;
- public static final int TYPE_OPTIONS_POPUP = 1 << 8;
+ public static final int TYPE_QUICKSTEP_PREVIEW = 1 << 7;
+ public static final int TYPE_TASK_MENU = 1 << 8;
+ public static final int TYPE_OPTIONS_POPUP = 1 << 9;
public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
| TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 469b8bbce..6b0a90a78 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -28,7 +28,6 @@ import android.content.res.XmlResourceParser;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.drawable.Drawable;
import android.net.Uri;
-import android.os.Build;
import android.os.Build.VERSION;
import android.os.Bundle;
import android.os.Process;
@@ -37,16 +36,18 @@ import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
import android.util.Patterns;
+
import com.android.launcher3.LauncherProvider.SqlArguments;
import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.LauncherIcons;
import com.android.launcher3.util.Thunk;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.IOException;
import java.util.ArrayList;
import java.util.Locale;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
/**
* Layout parsing code for auto installs layout
@@ -230,9 +231,7 @@ public class AutoInstallsLayout {
if (HOTSEAT_CONTAINER_NAME.equals(getAttributeValue(parser, ATTR_CONTAINER))) {
out[0] = Favorites.CONTAINER_HOTSEAT;
// Hack: hotseat items are stored using screen ids
- long rank = Long.parseLong(getAttributeValue(parser, ATTR_RANK));
- out[1] = (FeatureFlags.NO_ALL_APPS_ICON || rank < mIdp.getAllAppsButtonRank())
- ? rank : (rank + 1);
+ out[1] = Long.parseLong(getAttributeValue(parser, ATTR_RANK));
} else {
out[0] = Favorites.CONTAINER_DESKTOP;
out[1] = Long.parseLong(getAttributeValue(parser, ATTR_SCREEN));
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 820c125e1..9839c12dc 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -59,6 +59,9 @@ public class DeviceProfile {
private static final float TALL_DEVICE_ASPECT_RATIO_THRESHOLD = 2.0f;
+ // To evenly space the icons, increase the left/right margins for tablets in portrait mode.
+ private static final int PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER = 4;
+
// Workspace
public final int desiredWorkspaceLeftRightMarginPx;
public final int cellLayoutPaddingLeftRightPx;
@@ -172,7 +175,9 @@ public class DeviceProfile {
defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
desiredWorkspaceLeftRightMarginPx = isVerticalBarLayout() ? 0 : edgeMarginPx;
- cellLayoutPaddingLeftRightPx =
+ int cellLayoutPaddingLeftRightMultiplier = !isVerticalBarLayout() && isTablet
+ ? PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER : 1;
+ cellLayoutPaddingLeftRightPx = cellLayoutPaddingLeftRightMultiplier *
res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_padding);
cellLayoutBottomPaddingPx =
res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_bottom_padding);
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 6668f2cbe..15a9f2e06 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -16,26 +16,18 @@
package com.android.launcher3;
-import static com.android.launcher3.LauncherState.ALL_APPS;
-
import android.content.Context;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
-import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.widget.FrameLayout;
-import android.widget.TextView;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
public class Hotseat extends FrameLayout implements LogContainerProvider, Insettable {
@@ -92,45 +84,6 @@ public class Hotseat extends FrameLayout implements LogContainerProvider, Insett
} else {
mContent.setGridSize(idp.numHotseatIcons, 1);
}
-
- if (!FeatureFlags.NO_ALL_APPS_ICON) {
- // Add the Apps button
- Context context = getContext();
- DeviceProfile grid = mLauncher.getDeviceProfile();
- int allAppsButtonRank = grid.inv.getAllAppsButtonRank();
-
- LayoutInflater inflater = LayoutInflater.from(context);
- TextView allAppsButton = (TextView)
- inflater.inflate(R.layout.all_apps_button, mContent, false);
- Drawable d = context.getResources().getDrawable(R.drawable.all_apps_button_icon);
- d.setBounds(0, 0, grid.iconSizePx, grid.iconSizePx);
-
- int scaleDownPx = getResources().getDimensionPixelSize(R.dimen.all_apps_button_scale_down);
- Rect bounds = d.getBounds();
- d.setBounds(bounds.left, bounds.top + scaleDownPx / 2, bounds.right - scaleDownPx,
- bounds.bottom - scaleDownPx / 2);
- allAppsButton.setCompoundDrawables(null, d, null, null);
-
- allAppsButton.setContentDescription(context.getString(R.string.all_apps_button_label));
- if (mLauncher != null) {
- allAppsButton.setOnClickListener((v) -> {
- if (!mLauncher.isInState(ALL_APPS)) {
- mLauncher.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
- ControlType.ALL_APPS_BUTTON);
- mLauncher.getStateManager().goToState(ALL_APPS);
- }
- });
- allAppsButton.setOnFocusChangeListener(mLauncher.mFocusHandler);
- }
-
- // Note: We do this to ensure that the hotseat is always laid out in the orientation of
- // the hotseat in order regardless of which orientation they were added
- int x = getCellXFromOrder(allAppsButtonRank);
- int y = getCellYFromOrder(allAppsButtonRank);
- CellLayout.LayoutParams lp = new CellLayout.LayoutParams(x, y, 1, 1);
- lp.canReorder = false;
- mContent.addViewToCellLayout(allAppsButton, -1, allAppsButton.getId(), lp, true);
- }
}
@Override
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 22bc162b6..70845541b 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -28,7 +28,6 @@ import android.util.Xml;
import android.view.Display;
import android.view.WindowManager;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.ConfigMonitor;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Thunk;
@@ -314,17 +313,6 @@ public class InvariantDeviceProfile {
return this;
}
- public int getAllAppsButtonRank() {
- if (FeatureFlags.IS_DOGFOOD_BUILD && FeatureFlags.NO_ALL_APPS_ICON) {
- throw new IllegalAccessError("Accessing all apps rank when all-apps is disabled");
- }
- return numHotseatIcons / 2;
- }
-
- public boolean isAllAppsButtonRank(int rank) {
- return rank == getAllAppsButtonRank();
- }
-
public DeviceProfile getDeviceProfile(Context context) {
return context.getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE ? landscapeProfile : portraitProfile;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 534d6e3bc..a54512205 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -16,6 +16,7 @@
package com.android.launcher3;
+import static android.content.pm.ActivityInfo.CONFIG_LOCALE;
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
@@ -39,7 +40,6 @@ import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks2;
import android.content.Context;
-import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
@@ -85,6 +85,7 @@ import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.folder.FolderIconPreviewVerifier;
import com.android.launcher3.keyboard.CustomActionsPopup;
@@ -355,6 +356,11 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
@Override
public void onConfigurationChanged(Configuration newConfig) {
int diff = newConfig.diff(mOldConfig);
+
+ if ((diff & CONFIG_LOCALE) != 0) {
+ Folder.setLocaleDependentFields(getResources(), true /* force */);
+ }
+
if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) {
mUserEventDispatcher = null;
initDeviceProfile(mDeviceProfile.inv);
@@ -1240,7 +1246,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
mAppsView.reset(isStarted() /* animate */);
}
- if (shouldMoveToDefaultScreen && !mWorkspace.isTouchActive()) {
+ if (shouldMoveToDefaultScreen && !mWorkspace.isHandlingTouch()) {
mWorkspace.post(mWorkspace::moveToDefaultScreen);
}
}
@@ -1599,14 +1605,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
if (topView != null && topView.onBackPressed()) {
// Handled by the floating view.
- } else if (!isInState(NORMAL)) {
- LauncherState lastState = mStateManager.getLastState();
- ued.logActionCommand(Action.Command.BACK, mStateManager.getState().containerType,
- lastState.containerType);
- mStateManager.goToState(lastState);
} else {
- // Back button is a no-op here, but give at least some feedback for the button press
- mWorkspace.showOutlinesTemporarily();
+ mStateManager.getState().onBackPressed(this);
}
}
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 8a15b24f1..bbe44c005 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -30,6 +30,7 @@ import com.android.launcher3.uioverrides.AllAppsState;
import com.android.launcher3.uioverrides.FastOverviewState;
import com.android.launcher3.uioverrides.OverviewState;
import com.android.launcher3.uioverrides.UiFactory;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import java.util.Arrays;
@@ -251,6 +252,16 @@ public class LauncherState {
}
}
+ public void onBackPressed(Launcher launcher) {
+ if (this != NORMAL) {
+ LauncherStateManager lsm = launcher.getStateManager();
+ LauncherState lastState = lsm.getLastState();
+ launcher.getUserEventDispatcher().logActionCommand(Action.Command.BACK,
+ containerType, lastState.containerType);
+ lsm.goToState(lastState);
+ }
+ }
+
protected static void dispatchWindowStateChanged(Launcher launcher) {
launcher.getWindow().getDecorView().sendAccessibilityEvent(TYPE_WINDOW_STATE_CHANGED);
}
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 3c7c1aa07..5bbfc1d79 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -306,7 +306,13 @@ public class LauncherStateManager {
*/
public AnimatorPlaybackController createAnimationToNewWorkspace(
LauncherState fromState, LauncherState state, long duration) {
+ // Since we are creating a state animation to a different state, temporarily prevent state
+ // change as part of config reset.
+ LauncherState originalRestState = mRestState;
+ mRestState = state;
mConfig.reset();
+ mRestState = originalRestState;
+
for (StateHandler handler : getStateHandlers()) {
handler.setState(fromState);
}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index db5dc6635..c81f679d0 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -63,7 +63,6 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
protected static final ComputePageScrollsLogic SIMPLE_SCROLL_LOGIC = (v) -> v.getVisibility() != GONE;
public static final int PAGE_SNAP_ANIMATION_DURATION = 750;
- public static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950;
// OverScroll constants
private final static int OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION = 270;
@@ -109,13 +108,7 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
private float mTotalMotionX;
protected int[] mPageScrolls;
-
- protected final static int TOUCH_STATE_REST = 0;
- protected final static int TOUCH_STATE_SCROLLING = 1;
- protected final static int TOUCH_STATE_PREV_PAGE = 2;
- protected final static int TOUCH_STATE_NEXT_PAGE = 3;
-
- protected int mTouchState = TOUCH_STATE_REST;
+ private boolean mIsBeingDragged;
protected int mTouchSlop;
private int mMaximumVelocity;
@@ -451,7 +444,7 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
// We don't want to trigger a page end moving unless the page has settled
// and the user has stopped scrolling
- if (mTouchState == TOUCH_STATE_REST) {
+ if (!mIsBeingDragged) {
pageEndTransition();
}
@@ -812,9 +805,9 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
}
/** Returns whether x and y originated within the buffered viewport */
- private boolean isTouchPointInViewportWithBuffer(int x, int y) {
+ private boolean isTouchPointInViewportWithBuffer(float x, float y) {
sTmpRect.set(-getMeasuredWidth() / 2, 0, 3 * getMeasuredWidth() / 2, getMeasuredHeight());
- return sTmpRect.contains(x, y);
+ return sTmpRect.contains((int) x, (int) y);
}
@Override
@@ -835,8 +828,7 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
* motion.
*/
final int action = ev.getAction();
- if ((action == MotionEvent.ACTION_MOVE) &&
- (mTouchState == TOUCH_STATE_SCROLLING)) {
+ if ((action == MotionEvent.ACTION_MOVE) && mIsBeingDragged) {
return true;
}
@@ -877,17 +869,13 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop / 3);
if (finishedScrolling) {
- mTouchState = TOUCH_STATE_REST;
+ mIsBeingDragged = false;
if (!mScroller.isFinished() && !mFreeScroll) {
setCurrentPage(getNextPage());
pageEndTransition();
}
} else {
- if (isTouchPointInViewportWithBuffer((int) mDownMotionX, (int) mDownMotionY)) {
- mTouchState = TOUCH_STATE_SCROLLING;
- } else {
- mTouchState = TOUCH_STATE_REST;
- }
+ mIsBeingDragged = isTouchPointInViewportWithBuffer(mDownMotionX, mDownMotionY);
}
break;
@@ -908,11 +896,11 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
* The only time we want to intercept motion events is if we are in the
* drag mode.
*/
- return mTouchState != TOUCH_STATE_REST;
+ return mIsBeingDragged;
}
public boolean isHandlingTouch() {
- return mTouchState != TOUCH_STATE_REST;
+ return mIsBeingDragged;
}
protected void determineScrollingStart(MotionEvent ev) {
@@ -931,7 +919,7 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
// Disallow scrolling if we started the gesture from outside the viewport
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);
- if (!isTouchPointInViewportWithBuffer((int) x, (int) y)) return;
+ if (!isTouchPointInViewportWithBuffer(x, y)) return;
final int xDiff = (int) Math.abs(x - mLastMotionX);
@@ -940,7 +928,7 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
if (xMoved) {
// Scroll if the user moved far enough along the X axis
- mTouchState = TOUCH_STATE_SCROLLING;
+ mIsBeingDragged = true;
mTotalMotionX += Math.abs(mLastMotionX - x);
mLastMotionX = x;
mLastMotionXRemainder = 0;
@@ -1077,14 +1065,14 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
mTotalMotionX = 0;
mActivePointerId = ev.getPointerId(0);
- if (mTouchState == TOUCH_STATE_SCROLLING) {
+ if (mIsBeingDragged) {
onScrollInteractionBegin();
pageBeginTransition();
}
break;
case MotionEvent.ACTION_MOVE:
- if (mTouchState == TOUCH_STATE_SCROLLING) {
+ if (mIsBeingDragged) {
// Scroll to follow the motion event
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
@@ -1111,7 +1099,7 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
break;
case MotionEvent.ACTION_UP:
- if (mTouchState == TOUCH_STATE_SCROLLING) {
+ if (mIsBeingDragged) {
final int activePointerId = mActivePointerId;
final int pointerIndex = ev.findPointerIndex(activePointerId);
final float x = ev.getX(pointerIndex);
@@ -1193,26 +1181,6 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
invalidate();
}
onScrollInteractionEnd();
- } else if (mTouchState == TOUCH_STATE_PREV_PAGE) {
- // at this point we have not moved beyond the touch slop
- // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
- // we can just page
- int nextPage = Math.max(0, mCurrentPage - 1);
- if (nextPage != mCurrentPage) {
- snapToPage(nextPage);
- } else {
- snapToDestination();
- }
- } else if (mTouchState == TOUCH_STATE_NEXT_PAGE) {
- // at this point we have not moved beyond the touch slop
- // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
- // we can just page
- int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1);
- if (nextPage != mCurrentPage) {
- snapToPage(nextPage);
- } else {
- snapToDestination();
- }
}
// End any intermediate reordering states
@@ -1220,7 +1188,7 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
break;
case MotionEvent.ACTION_CANCEL:
- if (mTouchState == TOUCH_STATE_SCROLLING) {
+ if (mIsBeingDragged) {
snapToDestination();
onScrollInteractionEnd();
}
@@ -1242,7 +1210,7 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
private void resetTouchState() {
releaseVelocityTracker();
- mTouchState = TOUCH_STATE_REST;
+ mIsBeingDragged = false;
mActivePointerId = INVALID_POINTER;
}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 8683b2103..a8513185e 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -16,6 +16,7 @@
package com.android.launcher3;
+import android.app.ActivityManager;
import android.app.WallpaperManager;
import android.content.ComponentName;
import android.content.Context;
@@ -130,6 +131,9 @@ public final class Utilities {
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
+ public static final boolean IS_RUNNING_IN_TEST_HARNESS =
+ ActivityManager.isRunningInTestHarness();
+
public static boolean isPropertyEnabled(String propertyName) {
return Log.isLoggable(propertyName, Log.VERBOSE);
}
@@ -598,4 +602,8 @@ public final class Utilities {
msg.setAsynchronous(true);
handler.sendMessage(msg);
}
+
+ public interface Consumer<T> {
+ void accept(T var1);
+ }
}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 67bdd3bdd..28783fa2b 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -54,6 +54,7 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Toast;
import com.android.launcher3.Launcher.LauncherOverlay;
@@ -286,7 +287,9 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
mInsets.set(insets);
DeviceProfile grid = mLauncher.getDeviceProfile();
- mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx);
+ mMaxDistanceForFolderCreation = grid.isTablet
+ ? 0.75f * grid.iconSizePx
+ : 0.55f * grid.iconSizePx;
mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
Rect padding = grid.workspacePadding;
@@ -471,10 +474,6 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
super.onViewAdded(child);
}
- public boolean isTouchActive() {
- return mTouchState != TOUCH_STATE_REST;
- }
-
/**
* Initializes and binds the first page
* @param qsb an existing qsb to recycle or null.
@@ -547,7 +546,6 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
// created CellLayout.
CellLayout newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
R.layout.workspace_screen, this, false /* attachToRoot */);
- newScreen.getShortcutsAndWidgets().setId(R.id.workspace_page_container);
int paddingLeftRight = mLauncher.getDeviceProfile().cellLayoutPaddingLeftRightPx;
int paddingBottom = mLauncher.getDeviceProfile().cellLayoutBottomPaddingPx;
newScreen.setPadding(paddingLeftRight, 0, paddingLeftRight, paddingBottom);
@@ -1034,7 +1032,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
}
protected void onScrollInteractionBegin() {
- super.onScrollInteractionEnd();
+ super.onScrollInteractionBegin();
mScrollInteractionBegan = true;
}
@@ -1289,12 +1287,6 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
}
}
- public void showOutlinesTemporarily() {
- if (!mIsPageInTransition && !isTouchActive()) {
- snapToPage(mCurrentPage);
- }
- }
-
private void updatePageAlphaValues() {
// We need to check the isDragging case because updatePageAlphaValues is called between
// goToState(SPRING_LOADED) and onStartStateTransition.
@@ -1493,6 +1485,18 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
}
}
+ @Override
+ public AccessibilityNodeInfo createAccessibilityNodeInfo() {
+ if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) {
+ // TAPL tests verify that workspace is not present in Overview and AllApps states.
+ // TAPL can work only if UIDevice is set up as setCompressedLayoutHeirarchy(false).
+ // Hiding workspace from the tests when it's
+ // IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS.
+ return null;
+ }
+ return super.createAccessibilityNodeInfo();
+ }
+
private void updateAccessibilityFlags(int accessibilityFlag, CellLayout page) {
page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
page.getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag);
@@ -2019,22 +2023,8 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
}
public void onNoCellFound(View dropTargetLayout) {
- if (mLauncher.isHotseatLayout(dropTargetLayout)) {
- Hotseat hotseat = mLauncher.getHotseat();
- boolean droppedOnAllAppsIcon = !FeatureFlags.NO_ALL_APPS_ICON
- && mTargetCell != null && !mLauncher.getDeviceProfile().inv.isAllAppsButtonRank(
- hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]));
- if (!droppedOnAllAppsIcon) {
- // Only show message when hotseat is full and drop target was not AllApps button
- showOutOfSpaceMessage(true);
- }
- } else {
- showOutOfSpaceMessage(false);
- }
- }
-
- private void showOutOfSpaceMessage(boolean isHotseatLayout) {
- int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space);
+ int strId = mLauncher.isHotseatLayout(dropTargetLayout)
+ ? R.string.hotseat_out_of_space : R.string.out_of_space;
Toast.makeText(mLauncher, mLauncher.getString(strId), Toast.LENGTH_SHORT).show();
}
@@ -2630,16 +2620,10 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
- if (info.container == NO_ID) {
+ if (info.container == NO_ID && info instanceof AppInfo) {
// Came from all apps -- make a copy
- if (info instanceof AppInfo) {
- info = ((AppInfo) info).makeShortcut();
- d.dragInfo = info;
- } else if (info instanceof ShortcutInfo) {
- info = new ShortcutInfo((ShortcutInfo) info);
- d.dragInfo = info;
- }
-
+ info = ((AppInfo) info).makeShortcut();
+ d.dragInfo = info;
}
view = mLauncher.createShortcut(cellLayout, (ShortcutInfo) info);
break;
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index fdf32af6d..40cf0f3d0 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -21,6 +21,7 @@ import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.Bundle;
import android.os.Process;
import android.support.animation.DynamicAnimation;
import android.support.annotation.NonNull;
@@ -48,6 +49,7 @@ import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.keyboard.FocusedItemDecorator;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -549,4 +551,16 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
&& verticalFadingEdge);
}
}
+
+ @Override
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ if (AccessibilityManagerCompat.processTestRequest(
+ mLauncher, "TAPL_GET_SCROLL", action, arguments,
+ response ->
+ response.putInt("scrollY", getActiveRecyclerView().getCurrentScrollY()))) {
+ return true;
+ }
+
+ return super.performAccessibilityAction(action, arguments);
+ }
}
diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java
index e7cf09209..d766398ff 100644
--- a/src/com/android/launcher3/allapps/WorkModeSwitch.java
+++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java
@@ -24,6 +24,7 @@ import android.widget.Switch;
import com.android.launcher3.compat.UserManagerCompat;
+import java.lang.ref.WeakReference;
import java.util.List;
public class WorkModeSwitch extends Switch {
@@ -61,34 +62,57 @@ public class WorkModeSwitch extends Switch {
}
private void trySetQuietModeEnabledToAllProfilesAsync(boolean enabled) {
- new AsyncTask<Void, Void, Boolean>() {
+ new SetQuietModeEnabledAsyncTask(enabled, new WeakReference<>(this)).execute();
+ }
+
+ private static final class SetQuietModeEnabledAsyncTask
+ extends AsyncTask<Void, Void, Boolean> {
+
+ private final boolean enabled;
+ private final WeakReference<WorkModeSwitch> switchWeakReference;
- @Override
- protected void onPreExecute() {
- super.onPreExecute();
- setEnabled(false);
+ SetQuietModeEnabledAsyncTask(boolean enabled,
+ WeakReference<WorkModeSwitch> switchWeakReference) {
+ this.enabled = enabled;
+ this.switchWeakReference = switchWeakReference;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ WorkModeSwitch workModeSwitch = switchWeakReference.get();
+ if (workModeSwitch != null) {
+ workModeSwitch.setEnabled(false);
}
+ }
- @Override
- protected Boolean doInBackground(Void... voids) {
- UserManagerCompat userManager = UserManagerCompat.getInstance(getContext());
- List<UserHandle> userProfiles = userManager.getUserProfiles();
- boolean showConfirm = false;
- for (UserHandle userProfile : userProfiles) {
- if (Process.myUserHandle().equals(userProfile)) {
- continue;
- }
- showConfirm |= !userManager.requestQuietModeEnabled(enabled, userProfile);
+ @Override
+ protected Boolean doInBackground(Void... voids) {
+ WorkModeSwitch workModeSwitch = switchWeakReference.get();
+ if (workModeSwitch == null) {
+ return false;
+ }
+ UserManagerCompat userManager =
+ UserManagerCompat.getInstance(workModeSwitch.getContext());
+ List<UserHandle> userProfiles = userManager.getUserProfiles();
+ boolean showConfirm = false;
+ for (UserHandle userProfile : userProfiles) {
+ if (Process.myUserHandle().equals(userProfile)) {
+ continue;
}
- return showConfirm;
+ showConfirm |= !userManager.requestQuietModeEnabled(enabled, userProfile);
}
+ return showConfirm;
+ }
- @Override
- protected void onPostExecute(Boolean showConfirm) {
- if (showConfirm) {
- setEnabled(true);
+ @Override
+ protected void onPostExecute(Boolean showConfirm) {
+ if (showConfirm) {
+ WorkModeSwitch workModeSwitch = switchWeakReference.get();
+ if (workModeSwitch != null) {
+ workModeSwitch.setEnabled(true);
}
}
- }.execute();
+ }
}
}
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index 0c78381ea..3b0226e1c 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -17,9 +17,13 @@
package com.android.launcher3.compat;
import android.content.Context;
+import android.os.Bundle;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.android.launcher3.Utilities;
public class AccessibilityManagerCompat {
@@ -44,4 +48,56 @@ public class AccessibilityManagerCompat {
private static AccessibilityManager getManager(Context context) {
return (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
}
+
+ public static void sendEventToTest(Context context, String eventTag) {
+ final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
+ if (accessibilityManager == null) return;
+
+ sendEventToTest(accessibilityManager, eventTag, null);
+ }
+
+ private static void sendEventToTest(
+ AccessibilityManager accessibilityManager, String eventTag, Bundle data) {
+ final AccessibilityEvent e = AccessibilityEvent.obtain(
+ AccessibilityEvent.TYPE_ANNOUNCEMENT);
+ e.setClassName(eventTag);
+ e.setParcelableData(data);
+ accessibilityManager.sendAccessibilityEvent(e);
+ }
+
+ /**
+ * Returns accessibility manager to be used for communication with UI Automation tests.
+ * The tests may exchange custom accessibility messages with the launcher; the accessibility
+ * manager is used in these communications.
+ *
+ * If the launcher runs not under a test, the return is null, and no attempt to process or send
+ * custom accessibility messages should be made.
+ */
+ private static AccessibilityManager getAccessibilityManagerForTest(Context context) {
+ // If not running in a test harness, don't participate in test exchanges.
+ if (!Utilities.IS_RUNNING_IN_TEST_HARNESS) return null;
+
+ final AccessibilityManager accessibilityManager = getManager(context);
+ if (!accessibilityManager.isEnabled()) return null;
+
+ return accessibilityManager;
+ }
+
+ public static boolean processTestRequest(Context context, String eventTag, int action,
+ Bundle request, Utilities.Consumer<Bundle> responseFiller) {
+ final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
+ if (accessibilityManager == null) return false;
+
+ // The test sends a request via a ACTION_SET_TEXT.
+ if (action == AccessibilityNodeInfo.ACTION_SET_TEXT &&
+ eventTag.equals(request.getCharSequence(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE))) {
+ final Bundle response = new Bundle();
+ responseFiller.accept(response);
+ AccessibilityManagerCompat.sendEventToTest(
+ accessibilityManager, eventTag + "_RESPONSE", response);
+ return true;
+ }
+ return false;
+ }
}
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index f4c6380b7..05fc33f35 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -31,12 +31,8 @@ abstract class BaseFlags {
public static final boolean IS_DOGFOOD_BUILD = false;
public static final String AUTHORITY = "com.android.launcher3.settings".intern();
- // When enabled allows to use any point on the fast scrollbar to start dragging.
- public static final boolean LAUNCHER3_DIRECT_SCROLL = true;
// When enabled the promise icon is visible in all apps while installation an app.
public static final boolean LAUNCHER3_PROMISE_APPS_IN_ALL_APPS = false;
- // When enabled allows use of spring motions on the icons.
- public static final boolean LAUNCHER3_SPRING_ICONS = true;
// Feature flag to enable moving the QSB on the 0th screen of the workspace.
public static final boolean QSB_ON_FIRST_SCREEN = true;
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 8a216fc17..47bbbcb0d 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -16,6 +16,7 @@
package com.android.launcher3.dragndrop;
+import static com.android.launcher3.AbstractFloatingView.TYPE_DISCOVERY_BOUNCE;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
import static com.android.launcher3.LauncherState.NORMAL;
@@ -31,6 +32,7 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
import com.android.launcher3.ItemInfo;
@@ -145,6 +147,7 @@ public class DragController implements DragDriver.EventListener, TouchController
// Hide soft keyboard, if visible
UiThreadHelper.hideKeyboardAsync(mLauncher, mWindowToken);
+ AbstractFloatingView.closeOpenViews(mLauncher, false, TYPE_DISCOVERY_BOUNCE);
mOptions = options;
if (mOptions.systemDndStartPoint != null) {
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 551567ae7..b3d9bdd72 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -16,6 +16,8 @@
package com.android.launcher3.dragndrop;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_ICON_BADGED;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.FloatArrayEvaluator;
@@ -57,8 +59,6 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.ShortcutConfigActivityInfo;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.graphics.IconNormalizer;
import com.android.launcher3.graphics.LauncherIcons;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutInfoCompat;
@@ -70,8 +70,6 @@ import com.android.launcher3.widget.PendingAddShortcutInfo;
import java.util.Arrays;
import java.util.List;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_ICON_BADGED;
-
public class DragView extends View {
private static final ColorMatrix sTempMatrix1 = new ColorMatrix();
private static final ColorMatrix sTempMatrix2 = new ColorMatrix();
@@ -198,7 +196,7 @@ public class DragView extends View {
*/
@TargetApi(Build.VERSION_CODES.O)
public void setItemInfo(final ItemInfo info) {
- if (!(FeatureFlags.LAUNCHER3_SPRING_ICONS && Utilities.ATLEAST_OREO)) {
+ if (!Utilities.ATLEAST_OREO) {
return;
}
if (info.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 6b13da70c..6a3ebcfb0 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -25,6 +25,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.annotation.SuppressLint;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
import android.text.InputType;
@@ -191,14 +192,9 @@ public class Folder extends AbstractFloatingView implements DragSource,
public Folder(Context context, AttributeSet attrs) {
super(context, attrs);
setAlwaysDrawnWithCacheEnabled(false);
- Resources res = getResources();
- if (sDefaultFolderName == null) {
- sDefaultFolderName = res.getString(R.string.folder_name);
- }
- if (sHintText == null) {
- sHintText = res.getString(R.string.folder_hint_text);
- }
+ setLocaleDependentFields(getResources(), false /* force */);
+
mLauncher = Launcher.getLauncher(context);
// We need this view to be focusable in touch mode so that when text editing of the folder
// name is complete, we have something to focus on, thus hiding the cursor and giving
@@ -1473,4 +1469,13 @@ public class Folder extends AbstractFloatingView implements DragSource,
}
return false;
}
+
+ public static void setLocaleDependentFields(Resources res, boolean force) {
+ if (sDefaultFolderName == null || force) {
+ sDefaultFolderName = res.getString(R.string.folder_name);
+ }
+ if (sHintText == null || force) {
+ sHintText = res.getString(R.string.folder_hint_text);
+ }
+ }
}
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index bdc40d738..a60216c53 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -76,7 +76,6 @@ public class FolderIcon extends FrameLayout implements FolderListener {
@Thunk Launcher mLauncher;
@Thunk Folder mFolder;
private FolderInfo mInfo;
- @Thunk static boolean sStaticValuesDirty = true;
private CheckLongPressHelper mLongPressHelper;
private StylusEventHelper mStylusEventHelper;
@@ -184,12 +183,6 @@ public class FolderIcon extends FrameLayout implements FolderListener {
return icon;
}
- @Override
- protected Parcelable onSaveInstanceState() {
- sStaticValuesDirty = true;
- return super.onSaveInstanceState();
- }
-
public Folder getFolder() {
return mFolder;
}
diff --git a/src/com/android/launcher3/graphics/NinePatchDrawHelper.java b/src/com/android/launcher3/graphics/NinePatchDrawHelper.java
index fc209260a..587268968 100644
--- a/src/com/android/launcher3/graphics/NinePatchDrawHelper.java
+++ b/src/com/android/launcher3/graphics/NinePatchDrawHelper.java
@@ -33,7 +33,9 @@ public class NinePatchDrawHelper {
private final Rect mSrc = new Rect();
private final RectF mDst = new RectF();
- public final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ // Enable filtering to always get a nice edge. This avoids jagged line, when bitmap is
+ // translated by half pixel.
+ public final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
/**
* Draws the bitmap split into three parts horizontally, with the middle part having width
diff --git a/src/com/android/launcher3/graphics/ShadowDrawable.java b/src/com/android/launcher3/graphics/ShadowDrawable.java
index b40bf7828..19e2768cc 100644
--- a/src/com/android/launcher3/graphics/ShadowDrawable.java
+++ b/src/com/android/launcher3/graphics/ShadowDrawable.java
@@ -32,7 +32,6 @@ import android.os.Build;
import android.util.AttributeSet;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -146,7 +145,7 @@ public class ShadowDrawable extends Drawable {
d.draw(canvas);
}
- if (Utilities.ATLEAST_OREO) {
+ if (BitmapRenderer.USE_HARDWARE_BITMAP) {
bitmap = bitmap.copy(Bitmap.Config.HARDWARE, false);
}
mState.mLastDrawnBitmap = bitmap;
diff --git a/src/com/android/launcher3/graphics/ShadowGenerator.java b/src/com/android/launcher3/graphics/ShadowGenerator.java
index e12095bd7..52d45accf 100644
--- a/src/com/android/launcher3/graphics/ShadowGenerator.java
+++ b/src/com/android/launcher3/graphics/ShadowGenerator.java
@@ -126,13 +126,13 @@ public class ShadowGenerator {
}
public Bitmap createPill(int width, int height) {
- radius = height / 2;
+ radius = height / 2f;
- int centerX = Math.round(width / 2 + shadowBlur);
+ int centerX = Math.round(width / 2f + shadowBlur);
int centerY = Math.round(radius + shadowBlur + keyShadowDistance);
int center = Math.max(centerX, centerY);
bounds.set(0, 0, width, height);
- bounds.offsetTo(center - width / 2, center - height / 2);
+ bounds.offsetTo(center - width / 2f, center - height / 2f);
int size = center * 2;
Bitmap result = Bitmap.createBitmap(size, size, Config.ARGB_8888);
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
index d9b1a3f9e..12daea50f 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -138,10 +138,7 @@ public class GridSizeMigrationTask {
*/
protected boolean migrateHotseat() throws Exception {
ArrayList<DbEntry> items = loadHotseatEntries();
-
- int requiredCount = FeatureFlags.NO_ALL_APPS_ICON ? mDestHotseatSize : mDestHotseatSize - 1;
-
- while (items.size() > requiredCount) {
+ while (items.size() > mDestHotseatSize) {
// Pick the center item by default.
DbEntry toRemove = items.get(items.size() / 2);
@@ -171,9 +168,6 @@ public class GridSizeMigrationTask {
}
newScreenId++;
- if (!FeatureFlags.NO_ALL_APPS_ICON && mIdp.isAllAppsButtonRank(newScreenId)) {
- newScreenId++;
- }
}
return applyOperations();
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 6378ea180..744e98aea 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -24,7 +24,6 @@ import android.content.Intent.ShortcutIconResource;
import android.content.pm.LauncherActivityInfo;
import android.database.Cursor;
import android.database.CursorWrapper;
-import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.UserHandle;
import android.provider.BaseColumns;
@@ -387,15 +386,6 @@ public class LoaderCursor extends CursorWrapper {
protected boolean checkItemPlacement(ItemInfo item, ArrayList<Long> workspaceScreens) {
long containerIndex = item.screenId;
if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
- // Return early if we detect that an item is under the hotseat button
- if (!FeatureFlags.NO_ALL_APPS_ICON &&
- mIDP.isAllAppsButtonRank((int) item.screenId)) {
- Log.e(TAG, "Error loading shortcut into hotseat " + item
- + " into position (" + item.screenId + ":" + item.cellX + ","
- + item.cellY + ") occupied by all apps");
- return false;
- }
-
final GridOccupancy hotseatOccupancy =
occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT);
diff --git a/src/com/android/launcher3/notification/NotificationFooterLayout.java b/src/com/android/launcher3/notification/NotificationFooterLayout.java
index 1216a27be..66f525cb4 100644
--- a/src/com/android/launcher3/notification/NotificationFooterLayout.java
+++ b/src/com/android/launcher3/notification/NotificationFooterLayout.java
@@ -157,7 +157,7 @@ public class NotificationFooterLayout extends FrameLayout {
Rect fromBounds = sTempRect;
firstNotification.getGlobalVisibleRect(fromBounds);
float scale = (float) toBounds.height() / fromBounds.height();
- Animator moveAndScaleIcon = LauncherAnimUtils.ofPropertyValuesHolder(firstNotification,
+ Animator moveAndScaleIcon = ObjectAnimator.ofPropertyValuesHolder(firstNotification,
new PropertyListBuilder().scale(scale).translationY(toBounds.top - fromBounds.top
+ (fromBounds.height() * scale - fromBounds.height()) / 2).build());
moveAndScaleIcon.addListener(new AnimatorListenerAdapter() {
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 635e04371..10be92551 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -84,7 +84,7 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource,
private final List<DeepShortcutView> mShortcuts = new ArrayList<>();
private final PointF mInterceptTouchDown = new PointF();
- private final Point mIconLastTouchPos = new Point();
+ protected final Point mIconLastTouchPos = new Point();
private final int mStartDragThreshold;
private final LauncherAccessibilityDelegate mAccessibilityDelegate;
diff --git a/src/com/android/launcher3/provider/ImportDataTask.java b/src/com/android/launcher3/provider/ImportDataTask.java
index b1dd003f8..16c7417aa 100644
--- a/src/com/android/launcher3/provider/ImportDataTask.java
+++ b/src/com/android/launcher3/provider/ImportDataTask.java
@@ -308,9 +308,6 @@ public class ImportDataTask {
LongArrayMap<Object> hotseatItems = GridSizeMigrationTask.removeBrokenHotseatItems(mContext);
int myHotseatCount = LauncherAppState.getIDP(mContext).numHotseatIcons;
- if (!FeatureFlags.NO_ALL_APPS_ICON) {
- myHotseatCount--;
- }
if (hotseatItems.size() < myHotseatCount) {
// Insufficient hotseat items. Add a few more.
HotseatParserCallback parserCallback = new HotseatParserCallback(
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutView.java b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
index 7d0ea7bb9..c856cdbe5 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
@@ -141,4 +141,8 @@ public class DeepShortcutView extends FrameLayout {
public View getIconView() {
return mIconView;
}
+
+ public ShortcutInfoCompat getDetail() {
+ return mDetail;
+ }
}
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 55f850c8d..0e277eabc 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -43,6 +43,7 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -515,6 +516,8 @@ public abstract class AbstractStateChangeTouchController
logReachedState(logAction, targetState);
}
mLauncher.getStateManager().goToState(targetState, false /* animated */);
+
+ AccessibilityManagerCompat.sendEventToTest(mLauncher, "TAPL_WENT_TO_STATE");
}
}
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 97f836f4d..52fef9f7d 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -218,7 +218,7 @@ public class ItemClickHandler {
if (item instanceof ShortcutInfo) {
ShortcutInfo si = (ShortcutInfo) item;
if (si.hasStatusFlag(ShortcutInfo.FLAG_SUPPORTS_WEB_UI)
- && intent.getAction() == Intent.ACTION_VIEW) {
+ && Intent.ACTION_VIEW.equals(intent.getAction())) {
// make a copy of the intent that has the package set to null
// we do this because the platform sometimes disables instant
// apps temporarily (triggered by the user) and fallbacks to the
diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
index f59f14e58..668892791 100644
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -125,7 +125,7 @@ public class WorkspaceTouchListener implements OnTouchListener, Runnable {
}
if (action == ACTION_UP || action == ACTION_POINTER_UP) {
- if (!mWorkspace.isTouchActive()) {
+ if (!mWorkspace.isHandlingTouch()) {
final CellLayout currentPage =
(CellLayout) mWorkspace.getChildAt(mWorkspace.getCurrentPage());
if (currentPage != null) {
diff --git a/src/com/android/launcher3/util/FocusLogic.java b/src/com/android/launcher3/util/FocusLogic.java
index b793f5481..4f4cccd7a 100644
--- a/src/com/android/launcher3/util/FocusLogic.java
+++ b/src/com/android/launcher3/util/FocusLogic.java
@@ -201,10 +201,6 @@ public class FocusLogic {
ViewGroup hotseatParent = hotseatLayout.getShortcutsAndWidgets();
boolean isHotseatHorizontal = !dp.isVerticalBarLayout();
- boolean moreIconsInHotseatThanWorkspace = !FeatureFlags.NO_ALL_APPS_ICON &&
- (isHotseatHorizontal
- ? hotseatLayout.getCountX() > iconLayout.getCountX()
- : hotseatLayout.getCountY() > iconLayout.getCountY());
int m, n;
if (isHotseatHorizontal) {
@@ -215,19 +211,7 @@ public class FocusLogic {
n = hotseatLayout.getCountY();
}
int[][] matrix = createFullMatrix(m, n);
- if (moreIconsInHotseatThanWorkspace) {
- int allappsiconRank = dp.inv.getAllAppsButtonRank();
- if (isHotseatHorizontal) {
- for (int j = 0; j < n; j++) {
- matrix[allappsiconRank][j] = ALL_APPS_COLUMN;
- }
- } else {
- for (int j = 0; j < m; j++) {
- matrix[j][allappsiconRank] = ALL_APPS_COLUMN;
- }
- }
- }
- // Iterate thru the children of the workspace.
+ // Iterate through the children of the workspace.
for (int i = 0; i < iconParent.getChildCount(); i++) {
View cell = iconParent.getChildAt(i);
if (!cell.isFocusable()) {
@@ -235,17 +219,6 @@ public class FocusLogic {
}
int cx = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellX;
int cy = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellY;
- if (moreIconsInHotseatThanWorkspace) {
- int allappsiconRank = dp.inv.getAllAppsButtonRank();
- if (isHotseatHorizontal && cx >= allappsiconRank) {
- // Add 1 to account for the All Apps button.
- cx++;
- }
- if (!isHotseatHorizontal && cy >= allappsiconRank) {
- // Add 1 to account for the All Apps button.
- cy++;
- }
- }
matrix[cx][cy] = i;
}
diff --git a/src/com/android/launcher3/util/OverScroller.java b/src/com/android/launcher3/util/OverScroller.java
new file mode 100644
index 000000000..ec9abceab
--- /dev/null
+++ b/src/com/android/launcher3/util/OverScroller.java
@@ -0,0 +1,963 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+import static com.android.launcher3.anim.Interpolators.SCROLL;
+
+import android.content.Context;
+import android.hardware.SensorManager;
+import android.util.Log;
+import android.view.ViewConfiguration;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+/**
+ * This class encapsulates scrolling with the ability to overshoot the bounds
+ * of a scrolling operation. This class is a drop-in replacement for
+ * {@link android.widget.Scroller} in most cases.
+ */
+public class OverScroller {
+ private int mMode;
+
+ private final SplineOverScroller mScrollerX;
+ private final SplineOverScroller mScrollerY;
+
+ private Interpolator mInterpolator;
+
+ private final boolean mFlywheel;
+
+ private static final int DEFAULT_DURATION = 250;
+ private static final int SCROLL_MODE = 0;
+ private static final int FLING_MODE = 1;
+
+ /**
+ * Creates an OverScroller with a viscous fluid scroll interpolator and flywheel.
+ * @param context
+ */
+ public OverScroller(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Creates an OverScroller with flywheel enabled.
+ * @param context The context of this application.
+ * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
+ * be used.
+ */
+ public OverScroller(Context context, Interpolator interpolator) {
+ this(context, interpolator, true);
+ }
+
+ /**
+ * Creates an OverScroller.
+ * @param context The context of this application.
+ * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
+ * be used.
+ * @param flywheel If true, successive fling motions will keep on increasing scroll speed.
+ * @hide
+ */
+ public OverScroller(Context context, Interpolator interpolator, boolean flywheel) {
+ if (interpolator == null) {
+ mInterpolator = SCROLL;
+ } else {
+ mInterpolator = interpolator;
+ }
+ mFlywheel = flywheel;
+ mScrollerX = new SplineOverScroller(context);
+ mScrollerY = new SplineOverScroller(context);
+ }
+
+ /**
+ * Creates an OverScroller with flywheel enabled.
+ * @param context The context of this application.
+ * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
+ * be used.
+ * @param bounceCoefficientX A value between 0 and 1 that will determine the proportion of the
+ * velocity which is preserved in the bounce when the horizontal edge is reached. A null value
+ * means no bounce. This behavior is no longer supported and this coefficient has no effect.
+ * @param bounceCoefficientY Same as bounceCoefficientX but for the vertical direction. This
+ * behavior is no longer supported and this coefficient has no effect.
+ * @deprecated Use {@link #OverScroller(Context, Interpolator)} instead.
+ */
+ @Deprecated
+ public OverScroller(Context context, Interpolator interpolator,
+ float bounceCoefficientX, float bounceCoefficientY) {
+ this(context, interpolator, true);
+ }
+
+ /**
+ * Creates an OverScroller.
+ * @param context The context of this application.
+ * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
+ * be used.
+ * @param bounceCoefficientX A value between 0 and 1 that will determine the proportion of the
+ * velocity which is preserved in the bounce when the horizontal edge is reached. A null value
+ * means no bounce. This behavior is no longer supported and this coefficient has no effect.
+ * @param bounceCoefficientY Same as bounceCoefficientX but for the vertical direction. This
+ * behavior is no longer supported and this coefficient has no effect.
+ * @param flywheel If true, successive fling motions will keep on increasing scroll speed.
+ * @deprecated Use {@link #OverScroller(Context, Interpolator)} instead.
+ */
+ @Deprecated
+ public OverScroller(Context context, Interpolator interpolator,
+ float bounceCoefficientX, float bounceCoefficientY, boolean flywheel) {
+ this(context, interpolator, flywheel);
+ }
+
+ void setInterpolator(Interpolator interpolator) {
+ if (interpolator == null) {
+ mInterpolator = SCROLL;
+ } else {
+ mInterpolator = interpolator;
+ }
+ }
+
+ /**
+ * The amount of friction applied to flings. The default value
+ * is {@link ViewConfiguration#getScrollFriction}.
+ *
+ * @param friction A scalar dimension-less value representing the coefficient of
+ * friction.
+ */
+ public final void setFriction(float friction) {
+ mScrollerX.setFriction(friction);
+ mScrollerY.setFriction(friction);
+ }
+
+ /**
+ *
+ * Returns whether the scroller has finished scrolling.
+ *
+ * @return True if the scroller has finished scrolling, false otherwise.
+ */
+ public final boolean isFinished() {
+ return mScrollerX.mFinished && mScrollerY.mFinished;
+ }
+
+ /**
+ * Force the finished field to a particular value. Contrary to
+ * {@link #abortAnimation()}, forcing the animation to finished
+ * does NOT cause the scroller to move to the final x and y
+ * position.
+ *
+ * @param finished The new finished value.
+ */
+ public final void forceFinished(boolean finished) {
+ mScrollerX.mFinished = mScrollerY.mFinished = finished;
+ }
+
+ /**
+ * Returns the current X offset in the scroll.
+ *
+ * @return The new X offset as an absolute distance from the origin.
+ */
+ public final int getCurrX() {
+ return mScrollerX.mCurrentPosition;
+ }
+
+ /**
+ * Returns the current Y offset in the scroll.
+ *
+ * @return The new Y offset as an absolute distance from the origin.
+ */
+ public final int getCurrY() {
+ return mScrollerY.mCurrentPosition;
+ }
+
+ /**
+ * Returns the absolute value of the current velocity.
+ *
+ * @return The original velocity less the deceleration, norm of the X and Y velocity vector.
+ */
+ public float getCurrVelocity() {
+ return (float) Math.hypot(mScrollerX.mCurrVelocity, mScrollerY.mCurrVelocity);
+ }
+
+ /**
+ * Returns the start X offset in the scroll.
+ *
+ * @return The start X offset as an absolute distance from the origin.
+ */
+ public final int getStartX() {
+ return mScrollerX.mStart;
+ }
+
+ /**
+ * Returns the start Y offset in the scroll.
+ *
+ * @return The start Y offset as an absolute distance from the origin.
+ */
+ public final int getStartY() {
+ return mScrollerY.mStart;
+ }
+
+ /**
+ * Returns where the scroll will end. Valid only for "fling" scrolls.
+ *
+ * @return The final X offset as an absolute distance from the origin.
+ */
+ public final int getFinalX() {
+ return mScrollerX.mFinal;
+ }
+
+ /**
+ * Returns where the scroll will end. Valid only for "fling" scrolls.
+ *
+ * @return The final Y offset as an absolute distance from the origin.
+ */
+ public final int getFinalY() {
+ return mScrollerY.mFinal;
+ }
+
+ /**
+ * Returns how long the scroll event will take, in milliseconds.
+ *
+ * @return The duration of the scroll in milliseconds.
+ *
+ * @hide Pending removal once nothing depends on it
+ * @deprecated OverScrollers don't necessarily have a fixed duration.
+ * This function will lie to the best of its ability.
+ */
+ @Deprecated
+ public final int getDuration() {
+ return Math.max(mScrollerX.mDuration, mScrollerY.mDuration);
+ }
+
+ /**
+ * Extend the scroll animation. This allows a running animation to scroll
+ * further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
+ *
+ * @param extend Additional time to scroll in milliseconds.
+ * @see #setFinalX(int)
+ * @see #setFinalY(int)
+ *
+ * @hide Pending removal once nothing depends on it
+ * @deprecated OverScrollers don't necessarily have a fixed duration.
+ * Instead of setting a new final position and extending
+ * the duration of an existing scroll, use startScroll
+ * to begin a new animation.
+ */
+ @Deprecated
+ public void extendDuration(int extend) {
+ mScrollerX.extendDuration(extend);
+ mScrollerY.extendDuration(extend);
+ }
+
+ /**
+ * Sets the final position (X) for this scroller.
+ *
+ * @param newX The new X offset as an absolute distance from the origin.
+ * @see #extendDuration(int)
+ * @see #setFinalY(int)
+ *
+ * @hide Pending removal once nothing depends on it
+ * @deprecated OverScroller's final position may change during an animation.
+ * Instead of setting a new final position and extending
+ * the duration of an existing scroll, use startScroll
+ * to begin a new animation.
+ */
+ @Deprecated
+ public void setFinalX(int newX) {
+ mScrollerX.setFinalPosition(newX);
+ }
+
+ /**
+ * Sets the final position (Y) for this scroller.
+ *
+ * @param newY The new Y offset as an absolute distance from the origin.
+ * @see #extendDuration(int)
+ * @see #setFinalX(int)
+ *
+ * @hide Pending removal once nothing depends on it
+ * @deprecated OverScroller's final position may change during an animation.
+ * Instead of setting a new final position and extending
+ * the duration of an existing scroll, use startScroll
+ * to begin a new animation.
+ */
+ @Deprecated
+ public void setFinalY(int newY) {
+ mScrollerY.setFinalPosition(newY);
+ }
+
+ /**
+ * Call this when you want to know the new location. If it returns true, the
+ * animation is not yet finished.
+ */
+ public boolean computeScrollOffset() {
+ if (isFinished()) {
+ return false;
+ }
+
+ switch (mMode) {
+ case SCROLL_MODE:
+ long time = AnimationUtils.currentAnimationTimeMillis();
+ // Any scroller can be used for time, since they were started
+ // together in scroll mode. We use X here.
+ final long elapsedTime = time - mScrollerX.mStartTime;
+
+ final int duration = mScrollerX.mDuration;
+ if (elapsedTime < duration) {
+ final float q = mInterpolator.getInterpolation(elapsedTime / (float) duration);
+ mScrollerX.updateScroll(q);
+ mScrollerY.updateScroll(q);
+ } else {
+ abortAnimation();
+ }
+ break;
+
+ case FLING_MODE:
+ if (!mScrollerX.mFinished) {
+ if (!mScrollerX.update()) {
+ if (!mScrollerX.continueWhenFinished()) {
+ mScrollerX.finish();
+ }
+ }
+ }
+
+ if (!mScrollerY.mFinished) {
+ if (!mScrollerY.update()) {
+ if (!mScrollerY.continueWhenFinished()) {
+ mScrollerY.finish();
+ }
+ }
+ }
+
+ break;
+ }
+
+ return true;
+ }
+
+ /**
+ * Start scrolling by providing a starting point and the distance to travel.
+ * The scroll will use the default value of 250 milliseconds for the
+ * duration.
+ *
+ * @param startX Starting horizontal scroll offset in pixels. Positive
+ * numbers will scroll the content to the left.
+ * @param startY Starting vertical scroll offset in pixels. Positive numbers
+ * will scroll the content up.
+ * @param dx Horizontal distance to travel. Positive numbers will scroll the
+ * content to the left.
+ * @param dy Vertical distance to travel. Positive numbers will scroll the
+ * content up.
+ */
+ public void startScroll(int startX, int startY, int dx, int dy) {
+ startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
+ }
+
+ /**
+ * Start scrolling by providing a starting point and the distance to travel.
+ *
+ * @param startX Starting horizontal scroll offset in pixels. Positive
+ * numbers will scroll the content to the left.
+ * @param startY Starting vertical scroll offset in pixels. Positive numbers
+ * will scroll the content up.
+ * @param dx Horizontal distance to travel. Positive numbers will scroll the
+ * content to the left.
+ * @param dy Vertical distance to travel. Positive numbers will scroll the
+ * content up.
+ * @param duration Duration of the scroll in milliseconds.
+ */
+ public void startScroll(int startX, int startY, int dx, int dy, int duration) {
+ mMode = SCROLL_MODE;
+ mScrollerX.startScroll(startX, dx, duration);
+ mScrollerY.startScroll(startY, dy, duration);
+ }
+
+ /**
+ * Call this when you want to 'spring back' into a valid coordinate range.
+ *
+ * @param startX Starting X coordinate
+ * @param startY Starting Y coordinate
+ * @param minX Minimum valid X value
+ * @param maxX Maximum valid X value
+ * @param minY Minimum valid Y value
+ * @param maxY Minimum valid Y value
+ * @return true if a springback was initiated, false if startX and startY were
+ * already within the valid range.
+ */
+ public boolean springBack(int startX, int startY, int minX, int maxX, int minY, int maxY) {
+ mMode = FLING_MODE;
+
+ // Make sure both methods are called.
+ final boolean spingbackX = mScrollerX.springback(startX, minX, maxX);
+ final boolean spingbackY = mScrollerY.springback(startY, minY, maxY);
+ return spingbackX || spingbackY;
+ }
+
+ public void fling(int startX, int startY, int velocityX, int velocityY,
+ int minX, int maxX, int minY, int maxY) {
+ fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0);
+ }
+
+ /**
+ * Start scrolling based on a fling gesture. The distance traveled will
+ * depend on the initial velocity of the fling.
+ *
+ * @param startX Starting point of the scroll (X)
+ * @param startY Starting point of the scroll (Y)
+ * @param velocityX Initial velocity of the fling (X) measured in pixels per
+ * second.
+ * @param velocityY Initial velocity of the fling (Y) measured in pixels per
+ * second
+ * @param minX Minimum X value. The scroller will not scroll past this point
+ * unless overX > 0. If overfling is allowed, it will use minX as
+ * a springback boundary.
+ * @param maxX Maximum X value. The scroller will not scroll past this point
+ * unless overX > 0. If overfling is allowed, it will use maxX as
+ * a springback boundary.
+ * @param minY Minimum Y value. The scroller will not scroll past this point
+ * unless overY > 0. If overfling is allowed, it will use minY as
+ * a springback boundary.
+ * @param maxY Maximum Y value. The scroller will not scroll past this point
+ * unless overY > 0. If overfling is allowed, it will use maxY as
+ * a springback boundary.
+ * @param overX Overfling range. If > 0, horizontal overfling in either
+ * direction will be possible.
+ * @param overY Overfling range. If > 0, vertical overfling in either
+ * direction will be possible.
+ */
+ public void fling(int startX, int startY, int velocityX, int velocityY,
+ int minX, int maxX, int minY, int maxY, int overX, int overY) {
+ // Continue a scroll or fling in progress
+ if (mFlywheel && !isFinished()) {
+ float oldVelocityX = mScrollerX.mCurrVelocity;
+ float oldVelocityY = mScrollerY.mCurrVelocity;
+ if (Math.signum(velocityX) == Math.signum(oldVelocityX) &&
+ Math.signum(velocityY) == Math.signum(oldVelocityY)) {
+ velocityX += oldVelocityX;
+ velocityY += oldVelocityY;
+ }
+ }
+
+ mMode = FLING_MODE;
+ mScrollerX.fling(startX, velocityX, minX, maxX, overX);
+ mScrollerY.fling(startY, velocityY, minY, maxY, overY);
+ }
+
+ /**
+ * Notify the scroller that we've reached a horizontal boundary.
+ * Normally the information to handle this will already be known
+ * when the animation is started, such as in a call to one of the
+ * fling functions. However there are cases where this cannot be known
+ * in advance. This function will transition the current motion and
+ * animate from startX to finalX as appropriate.
+ *
+ * @param startX Starting/current X position
+ * @param finalX Desired final X position
+ * @param overX Magnitude of overscroll allowed. This should be the maximum
+ * desired distance from finalX. Absolute value - must be positive.
+ */
+ public void notifyHorizontalEdgeReached(int startX, int finalX, int overX) {
+ mScrollerX.notifyEdgeReached(startX, finalX, overX);
+ }
+
+ /**
+ * Notify the scroller that we've reached a vertical boundary.
+ * Normally the information to handle this will already be known
+ * when the animation is started, such as in a call to one of the
+ * fling functions. However there are cases where this cannot be known
+ * in advance. This function will animate a parabolic motion from
+ * startY to finalY.
+ *
+ * @param startY Starting/current Y position
+ * @param finalY Desired final Y position
+ * @param overY Magnitude of overscroll allowed. This should be the maximum
+ * desired distance from finalY. Absolute value - must be positive.
+ */
+ public void notifyVerticalEdgeReached(int startY, int finalY, int overY) {
+ mScrollerY.notifyEdgeReached(startY, finalY, overY);
+ }
+
+ /**
+ * Returns whether the current Scroller is currently returning to a valid position.
+ * Valid bounds were provided by the
+ * {@link #fling(int, int, int, int, int, int, int, int, int, int)} method.
+ *
+ * One should check this value before calling
+ * {@link #startScroll(int, int, int, int)} as the interpolation currently in progress
+ * to restore a valid position will then be stopped. The caller has to take into account
+ * the fact that the started scroll will start from an overscrolled position.
+ *
+ * @return true when the current position is overscrolled and in the process of
+ * interpolating back to a valid value.
+ */
+ public boolean isOverScrolled() {
+ return ((!mScrollerX.mFinished &&
+ mScrollerX.mState != SplineOverScroller.SPLINE) ||
+ (!mScrollerY.mFinished &&
+ mScrollerY.mState != SplineOverScroller.SPLINE));
+ }
+
+ /**
+ * Stops the animation. Contrary to {@link #forceFinished(boolean)},
+ * aborting the animating causes the scroller to move to the final x and y
+ * positions.
+ *
+ * @see #forceFinished(boolean)
+ */
+ public void abortAnimation() {
+ mScrollerX.finish();
+ mScrollerY.finish();
+ }
+
+ /**
+ * Returns the time elapsed since the beginning of the scrolling.
+ *
+ * @return The elapsed time in milliseconds.
+ *
+ * @hide
+ */
+ public int timePassed() {
+ final long time = AnimationUtils.currentAnimationTimeMillis();
+ final long startTime = Math.min(mScrollerX.mStartTime, mScrollerY.mStartTime);
+ return (int) (time - startTime);
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isScrollingInDirection(float xvel, float yvel) {
+ final int dx = mScrollerX.mFinal - mScrollerX.mStart;
+ final int dy = mScrollerY.mFinal - mScrollerY.mStart;
+ return !isFinished() && Math.signum(xvel) == Math.signum(dx) &&
+ Math.signum(yvel) == Math.signum(dy);
+ }
+
+ static class SplineOverScroller {
+ // Initial position
+ private int mStart;
+
+ // Current position
+ private int mCurrentPosition;
+
+ // Final position
+ private int mFinal;
+
+ // Initial velocity
+ private int mVelocity;
+
+ // Current velocity
+ private float mCurrVelocity;
+
+ // Constant current deceleration
+ private float mDeceleration;
+
+ // Animation starting time, in system milliseconds
+ private long mStartTime;
+
+ // Animation duration, in milliseconds
+ private int mDuration;
+
+ // Duration to complete spline component of animation
+ private int mSplineDuration;
+
+ // Distance to travel along spline animation
+ private int mSplineDistance;
+
+ // Whether the animation is currently in progress
+ private boolean mFinished;
+
+ // The allowed overshot distance before boundary is reached.
+ private int mOver;
+
+ // Fling friction
+ private float mFlingFriction = ViewConfiguration.getScrollFriction();
+
+ // Current state of the animation.
+ private int mState = SPLINE;
+
+ // Constant gravity value, used in the deceleration phase.
+ private static final float GRAVITY = 2000.0f;
+
+ // A context-specific coefficient adjusted to physical values.
+ private float mPhysicalCoeff;
+
+ private static float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
+ private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1)
+ private static final float START_TENSION = 0.5f;
+ private static final float END_TENSION = 1.0f;
+ private static final float P1 = START_TENSION * INFLEXION;
+ private static final float P2 = 1.0f - END_TENSION * (1.0f - INFLEXION);
+
+ private static final int NB_SAMPLES = 100;
+ private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1];
+ private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1];
+
+ private static final int SPLINE = 0;
+ private static final int CUBIC = 1;
+ private static final int BALLISTIC = 2;
+
+ static {
+ float x_min = 0.0f;
+ float y_min = 0.0f;
+ for (int i = 0; i < NB_SAMPLES; i++) {
+ final float alpha = (float) i / NB_SAMPLES;
+
+ float x_max = 1.0f;
+ float x, tx, coef;
+ while (true) {
+ x = x_min + (x_max - x_min) / 2.0f;
+ coef = 3.0f * x * (1.0f - x);
+ tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x;
+ if (Math.abs(tx - alpha) < 1E-5) break;
+ if (tx > alpha) x_max = x;
+ else x_min = x;
+ }
+ SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x;
+
+ float y_max = 1.0f;
+ float y, dy;
+ while (true) {
+ y = y_min + (y_max - y_min) / 2.0f;
+ coef = 3.0f * y * (1.0f - y);
+ dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y;
+ if (Math.abs(dy - alpha) < 1E-5) break;
+ if (dy > alpha) y_max = y;
+ else y_min = y;
+ }
+ SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y;
+ }
+ SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
+ }
+
+ void setFriction(float friction) {
+ mFlingFriction = friction;
+ }
+
+ SplineOverScroller(Context context) {
+ mFinished = true;
+ final float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
+ mPhysicalCoeff = SensorManager.GRAVITY_EARTH // g (m/s^2)
+ * 39.37f // inch/meter
+ * ppi
+ * 0.84f; // look and feel tuning
+ }
+
+ void updateScroll(float q) {
+ mCurrentPosition = mStart + Math.round(q * (mFinal - mStart));
+ }
+
+ /*
+ * Get a signed deceleration that will reduce the velocity.
+ */
+ static private float getDeceleration(int velocity) {
+ return velocity > 0 ? -GRAVITY : GRAVITY;
+ }
+
+ /*
+ * Modifies mDuration to the duration it takes to get from start to newFinal using the
+ * spline interpolation. The previous duration was needed to get to oldFinal.
+ */
+ private void adjustDuration(int start, int oldFinal, int newFinal) {
+ final int oldDistance = oldFinal - start;
+ final int newDistance = newFinal - start;
+ final float x = Math.abs((float) newDistance / oldDistance);
+ final int index = (int) (NB_SAMPLES * x);
+ if (index < NB_SAMPLES) {
+ final float x_inf = (float) index / NB_SAMPLES;
+ final float x_sup = (float) (index + 1) / NB_SAMPLES;
+ final float t_inf = SPLINE_TIME[index];
+ final float t_sup = SPLINE_TIME[index + 1];
+ final float timeCoef = t_inf + (x - x_inf) / (x_sup - x_inf) * (t_sup - t_inf);
+ mDuration *= timeCoef;
+ }
+ }
+
+ void startScroll(int start, int distance, int duration) {
+ mFinished = false;
+
+ mCurrentPosition = mStart = start;
+ mFinal = start + distance;
+
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mDuration = duration;
+
+ // Unused
+ mDeceleration = 0.0f;
+ mVelocity = 0;
+ }
+
+ void finish() {
+ mCurrentPosition = mFinal;
+ // Not reset since WebView relies on this value for fast fling.
+ // TODO: restore when WebView uses the fast fling implemented in this class.
+ // mCurrVelocity = 0.0f;
+ mFinished = true;
+ }
+
+ void setFinalPosition(int position) {
+ mFinal = position;
+ mFinished = false;
+ }
+
+ void extendDuration(int extend) {
+ final long time = AnimationUtils.currentAnimationTimeMillis();
+ final int elapsedTime = (int) (time - mStartTime);
+ mDuration = elapsedTime + extend;
+ mFinished = false;
+ }
+
+ boolean springback(int start, int min, int max) {
+ mFinished = true;
+
+ mCurrentPosition = mStart = mFinal = start;
+ mVelocity = 0;
+
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mDuration = 0;
+
+ if (start < min) {
+ startSpringback(start, min, 0);
+ } else if (start > max) {
+ startSpringback(start, max, 0);
+ }
+
+ return !mFinished;
+ }
+
+ private void startSpringback(int start, int end, int velocity) {
+ // mStartTime has been set
+ mFinished = false;
+ mState = CUBIC;
+ mCurrentPosition = mStart = start;
+ mFinal = end;
+ final int delta = start - end;
+ mDeceleration = getDeceleration(delta);
+ // TODO take velocity into account
+ mVelocity = -delta; // only sign is used
+ mOver = Math.abs(delta);
+ mDuration = (int) (1000.0 * Math.sqrt(-2.0 * delta / mDeceleration));
+ }
+
+ void fling(int start, int velocity, int min, int max, int over) {
+ mOver = over;
+ mFinished = false;
+ mCurrVelocity = mVelocity = velocity;
+ mDuration = mSplineDuration = 0;
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mCurrentPosition = mStart = start;
+
+ if (start > max || start < min) {
+ startAfterEdge(start, min, max, velocity);
+ return;
+ }
+
+ mState = SPLINE;
+ double totalDistance = 0.0;
+
+ if (velocity != 0) {
+ mDuration = mSplineDuration = getSplineFlingDuration(velocity);
+ totalDistance = getSplineFlingDistance(velocity);
+ }
+
+ mSplineDistance = (int) (totalDistance * Math.signum(velocity));
+ mFinal = start + mSplineDistance;
+
+ // Clamp to a valid final position
+ if (mFinal < min) {
+ adjustDuration(mStart, mFinal, min);
+ mFinal = min;
+ }
+
+ if (mFinal > max) {
+ adjustDuration(mStart, mFinal, max);
+ mFinal = max;
+ }
+ }
+
+ private double getSplineDeceleration(int velocity) {
+ return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * mPhysicalCoeff));
+ }
+
+ private double getSplineFlingDistance(int velocity) {
+ final double l = getSplineDeceleration(velocity);
+ final double decelMinusOne = DECELERATION_RATE - 1.0;
+ return mFlingFriction * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l);
+ }
+
+ /* Returns the duration, expressed in milliseconds */
+ private int getSplineFlingDuration(int velocity) {
+ final double l = getSplineDeceleration(velocity);
+ final double decelMinusOne = DECELERATION_RATE - 1.0;
+ return (int) (1000.0 * Math.exp(l / decelMinusOne));
+ }
+
+ private void fitOnBounceCurve(int start, int end, int velocity) {
+ // Simulate a bounce that started from edge
+ final float durationToApex = - velocity / mDeceleration;
+ // The float cast below is necessary to avoid integer overflow.
+ final float velocitySquared = (float) velocity * velocity;
+ final float distanceToApex = velocitySquared / 2.0f / Math.abs(mDeceleration);
+ final float distanceToEdge = Math.abs(end - start);
+ final float totalDuration = (float) Math.sqrt(
+ 2.0 * (distanceToApex + distanceToEdge) / Math.abs(mDeceleration));
+ mStartTime -= (int) (1000.0f * (totalDuration - durationToApex));
+ mCurrentPosition = mStart = end;
+ mVelocity = (int) (- mDeceleration * totalDuration);
+ }
+
+ private void startBounceAfterEdge(int start, int end, int velocity) {
+ mDeceleration = getDeceleration(velocity == 0 ? start - end : velocity);
+ fitOnBounceCurve(start, end, velocity);
+ onEdgeReached();
+ }
+
+ private void startAfterEdge(int start, int min, int max, int velocity) {
+ if (start > min && start < max) {
+ Log.e("OverScroller", "startAfterEdge called from a valid position");
+ mFinished = true;
+ return;
+ }
+ final boolean positive = start > max;
+ final int edge = positive ? max : min;
+ final int overDistance = start - edge;
+ boolean keepIncreasing = overDistance * velocity >= 0;
+ if (keepIncreasing) {
+ // Will result in a bounce or a to_boundary depending on velocity.
+ startBounceAfterEdge(start, edge, velocity);
+ } else {
+ final double totalDistance = getSplineFlingDistance(velocity);
+ if (totalDistance > Math.abs(overDistance)) {
+ fling(start, velocity, positive ? min : start, positive ? start : max, mOver);
+ } else {
+ startSpringback(start, edge, velocity);
+ }
+ }
+ }
+
+ void notifyEdgeReached(int start, int end, int over) {
+ // mState is used to detect successive notifications
+ if (mState == SPLINE) {
+ mOver = over;
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ // We were in fling/scroll mode before: current velocity is such that distance to
+ // edge is increasing. This ensures that startAfterEdge will not start a new fling.
+ startAfterEdge(start, end, end, (int) mCurrVelocity);
+ }
+ }
+
+ private void onEdgeReached() {
+ // mStart, mVelocity and mStartTime were adjusted to their values when edge was reached.
+ // The float cast below is necessary to avoid integer overflow.
+ final float velocitySquared = (float) mVelocity * mVelocity;
+ float distance = velocitySquared / (2.0f * Math.abs(mDeceleration));
+ final float sign = Math.signum(mVelocity);
+
+ if (distance > mOver) {
+ // Default deceleration is not sufficient to slow us down before boundary
+ mDeceleration = - sign * velocitySquared / (2.0f * mOver);
+ distance = mOver;
+ }
+
+ mOver = (int) distance;
+ mState = BALLISTIC;
+ mFinal = mStart + (int) (mVelocity > 0 ? distance : -distance);
+ mDuration = - (int) (1000.0f * mVelocity / mDeceleration);
+ }
+
+ boolean continueWhenFinished() {
+ switch (mState) {
+ case SPLINE:
+ // Duration from start to null velocity
+ if (mDuration < mSplineDuration) {
+ // If the animation was clamped, we reached the edge
+ mCurrentPosition = mStart = mFinal;
+ // TODO Better compute speed when edge was reached
+ mVelocity = (int) mCurrVelocity;
+ mDeceleration = getDeceleration(mVelocity);
+ mStartTime += mDuration;
+ onEdgeReached();
+ } else {
+ // Normal stop, no need to continue
+ return false;
+ }
+ break;
+ case BALLISTIC:
+ mStartTime += mDuration;
+ startSpringback(mFinal, mStart, 0);
+ break;
+ case CUBIC:
+ return false;
+ }
+
+ update();
+ return true;
+ }
+
+ /*
+ * Update the current position and velocity for current time. Returns
+ * true if update has been done and false if animation duration has been
+ * reached.
+ */
+ boolean update() {
+ final long time = AnimationUtils.currentAnimationTimeMillis();
+ final long currentTime = time - mStartTime;
+
+ if (currentTime == 0) {
+ // Skip work but report that we're still going if we have a nonzero duration.
+ return mDuration > 0;
+ }
+ if (currentTime > mDuration) {
+ return false;
+ }
+
+ double distance = 0.0;
+ switch (mState) {
+ case SPLINE: {
+ final float t = (float) currentTime / mSplineDuration;
+ final int index = (int) (NB_SAMPLES * t);
+ float distanceCoef = 1.f;
+ float velocityCoef = 0.f;
+ if (index < NB_SAMPLES) {
+ final float t_inf = (float) index / NB_SAMPLES;
+ final float t_sup = (float) (index + 1) / NB_SAMPLES;
+ final float d_inf = SPLINE_POSITION[index];
+ final float d_sup = SPLINE_POSITION[index + 1];
+ velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
+ distanceCoef = d_inf + (t - t_inf) * velocityCoef;
+ }
+
+ distance = distanceCoef * mSplineDistance;
+ mCurrVelocity = velocityCoef * mSplineDistance / mSplineDuration * 1000.0f;
+ break;
+ }
+
+ case BALLISTIC: {
+ final float t = currentTime / 1000.0f;
+ mCurrVelocity = mVelocity + mDeceleration * t;
+ distance = mVelocity * t + mDeceleration * t * t / 2.0f;
+ break;
+ }
+
+ case CUBIC: {
+ final float t = (float) (currentTime) / mDuration;
+ final float t2 = t * t;
+ final float sign = Math.signum(mVelocity);
+ distance = sign * mOver * (3.0f * t2 - 2.0f * t * t2);
+ mCurrVelocity = sign * mOver * 6.0f * (- t + t2);
+ break;
+ }
+ }
+
+ mCurrentPosition = mStart + (int) Math.round(distance);
+
+ return true;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index c8d14572e..59bea48c8 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -30,7 +30,6 @@ import android.view.animation.Interpolator;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.touch.SwipeDetector;
@@ -76,7 +75,7 @@ public abstract class AbstractSlideInView extends AbstractFloatingView
mScrollInterpolator = Interpolators.SCROLL_CUBIC;
mSwipeDetector = new SwipeDetector(context, this, SwipeDetector.VERTICAL);
- mOpenCloseAnimator = LauncherAnimUtils.ofPropertyValuesHolder(this);
+ mOpenCloseAnimator = ObjectAnimator.ofPropertyValuesHolder(this);
mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -103,7 +102,7 @@ public abstract class AbstractSlideInView extends AbstractFloatingView
directionsToDetectScroll, false);
mSwipeDetector.onTouchEvent(ev);
return mSwipeDetector.isDraggingOrSettling()
- || !mLauncher.getDragLayer().isEventOverView(mContent, ev);
+ || !getPopupContainer().isEventOverView(mContent, ev);
}
@Override
@@ -111,7 +110,7 @@ public abstract class AbstractSlideInView extends AbstractFloatingView
mSwipeDetector.onTouchEvent(ev);
if (ev.getAction() == MotionEvent.ACTION_UP && mSwipeDetector.isIdleState()) {
// If we got ACTION_UP without ever starting swipe, close the panel.
- if (!mLauncher.getDragLayer().isEventOverView(mContent, ev)) {
+ if (!getPopupContainer().isEventOverView(mContent, ev)) {
close(true);
}
}
@@ -178,6 +177,10 @@ public abstract class AbstractSlideInView extends AbstractFloatingView
protected void onCloseComplete() {
mIsOpen = false;
- mLauncher.getDragLayer().removeView(this);
+ getPopupContainer().removeView(this);
+ }
+
+ protected BaseDragLayer getPopupContainer() {
+ return mLauncher.getDragLayer();
}
}
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 504663936..dc6d2ff09 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -22,6 +22,7 @@ import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.AttributeSet;
@@ -133,6 +134,11 @@ public class OptionsPopupView extends ArrowPopup
popup.reorderAndShow(popup.getChildCount());
}
+ @VisibleForTesting
+ public static OptionsPopupView getOptionsPopup(Launcher launcher) {
+ return launcher.findViewById(R.id.deep_shortcuts_container);
+ }
+
public static void showDefaultOptions(Launcher launcher, float x, float y) {
float halfSize = launcher.getResources().getDimension(R.dimen.options_menu_thumb_size) / 2;
if (x < 0 || y < 0) {
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index 05bab8b45..ed1cf4f1e 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -224,8 +224,7 @@ public class RecyclerViewFastScroller extends View {
}
if (isNearThumb(x, y)) {
mTouchOffsetY = mDownY - mThumbOffsetY;
- } else if (FeatureFlags.LAUNCHER3_DIRECT_SCROLL
- && mRv.supportsFastScrolling()
+ } else if (mRv.supportsFastScrolling()
&& isNearScrollBar(mDownX)) {
calcTouchOffsetAndPrepToFastScroll(mDownY, mLastY);
updateFastScrollSectionNameAndThumbOffset(mLastY, y);
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 10708d600..20c8876d2 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -71,7 +71,7 @@ abstract class BaseWidgetSheet extends AbstractSlideInView
}
@Override
- public final boolean onLongClick(View v) {
+ public boolean onLongClick(View v) {
if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
if (v instanceof WidgetCell) {
@@ -96,7 +96,7 @@ abstract class BaseWidgetSheet extends AbstractSlideInView
}
int[] loc = new int[2];
- mLauncher.getDragLayer().getLocationInDragLayer(image, loc);
+ getPopupContainer().getLocationInDragLayer(image, loc);
new PendingItemDragHelper(v).startDrag(
image.getBitmapBounds(), image.getBitmap().getWidth(), image.getWidth(),
@@ -119,13 +119,13 @@ abstract class BaseWidgetSheet extends AbstractSlideInView
}
protected void clearNavBarColor() {
- mLauncher.getSystemUiController().updateUiState(
+ getSystemUiController().updateUiState(
SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET, 0);
}
protected void setupNavBarColor() {
- boolean isSheetDark = Themes.getAttrBoolean(mLauncher, R.attr.isMainColorDark);
- mLauncher.getSystemUiController().updateUiState(
+ boolean isSheetDark = Themes.getAttrBoolean(getContext(), R.attr.isMainColorDark);
+ getSystemUiController().updateUiState(
SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET,
isSheetDark ? SystemUiController.FLAG_DARK_NAV : SystemUiController.FLAG_LIGHT_NAV);
}
@@ -145,4 +145,7 @@ abstract class BaseWidgetSheet extends AbstractSlideInView
protected abstract int getElementsRowCount();
+ protected SystemUiController getSystemUiController() {
+ return mLauncher.getSystemUiController();
+ }
}
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index 5ce7e0453..4ba6b5b12 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -71,7 +71,7 @@ public class WidgetsBottomSheet extends BaseWidgetSheet implements Insettable {
onWidgetsBound();
- mLauncher.getDragLayer().addView(this);
+ getPopupContainer().addView(this);
mIsOpen = false;
animateOpen();
}
@@ -118,7 +118,7 @@ public class WidgetsBottomSheet extends BaseWidgetSheet implements Insettable {
LayoutInflater.from(getContext()).inflate(R.layout.widget_list_divider, parent, true);
}
- private WidgetCell addItemCell(ViewGroup parent) {
+ protected WidgetCell addItemCell(ViewGroup parent) {
WidgetCell widget = (WidgetCell) LayoutInflater.from(getContext()).inflate(
R.layout.widget_cell, parent, false);
diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java
index e94d81d75..2a7bb03aa 100644
--- a/src/com/android/launcher3/widget/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java
@@ -20,6 +20,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.graphics.Rect;
+import android.support.annotation.VisibleForTesting;
import android.util.AttributeSet;
import android.util.Pair;
import android.view.LayoutInflater;
@@ -159,7 +160,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet
private void open(boolean animate) {
if (animate) {
- if (mLauncher.getDragLayer().getInsets().bottom > 0) {
+ if (getPopupContainer().getInsets().bottom > 0) {
mContent.setAlpha(0);
setTranslationShift(VERTICAL_START_POSITION);
}
@@ -206,10 +207,10 @@ public class WidgetsFullSheet extends BaseWidgetSheet
mNoIntercept = false;
RecyclerViewFastScroller scroller = mRecyclerView.getScrollbar();
if (scroller.getThumbOffsetY() >= 0 &&
- mLauncher.getDragLayer().isEventOverView(scroller, ev)) {
+ getPopupContainer().isEventOverView(scroller, ev)) {
mNoIntercept = true;
- } else if (mLauncher.getDragLayer().isEventOverView(mContent, ev)) {
- mNoIntercept = !mRecyclerView.shouldContainerScroll(ev, mLauncher.getDragLayer());
+ } else if (getPopupContainer().isEventOverView(mContent, ev)) {
+ mNoIntercept = !mRecyclerView.shouldContainerScroll(ev, getPopupContainer());
}
}
return super.onControllerInterceptTouchEvent(ev);
@@ -219,11 +220,16 @@ public class WidgetsFullSheet extends BaseWidgetSheet
WidgetsFullSheet sheet = (WidgetsFullSheet) launcher.getLayoutInflater()
.inflate(R.layout.widgets_full_sheet, launcher.getDragLayer(), false);
sheet.mIsOpen = true;
- launcher.getDragLayer().addView(sheet);
+ sheet.getPopupContainer().addView(sheet);
sheet.open(animate);
return sheet;
}
+ @VisibleForTesting
+ public static WidgetsRecyclerView getWidgetsView(Launcher launcher) {
+ return launcher.findViewById(R.id.widgets_list_view);
+ }
+
@Override
protected int getElementsRowCount() {
return mAdapter.getItemCount();
diff --git a/tests/Android.mk b/tests/Android.mk
index f6f02feab..c7a222a96 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -30,3 +30,17 @@ LOCAL_PACKAGE_NAME := Launcher3Tests
LOCAL_INSTRUMENTATION_FOR := Launcher3
include $(BUILD_PACKAGE)
+
+#
+# Build rule for Tapl library.
+#
+include $(CLEAR_VARS)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator
+
+LOCAL_SRC_FILES := $(call all-java-files-under, tapl) \
+ ../quickstep/src/com/android/quickstep/SwipeUpSetting.java
+
+LOCAL_SDK_VERSION := current
+LOCAL_MODULE := ub-launcher-aosp-tapl
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java b/tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
index b92f61205..031909fe1 100644
--- a/tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
+++ b/tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
@@ -16,7 +16,6 @@ import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.GridSizeMigrationTask.MultiStepMigrationTask;
import com.android.launcher3.util.TestLauncherProvider;
@@ -87,13 +86,8 @@ public class GridSizeMigrationTaskTest {
mIdp.numHotseatIcons = 3;
new GridSizeMigrationTask(mContext, mIdp, mValidPackages, 5, 3)
.migrateHotseat();
- if (FeatureFlags.NO_ALL_APPS_ICON) {
- // First item is dropped as it has the least weight.
- verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
- } else {
- // First & last items are dropped as they have the least weight.
- verifyHotseat(hotseatItems[1], -1, hotseatItems[3]);
- }
+ // First item is dropped as it has the least weight.
+ verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
}
@Test
@@ -109,13 +103,8 @@ public class GridSizeMigrationTaskTest {
mIdp.numHotseatIcons = 3;
new GridSizeMigrationTask(mContext, mIdp, mValidPackages, 5, 3)
.migrateHotseat();
- if (FeatureFlags.NO_ALL_APPS_ICON) {
- // First item is dropped as it has the least weight.
- verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
- } else {
- // First & third items are dropped as they have the least weight.
- verifyHotseat(hotseatItems[1], -1, hotseatItems[4]);
- }
+ // First item is dropped as it has the least weight.
+ verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
}
private void verifyHotseat(long... sortedIds) {
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index f16f514cd..dd91fe86d 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -43,13 +43,11 @@ import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.R;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.testcomponent.AppWidgetNoConfig;
import com.android.launcher3.testcomponent.AppWidgetWithConfig;
import org.junit.Before;
-import java.util.Locale;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -101,19 +99,12 @@ public abstract class AbstractLauncherUiTest {
*/
protected UiObject2 openAllApps() {
mDevice.waitForIdle();
- if (FeatureFlags.NO_ALL_APPS_ICON) {
- UiObject2 hotseat = mDevice.wait(
- Until.findObject(getSelectorForId(R.id.hotseat)), 2500);
- Point start = hotseat.getVisibleCenter();
- int endY = (int) (mDevice.getDisplayHeight() * 0.1f);
- // 100 px/step
- mDevice.swipe(start.x, start.y, start.x, endY, (start.y - endY) / 100);
-
- } else {
- mDevice.wait(Until.findObject(
- By.desc(mTargetContext.getString(R.string.all_apps_button_label))),
- DEFAULT_UI_TIMEOUT).click();
- }
+ UiObject2 hotseat = mDevice.wait(
+ Until.findObject(getSelectorForId(R.id.hotseat)), 2500);
+ Point start = hotseat.getVisibleCenter();
+ int endY = (int) (mDevice.getDisplayHeight() * 0.1f);
+ // 100 px/step
+ mDevice.swipe(start.x, start.y, start.x, endY, (start.y - endY) / 100);
return findViewById(R.id.apps_list_view);
}
diff --git a/tests/tapl/com/android/launcher3/tapl/AllAppsFromHome.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 02f8183c8..e270b4605 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllAppsFromHome.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -16,36 +16,32 @@
package com.android.launcher3.tapl;
+import static org.junit.Assert.assertTrue;
+
import android.support.annotation.NonNull;
import android.support.test.uiautomator.BySelector;
import android.support.test.uiautomator.Direction;
import android.support.test.uiautomator.UiObject2;
/**
- * Operations on AllApps opened from Home.
+ * Operations on AllApps opened from Home. Also a parent for All Apps opened from Overview.
*/
-public final class AllAppsFromHome {
+public class AllApps extends LauncherInstrumentation.VisibleContainer {
private static final int MAX_SCROLL_ATTEMPTS = 40;
private static final int MIN_INTERACT_SIZE = 100;
private static final int FLING_SPEED = 12000;
- private final Launcher mLauncher;
private final int mHeight;
- AllAppsFromHome(Launcher launcher) {
- mLauncher = launcher;
- final UiObject2 allAppsContainer = assertState();
+ AllApps(LauncherInstrumentation launcher) {
+ super(launcher);
+ final UiObject2 allAppsContainer = verifyActiveContainer();
mHeight = allAppsContainer.getVisibleBounds().height();
}
- /**
- * Asserts that we are in all apps.
- *
- * @return All apps container.
- */
- @NonNull
- private UiObject2 assertState() {
- return mLauncher.assertState(Launcher.State.ALL_APPS);
+ @Override
+ protected LauncherInstrumentation.ContainerType getContainerType() {
+ return LauncherInstrumentation.ContainerType.ALL_APPS;
}
/**
@@ -57,19 +53,19 @@ public final class AllAppsFromHome {
*/
@NonNull
public AppIcon getAppIcon(String appName) {
- final UiObject2 allAppsContainer = assertState();
+ final UiObject2 allAppsContainer = verifyActiveContainer();
final BySelector appIconSelector = AppIcon.getAppIconSelector(appName);
if (!allAppsContainer.hasObject(appIconSelector)) {
scrollBackToBeginning();
int attempts = 0;
while (!allAppsContainer.hasObject(appIconSelector) &&
allAppsContainer.scroll(Direction.DOWN, 0.8f)) {
- mLauncher.assertTrue("Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
+ assertTrue("Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
++attempts <= MAX_SCROLL_ATTEMPTS);
- assertState();
+ verifyActiveContainer();
}
}
- assertState();
+ verifyActiveContainer();
final UiObject2 appIcon = mLauncher.getObjectInContainer(allAppsContainer, appIconSelector);
ensureIconVisible(appIcon, allAppsContainer);
@@ -77,21 +73,30 @@ public final class AllAppsFromHome {
}
private void scrollBackToBeginning() {
- final UiObject2 allAppsContainer = assertState();
+ final UiObject2 allAppsContainer = verifyActiveContainer();
+ final UiObject2 searchBox =
+ mLauncher.waitForObjectInContainer(allAppsContainer, "search_container_all_apps");
int attempts = 0;
- allAppsContainer.setGestureMargins(5, 500, 5, 5);
+ allAppsContainer.setGestureMargins(0, searchBox.getVisibleBounds().bottom + 1, 0, 5);
- while (allAppsContainer.scroll(Direction.UP, 0.5f)) {
- mLauncher.waitForIdle();
- assertState();
+ for (int scroll = getScroll(allAppsContainer);
+ scroll != 0;
+ scroll = getScroll(allAppsContainer)) {
+ assertTrue("Negative scroll position", scroll > 0);
- mLauncher.assertTrue("Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
+ assertTrue("Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
++attempts <= MAX_SCROLL_ATTEMPTS);
+
+ allAppsContainer.scroll(Direction.UP, 1);
}
- mLauncher.waitForIdle();
- assertState();
+ verifyActiveContainer();
+ }
+
+ private int getScroll(UiObject2 allAppsContainer) {
+ return mLauncher.getAnswerFromLauncher(allAppsContainer, "TAPL_GET_SCROLL").
+ getInt("scrollY", -1);
}
private void ensureIconVisible(UiObject2 appIcon, UiObject2 allAppsContainer) {
@@ -102,7 +107,7 @@ public final class AllAppsFromHome {
final float pct = Math.max(((float) (MIN_INTERACT_SIZE - appHeight)) / mHeight, 0.2f);
allAppsContainer.scroll(Direction.DOWN, pct);
mLauncher.waitForIdle();
- assertState();
+ verifyActiveContainer();
}
}
@@ -110,22 +115,22 @@ public final class AllAppsFromHome {
* Flings forward (down) and waits the fling's end.
*/
public void flingForward() {
- final UiObject2 allAppsContainer = assertState();
+ final UiObject2 allAppsContainer = verifyActiveContainer();
// Start the gesture in the center to avoid starting at elements near the top.
allAppsContainer.setGestureMargins(0, 0, 0, mHeight / 2);
allAppsContainer.fling(Direction.DOWN, FLING_SPEED);
- assertState();
+ verifyActiveContainer();
}
/**
* Flings backward (up) and waits the fling's end.
*/
public void flingBackward() {
- final UiObject2 allAppsContainer = assertState();
+ final UiObject2 allAppsContainer = verifyActiveContainer();
// Start the gesture in the center, for symmetry with forward.
allAppsContainer.setGestureMargins(0, mHeight / 2, 0, 0);
allAppsContainer.fling(Direction.UP, FLING_SPEED);
- assertState();
+ verifyActiveContainer();
}
/**
@@ -137,6 +142,6 @@ public final class AllAppsFromHome {
@Deprecated
@NonNull
public UiObject2 getObjectDeprecated() {
- return assertState();
+ return verifyActiveContainer();
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java b/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java
index cba708621..7ed2dc2f6 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java
@@ -22,24 +22,12 @@ import android.support.test.uiautomator.UiObject2;
/**
* Operations on AllApps opened from Overview.
- * Scroll gestures that are OK for {@link AllAppsFromHome} may close it, so they are not supported.
*/
-public final class AllAppsFromOverview {
- private final Launcher mLauncher;
+public final class AllAppsFromOverview extends AllApps {
- AllAppsFromOverview(Launcher launcher) {
- mLauncher = launcher;
- assertState();
- }
-
- /**
- * Asserts that we are in all apps.
- *
- * @return All apps container.
- */
- @NonNull
- private UiObject2 assertState() {
- return mLauncher.assertState(Launcher.State.ALL_APPS);
+ AllAppsFromOverview(LauncherInstrumentation launcher) {
+ super(launcher);
+ verifyActiveContainer();
}
/**
@@ -49,13 +37,13 @@ public final class AllAppsFromOverview {
*/
@NonNull
public Overview switchBackToOverview() {
- final UiObject2 allAppsContainer = assertState();
+ final UiObject2 allAppsContainer = verifyActiveContainer();
// Swipe from the search box to the bottom.
final UiObject2 qsb = mLauncher.waitForObjectInContainer(
allAppsContainer, "search_container_all_apps");
final Point start = qsb.getVisibleCenter();
final int endY = (int) (mLauncher.getDevice().getDisplayHeight() * 0.6);
- mLauncher.swipe(start.x, start.y, start.x, endY, (endY - start.y) / 100); // 100 px/step
+ mLauncher.swipe(start.x, start.y, start.x, endY);
return new Overview(mLauncher);
}
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index 73a74f2dd..721f7a8ef 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -16,6 +16,8 @@
package com.android.launcher3.tapl;
+import static org.junit.Assert.assertTrue;
+
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.BySelector;
import android.support.test.uiautomator.UiObject2;
@@ -26,25 +28,25 @@ import android.widget.TextView;
* App icon, whether in all apps or in workspace/
*/
public final class AppIcon {
- private final Launcher mLauncher;
+ private final LauncherInstrumentation mLauncher;
private final UiObject2 mIcon;
- AppIcon(Launcher launcher, UiObject2 icon) {
+ AppIcon(LauncherInstrumentation launcher, UiObject2 icon) {
mLauncher = launcher;
mIcon = icon;
}
static BySelector getAppIconSelector(String appName) {
- return By.clazz(TextView.class).text(appName).pkg(Launcher.LAUNCHER_PKG);
+ return By.clazz(TextView.class).text(appName).pkg(LauncherInstrumentation.LAUNCHER_PKG);
}
/**
* Clicks the icon to launch its app.
*/
- public void launch() {
- mLauncher.assertTrue("Launching an app didn't open a new window: " + mIcon.getText(),
- mIcon.clickAndWait(Until.newWindow(), Launcher.APP_LAUNCH_TIMEOUT_MS));
- mLauncher.assertState(Launcher.State.BACKGROUND);
+ public Background launch() {
+ assertTrue("Launching an app didn't open a new window: " + mIcon.getText(),
+ mIcon.clickAndWait(Until.newWindow(), LauncherInstrumentation.WAIT_TIME_MS));
+ return new Background(mLauncher);
}
UiObject2 getIcon() {
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
new file mode 100644
index 000000000..1aef9791d
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.tapl;
+
+/**
+ * Operations on a state when Launcher is inactive because some other app is active.
+ */
+public final class Background extends Home {
+
+ Background(LauncherInstrumentation launcher) {
+ super(launcher);
+ }
+
+ @Override
+ protected LauncherInstrumentation.ContainerType getContainerType() {
+ return LauncherInstrumentation.ContainerType.BACKGROUND;
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Home.java b/tests/tapl/com/android/launcher3/tapl/Home.java
index 0ec1a644e..436e5ff6b 100644
--- a/tests/tapl/com/android/launcher3/tapl/Home.java
+++ b/tests/tapl/com/android/launcher3/tapl/Home.java
@@ -16,38 +16,22 @@
package com.android.launcher3.tapl;
-import static junit.framework.TestCase.assertTrue;
-
-import android.graphics.Point;
import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.test.uiautomator.Direction;
import android.support.test.uiautomator.UiObject2;
-import android.view.KeyEvent;
/**
* Operations on the home screen.
+ *
+ * Launcher can be invoked both when its activity is in the foreground and when it is in the
+ * background. This class is a parent of the two classes {@link Background} and {@link Workspace}
+ * that essentially represents these two activity states. Any gestures (e.g., switchToOverview) that
+ * can be performed in both of these states can be defined here.
*/
-public final class Home {
+public abstract class Home extends LauncherInstrumentation.VisibleContainer {
- private final Launcher mLauncher;
- private final UiObject2 mHotseat;
- private final int ICON_DRAG_SPEED = 2000;
-
- Home(Launcher launcher) {
- mLauncher = launcher;
- assertState();
- mHotseat = launcher.waitForLauncherObject("hotseat");
- }
-
- /**
- * Asserts that we are in home.
- *
- * @return Workspace.
- */
- @NonNull
- private UiObject2 assertState() {
- return mLauncher.assertState(Launcher.State.HOME);
+ protected Home(LauncherInstrumentation launcher) {
+ super(launcher);
+ verifyActiveContainer();
}
/**
@@ -57,131 +41,19 @@ public final class Home {
*/
@NonNull
public Overview switchToOverview() {
- assertState();
+ verifyActiveContainer();
if (mLauncher.isSwipeUpEnabled()) {
final int height = mLauncher.getDevice().getDisplayHeight();
final UiObject2 navBar = mLauncher.getSystemUiObject("navigation_bar_frame");
- // Swipe from nav bar to 2/3rd down the screen.
mLauncher.swipe(
navBar.getVisibleBounds().centerX(), navBar.getVisibleBounds().centerY(),
- navBar.getVisibleBounds().centerX(), height * 2 / 3,
- (navBar.getVisibleBounds().centerY() - height * 2 / 3) / 100); // 100 px/step
+ navBar.getVisibleBounds().centerX(), height - 300
+ );
} else {
mLauncher.getSystemUiObject("recent_apps").click();
}
return new Overview(mLauncher);
}
-
- /**
- * Swipes up to All Apps.
- *
- * @return the App Apps object.
- */
- @NonNull
- public AllAppsFromHome switchToAllApps() {
- assertState();
- if (mLauncher.isSwipeUpEnabled()) {
- int midX = mLauncher.getDevice().getDisplayWidth() / 2;
- int height = mLauncher.getDevice().getDisplayHeight();
- // Swipe from 6/7ths down the screen to 1/7th down the screen.
- mLauncher.swipe(
- midX,
- height * 6 / 7,
- midX,
- height / 7,
- (height * 2 / 3) / 100); // 100 px/step
- } else {
- // Swipe from the hotseat to near the top, e.g. 10% of the screen.
- final UiObject2 hotseat = mHotseat;
- final Point start = hotseat.getVisibleCenter();
- final int endY = (int) (mLauncher.getDevice().getDisplayHeight() * 0.1f);
- mLauncher.swipe(
- start.x,
- start.y,
- start.x,
- endY,
- (start.y - endY) / 100); // 100 px/step
- }
-
- return new AllAppsFromHome(mLauncher);
- }
-
- /**
- * Returns an icon for the app, if currently visible.
- *
- * @param appName name of the app
- * @return app icon, if found, null otherwise.
- */
- @Nullable
- public AppIcon tryGetWorkspaceAppIcon(String appName) {
- final UiObject2 workspace = assertState();
- final UiObject2 icon = workspace.findObject(AppIcon.getAppIconSelector(appName));
- return icon != null ? new AppIcon(mLauncher, icon) : null;
- }
-
- /**
- * Ensures that workspace is scrollable. If it's not, drags an icon icons from hotseat to the
- * second screen.
- */
- public void ensureWorkspaceIsScrollable() {
- final UiObject2 workspace = assertState();
- if (!isWorkspaceScrollable(workspace)) {
- dragIconToNextScreen(getHotseatAppIcon("Messages"), workspace);
- }
- assertTrue("Home screen workspace didn't become scrollable",
- isWorkspaceScrollable(workspace));
- }
-
- private boolean isWorkspaceScrollable(UiObject2 workspace) {
- return workspace.isScrollable();
- }
-
- @NonNull
- private AppIcon getHotseatAppIcon(String appName) {
- return new AppIcon(mLauncher, mLauncher.getObjectInContainer(
- mHotseat, AppIcon.getAppIconSelector(appName)));
- }
-
- private void dragIconToNextScreen(AppIcon app, UiObject2 workspace) {
- final Point dest = new Point(
- mLauncher.getDevice().getDisplayWidth(), workspace.getVisibleBounds().centerY());
- app.getIcon().drag(dest, ICON_DRAG_SPEED);
- assertState();
- }
-
- /**
- * Flings to get to screens on the right. Waits for scrolling and a possible overscroll
- * recoil to complete.
- */
- public void flingForward() {
- final UiObject2 workspace = assertState();
- workspace.fling(Direction.RIGHT);
- mLauncher.waitForIdle();
- assertState();
- }
-
- /**
- * Flings to get to screens on the left. Waits for scrolling and a possible overscroll
- * recoil to complete.
- */
- public void flingBackward() {
- final UiObject2 workspace = assertState();
- workspace.fling(Direction.LEFT);
- mLauncher.waitForIdle();
- assertState();
- }
-
- /**
- * Opens widgets container by pressing Ctrl+W.
- *
- * @return the widgets container.
- */
- @NonNull
- public Widgets openAllWidgets() {
- assertState();
- mLauncher.getDevice().pressKeyCode(KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON);
- return new Widgets(mLauncher);
- }
} \ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/Launcher.java b/tests/tapl/com/android/launcher3/tapl/Launcher.java
deleted file mode 100644
index 5201dc8ba..000000000
--- a/tests/tapl/com/android/launcher3/tapl/Launcher.java
+++ /dev/null
@@ -1,303 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.tapl;
-
-import static com.android.systemui.shared.system.SettingsCompat.SWIPE_UP_SETTING_NAME;
-
-import android.content.res.Resources;
-import android.os.RemoteException;
-import android.provider.Settings;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.BySelector;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.Until;
-import android.util.Log;
-
-import org.junit.Assert;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-
-/**
- * The main tapl object. The only object that can be explicitly constructed by the using code. It
- * produces all other objects.
- */
-public final class Launcher {
-
- private static final String WORKSPACE_RES_ID = "workspace";
- private static final String APPS_RES_ID = "apps_view";
- private static final String OVERVIEW_RES_ID = "overview_panel";
- private static final String WIDGETS_RES_ID = "widgets_list_view";
-
- enum State {HOME, ALL_APPS, OVERVIEW, WIDGETS, BACKGROUND}
-
- static final String LAUNCHER_PKG = "com.google.android.apps.nexuslauncher";
- static final int APP_LAUNCH_TIMEOUT_MS = 10000;
- private static final int UI_OBJECT_WAIT_TIMEOUT_MS = 10000;
- private static final String SWIPE_UP_SETTING_AVAILABLE_RES_NAME =
- "config_swipe_up_gesture_setting_available";
- private static final String SWIPE_UP_ENABLED_DEFAULT_RES_NAME =
- "config_swipe_up_gesture_default";
- private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
- private static final String TAG = "tapl.Launcher";
- private final UiDevice mDevice;
- private final boolean mSwipeUpEnabled;
-
- /**
- * Constructs the root of TAPL hierarchy. You get all other object from it.
- */
- public Launcher(UiDevice device) {
- mDevice = device;
- final boolean swipeUpEnabledDefault =
- !getSystemBooleanRes(SWIPE_UP_SETTING_AVAILABLE_RES_NAME) ||
- getSystemBooleanRes(SWIPE_UP_ENABLED_DEFAULT_RES_NAME);
- mSwipeUpEnabled = Settings.Secure.getInt(
- InstrumentationRegistry.getTargetContext().getContentResolver(),
- SWIPE_UP_SETTING_NAME,
- swipeUpEnabledDefault ? 1 : 0) == 1;
- }
-
- private boolean getSystemBooleanRes(String resName) {
- final Resources res = Resources.getSystem();
- final int resId = res.getIdentifier(resName, "bool", "android");
- assertTrue("Resource not found: " + resName, resId != 0);
- return res.getBoolean(resId);
- }
-
- private void dumpViewHierarchy() {
- final ByteArrayOutputStream stream = new ByteArrayOutputStream();
- try {
- mDevice.dumpWindowHierarchy(stream);
- stream.flush();
- stream.close();
- for (String line : stream.toString().split("\\r?\\n")) {
- Log.e(TAG, line.trim());
- }
- } catch (IOException e) {
- Log.e(TAG, "error dumping XML to logcat", e);
- }
- }
-
- void fail(String message) {
- dumpViewHierarchy();
- Assert.fail(message);
- }
-
- void assertTrue(String message, boolean condition) {
- if (!condition) {
- fail(message);
- }
- }
-
- void assertNotNull(String message, Object object) {
- assertTrue(message, object != null);
- }
-
- private void failEquals(String message, Object actual) {
- String formatted = "Values should be different. ";
- if (message != null) {
- formatted = message + ". ";
- }
-
- formatted += "Actual: " + actual;
- fail(formatted);
- }
-
- void assertNotEquals(String message, int unexpected, int actual) {
- if (unexpected == actual) {
- failEquals(message, actual);
- }
- }
-
- boolean isSwipeUpEnabled() {
- return mSwipeUpEnabled;
- }
-
- UiObject2 assertState(State state) {
- switch (state) {
- case HOME: {
- //waitUntilGone(APPS_RES_ID);
- waitUntilGone(OVERVIEW_RES_ID);
- waitUntilGone(WIDGETS_RES_ID);
- return waitForLauncherObject(WORKSPACE_RES_ID);
- }
- case WIDGETS: {
- waitUntilGone(WORKSPACE_RES_ID);
- waitUntilGone(APPS_RES_ID);
- waitUntilGone(OVERVIEW_RES_ID);
- return waitForLauncherObject(WIDGETS_RES_ID);
- }
- case ALL_APPS: {
- waitUntilGone(OVERVIEW_RES_ID);
- waitUntilGone(WORKSPACE_RES_ID);
- waitUntilGone(WIDGETS_RES_ID);
- return waitForLauncherObject(APPS_RES_ID);
- }
- case OVERVIEW: {
- //waitForLauncherObject(APPS_RES_ID);
- waitUntilGone(WORKSPACE_RES_ID);
- waitUntilGone(WIDGETS_RES_ID);
- return waitForLauncherObject(OVERVIEW_RES_ID);
- }
- case BACKGROUND: {
- waitUntilGone(WORKSPACE_RES_ID);
- waitUntilGone(APPS_RES_ID);
- waitUntilGone(OVERVIEW_RES_ID);
- waitUntilGone(WIDGETS_RES_ID);
- return null;
- }
- default:
- fail("Invalid state: " + state);
- return null;
- }
- }
-
- /**
- * Presses nav bar home button.
- *
- * @return the Home object.
- */
- public Home pressHome() {
- getSystemUiObject("home").click();
- return getHome();
- }
-
- /**
- * Gets the Home object if the current state is "active home", i.e. workspace. Fails if the
- * launcher is not in that state.
- *
- * @return Home object.
- */
- @NonNull
- public Home getHome() {
- return new Home(this);
- }
-
- /**
- * Gets the Widgets object if the current state is showing all widgets. Fails if the launcher is
- * not in that state.
- *
- * @return Widgets object.
- */
- @NonNull
- public Widgets getAllWidgets() {
- return new Widgets(this);
- }
-
- /**
- * Gets the Overview object if the current state is showing the overview panel. Fails if the
- * launcher is not in that state.
- *
- * @return Overview object.
- */
- @NonNull
- public Overview getOverview() {
- return new Overview(this);
- }
-
- /**
- * Gets the All Apps object if the current state is showing the all apps panel. Fails if the
- * launcher is not in that state.
- *
- * @return All Aps object.
- */
- @NonNull
- public AllAppsFromHome getAllApps() {
- return new AllAppsFromHome(this);
- }
-
- /**
- * Gets the All Apps object if the current state is showing the all apps panel. Returns null if
- * the launcher is not in that state.
- *
- * @return All Aps object or null.
- */
- @Nullable
- public AllAppsFromHome tryGetAllApps() {
- return tryGetLauncherObject(APPS_RES_ID) != null ? getAllApps() : null;
- }
-
- private void waitUntilGone(String resId) {
-// assertTrue("Unexpected launcher object visible: " + resId,
-// mDevice.wait(Until.gone(getLauncherObjectSelector(resId)),
-// UI_OBJECT_WAIT_TIMEOUT_MS));
- }
-
- @NonNull
- UiObject2 getSystemUiObject(String resId) {
- try {
- mDevice.wakeUp();
- } catch (RemoteException e) {
- fail("Failed to wake up the device: " + e);
- }
- final UiObject2 object = mDevice.findObject(By.res(SYSTEMUI_PACKAGE, resId));
- assertNotNull("Can't find a systemui object with id: " + resId, object);
- return object;
- }
-
- @NonNull
- UiObject2 getObjectInContainer(UiObject2 container, BySelector selector) {
- final UiObject2 object = container.findObject(selector);
- assertNotNull("Can't find an object with selector: " + selector, object);
- return object;
- }
-
- @Nullable
- private UiObject2 tryGetLauncherObject(String resName) {
- return mDevice.findObject(getLauncherObjectSelector(resName));
- }
-
- @NonNull
- UiObject2 waitForObjectInContainer(UiObject2 container, String resName) {
- final UiObject2 object = container.wait(
- Until.findObject(getLauncherObjectSelector(resName)),
- UI_OBJECT_WAIT_TIMEOUT_MS);
- assertNotNull("Can find a launcher object id: " + resName + " in container: " +
- container.getResourceName(), object);
- return object;
- }
-
- @NonNull
- UiObject2 waitForLauncherObject(String resName) {
- final UiObject2 object = mDevice.wait(Until.findObject(getLauncherObjectSelector(resName)),
- UI_OBJECT_WAIT_TIMEOUT_MS);
- assertNotNull("Can find a launcher object; id: " + resName, object);
- return object;
- }
-
- static BySelector getLauncherObjectSelector(String resName) {
- return By.res(LAUNCHER_PKG, resName);
- }
-
- @NonNull
- UiDevice getDevice() {
- return mDevice;
- }
-
- void swipe(int startX, int startY, int endX, int endY, int steps) {
- mDevice.swipe(startX, startY, endX, endY, steps);
- waitForIdle();
- }
-
- void waitForIdle() {
- mDevice.waitForIdle();
- }
-} \ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
new file mode 100644
index 000000000..c3f27eeeb
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.tapl;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.ActivityManager;
+import android.app.UiAutomation;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.quickstep.SwipeUpSetting;
+
+import java.lang.ref.WeakReference;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * The main tapl object. The only object that can be explicitly constructed by the using code. It
+ * produces all other objects.
+ */
+public final class LauncherInstrumentation {
+
+ // Types for launcher containers that the user is interacting with. "Background" is a
+ // pseudo-container corresponding to inactive launcher covered by another app.
+ enum ContainerType {
+ WORKSPACE, ALL_APPS, OVERVIEW, WIDGETS, BACKGROUND
+ }
+
+ // Base class for launcher containers.
+ static abstract class VisibleContainer {
+ protected final LauncherInstrumentation mLauncher;
+
+ protected VisibleContainer(LauncherInstrumentation launcher) {
+ mLauncher = launcher;
+ launcher.setActiveContainer(this);
+ }
+
+ protected abstract ContainerType getContainerType();
+
+ /**
+ * Asserts that the launcher is in the mode matching 'this' object.
+ *
+ * @return UI object for the container.
+ */
+ final UiObject2 verifyActiveContainer() {
+ assertTrue("Attempt to use a stale container", this == sActiveContainer.get());
+ return mLauncher.verifyContainerType(getContainerType());
+ }
+ }
+
+ private static final String WORKSPACE_RES_ID = "workspace";
+ private static final String APPS_RES_ID = "apps_view";
+ private static final String OVERVIEW_RES_ID = "overview_panel";
+ private static final String WIDGETS_RES_ID = "widgets_list_view";
+ static final String LAUNCHER_PKG = "com.google.android.apps.nexuslauncher";
+ static final int WAIT_TIME_MS = 60000;
+ private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
+
+ private static WeakReference<VisibleContainer> sActiveContainer = new WeakReference<>(null);
+
+ private final UiDevice mDevice;
+ private final boolean mSwipeUpEnabled;
+
+ /**
+ * Constructs the root of TAPL hierarchy. You get all other objects from it.
+ */
+ public LauncherInstrumentation(UiDevice device) {
+ mDevice = device;
+ final boolean swipeUpEnabledDefault =
+ !SwipeUpSetting.isSwipeUpSettingAvailable() ||
+ SwipeUpSetting.isSwipeUpEnabledDefaultValue();
+ mSwipeUpEnabled = Settings.Secure.getInt(
+ InstrumentationRegistry.getTargetContext().getContentResolver(),
+ "swipe_up_to_switch_apps_enabled",
+ swipeUpEnabledDefault ? 1 : 0) == 1;
+ assertTrue("Device must run in a test harness", ActivityManager.isRunningInTestHarness());
+ }
+
+ void setActiveContainer(VisibleContainer container) {
+ sActiveContainer = new WeakReference<>(container);
+ }
+
+ boolean isSwipeUpEnabled() {
+ return mSwipeUpEnabled;
+ }
+
+ private UiObject2 verifyContainerType(ContainerType containerType) {
+ switch (containerType) {
+ case WORKSPACE: {
+ waitUntilGone(APPS_RES_ID);
+ waitUntilGone(OVERVIEW_RES_ID);
+ waitUntilGone(WIDGETS_RES_ID);
+ return waitForLauncherObject(WORKSPACE_RES_ID);
+ }
+ case WIDGETS: {
+ waitUntilGone(WORKSPACE_RES_ID);
+ waitUntilGone(APPS_RES_ID);
+ waitUntilGone(OVERVIEW_RES_ID);
+ return waitForLauncherObject(WIDGETS_RES_ID);
+ }
+ case ALL_APPS: {
+ waitUntilGone(WORKSPACE_RES_ID);
+ waitUntilGone(OVERVIEW_RES_ID);
+ waitUntilGone(WIDGETS_RES_ID);
+ return waitForLauncherObject(APPS_RES_ID);
+ }
+ case OVERVIEW: {
+ waitForLauncherObject(APPS_RES_ID);
+ waitUntilGone(WORKSPACE_RES_ID);
+ waitUntilGone(WIDGETS_RES_ID);
+ return waitForLauncherObject(OVERVIEW_RES_ID);
+ }
+ case BACKGROUND: {
+ waitUntilGone(WORKSPACE_RES_ID);
+ waitUntilGone(APPS_RES_ID);
+ waitUntilGone(OVERVIEW_RES_ID);
+ waitUntilGone(WIDGETS_RES_ID);
+ return null;
+ }
+ default:
+ fail("Invalid state: " + containerType);
+ return null;
+ }
+ }
+
+ private Bundle executeAndWaitForEvent(Runnable command,
+ UiAutomation.AccessibilityEventFilter eventFilter, String message) {
+ try {
+ final AccessibilityEvent event =
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .executeAndWaitForEvent(
+ command, eventFilter, WAIT_TIME_MS);
+ assertNotNull("executeAndWaitForEvent returned null (this can't happen)", event);
+ return (Bundle) event.getParcelableData();
+ } catch (TimeoutException e) {
+ fail(message);
+ return null;
+ }
+ }
+
+ Bundle getAnswerFromLauncher(UiObject2 view, String requestTag) {
+ // Send a fake set-text request to Launcher to initiate a response with requested data.
+ final String responseTag = requestTag + "_RESPONSE";
+ return executeAndWaitForEvent(
+ () -> view.setText(requestTag),
+ event -> responseTag.equals(event.getClassName()),
+ "Launcher didn't respond to request: " + requestTag);
+ }
+
+ /**
+ * Presses nav bar home button.
+ *
+ * @return the Workspace object.
+ */
+ public Workspace pressHome() {
+ // Click home, then wait for any accessibility event, then wait until accessibility events
+ // stop.
+ // We need waiting for any accessibility event generated after pressing Home because
+ // otherwise waitForIdle may return immediately in case when there was a big enough pause in
+ // accessibility events prior to pressing Home.
+ executeAndWaitForEvent(
+ () -> getSystemUiObject("home").click(),
+ event -> true,
+ "Pressing Home didn't produce any events");
+ mDevice.waitForIdle();
+ return getWorkspace();
+ }
+
+ /**
+ * Gets the Workspace object if the current state is "active home", i.e. workspace. Fails if the
+ * launcher is not in that state.
+ *
+ * @return Workspace object.
+ */
+ @NonNull
+ public Workspace getWorkspace() {
+ return new Workspace(this);
+ }
+
+ /**
+ * Gets the Workspace object if the current state is "background home", i.e. some other app is
+ * active. Fails if the launcher is not in that state.
+ *
+ * @return Background object.
+ */
+ @NonNull
+ public Background getBackground() {
+ return new Background(this);
+ }
+
+ /**
+ * Gets the Widgets object if the current state is showing all widgets. Fails if the launcher is
+ * not in that state.
+ *
+ * @return Widgets object.
+ */
+ @NonNull
+ public Widgets getAllWidgets() {
+ return new Widgets(this);
+ }
+
+ /**
+ * Gets the Overview object if the current state is showing the overview panel. Fails if the
+ * launcher is not in that state.
+ *
+ * @return Overview object.
+ */
+ @NonNull
+ public Overview getOverview() {
+ return new Overview(this);
+ }
+
+ /**
+ * Gets the All Apps object if the current state is showing the all apps panel opened by swiping
+ * from workspace. Fails if the launcher is not in that state. Please don't call this method if
+ * App Apps was opened by swiping up from Overview, as it won't fail and will return an
+ * incorrect object.
+ *
+ * @return All Aps object.
+ */
+ @NonNull
+ public AllApps getAllApps() {
+ return new AllApps(this);
+ }
+
+ /**
+ * Gets the All Apps object if the current state is showing the all apps panel opened by swiping
+ * from overview. Fails if the launcher is not in that state. Please don't call this method if
+ * App Apps was opened by swiping up from home, as it won't fail and will return an
+ * incorrect object.
+ *
+ * @return All Aps object.
+ */
+ @NonNull
+ public AllAppsFromOverview getAllAppsFromOverview() {
+ return new AllAppsFromOverview(this);
+ }
+
+ /**
+ * Gets the All Apps object if the current state is showing the all apps panel opened by swiping
+ * from workspace. Returns null if launcher is not in that state. Please don't call this method
+ * if App Apps was opened by swiping up from Overview, as it won't fail and will return an
+ * incorrect object.
+ *
+ * @return All Aps object or null.
+ */
+ @Nullable
+ public AllApps tryGetAllApps() {
+ return tryGetLauncherObject(APPS_RES_ID) != null ? getAllApps() : null;
+ }
+
+ private void waitUntilGone(String resId) {
+ assertTrue("Unexpected launcher object visible: " + resId,
+ mDevice.wait(Until.gone(getLauncherObjectSelector(resId)),
+ WAIT_TIME_MS));
+ }
+
+ @NonNull
+ UiObject2 getSystemUiObject(String resId) {
+ final UiObject2 object = mDevice.findObject(By.res(SYSTEMUI_PACKAGE, resId));
+ assertNotNull("Can't find a systemui object with id: " + resId, object);
+ return object;
+ }
+
+ @NonNull
+ UiObject2 getObjectInContainer(UiObject2 container, BySelector selector) {
+ final UiObject2 object = container.findObject(selector);
+ assertNotNull("Can't find an object with selector: " + selector, object);
+ return object;
+ }
+
+ @Nullable
+ private UiObject2 tryGetLauncherObject(String resName) {
+ return mDevice.findObject(getLauncherObjectSelector(resName));
+ }
+
+ @NonNull
+ UiObject2 waitForObjectInContainer(UiObject2 container, String resName) {
+ final UiObject2 object = container.wait(
+ Until.findObject(getLauncherObjectSelector(resName)),
+ WAIT_TIME_MS);
+ assertNotNull("Can't find a launcher object id: " + resName + " in container: " +
+ container.getResourceName(), object);
+ return object;
+ }
+
+ @NonNull
+ UiObject2 waitForLauncherObject(String resName) {
+ final UiObject2 object = mDevice.wait(Until.findObject(getLauncherObjectSelector(resName)),
+ WAIT_TIME_MS);
+ assertNotNull("Can't find a launcher object; id: " + resName, object);
+ return object;
+ }
+
+ static BySelector getLauncherObjectSelector(String resName) {
+ return By.res(LAUNCHER_PKG, resName);
+ }
+
+ @NonNull
+ UiDevice getDevice() {
+ return mDevice;
+ }
+
+ void swipe(int startX, int startY, int endX, int endY) {
+ executeAndWaitForEvent(
+ () -> mDevice.swipe(startX, startY, endX, endY, 60),
+ event -> "TAPL_WENT_TO_STATE".equals(event.getClassName()),
+ "Swipe failed to receive an event for the swipe end: " + startX + ", " + startY
+ + ", " + endX + ", " + endY);
+ }
+
+ void waitForIdle() {
+ mDevice.waitForIdle();
+ }
+} \ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/Overview.java b/tests/tapl/com/android/launcher3/tapl/Overview.java
index 225165571..3d504c45e 100644
--- a/tests/tapl/com/android/launcher3/tapl/Overview.java
+++ b/tests/tapl/com/android/launcher3/tapl/Overview.java
@@ -16,6 +16,8 @@
package com.android.launcher3.tapl;
+import static org.junit.Assert.assertNotEquals;
+
import android.graphics.Point;
import android.support.annotation.NonNull;
import android.support.test.uiautomator.Direction;
@@ -27,44 +29,37 @@ import java.util.List;
/**
* Overview pane.
*/
-public final class Overview {
+public final class Overview extends LauncherInstrumentation.VisibleContainer {
private static final int DEFAULT_FLING_SPEED = 15000;
- private final Launcher mLauncher;
-
- Overview(Launcher launcher) {
- mLauncher = launcher;
- assertState();
+ Overview(LauncherInstrumentation launcher) {
+ super(launcher);
+ verifyActiveContainer();
}
- /**
- * Asserts that we are in overview.
- *
- * @return Overview panel.
- */
- @NonNull
- private UiObject2 assertState() {
- return mLauncher.assertState(Launcher.State.OVERVIEW);
+ @Override
+ protected LauncherInstrumentation.ContainerType getContainerType() {
+ return LauncherInstrumentation.ContainerType.OVERVIEW;
}
/**
* Flings forward (left) and waits the fling's end.
*/
public void flingForward() {
- final UiObject2 overview = assertState();
+ final UiObject2 overview = verifyActiveContainer();
overview.fling(Direction.LEFT, DEFAULT_FLING_SPEED);
mLauncher.waitForIdle();
- assertState();
+ verifyActiveContainer();
}
/**
* Flings backward (right) and waits the fling's end.
*/
public void flingBackward() {
- final UiObject2 overview = assertState();
+ final UiObject2 overview = verifyActiveContainer();
overview.fling(Direction.RIGHT, DEFAULT_FLING_SPEED);
mLauncher.waitForIdle();
- assertState();
+ verifyActiveContainer();
}
/**
@@ -74,10 +69,10 @@ public final class Overview {
*/
@NonNull
public OverviewTask getCurrentTask() {
- assertState();
+ verifyActiveContainer();
final List<UiObject2> taskViews = mLauncher.getDevice().findObjects(
- Launcher.getLauncherObjectSelector("snapshot"));
- mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size());
+ LauncherInstrumentation.getLauncherObjectSelector("snapshot"));
+ assertNotEquals("Unable to find a task", 0, taskViews.size());
// taskViews contains up to 3 task views: the 'main' (having the widest visible
// part) one in the center, and parts of its right and left siblings. Find the
@@ -86,7 +81,7 @@ public final class Overview {
(t1, t2) -> Integer.compare(t1.getVisibleBounds().width(),
t2.getVisibleBounds().width()));
- return new OverviewTask(mLauncher, widestTask);
+ return new OverviewTask(mLauncher, widestTask, this);
}
/**
@@ -96,7 +91,7 @@ public final class Overview {
*/
@NonNull
public AllAppsFromOverview switchToAllApps() {
- assertState();
+ verifyActiveContainer();
// Swipe from the hotseat to near the top, e.g. 10% of the screen.
final UiObject2 predictionRow = mLauncher.waitForLauncherObject(
@@ -104,7 +99,7 @@ public final class Overview {
final Point start = predictionRow.getVisibleCenter();
final int endY = (int) (mLauncher.getDevice().getDisplayHeight() * 0.1f);
mLauncher.swipe(
- start.x, start.y, start.x, endY, (start.y - endY) / 100); // 100 px/step
+ start.x, start.y, start.x, endY);
return new AllAppsFromOverview(mLauncher);
}
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 68d308251..0b3a26492 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -16,6 +16,8 @@
package com.android.launcher3.tapl;
+import static org.junit.Assert.assertTrue;
+
import android.support.test.uiautomator.Direction;
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.Until;
@@ -24,29 +26,26 @@ import android.support.test.uiautomator.Until;
* A recent task in the overview panel carousel.
*/
public final class OverviewTask {
- private final Launcher mLauncher;
+ private final LauncherInstrumentation mLauncher;
private final UiObject2 mTask;
+ private final Overview mOverview;
- OverviewTask(Launcher launcher, UiObject2 task) {
+ OverviewTask(LauncherInstrumentation launcher, UiObject2 task, Overview overview) {
mLauncher = launcher;
- assertState();
mTask = task;
+ mOverview = overview;
+ verifyActiveContainer();
}
- /**
- * Asserts that we are in overview.
- *
- * @return Overview panel.
- */
- private void assertState() {
- mLauncher.assertState(Launcher.State.OVERVIEW);
+ private void verifyActiveContainer() {
+ mOverview.verifyActiveContainer();
}
/**
* Swipes the task up.
*/
public void dismiss() {
- assertState();
+ verifyActiveContainer();
// Dismiss the task via flinging it up.
mTask.fling(Direction.DOWN);
mLauncher.waitForIdle();
@@ -55,11 +54,11 @@ public final class OverviewTask {
/**
* Clicks at the task.
*/
- public void open() {
- assertState();
- mLauncher.assertTrue("Launching task didn't open a new window: " +
+ public Background open() {
+ verifyActiveContainer();
+ assertTrue("Launching task didn't open a new window: " +
mTask.getParent().getContentDescription(),
- mTask.clickAndWait(Until.newWindow(), Launcher.APP_LAUNCH_TIMEOUT_MS));
- mLauncher.assertState(Launcher.State.BACKGROUND);
+ mTask.clickAndWait(Until.newWindow(), LauncherInstrumentation.WAIT_TIME_MS));
+ return new Background(mLauncher);
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 7a5198a80..f67ef4c77 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -16,50 +16,43 @@
package com.android.launcher3.tapl;
-import android.support.annotation.NonNull;
import android.support.test.uiautomator.Direction;
import android.support.test.uiautomator.UiObject2;
/**
* All widgets container.
*/
-public final class Widgets {
+public final class Widgets extends LauncherInstrumentation.VisibleContainer {
private static final int FLING_SPEED = 12000;
- private final Launcher mLauncher;
-
- Widgets(Launcher launcher) {
- mLauncher = launcher;
- assertState();
+ Widgets(LauncherInstrumentation launcher) {
+ super(launcher);
+ verifyActiveContainer();
}
/**
* Flings forward (down) and waits the fling's end.
*/
public void flingForward() {
- final UiObject2 widgetsContainer = assertState();
+ final UiObject2 widgetsContainer = verifyActiveContainer();
+ widgetsContainer.setGestureMargin(100);
widgetsContainer.fling(Direction.DOWN, FLING_SPEED);
- mLauncher.waitForIdle();
- assertState();
+ verifyActiveContainer();
}
/**
* Flings backward (up) and waits the fling's end.
*/
public void flingBackward() {
- final UiObject2 widgetsContainer = assertState();
+ final UiObject2 widgetsContainer = verifyActiveContainer();
+ widgetsContainer.setGestureMargin(100);
widgetsContainer.fling(Direction.UP, FLING_SPEED);
mLauncher.waitForIdle();
- assertState();
+ verifyActiveContainer();
}
- /**
- * Asserts that we are in widgets.
- *
- * @return Widgets container.
- */
- @NonNull
- private UiObject2 assertState() {
- return mLauncher.assertState(Launcher.State.WIDGETS);
+ @Override
+ protected LauncherInstrumentation.ContainerType getContainerType() {
+ return LauncherInstrumentation.ContainerType.WIDGETS;
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
new file mode 100644
index 000000000..045ab5fcf
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.tapl;
+
+import static junit.framework.TestCase.assertTrue;
+
+import android.graphics.Point;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiObject2;
+import android.view.KeyEvent;
+
+/**
+ * Operations on the workspace screen.
+ */
+public final class Workspace extends Home {
+ private final UiObject2 mHotseat;
+ private final int ICON_DRAG_SPEED = 2000;
+
+ Workspace(LauncherInstrumentation launcher) {
+ super(launcher);
+ mHotseat = launcher.waitForLauncherObject("hotseat");
+ }
+
+ @Override
+ protected LauncherInstrumentation.ContainerType getContainerType() {
+ return LauncherInstrumentation.ContainerType.WORKSPACE;
+ }
+
+ /**
+ * Swipes up to All Apps.
+ *
+ * @return the App Apps object.
+ */
+ @NonNull
+ public AllApps switchToAllApps() {
+ verifyActiveContainer();
+ if (mLauncher.isSwipeUpEnabled()) {
+ int midX = mLauncher.getDevice().getDisplayWidth() / 2;
+ int height = mLauncher.getDevice().getDisplayHeight();
+ // Swipe from 6/7ths down the screen to 1/7th down the screen.
+ mLauncher.swipe(
+ midX,
+ height * 6 / 7,
+ midX,
+ height / 7
+ );
+ } else {
+ // Swipe from the hotseat to near the top, e.g. 10% of the screen.
+ final UiObject2 hotseat = mHotseat;
+ final Point start = hotseat.getVisibleCenter();
+ final int endY = (int) (mLauncher.getDevice().getDisplayHeight() * 0.1f);
+ mLauncher.swipe(
+ start.x,
+ start.y,
+ start.x,
+ endY
+ );
+ }
+
+ return new AllApps(mLauncher);
+ }
+
+ /**
+ * Returns an icon for the app, if currently visible.
+ *
+ * @param appName name of the app
+ * @return app icon, if found, null otherwise.
+ */
+ @Nullable
+ public AppIcon tryGetWorkspaceAppIcon(String appName) {
+ final UiObject2 workspace = verifyActiveContainer();
+ final UiObject2 icon = workspace.findObject(AppIcon.getAppIconSelector(appName));
+ return icon != null ? new AppIcon(mLauncher, icon) : null;
+ }
+
+ /**
+ * Ensures that workspace is scrollable. If it's not, drags an icon icons from hotseat to the
+ * second screen.
+ */
+ public void ensureWorkspaceIsScrollable() {
+ final UiObject2 workspace = verifyActiveContainer();
+ if (!isWorkspaceScrollable(workspace)) {
+ dragIconToNextScreen(getHotseatAppIcon("Messages"), workspace);
+ }
+ assertTrue("Home screen workspace didn't become scrollable",
+ isWorkspaceScrollable(workspace));
+ }
+
+ private boolean isWorkspaceScrollable(UiObject2 workspace) {
+ return workspace.isScrollable();
+ }
+
+ @NonNull
+ private AppIcon getHotseatAppIcon(String appName) {
+ return new AppIcon(mLauncher, mLauncher.getObjectInContainer(
+ mHotseat, AppIcon.getAppIconSelector(appName)));
+ }
+
+ private void dragIconToNextScreen(AppIcon app, UiObject2 workspace) {
+ final Point dest = new Point(
+ mLauncher.getDevice().getDisplayWidth(), workspace.getVisibleBounds().centerY());
+ app.getIcon().drag(dest, ICON_DRAG_SPEED);
+ verifyActiveContainer();
+ }
+
+ /**
+ * Flings to get to screens on the right. Waits for scrolling and a possible overscroll
+ * recoil to complete.
+ */
+ public void flingForward() {
+ final UiObject2 workspace = verifyActiveContainer();
+ workspace.fling(Direction.RIGHT);
+ mLauncher.waitForIdle();
+ verifyActiveContainer();
+ }
+
+ /**
+ * Flings to get to screens on the left. Waits for scrolling and a possible overscroll
+ * recoil to complete.
+ */
+ public void flingBackward() {
+ final UiObject2 workspace = verifyActiveContainer();
+ workspace.fling(Direction.LEFT);
+ mLauncher.waitForIdle();
+ verifyActiveContainer();
+ }
+
+ /**
+ * Opens widgets container by pressing Ctrl+W.
+ *
+ * @return the widgets container.
+ */
+ @NonNull
+ public Widgets openAllWidgets() {
+ verifyActiveContainer();
+ mLauncher.getDevice().pressKeyCode(KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON);
+ return new Widgets(mLauncher);
+ }
+} \ No newline at end of file