Merge "Clean up some more refs to thumbnail data" into ub-launcher3-qt-dev
diff --git a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 39f8448..900b94e 100644
--- a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -19,15 +19,21 @@
 
 import android.annotation.TargetApi;
 import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.graphics.Region;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.Process;
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.MotionEvent;
 
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.UserManagerCompat;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 
@@ -37,7 +43,16 @@
 @TargetApi(Build.VERSION_CODES.O)
 public class TouchInteractionService extends Service {
 
-    private static final String TAG = "TouchInteractionService";
+    private static final String TAG = "GoTouchInteractionService";
+    private boolean mIsUserUnlocked;
+    private BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
+                initWhenUserUnlocked();
+            }
+        }
+    };
 
     private final IBinder mMyBinder = new IOverviewProxy.Stub() {
 
@@ -53,17 +68,21 @@
 
         @Override
         public void onOverviewToggle() {
-            mOverviewCommandHelper.onOverviewToggle();
+            if (mIsUserUnlocked) {
+                mOverviewCommandHelper.onOverviewToggle();
+            }
         }
 
         @Override
         public void onOverviewShown(boolean triggeredFromAltTab) {
-            mOverviewCommandHelper.onOverviewShown(triggeredFromAltTab);
+            if (mIsUserUnlocked) {
+                mOverviewCommandHelper.onOverviewShown(triggeredFromAltTab);
+            }
         }
 
         @Override
         public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
-            if (triggeredFromAltTab && !triggeredFromHomeKey) {
+            if (mIsUserUnlocked && triggeredFromAltTab && !triggeredFromHomeKey) {
                 // onOverviewShownFromAltTab hides the overview and ends at the target app
                 mOverviewCommandHelper.onOverviewHidden();
             }
@@ -71,7 +90,9 @@
 
         @Override
         public void onTip(int actionType, int viewType) {
-            mOverviewCommandHelper.onTip(actionType, viewType);
+            if (mIsUserUnlocked) {
+                mOverviewCommandHelper.onTip(actionType, viewType);
+            }
         }
 
         @Override
@@ -123,17 +144,31 @@
     @Override
     public void onCreate() {
         super.onCreate();
-        mRecentsModel = RecentsModel.INSTANCE.get(this);
-        mOverviewComponentObserver = new OverviewComponentObserver(this);
-        mOverviewCommandHelper = new OverviewCommandHelper(this,
-                mOverviewComponentObserver);
+        if (UserManagerCompat.getInstance(this).isUserUnlocked(Process.myUserHandle())) {
+            initWhenUserUnlocked();
+        } else {
+            mIsUserUnlocked = false;
+            registerReceiver(mUserUnlockedReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
+        }
 
         sConnected = true;
     }
 
+    private void initWhenUserUnlocked() {
+        mRecentsModel = RecentsModel.INSTANCE.get(this);
+        mOverviewComponentObserver = new OverviewComponentObserver(this);
+        mOverviewCommandHelper = new OverviewCommandHelper(this,
+                mOverviewComponentObserver);
+        mIsUserUnlocked = true;
+        Utilities.unregisterReceiverSafely(this, mUserUnlockedReceiver);
+    }
+
     @Override
     public void onDestroy() {
-        mOverviewComponentObserver.onDestroy();
+        if (mIsUserUnlocked) {
+            mOverviewComponentObserver.onDestroy();
+        }
+        Utilities.unregisterReceiverSafely(this, mUserUnlockedReceiver);
         sConnected = false;
         super.onDestroy();
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 22ebe61..8f08f0d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -81,6 +81,7 @@
 import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
 import com.android.quickstep.inputconsumers.AssistantTouchConsumer;
 import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer;
+import com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer;
 import com.android.quickstep.inputconsumers.InputConsumer;
 import com.android.quickstep.inputconsumers.OtherActivityInputConsumer;
 import com.android.quickstep.inputconsumers.OverviewInputConsumer;
@@ -614,6 +615,10 @@
         } else if (mGestureBlockingActivity != null && runningTaskInfo != null
                 && mGestureBlockingActivity.equals(runningTaskInfo.topActivity)) {
             return mResetGestureInputConsumer;
+        } else if (mMode == Mode.NO_BUTTON && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
+            return new FallbackNoButtonInputConsumer(this, activityControl,
+                    mInputMonitorCompat, mSwipeSharedState, mSwipeTouchRegion,
+                    mOverviewComponentObserver, disableHorizontalSwipe(event), runningTaskInfo);
         } else {
             return createOtherActivityInputConsumer(event, runningTaskInfo);
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
new file mode 100644
index 0000000..d05ca2a
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2019 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.inputconsumers;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_POINTER_DOWN;
+import static android.view.MotionEvent.ACTION_POINTER_UP;
+import static android.view.MotionEvent.ACTION_UP;
+import static android.view.MotionEvent.INVALID_POINTER_ID;
+
+import static com.android.quickstep.WindowTransformSwipeHandler.MAX_SWIPE_DURATION;
+import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
+import static com.android.quickstep.WindowTransformSwipeHandler.MIN_SWIPE_DURATION;
+import static com.android.quickstep.inputconsumers.OtherActivityInputConsumer.QUICKSTEP_TOUCH_SLOP_RATIO;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.R;
+import com.android.quickstep.ActivityControlHelper;
+import com.android.quickstep.OverviewComponentObserver;
+import com.android.quickstep.SwipeSharedState;
+import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
+import com.android.quickstep.util.NavBarPosition;
+import com.android.quickstep.util.RecentsAnimationListenerSet;
+import com.android.quickstep.util.SwipeAnimationTargetSet;
+import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.BackgroundExecutor;
+import com.android.systemui.shared.system.InputMonitorCompat;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+public class FallbackNoButtonInputConsumer implements InputConsumer, SwipeAnimationListener {
+
+    private static final int STATE_NOT_FINISHED = 0;
+    private static final int STATE_FINISHED_TO_HOME = 1;
+    private static final int STATE_FINISHED_TO_APP = 2;
+
+    private static final float PROGRESS_TO_END_GESTURE = -2;
+
+    private final ActivityControlHelper mActivityControlHelper;
+    private final InputMonitorCompat mInputMonitor;
+    private final Context mContext;
+    private final NavBarPosition mNavBarPosition;
+    private final SwipeSharedState mSwipeSharedState;
+    private final OverviewComponentObserver mOverviewComponentObserver;
+    private final int mRunningTaskId;
+
+    private final ClipAnimationHelper mClipAnimationHelper;
+    private final TransformParams mTransformParams = new TransformParams();
+    private final float mTransitionDragLength;
+    private final DeviceProfile mDP;
+
+    private final RectF mSwipeTouchRegion;
+    private final boolean mDisableHorizontalSwipe;
+
+    private final PointF mDownPos = new PointF();
+    private final PointF mLastPos = new PointF();
+
+    private int mActivePointerId = -1;
+    // Slop used to determine when we say that the gesture has started.
+    private boolean mPassedPilferInputSlop;
+
+    private VelocityTracker mVelocityTracker;
+
+    // Distance after which we start dragging the window.
+    private final float mTouchSlop;
+
+    // Might be displacement in X or Y, depending on the direction we are swiping from the nav bar.
+    private float mStartDisplacement;
+    private SwipeAnimationTargetSet mSwipeAnimationTargetSet;
+    private float mProgress;
+
+    private int mState = STATE_NOT_FINISHED;
+
+    public FallbackNoButtonInputConsumer(Context context,
+            ActivityControlHelper activityControlHelper, InputMonitorCompat inputMonitor,
+            SwipeSharedState swipeSharedState, RectF swipeTouchRegion,
+            OverviewComponentObserver overviewComponentObserver,
+            boolean disableHorizontalSwipe, RunningTaskInfo runningTaskInfo) {
+        mContext = context;
+        mActivityControlHelper = activityControlHelper;
+        mInputMonitor = inputMonitor;
+        mOverviewComponentObserver = overviewComponentObserver;
+        mRunningTaskId = runningTaskInfo.id;
+
+        mSwipeSharedState = swipeSharedState;
+        mSwipeTouchRegion = swipeTouchRegion;
+        mDisableHorizontalSwipe = disableHorizontalSwipe;
+
+        mNavBarPosition = new NavBarPosition(context);
+        mVelocityTracker = VelocityTracker.obtain();
+
+        mTouchSlop = QUICKSTEP_TOUCH_SLOP_RATIO
+                * ViewConfiguration.get(context).getScaledTouchSlop();
+
+        mClipAnimationHelper = new ClipAnimationHelper(context);
+
+        mDP = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context).copy(context);
+        Rect tempRect = new Rect();
+        mTransitionDragLength = mActivityControlHelper.getSwipeUpDestinationAndLength(
+                mDP, context, tempRect);
+        mClipAnimationHelper.updateTargetRect(tempRect);
+    }
+
+    @Override
+    public int getType() {
+        return TYPE_FALLBACK_NO_BUTTON;
+    }
+
+    @Override
+    public void onMotionEvent(MotionEvent ev) {
+        if (mVelocityTracker == null) {
+            return;
+        }
+
+        mVelocityTracker.addMovement(ev);
+        if (ev.getActionMasked() == ACTION_POINTER_UP) {
+            mVelocityTracker.clear();
+        }
+
+        switch (ev.getActionMasked()) {
+            case ACTION_DOWN: {
+                mActivePointerId = ev.getPointerId(0);
+                mDownPos.set(ev.getX(), ev.getY());
+                mLastPos.set(mDownPos);
+                break;
+            }
+            case ACTION_POINTER_DOWN: {
+                if (!mPassedPilferInputSlop) {
+                    // Cancel interaction in case of multi-touch interaction
+                    int ptrIdx = ev.getActionIndex();
+                    if (!mSwipeTouchRegion.contains(ev.getX(ptrIdx), ev.getY(ptrIdx))) {
+                        forceCancelGesture(ev);
+                    }
+                }
+                break;
+            }
+            case ACTION_POINTER_UP: {
+                int ptrIdx = ev.getActionIndex();
+                int ptrId = ev.getPointerId(ptrIdx);
+                if (ptrId == mActivePointerId) {
+                    final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
+                    mDownPos.set(
+                            ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
+                            ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
+                    mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
+                    mActivePointerId = ev.getPointerId(newPointerIdx);
+                }
+                break;
+            }
+            case ACTION_MOVE: {
+                int pointerIndex = ev.findPointerIndex(mActivePointerId);
+                if (pointerIndex == INVALID_POINTER_ID) {
+                    break;
+                }
+                mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
+                float displacement = getDisplacement(ev);
+
+                if (!mPassedPilferInputSlop) {
+                    if (mDisableHorizontalSwipe && Math.abs(mLastPos.x - mDownPos.x)
+                            > Math.abs(mLastPos.y - mDownPos.y)) {
+                        // Horizontal gesture is not allowed in this region
+                        forceCancelGesture(ev);
+                        break;
+                    }
+
+                    if (Math.abs(displacement) >= mTouchSlop) {
+                        mPassedPilferInputSlop = true;
+
+                        // Deferred gesture, start the animation and gesture tracking once
+                        // we pass the actual touch slop
+                        startTouchTrackingForWindowAnimation(displacement);
+                    }
+                } else {
+                    updateDisplacement(displacement - mStartDisplacement);
+                }
+                break;
+            }
+            case ACTION_CANCEL:
+            case ACTION_UP: {
+                finishTouchTracking(ev);
+                break;
+            }
+        }
+    }
+
+    private void startTouchTrackingForWindowAnimation(float displacement) {
+        mStartDisplacement = Math.min(displacement, -mTouchSlop);
+
+        RecentsAnimationListenerSet listenerSet =
+                mSwipeSharedState.newRecentsAnimationListenerSet();
+        listenerSet.addListener(this);
+        Intent homeIntent = mOverviewComponentObserver.getHomeIntent();
+        BackgroundExecutor.get().submit(
+                () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
+                        homeIntent, null, listenerSet, null, null));
+
+        ActivityManagerWrapper.getInstance().closeSystemWindows(
+                CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+        mInputMonitor.pilferPointers();
+    }
+
+    private void updateDisplacement(float displacement) {
+        mProgress = displacement / mTransitionDragLength;
+        mTransformParams.setProgress(mProgress);
+
+        if (mSwipeAnimationTargetSet != null) {
+            mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
+        }
+    }
+
+    private void forceCancelGesture(MotionEvent ev) {
+        int action = ev.getAction();
+        ev.setAction(ACTION_CANCEL);
+        finishTouchTracking(ev);
+        ev.setAction(action);
+    }
+
+    /**
+     * Called when the gesture has ended. Does not correlate to the completion of the interaction as
+     * the animation can still be running.
+     */
+    private void finishTouchTracking(MotionEvent ev) {
+        if (ev.getAction() == ACTION_CANCEL) {
+            mState = STATE_FINISHED_TO_APP;
+        } else {
+            mVelocityTracker.computeCurrentVelocity(1000,
+                    ViewConfiguration.get(mContext).getScaledMaximumFlingVelocity());
+            float velocityX = mVelocityTracker.getXVelocity(mActivePointerId);
+            float velocityY = mVelocityTracker.getYVelocity(mActivePointerId);
+            float velocity = mNavBarPosition.isRightEdge() ? velocityX
+                    : mNavBarPosition.isLeftEdge() ? -velocityX
+                            : velocityY;
+            float flingThreshold = mContext.getResources()
+                    .getDimension(R.dimen.quickstep_fling_threshold_velocity);
+            boolean isFling = Math.abs(velocity) > flingThreshold;
+
+            boolean goingHome;
+            if (!isFling) {
+                goingHome = -mProgress >= MIN_PROGRESS_FOR_OVERVIEW;
+            } else {
+                goingHome = velocity < 0;
+            }
+
+            if (goingHome) {
+                mState = STATE_FINISHED_TO_HOME;
+            } else {
+                mState = STATE_FINISHED_TO_APP;
+            }
+        }
+
+        if (mSwipeAnimationTargetSet != null) {
+            finishAnimationTargetSet();
+        }
+    }
+
+    private void finishAnimationTargetSet() {
+        if (mState == STATE_FINISHED_TO_APP) {
+            mSwipeAnimationTargetSet.finishController(false, null, false);
+        } else {
+            if (mProgress < PROGRESS_TO_END_GESTURE) {
+                mSwipeAnimationTargetSet.finishController(true, null, true);
+            } else {
+                long duration = (long) (Math.min(mProgress - PROGRESS_TO_END_GESTURE, 1)
+                        * MAX_SWIPE_DURATION / Math.abs(PROGRESS_TO_END_GESTURE));
+                if (duration < 0) {
+                    duration = MIN_SWIPE_DURATION;
+                }
+
+                ValueAnimator anim = ValueAnimator.ofFloat(mProgress, PROGRESS_TO_END_GESTURE);
+                anim.addUpdateListener(a -> {
+                    float p = (Float) anim.getAnimatedValue();
+                    mTransformParams.setProgress(p);
+                    mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
+                });
+                anim.setDuration(duration);
+                anim.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mSwipeAnimationTargetSet.finishController(true, null, true);
+                    }
+                });
+                anim.start();
+            }
+        }
+    }
+
+    @Override
+    public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
+        mSwipeAnimationTargetSet = targetSet;
+        Rect overviewStackBounds = new Rect(0, 0, mDP.widthPx, mDP.heightPx);
+        RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mRunningTaskId);
+
+        mDP.updateIsSeascape(mContext.getSystemService(WindowManager.class));
+        if (runningTaskTarget != null) {
+            mClipAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget);
+        }
+        mClipAnimationHelper.prepareAnimation(mDP, false /* isOpening */);
+
+        overviewStackBounds
+                .inset(-overviewStackBounds.width() / 5, -overviewStackBounds.height() / 5);
+        mClipAnimationHelper.updateTargetRect(overviewStackBounds);
+        mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
+
+        if (mState != STATE_NOT_FINISHED) {
+            finishAnimationTargetSet();
+        }
+    }
+
+    @Override
+    public void onRecentsAnimationCanceled() { }
+
+    private float getDisplacement(MotionEvent ev) {
+        if (mNavBarPosition.isRightEdge()) {
+            return ev.getX() - mDownPos.x;
+        } else if (mNavBarPosition.isLeftEdge()) {
+            return mDownPos.x - ev.getX();
+        } else {
+            return ev.getY() - mDownPos.y;
+        }
+    }
+
+    @Override
+    public boolean allowInterceptByParent() {
+        return !mPassedPilferInputSlop;
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
index a1e5d47..f5cf654 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
@@ -33,6 +33,7 @@
     int TYPE_SCREEN_PINNED = 1 << 6;
     int TYPE_OVERVIEW_WITHOUT_FOCUS = 1 << 7;
     int TYPE_RESET_GESTURE = 1 << 8;
+    int TYPE_FALLBACK_NO_BUTTON = 1 << 9;
 
     String[] NAMES = new String[] {
            "TYPE_NO_OP",                    // 0
@@ -44,6 +45,7 @@
             "TYPE_SCREEN_PINNED",           // 6
             "TYPE_OVERVIEW_WITHOUT_FOCUS",  // 7
             "TYPE_RESET_GESTURE",           // 8
+            "TYPE_FALLBACK_NO_BUTTON",      // 9
     };
 
     InputConsumer NO_OP = () -> TYPE_NO_OP;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 6bc543f..4c137d3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -79,7 +79,7 @@
     private static final String UP_EVT = "OtherActivityInputConsumer.UP";
 
     // TODO: Move to quickstep contract
-    private static final float QUICKSTEP_TOUCH_SLOP_RATIO = 3;
+    public static final float QUICKSTEP_TOUCH_SLOP_RATIO = 3;
 
     private final CachedEventDispatcher mRecentsViewDispatcher = new CachedEventDispatcher();
     private final RunningTaskInfo mRunningTask;
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 0a73b8b..0738aff 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -33,7 +33,6 @@
 
 import com.android.systemui.shared.system.PackageManagerWrapper;
 
-import com.android.systemui.shared.system.QuickStepContract;
 import java.util.ArrayList;
 
 /**
@@ -58,7 +57,9 @@
     private String mUpdateRegisteredPackage;
     private ActivityControlHelper mActivityControlHelper;
     private Intent mOverviewIntent;
+    private Intent mHomeIntent;
     private int mSystemUiStateFlags;
+    private boolean mIsHomeAndOverviewSame;
 
     public OverviewComponentObserver(Context context) {
         mContext = context;
@@ -93,11 +94,14 @@
 
         final String overviewIntentCategory;
         ComponentName overviewComponent;
+        mHomeIntent = null;
+
         if ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0 &&
                 (defaultHome == null || mMyHomeComponent.equals(defaultHome))) {
             // User default home is same as out home app. Use Overview integrated in Launcher.
             overviewComponent = mMyHomeComponent;
             mActivityControlHelper = new LauncherActivityControllerHelper();
+            mIsHomeAndOverviewSame = true;
             overviewIntentCategory = Intent.CATEGORY_HOME;
 
             if (mUpdateRegisteredPackage != null) {
@@ -109,8 +113,12 @@
             // The default home app is a different launcher. Use the fallback Overview instead.
             overviewComponent = new ComponentName(mContext, RecentsActivity.class);
             mActivityControlHelper = new FallbackActivityControllerHelper();
+            mIsHomeAndOverviewSame = false;
             overviewIntentCategory = Intent.CATEGORY_DEFAULT;
 
+            mHomeIntent = new Intent(Intent.ACTION_MAIN)
+                    .addCategory(Intent.CATEGORY_HOME)
+                    .setComponent(defaultHome);
             // User's default home app can change as a result of package updates of this app (such
             // as uninstalling the app or removing the "Launcher" feature in an update).
             // Listen for package updates of this app (and remove any previously attached
@@ -135,6 +143,9 @@
                 .addCategory(overviewIntentCategory)
                 .setComponent(overviewComponent)
                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        if (mHomeIntent == null) {
+            mHomeIntent = mOverviewIntent;
+        }
     }
 
     /**
@@ -159,6 +170,20 @@
     }
 
     /**
+     * Get the current intent for going to the home activity.
+     */
+    public Intent getHomeIntent() {
+        return mHomeIntent;
+    }
+
+    /**
+     * Returns true if home and overview are same activity.
+     */
+    public boolean isHomeAndOverviewSame() {
+        return mIsHomeAndOverviewSame;
+    }
+
+    /**
      * Get the current activity control helper for managing interactions to the overview activity.
      *
      * @return the current activity control helper
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 0135911..e5f949b 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -43,10 +43,12 @@
 
 import com.android.launcher3.tapl.LauncherInstrumentation;
 import com.android.launcher3.testcomponent.TestCommandReceiver;
+import com.android.launcher3.util.rule.FailureWatcher;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
 
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 import org.junit.runners.model.Statement;
@@ -62,10 +64,14 @@
     private final LauncherInstrumentation mLauncher;
     private final ActivityInfo mOtherLauncherActivity;
 
-    @Rule public final TestRule mDisableHeadsUpNotification = disableHeadsUpNotification();
-    @Rule public final TestRule mQuickstepOnOffExecutor;
+    @Rule
+    public final TestRule mDisableHeadsUpNotification = disableHeadsUpNotification();
 
-    @Rule public final TestRule mSetLauncherCommand;
+    @Rule
+    public final TestRule mSetLauncherCommand;
+
+    @Rule
+    public final TestRule mOrderSensitiveRules;
 
     public FallbackRecentsTest() throws RemoteException {
         Instrumentation instrumentation = getInstrumentation();
@@ -74,7 +80,10 @@
         mDevice.setOrientationNatural();
         mLauncher = new LauncherInstrumentation(instrumentation);
 
-        mQuickstepOnOffExecutor = new NavigationModeSwitchRule(mLauncher);
+        mOrderSensitiveRules = RuleChain.
+                outerRule(new NavigationModeSwitchRule(mLauncher)).
+                around(new FailureWatcher(mDevice));
+
         mOtherLauncherActivity = context.getPackageManager().queryIntentActivities(
                 getHomeIntentInPackage(context),
                 MATCH_DISABLED_COMPONENTS).get(0).activityInfo;
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index e663e70..4a0ca5c 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -125,7 +125,7 @@
     protected TestRule getRulesInsideActivityMonitor() {
         return RuleChain.
                 outerRule(new PortraitLandscapeRunner(this)).
-                around(new FailureWatcher(this));
+                around(new FailureWatcher(mDevice));
     }
 
     @Rule
diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index 874ff19..d36126b 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -15,8 +15,9 @@
  */
 package com.android.launcher3.ui.widget;
 
+import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
+
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
@@ -24,43 +25,37 @@
 import android.appwidget.AppWidgetManager;
 import android.content.ComponentName;
 import android.content.ContentResolver;
-import android.content.ContentValues;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.os.Bundle;
 
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
 import com.android.launcher3.LauncherAppWidgetHost;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.Workspace;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.tapl.Workspace;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.TestViewHelpers;
 import com.android.launcher3.util.ContentWriter;
 import com.android.launcher3.util.rule.ShellCommandRule;
-import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
-import com.android.launcher3.widget.PendingAppWidgetHostView;
 import com.android.launcher3.widget.WidgetHostViewLoader;
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.Set;
 
-import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
-import androidx.test.uiautomator.UiSelector;
-import java.util.concurrent.Callable;
-
 /**
  * Tests for bind widget flow.
  *
@@ -131,16 +126,14 @@
 
         setupContents(item);
 
-        // Since there is no widget to verify, just wait until the workspace is ready.
-        // TODO: fix LauncherInstrumentation#LAUNCHER_PKG
-        mLauncher.getWorkspace();
+        final Workspace workspace = mLauncher.getWorkspace();
         // Item deleted from db
         mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
                 null, null, null, null, null);
         assertEquals(0, mCursor.getCount());
 
         // The view does not exist
-        assertFalse(mDevice.findObject(new UiSelector().description(info.label)).exists());
+        assertTrue("Widget exists", workspace.tryGetWidget(info.label, 0) == null);
     }
 
     @Test
@@ -189,12 +182,8 @@
 
         setupContents(item);
 
-        // Since there is no widget to verify, just wait until the workspace is ready.
-        // TODO: fix LauncherInstrumentation#LAUNCHER_PKG
-        mLauncher.getWorkspace();
-        // The view does not exist
-        assertFalse(mDevice.findObject(
-                new UiSelector().className(PendingAppWidgetHostView.class)).exists());
+        assertTrue("Pending widget exists",
+                mLauncher.getWorkspace().tryGetPendingWidget(0) == null);
         // Item deleted from db
         mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
                 null, null, null, null, null);
@@ -258,12 +247,12 @@
      * widget class is displayed on the homescreen.
      */
     private void setupContents(LauncherAppWidgetInfo item) {
-        int screenId = Workspace.FIRST_SCREEN_ID;
+        int screenId = FIRST_SCREEN_ID;
         // Update the screen id counter for the provider.
         LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
 
-        if (screenId > Workspace.FIRST_SCREEN_ID) {
-            screenId = Workspace.FIRST_SCREEN_ID;
+        if (screenId > FIRST_SCREEN_ID) {
+            screenId = FIRST_SCREEN_ID;
         }
 
         // Insert the item
@@ -283,15 +272,13 @@
     }
 
     private void verifyWidgetPresent(LauncherAppWidgetProviderInfo info) {
-        UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName())
-                .className(LauncherAppWidgetHostView.class).description(info.label);
-        assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_UI_TIMEOUT));
+        assertTrue("Widget is not present",
+                mLauncher.getWorkspace().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT) != null);
     }
 
     private void verifyPendingWidgetPresent() {
-        UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName())
-                .className(PendingAppWidgetHostView.class);
-        assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_UI_TIMEOUT));
+        assertTrue("Pending widget is not present",
+                mLauncher.getWorkspace().tryGetPendingWidget(DEFAULT_UI_TIMEOUT) != null);
     }
 
     /**
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index 2766a3e..edec914 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -29,7 +29,6 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
 import com.android.launcher3.ItemInfo;
@@ -81,15 +80,10 @@
 
     @Test
     public void testPinWidgetNoConfig() throws Throwable {
-        runTest("pinWidgetNoConfig", true, new ItemOperator() {
-            @Override
-            public boolean evaluate(ItemInfo info, View view) {
-                return info instanceof LauncherAppWidgetInfo &&
-                        ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
-                        ((LauncherAppWidgetInfo) info).providerName.getClassName()
-                                .equals(AppWidgetNoConfig.class.getName());
-            }
-        });
+        runTest("pinWidgetNoConfig", true, (info, view) -> info instanceof LauncherAppWidgetInfo &&
+                ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
+                ((LauncherAppWidgetInfo) info).providerName.getClassName()
+                        .equals(AppWidgetNoConfig.class.getName()));
     }
 
         @Test
@@ -99,28 +93,19 @@
                 RequestPinItemActivity.class, "setRemoteViewColor").putExtra(
                 RequestPinItemActivity.EXTRA_PARAM + "0", Color.RED);
 
-        runTest("pinWidgetNoConfig", true, new ItemOperator() {
-            @Override
-            public boolean evaluate(ItemInfo info, View view) {
-                return info instanceof LauncherAppWidgetInfo &&
-                        ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
-                        ((LauncherAppWidgetInfo) info).providerName.getClassName()
-                                .equals(AppWidgetNoConfig.class.getName());
-            }
-        }, command);
+        runTest("pinWidgetNoConfig", true, (info, view) -> info instanceof LauncherAppWidgetInfo &&
+                ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
+                ((LauncherAppWidgetInfo) info).providerName.getClassName()
+                        .equals(AppWidgetNoConfig.class.getName()), command);
     }
 
     @Test
     public void testPinWidgetWithConfig() throws Throwable {
-        runTest("pinWidgetWithConfig", true, new ItemOperator() {
-            @Override
-            public boolean evaluate(ItemInfo info, View view) {
-                return info instanceof LauncherAppWidgetInfo &&
+        runTest("pinWidgetWithConfig", true,
+                (info, view) -> info instanceof LauncherAppWidgetInfo &&
                         ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
                         ((LauncherAppWidgetInfo) info).providerName.getClassName()
-                                .equals(AppWidgetWithConfig.class.getName());
-            }
-        });
+                                .equals(AppWidgetWithConfig.class.getName()));
     }
 
     @Test
@@ -174,9 +159,7 @@
         // call the requested method to start the flow
         mTargetContext.sendBroadcast(RequestPinItemActivity.getCommandIntent(
                 RequestPinItemActivity.class, activityMethod));
-        UiObject2 widgetCell = mDevice.wait(
-                Until.findObject(By.clazz(WidgetCell.class)), DEFAULT_ACTIVITY_TIMEOUT);
-        assertNotNull(widgetCell);
+        mLauncher.getWidgetCell();
 
         // Accept confirmation:
         BlockingBroadcastReceiver resultReceiver = new BlockingBroadcastReceiver(mCallbackAction);
diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
index 09cc98d..eef2f24 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
@@ -4,7 +4,7 @@
 
 import android.util.Log;
 
-import com.android.launcher3.ui.AbstractLauncherUiTest;
+import androidx.test.uiautomator.UiDevice;
 
 import org.junit.rules.TestWatcher;
 import org.junit.runner.Description;
@@ -16,16 +16,16 @@
 public class FailureWatcher extends TestWatcher {
     private static final String TAG = "FailureWatcher";
     private static int sScreenshotCount = 0;
-    private AbstractLauncherUiTest mAbstractLauncherUiTest;
+    final private UiDevice mDevice;
 
-    public FailureWatcher(AbstractLauncherUiTest abstractLauncherUiTest) {
-        mAbstractLauncherUiTest = abstractLauncherUiTest;
+    public FailureWatcher(UiDevice device) {
+        mDevice = device;
     }
 
     private void dumpViewHierarchy() {
         final ByteArrayOutputStream stream = new ByteArrayOutputStream();
         try {
-            mAbstractLauncherUiTest.getDevice().dumpWindowHierarchy(stream);
+            mDevice.dumpWindowHierarchy(stream);
             stream.flush();
             stream.close();
             for (String line : stream.toString().split("\\r?\\n")) {
@@ -38,7 +38,7 @@
 
     @Override
     protected void failed(Throwable e, Description description) {
-        if (mAbstractLauncherUiTest.getDevice() == null) return;
+        if (mDevice == null) return;
         final String pathname = getInstrumentation().getTargetContext().
                 getFilesDir().getPath() + "/TaplTestScreenshot" + sScreenshotCount++ + ".png";
         Log.e(TAG, "Failed test " + description.getMethodName() +
@@ -48,12 +48,12 @@
         dumpViewHierarchy();
 
         try {
-            final String dumpsysResult = mAbstractLauncherUiTest.getDevice().executeShellCommand(
+            final String dumpsysResult = mDevice.executeShellCommand(
                     "dumpsys activity service TouchInteractionService");
             Log.d(TAG, "TouchInteractionService: " + dumpsysResult);
         } catch (IOException ex) {
         }
 
-        mAbstractLauncherUiTest.getDevice().takeScreenshot(new File(pathname));
+        mDevice.takeScreenshot(new File(pathname));
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 7171bf9..671e8fd 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -502,6 +502,13 @@
         }
     }
 
+    @NonNull
+    public WidgetCell getWidgetCell() {
+        try (LauncherInstrumentation.Closable c = addContextLayer("want to get widget cell")) {
+            return new WidgetCell(this);
+        }
+    }
+
     /**
      * Gets the Overview object if the current state is showing the overview panel. Fails if the
      * launcher is not in that state.
@@ -516,17 +523,6 @@
     }
 
     /**
-     * Gets the Base overview object if either Launcher is in overview state or the fallback
-     * overview activity is showing. Fails otherwise.
-     *
-     * @return BaseOverview object.
-     */
-    @NonNull
-    public BaseOverview getBaseOverview() {
-        return new BaseOverview(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
@@ -617,6 +613,16 @@
     }
 
     @NonNull
+    UiObject2 waitForLauncherObject(BySelector selector) {
+        return waitForObjectBySelector(selector.pkg(getLauncherPackageName()));
+    }
+
+    @NonNull
+    UiObject2 tryWaitForLauncherObject(BySelector selector, long timeout) {
+        return tryWaitForObjectBySelector(selector.pkg(getLauncherPackageName()), timeout);
+    }
+
+    @NonNull
     UiObject2 waitForFallbackLauncherObject(String resName) {
         return waitForObjectBySelector(getFallbackLauncherObjectSelector(resName));
     }
@@ -627,6 +633,10 @@
         return object;
     }
 
