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