Migrate unfold animation to Shell transitions [Part 2]

Adds real unfold animation for full-screen tasks when
doing the Shell unfold transition. When folding
the animation is done using the previous approach:
by listening TaskOrganizer and modifying the surfaces
without actually starting Shell transitions.

Common logic that handles surface modifications is
extracted to FullscreenUnfoldTaskAnimator.

Bug: 204925795
Test: folding/unfolding with a fullscreen app
  when Shell transitions enabled/disabled
Change-Id: I6d086b4df84d37ae61a46956bc99f0c925aa1755
This commit is contained in:
Nick Chameyev
2022-03-30 18:04:40 +00:00
parent a49154ec20
commit 66caf41259
9 changed files with 311 additions and 192 deletions

View File

@@ -14,6 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!-- Should be the same as in packages/apps/Launcher3/res/color-v31/taskbar_background.xml -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@android:color/system_neutral1_500" android:lStar="35" />
<item android:color="@android:color/system_neutral1_500" android:lStar="15" />
</selector>

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2021 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Matches taskbar color -->
<item android:color="@android:color/system_neutral2_500" android:lStar="35" />
</selector>

View File

@@ -319,26 +319,27 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
static Optional<FullscreenUnfoldController> provideFullscreenUnfoldController(
@DynamicOverride Optional<FullscreenUnfoldController> fullscreenUnfoldController,
@DynamicOverride Lazy<Optional<FullscreenUnfoldController>> fullscreenUnfoldController,
Optional<ShellUnfoldProgressProvider> progressProvider) {
if (progressProvider.isPresent()
&& progressProvider.get() != ShellUnfoldProgressProvider.NO_PROVIDER) {
return fullscreenUnfoldController;
return fullscreenUnfoldController.get();
}
return Optional.empty();
}
@BindsOptionalOf
@DynamicOverride
abstract UnfoldTransitionHandler optionalUnfoldTransitionHandler();
@WMSingleton
@Provides
static Optional<UnfoldTransitionHandler> provideUnfoldTransitionHandler(
Optional<ShellUnfoldProgressProvider> progressProvider,
TransactionPool transactionPool,
Transitions transitions,
@ShellMainThread ShellExecutor executor) {
if (progressProvider.isPresent()) {
return Optional.of(
new UnfoldTransitionHandler(progressProvider.get(), transactionPool, executor,
transitions));
@DynamicOverride Lazy<Optional<UnfoldTransitionHandler>> handler) {
if (progressProvider.isPresent()
&& progressProvider.get() != ShellUnfoldProgressProvider.NO_PROVIDER) {
return handler.get();
}
return Optional.empty();
}

View File

@@ -72,6 +72,8 @@ import com.android.wm.shell.splitscreen.StageTaskUnfoldController;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
import com.android.wm.shell.unfold.UnfoldBackgroundController;
import com.android.wm.shell.unfold.UnfoldTransitionHandler;
import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator;
import java.util.Optional;
@@ -347,15 +349,37 @@ public class WMShellModule {
@Provides
@DynamicOverride
static FullscreenUnfoldController provideFullscreenUnfoldController(
Context context,
Optional<ShellUnfoldProgressProvider> progressProvider,
Lazy<UnfoldBackgroundController> unfoldBackgroundController,
DisplayInsetsController displayInsetsController,
Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
FullscreenUnfoldTaskAnimator fullscreenUnfoldTaskAnimator,
UnfoldBackgroundController unfoldBackgroundController,
@ShellMainThread ShellExecutor mainExecutor
) {
return new FullscreenUnfoldController(context, mainExecutor,
unfoldBackgroundController.get(), progressProvider.get(),
displayInsetsController);
return new FullscreenUnfoldController(mainExecutor,
unfoldBackgroundController, progressProvider.get(),
unfoldTransitionHandler.get(), fullscreenUnfoldTaskAnimator);
}
@Provides
static FullscreenUnfoldTaskAnimator provideFullscreenUnfoldTaskAnimator(
Context context,
DisplayInsetsController displayInsetsController
) {
return new FullscreenUnfoldTaskAnimator(context, displayInsetsController);
}
@WMSingleton
@Provides
@DynamicOverride
static UnfoldTransitionHandler provideUnfoldTransitionHandler(
Optional<ShellUnfoldProgressProvider> progressProvider,
FullscreenUnfoldTaskAnimator animator,
UnfoldBackgroundController backgroundController,
TransactionPool transactionPool,
Transitions transitions,
@ShellMainThread ShellExecutor executor) {
return new UnfoldTransitionHandler(progressProvider.get(), animator,
transactionPool, backgroundController, executor, transitions);
}
@Provides

View File

@@ -76,6 +76,8 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
taskInfo.taskId);
final Point positionInParent = taskInfo.positionInParent;
mDataByTaskId.put(taskInfo.taskId, new TaskData(leash, positionInParent));
mAnimatableTasksListener.onTaskAppeared(taskInfo);
if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
mSyncQueue.runInSync(t -> {
// Reset several properties back to fullscreen (PiP, for example, leaves all these
@@ -87,15 +89,15 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
t.show(leash);
});
mAnimatableTasksListener.onTaskAppeared(taskInfo);
updateRecentsForVisibleFullscreenTask(taskInfo);
}
@Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
mAnimatableTasksListener.onTaskInfoChanged(taskInfo);
if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
mAnimatableTasksListener.onTaskInfoChanged(taskInfo);
updateRecentsForVisibleFullscreenTask(taskInfo);
final TaskData data = mDataByTaskId.get(taskInfo.taskId);

