/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.launcher3; import static com.android.launcher3.Utilities.postAsyncCallback; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs; import static com.android.systemui.shared.recents.utilities.Utilities.postAtFrontOfQueueAsynchronously; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.content.Context; import android.os.Handler; import android.os.RemoteException; import android.view.IRemoteAnimationFinishedCallback; import android.view.RemoteAnimationTarget; import androidx.annotation.BinderThread; import androidx.annotation.Nullable; import androidx.annotation.UiThread; import com.android.systemui.animation.RemoteAnimationDelegate; import com.android.systemui.animation.RemoteAnimationRunnerCompat; import java.lang.ref.WeakReference; /** * This class is needed to wrap any animation runner that is a part of the * RemoteAnimationDefinition: * - Launcher creates a new instance of the LauncherAppTransitionManagerImpl whenever it is * created, which in turn registers a new definition * - When the definition is registered, window manager retains a strong binder reference to the * runner passed in * - If the Launcher activity is recreated, the new definition registered will replace the old * reference in the system's activity record, but until the system server is GC'd, the binder * reference will still exist, which references the runner in the Launcher process, which * references the (old) Launcher activity through this class * * Instead we make the runner provided to the definition static only holding a weak reference to * the runner implementation. When this animation manager is destroyed, we remove the Launcher * reference to the runner, leaving only the weak ref from the runner. */ public class LauncherAnimationRunner extends RemoteAnimationRunnerCompat { private static final RemoteAnimationFactory DEFAULT_FACTORY = (transit, appTargets, wallpaperTargets, nonAppTargets, result) -> result.setAnimation(null, null); private final Handler mHandler; private final boolean mStartAtFrontOfQueue; private final WeakReference mFactory; private AnimationResult mAnimationResult; /** * @param startAtFrontOfQueue If true, the animation start will be posted at the front of the * queue to minimize latency. */ public LauncherAnimationRunner(Handler handler, RemoteAnimationFactory factory, boolean startAtFrontOfQueue) { mHandler = handler; mFactory = new WeakReference<>(factory); mStartAtFrontOfQueue = startAtFrontOfQueue; } // Called only in S+ platform @BinderThread public void onAnimationStart( int transit, RemoteAnimationTarget[] appTargets, RemoteAnimationTarget[] wallpaperTargets, RemoteAnimationTarget[] nonAppTargets, Runnable runnable) { Runnable r = () -> { finishExistingAnimation(); mAnimationResult = new AnimationResult(() -> mAnimationResult = null, runnable); getFactory().onAnimationStart(transit, appTargets, wallpaperTargets, nonAppTargets, mAnimationResult); }; if (mStartAtFrontOfQueue) { postAtFrontOfQueueAsynchronously(mHandler, r); } else { postAsyncCallback(mHandler, r); } } private RemoteAnimationFactory getFactory() { RemoteAnimationFactory factory = mFactory.get(); return factory != null ? factory : DEFAULT_FACTORY; } @UiThread private void finishExistingAnimation() { if (mAnimationResult != null) { mAnimationResult.finish(); mAnimationResult = null; } } /** * Called by the system */ @BinderThread @Override public void onAnimationCancelled() { postAsyncCallback(mHandler, () -> { finishExistingAnimation(); getFactory().onAnimationCancelled(); }); } /** * Used by RemoteAnimationFactory implementations to run the actual animation and its lifecycle * callbacks. */ public static final class AnimationResult extends IRemoteAnimationFinishedCallback.Stub { private final Runnable mSyncFinishRunnable; private final Runnable mASyncFinishRunnable; private AnimatorSet mAnimator; private Runnable mOnCompleteCallback; private boolean mFinished = false; private boolean mInitialized = false; private AnimationResult(Runnable syncFinishRunnable, Runnable asyncFinishRunnable) { mSyncFinishRunnable = syncFinishRunnable; mASyncFinishRunnable = asyncFinishRunnable; } @UiThread private void finish() { if (!mFinished) { mSyncFinishRunnable.run(); UI_HELPER_EXECUTOR.execute(() -> { mASyncFinishRunnable.run(); if (mOnCompleteCallback != null) { MAIN_EXECUTOR.execute(mOnCompleteCallback); } }); mFinished = true; } } @UiThread public void setAnimation(AnimatorSet animation, Context context) { setAnimation(animation, context, null, true); } /** * Sets the animation to play for this app launch * @param skipFirstFrame Iff true, we skip the first frame of the animation. * We set to false when skipping first frame causes jank. */ @UiThread public void setAnimation(AnimatorSet animation, Context context, @Nullable Runnable onCompleteCallback, boolean skipFirstFrame) { if (mInitialized) { throw new IllegalStateException("Animation already initialized"); } mInitialized = true; mAnimator = animation; mOnCompleteCallback = onCompleteCallback; if (mAnimator == null) { finish(); } else if (mFinished) { // Animation callback was already finished, skip the animation. mAnimator.start(); mAnimator.end(); if (mOnCompleteCallback != null) { mOnCompleteCallback.run(); } } else { // Start the animation mAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { finish(); } }); if (skipFirstFrame) { // Because t=0 has the app icon in its original spot, we can skip the // first frame and have the same movement one frame earlier. mAnimator.setCurrentPlayTime( Math.min(getSingleFrameMs(context), mAnimator.getTotalDuration())); } mAnimator.start(); } } /** * When used as a simple IRemoteAnimationFinishedCallback, this method is used to run the * animation finished runnable. */ @Override public void onAnimationFinished() throws RemoteException { mASyncFinishRunnable.run(); } } /** * Used with LauncherAnimationRunner as an interface for the runner to call back to the * implementation. */ public interface RemoteAnimationFactory extends RemoteAnimationDelegate { /** * Called on the UI thread when the animation targets are received. The implementation must * call {@link AnimationResult#setAnimation} with the target animation to be run. */ @Override @UiThread void onAnimationStart(int transit, RemoteAnimationTarget[] appTargets, RemoteAnimationTarget[] wallpaperTargets, RemoteAnimationTarget[] nonAppTargets, LauncherAnimationRunner.AnimationResult result); /** * Called when the animation is cancelled. This can happen with or without * the create being called. */ @Override @UiThread default void onAnimationCancelled() {} } }