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