Adding support for listening for app launch animation completion

Bug: 181165935
Bug: 179065491
Test: Verified on device
Change-Id: Ifa6a91560cb31b4dfb72a0f582607e873d8a002d
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 062ab71..f77f7e8 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -279,7 +279,7 @@
     /**
      * Used to set the override visibility state, used only to handle the transition home with the
      * recents animation.
-     * @see QuickstepAppTransitionManagerImpl#createWallpaperOpenRunner
+     * @see QuickstepTransitionManager#createWallpaperOpenRunner
      */
     public void addForceInvisibleFlag(@InvisibilityFlags int flag) {
         mForceInvisible |= flag;
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 5bfde15..e38ab74 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -27,6 +27,7 @@
 import android.graphics.Insets;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Process;
 import android.os.StrictMode;
@@ -40,6 +41,7 @@
 import android.view.WindowMetrics;
 import android.widget.Toast;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.LauncherSettings.Favorites;
@@ -52,10 +54,12 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
+import com.android.launcher3.util.ActivityOptionsWrapper;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
 import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.util.WindowBounds;
@@ -76,6 +80,7 @@
     protected boolean mIsSafeModeEnabled;
 
     private Runnable mOnStartCallback;
+    private RunnableList mOnResumeCallbacks = new RunnableList();
 
     private int mThemeRes = R.style.AppTheme;
 
@@ -98,6 +103,16 @@
     }
 
     @Override
+    protected void onResume() {
+        super.onResume();
+        mOnResumeCallbacks.executeAllAndClear();
+    }
+
+    public void addOnResumeCallback(Runnable callback) {
+        mOnResumeCallbacks.add(callback);
+    }
+
+    @Override
     public void onExtractedColorsChanged(WallpaperColorInfo wallpaperColorInfo) {
         updateTheme();
     }
@@ -149,20 +164,35 @@
         return new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight());
     }
 
-    public final Bundle getActivityLaunchOptionsAsBundle(View v) {
-        ActivityOptions activityOptions = getActivityLaunchOptions(v);
-        return activityOptions == null ? null : activityOptions.toBundle();
+    @NonNull
+    public ActivityOptionsWrapper getActivityLaunchOptions(View v) {
+        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();
+            }
+        }
+        ActivityOptions options =
+                ActivityOptions.makeClipRevealAnimation(v, left, top, width, height);
+        RunnableList callback = new RunnableList();
+        addOnResumeCallback(callback::executeAllAndDestroy);
+        return new ActivityOptionsWrapper(options, callback);
     }
 
-    public abstract ActivityOptions getActivityLaunchOptions(View v);
-
     public boolean startActivitySafely(View v, Intent intent, @Nullable ItemInfo item) {
         if (mIsSafeModeEnabled && !PackageManagerHelper.isSystemApp(this, intent)) {
             Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
             return false;
         }
 
-        Bundle optsBundle = (v != null) ? getActivityLaunchOptionsAsBundle(v) : null;
+        Bundle optsBundle = (v != null) ? getActivityLaunchOptions(v).toBundle() : null;
         UserHandle user = item == null ? null : item.user;
 
         // Prepare intent
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index e0be6de..2b58fb6 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -50,7 +50,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
-import com.android.launcher3.Launcher.OnResumeCallback;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dot.DotInfo;
@@ -81,7 +80,7 @@
  * because we want to make the bubble taller than the text and TextView's clip is
  * too aggressive.
  */
-public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback,
+public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
         IconLabelDotView, DraggableView, Reorderable {
 
     private static final int DISPLAY_WORKSPACE = 0;
@@ -431,13 +430,6 @@
         }
     }
 
