Refactor LauncherAppTransitionManager & polish for new app transitions.

* Add start delay when launcher resumes from all apps or landscape.
* Track the last app transition animator and cancel it before beginning a new one,
  otherwise the animators can conflict with each other.
  ie. Opening an app from all apps and then immediately pressing back to return to
      all apps.
* Use class overrride instead of UiFactory.

Bug: 70220260
Change-Id: I4755d45d820f9d551e443d6c4a148e8789c5bc57
diff --git a/proguard.flags b/proguard.flags
index 987fb6f..b8cade5 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -97,6 +97,11 @@
 # support jar.
 -keep class android.support.v7.widget.RecyclerView { *; }
 
+# LauncherAppTransitionManager
+-keep class com.android.launcher3.LauncherAppTransitionManagerImpl {
+    public <init>(...);
+}
+
 -keep interface com.android.launcher3.userevent.nano.LauncherLogProto.** {
   *;
 }
diff --git a/quickstep/res/values/override.xml b/quickstep/res/values/override.xml
new file mode 100644
index 0000000..ba99d81
--- /dev/null
+++ b/quickstep/res/values/override.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string name="app_transition_manager_class" translatable="false">com.android.launcher3.LauncherAppTransitionManagerImpl</string>
+</resources>
+
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManager.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
similarity index 78%
rename from quickstep/src/com/android/launcher3/LauncherAppTransitionManager.java
rename to quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 47179c5..9968ca7 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -26,6 +26,8 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Matrix;
@@ -55,11 +57,15 @@
 /**
  * Manages the opening and closing app transitions from Launcher.
  */
