1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.launcher3;
17 
18 import static com.android.launcher3.Utilities.postAsyncCallback;
19 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
20 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
21 import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
22 import static com.android.systemui.shared.recents.utilities.Utilities.postAtFrontOfQueueAsynchronously;
23 
24 import android.animation.Animator;
25 import android.animation.AnimatorListenerAdapter;
26 import android.animation.AnimatorSet;
27 import android.content.Context;
28 import android.os.Handler;
29 import android.os.RemoteException;
30 import android.view.IRemoteAnimationFinishedCallback;
31 import android.view.RemoteAnimationTarget;
32 
33 import androidx.annotation.BinderThread;
34 import androidx.annotation.Nullable;
35 import androidx.annotation.UiThread;
36 
37 import com.android.systemui.animation.RemoteAnimationDelegate;
38 import com.android.systemui.animation.RemoteAnimationRunnerCompat;
39 
40 import java.lang.ref.WeakReference;
41 
42 /**
43  * This class is needed to wrap any animation runner that is a part of the
44  * RemoteAnimationDefinition:
45  * - Launcher creates a new instance of the LauncherAppTransitionManagerImpl whenever it is
46  *   created, which in turn registers a new definition
47  * - When the definition is registered, window manager retains a strong binder reference to the
48  *   runner passed in
49  * - If the Launcher activity is recreated, the new definition registered will replace the old
50  *   reference in the system's activity record, but until the system server is GC'd, the binder
51  *   reference will still exist, which references the runner in the Launcher process, which
52  *   references the (old) Launcher activity through this class
53  *
54  * Instead we make the runner provided to the definition static only holding a weak reference to
55  * the runner implementation.  When this animation manager is destroyed, we remove the Launcher
56  * reference to the runner, leaving only the weak ref from the runner.
57  */
58 public class LauncherAnimationRunner extends RemoteAnimationRunnerCompat {
59 
60     private static final RemoteAnimationFactory DEFAULT_FACTORY =
61             (transit, appTargets, wallpaperTargets, nonAppTargets, result) ->
62                     result.setAnimation(null, null);
63 
64     private final Handler mHandler;
65     private final boolean mStartAtFrontOfQueue;
66     private final WeakReference<RemoteAnimationFactory> mFactory;
67 
68     private AnimationResult mAnimationResult;
69 
70     /**
71      * @param startAtFrontOfQueue If true, the animation start will be posted at the front of the
72      *                            queue to minimize latency.
73      */
LauncherAnimationRunner(Handler handler, RemoteAnimationFactory factory, boolean startAtFrontOfQueue)74     public LauncherAnimationRunner(Handler handler, RemoteAnimationFactory factory,
75             boolean startAtFrontOfQueue) {
76         mHandler = handler;
77         mFactory = new WeakReference<>(factory);
78         mStartAtFrontOfQueue = startAtFrontOfQueue;
79     }
80 
81     // Called only in S+ platform
82     @BinderThread
onAnimationStart( int transit, RemoteAnimationTarget[] appTargets, RemoteAnimationTarget[] wallpaperTargets, RemoteAnimationTarget[] nonAppTargets, Runnable runnable)83     public void onAnimationStart(
84             int transit,
85             RemoteAnimationTarget[] appTargets,
86             RemoteAnimationTarget[] wallpaperTargets,
87             RemoteAnimationTarget[] nonAppTargets,
88             Runnable runnable) {
89         Runnable r = () -> {
90             finishExistingAnimation();
91             mAnimationResult = new AnimationResult(() -> mAnimationResult = null, runnable);
92             getFactory().onAnimationStart(transit, appTargets, wallpaperTargets, nonAppTargets,
93                     mAnimationResult);
94         };
95         if (mStartAtFrontOfQueue) {
96             postAtFrontOfQueueAsynchronously(mHandler, r);
97         } else {
98             postAsyncCallback(mHandler, r);
99         }
100     }
101 
getFactory()102     private RemoteAnimationFactory getFactory() {
103         RemoteAnimationFactory factory = mFactory.get();
104         return factory != null ? factory : DEFAULT_FACTORY;
105     }
106 
107     @UiThread
finishExistingAnimation()108     private void finishExistingAnimation() {
109         if (mAnimationResult != null) {
110             mAnimationResult.finish();
111             mAnimationResult = null;
112         }
113     }
114 
115     /**
116      * Called by the system
117      */
118     @BinderThread
119     @Override
onAnimationCancelled()120     public void onAnimationCancelled() {
121         postAsyncCallback(mHandler, () -> {
122             finishExistingAnimation();
123             getFactory().onAnimationCancelled();
124         });
125     }
126 
127     /**
128      * Used by RemoteAnimationFactory implementations to run the actual animation and its lifecycle
129      * callbacks.
130      */
131     public static final class AnimationResult extends IRemoteAnimationFinishedCallback.Stub {
132 
133         private final Runnable mSyncFinishRunnable;
134         private final Runnable mASyncFinishRunnable;
135 
136         private AnimatorSet mAnimator;
137         private Runnable mOnCompleteCallback;
138         private boolean mFinished = false;
139         private boolean mInitialized = false;
140 
AnimationResult(Runnable syncFinishRunnable, Runnable asyncFinishRunnable)141         private AnimationResult(Runnable syncFinishRunnable, Runnable asyncFinishRunnable) {
142             mSyncFinishRunnable = syncFinishRunnable;
143             mASyncFinishRunnable = asyncFinishRunnable;
144         }
145 
146         @UiThread
finish()147         private void finish() {
148             if (!mFinished) {
149                 mSyncFinishRunnable.run();
150                 UI_HELPER_EXECUTOR.execute(() -> {
151                     mASyncFinishRunnable.run();
152                     if (mOnCompleteCallback != null) {
153                         MAIN_EXECUTOR.execute(mOnCompleteCallback);
154                     }
155                 });
156                 mFinished = true;
157             }
158         }
159 
160         @UiThread
setAnimation(AnimatorSet animation, Context context)161         public void setAnimation(AnimatorSet animation, Context context) {
162             setAnimation(animation, context, null, true);
163         }
164 
165         /**
166          * Sets the animation to play for this app launch
167          * @param skipFirstFrame Iff true, we skip the first frame of the animation.
168          *                       We set to false when skipping first frame causes jank.
169          */
170         @UiThread
setAnimation(AnimatorSet animation, Context context, @Nullable Runnable onCompleteCallback, boolean skipFirstFrame)171         public void setAnimation(AnimatorSet animation, Context context,
172                 @Nullable Runnable onCompleteCallback, boolean skipFirstFrame) {
173             if (mInitialized) {
174                 throw new IllegalStateException("Animation already initialized");
175             }
176             mInitialized = true;
177             mAnimator = animation;
178             mOnCompleteCallback = onCompleteCallback;
179             if (mAnimator == null) {
180                 finish();
181             } else if (mFinished) {
182                 // Animation callback was already finished, skip the animation.
183                 mAnimator.start();
184                 mAnimator.end();
185                 if (mOnCompleteCallback != null) {
186                     mOnCompleteCallback.run();
187                 }
188             } else {
189                 // Start the animation
190                 mAnimator.addListener(new AnimatorListenerAdapter() {
191                     @Override
192                     public void onAnimationEnd(Animator animation) {
193                         finish();
194                     }
195                 });
196                 if (skipFirstFrame) {
197                     // Because t=0 has the app icon in its original spot, we can skip the
198                     // first frame and have the same movement one frame earlier.
199                     mAnimator.setCurrentPlayTime(
200                             Math.min(getSingleFrameMs(context), mAnimator.getTotalDuration()));
201                 }
202                 mAnimator.start();
203             }
204         }
205 
206         /**
207          * When used as a simple IRemoteAnimationFinishedCallback, this method is used to run the
208          * animation finished runnable.
209          */
210         @Override
onAnimationFinished()211         public void onAnimationFinished() throws RemoteException {
212             mASyncFinishRunnable.run();
213         }
214     }
215 
216     /**
217      * Used with LauncherAnimationRunner as an interface for the runner to call back to the
218      * implementation.
219      */
220     public interface RemoteAnimationFactory extends RemoteAnimationDelegate<AnimationResult> {
221 
222         /**
223          * Called on the UI thread when the animation targets are received. The implementation must
224          * call {@link AnimationResult#setAnimation} with the target animation to be run.
225          */
226         @Override
227         @UiThread
onAnimationStart(int transit, RemoteAnimationTarget[] appTargets, RemoteAnimationTarget[] wallpaperTargets, RemoteAnimationTarget[] nonAppTargets, LauncherAnimationRunner.AnimationResult result)228         void onAnimationStart(int transit,
229                 RemoteAnimationTarget[] appTargets,
230                 RemoteAnimationTarget[] wallpaperTargets,
231                 RemoteAnimationTarget[] nonAppTargets,
232                 LauncherAnimationRunner.AnimationResult result);
233 
234         /**
235          * Called when the animation is cancelled. This can happen with or without
236          * the create being called.
237          */
238         @Override
239         @UiThread
onAnimationCancelled()240         default void onAnimationCancelled() {}
241     }
242 }
243