summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java223
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java79
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java31
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java104
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java24
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java8
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt34
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt18
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java66
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java81
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepository.kt133
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt22
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt25
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java56
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java19
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepositoryParameterizedTests.kt150
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepositoryTests.kt77
37 files changed, 1119 insertions, 316 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
index 1fbaeeac8608..29936cc2cac3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
@@ -33,7 +33,9 @@ import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSI
import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_RIGHT;
import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_TOP;
-import android.annotation.DimenRes;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityThread;
@@ -53,9 +55,11 @@ import android.view.Gravity;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
+import android.view.VelocityTracker;
import android.view.View;
import android.view.WindowManager;
import android.view.WindowlessWindowManager;
+import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.window.InputTransferToken;
@@ -97,6 +101,16 @@ class DividerPresenter implements View.OnTouchListener {
@VisibleForTesting
static final int DEFAULT_DIVIDER_WIDTH_DP = 24;
+ @VisibleForTesting
+ static final PathInterpolator FLING_ANIMATION_INTERPOLATOR =
+ new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+ @VisibleForTesting
+ static final int FLING_ANIMATION_DURATION = 250;
+ @VisibleForTesting
+ static final int MIN_DISMISS_VELOCITY_DP_PER_SECOND = 600;
+ @VisibleForTesting
+ static final int MIN_FLING_VELOCITY_DP_PER_SECOND = 400;
+
private final int mTaskId;
@NonNull
@@ -109,6 +123,14 @@ class DividerPresenter implements View.OnTouchListener {
private final Executor mCallbackExecutor;
/**
+ * The VelocityTracker of the divider, used to track the dragging velocity. This field is
+ * {@code null} until dragging starts.
+ */
+ @GuardedBy("mLock")
+ @Nullable
+ VelocityTracker mVelocityTracker;
+
+ /**
* The {@link Properties} of the divider. This field is {@code null} when no divider should be
* drawn, e.g. when the split doesn't have {@link DividerAttributes} or when the decor surface
* is not available.
@@ -370,13 +392,11 @@ class DividerPresenter implements View.OnTouchListener {
applicationContext.getResources().getDisplayMetrics());
}
- private static int getDimensionDp(@DimenRes int resId) {
- final Context context = ActivityThread.currentActivityThread().getApplication();
- final int px = context.getResources().getDimensionPixelSize(resId);
- return (int) TypedValue.convertPixelsToDimension(
- COMPLEX_UNIT_DIP,
- px,
- context.getResources().getDisplayMetrics());
+ private static float getDisplayDensity() {
+ // TODO(b/329193115) support divider on secondary display
+ final Context applicationContext =
+ ActivityThread.currentActivityThread().getApplication();
+ return applicationContext.getResources().getDisplayMetrics().density;
}
/**
@@ -487,24 +507,27 @@ class DividerPresenter implements View.OnTouchListener {
@Override
public boolean onTouch(@NonNull View view, @NonNull MotionEvent event) {
synchronized (mLock) {
- final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
- mDividerPosition = calculateDividerPosition(
- event, taskBounds, mRenderer.mDividerWidthPx, mProperties.mDividerAttributes,
- mProperties.mIsVerticalSplit, calculateMinPosition(), calculateMaxPosition());
- mRenderer.setDividerPosition(mDividerPosition);
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- onStartDragging();
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- onFinishDragging();
- break;
- case MotionEvent.ACTION_MOVE:
- onDrag();
- break;
- default:
- break;
+ if (mProperties != null && mRenderer != null) {
+ final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
+ mDividerPosition = calculateDividerPosition(
+ event, taskBounds, mRenderer.mDividerWidthPx,
+ mProperties.mDividerAttributes, mProperties.mIsVerticalSplit,
+ calculateMinPosition(), calculateMaxPosition());
+ mRenderer.setDividerPosition(mDividerPosition);
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ onStartDragging(event);
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ onFinishDragging(event);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ onDrag(event);
+ break;
+ default:
+ break;
+ }
}
}
@@ -514,7 +537,10 @@ class DividerPresenter implements View.OnTouchListener {
}
@GuardedBy("mLock")
- private void onStartDragging() {
+ private void onStartDragging(@NonNull MotionEvent event) {
+ mVelocityTracker = VelocityTracker.obtain();
+ mVelocityTracker.addMovement(event);
+
mRenderer.mIsDragging = true;
mRenderer.mDragHandle.setPressed(mRenderer.mIsDragging);
mRenderer.updateSurface();
@@ -536,16 +562,81 @@ class DividerPresenter implements View.OnTouchListener {
}
@GuardedBy("mLock")
- private void onDrag() {
+ private void onDrag(@NonNull MotionEvent event) {
+ if (mVelocityTracker != null) {
+ mVelocityTracker.addMovement(event);
+ }
mRenderer.updateSurface();
}
@GuardedBy("mLock")
- private void onFinishDragging() {
- mDividerPosition = adjustDividerPositionForSnapPoints(mDividerPosition);
- mRenderer.setDividerPosition(mDividerPosition);
+ private void onFinishDragging(@NonNull MotionEvent event) {
+ float velocity = 0.0f;
+ if (mVelocityTracker != null) {
+ mVelocityTracker.addMovement(event);
+ mVelocityTracker.computeCurrentVelocity(1000 /* units */);
+ velocity = mProperties.mIsVerticalSplit
+ ? mVelocityTracker.getXVelocity()
+ : mVelocityTracker.getYVelocity();
+ mVelocityTracker.recycle();
+ }
+
+ final int prevDividerPosition = mDividerPosition;
+ mDividerPosition = dividerPositionForSnapPoints(mDividerPosition, velocity);
+ if (mDividerPosition != prevDividerPosition) {
+ ValueAnimator animator = getFlingAnimator(prevDividerPosition, mDividerPosition);
+ animator.start();
+ } else {
+ onDraggingEnd();
+ }
+ }
+
+ @GuardedBy("mLock")
+ @NonNull
+ @VisibleForTesting
+ ValueAnimator getFlingAnimator(int prevDividerPosition, int snappedDividerPosition) {
+ final ValueAnimator animator =
+ getValueAnimator(prevDividerPosition, snappedDividerPosition);
+ animator.addUpdateListener(animation -> {
+ synchronized (mLock) {
+ updateDividerPosition((int) animation.getAnimatedValue());
+ }
+ });
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ synchronized (mLock) {
+ onDraggingEnd();
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ synchronized (mLock) {
+ onDraggingEnd();
+ }
+ }
+ });
+ return animator;
+ }
+
+ @VisibleForTesting
+ static ValueAnimator getValueAnimator(int prevDividerPosition, int snappedDividerPosition) {
+ ValueAnimator animator = ValueAnimator
+ .ofInt(prevDividerPosition, snappedDividerPosition)
+ .setDuration(FLING_ANIMATION_DURATION);
+ animator.setInterpolator(FLING_ANIMATION_INTERPOLATOR);
+ return animator;
+ }
+
+ @GuardedBy("mLock")
+ private void updateDividerPosition(int position) {
+ mRenderer.setDividerPosition(position);
mRenderer.updateSurface();
+ }
+ @GuardedBy("mLock")
+ private void onDraggingEnd() {
// Veil visibility change should be applied together with the surface boost transaction in
// the wct.
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
@@ -570,36 +661,76 @@ class DividerPresenter implements View.OnTouchListener {
/**
* Returns the divider position adjusted for the min max ratio and fullscreen expansion.
- *
- * If the dragging position is above the {@link DividerAttributes#getPrimaryMaxRatio()} or below
- * {@link DividerAttributes#getPrimaryMinRatio()} and
- * {@link DividerAttributes#isDraggingToFullscreenAllowed} is {@code true}, the system will
- * choose a snap algorithm to adjust the ending position to either fully expand one container or
- * move the divider back to the specified min/max ratio.
- *
- * TODO(b/327067596) implement snap algorithm
- *
* The adjusted divider position is in the range of [minPosition, maxPosition] for a split, 0
* for expanded right (bottom) container, or task width (height) minus the divider width for
* expanded left (top) container.
*/
@GuardedBy("mLock")
- private int adjustDividerPositionForSnapPoints(int dividerPosition) {
+ private int dividerPositionForSnapPoints(int dividerPosition, float velocity) {
final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
final int minPosition = calculateMinPosition();
final int maxPosition = calculateMaxPosition();
final int fullyExpandedPosition = mProperties.mIsVerticalSplit
? taskBounds.right - mRenderer.mDividerWidthPx
: taskBounds.bottom - mRenderer.mDividerWidthPx;
+
if (isDraggingToFullscreenAllowed(mProperties.mDividerAttributes)) {
- if (dividerPosition < minPosition) {
- return 0;
+ final float displayDensity = getDisplayDensity();
+ return dividerPositionWithDraggingToFullscreenAllowed(
+ dividerPosition,
+ minPosition,
+ maxPosition,
+ fullyExpandedPosition,
+ velocity,
+ displayDensity);
+ }
+ return Math.clamp(dividerPosition, minPosition, maxPosition);
+ }
+
+ /**
+ * Returns the divider position given a set of position options. A snap algorithm is used to
+ * adjust the ending position to either fully expand one container or move the divider back to
+ * the specified min/max ratio depending on the dragging velocity.
+ */
+ @VisibleForTesting
+ static int dividerPositionWithDraggingToFullscreenAllowed(int dividerPosition, int minPosition,
+ int maxPosition, int fullyExpandedPosition, float velocity, float displayDensity) {
+ final float minDismissVelocityPxPerSecond =
+ MIN_DISMISS_VELOCITY_DP_PER_SECOND * displayDensity;
+ final float minFlingVelocityPxPerSecond =
+ MIN_FLING_VELOCITY_DP_PER_SECOND * displayDensity;
+ if (dividerPosition < minPosition && velocity < -minDismissVelocityPxPerSecond) {
+ return 0;
+ }
+ if (dividerPosition > maxPosition && velocity > minDismissVelocityPxPerSecond) {
+ return fullyExpandedPosition;
+ }
+ if (Math.abs(velocity) < minFlingVelocityPxPerSecond) {
+ if (dividerPosition >= minPosition && dividerPosition <= maxPosition) {
+ return dividerPosition;
}
- if (dividerPosition > maxPosition) {
- return fullyExpandedPosition;
+ int[] possiblePositions = {0, minPosition, maxPosition, fullyExpandedPosition};
+ return snap(dividerPosition, possiblePositions);
+ }
+ if (velocity < 0) {
+ return 0;
+ } else {
+ return fullyExpandedPosition;
+ }
+ }
+
+ /** Calculates the snapped divider position based on the possible positions and distance. */
+ private static int snap(int dividerPosition, int[] possiblePositions) {
+ int snappedPosition = dividerPosition;
+ float minDistance = Float.MAX_VALUE;
+ for (int position : possiblePositions) {
+ float distance = Math.abs(dividerPosition - position);
+ if (distance < minDistance) {
+ snappedPosition = position;
+ minDistance = distance;
}
}
- return Math.clamp(dividerPosition, minPosition, maxPosition);
+ return snappedPosition;
}
private static void setDecorSurfaceBoosted(
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 13c2d1f73461..b764b6ee065f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -50,6 +50,7 @@ import static androidx.window.extensions.embedding.SplitPresenter.getActivityInt
import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions;
import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds;
import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
+import static androidx.window.extensions.embedding.TaskFragmentContainer.OverlayContainerRestoreParams;
import android.annotation.CallbackExecutor;
import android.app.Activity;
@@ -133,6 +134,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
private final List<EmbeddingRule> mSplitRules = new ArrayList<>();
/**
+ * Stores the token of the associated Activity that maps to the
+ * {@link OverlayContainerRestoreParams} of the most recent created overlay container.
+ */
+ @GuardedBy("mLock")
+ final ArrayMap<IBinder, OverlayContainerRestoreParams> mOverlayRestoreParams = new ArrayMap<>();
+
+ /**
* A developer-defined {@link SplitAttributes} calculator to compute the current
* {@link SplitAttributes} with the current device and window states.
* It is registered via {@link #setSplitAttributesCalculator(Function)}
@@ -686,11 +694,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
exception);
break;
case TYPE_ACTIVITY_REPARENTED_TO_TASK:
+ final IBinder candidateAssociatedActToken, lastOverlayToken;
+ if (Flags.fixPipRestoreToOverlay()) {
+ candidateAssociatedActToken = change.getOtherActivityToken();
+ lastOverlayToken = change.getTaskFragmentToken();
+ } else {
+ candidateAssociatedActToken = lastOverlayToken = null;
+ }
onActivityReparentedToTask(
wct,
taskId,
change.getActivityIntent(),
- change.getActivityToken());
+ change.getActivityToken(),
+ candidateAssociatedActToken,
+ lastOverlayToken);
break;
default:
throw new IllegalArgumentException(
@@ -917,11 +934,28 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* different process, the server will generate a temporary token that
* the organizer can use to reparent the activity through
* {@link WindowContainerTransaction} if needed.
+ * @param candidateAssociatedActToken The token of the candidate associated-activity.
+ * @param lastOverlayToken The last parent overlay container token.
*/
@VisibleForTesting
@GuardedBy("mLock")
void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
- int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) {
+ int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken,
+ @Nullable IBinder candidateAssociatedActToken, @Nullable IBinder lastOverlayToken) {
+ // Reparent the activity to an overlay container if needed.
+ final OverlayContainerRestoreParams params = getOverlayContainerRestoreParams(
+ candidateAssociatedActToken, lastOverlayToken);
+ if (params != null) {
+ final Activity associatedActivity = getActivity(candidateAssociatedActToken);
+ final TaskFragmentContainer targetContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ wct, params.mOptions, params.mIntent, associatedActivity);
+ if (targetContainer != null) {
+ wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(),
+ activityToken);
+ return;
+ }
+ }
+
// If the activity belongs to the current app process, we treat it as a new activity
// launch.
final Activity activity = getActivity(activityToken);
@@ -966,6 +1000,43 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
/**
+ * Returns the {@link OverlayContainerRestoreParams} that stored last time the {@code
+ * associatedActivityToken} associated with and only if data matches the {@code overlayToken}.
+ * Otherwise, return {@code null}.
+ */
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ @Nullable
+ OverlayContainerRestoreParams getOverlayContainerRestoreParams(
+ @Nullable IBinder associatedActivityToken, @Nullable IBinder overlayToken) {
+ if (!Flags.fixPipRestoreToOverlay()) {
+ return null;
+ }
+
+ if (associatedActivityToken == null || overlayToken == null) {
+ return null;
+ }
+
+ final TaskFragmentContainer.OverlayContainerRestoreParams params =
+ mOverlayRestoreParams.get(associatedActivityToken);
+ if (params == null) {
+ return null;
+ }
+
+ if (params.mOverlayToken != overlayToken) {
+ // Not the same overlay container, no need to restore.
+ return null;
+ }
+
+ final Activity associatedActivity = getActivity(associatedActivityToken);
+ if (associatedActivity == null || associatedActivity.isFinishing()) {
+ return null;
+ }
+
+ return params;
+ }
+
+ /**
* Called when the {@link WindowContainerTransaction} created with
* {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side.
*
@@ -1433,6 +1504,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
mTaskContainers.valueAt(i).onFinishingActivityPaused(wct, activityToken);
}
+
+ mOverlayRestoreParams.remove(activity.getActivityToken());
updateCallbackIfNecessary();
}
@@ -1450,6 +1523,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
mTaskContainers.valueAt(i).onActivityDestroyed(wct, activityToken);
}
+
+ mOverlayRestoreParams.remove(activity.getActivityToken());
// We didn't trigger the callback if there were any pending appeared activities, so check
// again after the pending is removed.
updateCallbackIfNecessary();
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 482554351b97..d0b6a01bb51e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -36,6 +36,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import java.util.ArrayList;
import java.util.Collections;
@@ -274,6 +275,15 @@ class TaskFragmentContainer {
addPendingAppearedActivity(pendingAppearedActivity);
}
mPendingAppearedIntent = pendingAppearedIntent;
+
+ // Save the information necessary for restoring the overlay when needed.
+ if (Flags.fixPipRestoreToOverlay() && overlayTag != null && pendingAppearedIntent != null
+ && associatedActivity != null && !associatedActivity.isFinishing()) {
+ final IBinder associatedActivityToken = associatedActivity.getActivityToken();
+ final OverlayContainerRestoreParams params = new OverlayContainerRestoreParams(mToken,
+ launchOptions, pendingAppearedIntent);
+ mController.mOverlayRestoreParams.put(associatedActivityToken, params);
+ }
}
/**
@@ -1105,4 +1115,25 @@ class TaskFragmentContainer {
}
return sb.append("]").toString();
}
+
+ static class OverlayContainerRestoreParams {
+ /** The token of the overlay container */
+ @NonNull
+ final IBinder mOverlayToken;
+
+ /** The launch options to create this container. */
+ @NonNull
+ final Bundle mOptions;
+
+ /** The Intent that used to be started in the overlay container. */
+ @NonNull
+ final Intent mIntent;
+
+ OverlayContainerRestoreParams(@NonNull IBinder overlayToken, @NonNull Bundle options,
+ @NonNull Intent intent) {
+ mOverlayToken = overlayToken;
+ mOptions = options;
+ mIntent = intent;
+ }
+ }
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
index b0a45e285896..746607c8094c 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
@@ -19,6 +19,10 @@ package androidx.window.extensions.embedding;
import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE;
import static android.window.TaskFragmentOperation.OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE;
+import static androidx.window.extensions.embedding.DividerPresenter.FLING_ANIMATION_DURATION;
+import static androidx.window.extensions.embedding.DividerPresenter.FLING_ANIMATION_INTERPOLATOR;
+import static androidx.window.extensions.embedding.DividerPresenter.MIN_DISMISS_VELOCITY_DP_PER_SECOND;
+import static androidx.window.extensions.embedding.DividerPresenter.MIN_FLING_VELOCITY_DP_PER_SECOND;
import static androidx.window.extensions.embedding.DividerPresenter.getBoundsOffsetForDivider;
import static androidx.window.extensions.embedding.DividerPresenter.getInitialDividerPosition;
import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_BOTTOM;
@@ -35,6 +39,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.res.Configuration;
import android.graphics.Color;
@@ -637,6 +642,105 @@ public class DividerPresenterTest {
DividerPresenter.getContainerBackgroundColor(container, defaultColor));
}
+ @Test
+ public void testGetValueAnimator() {
+ ValueAnimator animator =
+ DividerPresenter.getValueAnimator(
+ 375 /* prevDividerPosition */,
+ 500 /* snappedDividerPosition */);
+
+ assertEquals(animator.getDuration(), FLING_ANIMATION_DURATION);
+ assertEquals(animator.getInterpolator(), FLING_ANIMATION_INTERPOLATOR);
+ }
+
+ @Test
+ public void testDividerPositionWithDraggingToFullscreenAllowed() {
+ final float displayDensity = 600F;
+ final float dismissVelocity = MIN_DISMISS_VELOCITY_DP_PER_SECOND * displayDensity + 10f;
+ final float nonFlingVelocity = MIN_FLING_VELOCITY_DP_PER_SECOND * displayDensity - 10f;
+ final float flingVelocity = MIN_FLING_VELOCITY_DP_PER_SECOND * displayDensity + 10f;
+
+ // Divider position is less than minPosition and the velocity is enough to be dismissed
+ assertEquals(
+ 0, // Closed position
+ DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed(
+ 10 /* dividerPosition */,
+ 30 /* minPosition */,
+ 900 /* maxPosition */,
+ 1200 /* fullyExpandedPosition */,
+ -dismissVelocity,
+ displayDensity));
+
+ // Divider position is greater than maxPosition and the velocity is enough to be dismissed
+ assertEquals(
+ 1200, // Fully expanded position
+ DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed(
+ 1000 /* dividerPosition */,
+ 30 /* minPosition */,
+ 900 /* maxPosition */,
+ 1200 /* fullyExpandedPosition */,
+ dismissVelocity,
+ displayDensity));
+
+ // Divider position is returned when the velocity is not fast enough for fling and is in
+ // between minPosition and maxPosition
+ assertEquals(
+ 500, // dividerPosition is not snapped
+ DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed(
+ 500 /* dividerPosition */,
+ 30 /* minPosition */,
+ 900 /* maxPosition */,
+ 1200 /* fullyExpandedPosition */,
+ nonFlingVelocity,
+ displayDensity));
+
+ // Divider position is snapped when the velocity is not fast enough for fling and larger
+ // than maxPosition
+ assertEquals(
+ 900, // Closest position is maxPosition
+ DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed(
+ 950 /* dividerPosition */,
+ 30 /* minPosition */,
+ 900 /* maxPosition */,
+ 1200 /* fullyExpandedPosition */,
+ nonFlingVelocity,
+ displayDensity));
+
+ // Divider position is snapped when the velocity is not fast enough for fling and smaller
+ // than minPosition
+ assertEquals(
+ 30, // Closest position is minPosition
+ DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed(
+ 20 /* dividerPosition */,
+ 30 /* minPosition */,
+ 900 /* maxPosition */,
+ 1200 /* fullyExpandedPosition */,
+ nonFlingVelocity,
+ displayDensity));
+
+ // Divider position is greater than minPosition and the velocity is enough for fling
+ assertEquals(
+ 0, // Closed position
+ DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed(
+ 50 /* dividerPosition */,
+ 30 /* minPosition */,
+ 900 /* maxPosition */,
+ 1200 /* fullyExpandedPosition */,
+ -flingVelocity,
+ displayDensity));
+
+ // Divider position is less than maxPosition and the velocity is enough for fling
+ assertEquals(
+ 1200, // Fully expanded position
+ DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed(
+ 800 /* dividerPosition */,
+ 30 /* minPosition */,
+ 900 /* maxPosition */,
+ 1200 /* fullyExpandedPosition */,
+ flingVelocity,
+ displayDensity));
+ }
+
private TaskFragmentContainer createMockTaskFragmentContainer(
@NonNull IBinder token, @NonNull Rect bounds) {
final TaskFragmentContainer container = mock(TaskFragmentContainer.class);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index 9ebcb759115d..f3222572a3e2 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -836,6 +836,30 @@ public class OverlayPresentationTest {
any());
}
+ @Test
+ public void testOnActivityReparentedToTask_overlayRestoration() {
+ mSetFlagRule.enableFlags(Flags.FLAG_FIX_PIP_RESTORE_TO_OVERLAY);
+
+ // Prepares and mock the data necessary for the test.
+ final IBinder activityToken = mActivity.getActivityToken();
+ final Intent intent = new Intent();
+ final IBinder fillTaskActivityToken = new Binder();
+ final IBinder lastOverlayToken = new Binder();
+ final TaskFragmentContainer overlayContainer = mSplitController.newContainer(intent,
+ mActivity, TASK_ID);
+ final TaskFragmentContainer.OverlayContainerRestoreParams params = mock(
+ TaskFragmentContainer.OverlayContainerRestoreParams.class);
+ doReturn(params).when(mSplitController).getOverlayContainerRestoreParams(any(), any());
+ doReturn(overlayContainer).when(mSplitController).createOrUpdateOverlayTaskFragmentIfNeeded(
+ any(), any(), any(), any());
+
+ // Verify the activity should be reparented to the overlay container.
+ mSplitController.onActivityReparentedToTask(mTransaction, TASK_ID, intent, activityToken,
+ fillTaskActivityToken, lastOverlayToken);
+ verify(mTransaction).reparentActivityToTaskFragment(
+ eq(overlayContainer.getTaskFragmentToken()), eq(activityToken));
+ }
+
/**
* A simplified version of {@link SplitController#createOrUpdateOverlayTaskFragmentIfNeeded}
*/
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 7d86ec2272af..35353dbe36be 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -397,7 +397,8 @@ public class SplitControllerTest {
@Test
public void testOnActivityReparentedToTask_sameProcess() {
mSplitController.onActivityReparentedToTask(mTransaction, TASK_ID, new Intent(),
- mActivity.getActivityToken());
+ mActivity.getActivityToken(), null /* fillTaskActivityToken */,
+ null /* lastOverlayToken */);
// Treated as on activity created, but allow to split as primary.
verify(mSplitController).resolveActivityToContainer(mTransaction,
@@ -413,7 +414,8 @@ public class SplitControllerTest {
final IBinder activityToken = new Binder();
final Intent intent = new Intent();
- mSplitController.onActivityReparentedToTask(mTransaction, TASK_ID, intent, activityToken);
+ mSplitController.onActivityReparentedToTask(mTransaction, TASK_ID, intent, activityToken,
+ null /* fillTaskActivityToken */, null /* lastOverlayToken */);
// Treated as starting new intent
verify(mSplitController, never()).resolveActivityToContainer(any(), any(), anyBoolean());
@@ -1210,7 +1212,7 @@ public class SplitControllerTest {
mSplitController.onTransactionReady(transaction);
verify(mSplitController).onActivityReparentedToTask(any(), eq(TASK_ID), eq(intent),
- eq(activityToken));
+ eq(activityToken), any(), any());
verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(),
anyInt(), anyBoolean());
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
index 8487e3792993..9e1440d5716b 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
@@ -218,11 +218,10 @@ class BubblePositionerTest {
insets = Insets.of(10, 20, 5, 15),
windowBounds = Rect(0, 0, 1800, 2600)
)
- val bubbleBarBounds = Rect(1700, 2500, 1780, 2600)
positioner.setShowingInBubbleBar(true)
positioner.update(deviceConfig)
- positioner.bubbleBarBounds = bubbleBarBounds
+ positioner.bubbleBarTopOnScreen = 2500
val spaceBetweenTopInsetAndBubbleBarInLandscape = 1680
val expandedViewVerticalSpacing =
@@ -246,10 +245,9 @@ class BubblePositionerTest {
insets = Insets.of(10, 20, 5, 15),
windowBounds = Rect(0, 0, screenWidth, 2600)
)
- val bubbleBarBounds = Rect(100, 2500, 280, 2550)
positioner.setShowingInBubbleBar(true)
positioner.update(deviceConfig)
- positioner.bubbleBarBounds = bubbleBarBounds
+ positioner.bubbleBarTopOnScreen = 2500
val spaceBetweenTopInsetAndBubbleBarInLandscape = 180
val expandedViewSpacing =
@@ -597,16 +595,19 @@ class BubblePositionerTest {
private fun testGetBubbleBarExpandedViewBounds(onLeft: Boolean, isOverflow: Boolean) {
positioner.setShowingInBubbleBar(true)
+ val windowBounds = Rect(0, 0, 2000, 2600)
+ val insets = Insets.of(10, 20, 5, 15)
val deviceConfig =
defaultDeviceConfig.copy(
isLargeScreen = true,
isLandscape = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 2000, 2600)
+ insets = insets,
+ windowBounds = windowBounds
)
positioner.update(deviceConfig)
- positioner.bubbleBarBounds = getBubbleBarBounds(onLeft, deviceConfig)
+ val bubbleBarHeight = 100
+ positioner.bubbleBarTopOnScreen = windowBounds.bottom - insets.bottom - bubbleBarHeight
val expandedViewPadding =
context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding)
@@ -624,7 +625,7 @@ class BubblePositionerTest {
left = right - positioner.getExpandedViewWidthForBubbleBar(isOverflow)
}
// Above the bubble bar
- val bottom = positioner.bubbleBarBounds.top - expandedViewPadding
+ val bottom = positioner.bubbleBarTopOnScreen - expandedViewPadding
// Calculate right and top based on size
val top = bottom - positioner.getExpandedViewHeightForBubbleBar(isOverflow)
val expectedBounds = Rect(left, top, right, bottom)
@@ -666,21 +667,4 @@ class BubblePositionerTest {
positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
return allowableStackRegion.top + allowableStackRegion.height() * offsetPercent
}
-
- private fun getBubbleBarBounds(onLeft: Boolean, deviceConfig: DeviceConfig): Rect {
- val width = 200
- val height = 100
- val bottom = deviceConfig.windowBounds.bottom - deviceConfig.insets.bottom
- val top = bottom - height
- val left: Int
- val right: Int
- if (onLeft) {
- left = deviceConfig.insets.left
- right = left + width
- } else {
- right = deviceConfig.windowBounds.right - deviceConfig.insets.right
- left = right - width
- }
- return Rect(left, top, right, bottom)
- }
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
index 076414132e27..12d19279111a 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
@@ -53,7 +53,6 @@ class BubbleExpandedViewPinControllerTest {
const val SCREEN_WIDTH = 2000
const val SCREEN_HEIGHT = 1000
- const val BUBBLE_BAR_WIDTH = 100
const val BUBBLE_BAR_HEIGHT = 50
}
@@ -84,14 +83,8 @@ class BubbleExpandedViewPinControllerTest {
insets = Insets.of(10, 20, 30, 40)
)
positioner.update(deviceConfig)
- positioner.bubbleBarBounds =
- Rect(
- SCREEN_WIDTH - deviceConfig.insets.right - BUBBLE_BAR_WIDTH,
- SCREEN_HEIGHT - deviceConfig.insets.bottom - BUBBLE_BAR_HEIGHT,
- SCREEN_WIDTH - deviceConfig.insets.right,
- SCREEN_HEIGHT - deviceConfig.insets.bottom
- )
-
+ positioner.bubbleBarTopOnScreen =
+ SCREEN_HEIGHT - deviceConfig.insets.bottom - BUBBLE_BAR_HEIGHT
controller = BubbleExpandedViewPinController(context, container, positioner)
testListener = TestLocationChangeListener()
controller.setListener(testListener)
@@ -247,9 +240,10 @@ class BubbleExpandedViewPinControllerTest {
private val dropTargetView: View?
get() = container.findViewById(R.id.bubble_bar_drop_target)
- private fun getExpectedDropTargetBounds(onLeft: Boolean): Rect = Rect().also {
- positioner.getBubbleBarExpandedViewBounds(onLeft, false /* isOveflowExpanded */, it)
- }
+ private fun getExpectedDropTargetBounds(onLeft: Boolean): Rect =
+ Rect().also {
+ positioner.getBubbleBarExpandedViewBounds(onLeft, false /* isOveflowExpanded */, it)
+ }
private fun runOnMainSync(runnable: Runnable) {
InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable)
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 8d24c161e3e4..aa4fb4448ac7 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -247,13 +247,11 @@
<!-- Padding for the bubble popup view contents. -->
<dimen name="bubble_popup_padding">24dp</dimen>
<!-- The size of the caption bar inset at the top of bubble bar expanded view. -->
- <dimen name="bubble_bar_expanded_view_caption_height">32dp</dimen>
+ <dimen name="bubble_bar_expanded_view_caption_height">36dp</dimen>
<!-- The width of the caption bar at the top of bubble bar expanded view. -->
- <dimen name="bubble_bar_expanded_view_caption_width">128dp</dimen>
- <!-- The height of the dots shown for the caption menu in the bubble bar expanded view.. -->
- <dimen name="bubble_bar_expanded_view_caption_dot_size">4dp</dimen>
- <!-- The spacing between the dots for the caption menu in the bubble bar expanded view.. -->
- <dimen name="bubble_bar_expanded_view_caption_dot_spacing">4dp</dimen>
+ <dimen name="bubble_bar_expanded_view_caption_width">80dp</dimen>
+ <!-- The height of the handle shown for the caption menu in the bubble bar expanded view. -->
+ <dimen name="bubble_bar_expanded_view_handle_height">4dp</dimen>
<!-- Width of the expanded bubble bar view shown when the bubble is expanded. -->
<dimen name="bubble_bar_expanded_view_width">412dp</dimen>
<!-- Minimum width of the bubble bar manage menu. -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 38c344322a30..42a4ab2e352d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1189,10 +1189,12 @@ public class BubbleController implements ConfigurationChangeListener,
* A bubble is no longer being dragged in Launcher. And was released in given location.
* Will be called only when bubble bar is expanded.
*
- * @param location location where bubble was released
+ * @param location location where bubble was released
+ * @param topOnScreen top coordinate of the bubble bar on the screen after release
*/
- public void stopBubbleDrag(BubbleBarLocation location) {
+ public void stopBubbleDrag(BubbleBarLocation location, int topOnScreen) {
mBubblePositioner.setBubbleBarLocation(location);
+ mBubblePositioner.setBubbleBarTopOnScreen(topOnScreen);
if (mBubbleData.getSelectedBubble() != null) {
mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ true);
}
@@ -1248,8 +1250,8 @@ public class BubbleController implements ConfigurationChangeListener,
* <p>This is used by external callers (launcher).
*/
@VisibleForTesting
- public void expandStackAndSelectBubbleFromLauncher(String key, Rect bubbleBarBounds) {
- mBubblePositioner.setBubbleBarBounds(bubbleBarBounds);
+ public void expandStackAndSelectBubbleFromLauncher(String key, int topOnScreen) {
+ mBubblePositioner.setBubbleBarTopOnScreen(topOnScreen);
if (BubbleOverflow.KEY.equals(key)) {
mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow());
@@ -2364,10 +2366,9 @@ public class BubbleController implements ConfigurationChangeListener,
}
@Override
- public void showBubble(String key, Rect bubbleBarBounds) {
+ public void showBubble(String key, int topOnScreen) {
mMainExecutor.execute(
- () -> mController.expandStackAndSelectBubbleFromLauncher(
- key, bubbleBarBounds));
+ () -> mController.expandStackAndSelectBubbleFromLauncher(key, topOnScreen));
}
@Override
@@ -2386,8 +2387,8 @@ public class BubbleController implements ConfigurationChangeListener,
}
@Override
- public void stopBubbleDrag(BubbleBarLocation location) {
- mMainExecutor.execute(() -> mController.stopBubbleDrag(location));
+ public void stopBubbleDrag(BubbleBarLocation location, int topOnScreen) {
+ mMainExecutor.execute(() -> mController.stopBubbleDrag(location, topOnScreen));
}
@Override
@@ -2408,9 +2409,9 @@ public class BubbleController implements ConfigurationChangeListener,
}
@Override
- public void setBubbleBarBounds(Rect bubbleBarBounds) {
+ public void updateBubbleBarTopOnScreen(int topOnScreen) {
mMainExecutor.execute(() -> {
- mBubblePositioner.setBubbleBarBounds(bubbleBarBounds);
+ mBubblePositioner.setBubbleBarTopOnScreen(topOnScreen);
if (mLayerView != null) mLayerView.updateExpandedView();
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index a35a004cdace..1e482cac0b46 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -98,7 +98,7 @@ public class BubblePositioner {
private boolean mShowingInBubbleBar;
private BubbleBarLocation mBubbleBarLocation = BubbleBarLocation.DEFAULT;
- private final Rect mBubbleBarBounds = new Rect();
+ private int mBubbleBarTopOnScreen;
public BubblePositioner(Context context, WindowManager windowManager) {
mContext = context;
@@ -846,17 +846,17 @@ public class BubblePositioner {
}
/**
- * Sets the position of the bubble bar in display coordinates.
+ * Set top coordinate of bubble bar on screen
*/
- public void setBubbleBarBounds(Rect bubbleBarBounds) {
- mBubbleBarBounds.set(bubbleBarBounds);
+ public void setBubbleBarTopOnScreen(int topOnScreen) {
+ mBubbleBarTopOnScreen = topOnScreen;
}
/**
- * Returns the display coordinates of the bubble bar.
+ * Returns the top coordinate of bubble bar on screen
*/
- public Rect getBubbleBarBounds() {
- return mBubbleBarBounds;
+ public int getBubbleBarTopOnScreen() {
+ return mBubbleBarTopOnScreen;
}
/**
@@ -908,7 +908,7 @@ public class BubblePositioner {
/** The bottom position of the expanded view when showing above the bubble bar. */
public int getExpandedViewBottomForBubbleBar() {
- return mBubbleBarBounds.top - mExpandedViewPadding;
+ return mBubbleBarTopOnScreen - mExpandedViewPadding;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 1eff149f2e91..1db556c04180 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -31,7 +31,7 @@ interface IBubbles {
oneway void unregisterBubbleListener(in IBubblesListener listener) = 2;
- oneway void showBubble(in String key, in Rect bubbleBarBounds) = 3;
+ oneway void showBubble(in String key, in int topOnScreen) = 3;
oneway void dragBubbleToDismiss(in String key) = 4;
@@ -45,7 +45,7 @@ interface IBubbles {
oneway void setBubbleBarLocation(in BubbleBarLocation location) = 9;
- oneway void setBubbleBarBounds(in Rect bubbleBarBounds) = 10;
+ oneway void updateBubbleBarTopOnScreen(in int topOnScreen) = 10;
- oneway void stopBubbleDrag(in BubbleBarLocation location) = 11;
+ oneway void stopBubbleDrag(in BubbleBarLocation location, in int topOnScreen) = 11;
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index 45ad6319bbf8..8e58db198b13 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -237,12 +237,10 @@ public class BubbleBarAnimationHelper {
private void setScaleFromBubbleBar(AnimatableScaleMatrix matrix, float scale) {
// Set the pivot point for the scale, so the view animates out from the bubble bar.
- Rect bubbleBarBounds = mPositioner.getBubbleBarBounds();
- matrix.setScale(
- scale,
- scale,
- bubbleBarBounds.centerX(),
- bubbleBarBounds.top);
+ Rect availableRect = mPositioner.getAvailableRect();
+ float pivotX = mPositioner.isBubbleBarOnLeft() ? availableRect.left : availableRect.right;
+ float pivotY = mPositioner.getBubbleBarTopOnScreen();
+ matrix.setScale(scale, scale, pivotX, pivotY);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
index 2b7a0706b4de..d54a6b002e43 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
@@ -37,15 +37,11 @@ import com.android.wm.shell.R;
*/
public class BubbleBarHandleView extends View {
private static final long COLOR_CHANGE_DURATION = 120;
-
- // The handle view is currently rendered as 3 evenly spaced dots.
- private int mDotSize;
- private int mDotSpacing;
// Path used to draw the dots
private final Path mPath = new Path();
- private @ColorInt int mHandleLightColor;
- private @ColorInt int mHandleDarkColor;
+ private final @ColorInt int mHandleLightColor;
+ private final @ColorInt int mHandleDarkColor;
private @Nullable ObjectAnimator mColorChangeAnim;
public BubbleBarHandleView(Context context) {
@@ -63,10 +59,8 @@ public class BubbleBarHandleView extends View {
public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- mDotSize = getResources().getDimensionPixelSize(
- R.dimen.bubble_bar_expanded_view_caption_dot_size);
- mDotSpacing = getResources().getDimensionPixelSize(
- R.dimen.bubble_bar_expanded_view_caption_dot_spacing);
+ final int handleHeight = getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_handle_height);
mHandleLightColor = ContextCompat.getColor(getContext(),
R.color.bubble_bar_expanded_view_handle_light);
mHandleDarkColor = ContextCompat.getColor(getContext(),
@@ -76,27 +70,13 @@ public class BubbleBarHandleView extends View {
setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
- final int handleCenterX = view.getWidth() / 2;
final int handleCenterY = view.getHeight() / 2;
- final int handleTotalWidth = mDotSize * 3 + mDotSpacing * 2;
- final int handleLeft = handleCenterX - handleTotalWidth / 2;
- final int handleTop = handleCenterY - mDotSize / 2;
- final int handleBottom = handleTop + mDotSize;
- RectF dot1 = new RectF(
- handleLeft, handleTop,
- handleLeft + mDotSize, handleBottom);
- RectF dot2 = new RectF(
- dot1.right + mDotSpacing, handleTop,
- dot1.right + mDotSpacing + mDotSize, handleBottom
- );
- RectF dot3 = new RectF(
- dot2.right + mDotSpacing, handleTop,
- dot2.right + mDotSpacing + mDotSize, handleBottom
- );
+ final int handleTop = handleCenterY - handleHeight / 2;
+ final int handleBottom = handleTop + handleHeight;
+ final int radius = handleHeight / 2;
+ RectF handle = new RectF(/* left = */ 0, handleTop, view.getWidth(), handleBottom);
mPath.reset();
- mPath.addOval(dot1, Path.Direction.CW);
- mPath.addOval(dot2, Path.Direction.CW);
- mPath.addOval(dot3, Path.Direction.CW);
+ mPath.addRoundRect(handle, radius, radius, Path.Direction.CW);
outline.setPath(mPath);
}
});
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 607a3b5423d1..2234041b8c9d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -347,7 +347,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
if (mMoving) {
final int position = mSplitLayout.getDividerPosition() + touchPos - mStartPos;
mLastDraggingPosition = position;
- mSplitLayout.updateDividerBounds(position);
+ mSplitLayout.updateDividerBounds(position, true /* shouldUseParallaxEffect */);
}
break;
case MotionEvent.ACTION_UP:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index 30eb8b5d2f05..de016d3ae400 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -31,7 +31,6 @@ import android.animation.ValueAnimator;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Configuration;
-import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -57,7 +56,13 @@ import com.android.wm.shell.common.SurfaceUtils;
import java.util.function.Consumer;
/**
- * Handles split decor like showing resizing hint for a specific split.
+ * Handles additional layers over a running task in a split pair, for example showing a veil with an
+ * app icon when the task is being resized (usually to hide weird layouts while the app is being
+ * stretched). One SplitDecorManager is initialized on each window.
+ * <br>
+ * Currently, we show a veil when:
+ * a) Task is resizing down from a fullscreen window.
+ * b) Task is being stretched past its original bounds.
*/
public class SplitDecorManager extends WindowlessWindowManager {
private static final String TAG = SplitDecorManager.class.getSimpleName();
@@ -78,7 +83,11 @@ public class SplitDecorManager extends WindowlessWindowManager {
private boolean mShown;
private boolean mIsResizing;
- private final Rect mOldBounds = new Rect();
+ /** The original bounds of the main task, captured at the beginning of a resize transition. */
+ private final Rect mOldMainBounds = new Rect();
+ /** The original bounds of the side task, captured at the beginning of a resize transition. */
+ private final Rect mOldSideBounds = new Rect();
+ /** The current bounds of the main task, mid-resize. */
private final Rect mResizingBounds = new Rect();
private final Rect mTempRect = new Rect();
private ValueAnimator mFadeAnimator;
@@ -184,29 +193,38 @@ public class SplitDecorManager extends WindowlessWindowManager {
mResizingIconView = null;
mIsResizing = false;
mShown = false;
- mOldBounds.setEmpty();
+ mOldMainBounds.setEmpty();
+ mOldSideBounds.setEmpty();
mResizingBounds.setEmpty();
}
/** Showing resizing hint. */
public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds,
Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY,
- boolean immediately) {
+ boolean immediately, float[] veilColor) {
if (mResizingIconView == null) {
return;
}
if (!mIsResizing) {
mIsResizing = true;
- mOldBounds.set(newBounds);
+ mOldMainBounds.set(newBounds);
+ mOldSideBounds.set(sideBounds);
}
mResizingBounds.set(newBounds);
mOffsetX = offsetX;
mOffsetY = offsetY;
- final boolean show =
- newBounds.width() > mOldBounds.width() || newBounds.height() > mOldBounds.height();
- final boolean update = show != mShown;
+ // Show a veil when:
+ // a) Task is resizing down from a fullscreen window.
+ // b) Task is being stretched past its original bounds.
+ final boolean isResizingDownFromFullscreen =
+ mOldSideBounds.width() <= 1 || mOldSideBounds.height() <= 1;
+ final boolean isStretchingPastOriginalBounds =
+ newBounds.width() > mOldMainBounds.width()
+ || newBounds.height() > mOldMainBounds.height();
+ final boolean showVeil = isResizingDownFromFullscreen || isStretchingPastOriginalBounds;
+ final boolean update = showVeil != mShown;
if (update && mFadeAnimator != null && mFadeAnimator.isRunning()) {
// If we need to animate and animator still running, cancel it before we ensure both
// background and icon surfaces are non null for next animation.
@@ -216,18 +234,18 @@ public class SplitDecorManager extends WindowlessWindowManager {
if (mBackgroundLeash == null) {
mBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash,
RESIZING_BACKGROUND_SURFACE_NAME, mSurfaceSession);
- t.setColor(mBackgroundLeash, getResizingBackgroundColor(resizingTask))
+ t.setColor(mBackgroundLeash, veilColor)
.setLayer(mBackgroundLeash, Integer.MAX_VALUE - 1);
}
if (mGapBackgroundLeash == null && !immediately) {
final boolean isLandscape = newBounds.height() == sideBounds.height();
- final int left = isLandscape ? mOldBounds.width() : 0;
- final int top = isLandscape ? 0 : mOldBounds.height();
+ final int left = isLandscape ? mOldMainBounds.width() : 0;
+ final int top = isLandscape ? 0 : mOldMainBounds.height();
mGapBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash,
GAP_BACKGROUND_SURFACE_NAME, mSurfaceSession);
// Fill up another side bounds area.
- t.setColor(mGapBackgroundLeash, getResizingBackgroundColor(resizingTask))
+ t.setColor(mGapBackgroundLeash, veilColor)
.setLayer(mGapBackgroundLeash, Integer.MAX_VALUE - 2)
.setPosition(mGapBackgroundLeash, left, top)
.setWindowCrop(mGapBackgroundLeash, sideBounds.width(), sideBounds.height());
@@ -251,12 +269,12 @@ public class SplitDecorManager extends WindowlessWindowManager {
if (update) {
if (immediately) {
- t.setVisibility(mBackgroundLeash, show);
- t.setVisibility(mIconLeash, show);
+ t.setVisibility(mBackgroundLeash, showVeil);
+ t.setVisibility(mIconLeash, showVeil);
} else {
- startFadeAnimation(show, false, null);
+ startFadeAnimation(showVeil, false, null);
}
- mShown = show;
+ mShown = showVeil;
}
}
@@ -309,7 +327,8 @@ public class SplitDecorManager extends WindowlessWindowManager {
mIsResizing = false;
mOffsetX = 0;
mOffsetY = 0;
- mOldBounds.setEmpty();
+ mOldMainBounds.setEmpty();
+ mOldSideBounds.setEmpty();
mResizingBounds.setEmpty();
if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
if (!mShown) {
@@ -346,14 +365,14 @@ public class SplitDecorManager extends WindowlessWindowManager {
/** Screenshot host leash and attach on it if meet some conditions */
public void screenshotIfNeeded(SurfaceControl.Transaction t) {
- if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) {
+ if (!mShown && mIsResizing && !mOldMainBounds.equals(mResizingBounds)) {
if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
mScreenshotAnimator.cancel();
} else if (mScreenshot != null) {
t.remove(mScreenshot);
}
- mTempRect.set(mOldBounds);
+ mTempRect.set(mOldMainBounds);
mTempRect.offsetTo(0, 0);
mScreenshot = ScreenshotUtils.takeScreenshot(t, mHostLeash, mTempRect,
Integer.MAX_VALUE - 1);
@@ -364,7 +383,7 @@ public class SplitDecorManager extends WindowlessWindowManager {
public void setScreenshotIfNeeded(SurfaceControl screenshot, SurfaceControl.Transaction t) {
if (screenshot == null || !screenshot.isValid()) return;
- if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) {
+ if (!mShown && mIsResizing && !mOldMainBounds.equals(mResizingBounds)) {
if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
mScreenshotAnimator.cancel();
} else if (mScreenshot != null) {
@@ -465,9 +484,4 @@ public class SplitDecorManager extends WindowlessWindowManager {
mIcon = null;
}
}
-
- private static float[] getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) {
- final int taskBgColor = taskInfo.taskDescription.getBackgroundColor();
- return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).getComponents();
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 2ea32f44a78b..8331654839c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -496,10 +496,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
* Updates bounds with the passing position. Usually used to update recording bounds while
* performing animation or dragging divider bar to resize the splits.
*/
- void updateDividerBounds(int position) {
+ void updateDividerBounds(int position, boolean shouldUseParallaxEffect) {
updateBounds(position);
mSplitLayoutHandler.onLayoutSizeChanging(this, mSurfaceEffectPolicy.mParallaxOffset.x,
- mSurfaceEffectPolicy.mParallaxOffset.y);
+ mSurfaceEffectPolicy.mParallaxOffset.y, shouldUseParallaxEffect);
}
void setDividerPosition(int position, boolean applyLayoutChange) {
@@ -647,7 +647,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
.setDuration(duration);
mDividerFlingAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
mDividerFlingAnimator.addUpdateListener(
- animation -> updateDividerBounds((int) animation.getAnimatedValue()));
+ animation -> updateDividerBounds(
+ (int) animation.getAnimatedValue(), false /* shouldUseParallaxEffect */)
+ );
mDividerFlingAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -897,7 +899,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
* @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl,
* SurfaceControl, SurfaceControl, boolean)
*/
- void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY);
+ void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY,
+ boolean shouldUseParallaxEffect);
/**
* Calls when finish resizing the split bounds.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
index f9259e79472e..e8226051b672 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
@@ -16,8 +16,6 @@
package com.android.wm.shell.common.split;
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED;
-
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -26,25 +24,18 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT
import android.app.ActivityManager;
import android.app.PendingIntent;
-import android.content.ComponentName;
import android.content.Intent;
-import android.content.pm.LauncherApps;
-import android.content.pm.ShortcutInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.Color;
import android.graphics.Rect;
-import android.os.UserHandle;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.util.ArrayUtils;
import com.android.wm.shell.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
-import java.util.Arrays;
-import java.util.List;
-
/** Helper utility class for split screen components to use. */
public class SplitScreenUtils {
/** Reverse the split position. */
@@ -137,4 +128,10 @@ public class SplitScreenUtils {
return isLandscape;
}
}
+
+ /** Returns the specified background color that matches a RunningTaskInfo. */
+ public static Color getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) {
+ final int taskBgColor = taskInfo.taskDescription.getBackgroundColor();
+ return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index fb0a1ab3062e..4e9e8f97620c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -100,6 +100,7 @@ import com.android.wm.shell.unfold.qualifier.UnfoldShellTransition;
import com.android.wm.shell.unfold.qualifier.UnfoldTransition;
import com.android.wm.shell.windowdecor.CaptionWindowDecorViewModel;
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel;
+import com.android.wm.shell.windowdecor.ResizeHandleSizeRepository;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import dagger.Binds;
@@ -220,7 +221,8 @@ public abstract class WMShellModule {
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ ResizeHandleSizeRepository resizeHandleSizeRepository) {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return new DesktopModeWindowDecorViewModel(
context,
@@ -237,7 +239,8 @@ public abstract class WMShellModule {
syncQueue,
transitions,
desktopTasksController,
- rootTaskDisplayAreaOrganizer);
+ rootTaskDisplayAreaOrganizer,
+ resizeHandleSizeRepository);
}
return new CaptionWindowDecorViewModel(
context,
@@ -247,7 +250,8 @@ public abstract class WMShellModule {
displayController,
rootTaskDisplayAreaOrganizer,
syncQueue,
- transitions);
+ transitions,
+ resizeHandleSizeRepository);
}
//
@@ -529,7 +533,8 @@ public abstract class WMShellModule {
exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler,
dragToDesktopTransitionHandler, desktopModeTaskRepository,
desktopModeLoggerTransitionObserver, launchAdjacentController,
- recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter);
+ recentsTransitionHandler, multiInstanceHelper,
+ mainExecutor, desktopTasksLimiter);
}
@WMSingleton
@@ -622,6 +627,12 @@ public abstract class WMShellModule {
return new DesktopModeEventLogger();
}
+ @WMSingleton
+ @Provides
+ static ResizeHandleSizeRepository provideResizeHandleSizeRepository() {
+ return new ResizeHandleSizeRepository();
+ }
+
//
// Drag and drop
//
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 2dc4573b4921..79a209016f59 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
@@ -906,7 +906,8 @@ class DesktopTasksController(
task.taskId
)
return WindowContainerTransaction().also { wct ->
- addMoveToFullscreenChanges(wct, task)
+ bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId)
+ wct.reorder(task.token, true)
}
}
// Desktop Mode is showing and we're launching a new Task - we might need to minimize
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index 59d696918448..4bb10dfdf8c6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -22,11 +22,11 @@ import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS;
import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.common.split.SplitScreenUtils.getResizingBackgroundColor;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
@@ -41,7 +41,6 @@ import android.app.StatusBarManager;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.Region;
@@ -278,7 +277,7 @@ public class DragLayout extends LinearLayout
final int activityType = taskInfo1.getActivityType();
if (activityType == ACTIVITY_TYPE_STANDARD) {
Drawable icon1 = mIconProvider.getIcon(taskInfo1.topActivityInfo);
- int bgColor1 = getResizingBackgroundColor(taskInfo1);
+ int bgColor1 = getResizingBackgroundColor(taskInfo1).toArgb();
mDropZoneView1.setAppInfo(bgColor1, icon1);
mDropZoneView2.setAppInfo(bgColor1, icon1);
updateDropZoneSizes(null, null); // passing null splits the views evenly
@@ -298,10 +297,10 @@ public class DragLayout extends LinearLayout
mSplitScreenController.getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
if (topOrLeftTask != null && bottomOrRightTask != null) {
Drawable topOrLeftIcon = mIconProvider.getIcon(topOrLeftTask.topActivityInfo);
- int topOrLeftColor = getResizingBackgroundColor(topOrLeftTask);
+ int topOrLeftColor = getResizingBackgroundColor(topOrLeftTask).toArgb();
Drawable bottomOrRightIcon = mIconProvider.getIcon(
bottomOrRightTask.topActivityInfo);
- int bottomOrRightColor = getResizingBackgroundColor(bottomOrRightTask);
+ int bottomOrRightColor = getResizingBackgroundColor(bottomOrRightTask).toArgb();
mDropZoneView1.setAppInfo(topOrLeftColor, topOrLeftIcon);
mDropZoneView2.setAppInfo(bottomOrRightColor, bottomOrRightIcon);
}
@@ -556,11 +555,6 @@ public class DragLayout extends LinearLayout
}
}
- private static int getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) {
- final int taskBgColor = taskInfo.taskDescription.getBackgroundColor();
- return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb();
- }
-
/**
* Dumps information about this drag layout.
*/
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 8e97068dce84..4299088a51f0 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
@@ -43,6 +43,7 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
import static com.android.wm.shell.common.split.SplitScreenConstants.splitPositionToString;
+import static com.android.wm.shell.common.split.SplitScreenUtils.getResizingBackgroundColor;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
@@ -2388,14 +2389,20 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
@Override
- public void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY) {
+ public void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY,
+ boolean shouldUseParallaxEffect) {
final SurfaceControl.Transaction t = mTransactionPool.acquire();
t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
- updateSurfaceBounds(layout, t, true /* applyResizingOffset */);
+ updateSurfaceBounds(layout, t, shouldUseParallaxEffect);
getMainStageBounds(mTempRect1);
getSideStageBounds(mTempRect2);
- mMainStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately);
- mSideStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately);
+ // TODO (b/307490004): "commonColor" below is a temporary fix to ensure the colors on both
+ // sides match. When b/307490004 is fixed, this code can be reverted.
+ float[] commonColor = getResizingBackgroundColor(mSideStage.mRootTaskInfo).getComponents();
+ mMainStage.onResizing(
+ mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately, commonColor);
+ mSideStage.onResizing(
+ mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately, commonColor);
t.apply();
mTransactionPool.release(t);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index f77c80d2ff3b..0f3d6cade95a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -314,10 +314,10 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX,
- int offsetY, boolean immediately) {
+ int offsetY, boolean immediately, float[] veilColor) {
if (mSplitDecorManager != null && mRootTaskInfo != null) {
mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, sideBounds, t, offsetX,
- offsetY, immediately);
+ offsetY, immediately, veilColor);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index e85cb6400000..bfa163cb2860 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -63,6 +63,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private final SyncTransactionQueue mSyncQueue;
private final Transitions mTransitions;
+ private final ResizeHandleSizeRepository mResizeHandleSizeRepository;
private TaskOperations mTaskOperations;
private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
@@ -75,7 +76,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
DisplayController displayController,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
SyncTransactionQueue syncQueue,
- Transitions transitions) {
+ Transitions transitions,
+ ResizeHandleSizeRepository resizeHandleSizeRepository) {
mContext = context;
mMainHandler = mainHandler;
mMainChoreographer = mainChoreographer;
@@ -84,6 +86,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mSyncQueue = syncQueue;
mTransitions = transitions;
+ mResizeHandleSizeRepository = resizeHandleSizeRepository;
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
mTaskOperations = new TaskOperations(null, mContext, mSyncQueue);
}
@@ -231,7 +234,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
taskSurface,
mMainHandler,
mMainChoreographer,
- mSyncQueue);
+ mSyncQueue,
+ mResizeHandleSizeRepository);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
final FluidResizeTaskPositioner taskPositioner =
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 6671391efdeb..1be3b02e1465 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
@@ -16,11 +16,11 @@
package com.android.wm.shell.windowdecor;
-import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
-import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
-import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize;
+import static com.android.wm.shell.windowdecor.ResizeHandleSizeRepository.getFineResizeCornerPixels;
+import static com.android.wm.shell.windowdecor.ResizeHandleSizeRepository.getLargeResizeCornerPixels;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration;
import android.app.WindowConfiguration.WindowingMode;
@@ -45,6 +45,8 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
+import java.util.function.Consumer;
+
/**
* Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
* {@link CaptionWindowDecorViewModel}. The caption bar contains a back button, minimize button,
@@ -58,12 +60,28 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
private View.OnClickListener mOnCaptionButtonClickListener;
private View.OnTouchListener mOnCaptionTouchListener;
private DragPositioningCallback mDragPositioningCallback;
+ // Listener for handling drag resize events. Will be null if the task cannot be resized.
+ @Nullable
private DragResizeInputListener mDragResizeListener;
private DragDetector mDragDetector;
private RelayoutParams mRelayoutParams = new RelayoutParams();
private final RelayoutResult<WindowDecorLinearLayout> mResult =
new RelayoutResult<>();
+ private final ResizeHandleSizeRepository mResizeHandleSizeRepository;
+ private final Consumer<ResizeHandleSizeRepository> mResizeHandleSizeChangedFunction =
+ (ResizeHandleSizeRepository sizeRepository) -> {
+ if (mDragResizeListener == null) {
+ return;
+ }
+ final Resources res = mResult.mRootView.getResources();
+ mDragResizeListener.setGeometry(
+ new DragResizeWindowGeometry(0 /* taskCornerRadius */,
+ new Size(mResult.mWidth, mResult.mHeight),
+ sizeRepository.getResizeEdgeHandlePixels(res),
+ getFineResizeCornerPixels(res), getLargeResizeCornerPixels(res)),
+ mDragDetector.getTouchSlop());
+ };
CaptionWindowDecoration(
Context context,
@@ -73,13 +91,16 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
SurfaceControl taskSurface,
Handler handler,
Choreographer choreographer,
- SyncTransactionQueue syncQueue) {
+ SyncTransactionQueue syncQueue,
+ ResizeHandleSizeRepository resizeHandleSizeRepository) {
super(context, displayController, taskOrganizer, taskInfo, taskSurface,
taskInfo.getConfiguration());
mHandler = handler;
mChoreographer = choreographer;
mSyncQueue = syncQueue;
+ mResizeHandleSizeRepository = resizeHandleSizeRepository;
+ mResizeHandleSizeRepository.registerSizeChangeFunction(mResizeHandleSizeChangedFunction);
}
void setCaptionListeners(
@@ -238,10 +259,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
.getScaledTouchSlop();
mDragDetector.setTouchSlop(touchSlop);
- final Resources res = mResult.mRootView.getResources();
- mDragResizeListener.setGeometry(new DragResizeWindowGeometry(0 /* taskCornerRadius */,
- new Size(mResult.mWidth, mResult.mHeight), getResizeEdgeHandleSize(res),
- getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop);
+ mResizeHandleSizeChangedFunction.accept(mResizeHandleSizeRepository);
}
/**
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 9afb057ffbe5..10ab13a74042 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
@@ -149,6 +149,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
new DesktopModeKeyguardChangeListener();
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private final DisplayInsetsController mDisplayInsetsController;
+ private final ResizeHandleSizeRepository mResizeHandleSizeRepository;
private final Region mExclusionRegion = Region.obtain();
private boolean mInImmersiveMode;
@@ -181,7 +182,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ ResizeHandleSizeRepository resizeHandleSizeRepository
) {
this(
context,
@@ -202,7 +204,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
new InputMonitorFactory(),
SurfaceControl.Transaction::new,
rootTaskDisplayAreaOrganizer,
- new SparseArray<>());
+ new SparseArray<>(),
+ resizeHandleSizeRepository);
}
@VisibleForTesting
@@ -225,7 +228,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
InputMonitorFactory inputMonitorFactory,
Supplier<SurfaceControl.Transaction> transactionFactory,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
- SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId) {
+ SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId,
+ ResizeHandleSizeRepository resizeHandleSizeRepository) {
mContext = context;
mMainExecutor = shellExecutor;
mMainHandler = mainHandler;
@@ -246,6 +250,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mInputManager = mContext.getSystemService(InputManager.class);
mWindowDecorByTaskId = windowDecorByTaskId;
+ mResizeHandleSizeRepository = resizeHandleSizeRepository;
shellInit.addInitCallback(this::onInit, this);
}
@@ -1060,7 +1065,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mMainHandler,
mMainChoreographer,
mSyncQueue,
- mRootTaskDisplayAreaOrganizer);
+ mRootTaskDisplayAreaOrganizer,
+ mResizeHandleSizeRepository);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
final DragPositioningCallback dragPositioningCallback;
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 4d4dc3c72420..18c31139575c 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
@@ -24,11 +24,11 @@ import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
-import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
-import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
-import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize;
+import static com.android.wm.shell.windowdecor.ResizeHandleSizeRepository.getFineResizeCornerPixels;
+import static com.android.wm.shell.windowdecor.ResizeHandleSizeRepository.getLargeResizeCornerPixels;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
@@ -60,6 +60,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.IconProvider;
+import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -75,6 +76,7 @@ import com.android.wm.shell.windowdecor.viewholder.DesktopModeWindowDecorationVi
import kotlin.Unit;
+import java.util.function.Function;
import java.util.function.Supplier;
/**
@@ -96,6 +98,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private View.OnLongClickListener mOnCaptionLongClickListener;
private View.OnGenericMotionListener mOnCaptionGenericMotionListener;
private DragPositioningCallback mDragPositioningCallback;
+ // Listener for handling drag resize events. Will be null if the task cannot be resized.
+ @Nullable
private DragResizeInputListener mDragResizeListener;
private DragDetector mDragDetector;
@@ -118,6 +122,19 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+ private final ResizeHandleSizeRepository mResizeHandleSizeRepository;
+ private final Function<ResizeHandleSizeRepository, Boolean> mResizeHandleSizeChangedFunction =
+ (ResizeHandleSizeRepository sizeRepository) -> {
+ final Resources res = mResult.mRootView.getResources();
+ return mDragResizeListener == null || mDragResizeListener.setGeometry(
+ new DragResizeWindowGeometry(mRelayoutParams.mCornerRadius,
+ new Size(mResult.mWidth, mResult.mHeight),
+ sizeRepository.getResizeEdgeHandlePixels(res),
+ getFineResizeCornerPixels(res),
+ getLargeResizeCornerPixels(res)),
+ mDragDetector.getTouchSlop());
+ };
+
DesktopModeWindowDecoration(
Context context,
DisplayController displayController,
@@ -128,12 +145,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
Handler handler,
Choreographer choreographer,
SyncTransactionQueue syncQueue,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ ResizeHandleSizeRepository resizeHandleSizeRepository) {
this (context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
handler, choreographer, syncQueue, rootTaskDisplayAreaOrganizer,
- SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
- WindowContainerTransaction::new, SurfaceControl::new,
- new SurfaceControlViewHostFactory() {});
+ resizeHandleSizeRepository, SurfaceControl.Builder::new,
+ SurfaceControl.Transaction::new, WindowContainerTransaction::new,
+ SurfaceControl::new, new SurfaceControlViewHostFactory() {});
}
DesktopModeWindowDecoration(
@@ -147,6 +165,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
Choreographer choreographer,
SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ ResizeHandleSizeRepository resizeHandleSizeRepository,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
@@ -160,6 +179,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mChoreographer = choreographer;
mSyncQueue = syncQueue;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+ mResizeHandleSizeRepository = resizeHandleSizeRepository;
+ mResizeHandleSizeRepository.registerSizeChangeFunction(
+ mResizeHandleSizeChangedFunction::apply);
}
void setCaptionListeners(
@@ -305,11 +327,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
// If either task geometry or position have changed, update this task's
// exclusion region listener
- final Resources res = mResult.mRootView.getResources();
- if (mDragResizeListener.setGeometry(
- new DragResizeWindowGeometry(mRelayoutParams.mCornerRadius,
- new Size(mResult.mWidth, mResult.mHeight), getResizeEdgeHandleSize(res),
- getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop)
+ if (mResizeHandleSizeChangedFunction.apply(mResizeHandleSizeRepository)
|| !mTaskInfo.positionInParent.equals(mPositionInParent)) {
updateExclusionRegion();
}
@@ -332,13 +350,16 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
boolean applyStartTransactionOnDraw,
boolean shouldSetTaskPositionAndCrop) {
final int captionLayoutId = getDesktopModeWindowDecorLayoutId(taskInfo.getWindowingMode());
+ final boolean isAppHeader =
+ captionLayoutId == R.layout.desktop_mode_app_controls_window_decor;
+ final boolean isAppHandle = captionLayoutId == R.layout.desktop_mode_focused_window_decor;
relayoutParams.reset();
relayoutParams.mRunningTaskInfo = taskInfo;
relayoutParams.mLayoutResId = captionLayoutId;
relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode());
relayoutParams.mCaptionWidthId = getCaptionWidthId(relayoutParams.mLayoutResId);
- if (captionLayoutId == R.layout.desktop_mode_app_controls_window_decor) {
+ if (isAppHeader) {
if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) {
// If the app is requesting to customize the caption bar, allow input to fall
// through to the windows below so that the app can respond to input events on
@@ -359,7 +380,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
controlsElement.mWidthResId = R.dimen.desktop_mode_customizable_caption_margin_end;
controlsElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.END;
relayoutParams.mOccludingCaptionElements.add(controlsElement);
- } else if (captionLayoutId == R.layout.desktop_mode_focused_window_decor) {
+ } else if (isAppHandle) {
// The focused decor (fullscreen/split) does not need to handle input because input in
// the App Handle is handled by the InputMonitor in DesktopModeWindowDecorViewModel.
relayoutParams.mInputFeatures
@@ -372,19 +393,25 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
relayoutParams.mSetTaskPositionAndCrop = shouldSetTaskPositionAndCrop;
- // The configuration used to lay out the window decoration. The system context's config is
- // used when the task density has been overridden to a custom density so that the resources
- // and views of the decoration aren't affected and match the rest of the System UI, if not
- // then just use the task's configuration. A copy is made instead of using the original
- // reference so that the configuration isn't mutated on config changes and diff checks can
- // be made in WindowDecoration#relayout using the pre/post-relayout configuration.
- // See b/301119301.
+
+ // The configuration used to layout the window decoration. A copy is made instead of using
+ // the original reference so that the configuration isn't mutated on config changes and
+ // diff checks can be made in WindowDecoration#relayout using the pre/post-relayout
+ // configuration. See b/301119301.
// TODO(b/301119301): consider moving the config data needed for diffs to relayout params
// instead of using a whole Configuration as a parameter.
final Configuration windowDecorConfig = new Configuration();
- windowDecorConfig.setTo(DesktopTasksController.isDesktopDensityOverrideSet()
- ? context.getResources().getConfiguration() // Use system context.
- : taskInfo.configuration); // Use task configuration.
+ if (Flags.enableAppHeaderWithTaskDensity() && isAppHeader) {
+ // Should match the density of the task. The task may have had its density overridden
+ // to be different that SysUI's.
+ windowDecorConfig.setTo(taskInfo.configuration);
+ } else if (DesktopTasksController.isDesktopDensityOverrideSet()) {
+ // The task has had its density overridden, but keep using the system's density to
+ // layout the header.
+ windowDecorConfig.setTo(context.getResources().getConfiguration());
+ } else {
+ windowDecorConfig.setTo(taskInfo.configuration);
+ }
relayoutParams.mWindowDecorConfig = windowDecorConfig;
if (DesktopModeStatus.useRoundedCorners()) {
@@ -936,7 +963,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
Handler handler,
Choreographer choreographer,
SyncTransactionQueue syncQueue,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ ResizeHandleSizeRepository resizeHandleSizeRepository) {
final Configuration windowDecorConfig =
DesktopTasksController.isDesktopDensityOverrideSet()
? context.getResources().getConfiguration() // Use system context
@@ -951,7 +979,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
handler,
choreographer,
syncQueue,
- rootTaskDisplayAreaOrganizer);
+ rootTaskDisplayAreaOrganizer,
+ resizeHandleSizeRepository);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
index da268988bac7..a3b0a7122752 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
@@ -119,6 +119,10 @@ class DragDetector {
mTouchSlop = touchSlop;
}
+ int getTouchSlop() {
+ return mTouchSlop;
+ }
+
private void resetState() {
mIsDragEvent = false;
mInputDownPoint.set(0, 0);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
index 4f513f0a0fd8..80d60d4fbdd0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
@@ -26,7 +26,6 @@ import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED;
import android.annotation.NonNull;
-import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
@@ -36,8 +35,6 @@ import android.view.MotionEvent;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import com.android.wm.shell.R;
-
import java.util.Objects;
/**
@@ -63,6 +60,10 @@ final class DragResizeWindowGeometry {
// Extra-large edge bounds for logging to help debug when an edge resize is ignored.
private final @Nullable TaskEdges mDebugTaskEdges;
+ /**
+ * Constructs an instance representing the drag resize touch input regions, where all sizes
+ * are represented in pixels.
+ */
DragResizeWindowGeometry(int taskCornerRadius, @NonNull Size taskSize,
int resizeHandleThickness, int fineCornerSize, int largeCornerSize) {
mTaskCornerRadius = taskCornerRadius;
@@ -82,31 +83,6 @@ final class DragResizeWindowGeometry {
}
/**
- * Returns the resource value to use for the resize handle on the edge of the window.
- */
- static int getResizeEdgeHandleSize(@NonNull Resources res) {
- return enableWindowingEdgeDragResize()
- ? res.getDimensionPixelSize(R.dimen.desktop_mode_edge_handle)
- : res.getDimensionPixelSize(R.dimen.freeform_resize_handle);
- }
-
- /**
- * Returns the resource value to use for course input, such as touch, that benefits from a large
- * square on each of the window's corners.
- */
- static int getLargeResizeCornerSize(@NonNull Resources res) {
- return res.getDimensionPixelSize(R.dimen.desktop_mode_corner_resize_large);
- }
-
- /**
- * Returns the resource value to use for fine input, such as stylus, that can use a smaller
- * square on each of the window's corners.
- */
- static int getFineResizeCornerSize(@NonNull Resources res) {
- return res.getDimensionPixelSize(R.dimen.freeform_resize_corner);
- }
-
- /**
* Returns the size of the task this geometry is calculated for.
*/
@NonNull Size getTaskSize() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepository.kt
new file mode 100644
index 000000000000..be7a301ec4c3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepository.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor
+
+import android.content.res.Resources
+import com.android.window.flags.Flags.enableWindowingEdgeDragResize
+import com.android.wm.shell.R
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.util.KtProtoLog
+import java.util.function.Consumer
+
+/** Repository for desktop mode drag resize handle sizes. */
+class ResizeHandleSizeRepository {
+ private val TAG = "ResizeHandleSizeRepository"
+ private var edgeResizeHandleSizePixels: Int? = null
+ private var sizeChangeFunctions: MutableList<Consumer<ResizeHandleSizeRepository>> =
+ mutableListOf()
+
+ /**
+ * Resets the window edge resize handle size if necessary.
+ */
+ fun resetResizeEdgeHandlePixels() {
+ if (enableWindowingEdgeDragResize() && edgeResizeHandleSizePixels != null) {
+ edgeResizeHandleSizePixels = null
+ applyToAll()
+ }
+ }
+
+ /**
+ * Sets the window edge resize handle to the given size in pixels.
+ */
+ fun setResizeEdgeHandlePixels(sizePixels: Int) {
+ if (enableWindowingEdgeDragResize()) {
+ KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "$TAG: Set edge handle size to $sizePixels")
+ if (edgeResizeHandleSizePixels != null && edgeResizeHandleSizePixels == sizePixels) {
+ // Skip updating since override is the same size
+ return
+ }
+ edgeResizeHandleSizePixels = sizePixels
+ applyToAll()
+ } else {
+ KtProtoLog.d(
+ WM_SHELL_DESKTOP_MODE,
+ "$TAG: Can't set edge handle size to $sizePixels since " +
+ "enable_windowing_edge_drag_resize disabled"
+ )
+ }
+ }
+
+ /**
+ * Returns the resource value, in pixels, to use for the resize handle on the edge of the
+ * window.
+ */
+ fun getResizeEdgeHandlePixels(res: Resources): Int {
+ try {
+ return if (enableWindowingEdgeDragResize()) {
+ val resPixelSize = res.getDimensionPixelSize(R.dimen.desktop_mode_edge_handle)
+ val size = edgeResizeHandleSizePixels ?: resPixelSize
+ KtProtoLog.d(
+ WM_SHELL_DESKTOP_MODE,
+ "$TAG: Get edge handle size of $size from (vs base value $resPixelSize)"
+ )
+ size
+ } else {
+ KtProtoLog.d(
+ WM_SHELL_DESKTOP_MODE,
+ "$TAG: Get edge handle size from freeform since flag is disabled"
+ )
+ res.getDimensionPixelSize(R.dimen.freeform_resize_handle)
+ }
+ } catch (e: Resources.NotFoundException) {
+ KtProtoLog.e(WM_SHELL_DESKTOP_MODE, "$TAG: Unable to get edge handle size", e)
+ return 0
+ }
+ }
+
+ /** Register function to run when the resize handle size changes. */
+ fun registerSizeChangeFunction(function: Consumer<ResizeHandleSizeRepository>) {
+ sizeChangeFunctions.add(function)
+ }
+
+ private fun applyToAll() {
+ for (f in sizeChangeFunctions) {
+ f.accept(this)
+ }
+ }
+
+ companion object {
+ private val TAG = "ResizeHandleSizeRepositoryCompanion"
+
+ /**
+ * Returns the resource value in pixels to use for course input, such as touch, that
+ * benefits from a large square on each of the window's corners.
+ */
+ @JvmStatic
+ fun getLargeResizeCornerPixels(res: Resources): Int {
+ return try {
+ res.getDimensionPixelSize(R.dimen.desktop_mode_corner_resize_large)
+ } catch (e: Resources.NotFoundException) {
+ KtProtoLog.e(WM_SHELL_DESKTOP_MODE, "$TAG: Unable to get large corner size", e)
+ 0
+ }
+ }
+
+ /**
+ * Returns the resource value, in pixels, to use for fine input, such as stylus, that can
+ * use a smaller square on each of the window's corners.
+ */
+ @JvmStatic
+ fun getFineResizeCornerPixels(res: Resources): Int {
+ return try {
+ res.getDimensionPixelSize(R.dimen.freeform_resize_corner)
+ } catch (e: Resources.NotFoundException) {
+ KtProtoLog.e(WM_SHELL_DESKTOP_MODE, "$TAG: Unable to get fine corner size", e)
+ 0
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 8de60b7acc91..cfe8e07aa6e5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -26,6 +26,7 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_STA
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
@@ -115,9 +116,9 @@ public class SplitLayoutTests extends ShellTestCase {
@Test
public void testUpdateDivideBounds() {
- mSplitLayout.updateDividerBounds(anyInt());
+ mSplitLayout.updateDividerBounds(anyInt(), anyBoolean());
verify(mSplitLayoutHandler).onLayoutSizeChanging(any(SplitLayout.class), anyInt(),
- anyInt());
+ anyInt(), anyBoolean());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index f67da5573b7d..14f57bd41e23 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -1072,7 +1072,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- fun handleRequest_freeformTask_freeformNotVisible_returnSwitchToFullscreenWCT() {
+ fun handleRequest_freeformTask_freeformNotVisible_reorderedToTop() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
val freeformTask1 = setUpFreeformTask()
@@ -1084,30 +1084,32 @@ class DesktopTasksControllerTest : ShellTestCase() {
Binder(),
createTransition(freeformTask2, type = TRANSIT_TO_FRONT)
)
- assertThat(result?.changes?.get(freeformTask2.token.asBinder())?.windowingMode)
- .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
+
+ assertThat(result?.hierarchyOps?.size).isEqualTo(2)
+ result!!.assertReorderAt(1, freeformTask2, toTop = true)
}
@Test
- fun handleRequest_freeformTask_noOtherTasks_returnSwitchToFullscreenWCT() {
+ fun handleRequest_freeformTask_noOtherTasks_reorderedToTop() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
val task = createFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task))
- assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
- .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
+
+ assertThat(result?.hierarchyOps?.size).isEqualTo(1)
+ result!!.assertReorderAt(0, task, toTop = true)
}
@Test
- fun handleRequest_freeformTask_freeformOnOtherDisplayOnly_returnSwitchToFullscreenWCT() {
+ fun handleRequest_freeformTask_freeformOnOtherDisplayOnly_reorderedToTop() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
- createFreeformTask(displayId = SECOND_DISPLAY)
+ val taskSecondDisplay = createFreeformTask(displayId = SECOND_DISPLAY)
val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay))
- assertThat(result?.changes?.get(taskDefaultDisplay.token.asBinder())?.windowingMode)
- .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
+ assertThat(result?.hierarchyOps?.size).isEqualTo(1)
+ result!!.assertReorderAt(0, taskDefaultDisplay, toTop = true)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index aa2cee79fcfc..282495de0fc1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -121,6 +121,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
@Mock private lateinit var mockShellController: ShellController
@Mock private lateinit var mockShellExecutor: ShellExecutor
@Mock private lateinit var mockRootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+ @Mock private lateinit var mockResizeHandleSizeRepository: ResizeHandleSizeRepository
@Mock private lateinit var mockShellCommandHandler: ShellCommandHandler
@Mock private lateinit var mockWindowManager: IWindowManager
@@ -156,7 +157,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
mockInputMonitorFactory,
transactionFactory,
mockRootTaskDisplayAreaOrganizer,
- windowDecorByTaskIdSpy
+ windowDecorByTaskIdSpy,
+ mockResizeHandleSizeRepository
)
whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
@@ -197,7 +199,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
mockMainHandler,
mockMainChoreographer,
mockSyncQueue,
- mockRootTaskDisplayAreaOrganizer
+ mockRootTaskDisplayAreaOrganizer,
+ mockResizeHandleSizeRepository
)
verify(decoration).close()
}
@@ -221,7 +224,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
mockMainHandler,
mockMainChoreographer,
mockSyncQueue,
- mockRootTaskDisplayAreaOrganizer
+ mockRootTaskDisplayAreaOrganizer,
+ mockResizeHandleSizeRepository
)
task.setWindowingMode(WINDOWING_MODE_FREEFORM)
@@ -236,7 +240,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
mockMainHandler,
mockMainChoreographer,
mockSyncQueue,
- mockRootTaskDisplayAreaOrganizer
+ mockRootTaskDisplayAreaOrganizer,
+ mockResizeHandleSizeRepository
)
}
@@ -296,7 +301,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
onTaskChanging(task)
verify(mockDesktopModeWindowDecorFactory, never())
- .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+ .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any())
}
@Test
@@ -309,7 +314,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
onTaskOpening(task)
verify(mockDesktopModeWindowDecorFactory, never())
- .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+ .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any())
}
@Test
@@ -406,7 +411,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
onTaskOpening(task)
verify(mockDesktopModeWindowDecorFactory, never())
- .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+ .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any())
} finally {
mockitoSession.finishMocking()
}
@@ -430,7 +435,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
onTaskOpening(task)
verify(mockDesktopModeWindowDecorFactory)
- .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+ .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any())
} finally {
mockitoSession.finishMocking()
}
@@ -453,7 +458,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
onTaskOpening(task)
verify(mockDesktopModeWindowDecorFactory)
- .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+ .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any())
} finally {
mockitoSession.finishMocking()
}
@@ -497,7 +502,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
val decoration = mock(DesktopModeWindowDecoration::class.java)
whenever(
mockDesktopModeWindowDecorFactory.create(
- any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+ any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any())
).thenReturn(decoration)
decoration.mTaskInfo = task
whenever(decoration.isFocused).thenReturn(task.isFocused)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 608f74b95280..cff93137ba45 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.windowdecor;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND;
import static com.google.common.truth.Truth.assertThat;
@@ -39,6 +40,9 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Handler;
import android.os.SystemProperties;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.view.Choreographer;
@@ -51,6 +55,7 @@ import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
+import com.android.window.flags.Flags;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
@@ -61,6 +66,7 @@ import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams;
import org.junit.Before;
import org.junit.BeforeClass;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -83,6 +89,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
private static final String USE_ROUNDED_CORNERS_SYSPROP_KEY =
"persist.wm.debug.desktop_use_rounded_corners";
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
@Mock
private DisplayController mMockDisplayController;
@Mock
@@ -107,6 +115,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory;
@Mock
private TypedArray mMockRoundedCornersRadiusArray;
+ @Mock
+ private ResizeHandleSizeRepository mMockResizeHandleSizeRepository;
private final Configuration mConfiguration = new Configuration();
@@ -175,6 +185,48 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY)
+ public void updateRelayoutParams_appHeader_usesTaskDensity() {
+ final int systemDensity = mTestableContext.getOrCreateTestableResources().getResources()
+ .getConfiguration().densityDpi;
+ final int customTaskDensity = systemDensity + 300;
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ taskInfo.configuration.densityDpi = customTaskDensity;
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false);
+
+ assertThat(relayoutParams.mWindowDecorConfig.densityDpi).isEqualTo(customTaskDensity);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY)
+ public void updateRelayoutParams_appHeader_usesSystemDensity() {
+ final int systemDensity = mTestableContext.getOrCreateTestableResources().getResources()
+ .getConfiguration().densityDpi;
+ final int customTaskDensity = systemDensity + 300;
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ taskInfo.configuration.densityDpi = customTaskDensity;
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false);
+
+ assertThat(relayoutParams.mWindowDecorConfig.densityDpi).isEqualTo(systemDensity);
+ }
+
+ @Test
public void updateRelayoutParams_freeformAndTransparentAppearance_allowsInputFallthrough() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
@@ -296,8 +348,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
return new DesktopModeWindowDecoration(mContext, mMockDisplayController,
mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl, mConfiguration,
mMockHandler, mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer,
- SurfaceControl.Builder::new, mMockTransactionSupplier,
- WindowContainerTransaction::new, SurfaceControl::new,
+ mMockResizeHandleSizeRepository, SurfaceControl.Builder::new,
+ mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new,
mMockSurfaceControlViewHostFactory);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
index 54645083eca8..62fb1c441118 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
@@ -27,10 +27,7 @@ import static com.google.common.truth.Truth.assertThat;
import android.annotation.NonNull;
import android.graphics.Point;
import android.graphics.Region;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.util.Size;
@@ -74,7 +71,7 @@ public class DragResizeWindowGeometryTests {
TASK_SIZE.getHeight() + EDGE_RESIZE_THICKNESS / 2);
@Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
/**
* Check that both groups of objects satisfy equals/hashcode within each group, and that each
@@ -147,8 +144,8 @@ public class DragResizeWindowGeometryTests {
* capture all eligible input regardless of source (touch or cursor).
*/
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
public void testRegionUnion_edgeDragResizeEnabled_containsLargeCorners() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE);
Region region = new Region();
GEOMETRY.union(region);
// Make sure we're choosing a point outside of any debug region buffer.
@@ -164,8 +161,8 @@ public class DragResizeWindowGeometryTests {
* size.
*/
@Test
- @RequiresFlagsDisabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
public void testRegionUnion_edgeDragResizeDisabled_containsFineCorners() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE);
Region region = new Region();
GEOMETRY.union(region);
final int cornerRadius = DragResizeWindowGeometry.DEBUG
@@ -176,16 +173,16 @@ public class DragResizeWindowGeometryTests {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
public void testCalculateControlType_edgeDragResizeEnabled_edges() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE);
// The input source (touch or cursor) shouldn't impact the edge resize size.
validateCtrlTypeForEdges(/* isTouch= */ false);
validateCtrlTypeForEdges(/* isTouch= */ true);
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
public void testCalculateControlType_edgeDragResizeDisabled_edges() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE);
// Edge resizing is not supported when the flag is disabled.
validateCtrlTypeForEdges(/* isTouch= */ false);
validateCtrlTypeForEdges(/* isTouch= */ false);
@@ -203,8 +200,8 @@ public class DragResizeWindowGeometryTests {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
public void testCalculateControlType_edgeDragResizeEnabled_corners() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE);
final TestPoints fineTestPoints = new TestPoints(TASK_SIZE, FINE_CORNER_SIZE / 2);
final TestPoints largeCornerTestPoints = new TestPoints(TASK_SIZE, LARGE_CORNER_SIZE / 2);
@@ -226,8 +223,8 @@ public class DragResizeWindowGeometryTests {
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
public void testCalculateControlType_edgeDragResizeDisabled_corners() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE);
final TestPoints fineTestPoints = new TestPoints(TASK_SIZE, FINE_CORNER_SIZE / 2);
final TestPoints largeCornerTestPoints = new TestPoints(TASK_SIZE, LARGE_CORNER_SIZE / 2);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepositoryParameterizedTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepositoryParameterizedTests.kt
new file mode 100644
index 000000000000..a9fddc623b96
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepositoryParameterizedTests.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor
+
+import android.content.Context
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags
+import java.util.function.Consumer
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameter
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+/**
+ * Tests for {@link ResizeHandleSizeRepository}.
+ *
+ * Build/Install/Run: atest WMShellUnitTests:ResizeHandleSizeRepositoryParameterizedTests
+ */
+@SmallTest
+@RunWith(Parameterized::class)
+class ResizeHandleSizeRepositoryParameterizedTests {
+ private val resources = ApplicationProvider.getApplicationContext<Context>().resources
+ private val resizeHandleSizeRepository = ResizeHandleSizeRepository()
+ @Mock private lateinit var mockSizeChangeFunctionOne: Consumer<ResizeHandleSizeRepository>
+ @Mock private lateinit var mockSizeChangeFunctionTwo: Consumer<ResizeHandleSizeRepository>
+
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+ @Parameter(0) lateinit var name: String
+ // The current ResizeHandleSizeRepository API under test.
+
+ @Parameter(1) lateinit var operation: (ResizeHandleSizeRepository) -> Unit
+
+ @Before
+ fun setOverrideBeforeResetResizeHandle() {
+ MockitoAnnotations.initMocks(this)
+ if (name != "reset") return
+ val originalEdgeHandle =
+ resizeHandleSizeRepository.getResizeEdgeHandlePixels(resources)
+ resizeHandleSizeRepository.setResizeEdgeHandlePixels(originalEdgeHandle + 2)
+ }
+
+ companion object {
+ @Parameters(name = "{index}: {0}")
+ @JvmStatic
+ fun data(): Iterable<Array<Any>> {
+ return listOf(
+ arrayOf(
+ "reset",
+ { sizeRepository: ResizeHandleSizeRepository ->
+ sizeRepository.resetResizeEdgeHandlePixels()
+ }
+ ),
+ arrayOf(
+ "set",
+ { sizeRepository: ResizeHandleSizeRepository ->
+ sizeRepository.setResizeEdgeHandlePixels(99)
+ }
+ )
+ )
+ }
+ }
+
+ // =================
+ // Validate that listeners are notified correctly for reset resize handle API.
+ // =================
+
+ @Test
+ fun testUpdateResizeHandleSize_flagDisabled() {
+ setFlagsRule.disableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+ registerSizeChangeFunctions()
+ operation.invoke(resizeHandleSizeRepository)
+ // Nothing is notified since flag is disabled.
+ verify(mockSizeChangeFunctionOne, never()).accept(any())
+ verify(mockSizeChangeFunctionTwo, never()).accept(any())
+ }
+
+ @Test
+ fun testUpdateResizeHandleSize_flagEnabled_noListeners() {
+ setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+ operation.invoke(resizeHandleSizeRepository)
+ // Nothing is notified since nothing was registered.
+ verify(mockSizeChangeFunctionOne, never()).accept(any())
+ verify(mockSizeChangeFunctionTwo, never()).accept(any())
+ }
+
+ @Test
+ fun testUpdateResizeHandleSize_flagEnabled_listenersNotified() {
+ setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+ registerSizeChangeFunctions()
+ operation.invoke(resizeHandleSizeRepository)
+ // Functions notified when reset.
+ verify(mockSizeChangeFunctionOne).accept(any())
+ verify(mockSizeChangeFunctionTwo).accept(any())
+ }
+
+ @Test
+ fun testUpdateResizeHandleSize_flagEnabled_listenerFails() {
+ setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+ registerSizeChangeFunctions()
+ operation.invoke(resizeHandleSizeRepository)
+ // Functions notified when reset.
+ verify(mockSizeChangeFunctionOne).accept(any())
+ verify(mockSizeChangeFunctionTwo).accept(any())
+ }
+
+ @Test
+ fun testUpdateResizeHandleSize_flagEnabled_ignoreSecondListener() {
+ setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+ registerSizeChangeFunctions()
+ val extraConsumerMock = mock(Consumer::class.java) as Consumer<ResizeHandleSizeRepository>
+ resizeHandleSizeRepository.registerSizeChangeFunction(extraConsumerMock)
+ // First listener succeeds, second one that fails is ignored.
+ operation.invoke(resizeHandleSizeRepository)
+ // Functions notified when reset.
+ verify(mockSizeChangeFunctionOne).accept(any())
+ verify(mockSizeChangeFunctionTwo).accept(any())
+ verify(extraConsumerMock).accept(any())
+ }
+
+ private fun registerSizeChangeFunctions() {
+ resizeHandleSizeRepository.registerSizeChangeFunction(mockSizeChangeFunctionOne)
+ resizeHandleSizeRepository.registerSizeChangeFunction(mockSizeChangeFunctionTwo)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepositoryTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepositoryTests.kt
new file mode 100644
index 000000000000..59bbc72a733b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepositoryTests.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor
+
+import android.content.Context
+import android.platform.test.flag.junit.SetFlagsRule
+import android.testing.AndroidTestingRunner
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for {@link ResizeHandleSizeRepository}. Validate that get/reset/set work correctly.
+ *
+ * Build/Install/Run: atest WMShellUnitTests:ResizeHandleSizeRepositoryTests
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ResizeHandleSizeRepositoryTests {
+ private val resources = ApplicationProvider.getApplicationContext<Context>().resources
+ private val resizeHandleSizeRepository = ResizeHandleSizeRepository()
+
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+ @Test
+ fun testOverrideResizeEdgeHandlePixels_flagEnabled_resetSucceeds() {
+ setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+ // Reset does nothing when no override is set.
+ val originalEdgeHandle =
+ resizeHandleSizeRepository.getResizeEdgeHandlePixels(resources)
+ resizeHandleSizeRepository.resetResizeEdgeHandlePixels()
+ assertThat(resizeHandleSizeRepository.getResizeEdgeHandlePixels(resources))
+ .isEqualTo(originalEdgeHandle)
+
+ // Now try to set the value; reset should succeed.
+ resizeHandleSizeRepository.setResizeEdgeHandlePixels(originalEdgeHandle + 2)
+ resizeHandleSizeRepository.resetResizeEdgeHandlePixels()
+ assertThat(resizeHandleSizeRepository.getResizeEdgeHandlePixels(resources))
+ .isEqualTo(originalEdgeHandle)
+ }
+
+ @Test
+ fun testOverrideResizeEdgeHandlePixels_flagDisabled_resetFails() {
+ setFlagsRule.disableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+ // Reset does nothing when no override is set.
+ val originalEdgeHandle =
+ resizeHandleSizeRepository.getResizeEdgeHandlePixels(resources)
+ resizeHandleSizeRepository.resetResizeEdgeHandlePixels()
+ assertThat(resizeHandleSizeRepository.getResizeEdgeHandlePixels(resources))
+ .isEqualTo(originalEdgeHandle)
+
+ // Now try to set the value; reset should do nothing.
+ val newEdgeHandle = originalEdgeHandle + 2
+ resizeHandleSizeRepository.setResizeEdgeHandlePixels(newEdgeHandle)
+ resizeHandleSizeRepository.resetResizeEdgeHandlePixels()
+ assertThat(resizeHandleSizeRepository.getResizeEdgeHandlePixels(resources))
+ .isEqualTo(originalEdgeHandle)
+ }
+}