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 }