/* * Copyright (C) 2022 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.wm.shell.unfold; import static android.view.WindowManager.TRANSIT_CHANGE; import android.os.IBinder; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.transition.Transitions.TransitionFinishCallback; import com.android.wm.shell.transition.Transitions.TransitionHandler; import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener; import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator; import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator; import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; /** * Transition handler that is responsible for animating app surfaces when unfolding of foldable * devices. It does not handle the folding animation, which is done in * {@link UnfoldAnimationController}. */ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListener { private final ShellUnfoldProgressProvider mUnfoldProgressProvider; private final Transitions mTransitions; private final Executor mExecutor; private final TransactionPool mTransactionPool; @Nullable private TransitionFinishCallback mFinishCallback; @Nullable private IBinder mTransition; private final List mAnimators = new ArrayList<>(); public UnfoldTransitionHandler(ShellInit shellInit, ShellUnfoldProgressProvider unfoldProgressProvider, FullscreenUnfoldTaskAnimator fullscreenUnfoldAnimator, SplitTaskUnfoldAnimator splitUnfoldTaskAnimator, TransactionPool transactionPool, Executor executor, Transitions transitions) { mUnfoldProgressProvider = unfoldProgressProvider; mTransactionPool = transactionPool; mExecutor = executor; mTransitions = transitions; mAnimators.add(splitUnfoldTaskAnimator); mAnimators.add(fullscreenUnfoldAnimator); // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic // override for this controller from the base module if (unfoldProgressProvider != ShellUnfoldProgressProvider.NO_PROVIDER && Transitions.ENABLE_SHELL_TRANSITIONS) { shellInit.addInitCallback(this::onInit, this); } } /** * Called when the transition handler is initialized. */ public void onInit() { for (int i = 0; i < mAnimators.size(); i++) { mAnimators.get(i).init(); } mTransitions.addHandler(this); mUnfoldProgressProvider.addListener(mExecutor, this); } @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull TransitionFinishCallback finishCallback) { if (transition != mTransition) return false; for (int i = 0; i < mAnimators.size(); i++) { final UnfoldTaskAnimator animator = mAnimators.get(i); animator.clearTasks(); info.getChanges().forEach(change -> { if (change.getTaskInfo() != null && change.getMode() == TRANSIT_CHANGE && animator.isApplicableTask(change.getTaskInfo())) { animator.onTaskAppeared(change.getTaskInfo(), change.getLeash()); } }); if (animator.hasActiveTasks()) { animator.prepareStartTransaction(startTransaction); animator.prepareFinishTransaction(finishTransaction); animator.start(); } } startTransaction.apply(); mFinishCallback = finishCallback; return true; } @Override public void onStateChangeProgress(float progress) { if (mTransition == null) return; SurfaceControl.Transaction transaction = null; for (int i = 0; i < mAnimators.size(); i++) { final UnfoldTaskAnimator animator = mAnimators.get(i); if (animator.hasActiveTasks()) { if (transaction == null) { transaction = mTransactionPool.acquire(); } animator.applyAnimationProgress(progress, transaction); } } if (transaction != null) { transaction.apply(); mTransactionPool.release(transaction); } } @Override public void onStateChangeFinished() { if (mFinishCallback == null) return; for (int i = 0; i < mAnimators.size(); i++) { final UnfoldTaskAnimator animator = mAnimators.get(i); animator.clearTasks(); animator.stop(); } mFinishCallback.onTransitionFinished(null, null); mFinishCallback = null; mTransition = null; } @Nullable @Override public WindowContainerTransaction handleRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request) { if (request.getType() == TRANSIT_CHANGE && request.getDisplayChange() != null && request.getDisplayChange().isPhysicalDisplayChanged()) { mTransition = transition; return new WindowContainerTransaction(); } return null; } public boolean willHandleTransition() { return mTransition != null; } }