View File

@@ -17,213 +17,119 @@
package com.android.wm.shell.fullscreen;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.util.MathUtils.lerp;
import static android.view.Display.DEFAULT_DISPLAY;
import android.animation.RectEvaluator;
import android.animation.TypeEvaluator;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.util.SparseArray;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.SurfaceControl;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
import com.android.wm.shell.unfold.UnfoldBackgroundController;
import com.android.wm.shell.unfold.UnfoldTransitionHandler;
import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator;
import java.util.concurrent.Executor;
/**
* Controls full screen app unfold transition: animating cropping window and scaling when
* folding or unfolding a foldable device.
*
* - When Shell transitions are disabled (legacy mode) this controller animates task surfaces
* when doing both fold and unfold.
*
* - When Shell transitions are enabled this controller animates the surfaces only when
* folding a foldable device. It's not done as a shell transition because we are not committed
* to the display size WM changes yet.
* In this case unfolding is handled by
* {@link com.android.wm.shell.unfold.UnfoldTransitionHandler}.
*/
public final class FullscreenUnfoldController implements UnfoldListener,
OnInsetsChangedListener {
private static final float[] FLOAT_9 = new float[9];
private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect());
private static final float HORIZONTAL_START_MARGIN = 0.08f;
private static final float VERTICAL_START_MARGIN = 0.03f;
private static final float END_SCALE = 1f;
private static final float START_SCALE = END_SCALE - VERTICAL_START_MARGIN * 2;
public final class FullscreenUnfoldController implements UnfoldListener {
private final Executor mExecutor;
private final ShellUnfoldProgressProvider mProgressProvider;
private final DisplayInsetsController mDisplayInsetsController;
private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
private final UnfoldBackgroundController mBackgroundController;
private InsetsSource mTaskbarInsetsSource;
private final float mWindowCornerRadiusPx;
private final float mExpandedTaskBarHeight;
private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
private final FullscreenUnfoldTaskAnimator mAnimator;
private final UnfoldTransitionHandler mUnfoldTransitionHandler;
private boolean mShouldHandleAnimation = false;
public FullscreenUnfoldController(
@NonNull Context context,
@NonNull Executor executor,
@NonNull UnfoldBackgroundController backgroundController,
@NonNull ShellUnfoldProgressProvider progressProvider,
@NonNull DisplayInsetsController displayInsetsController
@NonNull UnfoldTransitionHandler unfoldTransitionHandler,
@NonNull FullscreenUnfoldTaskAnimator animator
) {
mExecutor = executor;
mProgressProvider = progressProvider;
mDisplayInsetsController = displayInsetsController;
mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.taskbar_frame_height);
mBackgroundController = backgroundController;
mUnfoldTransitionHandler = unfoldTransitionHandler;
mAnimator = animator;
}
/**
* Initializes the controller
*/
public void init() {
mAnimator.init();
mProgressProvider.addListener(mExecutor, this);
mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this);
}
@Override
public void onStateChangeStarted() {
mShouldHandleAnimation = !mUnfoldTransitionHandler.willHandleTransition();
}
@Override
public void onStateChangeProgress(float progress) {
if (mAnimationContextByTaskId.size() == 0) return;
if (!mAnimator.hasActiveTasks() || !mShouldHandleAnimation) return;
mBackgroundController.ensureBackground(mTransaction);
for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
context.mCurrentCropRect.set(RECT_EVALUATOR
.evaluate(progress, context.mStartCropRect, context.mEndCropRect));
float scale = lerp(START_SCALE, END_SCALE, progress);
context.mMatrix.setScale(scale, scale, context.mCurrentCropRect.exactCenterX(),
context.mCurrentCropRect.exactCenterY());
mTransaction.setWindowCrop(context.mLeash, context.mCurrentCropRect)
.setMatrix(context.mLeash, context.mMatrix, FLOAT_9)
.setCornerRadius(context.mLeash, mWindowCornerRadiusPx);
}
mAnimator.applyAnimationProgress(progress, mTransaction);
mTransaction.apply();
}
@Override
public void onStateChangeFinished() {
for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
resetSurface(context);
if (!mShouldHandleAnimation) {
return;
}
mShouldHandleAnimation = false;
mAnimator.resetAllSurfaces(mTransaction);
mBackgroundController.removeBackground(mTransaction);
mTransaction.apply();
}
@Override
public void insetsChanged(InsetsState insetsState) {
mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
AnimationContext context = mAnimationContextByTaskId.valueAt(i);
context.update(mTaskbarInsetsSource, context.mTaskInfo);
}
}
/**
* Called when a new matching task appeared
*/
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
AnimationContext animationContext = new AnimationContext(leash, mTaskbarInsetsSource,
taskInfo);
mAnimationContextByTaskId.put(taskInfo.taskId, animationContext);
mAnimator.addTask(taskInfo, leash);
}
/**
* Called when matching task changed
*/
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
AnimationContext animationContext = mAnimationContextByTaskId.get(taskInfo.taskId);
if (animationContext != null) {
animationContext.update(mTaskbarInsetsSource, taskInfo);
}
mAnimator.onTaskInfoChanged(taskInfo);
}
/**
* Called when matching task vanished
*/
public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
AnimationContext animationContext = mAnimationContextByTaskId.get(taskInfo.taskId);
if (animationContext != null) {
// PiP task has its own cleanup path, ignore surface reset to avoid conflict.
if (taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED) {
resetSurface(animationContext);
}
mAnimationContextByTaskId.remove(taskInfo.taskId);
// PiP task has its own cleanup path, ignore surface reset to avoid conflict.
if (taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED) {
mAnimator.resetSurface(taskInfo, mTransaction);
}
mAnimator.removeTask(taskInfo);
if (mAnimationContextByTaskId.size() == 0) {
if (!mAnimator.hasActiveTasks()) {
mBackgroundController.removeBackground(mTransaction);
}
mTransaction.apply();
}
private void resetSurface(AnimationContext context) {
mTransaction
.setWindowCrop(context.mLeash, null)
.setCornerRadius(context.mLeash, 0.0F)
.setMatrix(context.mLeash, 1.0F, 0.0F, 0.0F, 1.0F)
.setPosition(context.mLeash,
(float) context.mTaskInfo.positionInParent.x,
(float) context.mTaskInfo.positionInParent.y);
}
private class AnimationContext {
final SurfaceControl mLeash;
final Rect mStartCropRect = new Rect();
final Rect mEndCropRect = new Rect();
final Rect mCurrentCropRect = new Rect();
final Matrix mMatrix = new Matrix();
TaskInfo mTaskInfo;
private AnimationContext(SurfaceControl leash,
InsetsSource taskBarInsetsSource,
TaskInfo taskInfo) {
this.mLeash = leash;
update(taskBarInsetsSource, taskInfo);
}
private void update(InsetsSource taskBarInsetsSource, TaskInfo taskInfo) {
mTaskInfo = taskInfo;
mStartCropRect.set(mTaskInfo.getConfiguration().windowConfiguration.getBounds());
if (taskBarInsetsSource != null) {
// Only insets the cropping window with task bar when it's expanded
if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
mStartCropRect.inset(taskBarInsetsSource
.calculateVisibleInsets(mStartCropRect));
}
}
mEndCropRect.set(mStartCropRect);
int horizontalMargin = (int) (mEndCropRect.width() * HORIZONTAL_START_MARGIN);
mStartCropRect.left = mEndCropRect.left + horizontalMargin;
mStartCropRect.right = mEndCropRect.right - horizontalMargin;
int verticalMargin = (int) (mEndCropRect.height() * VERTICAL_START_MARGIN);
mStartCropRect.top = mEndCropRect.top + verticalMargin;
mStartCropRect.bottom = mEndCropRect.bottom - verticalMargin;
}
}
}