-    @Override
-    public void onLauncherResume() {
-        // Reset the pressed state of icon that was locked in the press state while activity
-        // was launching
-        setStayPressed(false);
-    }
-
     void clearPressedBackground() {
         setPressed(false);
         setStayPressed(false);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 78e6f68..1546ee3 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -61,7 +61,6 @@
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
-import android.app.ActivityOptions;
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetManager;
 import android.content.ActivityNotFoundException;
@@ -276,7 +275,6 @@
 
     private static final int THEME_CROSS_FADE_ANIMATION_DURATION = 375;
 
-    private LauncherAppTransitionManager mAppTransitionManager;
     private Configuration mOldConfig;
 
     private LiveSearchManager mLiveSearchManager;
@@ -312,8 +310,6 @@
     @Thunk
     boolean mWorkspaceLoading = true;
 
-    private ArrayList<OnResumeCallback> mOnResumeCallbacks = new ArrayList<>();
-
     // Used to notify when an activity launch has been deferred because launcher is not yet resumed
     // TODO: See if we can remove this later
     private Runnable mOnDeferredActivityLaunchCallback;
@@ -419,9 +415,6 @@
         crossFadeWithPreviousAppearance();
         mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
 
-        mAppTransitionManager = LauncherAppTransitionManager.newInstance(this);
-        mAppTransitionManager.registerRemoteAnimations();
-
         boolean internalStateHandled = ACTIVITY_TRACKER.handleCreate(this);
         if (internalStateHandled) {
             if (savedInstanceState != null) {
@@ -1090,15 +1083,6 @@
                 TraceHelper.FLAG_UI_EVENT);
         super.onResume();
 
-        if (!mOnResumeCallbacks.isEmpty()) {
-            final ArrayList<OnResumeCallback> resumeCallbacks = new ArrayList<>(mOnResumeCallbacks);
-            mOnResumeCallbacks.clear();
-            for (int i = resumeCallbacks.size() - 1; i >= 0; i--) {
-                resumeCallbacks.get(i).onLauncherResume();
-            }
-            resumeCallbacks.clear();
-        }
-
         if (mDeferOverlayCallbacks) {
             scheduleDeferredCheck();
         } else {
@@ -1609,7 +1593,6 @@
         LauncherAppState.getIDP(this).removeOnChangeListener(this);
 
         mOverlayManager.onActivityDestroyed(this);
-        mAppTransitionManager.onActivityDestroyed();
         mUserChangedCallbackCloseable.close();
         mLiveSearchManager.stop();
     }
@@ -1935,16 +1918,6 @@
 
     @TargetApi(Build.VERSION_CODES.M)
     @Override
-    public ActivityOptions getActivityLaunchOptions(View v) {
-        return mAppTransitionManager.getActivityLaunchOptions(this, v);
-    }
-
-    public LauncherAppTransitionManager getAppTransitionManager() {
-        return mAppTransitionManager;
-    }
-
-    @TargetApi(Build.VERSION_CODES.M)
-    @Override
     protected boolean onErrorStartingShortcut(Intent intent, ItemInfo info) {
         // Due to legacy reasons, direct call shortcuts require Launchers to have the
         // corresponding permission. Show the appropriate permission prompt if that
@@ -1993,7 +1966,7 @@
             // state when we return to launcher.
             BubbleTextView btv = (BubbleTextView) v;
             btv.setStayPressed(true);
-            addOnResumeCallback(btv);
+            addOnResumeCallback(() -> btv.setStayPressed(false));
         }
         return success;
     }
@@ -2037,10 +2010,6 @@
         return result;
     }
 
-    public void addOnResumeCallback(OnResumeCallback callback) {
-        mOnResumeCallbacks.add(callback);
-    }
-
     /**
      * Persistant callback which notifies when an activity launch is deferred because the activity
      * was not yet resumed.
@@ -2814,15 +2783,6 @@
         return (T) activityContext;
     }
 
-
-    /**
-     * Callback for listening for onResume
-     */
-    public interface OnResumeCallback {
-
-        void onLauncherResume();
-    }
-
     /**
      * Cross-fades the launcher's updated appearance with its previous appearance.
      *
@@ -2865,6 +2825,10 @@
         return false;
     }
 
+    public boolean supportsAdaptiveIconAnimation(View clickedView) {
+        return false;
+    }
+
     public DragOptions getDefaultWorkspaceDragOptions() {
         return new DragOptions();
     }
diff --git a/src/com/android/launcher3/LauncherAppTransitionManager.java b/src/com/android/launcher3/LauncherAppTransitionManager.java
deleted file mode 100644
index d0bf577..0000000
--- a/src/com/android/launcher3/LauncherAppTransitionManager.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3;
-
-import android.app.ActivityOptions;
-import android.content.Context;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.view.View;
-
-import com.android.launcher3.util.ResourceBasedOverride;
-
-/**
- * Manages the opening and closing app transitions from Launcher.
- */
-public class LauncherAppTransitionManager implements ResourceBasedOverride {
-
-    public static LauncherAppTransitionManager newInstance(Context context) {
-        return Overrides.getObject(LauncherAppTransitionManager.class,
-                context, R.string.app_transition_manager_class);
-    }
-
-    public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) {
-        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);
-    }
-
-    public boolean supportsAdaptiveIconAnimation(View clickedView) {
-        return false;
-    }
-
-    /**
-     * Registers remote animations for certain system transitions.
-     */
-    public void registerRemoteAnimations() {
-        // Do nothing
-    }
-
-    /**
-     * Handles clean up when activity is destroyed.
-     */
-    public void onActivityDestroyed() {
-        // Do nothing
-    }
-
-    /**
-     * Unregisters all remote animations.
-     */
-    public void unregisterRemoteAnimations() {
-        // Do nothing
-    }
-
-    /**
-     * Registers remote transitions for certain system transitions.
-     */
-    public void registerRemoteTransitions() {
-        // Do nothing
-    }
-
-    /**
-     * Unregisters all remote transitions.
-     */
-    public void unregisterRemoteTransitions() {
-        // Do nothing
-    }
-}
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 4fd87cb..8bc5ad0 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -33,7 +33,6 @@
 import android.view.View;
 import android.widget.Toast;
 
