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.DefaultDisplay.getSingleFrameMs;
20 import static com.android.systemui.shared.recents.utilities.Utilities
21         .postAtFrontOfQueueAsynchronously;
22 
23 import android.animation.Animator;
24 import android.animation.AnimatorListenerAdapter;
25 import android.animation.AnimatorSet;
26 import android.annotation.TargetApi;
27 import android.content.Context;
28 import android.os.Build;
29 import android.os.Handler;
30 
31 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
32 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
33 
34 import androidx.annotation.BinderThread;
35 import androidx.annotation.UiThread;
36 
37 @TargetApi(Build.VERSION_CODES.P)
38 public abstract class LauncherAnimationRunner implements RemoteAnimationRunnerCompat {
39 
40     private final Handler mHandler;
41     private final boolean mStartAtFrontOfQueue;
42     private AnimationResult mAnimationResult;
43 
44     /**
45      * @param startAtFrontOfQueue If true, the animation start will be posted at the front of the
46      *                            queue to minimize latency.
47      */
LauncherAnimationRunner(Handler handler, boolean startAtFrontOfQueue)48     public LauncherAnimationRunner(Handler handler, boolean startAtFrontOfQueue) {
49         mHandler = handler;
50         mStartAtFrontOfQueue = startAtFrontOfQueue;
51     }
52 
53     @BinderThread
54     @Override
onAnimationStart(RemoteAnimationTargetCompat[] targetCompats, Runnable runnable)55     public void onAnimationStart(RemoteAnimationTargetCompat[] targetCompats, Runnable runnable) {
56         Runnable r = () -> {
57             finishExistingAnimation();
58             mAnimationResult = new AnimationResult(runnable);
59             onCreateAnimation(targetCompats, mAnimationResult);
60         };
61         if (mStartAtFrontOfQueue) {
62             postAtFrontOfQueueAsynchronously(mHandler, r);
63         } else {
64             postAsyncCallback(mHandler, r);
65         }
66     }
67 
68     /**
69      * Called on the UI thread when the animation targets are received. The implementation must
70      * call {@link AnimationResult#setAnimation} with the target animation to be run.
71      */
72     @UiThread
onCreateAnimation( RemoteAnimationTargetCompat[] targetCompats, AnimationResult result)73     public abstract void onCreateAnimation(
74             RemoteAnimationTargetCompat[] targetCompats, AnimationResult result);
75 
76     @UiThread
finishExistingAnimation()77     private void finishExistingAnimation() {
78         if (mAnimationResult != null) {
79             mAnimationResult.finish();
80             mAnimationResult = null;
81         }
82     }
83 
84     /**
85      * Called by the system
86      */
87     @BinderThread
88     @Override
onAnimationCancelled()89     public void onAnimationCancelled() {
90         postAsyncCallback(mHandler, this::finishExistingAnimation);
91     }
92 
93     public static final class AnimationResult {
94 
95         private final Runnable mFinishRunnable;
96 
97         private AnimatorSet mAnimator;
98         private boolean mFinished = false;
99         private boolean mInitialized = false;
100 
AnimationResult(Runnable finishRunnable)101         private AnimationResult(Runnable finishRunnable) {
102             mFinishRunnable = finishRunnable;
103         }
104 
105         @UiThread
finish()106         private void finish() {
107             if (!mFinished) {
108                 mFinishRunnable.run();
109                 mFinished = true;
110             }
111         }
112 
113         @UiThread
setAnimation(AnimatorSet animation, Context context)114         public void setAnimation(AnimatorSet animation, Context context) {
115             if (mInitialized) {
116                 throw new IllegalStateException("Animation already initialized");
117             }
118             mInitialized = true;
119             mAnimator = animation;
120             if (mAnimator == null) {
121                 finish();
122             } else if (mFinished) {
123                 // Animation callback was already finished, skip the animation.
124                 mAnimator.start();
125                 mAnimator.end();
126             } else {
127                 // Start the animation
128                 mAnimator.addListener(new AnimatorListenerAdapter() {
129                     @Override
130                     public void onAnimationEnd(Animator animation) {
131                         finish();
132                     }
133                 });
134                 mAnimator.start();
135 
136                 // Because t=0 has the app icon in its original spot, we can skip the
137                 // first frame and have the same movement one frame earlier.
138                 mAnimator.setCurrentPlayTime(getSingleFrameMs(context));
139             }
140         }
141     }
142 }