View File

@@ -79,7 +79,7 @@ public class UnfoldBackgroundController {
}
private float[] getBackgroundColor(Context context) {
int colorInt = context.getResources().getColor(R.color.unfold_transition_background);
int colorInt = context.getResources().getColor(R.color.taskbar_background);
return new float[]{
(float) red(colorInt) / 255.0F,
(float) green(colorInt) / 255.0F,

View File

@@ -34,15 +34,20 @@ 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 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 com.android.wm.shell.fullscreen.FullscreenUnfoldController}.
*/
public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListener {
private final ShellUnfoldProgressProvider mUnfoldProgressProvider;
private final Transitions mTransitions;
private final UnfoldBackgroundController mUnfoldBackgroundController;
private final Executor mExecutor;
private final TransactionPool mTransactionPool;
@@ -51,17 +56,22 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
@Nullable
private IBinder mTransition;
private final List<TransitionInfo.Change> mAnimatedFullscreenTasks = new ArrayList<>();
private final FullscreenUnfoldTaskAnimator mFullscreenAnimator;
public UnfoldTransitionHandler(ShellUnfoldProgressProvider unfoldProgressProvider,
TransactionPool transactionPool, Executor executor, Transitions transitions) {
FullscreenUnfoldTaskAnimator animator, TransactionPool transactionPool,
UnfoldBackgroundController unfoldBackgroundController,
Executor executor, Transitions transitions) {
mUnfoldProgressProvider = unfoldProgressProvider;
mFullscreenAnimator = animator;
mTransactionPool = transactionPool;
mUnfoldBackgroundController = unfoldBackgroundController;
mExecutor = executor;
mTransitions = transitions;
}
public void init() {
mFullscreenAnimator.init();
mTransitions.addHandler(this);
mUnfoldProgressProvider.addListener(mExecutor, this);
}
@@ -71,40 +81,36 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull TransitionFinishCallback finishCallback) {
if (transition != mTransition) return false;
mUnfoldBackgroundController.ensureBackground(startTransaction);
startTransaction.apply();
mAnimatedFullscreenTasks.clear();
mFullscreenAnimator.clearTasks();
info.getChanges().forEach(change -> {
final boolean allowedToAnimate = change.getTaskInfo() != null
&& change.getTaskInfo().isVisible()
&& change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
&& change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME
&& change.getMode() == TRANSIT_CHANGE;
if (allowedToAnimate) {
mAnimatedFullscreenTasks.add(change);
mFullscreenAnimator.addTask(change.getTaskInfo(), change.getLeash());
}
});
mFullscreenAnimator.resetAllSurfaces(finishTransaction);
mUnfoldBackgroundController.removeBackground(finishTransaction);
mFinishCallback = finishCallback;
mTransition = null;
return true;
}
@Override
public void onStateChangeProgress(float progress) {
mAnimatedFullscreenTasks.forEach(change -> {
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
// TODO: this is a placeholder animation, replace with a spec version in the next CLs
final float testScale = 0.8f + 0.2f * progress;
transaction.setScale(change.getLeash(), testScale, testScale);
transaction.apply();
mTransactionPool.release(transaction);
});
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
mFullscreenAnimator.applyAnimationProgress(progress, transaction);
transaction.apply();
mTransactionPool.release(transaction);
}
@Override
@@ -112,7 +118,8 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
if (mFinishCallback != null) {
mFinishCallback.onTransitionFinished(null, null);
mFinishCallback = null;
mAnimatedFullscreenTasks.clear();
mTransition = null;
mFullscreenAnimator.clearTasks();
}
}
@@ -120,11 +127,14 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
if (request.getType() == TRANSIT_CHANGE && request.getDisplayChange() != null
&& request.getDisplayChange().isPhysicalDisplayChanged()) {
if (request.getType() == TRANSIT_CHANGE && request.getDisplayChange() != null) {
mTransition = transition;
return new WindowContainerTransaction();
}
return null;
}
public boolean willHandleTransition() {
return mTransition != null;
}
}

