Merge "Migrate unfold animation to Shell transitions [Part 2]" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
37f9fa6afb
@@ -14,6 +14,7 @@
|
|||||||
~ See the License for the specific language governing permissions and
|
~ See the License for the specific language governing permissions and
|
||||||
~ limitations under the License.
|
~ 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">
|
<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>
|
</selector>
|
||||||
@@ -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>
|
|
||||||
@@ -319,26 +319,27 @@ public abstract class WMShellBaseModule {
|
|||||||
@WMSingleton
|
@WMSingleton
|
||||||
@Provides
|
@Provides
|
||||||
static Optional<FullscreenUnfoldController> provideFullscreenUnfoldController(
|
static Optional<FullscreenUnfoldController> provideFullscreenUnfoldController(
|
||||||
@DynamicOverride Optional<FullscreenUnfoldController> fullscreenUnfoldController,
|
@DynamicOverride Lazy<Optional<FullscreenUnfoldController>> fullscreenUnfoldController,
|
||||||
Optional<ShellUnfoldProgressProvider> progressProvider) {
|
Optional<ShellUnfoldProgressProvider> progressProvider) {
|
||||||
if (progressProvider.isPresent()
|
if (progressProvider.isPresent()
|
||||||
&& progressProvider.get() != ShellUnfoldProgressProvider.NO_PROVIDER) {
|
&& progressProvider.get() != ShellUnfoldProgressProvider.NO_PROVIDER) {
|
||||||
return fullscreenUnfoldController;
|
return fullscreenUnfoldController.get();
|
||||||
}
|
}
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@BindsOptionalOf
|
||||||
|
@DynamicOverride
|
||||||
|
abstract UnfoldTransitionHandler optionalUnfoldTransitionHandler();
|
||||||
|
|
||||||
@WMSingleton
|
@WMSingleton
|
||||||
@Provides
|
@Provides
|
||||||
static Optional<UnfoldTransitionHandler> provideUnfoldTransitionHandler(
|
static Optional<UnfoldTransitionHandler> provideUnfoldTransitionHandler(
|
||||||
Optional<ShellUnfoldProgressProvider> progressProvider,
|
Optional<ShellUnfoldProgressProvider> progressProvider,
|
||||||
TransactionPool transactionPool,
|
@DynamicOverride Lazy<Optional<UnfoldTransitionHandler>> handler) {
|
||||||
Transitions transitions,
|
if (progressProvider.isPresent()
|
||||||
@ShellMainThread ShellExecutor executor) {
|
&& progressProvider.get() != ShellUnfoldProgressProvider.NO_PROVIDER) {
|
||||||
if (progressProvider.isPresent()) {
|
return handler.get();
|
||||||
return Optional.of(
|
|
||||||
new UnfoldTransitionHandler(progressProvider.get(), transactionPool, executor,
|
|
||||||
transitions));
|
|
||||||
}
|
}
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ import com.android.wm.shell.splitscreen.StageTaskUnfoldController;
|
|||||||
import com.android.wm.shell.transition.Transitions;
|
import com.android.wm.shell.transition.Transitions;
|
||||||
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
|
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
|
||||||
import com.android.wm.shell.unfold.UnfoldBackgroundController;
|
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;
|
import java.util.Optional;
|
||||||
|
|
||||||
@@ -344,15 +346,37 @@ public class WMShellModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@DynamicOverride
|
@DynamicOverride
|
||||||
static FullscreenUnfoldController provideFullscreenUnfoldController(
|
static FullscreenUnfoldController provideFullscreenUnfoldController(
|
||||||
Context context,
|
|
||||||
Optional<ShellUnfoldProgressProvider> progressProvider,
|
Optional<ShellUnfoldProgressProvider> progressProvider,
|
||||||
Lazy<UnfoldBackgroundController> unfoldBackgroundController,
|
Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
|
||||||
DisplayInsetsController displayInsetsController,
|
FullscreenUnfoldTaskAnimator fullscreenUnfoldTaskAnimator,
|
||||||
|
UnfoldBackgroundController unfoldBackgroundController,
|
||||||
@ShellMainThread ShellExecutor mainExecutor
|
@ShellMainThread ShellExecutor mainExecutor
|
||||||
) {
|
) {
|
||||||
return new FullscreenUnfoldController(context, mainExecutor,
|
return new FullscreenUnfoldController(mainExecutor,
|
||||||
unfoldBackgroundController.get(), progressProvider.get(),
|
unfoldBackgroundController, progressProvider.get(),
|
||||||
displayInsetsController);
|
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
|
@Provides
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
|
|||||||
taskInfo.taskId);
|
taskInfo.taskId);
|
||||||
final Point positionInParent = taskInfo.positionInParent;
|
final Point positionInParent = taskInfo.positionInParent;
|
||||||
mDataByTaskId.put(taskInfo.taskId, new TaskData(leash, positionInParent));
|
mDataByTaskId.put(taskInfo.taskId, new TaskData(leash, positionInParent));
|
||||||
|
mAnimatableTasksListener.onTaskAppeared(taskInfo);
|
||||||
|
|
||||||
if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
|
if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
|
||||||
mSyncQueue.runInSync(t -> {
|
mSyncQueue.runInSync(t -> {
|
||||||
// Reset several properties back to fullscreen (PiP, for example, leaves all these
|
// 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);
|
t.show(leash);
|
||||||
});
|
});
|
||||||
|
|
||||||
mAnimatableTasksListener.onTaskAppeared(taskInfo);
|
|
||||||
updateRecentsForVisibleFullscreenTask(taskInfo);
|
updateRecentsForVisibleFullscreenTask(taskInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
|
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
|
||||||
|
mAnimatableTasksListener.onTaskInfoChanged(taskInfo);
|
||||||
|
|
||||||
if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
|
if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
|
||||||
|
|
||||||
mAnimatableTasksListener.onTaskInfoChanged(taskInfo);
|
|
||||||
updateRecentsForVisibleFullscreenTask(taskInfo);
|
updateRecentsForVisibleFullscreenTask(taskInfo);
|
||||||
|
|
||||||
final TaskData data = mDataByTaskId.get(taskInfo.taskId);
|
final TaskData data = mDataByTaskId.get(taskInfo.taskId);
|
||||||
|
|||||||
@@ -17,213 +17,119 @@
|
|||||||
package com.android.wm.shell.fullscreen;
|
package com.android.wm.shell.fullscreen;
|
||||||
|
|
||||||
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
|
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.annotation.NonNull;
|
||||||
import android.app.ActivityManager;
|
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 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;
|
||||||
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
|
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
|
||||||
import com.android.wm.shell.unfold.UnfoldBackgroundController;
|
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;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controls full screen app unfold transition: animating cropping window and scaling when
|
* Controls full screen app unfold transition: animating cropping window and scaling when
|
||||||
* folding or unfolding a foldable device.
|
* 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,
|
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;
|
|
||||||
|
|
||||||
private final Executor mExecutor;
|
private final Executor mExecutor;
|
||||||
private final ShellUnfoldProgressProvider mProgressProvider;
|
private final ShellUnfoldProgressProvider mProgressProvider;
|
||||||
private final DisplayInsetsController mDisplayInsetsController;
|
|
||||||
|
|
||||||
private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
|
|
||||||
private final UnfoldBackgroundController mBackgroundController;
|
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 SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
|
||||||
|
private final FullscreenUnfoldTaskAnimator mAnimator;
|
||||||
|
private final UnfoldTransitionHandler mUnfoldTransitionHandler;
|
||||||
|
|
||||||
|
private boolean mShouldHandleAnimation = false;
|
||||||
|
|
||||||
public FullscreenUnfoldController(
|
public FullscreenUnfoldController(
|
||||||
@NonNull Context context,
|
|
||||||
@NonNull Executor executor,
|
@NonNull Executor executor,
|
||||||
@NonNull UnfoldBackgroundController backgroundController,
|
@NonNull UnfoldBackgroundController backgroundController,
|
||||||
@NonNull ShellUnfoldProgressProvider progressProvider,
|
@NonNull ShellUnfoldProgressProvider progressProvider,
|
||||||
@NonNull DisplayInsetsController displayInsetsController
|
@NonNull UnfoldTransitionHandler unfoldTransitionHandler,
|
||||||
|
@NonNull FullscreenUnfoldTaskAnimator animator
|
||||||
) {
|
) {
|
||||||
mExecutor = executor;
|
mExecutor = executor;
|
||||||
mProgressProvider = progressProvider;
|
mProgressProvider = progressProvider;
|
||||||
mDisplayInsetsController = displayInsetsController;
|
|
||||||
mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
|
|
||||||
mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
|
|
||||||
com.android.internal.R.dimen.taskbar_frame_height);
|
|
||||||
mBackgroundController = backgroundController;
|
mBackgroundController = backgroundController;
|
||||||
|
mUnfoldTransitionHandler = unfoldTransitionHandler;
|
||||||
|
mAnimator = animator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the controller
|
* Initializes the controller
|
||||||
*/
|
*/
|
||||||
public void init() {
|
public void init() {
|
||||||
|
mAnimator.init();
|
||||||
mProgressProvider.addListener(mExecutor, this);
|
mProgressProvider.addListener(mExecutor, this);
|
||||||
mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this);
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStateChangeStarted() {
|
||||||
|
mShouldHandleAnimation = !mUnfoldTransitionHandler.willHandleTransition();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStateChangeProgress(float progress) {
|
public void onStateChangeProgress(float progress) {
|
||||||
if (mAnimationContextByTaskId.size() == 0) return;
|
if (!mAnimator.hasActiveTasks() || !mShouldHandleAnimation) return;
|
||||||
|
|
||||||
mBackgroundController.ensureBackground(mTransaction);
|
mBackgroundController.ensureBackground(mTransaction);
|
||||||
|
mAnimator.applyAnimationProgress(progress, 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
mTransaction.apply();
|
mTransaction.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStateChangeFinished() {
|
public void onStateChangeFinished() {
|
||||||
for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
|
if (!mShouldHandleAnimation) {
|
||||||
final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
|
return;
|
||||||
resetSurface(context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mShouldHandleAnimation = false;
|
||||||
|
mAnimator.resetAllSurfaces(mTransaction);
|
||||||
mBackgroundController.removeBackground(mTransaction);
|
mBackgroundController.removeBackground(mTransaction);
|
||||||
mTransaction.apply();
|
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
|
* Called when a new matching task appeared
|
||||||
*/
|
*/
|
||||||
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
|
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
|
||||||
AnimationContext animationContext = new AnimationContext(leash, mTaskbarInsetsSource,
|
mAnimator.addTask(taskInfo, leash);
|
||||||
taskInfo);
|
|
||||||
mAnimationContextByTaskId.put(taskInfo.taskId, animationContext);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when matching task changed
|
* Called when matching task changed
|
||||||
*/
|
*/
|
||||||
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
|
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
|
||||||
AnimationContext animationContext = mAnimationContextByTaskId.get(taskInfo.taskId);
|
mAnimator.onTaskInfoChanged(taskInfo);
|
||||||
if (animationContext != null) {
|
|
||||||
animationContext.update(mTaskbarInsetsSource, taskInfo);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when matching task vanished
|
* Called when matching task vanished
|
||||||
*/
|
*/
|
||||||
public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
|
public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
|
||||||
AnimationContext animationContext = mAnimationContextByTaskId.get(taskInfo.taskId);
|
// PiP task has its own cleanup path, ignore surface reset to avoid conflict.
|
||||||
if (animationContext != null) {
|
if (taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED) {
|
||||||
// PiP task has its own cleanup path, ignore surface reset to avoid conflict.
|
mAnimator.resetSurface(taskInfo, mTransaction);
|
||||||
if (taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED) {
|
|
||||||
resetSurface(animationContext);
|
|
||||||
}
|
|
||||||
mAnimationContextByTaskId.remove(taskInfo.taskId);
|
|
||||||
}
|
}
|
||||||
|
mAnimator.removeTask(taskInfo);
|
||||||
|
|
||||||
if (mAnimationContextByTaskId.size() == 0) {
|
if (!mAnimator.hasActiveTasks()) {
|
||||||
mBackgroundController.removeBackground(mTransaction);
|
mBackgroundController.removeBackground(mTransaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
mTransaction.apply();
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ public class UnfoldBackgroundController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private float[] getBackgroundColor(Context context) {
|
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[]{
|
return new float[]{
|
||||||
(float) red(colorInt) / 255.0F,
|
(float) red(colorInt) / 255.0F,
|
||||||
(float) green(colorInt) / 255.0F,
|
(float) green(colorInt) / 255.0F,
|
||||||
|
|||||||
@@ -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.TransitionFinishCallback;
|
||||||
import com.android.wm.shell.transition.Transitions.TransitionHandler;
|
import com.android.wm.shell.transition.Transitions.TransitionHandler;
|
||||||
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
|
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;
|
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 {
|
public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListener {
|
||||||
|
|
||||||
private final ShellUnfoldProgressProvider mUnfoldProgressProvider;
|
private final ShellUnfoldProgressProvider mUnfoldProgressProvider;
|
||||||
private final Transitions mTransitions;
|
private final Transitions mTransitions;
|
||||||
|
private final UnfoldBackgroundController mUnfoldBackgroundController;
|
||||||
private final Executor mExecutor;
|
private final Executor mExecutor;
|
||||||
private final TransactionPool mTransactionPool;
|
private final TransactionPool mTransactionPool;
|
||||||
|
|
||||||
@@ -51,17 +56,22 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
|
|||||||
@Nullable
|
@Nullable
|
||||||
private IBinder mTransition;
|
private IBinder mTransition;
|
||||||
|
|
||||||
private final List<TransitionInfo.Change> mAnimatedFullscreenTasks = new ArrayList<>();
|
private final FullscreenUnfoldTaskAnimator mFullscreenAnimator;
|
||||||
|
|
||||||
public UnfoldTransitionHandler(ShellUnfoldProgressProvider unfoldProgressProvider,
|
public UnfoldTransitionHandler(ShellUnfoldProgressProvider unfoldProgressProvider,
|
||||||
TransactionPool transactionPool, Executor executor, Transitions transitions) {
|
FullscreenUnfoldTaskAnimator animator, TransactionPool transactionPool,
|
||||||
|
UnfoldBackgroundController unfoldBackgroundController,
|
||||||
|
Executor executor, Transitions transitions) {
|
||||||
mUnfoldProgressProvider = unfoldProgressProvider;
|
mUnfoldProgressProvider = unfoldProgressProvider;
|
||||||
|
mFullscreenAnimator = animator;
|
||||||
mTransactionPool = transactionPool;
|
mTransactionPool = transactionPool;
|
||||||
|
mUnfoldBackgroundController = unfoldBackgroundController;
|
||||||
mExecutor = executor;
|
mExecutor = executor;
|
||||||
mTransitions = transitions;
|
mTransitions = transitions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init() {
|
public void init() {
|
||||||
|
mFullscreenAnimator.init();
|
||||||
mTransitions.addHandler(this);
|
mTransitions.addHandler(this);
|
||||||
mUnfoldProgressProvider.addListener(mExecutor, this);
|
mUnfoldProgressProvider.addListener(mExecutor, this);
|
||||||
}
|
}
|
||||||
@@ -71,40 +81,36 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
|
|||||||
@NonNull SurfaceControl.Transaction startTransaction,
|
@NonNull SurfaceControl.Transaction startTransaction,
|
||||||
@NonNull SurfaceControl.Transaction finishTransaction,
|
@NonNull SurfaceControl.Transaction finishTransaction,
|
||||||
@NonNull TransitionFinishCallback finishCallback) {
|
@NonNull TransitionFinishCallback finishCallback) {
|
||||||
|
|
||||||
if (transition != mTransition) return false;
|
if (transition != mTransition) return false;
|
||||||
|
|
||||||
|
mUnfoldBackgroundController.ensureBackground(startTransaction);
|
||||||
startTransaction.apply();
|
startTransaction.apply();
|
||||||
|
|
||||||
mAnimatedFullscreenTasks.clear();
|
mFullscreenAnimator.clearTasks();
|
||||||
info.getChanges().forEach(change -> {
|
info.getChanges().forEach(change -> {
|
||||||
final boolean allowedToAnimate = change.getTaskInfo() != null
|
final boolean allowedToAnimate = change.getTaskInfo() != null
|
||||||
|
&& change.getTaskInfo().isVisible()
|
||||||
&& change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
|
&& change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
|
||||||
&& change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME
|
&& change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME
|
||||||
&& change.getMode() == TRANSIT_CHANGE;
|
&& change.getMode() == TRANSIT_CHANGE;
|
||||||
|
|
||||||
if (allowedToAnimate) {
|
if (allowedToAnimate) {
|
||||||
mAnimatedFullscreenTasks.add(change);
|
mFullscreenAnimator.addTask(change.getTaskInfo(), change.getLeash());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
mFullscreenAnimator.resetAllSurfaces(finishTransaction);
|
||||||
|
mUnfoldBackgroundController.removeBackground(finishTransaction);
|
||||||
mFinishCallback = finishCallback;
|
mFinishCallback = finishCallback;
|
||||||
mTransition = null;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStateChangeProgress(float progress) {
|
public void onStateChangeProgress(float progress) {
|
||||||
mAnimatedFullscreenTasks.forEach(change -> {
|
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
|
||||||
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
|
mFullscreenAnimator.applyAnimationProgress(progress, transaction);
|
||||||
|
transaction.apply();
|
||||||
// TODO: this is a placeholder animation, replace with a spec version in the next CLs
|
mTransactionPool.release(transaction);
|
||||||
final float testScale = 0.8f + 0.2f * progress;
|
|
||||||
transaction.setScale(change.getLeash(), testScale, testScale);
|
|
||||||
|
|
||||||
transaction.apply();
|
|
||||||
mTransactionPool.release(transaction);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -112,7 +118,8 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
|
|||||||
if (mFinishCallback != null) {
|
if (mFinishCallback != null) {
|
||||||
mFinishCallback.onTransitionFinished(null, null);
|
mFinishCallback.onTransitionFinished(null, null);
|
||||||
mFinishCallback = null;
|
mFinishCallback = null;
|
||||||
mAnimatedFullscreenTasks.clear();
|
mTransition = null;
|
||||||
|
mFullscreenAnimator.clearTasks();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,11 +127,14 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
|
|||||||
@Override
|
@Override
|
||||||
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
|
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
|
||||||
@NonNull TransitionRequestInfo request) {
|
@NonNull TransitionRequestInfo request) {
|
||||||
if (request.getType() == TRANSIT_CHANGE && request.getDisplayChange() != null
|
if (request.getType() == TRANSIT_CHANGE && request.getDisplayChange() != null) {
|
||||||
&& request.getDisplayChange().isPhysicalDisplayChanged()) {
|
|
||||||
mTransition = transition;
|
mTransition = transition;
|
||||||
return new WindowContainerTransaction();
|
return new WindowContainerTransaction();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean willHandleTransition() {
|
||||||
|
return mTransition != null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user