-import com.android.launcher3.Launcher.OnResumeCallback;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.logging.FileLog;
@@ -228,7 +227,7 @@
             DeferredOnComplete deferred = (DeferredOnComplete) d.dragSource;
             if (target != null) {
                 deferred.mPackageName = target.getPackageName();
-                mLauncher.addOnResumeCallback(deferred);
+                mLauncher.addOnResumeCallback(deferred::onLauncherResume);
             } else {
                 deferred.sendFailure();
             }
@@ -311,7 +310,7 @@
      * A wrapper around {@link DragSource} which delays the {@link #onDropCompleted} action until
      * {@link #onLauncherResume}
      */
-    private class DeferredOnComplete implements DragSource, OnResumeCallback {
+    private class DeferredOnComplete implements DragSource {
 
         private final DragSource mOriginal;
         private final Context mContext;
@@ -330,7 +329,6 @@
             mDragObject = d;
         }
 
-        @Override
         public void onLauncherResume() {
             // We use MATCH_UNINSTALLED_PACKAGES as the app can be on SD card as well.
             if (new PackageManagerHelper(mContext).getApplicationInfo(mPackageName,
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index 0e8d4ae..f5e74b7 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -17,7 +17,6 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.app.ActivityOptions;
 import android.content.Intent;
 import android.os.Bundle;
 import android.view.View;
@@ -168,11 +167,6 @@
     }
 
     @Override
-    public ActivityOptions getActivityLaunchOptions(View v) {
-        return null;
-    }
-
-    @Override
     protected void reapplyUi() { }
 
     @Override
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 61bd30a..2e54904 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -180,7 +180,7 @@
                 LauncherApps launcherApps = launcher.getSystemService(LauncherApps.class);
                 try {
                     launcherApps.startPackageInstallerSessionDetailsActivity(sessionInfo, null,
-                            launcher.getActivityLaunchOptionsAsBundle(v));
+                            launcher.getActivityLaunchOptions(v).toBundle());
                     return;
                 } catch (Exception e) {
                     Log.e(TAG, "Unable to launch market intent for package=" + packageName, e);
@@ -304,7 +304,7 @@
                 intent.setPackage(null);
             }
         }
-        if (v != null && launcher.getAppTransitionManager().supportsAdaptiveIconAnimation(v)) {
+        if (v != null && launcher.supportsAdaptiveIconAnimation(v)) {
             // Preload the icon to reduce latency b/w swapping the floating view with the original.
             FloatingIconView.fetchIcon(launcher, v, item, true /* isOpening */);
         }
diff --git a/src/com/android/launcher3/util/ActivityOptionsWrapper.java b/src/com/android/launcher3/util/ActivityOptionsWrapper.java
new file mode 100644
index 0000000..99cc1f7
--- /dev/null
+++ b/src/com/android/launcher3/util/ActivityOptionsWrapper.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+
+import android.app.ActivityOptions;
+import android.os.Bundle;
+
+/**
+ * A wrapper around {@link ActivityOptions} to allow custom functionality in launcher
+ */
+public class ActivityOptionsWrapper {
+
+    public final ActivityOptions options;
+    public final RunnableList onEndCallback;
+
+    public ActivityOptionsWrapper(ActivityOptions options, RunnableList onEndCallback) {
+        this.options = options;
+        this.onEndCallback = onEndCallback;
+    }
+
+    /**
+     * @see {@link ActivityOptions#toBundle()}
+     */
+    public Bundle toBundle() {
+        return options.toBundle();
+    }
+}
diff --git a/src/com/android/launcher3/util/RunnableList.java b/src/com/android/launcher3/util/RunnableList.java
new file mode 100644
index 0000000..55add14
--- /dev/null
+++ b/src/com/android/launcher3/util/RunnableList.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class to hold a list of runnable
+ */
+public class RunnableList {
+
+    private ArrayList<Runnable> mList = null;
+    private boolean mDestroyed = false;
+
+    /**
+     * Ads a runnable to this list
+     */
+    public void add(Runnable runnable) {
+        if (mDestroyed) {
+            runnable.run();
+            return;
+        }
+        if (mList == null) {
+            mList = new ArrayList<>();
+        }
+        mList.add(runnable);
+    }
+
+    /**
+     * Destroys the list, executing any pending callbacks. All new callbacks are
+     * immediately executed
+     */
+    public void executeAllAndDestroy() {
+        mDestroyed = true;
+        executeAllAndClear();
+    }
+
+    /**
+     * Executes all previously added runnable and clears the list
+     */
+    public void executeAllAndClear() {
+        if (mList != null) {
+            ArrayList<Runnable> list = mList;
+            mList = null;
+            int count = list.size();
+            for (int i = 0; i < count; i++) {
+                list.get(i).run();
+            }
+        }
+    }
+}