-public class LauncherAppTransitionManager {
+public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManager {
 
     private static final String TAG = "LauncherTransition";
     private static final int REFRESH_RATE_MS = 16;
 
+    private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
+            "android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
+
+    private static final int LAUNCHER_RESUME_START_DELAY = 150;
     private static final int CLOSING_TRANSITION_DURATION_MS = 350;
 
     // Progress = 0: All apps is fully pulled up, Progress = 1: All apps is fully pulled down.
@@ -76,60 +82,79 @@
     private ImageView mFloatingView;
     private boolean mIsRtl;
 
-    public LauncherAppTransitionManager(Launcher launcher) {
-        mLauncher = launcher;
-        mDragLayer = launcher.getDragLayer();
-        mDeviceProfile = launcher.getDeviceProfile();
+    private Animator mCurrentAnimator;
 
-        mIsRtl = Utilities.isRtl(launcher.getResources());
+    public LauncherAppTransitionManagerImpl(Context context) {
+        mLauncher = Launcher.getLauncher(context);
+        mDragLayer = mLauncher.getDragLayer();
+        mDeviceProfile = mLauncher.getDeviceProfile();
 
-        Resources res = launcher.getResources();
+        mIsRtl = Utilities.isRtl(mLauncher.getResources());
+
+        Resources res = mLauncher.getResources();
         mContentTransY = res.getDimensionPixelSize(R.dimen.content_trans_y);
         mWorkspaceTransY = res.getDimensionPixelSize(R.dimen.workspace_trans_y);
     }
 
+    private void setCurrentAnimator(Animator animator) {
+        if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
+            mCurrentAnimator.cancel();
+        }
+        mCurrentAnimator = animator;
+    }
+
     /**
      * @return A Bundle with remote animations that controls how the window of the opening
      *         targets are displayed.
      */
-    public Bundle getActivityLauncherOptions(View v) {
-        RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mLauncher) {
-            @Override
-            public void onAnimationStart(RemoteAnimationTargetCompat[] targets,
-                    Runnable finishedCallback) {
-                // Post at front of queue ignoring sync barriers to make sure it gets processed
-                // before the next frame.
-                postAtFrontOfQueueAsynchronously(v.getHandler(), () -> {
-                    mAnimator = new AnimatorSet();
-                    mAnimator.play(getLauncherAnimators(v));
-                    mAnimator.play(getWindowAnimators(v, targets));
-                    mAnimator.addListener(new AnimatorListenerAdapter() {
-                        @Override
-                        public void onAnimationEnd(Animator animation) {
-                            // Reset launcher to normal state
-                            v.setVisibility(View.VISIBLE);
-                            ((ViewGroup) mDragLayer.getParent()).removeView(mFloatingView);
+    @Override
+    public Bundle getActivityLaunchOptions(Launcher launcher, View v) {
+        if (hasControlRemoteAppTransitionPermission()) {
+            try {
+                RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mLauncher) {
+                    @Override
+                    public void onAnimationStart(RemoteAnimationTargetCompat[] targets,
+                                                 Runnable finishedCallback) {
+                        // Post at front of queue ignoring sync barriers to make sure it gets
+                        // processed before the next frame.
+                        postAtFrontOfQueueAsynchronously(v.getHandler(), () -> {
+                            mAnimator = new AnimatorSet();
+                            setCurrentAnimator(mAnimator);
+                            mAnimator.play(getLauncherAnimators(v));
+                            mAnimator.play(getWindowAnimators(v, targets));
+                            mAnimator.addListener(new AnimatorListenerAdapter() {
+                                @Override
+                                public void onAnimationEnd(Animator animation) {
+                                    // Reset launcher to normal state
+                                    v.setVisibility(View.VISIBLE);
+                                    ((ViewGroup) mDragLayer.getParent()).removeView(mFloatingView);
 
-                            mDragLayer.setAlpha(1f);
-                            mDragLayer.setTranslationY(0f);
+                                    mDragLayer.setAlpha(1f);
+                                    mDragLayer.setTranslationY(0f);
 
-                            View appsView = mLauncher.getAppsView();
-                            appsView.setAlpha(1f);
-                            appsView.setTranslationY(0f);
+                                    View appsView = mLauncher.getAppsView();
+                                    appsView.setAlpha(1f);
+                                    appsView.setTranslationY(0f);
 
-                            finishedCallback.run();
-                        }
-                    });
-                    mAnimator.start();
-                    // Because t=0 has the app icon in its original spot, we can skip the first
-                    // frame and have the same movement one frame earlier.
-                    mAnimator.setCurrentPlayTime(REFRESH_RATE_MS);
-                });
+                                    finishedCallback.run();
+                                }
+                            });
+                            mAnimator.start();
+                            // Because t=0 has the app icon in its original spot, we can skip the
+                            // first frame and have the same movement one frame earlier.
+                            mAnimator.setCurrentPlayTime(REFRESH_RATE_MS);
+                        });
+                    }
+                };
+
+                return ActivityOptionsCompat.makeRemoteAnimation(
+                        new RemoteAnimationAdapterCompat(runner, 500, 380)).toBundle();
+            } catch (NoClassDefFoundError e) {
+                // Gracefully fall back to default launch options if the user's platform doesn't
+                // have the latest changes.
             }
-        };
-
-        return ActivityOptionsCompat.makeRemoteAnimation(
-                new RemoteAnimationAdapterCompat(runner, 500, 380)).toBundle();
+        }
+        return getDefaultActivityLaunchOptions(launcher, v);
     }
 
     /**
@@ -149,7 +174,7 @@
      *             Else: Animate the content so that it moves downwards and fades out.
      */
     private AnimatorSet getLauncherContentAnimator(boolean show) {
-        AnimatorSet hideLauncher = new AnimatorSet();
+        AnimatorSet launcherAnimator = new AnimatorSet();
 
         float[] alphas = show
                 ? new float[] {0, 1}
@@ -161,6 +186,9 @@
         if (mLauncher.isInState(LauncherState.ALL_APPS) && !mDeviceProfile.isVerticalBarLayout()) {
             // All Apps in portrait mode is full screen, so we only animate AllAppsContainerView.
             View appsView = mLauncher.getAppsView();
+            appsView.setAlpha(alphas[0]);
+            appsView.setTranslationY(trans[0]);
+
             ObjectAnimator alpha = ObjectAnimator.ofFloat(appsView, View.ALPHA, alphas);
             alpha.setDuration(217);
             alpha.setInterpolator(Interpolators.LINEAR);
@@ -168,9 +196,12 @@
             transY.setInterpolator(Interpolators.AGGRESSIVE_EASE);
             transY.setDuration(350);
 
-            hideLauncher.play(alpha);
-            hideLauncher.play(transY);
+            launcherAnimator.play(alpha);
+            launcherAnimator.play(transY);
         } else {
+            mDragLayer.setAlpha(alphas[0]);
+            mDragLayer.setTranslationY(trans[0]);
+
             ObjectAnimator dragLayerAlpha = ObjectAnimator.ofFloat(mDragLayer, View.ALPHA, alphas);
             dragLayerAlpha.setDuration(217);
             dragLayerAlpha.setInterpolator(Interpolators.LINEAR);
@@ -179,10 +210,10 @@
             dragLayerTransY.setInterpolator(Interpolators.AGGRESSIVE_EASE);
             dragLayerTransY.setDuration(350);
 
-            hideLauncher.play(dragLayerAlpha);
-            hideLauncher.play(dragLayerTransY);
+            launcherAnimator.play(dragLayerAlpha);
+            launcherAnimator.play(dragLayerTransY);
         }
-        return hideLauncher;
+        return launcherAnimator;
     }
 
     /**
@@ -361,15 +392,22 @@
     /**
      * Registers remote animations used when closing apps to home screen.
      */
+    @Override
     public void registerRemoteAnimations() {
-        RemoteAnimationDefinitionCompat definition = new RemoteAnimationDefinitionCompat();
-        definition.addRemoteAnimation(WindowManagerWrapper.TRANSIT_WALLPAPER_OPEN,
-                new RemoteAnimationAdapterCompat(getWallpaperOpenRunner(), 0,
-                        CLOSING_TRANSITION_DURATION_MS));
+        if (hasControlRemoteAppTransitionPermission()) {
+            try {
+                RemoteAnimationDefinitionCompat definition = new RemoteAnimationDefinitionCompat();
+                definition.addRemoteAnimation(WindowManagerWrapper.TRANSIT_WALLPAPER_OPEN,
+                        new RemoteAnimationAdapterCompat(getWallpaperOpenRunner(), 0,
+                                CLOSING_TRANSITION_DURATION_MS));
 
 //      TODO: App controlled transition for unlock to home TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER
 
-        new ActivityCompat(mLauncher).registerRemoteAnimations(definition);
+                new ActivityCompat(mLauncher).registerRemoteAnimations(definition);
+            } catch (NoClassDefFoundError e) {
+                // Gracefully fall back if the user's platform doesn't have the latest changes
+            }
+        }
     }
 
     /**
@@ -385,11 +423,13 @@
                 postAtFrontOfQueueAsynchronously(handler, () -> {
                     // We use a separate transition for Overview mode.
                     if (mLauncher.isInState(LauncherState.OVERVIEW)) {
+                        setCurrentAnimator(null);
                         finishedCallback.run();
                         return;
                     }
 
                     mAnimator = new AnimatorSet();
+                    setCurrentAnimator(mAnimator);
                     mAnimator.addListener(new AnimatorListenerAdapter() {
                         @Override
                         public void onAnimationEnd(Animator animation) {
@@ -465,7 +505,9 @@
     private AnimatorSet getLauncherResumeAnimation() {
         if (mLauncher.isInState(LauncherState.ALL_APPS)
                 || mLauncher.getDeviceProfile().isVerticalBarLayout()) {
-            return getLauncherContentAnimator(true /* show */);
+            AnimatorSet contentAnimator = getLauncherContentAnimator(true /* show */);
+            contentAnimator.setStartDelay(LAUNCHER_RESUME_START_DELAY);
+            return contentAnimator;
         } else {
             AnimatorSet workspaceAnimator = new AnimatorSet();
             mLauncher.getWorkspace().setTranslationY(mWorkspaceTransY);
@@ -474,7 +516,7 @@
                     View.TRANSLATION_Y, mWorkspaceTransY, 0));
             workspaceAnimator.play(ObjectAnimator.ofFloat(mLauncher.getWorkspace(), View.ALPHA,
                     0, 1f));
