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