diff options
Diffstat (limited to 'libs')
57 files changed, 848 insertions, 503 deletions
diff --git a/libs/WindowManager/Jetpack/Android.bp b/libs/WindowManager/Jetpack/Android.bp index a5b192cd7ceb..abe8f859f2fe 100644 --- a/libs/WindowManager/Jetpack/Android.bp +++ b/libs/WindowManager/Jetpack/Android.bp @@ -55,20 +55,6 @@ prebuilt_etc { } // Extensions -// NOTE: This module is still under active development and must not -// be used in production. Use 'androidx.window.sidecar' instead. -android_library_import { - name: "window-extensions", - aars: ["window-extensions-release.aar"], - sdk_version: "current", -} - -android_library_import { - name: "window-extensions-core", - aars: ["window-extensions-core-release.aar"], - sdk_version: "current", -} - java_library { name: "androidx.window.extensions", srcs: [ @@ -77,8 +63,8 @@ java_library { "src/androidx/window/common/**/*.java", ], static_libs: [ - "window-extensions", - "window-extensions-core", + "androidx.window.extensions_extensions-nodeps", + "androidx.window.extensions.core_core-nodeps", ], installable: true, sdk_version: "core_platform", diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java index 8386131b177d..a7a6b3c92157 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java @@ -122,16 +122,6 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { addWindowLayoutInfoListener(activity, extConsumer); } - @Override - public void addWindowLayoutInfoListener(@NonNull @UiContext Context context, - @NonNull java.util.function.Consumer<WindowLayoutInfo> consumer) { - final Consumer<WindowLayoutInfo> extConsumer = consumer::accept; - synchronized (mLock) { - mJavaToExtConsumers.put(consumer, extConsumer); - } - addWindowLayoutInfoListener(context, extConsumer); - } - /** * Similar to {@link #addWindowLayoutInfoListener(Activity, java.util.function.Consumer)}, but * takes a UI Context as a parameter. diff --git a/libs/WindowManager/Jetpack/window-extensions-core-release.aar b/libs/WindowManager/Jetpack/window-extensions-core-release.aar Binary files differdeleted file mode 100644 index 96ff840b984b..000000000000 --- a/libs/WindowManager/Jetpack/window-extensions-core-release.aar +++ /dev/null diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar Binary files differdeleted file mode 100644 index c3b6916121d0..000000000000 --- a/libs/WindowManager/Jetpack/window-extensions-release.aar +++ /dev/null diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java index 521a65cc4df6..bfbddbbe4aa0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java @@ -22,6 +22,7 @@ import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static java.util.Objects.requireNonNull; import android.content.Context; +import android.graphics.Rect; import android.os.IBinder; import android.util.ArrayMap; import android.view.SurfaceControl; @@ -35,6 +36,9 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.util.TransitionUtil; + +import java.util.List; /** * Responsible for handling ActivityEmbedding related transitions. @@ -86,12 +90,13 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { boolean containsEmbeddingSplit = false; - for (TransitionInfo.Change change : info.getChanges()) { + boolean containsNonEmbeddedChange = false; + final List<TransitionInfo.Change> changes = info.getChanges(); + for (int i = changes.size() - 1; i >= 0; i--) { + final TransitionInfo.Change change = changes.get(i); if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) { - // Only animate the transition if all changes are in a Task with ActivityEmbedding. - return false; - } - if (!containsEmbeddingSplit && !change.hasFlags(FLAG_FILLS_TASK)) { + containsNonEmbeddedChange = true; + } else if (!change.hasFlags(FLAG_FILLS_TASK)) { // Whether the Task contains any ActivityEmbedding split before or after the // transition. containsEmbeddingSplit = true; @@ -103,6 +108,9 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle // such as the device is in a folded state. return false; } + if (containsNonEmbeddedChange && !handleNonEmbeddedChanges(changes)) { + return false; + } // Start ActivityEmbedding animation. mTransitionCallbacks.put(transition, finishCallback); @@ -110,6 +118,37 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle return true; } + private boolean handleNonEmbeddedChanges(List<TransitionInfo.Change> changes) { + final Rect nonClosingEmbeddedArea = new Rect(); + for (int i = changes.size() - 1; i >= 0; i--) { + final TransitionInfo.Change change = changes.get(i); + if (!TransitionUtil.isClosingType(change.getMode())) { + if (change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) { + nonClosingEmbeddedArea.union(change.getEndAbsBounds()); + continue; + } + // Not able to handle non-embedded container if it is not closing. + return false; + } + } + for (int i = changes.size() - 1; i >= 0; i--) { + final TransitionInfo.Change change = changes.get(i); + if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY) + && !nonClosingEmbeddedArea.contains(change.getEndAbsBounds())) { + // Unknown to animate containers outside the area of embedded activities. + return false; + } + } + // Drop the non-embedded closing change because it is occluded by embedded activities. + for (int i = changes.size() - 1; i >= 0; i--) { + final TransitionInfo.Change change = changes.get(i); + if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) { + changes.remove(i); + } + } + return true; + } + @Nullable @Override public WindowContainerTransaction handleRequest(@NonNull IBinder transition, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java index e84a78f42616..133fd87a2f63 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java @@ -33,12 +33,19 @@ public interface BackAnimation { * * @param touchX the X touch position of the {@link MotionEvent}. * @param touchY the Y touch position of the {@link MotionEvent}. + * @param velocityX the X velocity computed from the {@link MotionEvent}. + * @param velocityY the Y velocity computed from the {@link MotionEvent}. * @param keyAction the original {@link KeyEvent#getAction()} when the event was dispatched to * the process. This is forwarded separately because the input pipeline may mutate * the {#event} action state later. * @param swipeEdge the edge from which the swipe begins. */ - void onBackMotion(float touchX, float touchY, int keyAction, + void onBackMotion( + float touchX, + float touchY, + float velocityX, + float velocityY, + int keyAction, @BackEvent.SwipeEdge int swipeEdge); /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index 210c9aab14d6..47d3a5c52074 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -256,8 +256,20 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private class BackAnimationImpl implements BackAnimation { @Override public void onBackMotion( - float touchX, float touchY, int keyAction, @BackEvent.SwipeEdge int swipeEdge) { - mShellExecutor.execute(() -> onMotionEvent(touchX, touchY, keyAction, swipeEdge)); + float touchX, + float touchY, + float velocityX, + float velocityY, + int keyAction, + @BackEvent.SwipeEdge int swipeEdge + ) { + mShellExecutor.execute(() -> onMotionEvent( + /* touchX = */ touchX, + /* touchY = */ touchY, + /* velocityX = */ velocityX, + /* velocityY = */ velocityY, + /* keyAction = */ keyAction, + /* swipeEdge = */ swipeEdge)); } @Override @@ -332,13 +344,18 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont * Called when a new motion event needs to be transferred to this * {@link BackAnimationController} */ - public void onMotionEvent(float touchX, float touchY, int keyAction, + public void onMotionEvent( + float touchX, + float touchY, + float velocityX, + float velocityY, + int keyAction, @BackEvent.SwipeEdge int swipeEdge) { if (mPostCommitAnimationInProgress) { return; } - mTouchTracker.update(touchX, touchY); + mTouchTracker.update(touchX, touchY, velocityX, velocityY); if (keyAction == MotionEvent.ACTION_DOWN) { if (!mBackGestureStarted) { mShouldStartOnNextMoveEvent = true; @@ -561,6 +578,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } if (runner.isWaitingAnimation()) { ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Gesture released, but animation didn't ready."); + // Supposed it is in post commit animation state, and start the timeout to watch + // if the animation is ready. + mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION); return; } else if (runner.isAnimationCancelled()) { invokeOrCancelBack(); @@ -577,6 +597,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont if (mPostCommitAnimationInProgress) { return; } + + mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable); ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startPostCommitAnimation()"); mPostCommitAnimationInProgress = true; mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION); @@ -595,9 +617,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont */ @VisibleForTesting void onBackAnimationFinished() { - if (!mPostCommitAnimationInProgress) { - return; - } // Stop timeout runner. mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable); mPostCommitAnimationInProgress = false; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java index 695ef4e66302..904574b08562 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java @@ -42,11 +42,13 @@ class TouchTracker { */ private float mInitTouchX; private float mInitTouchY; + private float mLatestVelocityX; + private float mLatestVelocityY; private float mStartThresholdX; private int mSwipeEdge; private boolean mCancelled; - void update(float touchX, float touchY) { + void update(float touchX, float touchY, float velocityX, float velocityY) { /** * If back was previously cancelled but the user has started swiping in the forward * direction again, restart back. @@ -58,6 +60,8 @@ class TouchTracker { } mLatestTouchX = touchX; mLatestTouchY = touchY; + mLatestVelocityX = velocityX; + mLatestVelocityY = velocityY; } void setTriggerBack(boolean triggerBack) { @@ -84,7 +88,14 @@ class TouchTracker { } BackMotionEvent createStartEvent(RemoteAnimationTarget target) { - return new BackMotionEvent(mInitTouchX, mInitTouchY, 0, mSwipeEdge, target); + return new BackMotionEvent( + /* touchX = */ mInitTouchX, + /* touchY = */ mInitTouchY, + /* progress = */ 0, + /* velocityX = */ 0, + /* velocityY = */ 0, + /* swipeEdge = */ mSwipeEdge, + /* departingAnimationTarget = */ target); } BackMotionEvent createProgressEvent() { @@ -111,7 +122,14 @@ class TouchTracker { } BackMotionEvent createProgressEvent(float progress) { - return new BackMotionEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null); + return new BackMotionEvent( + /* touchX = */ mLatestTouchX, + /* touchY = */ mLatestTouchY, + /* progress = */ progress, + /* velocityX = */ mLatestVelocityX, + /* velocityY = */ mLatestVelocityY, + /* swipeEdge = */ mSwipeEdge, + /* departingAnimationTarget = */ null); } public void setProgressThreshold(float progressThreshold) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 670b24c176b5..0400963a47e8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -25,6 +25,7 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.app.WindowConfiguration.WindowingMode import android.content.Context +import android.graphics.Point import android.graphics.Rect import android.os.IBinder import android.os.SystemProperties @@ -193,6 +194,21 @@ class DesktopTasksController( } } + + /** + * Move a task to fullscreen after being dragged from fullscreen and released back into + * status bar area + */ + fun cancelMoveToFreeform(task: RunningTaskInfo, startPosition: Point) { + val wct = WindowContainerTransaction() + addMoveToFullscreenChanges(wct, task.token) + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct, startPosition) + } else { + shellTaskOrganizer.applyTransaction(wct) + } + } + fun moveToFullscreenWithAnimation(task: ActivityManager.RunningTaskInfo) { val wct = WindowContainerTransaction() addMoveToFullscreenChanges(wct, task.token) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java index 3df2340d4524..27eda16f4171 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java @@ -17,11 +17,13 @@ package com.android.wm.shell.desktopmode; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.app.ActivityManager; +import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; import android.view.SurfaceControl; @@ -55,6 +57,7 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition public static final int FREEFORM_ANIMATION_DURATION = 336; private final List<IBinder> mPendingTransitionTokens = new ArrayList<>(); + private Point mStartPosition; public EnterDesktopTaskTransitionHandler( Transitions transitions) { @@ -79,6 +82,17 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition mPendingTransitionTokens.add(token); } + /** + * Starts Transition of type TRANSIT_CANCEL_ENTERING_DESKTOP_MODE + * @param wct WindowContainerTransaction for transition + * @param startPosition Position of task when transition is triggered + */ + public void startCancelMoveToDesktopMode(@NonNull WindowContainerTransaction wct, + Point startPosition) { + mStartPosition = startPosition; + startTransition(Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE, wct); + } + @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, @@ -173,6 +187,37 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition return true; } + if (type == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE + && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN + && mStartPosition != null) { + // This Transition animates a task to fullscreen after being dragged from the status + // bar and then released back into the status bar area + final SurfaceControl sc = change.getLeash(); + startT.setWindowCrop(sc, null); + startT.apply(); + + final ValueAnimator animator = new ValueAnimator(); + animator.setFloatValues(DRAG_FREEFORM_SCALE, 1f); + animator.setDuration(FREEFORM_ANIMATION_DURATION); + final SurfaceControl.Transaction t = mTransactionSupplier.get(); + animator.addUpdateListener(animation -> { + final float scale = animation.getAnimatedFraction(); + t.setPosition(sc, mStartPosition.x * (1 - scale), + mStartPosition.y * (1 - scale)); + t.setScale(sc, scale, scale); + t.apply(); + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mTransitions.getMainExecutor().execute( + () -> finishCallback.onTransitionFinished(null, null)); + } + }); + animator.start(); + return true; + } + return false; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index f70df833cf4f..1d7e64988359 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -30,14 +30,17 @@ import android.app.TaskInfo; import android.content.Context; import android.content.pm.ActivityInfo; import android.graphics.Rect; +import android.os.SystemClock; import android.view.Surface; import android.view.SurfaceControl; import android.window.TaskSnapshot; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; +import com.android.internal.protolog.common.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.animation.Interpolators; +import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.transition.Transitions; import java.lang.annotation.Retention; @@ -61,6 +64,14 @@ public class PipAnimationController { @Retention(RetentionPolicy.SOURCE) public @interface AnimationType {} + /** + * The alpha type is set for swiping to home. But the swiped task may not enter PiP. And if + * another task enters PiP by non-swipe ways, e.g. call API in foreground or switch to 3-button + * navigation, then the alpha type is unexpected. So use a timeout to avoid applying wrong + * animation style to an unrelated task. + */ + private static final int ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS = 800; + public static final int TRANSITION_DIRECTION_NONE = 0; public static final int TRANSITION_DIRECTION_SAME = 1; public static final int TRANSITION_DIRECTION_TO_PIP = 2; @@ -109,6 +120,9 @@ public class PipAnimationController { }); private PipTransitionAnimator mCurrentAnimator; + @AnimationType + private int mOneShotAnimationType = ANIM_TYPE_BOUNDS; + private long mLastOneShotAlphaAnimationTime; public PipAnimationController(PipSurfaceTransactionHelper helper) { mSurfaceTransactionHelper = helper; @@ -222,6 +236,37 @@ public class PipAnimationController { } /** + * Sets the preferred enter animation type for one time. This is typically used to set the + * animation type to {@link PipAnimationController#ANIM_TYPE_ALPHA}. + * <p> + * For example, gesture navigation would first fade out the PiP activity, and the transition + * should be responsible to animate in (such as fade in) the PiP. + */ + public void setOneShotEnterAnimationType(@AnimationType int animationType) { + mOneShotAnimationType = animationType; + if (animationType == ANIM_TYPE_ALPHA) { + mLastOneShotAlphaAnimationTime = SystemClock.uptimeMillis(); + } + } + + /** Returns the preferred animation type and consumes the one-shot type if needed. */ + @AnimationType + public int takeOneShotEnterAnimationType() { + final int type = mOneShotAnimationType; + if (type == ANIM_TYPE_ALPHA) { + // Restore to default type. + mOneShotAnimationType = ANIM_TYPE_BOUNDS; + if (SystemClock.uptimeMillis() - mLastOneShotAlphaAnimationTime + > ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "Alpha animation is expired. Use bounds animation."); + return ANIM_TYPE_BOUNDS; + } + } + return type; + } + + /** * Additional callback interface for PiP animation */ public static class PipAnimationCallback { @@ -255,7 +300,7 @@ public class PipAnimationController { * @return true if handled by the handler, false otherwise. */ public boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, - Rect destinationBounds) { + Rect destinationBounds, float alpha) { return false; } } @@ -356,9 +401,10 @@ public class PipAnimationController { } boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, - Rect destinationBounds) { + Rect destinationBounds, float alpha) { if (mPipTransactionHandler != null) { - return mPipTransactionHandler.handlePipTransaction(leash, tx, destinationBounds); + return mPipTransactionHandler.handlePipTransaction( + leash, tx, destinationBounds, alpha); } return false; } @@ -503,7 +549,9 @@ public class PipAnimationController { getSurfaceTransactionHelper().alpha(tx, leash, alpha) .round(tx, leash, shouldApplyCornerRadius()) .shadow(tx, leash, shouldApplyShadowRadius()); - tx.apply(); + if (!handlePipTransaction(leash, tx, destinationBounds, alpha)) { + tx.apply(); + } } @Override @@ -618,7 +666,7 @@ public class PipAnimationController { .shadow(tx, leash, shouldApplyShadowRadius()); } } - if (!handlePipTransaction(leash, tx, bounds)) { + if (!handlePipTransaction(leash, tx, bounds, /* alpha= */ 1f)) { tx.apply(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java index 000624499f79..0775f5279e31 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java @@ -45,6 +45,13 @@ public interface PipMenuController { String MENU_WINDOW_TITLE = "PipMenuView"; /** + * Used with + * {@link PipMenuController#movePipMenu(SurfaceControl, SurfaceControl.Transaction, Rect, + * float)} to indicate that we don't want to affect the alpha value of the menu surfaces. + */ + float ALPHA_NO_CHANGE = -1f; + + /** * Called when * {@link PipTaskOrganizer#onTaskAppeared(RunningTaskInfo, SurfaceControl)} * is called. @@ -85,8 +92,8 @@ public interface PipMenuController { * need to synchronize the movements on the same frame as PiP. */ default void movePipMenu(@Nullable SurfaceControl pipLeash, - @Nullable SurfaceControl.Transaction t, - Rect destinationBounds) {} + @Nullable SurfaceControl.Transaction t, Rect destinationBounds, float alpha) { + } /** * Update the PiP menu with the given bounds for re-layout purposes. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index a0bd064149d2..23706a5e35a4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -62,7 +62,6 @@ import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Rect; import android.os.RemoteException; -import android.os.SystemClock; import android.os.SystemProperties; import android.util.Log; import android.view.Choreographer; @@ -111,12 +110,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, DisplayController.OnDisplaysChangedListener, ShellTaskOrganizer.FocusListener { private static final String TAG = PipTaskOrganizer.class.getSimpleName(); private static final boolean DEBUG = false; - /** - * The alpha type is set for swiping to home. But the swiped task may not enter PiP. And if - * another task enters PiP by non-swipe ways, e.g. call API in foreground or switch to 3-button - * navigation, then the alpha type is unexpected. - */ - private static final int ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS = 1000; /** * The fixed start delay in ms when fading out the content overlay from bounds animation. @@ -256,7 +249,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, }, null); } - private boolean shouldSyncPipTransactionWithMenu() { + protected boolean shouldSyncPipTransactionWithMenu() { return mPipMenuController.isMenuVisible(); } @@ -284,9 +277,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, new PipAnimationController.PipTransactionHandler() { @Override public boolean handlePipTransaction(SurfaceControl leash, - SurfaceControl.Transaction tx, Rect destinationBounds) { + SurfaceControl.Transaction tx, Rect destinationBounds, float alpha) { if (shouldSyncPipTransactionWithMenu()) { - mPipMenuController.movePipMenu(leash, tx, destinationBounds); + mPipMenuController.movePipMenu(leash, tx, destinationBounds, alpha); return true; } return false; @@ -301,8 +294,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, private WindowContainerToken mToken; private SurfaceControl mLeash; protected PipTransitionState mPipTransitionState; - private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS; - private long mLastOneShotAlphaAnimationTime; private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mSurfaceControlTransactionFactory; protected PictureInPictureParams mPictureInPictureParams; @@ -390,6 +381,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, return mPipTransitionController; } + PipAnimationController.PipTransactionHandler getPipTransactionHandler() { + return mPipTransactionHandler; + } + public Rect getCurrentOrAnimatingBounds() { PipAnimationController.PipTransitionAnimator animator = mPipAnimationController.getCurrentAnimator(); @@ -422,18 +417,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } /** - * Sets the preferred animation type for one time. - * This is typically used to set the animation type to - * {@link PipAnimationController#ANIM_TYPE_ALPHA}. - */ - public void setOneShotAnimationType(@PipAnimationController.AnimationType int animationType) { - mOneShotAnimationType = animationType; - if (animationType == ANIM_TYPE_ALPHA) { - mLastOneShotAlphaAnimationTime = SystemClock.uptimeMillis(); - } - } - - /** * Override if the PiP should always use a fade-in animation during PiP entry. * * @return true if the mOneShotAnimationType should always be @@ -733,26 +716,17 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, return; } - if (mOneShotAnimationType == ANIM_TYPE_ALPHA - && SystemClock.uptimeMillis() - mLastOneShotAlphaAnimationTime - > ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: Alpha animation is expired. Use bounds animation.", TAG); - mOneShotAnimationType = ANIM_TYPE_BOUNDS; - } - if (Transitions.ENABLE_SHELL_TRANSITIONS) { // For Shell transition, we will animate the window in PipTransition#startAnimation // instead of #onTaskAppeared. return; } - if (shouldAlwaysFadeIn()) { - mOneShotAnimationType = ANIM_TYPE_ALPHA; - } - + final int animationType = shouldAlwaysFadeIn() + ? ANIM_TYPE_ALPHA + : mPipAnimationController.takeOneShotEnterAnimationType(); if (mWaitForFixedRotation) { - onTaskAppearedWithFixedRotation(); + onTaskAppearedWithFixedRotation(animationType); return; } @@ -763,7 +737,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, Objects.requireNonNull(destinationBounds, "Missing destination bounds"); final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds(); - if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { + if (animationType == ANIM_TYPE_BOUNDS) { if (!shouldAttachMenuEarly()) { mPipMenuController.attach(mLeash); } @@ -773,16 +747,15 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, mEnterAnimationDuration, null /* updateBoundsCallback */); mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP); - } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { + } else if (animationType == ANIM_TYPE_ALPHA) { enterPipWithAlphaAnimation(destinationBounds, mEnterAnimationDuration); - mOneShotAnimationType = ANIM_TYPE_BOUNDS; } else { - throw new RuntimeException("Unrecognized animation type: " + mOneShotAnimationType); + throw new RuntimeException("Unrecognized animation type: " + animationType); } } - private void onTaskAppearedWithFixedRotation() { - if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { + private void onTaskAppearedWithFixedRotation(int animationType) { + if (animationType == ANIM_TYPE_ALPHA) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: Defer entering PiP alpha animation, fixed rotation is ongoing", TAG); // If deferred, hside the surface till fixed rotation is completed. @@ -791,7 +764,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, tx.setAlpha(mLeash, 0f); tx.show(mLeash); tx.apply(); - mOneShotAnimationType = ANIM_TYPE_BOUNDS; return; } final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds(); @@ -1417,7 +1389,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, .scale(tx, mLeash, startBounds, toBounds, degrees) .round(tx, mLeash, startBounds, toBounds); if (shouldSyncPipTransactionWithMenu()) { - mPipMenuController.movePipMenu(mLeash, tx, toBounds); + mPipMenuController.movePipMenu(mLeash, tx, toBounds, PipMenuController.ALPHA_NO_CHANGE); } else { tx.apply(); } @@ -1583,7 +1555,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, if (!isInPip()) { return; } - mPipMenuController.movePipMenu(null, null, destinationBounds); + mPipMenuController.movePipMenu(null, null, destinationBounds, + PipMenuController.ALPHA_NO_CHANGE); mPipMenuController.updateMenuBounds(destinationBounds); } @@ -1895,7 +1868,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, + " binder=" + (mToken != null ? mToken.asBinder() : null)); pw.println(innerPrefix + "mLeash=" + mLeash); pw.println(innerPrefix + "mState=" + mPipTransitionState.getTransitionState()); - pw.println(innerPrefix + "mOneShotAnimationType=" + mOneShotAnimationType); pw.println(innerPrefix + "mPictureInPictureParams=" + mPictureInPictureParams); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 4a76a502462c..c5e92294794e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -87,7 +87,7 @@ public class PipTransition extends PipTransitionController { private final int mEnterExitAnimationDuration; private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; private final Optional<SplitScreenController> mSplitScreenOptional; - private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS; + private @PipAnimationController.AnimationType int mEnterAnimationType = ANIM_TYPE_BOUNDS; private Transitions.TransitionFinishCallback mFinishCallback; private SurfaceControl.Transaction mFinishTransaction; private final Rect mExitDestinationBounds = new Rect(); @@ -133,20 +133,6 @@ public class PipTransition extends PipTransitionController { } @Override - public void setIsFullAnimation(boolean isFullAnimation) { - setOneShotAnimationType(isFullAnimation ? ANIM_TYPE_BOUNDS : ANIM_TYPE_ALPHA); - } - - /** - * Sets the preferred animation type for one time. - * This is typically used to set the animation type to - * {@link PipAnimationController#ANIM_TYPE_ALPHA}. - */ - private void setOneShotAnimationType(@PipAnimationController.AnimationType int animationType) { - mOneShotAnimationType = animationType; - } - - @Override public void startExitTransition(int type, WindowContainerTransaction out, @Nullable Rect destinationBounds) { if (destinationBounds != null) { @@ -288,7 +274,10 @@ public class PipTransition extends PipTransitionController { if (!requestHasPipEnter(request)) { throw new IllegalStateException("Called PiP augmentRequest when request has no PiP"); } - if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { + mEnterAnimationType = mPipOrganizer.shouldAlwaysFadeIn() + ? ANIM_TYPE_ALPHA + : mPipAnimationController.takeOneShotEnterAnimationType(); + if (mEnterAnimationType == ANIM_TYPE_ALPHA) { mRequestedEnterTransition = transition; mRequestedEnterTask = request.getTriggerTask().token; outWCT.setActivityWindowingMode(request.getTriggerTask().token, @@ -308,7 +297,7 @@ public class PipTransition extends PipTransitionController { @Override public boolean handleRotateDisplay(int startRotation, int endRotation, WindowContainerTransaction wct) { - if (mRequestedEnterTransition != null && mOneShotAnimationType == ANIM_TYPE_ALPHA) { + if (mRequestedEnterTransition != null && mEnterAnimationType == ANIM_TYPE_ALPHA) { // A fade-in was requested but not-yet started. In this case, just recalculate the // initial state under the new rotation. int rotationDelta = deltaRotation(startRotation, endRotation); @@ -760,7 +749,6 @@ public class PipTransition extends PipTransitionController { if (taskInfo.pictureInPictureParams != null && taskInfo.pictureInPictureParams.isAutoEnterEnabled() && mPipTransitionState.getInSwipePipToHomeTransition()) { - mOneShotAnimationType = ANIM_TYPE_BOUNDS; final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay; startTransaction.setMatrix(leash, Matrix.IDENTITY_MATRIX, new float[9]) .setPosition(leash, destinationBounds.left, destinationBounds.top) @@ -796,17 +784,16 @@ public class PipTransition extends PipTransitionController { startTransaction.setMatrix(leash, tmpTransform, new float[9]); } - if (mPipOrganizer.shouldAlwaysFadeIn()) { - mOneShotAnimationType = ANIM_TYPE_ALPHA; - } - - if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { + final int enterAnimationType = mEnterAnimationType; + if (enterAnimationType == ANIM_TYPE_ALPHA) { + // Restore to default type. + mEnterAnimationType = ANIM_TYPE_BOUNDS; startTransaction.setAlpha(leash, 0f); } startTransaction.apply(); PipAnimationController.PipTransitionAnimator animator; - if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { + if (enterAnimationType == ANIM_TYPE_BOUNDS) { animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds, currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, 0 /* startingAngle */, rotationDelta); @@ -829,15 +816,14 @@ public class PipTransition extends PipTransitionController { animator.setColorContentOverlay(mContext); } } - } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { + } else if (enterAnimationType == ANIM_TYPE_ALPHA) { animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds, 0f, 1f); - mOneShotAnimationType = ANIM_TYPE_BOUNDS; } else { - throw new RuntimeException("Unrecognized animation type: " - + mOneShotAnimationType); + throw new RuntimeException("Unrecognized animation type: " + enterAnimationType); } animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP) + .setPipTransactionHandler(mPipOrganizer.getPipTransactionHandler()) .setPipAnimationCallback(mPipAnimationCallback) .setDuration(mEnterExitAnimationDuration); if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) { @@ -897,7 +883,7 @@ public class PipTransition extends PipTransitionController { .setWindowCrop(leash, endBounds.width(), endBounds.height()); } } - mSplitScreenOptional.get().finishEnterSplitScreen(startTransaction); + mSplitScreenOptional.get().finishEnterSplitScreen(finishTransaction); startTransaction.apply(); mPipOrganizer.onExitPipFinished(taskInfo); @@ -964,7 +950,8 @@ public class PipTransition extends PipTransitionController { } private void finishResizeForMenu(Rect destinationBounds) { - mPipMenuController.movePipMenu(null, null, destinationBounds); + mPipMenuController.movePipMenu(null, null, destinationBounds, + PipMenuController.ALPHA_NO_CHANGE); mPipMenuController.updateMenuBounds(destinationBounds); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index f51e247fe112..7979ce7a80c1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -105,15 +105,6 @@ public abstract class PipTransitionController implements Transitions.TransitionH } /** - * Called to inform the transition that the animation should start with the assumption that - * PiP is not animating from its original bounds, but rather a continuation of another - * animation. For example, gesture navigation would first fade out the PiP activity, and the - * transition should be responsible to animate in (such as fade in) the PiP. - */ - public void setIsFullAnimation(boolean isFullAnimation) { - } - - /** * Called when the Shell wants to start an exit Pip transition/animation. */ public void startExitTransition(int type, WindowContainerTransaction out, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java index 94e593b106a5..e7a1395f541c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java @@ -298,7 +298,8 @@ public class PhonePipMenuController implements PipMenuController { } // Sync the menu bounds before showing it in case it is out of sync. - movePipMenu(null /* pipLeash */, null /* transaction */, stackBounds); + movePipMenu(null /* pipLeash */, null /* transaction */, stackBounds, + PipMenuController.ALPHA_NO_CHANGE); updateMenuBounds(stackBounds); mPipMenuView.showMenu(menuState, stackBounds, allowMenuTimeout, willResizeMenu, withDelay, @@ -311,7 +312,7 @@ public class PhonePipMenuController implements PipMenuController { @Override public void movePipMenu(@Nullable SurfaceControl pipLeash, @Nullable SurfaceControl.Transaction t, - Rect destinationBounds) { + Rect destinationBounds, float alpha) { if (destinationBounds.isEmpty()) { return; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 463ad77d828f..b0bb14b49db6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -968,12 +968,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipBoundsState.setShelfVisibility(visible, shelfHeight); } - private void setPinnedStackAnimationType(int animationType) { - mPipTaskOrganizer.setOneShotAnimationType(animationType); - mPipTransitionController.setIsFullAnimation( - animationType == PipAnimationController.ANIM_TYPE_BOUNDS); - } - @VisibleForTesting void setPinnedStackAnimationListener(PipAnimationListener callback) { mPinnedStackAnimationRecentsCallback = callback; @@ -1337,7 +1331,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb @Override public void setPipAnimationTypeToAlpha() { executeRemoteCallWithTaskPermission(mController, "setPipAnimationTypeToAlpha", - (controller) -> controller.setPinnedStackAnimationType(ANIM_TYPE_ALPHA)); + (controller) -> controller.mPipAnimationController.setOneShotEnterAnimationType( + ANIM_TYPE_ALPHA)); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java index a7171fd5b220..82fe38ccc7b3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java @@ -21,6 +21,7 @@ import static com.android.wm.shell.pip.PipUtils.dpToPx; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.Point; import android.graphics.PointF; @@ -366,11 +367,8 @@ public class PipSizeSpecHandler { mContext = context; mPipDisplayLayoutState = pipDisplayLayoutState; - boolean enablePipSizeLargeScreen = SystemProperties - .getBoolean("persist.wm.debug.enable_pip_size_large_screen", true); - // choose between two implementations of size spec logic - if (enablePipSizeLargeScreen) { + if (supportsPipSizeLargeScreen()) { mSizeSpecSourceImpl = new SizeSpecLargeScreenOptimizedImpl(); } else { mSizeSpecSourceImpl = new SizeSpecDefaultImpl(); @@ -515,6 +513,18 @@ public class PipSizeSpecHandler { } } + @VisibleForTesting + boolean supportsPipSizeLargeScreen() { + // TODO(b/271468706): switch Tv to having a dedicated SizeSpecSource once the SizeSpecSource + // can be injected + return SystemProperties + .getBoolean("persist.wm.debug.enable_pip_size_large_screen", true) && !isTv(); + } + + private boolean isTv() { + return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); + } + /** Dumps internal state. */ public void dump(PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java index b18e21c03c63..b2a189b45d6c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java @@ -30,6 +30,7 @@ import android.os.Handler; import android.view.SurfaceControl; import android.view.View; import android.view.ViewRootImpl; +import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.window.SurfaceSyncGroup; @@ -202,8 +203,10 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis } private void addPipMenuViewToSystemWindows(View v, String title) { - mSystemWindows.addView(v, getPipMenuLayoutParams(mContext, title, 0 /* width */, - 0 /* height */), 0 /* displayId */, SHELL_ROOT_LAYER_PIP); + final WindowManager.LayoutParams layoutParams = + getPipMenuLayoutParams(mContext, title, 0 /* width */, 0 /* height */); + layoutParams.alpha = 0f; + mSystemWindows.addView(v, layoutParams, 0 /* displayId */, SHELL_ROOT_LAYER_PIP); } void onPipTransitionFinished(boolean enterTransition) { @@ -309,9 +312,9 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis @Override public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction pipTx, - Rect pipBounds) { + Rect pipBounds, float alpha) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: movePipMenu: %s", TAG, pipBounds.toShortString()); + "%s: movePipMenu: %s, alpha %s", TAG, pipBounds.toShortString(), alpha); if (pipBounds.isEmpty()) { if (pipTx == null) { @@ -333,6 +336,11 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis pipTx.setPosition(frontSurface, menuDestBounds.left, menuDestBounds.top); pipTx.setPosition(backSurface, menuDestBounds.left, menuDestBounds.top); + if (alpha != ALPHA_NO_CHANGE) { + pipTx.setAlpha(frontSurface, alpha); + pipTx.setAlpha(backSurface, alpha); + } + // Synchronize drawing the content in the front and back surfaces together with the pip // transaction and the position change for the front and back surfaces final SurfaceSyncGroup syncGroup = new SurfaceSyncGroup("TvPip"); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java index 0940490e9944..4819f665d6d3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java @@ -98,4 +98,11 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer { protected boolean shouldAlwaysFadeIn() { return true; } + + @Override + protected boolean shouldSyncPipTransactionWithMenu() { + // We always have a menu visible and want to sync the pip transaction with the menu, even + // when the menu alpha is 0 (e.g. when a fade-in animation starts). + return true; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 2cd16be9590c..498f95c8595e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -541,6 +541,34 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, instanceId); } + void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1, + int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, + float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { + if (options1 == null) options1 = new Bundle(); + final ActivityOptions activityOptions = ActivityOptions.fromBundle(options1); + final String packageName1 = shortcutInfo.getPackage(); + // NOTE: This doesn't correctly pull out packageName2 if taskId is referring to a task in + // recents that hasn't launched and is not being organized + final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer); + if (samePackage(packageName1, packageName2)) { + if (supportMultiInstancesSplit(packageName1)) { + activityOptions.setApplyMultipleTaskFlagForShortcut(true); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); + } else { + if (mRecentTasksOptional.isPresent()) { + mRecentTasksOptional.get().removeSplitPair(taskId); + } + taskId = INVALID_TASK_ID; + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, + "Cancel entering split as not supporting multi-instances"); + Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text, + Toast.LENGTH_SHORT).show(); + } + } + mStageCoordinator.startShortcutAndTask(shortcutInfo, options1, taskId, options2, + splitPosition, splitRatio, remoteTransition, instanceId); + } + /** * See {@link #startIntent(PendingIntent, Intent, int, Bundle)} * @param instanceId to be used by {@link SplitscreenEventLogger} @@ -580,6 +608,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { Intent fillInIntent = null; final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent); + // NOTE: This doesn't correctly pull out packageName2 if taskId is referring to a task in + // recents that hasn't launched and is not being organized final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer); if (samePackage(packageName1, packageName2)) { if (supportMultiInstancesSplit(packageName1)) { @@ -587,6 +617,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } else { + if (mRecentTasksOptional.isPresent()) { + mRecentTasksOptional.get().removeSplitPair(taskId); + } + taskId = INVALID_TASK_ID; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Cancel entering split as not supporting multi-instances"); Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text, @@ -1075,9 +1109,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startShortcutAndTask", - (controller) -> controller.mStageCoordinator.startShortcutAndTask(shortcutInfo, - options1, taskId, options2, splitPosition, splitRatio, remoteTransition, - instanceId)); + (controller) -> controller.startShortcutAndTask(shortcutInfo, options1, taskId, + options2, splitPosition, splitRatio, remoteTransition, instanceId)); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index 22800ad8e8a8..2c08cd44becc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -22,6 +22,7 @@ import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; +import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString; @@ -155,8 +156,10 @@ class SplitScreenTransitions { } boolean isRootOrSplitSideRoot = change.getParent() == null || topRoot.equals(change.getParent()); - // For enter or exit, we only want to animate the side roots but not the top-root. - if (!isRootOrSplitSideRoot || topRoot.equals(change.getContainer())) { + boolean isDivider = change.getFlags() == FLAG_IS_DIVIDER_BAR; + // For enter or exit, we only want to animate side roots and the divider but not the + // top-root. + if (!isRootOrSplitSideRoot || topRoot.equals(change.getContainer()) || isDivider) { continue; } @@ -165,6 +168,10 @@ class SplitScreenTransitions { t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top); t.setWindowCrop(leash, change.getEndAbsBounds().width(), change.getEndAbsBounds().height()); + } else if (isDivider) { + t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top); + t.setLayer(leash, Integer.MAX_VALUE); + t.show(leash); } boolean isOpening = isOpeningTransition(info); if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) { @@ -282,6 +289,12 @@ class SplitScreenTransitions { return null; } + void startFullscreenTransition(WindowContainerTransaction wct, + @Nullable RemoteTransition handler) { + mTransitions.startTransition(TRANSIT_OPEN, wct, + new OneShotRemoteHandler(mTransitions.getMainExecutor(), handler)); + } + /** Starts a transition to enter split with a remote transition animator. */ IBinder startEnterTransition( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index f00fdba50748..e4f27240b2cb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -612,6 +612,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); + if (taskId2 == INVALID_TASK_ID) { + if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) { + prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct); + } + if (mRecentTasks.isPresent()) { + mRecentTasks.get().removeSplitPair(taskId1); + } + options1 = options1 != null ? options1 : new Bundle(); + wct.startTask(taskId1, options1); + mSplitTransitions.startFullscreenTransition(wct, remoteTransition); + return; + } + prepareEvictChildTasksIfSplitActive(wct); setSideStagePosition(splitPosition, wct); options1 = options1 != null ? options1 : new Bundle(); @@ -627,6 +640,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @SplitPosition int splitPosition, float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); + if (taskId == INVALID_TASK_ID) { + options1 = options1 != null ? options1 : new Bundle(); + wct.sendPendingIntent(pendingIntent, fillInIntent, options1); + mSplitTransitions.startFullscreenTransition(wct, remoteTransition); + return; + } + prepareEvictChildTasksIfSplitActive(wct); setSideStagePosition(splitPosition, wct); options1 = options1 != null ? options1 : new Bundle(); @@ -641,6 +661,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); + if (taskId == INVALID_TASK_ID) { + options1 = options1 != null ? options1 : new Bundle(); + wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1); + mSplitTransitions.startFullscreenTransition(wct, remoteTransition); + return; + } + prepareEvictChildTasksIfSplitActive(wct); setSideStagePosition(splitPosition, wct); options1 = options1 != null ? options1 : new Bundle(); @@ -689,6 +716,17 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @SplitPosition int splitPosition, float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); + if (pendingIntent2 == null) { + options1 = options1 != null ? options1 : new Bundle(); + if (shortcutInfo1 != null) { + wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1); + } else { + wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1); + } + mSplitTransitions.startFullscreenTransition(wct, remoteTransition); + return; + } + if (!mMainStage.isActive()) { // Build a request WCT that will launch both apps such that task 0 is on the main stage // while task 1 is on the side stage. @@ -730,6 +768,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (options1 == null) options1 = new Bundle(); if (taskId2 == INVALID_TASK_ID) { // Launching a solo task. + // Exit split first if this task under split roots. + if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) { + exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT); + } ActivityOptions activityOptions = ActivityOptions.fromBundle(options1); activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter)); options1 = activityOptions.toBundle(); @@ -931,10 +973,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSyncQueue.queue(wrapAsSplitRemoteAnimation(adapter), WindowManager.TRANSIT_OPEN, wct); } - mSyncQueue.runInSync(t -> { - setDividerVisibility(true, t); - }); - setEnterInstanceId(instanceId); } @@ -973,6 +1011,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Override public void onAnimationCancelled(boolean isKeyguardOccluded) { onRemoteAnimationFinishedOrCancelled(evictWct); + setDividerVisibility(true, null); try { adapter.getRunner().onAnimationCancelled(isKeyguardOccluded); } catch (RemoteException e) { @@ -1013,6 +1052,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, t.setPosition(apps[i].leash, 0, 0); } } + setDividerVisibility(true, t); t.apply(); IRemoteAnimationFinishedCallback wrapCallback = @@ -1503,6 +1543,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, void finishEnterSplitScreen(SurfaceControl.Transaction t) { mSplitLayout.init(); setDividerVisibility(true, t); + // Ensure divider surface are re-parented back into the hierarchy at the end of the + // transition. See Transition#buildFinishTransaction for more detail. + t.reparent(mSplitLayout.getDividerLeash(), mRootTaskLeash); + updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); t.show(mRootTaskLeash); setSplitsVisible(true); @@ -1812,6 +1856,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, setDividerVisibility(mainStageVisible, null); } + // Set divider visibility flag and try to apply it, the param transaction is used to apply. + // See applyDividerVisibility for more detail. private void setDividerVisibility(boolean visible, @Nullable SurfaceControl.Transaction t) { if (visible == mDividerVisible) { return; @@ -1838,14 +1884,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return; } - if (t != null) { - applyDividerVisibility(t); - } else { - mSyncQueue.runInSync(transaction -> applyDividerVisibility(transaction)); - } + applyDividerVisibility(t); } - private void applyDividerVisibility(SurfaceControl.Transaction t) { + // Apply divider visibility by current visibility flag. If param transaction is non-null, it + // will apply by that transaction, if it is null and visible, it will run a fade-in animation, + // otherwise hide immediately. + private void applyDividerVisibility(@Nullable SurfaceControl.Transaction t) { final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash(); if (dividerLeash == null) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, @@ -1862,7 +1907,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mDividerFadeInAnimator.cancel(); } - if (mDividerVisible) { + mSplitLayout.getRefDividerBounds(mTempRect1); + if (t != null) { + t.setVisibility(dividerLeash, mDividerVisible); + t.setLayer(dividerLeash, Integer.MAX_VALUE); + t.setPosition(dividerLeash, mTempRect1.left, mTempRect1.top); + } else if (mDividerVisible) { final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); mDividerFadeInAnimator = ValueAnimator.ofFloat(0f, 1f); mDividerFadeInAnimator.addUpdateListener(animation -> { @@ -1902,7 +1952,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mDividerFadeInAnimator.start(); } else { - t.hide(dividerLeash); + final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); + transaction.hide(dividerLeash); + transaction.apply(); + mTransactionPool.release(transaction); } } @@ -2361,9 +2414,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) { - // TODO(shell-transitions): Implement a fallback behavior for now. - throw new IllegalStateException("Somehow removed the last task in a stage" - + " outside of a proper transition"); + Log.e(TAG, "Somehow removed the last task in a stage outside of a proper " + + "transition."); + final WindowContainerTransaction wct = new WindowContainerTransaction(); + final int dismissTop = mMainStage.getChildCount() == 0 + ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; + prepareExitSplitScreen(dismissTop, wct); + mSplitTransitions.startDismissTransition(wct, this, dismissTop, + EXIT_REASON_UNKNOWN); // This can happen in some pathological cases. For example: // 1. main has 2 tasks [Task A (Single-task), Task B], side has one task [Task C] // 2. Task B closes itself and starts Task A in LAUNCH_ADJACENT at the same time @@ -2486,7 +2544,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } finishEnterSplitScreen(finishT); - addDividerBarToTransition(info, finishT, true /* show */); + addDividerBarToTransition(info, true /* show */); return true; } @@ -2629,7 +2687,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (dismissTransition.mDismissTop == STAGE_TYPE_UNDEFINED) { // TODO: Have a proper remote for this. Until then, though, reset state and use the // normal animation stuff (which falls back to the normal launcher remote). - t.hide(mSplitLayout.getDividerLeash()); + setDividerVisibility(false, t); mSplitLayout.release(t); mSplitTransitions.mPendingDismiss = null; return false; @@ -2647,7 +2705,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, }); } - addDividerBarToTransition(info, finishT, false /* show */); + addDividerBarToTransition(info, false /* show */); return true; } @@ -2688,11 +2746,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, logExit(EXIT_REASON_UNKNOWN); } - private void addDividerBarToTransition(@NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction finishT, boolean show) { + private void addDividerBarToTransition(@NonNull TransitionInfo info, boolean show) { final SurfaceControl leash = mSplitLayout.getDividerLeash(); final TransitionInfo.Change barChange = new TransitionInfo.Change(null /* token */, leash); mSplitLayout.getRefDividerBounds(mTempRect1); + barChange.setParent(mRootTaskInfo.token); barChange.setStartAbsBounds(mTempRect1); barChange.setEndAbsBounds(mTempRect1); barChange.setMode(show ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK); @@ -2700,15 +2758,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Technically this should be order-0, but this is running after layer assignment // and it's a special case, so just add to end. info.addChange(barChange); - - if (show) { - finishT.setLayer(leash, Integer.MAX_VALUE); - finishT.setPosition(leash, mTempRect1.left, mTempRect1.top); - finishT.show(leash); - // Ensure divider surface are re-parented back into the hierarchy at the end of the - // transition. See Transition#buildFinishTransaction for more detail. - finishT.reparent(leash, mRootTaskLeash); - } } RemoteAnimationTarget getDividerBarLegacyTarget() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index fa4de16b37f1..bdb7d44bad32 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -143,6 +143,10 @@ public class Transitions implements RemoteCallable<Transitions> { /** Transition type to fullscreen from desktop mode. */ public static final int TRANSIT_EXIT_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 12; + /** Transition type to animate back to fullscreen when drag to freeform is cancelled. */ + public static final int TRANSIT_CANCEL_ENTERING_DESKTOP_MODE = + WindowManager.TRANSIT_FIRST_CUSTOM + 13; + private final WindowOrganizer mOrganizer; private final Context mContext; private final ShellExecutor mMainExecutor; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 060dc4e05b46..dfde7e6feff5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -110,19 +110,11 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL final SurfaceControl oldDecorationSurface = mDecorationContainerSurface; final WindowContainerTransaction wct = new WindowContainerTransaction(); - final int outsetLeftId = R.dimen.freeform_resize_handle; - final int outsetTopId = R.dimen.freeform_resize_handle; - final int outsetRightId = R.dimen.freeform_resize_handle; - final int outsetBottomId = R.dimen.freeform_resize_handle; - mRelayoutParams.reset(); mRelayoutParams.mRunningTaskInfo = taskInfo; mRelayoutParams.mLayoutResId = R.layout.caption_window_decor; mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height; mRelayoutParams.mShadowRadiusId = shadowRadiusID; - if (isDragResizeable) { - mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId); - } relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index f99821747fef..5226eeebcaa2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -34,6 +34,7 @@ import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityTaskManager; import android.content.Context; import android.content.res.Resources; +import android.graphics.Point; import android.graphics.Rect; import android.hardware.input.InputManager; import android.os.Handler; @@ -197,7 +198,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { @Override public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) { - if (mTransitionPausingRelayout.equals(merged)) { + if (merged.equals(mTransitionPausingRelayout)) { mTransitionPausingRelayout = playing; } } @@ -312,8 +313,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } else if (id == R.id.back_button) { mTaskOperations.injectBackKey(); } else if (id == R.id.caption_handle || id == R.id.open_menu_button) { - moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId)); - decoration.createHandleMenu(); + if (!decoration.isHandleMenuActive()) { + moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId)); + decoration.createHandleMenu(); + } else { + decoration.closeHandleMenu(); + } } else if (id == R.id.desktop_button) { mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true)); mDesktopTasksController.ifPresent(c -> c.moveToDesktop(mTaskId)); @@ -553,8 +558,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDragToDesktopAnimationStarted = false; return; } else if (mDragToDesktopAnimationStarted) { - mDesktopTasksController.ifPresent(c -> - c.moveToFullscreen(relevantDecor.mTaskInfo)); + Point startPosition = new Point((int) ev.getX(), (int) ev.getY()); + mDesktopTasksController.ifPresent( + c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo, + startPosition)); mDragToDesktopAnimationStarted = false; return; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index efc90b5e63e1..67e99d73b811 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -42,6 +42,7 @@ import android.widget.Button; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; +import android.window.SurfaceSyncGroup; import android.window.WindowContainerTransaction; import com.android.launcher3.icons.IconProvider; @@ -208,11 +209,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin final SurfaceControl oldDecorationSurface = mDecorationContainerSurface; final WindowContainerTransaction wct = new WindowContainerTransaction(); - final int outsetLeftId = R.dimen.freeform_resize_handle; - final int outsetTopId = R.dimen.freeform_resize_handle; - final int outsetRightId = R.dimen.freeform_resize_handle; - final int outsetBottomId = R.dimen.freeform_resize_handle; - final int windowDecorLayoutId = getDesktopModeWindowDecorLayoutId( taskInfo.getWindowingMode()); mRelayoutParams.reset(); @@ -220,9 +216,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mRelayoutParams.mLayoutResId = windowDecorLayoutId; mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height; mRelayoutParams.mShadowRadiusId = shadowRadiusID; - if (isDragResizeable) { - mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId); - } relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo @@ -319,51 +312,50 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin * Create and display handle menu window */ void createHandleMenu() { + final SurfaceSyncGroup ssg = new SurfaceSyncGroup(TAG); final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); updateHandleMenuPillPositions(); - createAppInfoPill(t); + createAppInfoPill(t, ssg); // Only show windowing buttons in proto2. Proto1 uses a system-level mode only. final boolean shouldShowWindowingPill = DesktopModeStatus.isProto2Enabled(); if (shouldShowWindowingPill) { - createWindowingPill(t); + createWindowingPill(t, ssg); } - createMoreActionsPill(t); + createMoreActionsPill(t, ssg); - mSyncQueue.runInSync(transaction -> { - transaction.merge(t); - t.close(); - }); + ssg.addTransaction(t); + ssg.markSyncReady(); setupHandleMenu(shouldShowWindowingPill); } - private void createAppInfoPill(SurfaceControl.Transaction t) { + private void createAppInfoPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) { final int x = (int) mHandleMenuAppInfoPillPosition.x; final int y = (int) mHandleMenuAppInfoPillPosition.y; mHandleMenuAppInfoPill = addWindow( R.layout.desktop_mode_window_decor_handle_menu_app_info_pill, "Menu's app info pill", - t, x, y, mMenuWidth, mAppInfoPillHeight, mShadowRadius, mCornerRadius); + t, ssg, x, y, mMenuWidth, mAppInfoPillHeight, mShadowRadius, mCornerRadius); } - private void createWindowingPill(SurfaceControl.Transaction t) { + private void createWindowingPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) { final int x = (int) mHandleMenuWindowingPillPosition.x; final int y = (int) mHandleMenuWindowingPillPosition.y; mHandleMenuWindowingPill = addWindow( R.layout.desktop_mode_window_decor_handle_menu_windowing_pill, "Menu's windowing pill", - t, x, y, mMenuWidth, mWindowingPillHeight, mShadowRadius, mCornerRadius); + t, ssg, x, y, mMenuWidth, mWindowingPillHeight, mShadowRadius, mCornerRadius); } - private void createMoreActionsPill(SurfaceControl.Transaction t) { + private void createMoreActionsPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) { final int x = (int) mHandleMenuMoreActionsPillPosition.x; final int y = (int) mHandleMenuMoreActionsPillPosition.y; mHandleMenuMoreActionsPill = addWindow( R.layout.desktop_mode_window_decor_handle_menu_more_actions_pill, "Menu's more actions pill", - t, x, y, mMenuWidth, mMoreActionsPillHeight, mShadowRadius, mCornerRadius); + t, ssg, x, y, mMenuWidth, mMoreActionsPillHeight, mShadowRadius, mCornerRadius); } private void setupHandleMenu(boolean windowingPillShown) { @@ -424,13 +416,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_controls_window_decor) { // Align the handle menu to the left of the caption. - menuX = mRelayoutParams.mCaptionX - mResult.mDecorContainerOffsetX + mMarginMenuStart; - menuY = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY + mMarginMenuTop; + menuX = mRelayoutParams.mCaptionX + mMarginMenuStart; + menuY = mRelayoutParams.mCaptionY + mMarginMenuTop; } else { // Position the handle menu at the center of the caption. - menuX = mRelayoutParams.mCaptionX + (captionWidth / 2) - (mMenuWidth / 2) - - mResult.mDecorContainerOffsetX; - menuY = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY + mMarginMenuStart; + menuX = mRelayoutParams.mCaptionX + (captionWidth / 2) - (mMenuWidth / 2); + menuY = mRelayoutParams.mCaptionY + mMarginMenuStart; } // App Info pill setup. @@ -487,26 +478,30 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin if (mHandleMenuAppInfoPill.mWindowViewHost.getView().getWidth() == 0) return; PointF inputPoint = offsetCaptionLocation(ev); + + // If this is called before open_menu_button's onClick, we don't want to close + // the menu since it will just reopen in onClick. + final boolean pointInOpenMenuButton = pointInView( + mResult.mRootView.findViewById(R.id.open_menu_button), + inputPoint.x, + inputPoint.y); + final boolean pointInAppInfoPill = pointInView( mHandleMenuAppInfoPill.mWindowViewHost.getView(), - inputPoint.x - mHandleMenuAppInfoPillPosition.x - mResult.mDecorContainerOffsetX, - inputPoint.y - mHandleMenuAppInfoPillPosition.y - - mResult.mDecorContainerOffsetY); + inputPoint.x - mHandleMenuAppInfoPillPosition.x, + inputPoint.y - mHandleMenuAppInfoPillPosition.y); boolean pointInWindowingPill = false; if (mHandleMenuWindowingPill != null) { pointInWindowingPill = pointInView(mHandleMenuWindowingPill.mWindowViewHost.getView(), - inputPoint.x - mHandleMenuWindowingPillPosition.x - - mResult.mDecorContainerOffsetX, - inputPoint.y - mHandleMenuWindowingPillPosition.y - - mResult.mDecorContainerOffsetY); + inputPoint.x - mHandleMenuWindowingPillPosition.x, + inputPoint.y - mHandleMenuWindowingPillPosition.y); } final boolean pointInMoreActionsPill = pointInView( mHandleMenuMoreActionsPill.mWindowViewHost.getView(), - inputPoint.x - mHandleMenuMoreActionsPillPosition.x - - mResult.mDecorContainerOffsetX, - inputPoint.y - mHandleMenuMoreActionsPillPosition.y - - mResult.mDecorContainerOffsetY); - if (!pointInAppInfoPill && !pointInWindowingPill && !pointInMoreActionsPill) { + inputPoint.x - mHandleMenuMoreActionsPillPosition.x, + inputPoint.y - mHandleMenuMoreActionsPillPosition.y); + if (!pointInAppInfoPill && !pointInWindowingPill + && !pointInMoreActionsPill && !pointInOpenMenuButton) { closeHandleMenu(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java index 8cb575cc96e3..d5437c72acac 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -64,8 +64,8 @@ class DragResizeInputListener implements AutoCloseable { private final TaskResizeInputEventReceiver mInputEventReceiver; private final DragPositioningCallback mCallback; - private int mWidth; - private int mHeight; + private int mTaskWidth; + private int mTaskHeight; private int mResizeHandleThickness; private int mCornerSize; @@ -128,78 +128,84 @@ class DragResizeInputListener implements AutoCloseable { * This is also used to update the touch regions of this handler every event dispatched here is * a potential resize request. * - * @param width The width of the drag resize handler in pixels, including resize handle - * thickness. That is task width + 2 * resize handle thickness. - * @param height The height of the drag resize handler in pixels, including resize handle - * thickness. That is task height + 2 * resize handle thickness. + * @param taskWidth The width of the task. + * @param taskHeight The height of the task. * @param resizeHandleThickness The thickness of the resize handle in pixels. * @param cornerSize The size of the resize handle centered in each corner. * @param touchSlop The distance in pixels user has to drag with touch for it to register as * a resize action. */ - void setGeometry(int width, int height, int resizeHandleThickness, int cornerSize, + void setGeometry(int taskWidth, int taskHeight, int resizeHandleThickness, int cornerSize, int touchSlop) { - if (mWidth == width && mHeight == height + if (mTaskWidth == taskWidth && mTaskHeight == taskHeight && mResizeHandleThickness == resizeHandleThickness && mCornerSize == cornerSize) { return; } - mWidth = width; - mHeight = height; + mTaskWidth = taskWidth; + mTaskHeight = taskHeight; mResizeHandleThickness = resizeHandleThickness; mCornerSize = cornerSize; mDragDetector.setTouchSlop(touchSlop); Region touchRegion = new Region(); - final Rect topInputBounds = new Rect(0, 0, mWidth, mResizeHandleThickness); + final Rect topInputBounds = new Rect( + -mResizeHandleThickness, + -mResizeHandleThickness, + mTaskWidth + mResizeHandleThickness, + 0); touchRegion.union(topInputBounds); - final Rect leftInputBounds = new Rect(0, mResizeHandleThickness, - mResizeHandleThickness, mHeight - mResizeHandleThickness); + final Rect leftInputBounds = new Rect( + -mResizeHandleThickness, + 0, + 0, + mTaskHeight); touchRegion.union(leftInputBounds); final Rect rightInputBounds = new Rect( - mWidth - mResizeHandleThickness, mResizeHandleThickness, - mWidth, mHeight - mResizeHandleThickness); + mTaskWidth, + 0, + mTaskWidth + mResizeHandleThickness, + mTaskHeight); touchRegion.union(rightInputBounds); - final Rect bottomInputBounds = new Rect(0, mHeight - mResizeHandleThickness, - mWidth, mHeight); + final Rect bottomInputBounds = new Rect( + -mResizeHandleThickness, + mTaskHeight, + mTaskWidth + mResizeHandleThickness, + mTaskHeight + mResizeHandleThickness); touchRegion.union(bottomInputBounds); // Set up touch areas in each corner. int cornerRadius = mCornerSize / 2; mLeftTopCornerBounds = new Rect( - mResizeHandleThickness - cornerRadius, - mResizeHandleThickness - cornerRadius, - mResizeHandleThickness + cornerRadius, - mResizeHandleThickness + cornerRadius - ); + -cornerRadius, + -cornerRadius, + cornerRadius, + cornerRadius); touchRegion.union(mLeftTopCornerBounds); mRightTopCornerBounds = new Rect( - mWidth - mResizeHandleThickness - cornerRadius, - mResizeHandleThickness - cornerRadius, - mWidth - mResizeHandleThickness + cornerRadius, - mResizeHandleThickness + cornerRadius - ); + mTaskWidth - cornerRadius, + -cornerRadius, + mTaskWidth + cornerRadius, + cornerRadius); touchRegion.union(mRightTopCornerBounds); mLeftBottomCornerBounds = new Rect( - mResizeHandleThickness - cornerRadius, - mHeight - mResizeHandleThickness - cornerRadius, - mResizeHandleThickness + cornerRadius, - mHeight - mResizeHandleThickness + cornerRadius - ); + -cornerRadius, + mTaskHeight - cornerRadius, + cornerRadius, + mTaskHeight + cornerRadius); touchRegion.union(mLeftBottomCornerBounds); mRightBottomCornerBounds = new Rect( - mWidth - mResizeHandleThickness - cornerRadius, - mHeight - mResizeHandleThickness - cornerRadius, - mWidth - mResizeHandleThickness + cornerRadius, - mHeight - mResizeHandleThickness + cornerRadius - ); + mTaskWidth - cornerRadius, + mTaskHeight - cornerRadius, + mTaskWidth + cornerRadius, + mTaskHeight + cornerRadius); touchRegion.union(mRightBottomCornerBounds); try { @@ -358,16 +364,16 @@ class DragResizeInputListener implements AutoCloseable { @TaskPositioner.CtrlType private int calculateResizeHandlesCtrlType(float x, float y) { int ctrlType = 0; - if (x < mResizeHandleThickness) { + if (x < 0) { ctrlType |= TaskPositioner.CTRL_TYPE_LEFT; } - if (x > mWidth - mResizeHandleThickness) { + if (x > mTaskWidth) { ctrlType |= TaskPositioner.CTRL_TYPE_RIGHT; } - if (y < mResizeHandleThickness) { + if (y < 0) { ctrlType |= TaskPositioner.CTRL_TYPE_TOP; } - if (y > mHeight - mResizeHandleThickness) { + if (y > mTaskHeight) { ctrlType |= TaskPositioner.CTRL_TYPE_BOTTOM; } return ctrlType; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index 4ebd09fdecee..19a31822aabb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -34,6 +34,7 @@ import android.view.ViewRootImpl; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowlessWindowManager; +import android.window.SurfaceSyncGroup; import android.window.TaskConstants; import android.window.WindowContainerTransaction; @@ -98,7 +99,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> private final Binder mOwner = new Binder(); private final Rect mCaptionInsetsRect = new Rect(); - private final Rect mTaskSurfaceCrop = new Rect(); private final float[] mTmpColor = new float[3]; WindowDecoration( @@ -193,13 +193,13 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mDecorWindowContext = mContext.createConfigurationContext(taskConfig); if (params.mLayoutResId != 0) { outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext) - .inflate(params.mLayoutResId, null); + .inflate(params.mLayoutResId, null); } } if (outResult.mRootView == null) { outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext) - .inflate(params.mLayoutResId , null); + .inflate(params.mLayoutResId, null); } // DecorationContainerSurface @@ -218,21 +218,14 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> final Rect taskBounds = taskConfig.windowConfiguration.getBounds(); final Resources resources = mDecorWindowContext.getResources(); - outResult.mDecorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId); - outResult.mDecorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId); - outResult.mWidth = taskBounds.width() - + loadDimensionPixelSize(resources, params.mOutsetRightId) - - outResult.mDecorContainerOffsetX; - outResult.mHeight = taskBounds.height() - + loadDimensionPixelSize(resources, params.mOutsetBottomId) - - outResult.mDecorContainerOffsetY; - startT.setPosition( - mDecorationContainerSurface, - outResult.mDecorContainerOffsetX, outResult.mDecorContainerOffsetY) - .setWindowCrop(mDecorationContainerSurface, - outResult.mWidth, outResult.mHeight) + outResult.mWidth = taskBounds.width(); + outResult.mHeight = taskBounds.height(); + startT.setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight) .show(mDecorationContainerSurface); + // TODO(b/270202228): This surface can be removed. Instead, use + // |mDecorationContainerSurface| to set the background now that it no longer has outsets + // and its crop is set to the task bounds. // TaskBackgroundSurface if (mTaskBackgroundSurface == null) { final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get(); @@ -250,8 +243,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f; mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f; mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f; - startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), - taskBounds.height()) + startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), taskBounds.height()) .setShadowRadius(mTaskBackgroundSurface, shadowRadius) .setColor(mTaskBackgroundSurface, mTmpColor) .show(mTaskBackgroundSurface); @@ -269,11 +261,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId); final int captionWidth = taskBounds.width(); - startT.setPosition( - mCaptionContainerSurface, - -outResult.mDecorContainerOffsetX + params.mCaptionX, - -outResult.mDecorContainerOffsetY + params.mCaptionY) - .setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight) + startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight) .show(mCaptionContainerSurface); if (mCaptionWindowManager == null) { @@ -314,14 +302,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> // Task surface itself Point taskPosition = mTaskInfo.positionInParent; - mTaskSurfaceCrop.set( - outResult.mDecorContainerOffsetX, - outResult.mDecorContainerOffsetY, - outResult.mWidth + outResult.mDecorContainerOffsetX, - outResult.mHeight + outResult.mDecorContainerOffsetY); startT.show(mTaskSurface); finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y) - .setCrop(mTaskSurface, mTaskSurfaceCrop); + .setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight); } /** @@ -400,18 +383,20 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> /** * Create a window associated with this WindowDecoration. * Note that subclass must dispose of this when the task is hidden/closed. - * @param layoutId layout to make the window from - * @param t the transaction to apply - * @param xPos x position of new window - * @param yPos y position of new window - * @param width width of new window - * @param height height of new window + * + * @param layoutId layout to make the window from + * @param t the transaction to apply + * @param xPos x position of new window + * @param yPos y position of new window + * @param width width of new window + * @param height height of new window * @param shadowRadius radius of the shadow of the new window * @param cornerRadius radius of the corners of the new window * @return the {@link AdditionalWindow} that was added. */ AdditionalWindow addWindow(int layoutId, String namePrefix, SurfaceControl.Transaction t, - int xPos, int yPos, int width, int height, int shadowRadius, int cornerRadius) { + SurfaceSyncGroup ssg, int xPos, int yPos, int width, int height, int shadowRadius, + int cornerRadius) { final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get(); SurfaceControl windowSurfaceControl = builder .setName(namePrefix + " of Task=" + mTaskInfo.taskId) @@ -435,49 +420,27 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> windowSurfaceControl, null /* hostInputToken */); SurfaceControlViewHost viewHost = mSurfaceControlViewHostFactory .create(mDecorWindowContext, mDisplay, windowManager); - viewHost.setView(v, lp); + ssg.add(viewHost.getSurfacePackage(), () -> viewHost.setView(v, lp)); return new AdditionalWindow(windowSurfaceControl, viewHost, mSurfaceControlTransactionSupplier); } - static class RelayoutParams{ + static class RelayoutParams { RunningTaskInfo mRunningTaskInfo; int mLayoutResId; int mCaptionHeightId; int mCaptionWidthId; int mShadowRadiusId; - int mOutsetTopId; - int mOutsetBottomId; - int mOutsetLeftId; - int mOutsetRightId; - int mCaptionX; int mCaptionY; - void setOutsets(int leftId, int topId, int rightId, int bottomId) { - mOutsetLeftId = leftId; - mOutsetTopId = topId; - mOutsetRightId = rightId; - mOutsetBottomId = bottomId; - } - - void setCaptionPosition(int left, int top) { - mCaptionX = left; - mCaptionY = top; - } - void reset() { mLayoutResId = Resources.ID_NULL; mCaptionHeightId = Resources.ID_NULL; mCaptionWidthId = Resources.ID_NULL; mShadowRadiusId = Resources.ID_NULL; - mOutsetTopId = Resources.ID_NULL; - mOutsetBottomId = Resources.ID_NULL; - mOutsetLeftId = Resources.ID_NULL; - mOutsetRightId = Resources.ID_NULL; - mCaptionX = 0; mCaptionY = 0; } @@ -487,14 +450,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> int mWidth; int mHeight; T mRootView; - int mDecorContainerOffsetX; - int mDecorContainerOffsetY; void reset() { mWidth = 0; mHeight = 0; - mDecorContainerOffsetX = 0; - mDecorContainerOffsetY = 0; mRootView = null; } } diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml index 67ca9a1a17f7..b6d92814ad43 100644 --- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml +++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml @@ -29,6 +29,7 @@ <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" /> <option name="teardown-command" value="settings delete system show_touches" /> <option name="teardown-command" value="settings delete system pointer_location" /> + <option name="teardown-command" value="cmd overlay enable com.android.internal.systemui.navbar.gestural" /> </target_preparer> <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true"/> diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt index e986ee127708..c416ad011c4e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt @@ -137,7 +137,7 @@ fun FlickerTest.splitAppLayerBoundsBecomesVisible( portraitPosTop: Boolean ) { assertLayers { - this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component)) + this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true) .then() .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component)) .then() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java index cbbb29199d75..b8f615a1855f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java @@ -16,6 +16,7 @@ package com.android.wm.shell.activityembedding; +import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; @@ -82,10 +83,13 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation @Test public void testStartAnimation_containsNonActivityEmbeddingChange() { + final TransitionInfo.Change nonEmbeddedOpen = createChange(0 /* flags */); + final TransitionInfo.Change embeddedOpen = createEmbeddedChange( + EMBEDDED_LEFT_BOUNDS, EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS); + nonEmbeddedOpen.setMode(TRANSIT_OPEN); final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0) - .addChange(createEmbeddedChange( - EMBEDDED_LEFT_BOUNDS, EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS)) - .addChange(createChange(0 /* flags */)) + .addChange(embeddedOpen) + .addChange(nonEmbeddedOpen) .build(); // No-op because it contains non-embedded change. @@ -95,6 +99,22 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation verifyNoMoreInteractions(mStartTransaction); verifyNoMoreInteractions(mFinishTransaction); verifyNoMoreInteractions(mFinishCallback); + + final TransitionInfo.Change nonEmbeddedClose = createChange(0 /* flags */); + nonEmbeddedClose.setMode(TRANSIT_CLOSE); + nonEmbeddedClose.setEndAbsBounds(TASK_BOUNDS); + final TransitionInfo.Change embeddedOpen2 = createEmbeddedChange( + EMBEDDED_RIGHT_BOUNDS, EMBEDDED_RIGHT_BOUNDS, TASK_BOUNDS); + final TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_OPEN, 0) + .addChange(embeddedOpen) + .addChange(embeddedOpen2) + .addChange(nonEmbeddedClose) + .build(); + // Ok to animate because nonEmbeddedClose is occluded by embeddedOpen and embeddedOpen2. + assertTrue(mController.startAnimation(mTransition, info2, mStartTransaction, + mFinishTransaction, mFinishCallback)); + // The non-embedded change is dropped to avoid affecting embedded animation. + assertFalse(info2.getChanges().contains(nonEmbeddedClose)); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index 806bffebd4cb..d95c7a488ea1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -166,6 +166,9 @@ public class BackAnimationControllerTest extends ShellTestCase { doMotionEvent(MotionEvent.ACTION_DOWN, 0); doMotionEvent(MotionEvent.ACTION_MOVE, 0); mController.setTriggerBack(true); + } + + private void releaseBackGesture() { doMotionEvent(MotionEvent.ACTION_UP, 0); } @@ -201,6 +204,8 @@ public class BackAnimationControllerTest extends ShellTestCase { .setOnBackNavigationDone(new RemoteCallback(result))); triggerBackGesture(); simulateRemoteAnimationStart(type); + mShellExecutor.flushAll(); + releaseBackGesture(); simulateRemoteAnimationFinished(); mShellExecutor.flushAll(); @@ -252,6 +257,7 @@ public class BackAnimationControllerTest extends ShellTestCase { createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, false); triggerBackGesture(); + releaseBackGesture(); verify(mAppCallback, times(1)).onBackInvoked(); @@ -269,6 +275,8 @@ public class BackAnimationControllerTest extends ShellTestCase { triggerBackGesture(); simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); + releaseBackGesture(); + // Check that back invocation is dispatched. verify(mAnimatorCallback).onBackInvoked(); verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any()); @@ -308,6 +316,9 @@ public class BackAnimationControllerTest extends ShellTestCase { triggerBackGesture(); simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); + mShellExecutor.flushAll(); + + releaseBackGesture(); // Simulate transition timeout. mShellExecutor.flushAll(); @@ -369,6 +380,9 @@ public class BackAnimationControllerTest extends ShellTestCase { simulateRemoteAnimationStart(type); mShellExecutor.flushAll(); + releaseBackGesture(); + mShellExecutor.flushAll(); + assertTrue("Navigation Done callback not called for " + BackNavigationInfo.typeToString(type), result.mBackNavigationDone); assertTrue("TriggerBack should have been true", result.mTriggerBack); @@ -395,6 +409,8 @@ public class BackAnimationControllerTest extends ShellTestCase { .setOnBackNavigationDone(new RemoteCallback(result))); triggerBackGesture(); mShellExecutor.flushAll(); + releaseBackGesture(); + mShellExecutor.flushAll(); assertTrue("Navigation Done callback not called for " + BackNavigationInfo.typeToString(type), result.mBackNavigationDone); @@ -458,9 +474,12 @@ public class BackAnimationControllerTest extends ShellTestCase { private void doMotionEvent(int actionDown, int coordinate) { mController.onMotionEvent( - coordinate, coordinate, - actionDown, - BackEvent.EDGE_LEFT); + /* touchX */ coordinate, + /* touchY */ coordinate, + /* velocityX = */ 0, + /* velocityY = */ 0, + /* keyAction */ actionDown, + /* swipeEdge */ BackEvent.EDGE_LEFT); } private void simulateRemoteAnimationStart(int type) throws RemoteException { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java index 3608474bd90e..874ef80c29f0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java @@ -16,8 +16,6 @@ package com.android.wm.shell.back; -import static android.window.BackEvent.EDGE_LEFT; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -48,12 +46,21 @@ public class BackProgressAnimatorTest { private CountDownLatch mTargetProgressCalled = new CountDownLatch(1); private Handler mMainThreadHandler; + private BackMotionEvent backMotionEventFrom(float touchX, float progress) { + return new BackMotionEvent( + /* touchX = */ touchX, + /* touchY = */ 0, + /* progress = */ progress, + /* velocityX = */ 0, + /* velocityY = */ 0, + /* swipeEdge = */ BackEvent.EDGE_LEFT, + /* departingAnimationTarget = */ null); + } + @Before public void setUp() throws Exception { mMainThreadHandler = new Handler(Looper.getMainLooper()); - final BackMotionEvent backEvent = new BackMotionEvent( - 0, 0, - 0, EDGE_LEFT, null); + final BackMotionEvent backEvent = backMotionEventFrom(0, 0); mMainThreadHandler.post( () -> { mProgressAnimator = new BackProgressAnimator(); @@ -63,9 +70,7 @@ public class BackProgressAnimatorTest { @Test public void testBackProgressed() throws InterruptedException { - final BackMotionEvent backEvent = new BackMotionEvent( - 100, 0, - mTargetProgress, EDGE_LEFT, null); + final BackMotionEvent backEvent = backMotionEventFrom(100, mTargetProgress); mMainThreadHandler.post( () -> mProgressAnimator.onBackProgressed(backEvent)); @@ -78,9 +83,7 @@ public class BackProgressAnimatorTest { @Test public void testBackCancelled() throws InterruptedException { // Give the animator some progress. - final BackMotionEvent backEvent = new BackMotionEvent( - 100, 0, - mTargetProgress, EDGE_LEFT, null); + final BackMotionEvent backEvent = backMotionEventFrom(100, mTargetProgress); mMainThreadHandler.post( () -> mProgressAnimator.onBackProgressed(backEvent)); mTargetProgressCalled.await(1, TimeUnit.SECONDS); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java index ba9c159bad28..d62e6601723a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java @@ -47,43 +47,45 @@ public class TouchTrackerTest { public void generatesProgress_leftEdge() { mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT); float touchX = 10; + float velocityX = 0; + float velocityY = 0; // Pre-commit - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f); // Post-commit touchX += 100; mTouchTracker.setTriggerBack(true); - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f); // Cancel touchX -= 10; mTouchTracker.setTriggerBack(false); - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), 0, 0f); // Cancel more touchX -= 10; - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), 0, 0f); // Restart touchX += 10; - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), 0, 0f); // Restarted, but pre-commit float restartX = touchX; touchX += 10; - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), (touchX - restartX) / FAKE_THRESHOLD, 0f); // Restarted, post-commit touchX += 10; mTouchTracker.setTriggerBack(true); - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f); } @@ -91,43 +93,45 @@ public class TouchTrackerTest { public void generatesProgress_rightEdge() { mTouchTracker.setGestureStartLocation(INITIAL_X_RIGHT_EDGE, 0, BackEvent.EDGE_RIGHT); float touchX = INITIAL_X_RIGHT_EDGE - 10; // Fake right edge + float velocityX = 0f; + float velocityY = 0f; // Pre-commit - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f); // Post-commit touchX -= 100; mTouchTracker.setTriggerBack(true); - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f); // Cancel touchX += 10; mTouchTracker.setTriggerBack(false); - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), 0, 0f); // Cancel more touchX += 10; - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), 0, 0f); // Restart touchX -= 10; - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), 0, 0f); // Restarted, but pre-commit float restartX = touchX; touchX -= 10; - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), (restartX - touchX) / FAKE_THRESHOLD, 0f); // Restarted, post-commit touchX -= 10; mTouchTracker.setTriggerBack(true); - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java index 15bb10ed4f2b..842c699fa42d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java @@ -262,7 +262,8 @@ public class PipTaskOrganizerTest extends ShellTestCase { DisplayLayout layout = new DisplayLayout(info, mContext.getResources(), true, true); mPipDisplayLayoutState.setDisplayLayout(layout); - mPipTaskOrganizer.setOneShotAnimationType(PipAnimationController.ANIM_TYPE_ALPHA); + doReturn(PipAnimationController.ANIM_TYPE_ALPHA).when(mMockPipAnimationController) + .takeOneShotEnterAnimationType(); mPipTaskOrganizer.setSurfaceControlTransactionFactory( MockSurfaceControlHelper::createMockSurfaceControlTransaction); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java index 390c830069eb..425bbf056b85 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java @@ -18,7 +18,6 @@ package com.android.wm.shell.pip.phone; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; @@ -76,7 +75,7 @@ public class PipSizeSpecHandlerTest extends ShellTestCase { @Mock private Resources mResources; private PipDisplayLayoutState mPipDisplayLayoutState; - private PipSizeSpecHandler mPipSizeSpecHandler; + private TestPipSizeSpecHandler mPipSizeSpecHandler; /** * Sets up static Mockito session for SystemProperties and mocks necessary static methods. @@ -84,8 +83,6 @@ public class PipSizeSpecHandlerTest extends ShellTestCase { private static void setUpStaticSystemPropertiesSession() { sStaticMockitoSession = mockitoSession() .mockStatic(SystemProperties.class).startMocking(); - // make sure the feature flag is on - when(SystemProperties.getBoolean(anyString(), anyBoolean())).thenReturn(true); when(SystemProperties.get(anyString(), anyString())).thenAnswer(invocation -> { String property = invocation.getArgument(0); if (property.equals("com.android.wm.shell.pip.phone.def_percentage")) { @@ -161,7 +158,7 @@ public class PipSizeSpecHandlerTest extends ShellTestCase { mPipDisplayLayoutState.setDisplayLayout(displayLayout); setUpStaticSystemPropertiesSession(); - mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState); + mPipSizeSpecHandler = new TestPipSizeSpecHandler(mContext, mPipDisplayLayoutState); // no overridden min edge size by default mPipSizeSpecHandler.setOverrideMinSize(null); @@ -214,4 +211,16 @@ public class PipSizeSpecHandlerTest extends ShellTestCase { Assert.assertEquals(expectedSize, actualSize); } + + static class TestPipSizeSpecHandler extends PipSizeSpecHandler { + + TestPipSizeSpecHandler(Context context, PipDisplayLayoutState displayLayoutState) { + super(context, displayLayoutState); + } + + @Override + boolean supportsPipSizeLargeScreen() { + return true; + } + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index eda6fdc4dbd4..e6219d1aa792 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -113,6 +113,7 @@ public class StageCoordinatorTests extends ShellTestCase { private SurfaceSession mSurfaceSession = new SurfaceSession(); private SurfaceControl mRootLeash; + private SurfaceControl mDividerLeash; private ActivityManager.RunningTaskInfo mRootTask; private StageCoordinator mStageCoordinator; private Transitions mTransitions; @@ -129,12 +130,14 @@ public class StageCoordinatorTests extends ShellTestCase { mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool, mMainExecutor, Optional.empty())); + mDividerLeash = new SurfaceControl.Builder(mSurfaceSession).setName("fakeDivider").build(); when(mSplitLayout.getBounds1()).thenReturn(mBounds1); when(mSplitLayout.getBounds2()).thenReturn(mBounds2); when(mSplitLayout.getRootBounds()).thenReturn(mRootBounds); when(mSplitLayout.isLandscape()).thenReturn(false); when(mSplitLayout.applyTaskChanges(any(), any(), any())).thenReturn(true); + when(mSplitLayout.getDividerLeash()).thenReturn(mDividerLeash); mRootTask = new TestRunningTaskInfoBuilder().build(); mRootLeash = new SurfaceControl.Builder(mSurfaceSession).setName("test").build(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index dfa3c1010eed..38a519af934b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -49,6 +49,7 @@ import android.view.View; import android.view.ViewRootImpl; import android.view.WindowInsets; import android.view.WindowManager.LayoutParams; +import android.window.SurfaceSyncGroup; import android.window.TaskConstants; import android.window.WindowContainerTransaction; @@ -100,6 +101,8 @@ public class WindowDecorationTests extends ShellTestCase { private TestView mMockView; @Mock private WindowContainerTransaction mMockWindowContainerTransaction; + @Mock + private SurfaceSyncGroup mMockSurfaceSyncGroup; private final List<SurfaceControl.Transaction> mMockSurfaceControlTransactions = new ArrayList<>(); @@ -159,14 +162,8 @@ public class WindowDecorationTests extends ShellTestCase { .setVisible(false) .build(); taskInfo.isFocused = false; - // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is - // 64px. + // Density is 2. Shadow radius is 10px. Caption height is 64px. taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; - mRelayoutParams.setOutsets( - R.dimen.test_window_decor_left_outset, - R.dimen.test_window_decor_top_outset, - R.dimen.test_window_decor_right_outset, - R.dimen.test_window_decor_bottom_outset); final SurfaceControl taskSurface = mock(SurfaceControl.class); final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); @@ -213,14 +210,8 @@ public class WindowDecorationTests extends ShellTestCase { .setVisible(true) .build(); taskInfo.isFocused = true; - // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is - // 64px. + // Density is 2. Shadow radius is 10px. Caption height is 64px. taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; - mRelayoutParams.setOutsets( - R.dimen.test_window_decor_left_outset, - R.dimen.test_window_decor_top_outset, - R.dimen.test_window_decor_right_outset, - R.dimen.test_window_decor_bottom_outset); final SurfaceControl taskSurface = mock(SurfaceControl.class); final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); @@ -229,8 +220,7 @@ public class WindowDecorationTests extends ShellTestCase { verify(decorContainerSurfaceBuilder).setParent(taskSurface); verify(decorContainerSurfaceBuilder).setContainerLayer(); verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true); - verify(mMockSurfaceControlStartT).setPosition(decorContainerSurface, -20, -40); - verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 380, 220); + verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 300, 100); verify(taskBackgroundSurfaceBuilder).setParent(taskSurface); verify(taskBackgroundSurfaceBuilder).setEffectLayer(); @@ -244,7 +234,6 @@ public class WindowDecorationTests extends ShellTestCase { verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface); verify(captionContainerSurfaceBuilder).setContainerLayer(); - verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40); verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64); verify(mMockSurfaceControlStartT).show(captionContainerSurface); @@ -268,12 +257,12 @@ public class WindowDecorationTests extends ShellTestCase { verify(mMockSurfaceControlFinishT) .setPosition(taskSurface, TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y); verify(mMockSurfaceControlFinishT) - .setCrop(taskSurface, new Rect(-20, -40, 360, 180)); + .setWindowCrop(taskSurface, 300, 100); verify(mMockSurfaceControlStartT) .show(taskSurface); - assertEquals(380, mRelayoutResult.mWidth); - assertEquals(220, mRelayoutResult.mHeight); + assertEquals(300, mRelayoutResult.mWidth); + assertEquals(100, mRelayoutResult.mHeight); } @Test @@ -309,14 +298,8 @@ public class WindowDecorationTests extends ShellTestCase { .setVisible(true) .build(); taskInfo.isFocused = true; - // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is - // 64px. + // Density is 2. Shadow radius is 10px. Caption height is 64px. taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; - mRelayoutParams.setOutsets( - R.dimen.test_window_decor_left_outset, - R.dimen.test_window_decor_top_outset, - R.dimen.test_window_decor_right_outset, - R.dimen.test_window_decor_bottom_outset); final SurfaceControl taskSurface = mock(SurfaceControl.class); final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); @@ -419,11 +402,6 @@ public class WindowDecorationTests extends ShellTestCase { .build(); taskInfo.isFocused = true; taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; - mRelayoutParams.setOutsets( - R.dimen.test_window_decor_left_outset, - R.dimen.test_window_decor_top_outset, - R.dimen.test_window_decor_right_outset, - R.dimen.test_window_decor_bottom_outset); final SurfaceControl taskSurface = mock(SurfaceControl.class); final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); windowDecor.relayout(taskInfo); @@ -438,7 +416,7 @@ public class WindowDecorationTests extends ShellTestCase { verify(additionalWindowSurfaceBuilder).setContainerLayer(); verify(additionalWindowSurfaceBuilder).setParent(decorContainerSurface); verify(additionalWindowSurfaceBuilder).build(); - verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 20, 40); + verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 0, 0); final int width = WindowDecoration.loadDimensionPixelSize( mContext.getResources(), mCaptionMenuWidthId); final int height = WindowDecoration.loadDimensionPixelSize( @@ -496,11 +474,6 @@ public class WindowDecorationTests extends ShellTestCase { .build(); taskInfo.isFocused = true; taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; - mRelayoutParams.setOutsets( - R.dimen.test_window_decor_left_outset, - R.dimen.test_window_decor_top_outset, - R.dimen.test_window_decor_right_outset, - R.dimen.test_window_decor_bottom_outset); final SurfaceControl taskSurface = mock(SurfaceControl.class); final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); @@ -508,7 +481,6 @@ public class WindowDecorationTests extends ShellTestCase { verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface); verify(captionContainerSurfaceBuilder).setContainerLayer(); - verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40); // Width of the captionContainerSurface should match the width of TASK_BOUNDS verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64); verify(mMockSurfaceControlStartT).show(captionContainerSurface); @@ -584,9 +556,7 @@ public class WindowDecorationTests extends ShellTestCase { String name = "Test Window"; WindowDecoration.AdditionalWindow additionalWindow = addWindow(R.layout.desktop_mode_window_decor_handle_menu_app_info_pill, name, - mMockSurfaceControlAddWindowT, - x - mRelayoutResult.mDecorContainerOffsetX, - y - mRelayoutResult.mDecorContainerOffsetY, + mMockSurfaceControlAddWindowT, mMockSurfaceSyncGroup, x, y, width, height, shadowRadius, cornerRadius); return additionalWindow; } diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index 0b58406516e3..924fbd659824 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -51,6 +51,9 @@ #include "include/gpu/GrDirectContext.h" #include "pipeline/skia/AnimatedDrawables.h" #include "pipeline/skia/FunctorDrawable.h" +#ifdef __ANDROID__ +#include "renderthread/CanvasContext.h" +#endif namespace android { namespace uirenderer { @@ -489,7 +492,19 @@ struct DrawPoints final : Op { size_t count; SkPaint paint; void draw(SkCanvas* c, const SkMatrix&) const { - c->drawPoints(mode, count, pod<SkPoint>(this), paint); + if (paint.isAntiAlias()) { + c->drawPoints(mode, count, pod<SkPoint>(this), paint); + } else { + c->save(); +#ifdef __ANDROID__ + auto pixelSnap = renderthread::CanvasContext::getActiveContext()->getPixelSnapMatrix(); + auto transform = c->getLocalToDevice(); + transform.postConcat(pixelSnap); + c->setMatrix(transform); +#endif + c->drawPoints(mode, count, pod<SkPoint>(this), paint); + c->restore(); + } } }; struct DrawVertices final : Op { diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp index d08bc5c583c2..8049dc946c9e 100644 --- a/libs/hwui/hwui/AnimatedImageDrawable.cpp +++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp @@ -29,9 +29,10 @@ namespace android { -AnimatedImageDrawable::AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed) - : mSkAnimatedImage(std::move(animatedImage)), mBytesUsed(bytesUsed) { - mTimeToShowNextSnapshot = ms2ns(mSkAnimatedImage->currentFrameDuration()); +AnimatedImageDrawable::AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed, + SkEncodedImageFormat format) + : mSkAnimatedImage(std::move(animatedImage)), mBytesUsed(bytesUsed), mFormat(format) { + mTimeToShowNextSnapshot = ms2ns(currentFrameDuration()); setStagingBounds(mSkAnimatedImage->getBounds()); } @@ -92,7 +93,7 @@ bool AnimatedImageDrawable::isDirty(nsecs_t* outDelay) { // directly from mSkAnimatedImage. lock.unlock(); std::unique_lock imageLock{mImageLock}; - *outDelay = ms2ns(mSkAnimatedImage->currentFrameDuration()); + *outDelay = ms2ns(currentFrameDuration()); return true; } else { // The next snapshot has not yet been decoded, but we've already passed @@ -109,7 +110,7 @@ AnimatedImageDrawable::Snapshot AnimatedImageDrawable::decodeNextFrame() { Snapshot snap; { std::unique_lock lock{mImageLock}; - snap.mDurationMS = mSkAnimatedImage->decodeNextFrame(); + snap.mDurationMS = adjustFrameDuration(mSkAnimatedImage->decodeNextFrame()); snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot()); } @@ -123,7 +124,7 @@ AnimatedImageDrawable::Snapshot AnimatedImageDrawable::reset() { std::unique_lock lock{mImageLock}; mSkAnimatedImage->reset(); snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot()); - snap.mDurationMS = mSkAnimatedImage->currentFrameDuration(); + snap.mDurationMS = currentFrameDuration(); } return snap; @@ -274,7 +275,7 @@ int AnimatedImageDrawable::drawStaging(SkCanvas* canvas) { { std::unique_lock lock{mImageLock}; mSkAnimatedImage->reset(); - durationMS = mSkAnimatedImage->currentFrameDuration(); + durationMS = currentFrameDuration(); } { std::unique_lock lock{mSwapLock}; @@ -306,7 +307,7 @@ int AnimatedImageDrawable::drawStaging(SkCanvas* canvas) { { std::unique_lock lock{mImageLock}; if (update) { - durationMS = mSkAnimatedImage->decodeNextFrame(); + durationMS = adjustFrameDuration(mSkAnimatedImage->decodeNextFrame()); } canvas->drawDrawable(mSkAnimatedImage.get()); @@ -336,4 +337,20 @@ SkRect AnimatedImageDrawable::onGetBounds() { return SkRectMakeLargest(); } +int AnimatedImageDrawable::adjustFrameDuration(int durationMs) { + if (durationMs == SkAnimatedImage::kFinished) { + return SkAnimatedImage::kFinished; + } + + if (mFormat == SkEncodedImageFormat::kGIF) { + // Match Chrome & Firefox behavior that gifs with a duration <= 10ms is bumped to 100ms + return durationMs <= 10 ? 100 : durationMs; + } + return durationMs; +} + +int AnimatedImageDrawable::currentFrameDuration() { + return adjustFrameDuration(mSkAnimatedImage->currentFrameDuration()); +} + } // namespace android diff --git a/libs/hwui/hwui/AnimatedImageDrawable.h b/libs/hwui/hwui/AnimatedImageDrawable.h index 8ca3c7e125f1..1e965abc82b5 100644 --- a/libs/hwui/hwui/AnimatedImageDrawable.h +++ b/libs/hwui/hwui/AnimatedImageDrawable.h @@ -16,16 +16,16 @@ #pragma once -#include <cutils/compiler.h> -#include <utils/Macros.h> -#include <utils/RefBase.h> -#include <utils/Timers.h> - #include <SkAnimatedImage.h> #include <SkCanvas.h> #include <SkColorFilter.h> #include <SkDrawable.h> +#include <SkEncodedImageFormat.h> #include <SkPicture.h> +#include <cutils/compiler.h> +#include <utils/Macros.h> +#include <utils/RefBase.h> +#include <utils/Timers.h> #include <future> #include <mutex> @@ -48,7 +48,8 @@ class AnimatedImageDrawable : public SkDrawable { public: // bytesUsed includes the approximate sizes of the SkAnimatedImage and the SkPictures in the // Snapshots. - AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed); + AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed, + SkEncodedImageFormat format); /** * This updates the internal time and returns true if the image needs @@ -115,6 +116,7 @@ protected: private: sk_sp<SkAnimatedImage> mSkAnimatedImage; const size_t mBytesUsed; + const SkEncodedImageFormat mFormat; bool mRunning = false; bool mStarting = false; @@ -157,6 +159,9 @@ private: Properties mProperties; std::unique_ptr<OnAnimationEndListener> mEndListener; + + int adjustFrameDuration(int); + int currentFrameDuration(); }; } // namespace android diff --git a/libs/hwui/jni/AnimatedImageDrawable.cpp b/libs/hwui/jni/AnimatedImageDrawable.cpp index 373e893b9a25..a7f5aa83e624 100644 --- a/libs/hwui/jni/AnimatedImageDrawable.cpp +++ b/libs/hwui/jni/AnimatedImageDrawable.cpp @@ -97,7 +97,7 @@ static jlong AnimatedImageDrawable_nCreate(JNIEnv* env, jobject /*clazz*/, bytesUsed += picture->approximateBytesUsed(); } - + SkEncodedImageFormat format = imageDecoder->mCodec->getEncodedFormat(); sk_sp<SkAnimatedImage> animatedImg = SkAnimatedImage::Make(std::move(imageDecoder->mCodec), info, subset, std::move(picture)); @@ -108,8 +108,8 @@ static jlong AnimatedImageDrawable_nCreate(JNIEnv* env, jobject /*clazz*/, bytesUsed += sizeof(animatedImg.get()); - sk_sp<AnimatedImageDrawable> drawable(new AnimatedImageDrawable(std::move(animatedImg), - bytesUsed)); + sk_sp<AnimatedImageDrawable> drawable( + new AnimatedImageDrawable(std::move(animatedImg), bytesUsed, format)); return reinterpret_cast<jlong>(drawable.release()); } diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index cc987bcd8f0e..c4d3f5cedfa8 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -53,6 +53,8 @@ SkiaOpenGLPipeline::~SkiaOpenGLPipeline() { } MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() { + bool wasSurfaceless = mEglManager.isCurrent(EGL_NO_SURFACE); + // In case the surface was destroyed (e.g. a previous trimMemory call) we // need to recreate it here. if (mHardwareBuffer) { @@ -65,6 +67,37 @@ MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() { if (!mEglManager.makeCurrent(mEglSurface, &error)) { return MakeCurrentResult::AlreadyCurrent; } + + // Make sure read/draw buffer state of default framebuffer is GL_BACK. Vendor implementations + // disagree on the draw/read buffer state if the default framebuffer transitions from a surface + // to EGL_NO_SURFACE and vice-versa. There was a related discussion within Khronos on this topic. + // See https://cvs.khronos.org/bugzilla/show_bug.cgi?id=13534. + // The discussion was not resolved with a clear consensus + if (error == 0 && wasSurfaceless && mEglSurface != EGL_NO_SURFACE) { + GLint curReadFB = 0; + GLint curDrawFB = 0; + glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &curReadFB); + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &curDrawFB); + + GLint buffer = GL_NONE; + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glGetIntegerv(GL_DRAW_BUFFER0, &buffer); + if (buffer == GL_NONE) { + const GLenum drawBuffer = GL_BACK; + glDrawBuffers(1, &drawBuffer); + } + + glGetIntegerv(GL_READ_BUFFER, &buffer); + if (buffer == GL_NONE) { + glReadBuffer(GL_BACK); + } + + glBindFramebuffer(GL_READ_FRAMEBUFFER, curReadFB); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, curDrawFB); + + GL_CHECKPOINT(LOW); + } + return error ? MakeCurrentResult::Failed : MakeCurrentResult::Succeeded; } @@ -104,7 +137,8 @@ IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw( GrBackendRenderTarget backendRT(frame.width(), frame.height(), 0, STENCIL_BUFFER_SIZE, fboInfo); - SkSurfaceProps props(0, kUnknown_SkPixelGeometry); + SkSurfaceProps props(mColorMode == ColorMode::Default ? 0 : SkSurfaceProps::kAlwaysDither_Flag, + kUnknown_SkPixelGeometry); SkASSERT(mRenderThread.getGrContext() != nullptr); sk_sp<SkSurface> surface; diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h index 940d6bfdb83c..f0461bef170c 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h @@ -53,6 +53,14 @@ public: bool isSurfaceReady() override; bool isContextReady() override; + const SkM44& getPixelSnapMatrix() const override { + // Small (~1/16th) nudge to ensure that pixel-aligned non-AA'd draws fill the + // desired fragment + static const SkScalar kOffset = 0.063f; + static const SkM44 sSnapMatrix = SkM44::Translate(kOffset, kOffset); + return sSnapMatrix; + } + static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor); protected: diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 1f929685b62c..b020e966e05a 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -28,7 +28,6 @@ #include <SkMultiPictureDocument.h> #include <SkOverdrawCanvas.h> #include <SkOverdrawColorFilter.h> -#include <SkPaintFilterCanvas.h> #include <SkPicture.h> #include <SkPictureRecorder.h> #include <SkRect.h> @@ -450,23 +449,6 @@ void SkiaPipeline::endCapture(SkSurface* surface) { } } -class ForceDitherCanvas : public SkPaintFilterCanvas { -public: - ForceDitherCanvas(SkCanvas* canvas) : SkPaintFilterCanvas(canvas) {} - -protected: - bool onFilter(SkPaint& paint) const override { - paint.setDither(true); - return true; - } - - void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override { - // We unroll the drawable using "this" canvas, so that draw calls contained inside will - // get dithering applied - drawable->draw(this, matrix); - } -}; - void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip, const std::vector<sp<RenderNode>>& nodes, bool opaque, const Rect& contentDrawBounds, sk_sp<SkSurface> surface, @@ -521,12 +503,6 @@ void SkiaPipeline::renderFrameImpl(const SkRect& clip, canvas->clear(SK_ColorTRANSPARENT); } - std::optional<ForceDitherCanvas> forceDitherCanvas; - if (shouldForceDither()) { - forceDitherCanvas.emplace(canvas); - canvas = &forceDitherCanvas.value(); - } - if (1 == nodes.size()) { if (!nodes[0]->nothingToDraw()) { RenderNodeDrawable root(nodes[0].get(), canvas); diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h index 0763b06b53ef..befee8989383 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaPipeline.h @@ -98,8 +98,6 @@ protected: bool isCapturingSkp() const { return mCaptureMode != CaptureMode::None; } - virtual bool shouldForceDither() const { return mColorMode != ColorMode::Default; } - private: void renderFrameImpl(const SkRect& clip, const std::vector<sp<RenderNode>>& nodes, bool opaque, diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index 6f1b99b95bbd..86096d5bd01c 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -203,11 +203,6 @@ sk_sp<Bitmap> SkiaVulkanPipeline::allocateHardwareBitmap(renderthread::RenderThr return nullptr; } -bool SkiaVulkanPipeline::shouldForceDither() const { - if (mVkSurface && mVkSurface->isBeyond8Bit()) return false; - return SkiaPipeline::shouldForceDither(); -} - void SkiaVulkanPipeline::onContextDestroyed() { if (mVkSurface) { vulkanManager().destroySurface(mVkSurface); @@ -215,6 +210,10 @@ void SkiaVulkanPipeline::onContextDestroyed() { } } +const SkM44& SkiaVulkanPipeline::getPixelSnapMatrix() const { + return mVkSurface->getPixelSnapMatrix(); +} + } /* namespace skiapipeline */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h index 0713e93bccde..284cde537ec0 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h @@ -53,8 +53,8 @@ public: void onStop() override; bool isSurfaceReady() override; bool isContextReady() override; - bool supportsExtendedRangeHdr() const override { return true; } void setTargetSdrHdrRatio(float ratio) override; + const SkM44& getPixelSnapMatrix() const override; static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor); static sk_sp<Bitmap> allocateHardwareBitmap(renderthread::RenderThread& thread, @@ -63,8 +63,6 @@ public: protected: void onContextDestroyed() override; - bool shouldForceDither() const override; - private: renderthread::VulkanManager& vulkanManager(); renderthread::VulkanSurface* mVkSurface = nullptr; diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp index 23611efccd73..7e9d44fbdbd1 100644 --- a/libs/hwui/renderthread/CacheManager.cpp +++ b/libs/hwui/renderthread/CacheManager.cpp @@ -117,12 +117,8 @@ void CacheManager::trimMemory(TrimLevel mode) { // flush and submit all work to the gpu and wait for it to finish mGrContext->flushAndSubmit(/*syncCpu=*/true); - if (!Properties::isHighEndGfx && mode >= TrimLevel::MODERATE) { - mode = TrimLevel::COMPLETE; - } - switch (mode) { - case TrimLevel::COMPLETE: + case TrimLevel::BACKGROUND: mGrContext->freeGpuResources(); SkGraphics::PurgeAllCaches(); mRenderThread.destroyRenderingContext(); diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 6b2c99534a4c..f60c1f3c6ad8 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -236,7 +236,6 @@ void CanvasContext::setupPipelineSurface() { if (mNativeSurface && !mNativeSurface->didSetExtraBuffers()) { setBufferCount(mNativeSurface->getNativeWindow()); - } mFrameNumber = 0; @@ -301,10 +300,6 @@ void CanvasContext::setOpaque(bool opaque) { float CanvasContext::setColorMode(ColorMode mode) { if (mode != mColorMode) { - const bool isHdr = mode == ColorMode::Hdr || mode == ColorMode::Hdr10; - if (isHdr && !mRenderPipeline->supportsExtendedRangeHdr()) { - mode = ColorMode::WideColorGamut; - } mColorMode = mode; mRenderPipeline->setSurfaceColorProperties(mode); setupPipelineSurface(); @@ -871,6 +866,10 @@ SkISize CanvasContext::getNextFrameSize() const { return size; } +const SkM44& CanvasContext::getPixelSnapMatrix() const { + return mRenderPipeline->getPixelSnapMatrix(); +} + void CanvasContext::prepareAndDraw(RenderNode* node) { ATRACE_CALL(); diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 3f2533959c20..d7215de92375 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -200,6 +200,9 @@ public: SkISize getNextFrameSize() const; + // Returns the matrix to use to nudge non-AA'd points/lines towards the fragment center + const SkM44& getPixelSnapMatrix() const; + // Called when SurfaceStats are available. static void onSurfaceStatsAvailable(void* context, int32_t surfaceControlId, ASurfaceControlStats* stats); diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index 4fb114b71bf5..94f35fd9eaf2 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -423,6 +423,7 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window, EGLint attribs[] = {EGL_NONE, EGL_NONE, EGL_NONE}; EGLConfig config = mEglConfig; + bool overrideWindowDataSpaceForHdr = false; if (colorMode == ColorMode::A8) { // A8 doesn't use a color space if (!mEglConfigA8) { @@ -450,12 +451,13 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window, case ColorMode::Default: attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR; break; - // Extended Range HDR requires being able to manipulate the dataspace in ways - // we cannot easily do while going through EGLSurface. Given this requires - // composer3 support, just treat HDR as equivalent to wide color gamut if - // the GLES path is still being hit + // We don't have an EGL colorspace for extended range P3 that's used for HDR + // So override it after configuring the EGL context case ColorMode::Hdr: case ColorMode::Hdr10: + overrideWindowDataSpaceForHdr = true; + attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT; + break; case ColorMode::WideColorGamut: { skcms_Matrix3x3 colorGamut; LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&colorGamut), @@ -491,6 +493,16 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window, (void*)window, eglErrorString()); } + if (overrideWindowDataSpaceForHdr) { + // This relies on knowing that EGL will not re-set the dataspace after the call to + // eglCreateWindowSurface. Since the handling of the colorspace extension is largely + // implemented in libEGL in the platform, we can safely assume this is the case + int32_t err = ANativeWindow_setBuffersDataSpace( + window, + static_cast<android_dataspace>(STANDARD_DCI_P3 | TRANSFER_SRGB | RANGE_EXTENDED)); + LOG_ALWAYS_FATAL_IF(err, "Failed to ANativeWindow_setBuffersDataSpace %d", err); + } + return surface; } diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h index c68fcdfc76f2..6c2cb9d71c55 100644 --- a/libs/hwui/renderthread/IRenderPipeline.h +++ b/libs/hwui/renderthread/IRenderPipeline.h @@ -95,8 +95,8 @@ public: virtual void setPictureCapturedCallback( const std::function<void(sk_sp<SkPicture>&&)>& callback) = 0; - virtual bool supportsExtendedRangeHdr() const { return false; } virtual void setTargetSdrHdrRatio(float ratio) = 0; + virtual const SkM44& getPixelSnapMatrix() const = 0; virtual ~IRenderPipeline() {} }; diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index 718d4a16d5c8..96bfc1061d4e 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -42,6 +42,36 @@ namespace android { namespace uirenderer { namespace renderthread { +static std::array<std::string_view, 18> sEnableExtensions{ + VK_KHR_BIND_MEMORY_2_EXTENSION_NAME, + VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME, + VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME, + VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, + VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME, + VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, + VK_KHR_MAINTENANCE1_EXTENSION_NAME, + VK_KHR_MAINTENANCE2_EXTENSION_NAME, + VK_KHR_MAINTENANCE3_EXTENSION_NAME, + VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME, + VK_KHR_SURFACE_EXTENSION_NAME, + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME, + VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME, + VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME, + VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME, + VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, + VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, +}; + +static bool shouldEnableExtension(const std::string_view& extension) { + for (const auto& it : sEnableExtensions) { + if (it == extension) { + return true; + } + } + return false; +} + static void free_features_extensions_structs(const VkPhysicalDeviceFeatures2& features) { // All Vulkan structs that could be part of the features chain will start with the // structure type followed by the pNext pointer. We cast to the CommonVulkanHeader @@ -139,6 +169,11 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe bool hasKHRSurfaceExtension = false; bool hasKHRAndroidSurfaceExtension = false; for (const VkExtensionProperties& extension : mInstanceExtensionsOwner) { + if (!shouldEnableExtension(extension.extensionName)) { + ALOGV("Not enabling instance extension %s", extension.extensionName); + continue; + } + ALOGV("Enabling instance extension %s", extension.extensionName); mInstanceExtensions.push_back(extension.extensionName); if (!strcmp(extension.extensionName, VK_KHR_SURFACE_EXTENSION_NAME)) { hasKHRSurfaceExtension = true; @@ -219,6 +254,11 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe LOG_ALWAYS_FATAL_IF(VK_SUCCESS != err); bool hasKHRSwapchainExtension = false; for (const VkExtensionProperties& extension : mDeviceExtensionsOwner) { + if (!shouldEnableExtension(extension.extensionName)) { + ALOGV("Not enabling device extension %s", extension.extensionName); + continue; + } + ALOGV("Enabling device extension %s", extension.extensionName); mDeviceExtensions.push_back(extension.extensionName); if (!strcmp(extension.extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME)) { hasKHRSwapchainExtension = true; diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp index ae4f0572576e..10f456745147 100644 --- a/libs/hwui/renderthread/VulkanSurface.cpp +++ b/libs/hwui/renderthread/VulkanSurface.cpp @@ -63,6 +63,18 @@ static SkMatrix GetPreTransformMatrix(SkISize windowSize, int transform) { return SkMatrix::I(); } +static SkM44 GetPixelSnapMatrix(SkISize windowSize, int transform) { + // Small (~1/16th) nudge to ensure that pixel-aligned non-AA'd draws fill the + // desired fragment + static const SkScalar kOffset = 0.063f; + SkMatrix preRotation = GetPreTransformMatrix(windowSize, transform); + SkMatrix invert; + LOG_ALWAYS_FATAL_IF(!preRotation.invert(&invert)); + return SkM44::Translate(kOffset, kOffset) + .postConcat(SkM44(preRotation)) + .preConcat(SkM44(invert)); +} + static bool ConnectAndSetWindowDefaults(ANativeWindow* window) { ATRACE_CALL(); @@ -178,6 +190,8 @@ bool VulkanSurface::InitializeWindowInfoStruct(ANativeWindow* window, ColorMode outWindowInfo->preTransform = GetPreTransformMatrix(outWindowInfo->size, outWindowInfo->transform); + outWindowInfo->pixelSnapMatrix = + GetPixelSnapMatrix(outWindowInfo->size, outWindowInfo->transform); err = window->query(window, NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &query_value); if (err != 0 || query_value < 0) { @@ -413,6 +427,7 @@ VulkanSurface::NativeBufferInfo* VulkanSurface::dequeueNativeBuffer() { } mWindowInfo.preTransform = GetPreTransformMatrix(mWindowInfo.size, mWindowInfo.transform); + mWindowInfo.pixelSnapMatrix = GetPixelSnapMatrix(mWindowInfo.size, mWindowInfo.transform); } uint32_t idx; @@ -438,9 +453,15 @@ VulkanSurface::NativeBufferInfo* VulkanSurface::dequeueNativeBuffer() { VulkanSurface::NativeBufferInfo* bufferInfo = &mNativeBuffers[idx]; if (bufferInfo->skSurface.get() == nullptr) { + SkSurfaceProps surfaceProps; + if (mWindowInfo.colorMode != ColorMode::Default) { + surfaceProps = SkSurfaceProps(SkSurfaceProps::kAlwaysDither_Flag | surfaceProps.flags(), + surfaceProps.pixelGeometry()); + } bufferInfo->skSurface = SkSurface::MakeFromAHardwareBuffer( mGrContext, ANativeWindowBuffer_getHardwareBuffer(bufferInfo->buffer.get()), - kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, nullptr, /*from_window=*/true); + kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, &surfaceProps, + /*from_window=*/true); if (bufferInfo->skSurface.get() == nullptr) { ALOGE("SkSurface::MakeFromAHardwareBuffer failed"); mNativeWindow->cancelBuffer(mNativeWindow.get(), buffer, @@ -530,16 +551,6 @@ void VulkanSurface::setColorSpace(sk_sp<SkColorSpace> colorSpace) { } } -bool VulkanSurface::isBeyond8Bit() const { - switch (mWindowInfo.bufferFormat) { - case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM: - case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT: - return true; - default: - return false; - } -} - } /* namespace renderthread */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/renderthread/VulkanSurface.h b/libs/hwui/renderthread/VulkanSurface.h index 3b69b73bcab3..6f5280105e55 100644 --- a/libs/hwui/renderthread/VulkanSurface.h +++ b/libs/hwui/renderthread/VulkanSurface.h @@ -47,8 +47,7 @@ public: const SkMatrix& getCurrentPreTransform() { return mWindowInfo.preTransform; } void setColorSpace(sk_sp<SkColorSpace> colorSpace); - - bool isBeyond8Bit() const; + const SkM44& getPixelSnapMatrix() const { return mWindowInfo.pixelSnapMatrix; } private: /* @@ -107,6 +106,7 @@ private: SkISize actualSize; // transform to be applied to the SkSurface to map the coordinates to the provided transform SkMatrix preTransform; + SkM44 pixelSnapMatrix; }; VulkanSurface(ANativeWindow* window, const WindowInfo& windowInfo, GrDirectContext* grContext); |