summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jeremy Sim <jeremysim@google.com> 2024-11-08 22:44:59 -0800
committer Jeremy Sim <jeremysim@google.com> 2024-11-19 01:36:51 +0000
commit4789cc2d6f21a70893d0b841b438dca0de9bc1c4 (patch)
treecec9c136cf317407d31b397a92f1cba5890c65c1
parent025f8b58a19ed417351a2fdf01ec8f0e57e90ac8 (diff)
Flexible 2-app split: Touch zones
Changes how we detect a user's intent to change apps in flexible split. Previously, we relied on the app's TO_FRONT transition, which works in most cases, but notably fails on the swipe-up-to-Overview gesture (the swiped-over app briefly receives a TO_FRONT, which creates problems). Now, we use a new toggleable, touchable View which is attached to the split root. The touch zone is set and unset as the split layout changes. The zone lives behind insets and Taskbar surfaces, so it doesn't interfere with those, and screen-edge swipes are properly ignored. One small issue: The status bar blocks touches to this zone, so the touch zone feels a little small in portrait split when the top app is offscreen. Bug: 349828130 Flag: com.android.wm.shell.enable_flexible_two_app_split Test: Unchanged behavior when flag is off Change-Id: Iaaf62f15ae1a5c95d63ea601da5d5a9d4e75917e
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OffscreenTouchZone.java148
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java128
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java38
6 files changed, 283 insertions, 53 deletions
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 cf858deb0327..2c418d34f09a 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
@@ -90,8 +90,8 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
private int mHandleRegionHeight;
/**
- * Tracks divider bar visible bounds in screen-based coordination. Used to calculate with
- * insets.
+ * This is not the visible bounds you see on screen, but the actual behind-the-scenes window
+ * bounds, which is larger.
*/
private final Rect mDividerBounds = new Rect();
private final Rect mTempRect = new Rect();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OffscreenTouchZone.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OffscreenTouchZone.java
new file mode 100644
index 000000000000..381f0b037023
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OffscreenTouchZone.java
@@ -0,0 +1,148 @@
+/*
+ * 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.common.split;
+
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+
+import static com.android.wm.shell.common.split.SplitLayout.RESTING_TOUCH_LAYER;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.PixelFormat;
+import android.os.Binder;
+import android.view.MotionEvent;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.WindowlessWindowManager;
+
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * Holds and manages a single touchable surface. These are used in offscreen split layouts, where
+ * we use them as a signal that the user wants to bring an offscreen app back onscreen.
+ * <br>
+ * Split root
+ * / | \
+ * Stage root Divider Stage root
+ * / \
+ * Task *this class*
+ *
+ */
+public class OffscreenTouchZone {
+ private static final String TAG = "OffscreenTouchZone";
+
+ /**
+ * Whether this touch zone is on the top/left or the bottom/right screen edge.
+ */
+ private final boolean mIsTopLeft;
+ /** The function that will be run when this zone is tapped. */
+ private final Runnable mOnClickRunnable;
+ private SurfaceControlViewHost mViewHost;
+
+ /**
+ * @param isTopLeft Whether the desired touch zone will be on the top/left or the bottom/right
+ * screen edge.
+ * @param runnable The function to run when the touch zone is tapped.
+ */
+ OffscreenTouchZone(boolean isTopLeft, Runnable runnable) {
+ mIsTopLeft = isTopLeft;
+ mOnClickRunnable = runnable;
+ }
+
+ /** Sets up a touch zone. */
+ public void inflate(Context context, Configuration config, SyncTransactionQueue syncQueue,
+ SurfaceControl stageRoot) {
+ View touchableView = new View(context);
+ touchableView.setOnTouchListener(new OffscreenTouchListener());
+
+ // Set WM flags, tokens, and sizing on the touchable view. It will be the same size as its
+ // parent, the stage root.
+ // TODO (b/349828130): It's a bit wasteful to have the touch zone cover the whole app
+ // surface, even extending offscreen (keeps buffer active in memory), so can trim it down
+ // to the visible onscreen area in a future patch.
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.TYPE_INPUT_CONSUMER,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSLUCENT);
+ lp.token = new Binder();
+ lp.setTitle(TAG);
+ lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
+ touchableView.setLayoutParams(lp);
+
+ // Create a new leash under our stage leash.
+ final SurfaceControl.Builder builder = new SurfaceControl.Builder()
+ .setContainerLayer()
+ .setName(TAG + (mIsTopLeft ? "TopLeft" : "BottomRight"))
+ .setCallsite("OffscreenTouchZone::init");
+ builder.setParent(stageRoot);
+ SurfaceControl leash = builder.build();
+
+ // Create a ViewHost that will hold our view.
+ WindowlessWindowManager wwm = new WindowlessWindowManager(config, leash, null);
+ mViewHost = new SurfaceControlViewHost(context, context.getDisplay(), wwm,
+ "SplitTouchZones");
+ mViewHost.setView(touchableView, lp);
+
+ // Create a transaction so that we can activate and reposition our surface.
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ // Set layer to maximum. We want this surface to be above the app layer, or else touches
+ // will be blocked.
+ t.setLayer(leash, RESTING_TOUCH_LAYER);
+ // Leash starts off hidden, show it.
+ t.show(leash);
+ syncQueue.runInSync(transaction -> {
+ transaction.merge(t);
+ t.close();
+ });
+ }
+
+ /** Releases the touch zone when it's no longer needed. */
+ void release() {
+ if (mViewHost != null) {
+ mViewHost.release();
+ }
+ }
+
+ /**
+ * Listens for touch events.
+ * TODO (b/349828130): Update for mouse click events as well, and possibly keyboard?
+ */
+ private class OffscreenTouchListener implements View.OnTouchListener {
+ @Override
+ public boolean onTouch(View view, MotionEvent motionEvent) {
+ if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
+ mOnClickRunnable.run();
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Returns {@code true} if this touch zone represents an offscreen app on the top/left edge of
+ * the display, {@code false} for bottom/right.
+ */
+ public boolean isTopLeft() {
+ return mIsTopLeft;
+ }
+}
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 de3152ad7687..d20ad5d1b908 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
@@ -23,8 +23,8 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMA
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-import static com.android.wm.shell.common.split.SplitLayout.BEHIND_APP_VEIL_LAYER;
-import static com.android.wm.shell.common.split.SplitLayout.FRONT_APP_VEIL_LAYER;
+import static com.android.wm.shell.common.split.SplitLayout.ANIMATING_BACK_APP_VEIL_LAYER;
+import static com.android.wm.shell.common.split.SplitLayout.ANIMATING_FRONT_APP_VEIL_LAYER;
import static com.android.wm.shell.shared.split.SplitScreenConstants.FADE_DURATION;
import static com.android.wm.shell.shared.split.SplitScreenConstants.VEIL_DELAY_DURATION;
@@ -66,6 +66,13 @@ import java.util.function.Consumer;
* Currently, we show a veil when:
* a) Task is resizing down from a fullscreen window.
* b) Task is being stretched past its original bounds.
+ * <br>
+ * Split root
+ * / | \
+ * Stage root Divider Stage root
+ * / \
+ * Task *this class*
+ *
*/
public class SplitDecorManager extends WindowlessWindowManager {
private static final String TAG = SplitDecorManager.class.getSimpleName();
@@ -77,6 +84,7 @@ public class SplitDecorManager extends WindowlessWindowManager {
private Drawable mIcon;
private ImageView mVeilIconView;
private SurfaceControlViewHost mViewHost;
+ /** The parent surface that this is attached to. Should be the stage root. */
private SurfaceControl mHostLeash;
private SurfaceControl mIconLeash;
private SurfaceControl mBackgroundLeash;
@@ -389,7 +397,9 @@ public class SplitDecorManager extends WindowlessWindowManager {
mOffsetX = (int) iconOffsetX;
mOffsetY = (int) iconOffsetY;
- t.setLayer(leash, isGoingBehind ? BEHIND_APP_VEIL_LAYER : FRONT_APP_VEIL_LAYER);
+ t.setLayer(leash, isGoingBehind
+ ? ANIMATING_BACK_APP_VEIL_LAYER
+ : ANIMATING_FRONT_APP_VEIL_LAYER);
if (!mShown) {
if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
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 0f21756bb863..6beff1979e6d 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
@@ -33,6 +33,8 @@ import static com.android.wm.shell.shared.animation.Interpolators.LINEAR;
import static com.android.wm.shell.shared.animation.Interpolators.SLOWDOWN_INTERPOLATOR;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_90_10;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_3_10_45_45;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_3_45_45_10;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -72,6 +74,7 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.protolog.ProtoLog;
+import com.android.wm.shell.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -80,6 +83,7 @@ import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget;
+import com.android.wm.shell.common.split.SplitWindowManager.ParentContainerCallbacks;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
@@ -88,6 +92,7 @@ import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.splitscreen.StageTaskListener;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
@@ -112,15 +117,19 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
public static final float OFFSCREEN_ASYMMETRIC_RATIO = 0.1f;
// Here are some (arbitrarily decided) layer definitions used during animations to make sure the
- // layers stay in order. Note: This does not affect any other layer numbering systems because
- // the layer system in WindowManager is local within sibling groups. So, for example, each
- // "veil layer" defined here actually has two sub-layers; and *their* layer values, which we set
- // in SplitDecorManager, are only important relative to each other.
- public static final int DIVIDER_LAYER = 0;
- public static final int FRONT_APP_VEIL_LAYER = DIVIDER_LAYER + 20;
- public static final int FRONT_APP_LAYER = DIVIDER_LAYER + 10;
- public static final int BEHIND_APP_VEIL_LAYER = DIVIDER_LAYER - 10;
- public static final int BEHIND_APP_LAYER = DIVIDER_LAYER - 20;
+ // layers stay in order. (During transitions, everything is reparented onto a transition root
+ // and can be freely relayered.)
+ public static final int ANIMATING_DIVIDER_LAYER = 0;
+ public static final int ANIMATING_FRONT_APP_VEIL_LAYER = ANIMATING_DIVIDER_LAYER + 20;
+ public static final int ANIMATING_FRONT_APP_LAYER = ANIMATING_DIVIDER_LAYER + 10;
+ public static final int ANIMATING_BACK_APP_VEIL_LAYER = ANIMATING_DIVIDER_LAYER - 10;
+ public static final int ANIMATING_BACK_APP_LAYER = ANIMATING_DIVIDER_LAYER - 20;
+ // The divider is on the split root, and is sibling with the stage roots. We want to keep it
+ // above the app stages.
+ public static final int RESTING_DIVIDER_LAYER = Integer.MAX_VALUE;
+ // The touch layer is on a stage root, and is sibling with things like the app activity itself
+ // and the app veil. We want it to be above all those.
+ public static final int RESTING_TOUCH_LAYER = Integer.MAX_VALUE;
// Animation specs for the swap animation
private static final int SWAP_ANIMATION_TOTAL_DURATION = 500;
@@ -155,10 +164,16 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
// The temp bounds outside of display bounds for side stage when split screen inactive to avoid
// flicker next time active split screen.
private final Rect mInvisibleBounds = new Rect();
+ /**
+ * Areas on the screen that the user can touch to shift the layout, bringing offscreen apps
+ * onscreen. If n apps are offscreen, there should be n such areas. Empty otherwise.
+ */
+ private final List<OffscreenTouchZone> mOffscreenTouchZones = new ArrayList<>();
private final SplitLayoutHandler mSplitLayoutHandler;
private final SplitWindowManager mSplitWindowManager;
private final DisplayController mDisplayController;
private final DisplayImeController mDisplayImeController;
+ private final ParentContainerCallbacks mParentContainerCallbacks;
private final ImePositionProcessor mImePositionProcessor;
private final ResizingEffectPolicy mSurfaceEffectPolicy;
private final ShellTaskOrganizer mTaskOrganizer;
@@ -199,6 +214,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mSplitLayoutHandler = splitLayoutHandler;
mDisplayController = displayController;
mDisplayImeController = displayImeController;
+ mParentContainerCallbacks = parentContainerCallbacks;
mSplitWindowManager = new SplitWindowManager(windowName, mContext, configuration,
parentContainerCallbacks);
mTaskOrganizer = taskOrganizer;
@@ -269,18 +285,6 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
return new Rect(mRootBounds);
}
- /** Gets bounds of divider window with screen based coordinate. */
- public Rect getDividerBounds() {
- return new Rect(mDividerBounds);
- }
-
- /** Gets bounds of divider window with parent based coordinate. */
- public Rect getRefDividerBounds() {
- final Rect outBounds = getDividerBounds();
- outBounds.offset(-mRootBounds.left, -mRootBounds.top);
- return outBounds;
- }
-
/** Copies the top/left bounds to the provided Rect (screen-based coordinates). */
public void copyTopLeftBounds(Rect rect) {
rect.set(getTopLeftBounds());
@@ -319,12 +323,36 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
return mContentBounds.getLast();
}
- /** Gets bounds of divider window with screen based coordinate on the param Rect. */
+ /**
+ * Gets the bounds of divider window, in screen-based coordinates. This is not the visible
+ * bounds you see on screen, but the actual behind-the-scenes window bounds, which is larger.
+ */
+ public Rect getDividerBounds() {
+ return new Rect(mDividerBounds);
+ }
+
+ /**
+ * Gets the bounds of divider window, in parent-based coordinates. This is not the visible
+ * bounds you see on screen, but the actual behind-the-scenes window bounds, which is larger.
+ */
+ public Rect getRefDividerBounds() {
+ final Rect outBounds = getDividerBounds();
+ outBounds.offset(-mRootBounds.left, -mRootBounds.top);
+ return outBounds;
+ }
+
+ /**
+ * Gets the bounds of divider window, in screen-based coordinates. This is not the visible
+ * bounds you see on screen, but the actual behind-the-scenes window bounds, which is larger.
+ */
public void getDividerBounds(Rect rect) {
rect.set(mDividerBounds);
}
- /** Gets bounds of divider window with parent based coordinate on the param Rect. */
+ /**
+ * Gets the bounds of divider window, in parent-based coordinates. This is not the visible
+ * bounds you see on screen, but the actual behind-the-scenes window bounds, which is larger.
+ */
public void getRefDividerBounds(Rect rect) {
getDividerBounds(rect);
rect.offset(-mRootBounds.left, -mRootBounds.top);
@@ -372,6 +400,46 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mIsLeftRightSplit ? 0 : mRootBounds.bottom);
}
+ /**
+ * (Re)calculates and activates any needed touch zones, so the user can tap them and retrieve
+ * offscreen apps.
+ */
+ public void populateTouchZones() {
+ if (!Flags.enableFlexibleTwoAppSplit()) {
+ return;
+ }
+
+ if (!mOffscreenTouchZones.isEmpty()) {
+ removeTouchZones();
+ }
+
+ int currentPosition = calculateCurrentSnapPosition();
+ switch (currentPosition) {
+ case SNAP_TO_2_10_90:
+ case SNAP_TO_3_10_45_45:
+ mOffscreenTouchZones.add(new OffscreenTouchZone(true /* isTopLeft */,
+ () -> flingDividerToOtherSide(currentPosition)));
+ break;
+ case SNAP_TO_2_90_10:
+ case SNAP_TO_3_45_45_10:
+ mOffscreenTouchZones.add(new OffscreenTouchZone(false /* isTopLeft */,
+ () -> flingDividerToOtherSide(currentPosition)));
+ break;
+ }
+
+ mOffscreenTouchZones.forEach(mParentContainerCallbacks::inflateOnStageRoot);
+ }
+
+ /** Removes all touch zones. */
+ public void removeTouchZones() {
+ if (!Flags.enableFlexibleTwoAppSplit()) {
+ return;
+ }
+
+ mOffscreenTouchZones.forEach(OffscreenTouchZone::release);
+ mOffscreenTouchZones.clear();
+ }
+
/** Applies new configuration, returns {@code false} if there's no effect to the layout. */
public boolean updateConfiguration(Configuration configuration) {
// Update the split bounds when necessary. Besides root bounds changed, split bounds need to
@@ -509,6 +577,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
if (mInitialized) return;
mInitialized = true;
mSplitWindowManager.init(this, mInsetsState, false /* isRestoring */);
+ populateTouchZones();
mDisplayImeController.addPositionProcessor(mImePositionProcessor);
}
@@ -517,6 +586,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
if (!mInitialized) return;
mInitialized = false;
mSplitWindowManager.release(t);
+ removeTouchZones();
mDisplayImeController.removePositionProcessor(mImePositionProcessor);
mImePositionProcessor.reset();
if (mDividerFlingAnimator != null) {
@@ -540,6 +610,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mImePositionProcessor.reset();
}
mSplitWindowManager.init(this, mInsetsState, true /* isRestoring */);
+ populateTouchZones();
// Update the surface positions again after recreating the divider in case nothing else
// triggers it
mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
@@ -782,6 +853,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
* DividerSnapAlgorithm will need to be refactored, and this function will change as well.
*/
public void flingDividerToOtherSide(@PersistentSnapPosition int currentSnapPosition) {
+ // If a fling animation is already running, just return.
+ if (mDividerFlingAnimator != null) return;
+
switch (currentSnapPosition) {
case SNAP_TO_2_10_90 ->
snapToTarget(mDividerPosition, mDividerSnapAlgorithm.getLastSplitTarget(),
@@ -1018,9 +1092,11 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
// Set layers
if (taskInfo != null) {
- t.setLayer(leash, isGoingBehind ? BEHIND_APP_LAYER : FRONT_APP_LAYER);
+ t.setLayer(leash, isGoingBehind
+ ? ANIMATING_BACK_APP_LAYER
+ : ANIMATING_FRONT_APP_LAYER);
} else {
- t.setLayer(leash, DIVIDER_LAYER);
+ t.setLayer(leash, ANIMATING_DIVIDER_LAYER);
}
if (offsetX == 0 && offsetY == 0) {
@@ -1079,7 +1155,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
getRefDividerBounds(mTempRect);
t.setPosition(dividerLeash, mTempRect.left, mTempRect.top);
// Resets layer of divider bar to make sure it is always on top.
- t.setLayer(dividerLeash, Integer.MAX_VALUE);
+ t.setLayer(dividerLeash, RESTING_DIVIDER_LAYER);
}
copyTopLeftRefBounds(mTempRect);
t.setPosition(leash1, mTempRect.left, mTempRect.top)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
index c5f19742c803..89573ccef24b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -67,6 +67,8 @@ public final class SplitWindowManager extends WindowlessWindowManager {
public interface ParentContainerCallbacks {
void attachToParentSurface(SurfaceControl.Builder b);
void onLeashReady(SurfaceControl leash);
+ /** Inflates the given touch zone on the appropriate stage root. */
+ void inflateOnStageRoot(OffscreenTouchZone touchZone);
}
public SplitWindowManager(String windowName, Context context, Configuration config,
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 45ecfa95b494..5e721bc1bd01 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
@@ -35,13 +35,11 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP
import static com.android.wm.shell.Flags.enableFlexibleSplit;
import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER;
-import static com.android.wm.shell.common.split.SplitScreenUtils.isPartiallyOffscreen;
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;
import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
-import static com.android.wm.shell.shared.TransitionUtil.isOrderOnly;
import static com.android.wm.shell.shared.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
@@ -137,6 +135,7 @@ import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.split.OffscreenTouchZone;
import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.common.split.SplitScreenUtils;
@@ -322,6 +321,20 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSyncQueue.runInSync(t -> applyDividerVisibility(t));
}
}
+
+ @Override
+ public void inflateOnStageRoot(OffscreenTouchZone touchZone) {
+ SurfaceControl topLeftLeash =
+ mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
+ ? mMainStage.mRootLeash : mSideStage.mRootLeash;
+ SurfaceControl bottomRightLeash =
+ mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
+ ? mSideStage.mRootLeash : mMainStage.mRootLeash;
+ touchZone.inflate(
+ mContext.createConfigurationContext(mRootTaskInfo.configuration),
+ mRootTaskInfo.configuration, mSyncQueue,
+ touchZone.isTopLeft() ? topLeftLeash : bottomRightLeash);
+ }
};
protected StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
@@ -2348,6 +2361,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout.setDividerInteractive(true, false, "onSplitResizeConsumed");
}, (finishWct, t) -> {
mSplitLayout.setDividerInteractive(true, false, "onSplitResizeFinish");
+ mSplitLayout.populateTouchZones();
}, mainDecor, sideDecor, decorManagers);
if (Flags.enableFlexibleTwoAppSplit()) {
@@ -2911,26 +2925,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mTaskOrganizer.applyTransaction(wct);
}
continue;
- } else if (Flags.enableFlexibleTwoAppSplit() && isOrderOnly(change)) {
- int focusedStageIndex = SPLIT_INDEX_UNDEFINED;
- if (taskInfo.token.equals(mMainStage.mRootTaskInfo.token)) {
- focusedStageIndex = mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
- ? SPLIT_INDEX_0 : SPLIT_INDEX_1;
- } else if (taskInfo.token.equals(mSideStage.mRootTaskInfo.token)) {
- focusedStageIndex = mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
- ? SPLIT_INDEX_1 : SPLIT_INDEX_0;
- }
-
- if (focusedStageIndex != SPLIT_INDEX_UNDEFINED) {
- @PersistentSnapPosition int currentSnapPosition =
- mSplitLayout.calculateCurrentSnapPosition();
- boolean offscreenTaskFocused =
- isPartiallyOffscreen(focusedStageIndex, currentSnapPosition);
-
- if (offscreenTaskFocused) {
- mSplitLayout.flingDividerToOtherSide(currentSnapPosition);
- }
- }
}
final StageTaskListener stage = getStageOfTask(taskInfo);
if (stage == null) {