+    private UiObject2 tryWaitForObjectBySelector(BySelector selector, long timeout) {
+        return mDevice.wait(Until.findObject(selector), timeout);
+    }
+
     BySelector getLauncherObjectSelector(String resName) {
         return By.res(getLauncherPackageName(), resName);
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/Widget.java b/tests/tapl/com/android/launcher3/tapl/Widget.java
new file mode 100644
index 0000000..128789d
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/Widget.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2019 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 androidx.test.uiautomator.UiObject2;
+
+public class Widget {
+    Widget(LauncherInstrumentation launcher, UiObject2 widget) {
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/WidgetCell.java b/tests/tapl/com/android/launcher3/tapl/WidgetCell.java
new file mode 100644
index 0000000..adb69ec
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/WidgetCell.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2019 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 androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiObject2;
+
+public class WidgetCell {
+    WidgetCell(LauncherInstrumentation launcher) {
+        final UiObject2 widgetCell = launcher.waitForLauncherObject(By.clazz(
+                "com.android.launcher3.widget.WidgetCell"));
+        launcher.assertNotNull("Can't find widget cell object", widgetCell);
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 62050b9..fc0a793 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -27,6 +27,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.Direction;
 import androidx.test.uiautomator.UiObject2;
 
@@ -215,4 +216,19 @@
     protected int getSwipeStartY() {
         return mLauncher.getRealDisplaySize().y - 1;
     }
+
+    @Nullable
+    public Widget tryGetWidget(String label, long timeout) {
+        final UiObject2 widget = mLauncher.tryWaitForLauncherObject(
+                By.clazz("com.android.launcher3.widget.LauncherAppWidgetHostView").desc(label),
+                timeout);
+        return widget != null ? new Widget(mLauncher, widget) : null;
+    }
+
+    @Nullable
+    public Widget tryGetPendingWidget(long timeout) {
+        final UiObject2 widget = mLauncher.tryWaitForLauncherObject(
+                By.clazz("com.android.launcher3.widget.PendingAppWidgetHostView"), timeout);
+        return widget != null ? new Widget(mLauncher, widget) : null;
+    }
 }
\ No newline at end of file