-            workspaceAnimator.setStartDelay(150);
+            workspaceAnimator.setStartDelay(LAUNCHER_RESUME_START_DELAY);
             workspaceAnimator.setDuration(333);
             workspaceAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
 
@@ -489,7 +531,7 @@
 
             Animator allAppsSlideIn =
                     ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS, startY, slideEnd);
-            allAppsSlideIn.setStartDelay(150);
+            allAppsSlideIn.setStartDelay(LAUNCHER_RESUME_START_DELAY);
             allAppsSlideIn.setDuration(317);
             allAppsSlideIn.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
 
@@ -505,6 +547,11 @@
         }
     }
 
+    private boolean hasControlRemoteAppTransitionPermission() {
+        return mLauncher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
     /**
      * Helper method that allows us to get interpolated values for embedded
      * animations with a delay and/or different duration.
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index a004dac..b4f40c2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -25,7 +25,6 @@
 import android.view.View.AccessibilityDelegate;
 
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppTransitionManager;
 import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.BitmapRenderer;
@@ -84,31 +83,4 @@
         RecentsView recents = launcher.getOverviewPanel();
         recents.reset();
     }
-
-    private static boolean hasControlRemoteAppTransitionPermission(Launcher launcher) {
-        return launcher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION)
-                == PackageManager.PERMISSION_GRANTED;
-    }
-
-    public static Bundle getActivityLaunchOptions(Launcher launcher, View v) {
-        if (hasControlRemoteAppTransitionPermission(launcher)) {
-            try {
-                return new LauncherAppTransitionManager(launcher).getActivityLauncherOptions(v);
-            } catch (NoClassDefFoundError e) {
-                // Gracefully fall back to default launch options if the user's platform doesn't
-                // have the latest changes.
-            }
-        }
-        return launcher.getDefaultActivityLaunchOptions(v);
-    }
-
-    public static void registerRemoteAnimations(Launcher launcher) {
-        if (hasControlRemoteAppTransitionPermission(launcher)) {
-            try {
-                new LauncherAppTransitionManager(launcher).registerRemoteAnimations();
-            } catch (NoClassDefFoundError e) {
-                // Gracefully fall back if the user's platform doesn't have the latest changes
-            }
-        }
-    }
 }
diff --git a/res/values/config.xml b/res/values/config.xml
index 2096200..3dddac2 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -92,6 +92,9 @@
     <!-- Name of a user event dispatcher class. -->
     <string name="user_event_dispatcher_class" translatable="false"></string>
 
+    <!-- Name of an app transition manager class. -->
+    <string name="app_transition_manager_class" translatable="false"></string>
+
     <!-- Name of a color extraction implementation class. -->
     <string name="color_extraction_impl_class" translatable="false"></string>
 
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 5db0a3b..a91907d 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -214,6 +214,8 @@
     private static final int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
     @Thunk static final int NEW_APPS_ANIMATION_DELAY = 500;
 
+    private LauncherAppTransitionManager mAppTransitionManager;
+
     @Thunk Workspace mWorkspace;
     private View mLauncherView;
     @Thunk DragLayer mDragLayer;
@@ -403,8 +405,11 @@
         getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
                 Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
 
+        mAppTransitionManager = Utilities.getOverrideObject(LauncherAppTransitionManager.class,
+                        this, R.string.app_transition_manager_class);
+
         if (!isInMultiWindowModeCompat()) {
-            UiFactory.registerRemoteAnimations(this);
+            mAppTransitionManager.registerRemoteAnimations();
         }
 
         if (mLauncherCallbacks != null) {
@@ -1918,38 +1923,11 @@
         }
     }
 
-    public Bundle getDefaultActivityLaunchOptions(View v) {
-        if (Utilities.ATLEAST_MARSHMALLOW) {
-            int left = 0, top = 0;
-            int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
-            if (v instanceof BubbleTextView) {
-                // Launch from center of icon, not entire view
-                Drawable icon = ((BubbleTextView) v).getIcon();
-                if (icon != null) {
-                    Rect bounds = icon.getBounds();
-                    left = (width - bounds.width()) / 2;
-                    top = v.getPaddingTop();
-                    width = bounds.width();
-                    height = bounds.height();
-                }
-            }
-            return ActivityOptions.makeClipRevealAnimation(v, left, top, width, height)
-                    .toBundle();
-        } else if (Utilities.ATLEAST_LOLLIPOP_MR1) {
-            // On L devices, we use the device default slide-up transition.
-            // On L MR1 devices, we use a custom version of the slide-up transition which
-            // doesn't have the delay present in the device default.
-            return ActivityOptions.makeCustomAnimation(
-                    this, R.anim.task_open_enter, R.anim.no_anim).toBundle();
-        }
-        return null;
-    }
-
     @TargetApi(Build.VERSION_CODES.M)
     public Bundle getActivityLaunchOptions(View v, boolean useDefaultLaunchOptions) {
         return useDefaultLaunchOptions
-                ? getDefaultActivityLaunchOptions(v)
-                : UiFactory.getActivityLaunchOptions(this, v);
+                ? mAppTransitionManager.getDefaultActivityLaunchOptions(this, v)
+                : mAppTransitionManager.getActivityLaunchOptions(this, v);
     }
 
     public Rect getViewBounds(View v) {
diff --git a/src/com/android/launcher3/LauncherAppTransitionManager.java b/src/com/android/launcher3/LauncherAppTransitionManager.java
new file mode 100644
index 0000000..9d68dc9
--- /dev/null
+++ b/src/com/android/launcher3/LauncherAppTransitionManager.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+
+import android.app.ActivityOptions;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.View;
+
+/**
+ * Manages the opening and closing app transitions from Launcher.
+ */
+public class LauncherAppTransitionManager {
+
+    public Bundle getDefaultActivityLaunchOptions(Launcher launcher, View v) {
+        if (Utilities.ATLEAST_MARSHMALLOW) {
+            int left = 0, top = 0;
+            int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
+            if (v instanceof BubbleTextView) {
+                // Launch from center of icon, not entire view
+                Drawable icon = ((BubbleTextView) v).getIcon();
+                if (icon != null) {
+                    Rect bounds = icon.getBounds();
+                    left = (width - bounds.width()) / 2;
+                    top = v.getPaddingTop();
+                    width = bounds.width();
+                    height = bounds.height();
+                }
+            }
+            return ActivityOptions.makeClipRevealAnimation(v, left, top, width, height)
+                    .toBundle();
+        } else if (Utilities.ATLEAST_LOLLIPOP_MR1) {
+            // On L devices, we use the device default slide-up transition.
+            // On L MR1 devices, we use a custom version of the slide-up transition which
+            // doesn't have the delay present in the device default.
+            return ActivityOptions.makeCustomAnimation(launcher, R.anim.task_open_enter,
+                    R.anim.no_anim).toBundle();
+        }
+        return null;
+    }
+
+    public Bundle getActivityLaunchOptions(Launcher launcher, View v) {
+        return getDefaultActivityLaunchOptions(launcher, v);
+    }
+
+    public void registerRemoteAnimations() {
+    }
+}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
index c857bf6..744125e 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
@@ -61,10 +61,4 @@
     }
 
     public static void resetOverview(Launcher launcher) { }
-
-    public static Bundle getActivityLaunchOptions(Launcher launcher, View v) {
-        return launcher.getDefaultActivityLaunchOptions(v);
-    }
-
-    public static void registerRemoteAnimations(Launcher launcher) { }
 }