Merge "Separate some elements to set visibility separately" into ub-launcher3-master
diff --git a/quickstep/res/layout/overview_panel.xml b/quickstep/res/layout/overview_panel.xml
index 54a90cf..e2f9ba8 100644
--- a/quickstep/res/layout/overview_panel.xml
+++ b/quickstep/res/layout/overview_panel.xml
@@ -22,6 +22,7 @@
     android:clipChildren="false"
     android:clipToPadding="false"
     android:alpha="0.0"
-    android:visibility="invisible" >
+    android:visibility="invisible"
+    android:focusableInTouchMode="true" >
 
 </com.android.quickstep.views.LauncherRecentsView>
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index b43352a..9e2e5ac 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -28,11 +28,14 @@
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.Looper;
+import android.support.annotation.Nullable;
+import android.support.annotation.UiThread;
 import android.view.View;
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherInitListener;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.allapps.AllAppsTransitionController;
@@ -79,6 +82,13 @@
     void startRecents(Context context, Intent intent, AssistDataReceiver assistDataReceiver,
             RecentsAnimationListener remoteAnimationListener);
 
+    @UiThread
+    @Nullable
+    RecentsView getVisibleRecentsView();
+
+    @UiThread
+    boolean switchToRecentsIfVisible();
+
     class LauncherActivityControllerHelper implements ActivityControlHelper<Launcher> {
 
         @Override
@@ -198,6 +208,36 @@
             ActivityManagerWrapper.getInstance().startRecentsActivity(
                     intent, assistDataReceiver, remoteAnimationListener, null, null);
         }
+
+        @Nullable
+        @UiThread
+        private Launcher getVisibleLaucher() {
+            LauncherAppState app = LauncherAppState.getInstanceNoCreate();
+            if (app == null) {
+                return null;
+            }
+            Launcher launcher = (Launcher) app.getModel().getCallback();
+            return (launcher != null) && launcher.isStarted() && launcher.hasWindowFocus() ?
+                    launcher : null;
+        }
+
+        @Nullable
+        @Override
+        public RecentsView getVisibleRecentsView() {
+            Launcher launcher = getVisibleLaucher();
+            return launcher != null && launcher.isInState(OVERVIEW)
+                    ? launcher.getOverviewPanel() : null;
+        }
+
+        @Override
+        public boolean switchToRecentsIfVisible() {
+            Launcher launcher = getVisibleLaucher();
+            if (launcher != null) {
+                launcher.getStateManager().goToState(OVERVIEW);
+                return true;
+            }
+            return false;
+        }
     }
 
     class FallbackActivityControllerHelper implements ActivityControlHelper<RecentsActivity> {
@@ -292,6 +332,21 @@
                             new FallbackActivityOptions(remoteAnimationListener), 10000, 10000));
             context.startActivity(intent, options.toBundle());
         }
