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.DisplayController.getSingleFrameMs; 20 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 21 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; 22 import static com.android.systemui.shared.recents.utilities.Utilities.postAtFrontOfQueueAsynchronously; 23 24 import android.animation.Animator; 25 import android.animation.AnimatorListenerAdapter; 26 import android.animation.AnimatorSet; 27 import android.annotation.TargetApi; 28 import android.content.Context; 29 import android.os.Build; 30 import android.os.Handler; 31 32 import androidx.annotation.BinderThread; 33 import androidx.annotation.Nullable; 34 import androidx.annotation.UiThread; 35 36 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat; 37 import com.android.systemui.shared.system.RemoteAnimationTargetCompat; 38 39 import java.lang.ref.WeakReference; 40 41 /** 42 * This class is needed to wrap any animation runner that is a part of the 43 * RemoteAnimationDefinition: 44 * - Launcher creates a new instance of the LauncherAppTransitionManagerImpl whenever it is 45 * created, which in turn registers a new definition 46 * - When the definition is registered, window manager retains a strong binder reference to the 47 * runner passed in 48 * - If the Launcher activity is recreated, the new definition registered will replace the old 49 * reference in the system's activity record, but until the system server is GC'd, the binder 50 * reference will still exist, which references the runner in the Launcher process, which 51 * references the (old) Launcher activity through this class 52 * 53 * Instead we make the runner provided to the definition static only holding a weak reference to 54 * the runner implementation. When this animation manager is destroyed, we remove the Launcher 55 * reference to the runner, leaving only the weak ref from the runner. 56 */ 57 @TargetApi(Build.VERSION_CODES.P) 58 public class LauncherAnimationRunner implements RemoteAnimationRunnerCompat { 59 60 private static final RemoteAnimationFactory DEFAULT_FACTORY = 61 (transit, appTargets, wallpaperTargets, nonAppTargets, result) -> 62 result.setAnimation(null, null); 63 64 private final Handler mHandler; 65 private final boolean mStartAtFrontOfQueue; 66 private final WeakReference<RemoteAnimationFactory> mFactory; 67 68 private AnimationResult mAnimationResult; 69 70 /** 71 * @param startAtFrontOfQueue If true, the animation start will be posted at the front of the 72 * queue to minimize latency. 73 */ LauncherAnimationRunner(Handler handler, RemoteAnimationFactory factory, boolean startAtFrontOfQueue)74 public LauncherAnimationRunner(Handler handler, RemoteAnimationFactory factory, 75 boolean startAtFrontOfQueue) { 76 mHandler = handler; 77 mFactory = new WeakReference<>(factory); 78 mStartAtFrontOfQueue = startAtFrontOfQueue; 79 } 80 81 // Called only in S+ platform 82 @BinderThread onAnimationStart( int transit, RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets, RemoteAnimationTargetCompat[] nonAppTargets, Runnable runnable)83 public void onAnimationStart( 84 int transit, 85 RemoteAnimationTargetCompat[] appTargets, 86 RemoteAnimationTargetCompat[] wallpaperTargets, 87 RemoteAnimationTargetCompat[] nonAppTargets, 88 Runnable runnable) { 89 Runnable r = () -> { 90 finishExistingAnimation(); 91 mAnimationResult = new AnimationResult(() -> mAnimationResult = null, runnable); 92 getFactory().onCreateAnimation(transit, appTargets, wallpaperTargets, nonAppTargets, 93 mAnimationResult); 94 }; 95 if (mStartAtFrontOfQueue) { 96 postAtFrontOfQueueAsynchronously(mHandler, r); 97 } else { 98 postAsyncCallback(mHandler, r); 99 } 100 } 101 102 // Called only in R platform 103 @BinderThread onAnimationStart(RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets, Runnable runnable)104 public void onAnimationStart(RemoteAnimationTargetCompat[] appTargets, 105 RemoteAnimationTargetCompat[] wallpaperTargets, Runnable runnable) { 106 onAnimationStart(0 /* transit */, appTargets, wallpaperTargets, 107 new RemoteAnimationTargetCompat[0], runnable); 108 } 109 110 // Called only in Q platform 111 @BinderThread 112 @Deprecated onAnimationStart(RemoteAnimationTargetCompat[] appTargets, Runnable runnable)113 public void onAnimationStart(RemoteAnimationTargetCompat[] appTargets, Runnable runnable) { 114 onAnimationStart(appTargets, new RemoteAnimationTargetCompat[0], runnable); 115 } 116 117 getFactory()118 private RemoteAnimationFactory getFactory() { 119 RemoteAnimationFactory factory = mFactory.get(); 120 return factory != null ? factory : DEFAULT_FACTORY; 121 } 122 123 @UiThread finishExistingAnimation()124 private void finishExistingAnimation() { 125 if (mAnimationResult != null) { 126 mAnimationResult.finish(); 127 mAnimationResult = null; 128 } 129 } 130 131 /** 132 * Called by the system 133 */ 134 @BinderThread 135 @Override onAnimationCancelled()136 public void onAnimationCancelled() { 137 postAsyncCallback(mHandler, () -> { 138 finishExistingAnimation(); 139 getFactory().onAnimationCancelled(); 140 }); 141 } 142 143 public static final class AnimationResult { 144 145 private final Runnable mSyncFinishRunnable; 146 private final Runnable mASyncFinishRunnable; 147 148 private AnimatorSet mAnimator; 149 private Runnable mOnCompleteCallback; 150 private boolean mFinished = false; 151 private boolean mInitialized = false; 152 AnimationResult(Runnable syncFinishRunnable, Runnable asyncFinishRunnable)153 private AnimationResult(Runnable syncFinishRunnable, Runnable asyncFinishRunnable) { 154 mSyncFinishRunnable = syncFinishRunnable; 155 mASyncFinishRunnable = asyncFinishRunnable; 156 } 157 158 @UiThread finish()159 private void finish() { 160 if (!mFinished) { 161 mSyncFinishRunnable.run(); 162 UI_HELPER_EXECUTOR.execute(() -> { 163 mASyncFinishRunnable.run(); 164 if (mOnCompleteCallback != null) { 165 MAIN_EXECUTOR.execute(mOnCompleteCallback); 166 } 167 }); 168 mFinished = true; 169 } 170 } 171 172 @UiThread setAnimation(AnimatorSet animation, Context context)173 public void setAnimation(AnimatorSet animation, Context context) { 174 setAnimation(animation, context, null, true); 175 } 176 177 /** 178 * Sets the animation to play for this app launch 179 * @param skipFirstFrame Iff true, we skip the first frame of the animation. 180 * We set to false when skipping first frame causes jank. 181 */ 182 @UiThread setAnimation(AnimatorSet animation, Context context, @Nullable Runnable onCompleteCallback, boolean skipFirstFrame)183 public void setAnimation(AnimatorSet animation, Context context, 184 @Nullable Runnable onCompleteCallback, boolean skipFirstFrame) { 185 if (mInitialized) { 186 throw new IllegalStateException("Animation already initialized"); 187 } 188 mInitialized = true; 189 mAnimator = animation; 190 mOnCompleteCallback = onCompleteCallback; 191 if (mAnimator == null) { 192 finish(); 193 } else if (mFinished) { 194 // Animation callback was already finished, skip the animation. 195 mAnimator.start(); 196 mAnimator.end(); 197 if (mOnCompleteCallback != null) { 198 mOnCompleteCallback.run(); 199 } 200 } else { 201 // Start the animation 202 mAnimator.addListener(new AnimatorListenerAdapter() { 203 @Override 204 public void onAnimationEnd(Animator animation) { 205 finish(); 206 } 207 }); 208 mAnimator.start(); 209 210 if (skipFirstFrame) { 211 // Because t=0 has the app icon in its original spot, we can skip the 212 // first frame and have the same movement one frame earlier. 213 mAnimator.setCurrentPlayTime( 214 Math.min(getSingleFrameMs(context), mAnimator.getTotalDuration())); 215 } 216 } 217 } 218 } 219 220 /** 221 * Used with LauncherAnimationRunner as an interface for the runner to call back to the 222 * implementation. 223 */ 224 @FunctionalInterface 225 public interface RemoteAnimationFactory { 226 227 /** 228 * Called on the UI thread when the animation targets are received. The implementation must 229 * call {@link AnimationResult#setAnimation} with the target animation to be run. 230 */ onCreateAnimation(int transit, RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets, RemoteAnimationTargetCompat[] nonAppTargets, LauncherAnimationRunner.AnimationResult result)231 void onCreateAnimation(int transit, 232 RemoteAnimationTargetCompat[] appTargets, 233 RemoteAnimationTargetCompat[] wallpaperTargets, 234 RemoteAnimationTargetCompat[] nonAppTargets, 235 LauncherAnimationRunner.AnimationResult result); 236 237 /** 238 * Called when the animation is cancelled. This can happen with or without 239 * the create being called. 240 */ onAnimationCancelled()241 default void onAnimationCancelled() { } 242 } 243 } 244