View File

@@ -0,0 +1,194 @@
/*
* 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.animation;
import static android.util.MathUtils.lerp;
import static android.view.Display.DEFAULT_DISPLAY;
import android.animation.RectEvaluator;
import android.animation.TypeEvaluator;
import android.app.TaskInfo;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.util.SparseArray;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.wm.shell.common.DisplayInsetsController;
/**
* This helper class contains logic that calculates scaling and cropping parameters
* for the folding/unfolding animation. As an input it receives TaskInfo objects and
* surfaces leashes and as an output it could fill surface transactions with required
* transformations.
*
* This class is used by
* {@link com.android.wm.shell.unfold.UnfoldTransitionHandler} and
* {@link com.android.wm.shell.fullscreen.FullscreenUnfoldController}. They use independent
* instances of FullscreenUnfoldTaskAnimator.
*/
public class FullscreenUnfoldTaskAnimator implements
DisplayInsetsController.OnInsetsChangedListener {
private static final float[] FLOAT_9 = new float[9];
private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect());
private static final float HORIZONTAL_START_MARGIN = 0.08f;
private static final float VERTICAL_START_MARGIN = 0.03f;
private static final float END_SCALE = 1f;
private static final float START_SCALE = END_SCALE - VERTICAL_START_MARGIN * 2;
private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
private final int mExpandedTaskBarHeight;
private final float mWindowCornerRadiusPx;
private final DisplayInsetsController mDisplayInsetsController;
private InsetsSource mTaskbarInsetsSource;
public FullscreenUnfoldTaskAnimator(Context context,
DisplayInsetsController displayInsetsController) {
mDisplayInsetsController = displayInsetsController;
mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.taskbar_frame_height);
mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
}
public void init() {
mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this);
}
@Override
public void insetsChanged(InsetsState insetsState) {
mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
AnimationContext context = mAnimationContextByTaskId.valueAt(i);
context.update(mTaskbarInsetsSource, context.mTaskInfo);
}
}
public boolean hasActiveTasks() {
return mAnimationContextByTaskId.size() > 0;
}
public void addTask(TaskInfo taskInfo, SurfaceControl leash) {
AnimationContext animationContext = new AnimationContext(leash, mTaskbarInsetsSource,
taskInfo);
mAnimationContextByTaskId.put(taskInfo.taskId, animationContext);
}
public void onTaskInfoChanged(TaskInfo taskInfo) {
AnimationContext animationContext = mAnimationContextByTaskId.get(taskInfo.taskId);
if (animationContext != null) {
animationContext.update(mTaskbarInsetsSource, taskInfo);
}
}
public void removeTask(TaskInfo taskInfo) {
mAnimationContextByTaskId.remove(taskInfo.taskId);
}
public void clearTasks() {
mAnimationContextByTaskId.clear();
}
public void resetSurface(TaskInfo taskInfo, Transaction transaction) {
final AnimationContext context = mAnimationContextByTaskId.get(taskInfo.taskId);
if (context != null) {
resetSurface(context, transaction);
}
}
public void applyAnimationProgress(float progress, Transaction transaction) {
if (mAnimationContextByTaskId.size() == 0) return;
for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
context.mCurrentCropRect.set(RECT_EVALUATOR
.evaluate(progress, context.mStartCropRect, context.mEndCropRect));
float scale = lerp(START_SCALE, END_SCALE, progress);
context.mMatrix.setScale(scale, scale, context.mCurrentCropRect.exactCenterX(),
context.mCurrentCropRect.exactCenterY());
transaction.setWindowCrop(context.mLeash, context.mCurrentCropRect)
.setMatrix(context.mLeash, context.mMatrix, FLOAT_9)
.setCornerRadius(context.mLeash, mWindowCornerRadiusPx)
.show(context.mLeash)
;
}
}
public void resetAllSurfaces(Transaction transaction) {
for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
resetSurface(context, transaction);
}
}
private void resetSurface(AnimationContext context, Transaction transaction) {
transaction
.setWindowCrop(context.mLeash, null)
.setCornerRadius(context.mLeash, 0.0F)
.setMatrix(context.mLeash, 1.0F, 0.0F, 0.0F, 1.0F)
.setPosition(context.mLeash,
(float) context.mTaskInfo.positionInParent.x,
(float) context.mTaskInfo.positionInParent.y);
}
private class AnimationContext {
final SurfaceControl mLeash;
final Rect mStartCropRect = new Rect();
final Rect mEndCropRect = new Rect();
final Rect mCurrentCropRect = new Rect();
final Matrix mMatrix = new Matrix();
TaskInfo mTaskInfo;
private AnimationContext(SurfaceControl leash, InsetsSource taskBarInsetsSource,
TaskInfo taskInfo) {
mLeash = leash;
update(taskBarInsetsSource, taskInfo);
}
private void update(InsetsSource taskBarInsetsSource, TaskInfo taskInfo) {
mTaskInfo = taskInfo;
mStartCropRect.set(mTaskInfo.getConfiguration().windowConfiguration.getBounds());
if (taskBarInsetsSource != null) {
// Only insets the cropping window with task bar when it's expanded
if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
mStartCropRect.inset(taskBarInsetsSource
.calculateVisibleInsets(mStartCropRect));
}
}
mEndCropRect.set(mStartCropRect);
int horizontalMargin = (int) (mEndCropRect.width() * HORIZONTAL_START_MARGIN);
mStartCropRect.left = mEndCropRect.left + horizontalMargin;
mStartCropRect.right = mEndCropRect.right - horizontalMargin;
int verticalMargin = (int) (mEndCropRect.height() * VERTICAL_START_MARGIN);
mStartCropRect.top = mEndCropRect.top + verticalMargin;
mStartCropRect.bottom = mEndCropRect.bottom - verticalMargin;
}
}
}