+
+        @Nullable
+        @Override
+        public RecentsView getVisibleRecentsView() {
+            RecentsActivity activity = RecentsActivityTracker.getCurrentActivity();
+            if (activity != null && activity.hasWindowFocus()) {
+                return activity.getOverviewPanel();
+            }
+            return null;
+        }
+
+        @Override
+        public boolean switchToRecentsIfVisible() {
+            return false;
+        }
     }
 
     interface LayoutListener {
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index f60473f..311411f 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -17,25 +17,54 @@
 
 import static com.android.launcher3.LauncherState.OVERVIEW;
 
+import android.animation.Animator;
+import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RecentTaskInfo;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityOptions;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.support.annotation.UiThread;
+import android.support.annotation.WorkerThread;
+import android.util.SparseArray;
 import android.view.ViewConfiguration;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.states.InternalStateHandler;
+import com.android.launcher3.util.TraceHelper;
 import com.android.quickstep.ActivityControlHelper.FallbackActivityControllerHelper;
 import com.android.quickstep.ActivityControlHelper.LauncherActivityControllerHelper;
 import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
+import com.android.systemui.shared.recents.view.RecentsTransition;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.AssistDataReceiver;
+import com.android.systemui.shared.system.BackgroundExecutor;
+import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
+import com.android.systemui.shared.system.RecentsAnimationListener;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * Helper class to handle various atomic commands for switching between Overview.
@@ -43,19 +72,39 @@
 @TargetApi(Build.VERSION_CODES.P)
 public class OverviewCommandHelper extends InternalStateHandler {
 
+    private static final int RID_RESET_SWIPE_HANDLER = 0;
+    private static final int RID_CANCEL_CONTROLLER = 1;
+    private static final int RID_CANCEL_ZOOM_OUT_ANIMATION = 2;
+
+    private static final long RECENTS_LAUNCH_DURATION = 150;
+
+    private static final String TAG = "OverviewCommandHelper";
     private static final boolean DEBUG_START_FALLBACK_ACTIVITY = false;
 
     private final Context mContext;
     private final ActivityManagerWrapper mAM;
+    private final RecentsModel mRecentsModel;
+    private final MainThreadExecutor mMainThreadExecutor;
 
     public final Intent homeIntent;
     public final ComponentName launcher;
 
+    private final SparseArray<Runnable> mCurrentCommandFinishRunnables = new SparseArray<>();
+    // Monotonically increasing command ids.
+    private int mCurrentCommandId = 0;
+
     private long mLastToggleTime;
+    private WindowTransformSwipeHandler mWindowTransformSwipeHandler;
+
+    private final Point mWindowSize = new Point();
+    private final Rect mTaskTargetRect = new Rect();
+    private final RectF mTempTaskTargetRect = new RectF();
 
     public OverviewCommandHelper(Context context) {
         mContext = context;
         mAM = ActivityManagerWrapper.getInstance();
+        mMainThreadExecutor = new MainThreadExecutor();
+        mRecentsModel = RecentsModel.getInstance(mContext);
 
         homeIntent = new Intent(Intent.ACTION_MAIN)
                 .addCategory(Intent.CATEGORY_HOME)
@@ -81,26 +130,152 @@
         initWhenReady();
     }
 
+    @UiThread
+    private void addFinishCommand(int requestId, int id, Runnable action) {
+        if (requestId < mCurrentCommandId) {
+            action.run();
+        } else {
+            mCurrentCommandFinishRunnables.put(id, action);
+        }
+    }
+
+    @UiThread
+    private void clearFinishCommand(int requestId, int id) {
+        if (requestId == mCurrentCommandId) {
+            mCurrentCommandFinishRunnables.remove(id);
+        }
+    }
+
+    @UiThread
+    private void initSwipeHandler(ActivityControlHelper helper, long time,
+            Consumer<WindowTransformSwipeHandler> onAnimationInitCallback) {
+        final int commandId = mCurrentCommandId;
+        RunningTaskInfo taskInfo = ActivityManagerWrapper.getInstance().getRunningTask();
+        final WindowTransformSwipeHandler handler =
+                new WindowTransformSwipeHandler(taskInfo, mContext, time, helper);
+
+        // Preload the plan
+        mRecentsModel.loadTasks(taskInfo.id, null);
+        mWindowTransformSwipeHandler = handler;
+
+        mTempTaskTargetRect.setEmpty();
+        handler.setGestureEndCallback(() -> {
+            if (mWindowTransformSwipeHandler == handler) {
+                mWindowTransformSwipeHandler = null;
+                mTempTaskTargetRect.setEmpty();
+            }
+            clearFinishCommand(commandId, RID_RESET_SWIPE_HANDLER);
+            clearFinishCommand(commandId, RID_CANCEL_CONTROLLER);
+        });
+        handler.initWhenReady();
+        addFinishCommand(commandId, RID_RESET_SWIPE_HANDLER, handler::reset);
+
+        TraceHelper.beginSection(TAG);
+        Runnable startActivity = () -> helper.startRecents(mContext, homeIntent,
+                new AssistDataReceiver() {
+                    @Override
+                    public void onHandleAssistData(Bundle bundle) {
+                        mRecentsModel.preloadAssistData(taskInfo.id, bundle);
+                    }
+                },
+                new RecentsAnimationListener() {
+                    public void onAnimationStart(
+                            RecentsAnimationControllerCompat controller,
+                            RemoteAnimationTargetCompat[] apps, Rect homeContentInsets,
+                            Rect minimizedHomeBounds) {
+                        if (mWindowTransformSwipeHandler == handler) {
+                            TraceHelper.partitionSection(TAG, "Received");
+                            handler.onRecentsAnimationStart(controller, apps, homeContentInsets,
+                                    minimizedHomeBounds);
+                            mTempTaskTargetRect.set(handler.getTargetRect(mWindowSize));
+
+                            mMainThreadExecutor.execute(() -> {
+                                addFinishCommand(commandId,
+                                        RID_CANCEL_CONTROLLER, () -> controller.finish(true));
+                                if (commandId == mCurrentCommandId) {
+                                    onAnimationInitCallback.accept(handler);
+                                }
+                            });
+                        } else {
+                            TraceHelper.endSection(TAG, "Finishing no handler");
+                            controller.finish(false /* toHome */);
+                        }
+                    }
+
+                    public void onAnimationCanceled() {
+                        TraceHelper.endSection(TAG, "Cancelled: " + handler);
+                        if (mWindowTransformSwipeHandler == handler) {
+                            handler.onRecentsAnimationCanceled();
+                        }
+                    }
+                });
+
+        // We should almost always get touch-town on background thread. This is an edge case
+        // when the background Choreographer has not yet initialized.
+        BackgroundExecutor.get().submit(startActivity);
+    }
+
+    @UiThread
+    private void startZoomOutAnim(final WindowTransformSwipeHandler handler) {
+        final int commandId = mCurrentCommandId;
+        ValueAnimator anim = ValueAnimator.ofInt(0, -handler.getTransitionLength());
+        anim.addUpdateListener((a) -> handler.updateDisplacement((Integer) a.getAnimatedValue()));
+        anim.addListener(new AnimationSuccessListener() {
+            @Override
+            public void onAnimationSuccess(Animator animator) {
+                handler.onGestureEnded(0);
+                clearFinishCommand(commandId, RID_CANCEL_ZOOM_OUT_ANIMATION);
+            }
+        });
+        handler.onGestureStarted();
+        anim.setDuration(RECENTS_LAUNCH_DURATION);
+        anim.setInterpolator(Interpolators.AGGRESSIVE_EASE);
+        anim.start();
+        addFinishCommand(commandId, RID_CANCEL_ZOOM_OUT_ANIMATION, anim::cancel);
+    }
+
     public void onOverviewToggle() {
-        getLauncher().runOnUiThread(() -> {
-                    if (isUsingFallbackActivity()) {
-                        mContext.startActivity(new Intent(mContext, RecentsActivity.class)
-                                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent
-                                        .FLAG_ACTIVITY_CLEAR_TASK));
-                        return;
-                    }
+        long time = SystemClock.elapsedRealtime();
+        mMainThreadExecutor.execute(() -> {
+            long elapsedTime = time - mLastToggleTime;
+            mLastToggleTime = time;
 
-                    long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime;
-                    mLastToggleTime = SystemClock.elapsedRealtime();
-
-                    if (isOverviewAlmostVisible()) {
-                        boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout();
-                        startNonLauncherTask(isQuickTap ? 2 : 1);
-                    } else {
-                        openRecents();
-                    }
+            mCurrentCommandId++;
+            mTempTaskTargetRect.round(mTaskTargetRect);
+            boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout();
+            int runnableCount = mCurrentCommandFinishRunnables.size();
+            if (runnableCount > 0) {
+                for (int i = 0; i < runnableCount; i++) {
+                    mCurrentCommandFinishRunnables.valueAt(i).run();
                 }
-        );
+                mCurrentCommandFinishRunnables.clear();
+                isQuickTap = true;
+            }
+
+            ActivityControlHelper helper = getActivityControlHelper();
+            RecentsView recents = helper.getVisibleRecentsView();
+            if (recents != null) {
+                int childCount = recents.getChildCount();
+                if (childCount != 0) {
+                    ((TaskView) recents.getChildAt(childCount >= 2 ? 1 : 0)).launchTask(true);
+                }
+
+                // There are not enough tasks. Skip
+                return;
+            }
+
+            if (isQuickTap) {
+                // Focus last task. Start is on background thread so that all ActivityManager calls
+                // are serialized
+                BackgroundExecutor.get().submit(this::startLastTask);
+                return;
+            }
+            if (helper.switchToRecentsIfVisible()) {
+                return;
+            }
+
+            initSwipeHandler(helper, time, this::startZoomOutAnim);
+        });
     }
 
     public void onOverviewShown() {
@@ -125,13 +300,35 @@
         );
     }
 
-    private void startNonLauncherTask(int backStackCount) {
-        for (RecentTaskInfo rti : mAM.getRecentTasks(backStackCount, UserHandle.myUserId())) {
-            backStackCount--;
-            if (backStackCount == 0) {
-                mAM.startActivityFromRecents(rti.id, null);
-                break;
+    @WorkerThread
+    private void startLastTask() {
+        // TODO: This should go through recents model.
+        List<RecentTaskInfo> tasks = mAM.getRecentTasks(2, UserHandle.myUserId());
+        if (tasks.size() > 1) {
+            RecentTaskInfo rti = tasks.get(1);
+
+            final ActivityOptions options;
+            if (!mTaskTargetRect.isEmpty()) {
+                final Rect targetRect = new Rect(mTaskTargetRect);
+                targetRect.offset(Utilities.isRtl(mContext.getResources())
+                        ? - mTaskTargetRect.width() : mTaskTargetRect.width(), 0);
+                final AppTransitionAnimationSpecCompat specCompat =
+                        new AppTransitionAnimationSpecCompat(rti.id, null, targetRect);
+                AppTransitionAnimationSpecsFuture specFuture =
+                        new AppTransitionAnimationSpecsFuture(mMainThreadExecutor.getHandler()) {
+
+                    @Override
+                    public List<AppTransitionAnimationSpecCompat> composeSpecs() {
+                        return Collections.singletonList(specCompat);
+                    }
+                };
+                options = RecentsTransition.createAspectScaleAnimation(mContext,
+                        mMainThreadExecutor.getHandler(), true /* scaleUp */,
+                        specFuture, () -> {});
+            } else {
+                options = ActivityOptions.makeBasic();
             }
+            mAM.startActivityFromRecents(rti.id, options);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 12e1a2b..e579205 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -93,4 +93,10 @@
         super.onTrimMemory(level);
         UiFactory.onTrimMemory(this, level);
     }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        RecentsActivityTracker.onRecentsActivityDestroy(this);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
index 6a82dc0..5bd606e 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
@@ -31,6 +31,7 @@
 
     private static final Object LOCK = new Object();
     private static WeakReference<RecentsActivityTracker> sTracker = new WeakReference<>(null);
+    private static WeakReference<RecentsActivity> sCurrentActivity = new WeakReference<>(null);
 
     private final BiPredicate<RecentsActivity, Boolean> mOnInitListener;
 
@@ -60,6 +61,21 @@
             if (tracker != null && tracker.mOnInitListener.test(activity, false)) {
                 sTracker.clear();
             }
+            sCurrentActivity = new WeakReference<>(activity);
+        }
+    }
+
+    public static void onRecentsActivityDestroy(RecentsActivity activity) {
+        synchronized (LOCK) {
+            if (sCurrentActivity.get() == activity) {
+                sCurrentActivity.clear();
+            }
+        }
+    }
+
+    public static RecentsActivity getCurrentActivity() {
+        synchronized (LOCK) {
+            return sCurrentActivity.get();
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 2d2a483..f0bb367 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -301,6 +301,15 @@
         mSourceRect.set(scaledTargetRect);
     }
 
+    public int getTransitionLength() {
+        return mTransitionDragLength;
+    }
+
+    public RectF getTargetRect(Point outWindowSize) {
+        outWindowSize.set(mDp.widthPx, mDp.heightPx);
+        return mInitialTargetRect;
+    }
+
     private long getFadeInDuration() {
         if (mCurrentShift.getCurrentAnimation() != null) {
             ObjectAnimator anim = mCurrentShift.getCurrentAnimation();
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 9c0e580..2709f40 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -610,6 +610,16 @@
     }
 
     @Override
+    public void onVisibilityAggregated(boolean isVisible) {
+        super.onVisibilityAggregated(isVisible);
+        if (isVisible && !isFocused()) {
+            // Having focus, even in touch mode, keeps us from losing [Alt+]Tab by preventing
+            // switching to keyboard mode.
+            requestFocus();
+        }
+    }
+
+    @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
         if (event.getKeyCode() == KeyEvent.KEYCODE_TAB
                 && event.getAction() == KeyEvent.ACTION_DOWN) {