diff options
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 Binary files differindex 27de1e907..9d91d7ecc 100644 --- a/quickstep/libs/sysui_shared.jar +++ b/quickstep/libs/sysui_shared.jar 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 Binary files differdeleted file mode 100644 index 253755ffe..000000000 --- a/res/drawable-hdpi/ic_allapps.png +++ /dev/null diff --git a/res/drawable-hdpi/ic_allapps_pressed.png b/res/drawable-hdpi/ic_allapps_pressed.png Binary files differdeleted file mode 100644 index 1e644c5d0..000000000 --- a/res/drawable-hdpi/ic_allapps_pressed.png +++ /dev/null diff --git a/res/drawable-mdpi/ic_allapps.png b/res/drawable-mdpi/ic_allapps.png Binary files differdeleted file mode 100644 index 6936b2079..000000000 --- a/res/drawable-mdpi/ic_allapps.png +++ /dev/null diff --git a/res/drawable-mdpi/ic_allapps_pressed.png b/res/drawable-mdpi/ic_allapps_pressed.png Binary files differdeleted file mode 100644 index 850ded6fe..000000000 --- a/res/drawable-mdpi/ic_allapps_pressed.png +++ /dev/null diff --git a/res/drawable-xhdpi/ic_allapps.png b/res/drawable-xhdpi/ic_allapps.png Binary files differdeleted file mode 100644 index c11c1038b..000000000 --- a/res/drawable-xhdpi/ic_allapps.png +++ /dev/null diff --git a/res/drawable-xhdpi/ic_allapps_pressed.png b/res/drawable-xhdpi/ic_allapps_pressed.png Binary files differdeleted file mode 100644 index f319bf1e6..000000000 --- a/res/drawable-xhdpi/ic_allapps_pressed.png +++ /dev/null diff --git a/res/drawable-xxhdpi/ic_allapps.png b/res/drawable-xxhdpi/ic_allapps.png Binary files differdeleted file mode 100644 index cf6a2cbc0..000000000 --- a/res/drawable-xxhdpi/ic_allapps.png +++ /dev/null diff --git a/res/drawable-xxhdpi/ic_allapps_pressed.png b/res/drawable-xxhdpi/ic_allapps_pressed.png Binary files differdeleted file mode 100644 index 379389ac7..000000000 --- a/res/drawable-xxhdpi/ic_allapps_pressed.png +++ /dev/null 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 |