• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.DisplayController.getSingleFrameMs;
20 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
21 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
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.annotation.TargetApi;
28 import android.content.Context;
29 import android.os.Build;
30 import android.os.Handler;
31 
32 import androidx.annotation.BinderThread;
33 import androidx.annotation.Nullable;
34 import androidx.annotation.UiThread;
35 
36 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
37 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
38 
39 import java.lang.ref.WeakReference;
40 
41 /**
42  * This class is needed to wrap any animation runner that is a part of the
43  * RemoteAnimationDefinition:
44  * - Launcher creates a new instance of the LauncherAppTransitionManagerImpl whenever it is
45  *   created, which in turn registers a new definition
46  * - When the definition is registered, window manager retains a strong binder reference to the
47  *   runner passed in
48  * - If the Launcher activity is recreated, the new definition registered will replace the old
49  *   reference in the system's activity record, but until the system server is GC'd, the binder
50  *   reference will still exist, which references the runner in the Launcher process, which
51  *   references the (old) Launcher activity through this class
52  *
53  * Instead we make the runner provided to the definition static only holding a weak reference to
54  * the runner implementation.  When this animation manager is destroyed, we remove the Launcher
55  * reference to the runner, leaving only the weak ref from the runner.
56  */
57 @TargetApi(Build.VERSION_CODES.P)
58 public class LauncherAnimationRunner implements 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, RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets, RemoteAnimationTargetCompat[] nonAppTargets, Runnable runnable)83     public void onAnimationStart(
84             int transit,
85             RemoteAnimationTargetCompat[] appTargets,
86             RemoteAnimationTargetCompat[] wallpaperTargets,
87             RemoteAnimationTargetCompat[] nonAppTargets,
88             Runnable runnable) {
89         Runnable r = () -> {
90             finishExistingAnimation();
91             mAnimationResult = new AnimationResult(() -> mAnimationResult = null, runnable);
92             getFactory().onCreateAnimation(transit, appTargets, wallpaperTargets, nonAppTargets,
93                     mAnimationResult);
94         };
95         if (mStartAtFrontOfQueue) {
96             postAtFrontOfQueueAsynchronously(mHandler, r);
97         } else {
98             postAsyncCallback(mHandler, r);
99         }
100     }
101 
102     // Called only in R platform
103     @BinderThread
onAnimationStart(RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets, Runnable runnable)104     public void onAnimationStart(RemoteAnimationTargetCompat[] appTargets,
105             RemoteAnimationTargetCompat[] wallpaperTargets, Runnable runnable) {
106         onAnimationStart(0 /* transit */, appTargets, wallpaperTargets,
107                 new RemoteAnimationTargetCompat[0], runnable);
108     }
109 
110     // Called only in Q platform
111     @BinderThread
112     @Deprecated
onAnimationStart(RemoteAnimationTargetCompat[] appTargets, Runnable runnable)113     public void onAnimationStart(RemoteAnimationTargetCompat[] appTargets, Runnable runnable) {
114         onAnimationStart(appTargets, new RemoteAnimationTargetCompat[0], runnable);
115     }
116 
117 
getFactory()118     private RemoteAnimationFactory getFactory() {
119         RemoteAnimationFactory factory = mFactory.get();
120         return factory != null ? factory : DEFAULT_FACTORY;
121     }
122 
123     @UiThread
finishExistingAnimation()124     private void finishExistingAnimation() {
125         if (mAnimationResult != null) {
126             mAnimationResult.finish();
127             mAnimationResult = null;
128         }
129     }
130 
131     /**
132      * Called by the system
133      */
134     @BinderThread
135     @Override
onAnimationCancelled()136     public void onAnimationCancelled() {
137         postAsyncCallback(mHandler, () -> {
138             finishExistingAnimation();
139             getFactory().onAnimationCancelled();
140         });
141     }
142 
143     public static final class AnimationResult {
144 
145         private final Runnable mSyncFinishRunnable;
146         private final Runnable mASyncFinishRunnable;
147 
148         private AnimatorSet mAnimator;
149         private Runnable mOnCompleteCallback;
150         private boolean mFinished = false;
151         private boolean mInitialized = false;
152 
AnimationResult(Runnable syncFinishRunnable, Runnable asyncFinishRunnable)153         private AnimationResult(Runnable syncFinishRunnable, Runnable asyncFinishRunnable) {
154             mSyncFinishRunnable = syncFinishRunnable;
155             mASyncFinishRunnable = asyncFinishRunnable;
156         }
157 
158         @UiThread
finish()159         private void finish() {
160             if (!mFinished) {
161                 mSyncFinishRunnable.run();
162                 UI_HELPER_EXECUTOR.execute(() -> {
163                     mASyncFinishRunnable.run();
164                     if (mOnCompleteCallback != null) {
165                         MAIN_EXECUTOR.execute(mOnCompleteCallback);
166                     }
167                 });
168                 mFinished = true;
169             }
170         }
171 
172         @UiThread
setAnimation(AnimatorSet animation, Context context)173         public void setAnimation(AnimatorSet animation, Context context) {
174             setAnimation(animation, context, null, true);
175         }
176 
177         /**
178          * Sets the animation to play for this app launch
179          * @param skipFirstFrame Iff true, we skip the first frame of the animation.
180          *                       We set to false when skipping first frame causes jank.
181          */
182         @UiThread
setAnimation(AnimatorSet animation, Context context, @Nullable Runnable onCompleteCallback, boolean skipFirstFrame)183         public void setAnimation(AnimatorSet animation, Context context,
184                 @Nullable Runnable onCompleteCallback, boolean skipFirstFrame) {
185             if (mInitialized) {
186                 throw new IllegalStateException("Animation already initialized");
187             }
188             mInitialized = true;
189             mAnimator = animation;
190             mOnCompleteCallback = onCompleteCallback;
191             if (mAnimator == null) {
192                 finish();
193             } else if (mFinished) {
194                 // Animation callback was already finished, skip the animation.
195                 mAnimator.start();
196                 mAnimator.end();
197                 if (mOnCompleteCallback != null) {
198                     mOnCompleteCallback.run();
199                 }
200             } else {
201                 // Start the animation
202                 mAnimator.addListener(new AnimatorListenerAdapter() {
203                     @Override
204                     public void onAnimationEnd(Animator animation) {
205                         finish();
206                     }
207                 });
208                 mAnimator.start();
209 
210                 if (skipFirstFrame) {
211                     // Because t=0 has the app icon in its original spot, we can skip the
212                     // first frame and have the same movement one frame earlier.
213                     mAnimator.setCurrentPlayTime(
214                             Math.min(getSingleFrameMs(context), mAnimator.getTotalDuration()));
215                 }
216             }
217         }
218     }
219 
220     /**
221      * Used with LauncherAnimationRunner as an interface for the runner to call back to the
222      * implementation.
223      */
224     @FunctionalInterface
225     public interface RemoteAnimationFactory {
226 
227         /**
228          * Called on the UI thread when the animation targets are received. The implementation must
229          * call {@link AnimationResult#setAnimation} with the target animation to be run.
230          */
onCreateAnimation(int transit, RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets, RemoteAnimationTargetCompat[] nonAppTargets, LauncherAnimationRunner.AnimationResult result)231         void onCreateAnimation(int transit,
232                 RemoteAnimationTargetCompat[] appTargets,
233                 RemoteAnimationTargetCompat[] wallpaperTargets,
234                 RemoteAnimationTargetCompat[] nonAppTargets,
235                 LauncherAnimationRunner.AnimationResult result);
236 
237         /**
238          * Called when the animation is cancelled. This can happen with or without
239          * the create being called.
240          */
onAnimationCancelled()241         default void onAnimationCancelled() { }
242     }
243 }
244