Add support for left-right splits on portrait devices
- Add a config to transpose the split such that you can split left/right
while in portrait. This is mostly useful for portrait based foldable
devices.
- Refactors the divider, split layout, and drag and drop to be based on
this new config vs the configuration orientation
Bug: 291018646
Test: Manual
Flag: enable_left_right_split_in_portrait
Change-Id: I8b3b81f2510457c31ababb2db1b5e4031d6dc17c
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 4d2d960..4511f3b 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -50,3 +50,10 @@
bug: "290220798"
is_fixed_read_only: true
}
+
+flag {
+ name: "enable_left_right_split_in_portrait"
+ namespace: "multitasking"
+ description: "Enables left/right split in portrait"
+ bug: "291018646"
+}
diff --git a/libs/WindowManager/Shell/res/layout/docked_stack_divider.xml b/libs/WindowManager/Shell/res/layout/docked_stack_divider.xml
deleted file mode 100644
index d732b01..0000000
--- a/libs/WindowManager/Shell/res/layout/docked_stack_divider.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 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.
--->
-
-<com.android.wm.shell.legacysplitscreen.DividerView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_height="match_parent"
- android:layout_width="match_parent">
-
- <View
- style="@style/DockedDividerBackground"
- android:id="@+id/docked_divider_background"
- android:background="@color/split_divider_background"/>
-
- <com.android.wm.shell.legacysplitscreen.MinimizedDockShadow
- style="@style/DockedDividerMinimizedShadow"
- android:id="@+id/minimized_dock_shadow"
- android:alpha="0"/>
-
- <com.android.wm.shell.common.split.DividerHandleView
- style="@style/DockedDividerHandle"
- android:id="@+id/docked_divider_handle"
- android:contentDescription="@string/accessibility_divider"
- android:background="@null"/>
-
-</com.android.wm.shell.legacysplitscreen.DividerView>
diff --git a/libs/WindowManager/Shell/res/layout/split_divider.xml b/libs/WindowManager/Shell/res/layout/split_divider.xml
index e3be700..db35c8c 100644
--- a/libs/WindowManager/Shell/res/layout/split_divider.xml
+++ b/libs/WindowManager/Shell/res/layout/split_divider.xml
@@ -24,17 +24,16 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
- <View
- style="@style/DockedDividerBackground"
- android:id="@+id/docked_divider_background"/>
-
<com.android.wm.shell.common.split.DividerHandleView
- style="@style/DockedDividerHandle"
android:id="@+id/docked_divider_handle"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:layout_gravity="center"
android:contentDescription="@string/accessibility_divider"
android:background="@null"/>
<com.android.wm.shell.common.split.DividerRoundedCorner
+ android:id="@+id/docked_divider_rounded_corner"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
diff --git a/libs/WindowManager/Shell/res/values-land/dimens.xml b/libs/WindowManager/Shell/res/values-land/dimens.xml
index a95323f..1b96fa2 100644
--- a/libs/WindowManager/Shell/res/values-land/dimens.xml
+++ b/libs/WindowManager/Shell/res/values-land/dimens.xml
@@ -16,13 +16,6 @@
*/
-->
<resources>
- <!-- Divider handle size for legacy split screen -->
- <dimen name="docked_divider_handle_width">2dp</dimen>
- <dimen name="docked_divider_handle_height">16dp</dimen>
- <!-- Divider handle size for split screen -->
- <dimen name="split_divider_handle_width">3dp</dimen>
- <dimen name="split_divider_handle_height">72dp</dimen>
-
<!-- Padding between status bar and bubbles when displayed in expanded state, smaller
value in landscape since we have limited vertical space-->
<dimen name="bubble_padding_top">4dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values-land/styles.xml b/libs/WindowManager/Shell/res/values-land/styles.xml
deleted file mode 100644
index e89f65b..0000000
--- a/libs/WindowManager/Shell/res/values-land/styles.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 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.
--->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="DockedDividerBackground">
- <item name="android:layout_width">@dimen/split_divider_bar_width</item>
- <item name="android:layout_height">match_parent</item>
- <item name="android:layout_gravity">center_horizontal</item>
- <item name="android:background">@color/split_divider_background</item>
- </style>
-
- <style name="DockedDividerHandle">
- <item name="android:layout_gravity">center</item>
- <item name="android:layout_width">48dp</item>
- <item name="android:layout_height">96dp</item>
- </style>
-
- <style name="DockedDividerMinimizedShadow">
- <item name="android:layout_width">8dp</item>
- <item name="android:layout_height">match_parent</item>
- </style>
-</resources>
-
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index f20d44d..8f9de61 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -96,6 +96,9 @@
<dimen name="docked_divider_handle_width">16dp</dimen>
<dimen name="docked_divider_handle_height">2dp</dimen>
<!-- Divider handle size for split screen -->
+ <dimen name="split_divider_handle_region_width">96dp</dimen>
+ <dimen name="split_divider_handle_region_height">48dp</dimen>
+
<dimen name="split_divider_handle_width">72dp</dimen>
<dimen name="split_divider_handle_height">3dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index 468cfd5..08c2a02 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -60,20 +60,9 @@
<style name="DockedDividerBackground">
<item name="android:layout_width">match_parent</item>
- <item name="android:layout_height">@dimen/split_divider_bar_width</item>
- <item name="android:layout_gravity">center_vertical</item>
- <item name="android:background">@color/split_divider_background</item>
- </style>
-
- <style name="DockedDividerMinimizedShadow">
- <item name="android:layout_width">match_parent</item>
- <item name="android:layout_height">8dp</item>
- </style>
-
- <style name="DockedDividerHandle">
+ <item name="android:layout_height">match_parent</item>
<item name="android:layout_gravity">center</item>
- <item name="android:layout_width">96dp</item>
- <item name="android:layout_height">48dp</item>
+ <item name="android:background">@color/split_divider_background</item>
</style>
<style name="TvPipEduText">
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
index ec26800..999da24 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
@@ -68,24 +68,33 @@
};
private final Paint mPaint = new Paint();
- private final int mWidth;
- private final int mHeight;
- private final int mTouchingWidth;
- private final int mTouchingHeight;
+ private int mWidth;
+ private int mHeight;
+ private int mTouchingWidth;
+ private int mTouchingHeight;
private int mCurrentWidth;
private int mCurrentHeight;
private AnimatorSet mAnimator;
private boolean mTouching;
private boolean mHovering;
- private final int mHoveringWidth;
- private final int mHoveringHeight;
+ private int mHoveringWidth;
+ private int mHoveringHeight;
+ private boolean mIsLeftRightSplit;
public DividerHandleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPaint.setColor(getResources().getColor(R.color.docked_divider_handle, null));
mPaint.setAntiAlias(true);
- mWidth = getResources().getDimensionPixelSize(R.dimen.split_divider_handle_width);
- mHeight = getResources().getDimensionPixelSize(R.dimen.split_divider_handle_height);
+ updateDimens();
+ }
+
+ private void updateDimens() {
+ mWidth = getResources().getDimensionPixelSize(mIsLeftRightSplit
+ ? R.dimen.split_divider_handle_height
+ : R.dimen.split_divider_handle_width);
+ mHeight = getResources().getDimensionPixelSize(mIsLeftRightSplit
+ ? R.dimen.split_divider_handle_width
+ : R.dimen.split_divider_handle_height);
mCurrentWidth = mWidth;
mCurrentHeight = mHeight;
mTouchingWidth = mWidth > mHeight ? mWidth / 2 : mWidth;
@@ -94,6 +103,11 @@
mHoveringHeight = mHeight > mWidth ? ((int) (mHeight * 1.5f)) : mHeight;
}
+ void setIsLeftRightSplit(boolean isLeftRightSplit) {
+ mIsLeftRightSplit = isLeftRightSplit;
+ updateDimens();
+ }
+
/** Sets touching state for this handle view. */
public void setTouching(boolean touching, boolean animate) {
if (touching == mTouching) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java
index 364bb65..834c15d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.common.split;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
import static android.view.RoundedCorner.POSITION_TOP_LEFT;
@@ -47,6 +46,7 @@
private InvertedRoundedCornerDrawInfo mTopRightCorner;
private InvertedRoundedCornerDrawInfo mBottomLeftCorner;
private InvertedRoundedCornerDrawInfo mBottomRightCorner;
+ private boolean mIsLeftRightSplit;
public DividerRoundedCorner(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
@@ -98,8 +98,8 @@
return false;
}
- private boolean isLandscape() {
- return getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE;
+ void setIsLeftRightSplit(boolean isLeftRightSplit) {
+ mIsLeftRightSplit = isLeftRightSplit;
}
/**
@@ -134,7 +134,7 @@
}
private void calculateStartPos(Point outPos) {
- if (isLandscape()) {
+ if (mIsLeftRightSplit) {
// Place left corner at the right side of the divider bar.
outPos.x = isLeftCorner()
? getWidth() / 2 + mDividerWidth / 2
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 0b0c693..0f0fbd9c 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
@@ -16,7 +16,6 @@
package com.android.wm.shell.common.split;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
@@ -27,6 +26,8 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Bundle;
import android.provider.DeviceConfig;
@@ -65,12 +66,15 @@
public static final long TOUCH_ANIMATION_DURATION = 150;
public static final long TOUCH_RELEASE_ANIMATION_DURATION = 200;
+ private final Paint mPaint = new Paint();
+ private final Rect mBackgroundRect = new Rect();
private final int mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
private SplitLayout mSplitLayout;
private SplitWindowManager mSplitWindowManager;
private SurfaceControlViewHost mViewHost;
private DividerHandleView mHandle;
+ private DividerRoundedCorner mCorners;
private View mBackground;
private int mTouchElevation;
@@ -81,6 +85,8 @@
private boolean mInteractive;
private boolean mSetTouchRegion = true;
private int mLastDraggingPosition;
+ private int mHandleRegionWidth;
+ private int mHandleRegionHeight;
/**
* Tracks divider bar visible bounds in screen-based coordination. Used to calculate with
@@ -123,7 +129,7 @@
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
final DividerSnapAlgorithm snapAlgorithm = mSplitLayout.mDividerSnapAlgorithm;
- if (isLandscape()) {
+ if (mSplitLayout.isLeftRightSplit()) {
info.addAction(new AccessibilityAction(R.id.action_move_tl_full,
mContext.getString(R.string.accessibility_action_divider_left_full)));
if (snapAlgorithm.isFirstSplitTargetAvailable()) {
@@ -215,6 +221,17 @@
mViewHost = viewHost;
layout.getDividerBounds(mDividerBounds);
onInsetsChanged(insetsState, false /* animate */);
+
+ final boolean isLeftRightSplit = mSplitLayout.isLeftRightSplit();
+ mHandle.setIsLeftRightSplit(isLeftRightSplit);
+ mCorners.setIsLeftRightSplit(isLeftRightSplit);
+
+ mHandleRegionWidth = getResources().getDimensionPixelSize(isLeftRightSplit
+ ? R.dimen.split_divider_handle_region_height
+ : R.dimen.split_divider_handle_region_width);
+ mHandleRegionHeight = getResources().getDimensionPixelSize(isLeftRightSplit
+ ? R.dimen.split_divider_handle_region_width
+ : R.dimen.split_divider_handle_region_height);
}
void onInsetsChanged(InsetsState insetsState, boolean animate) {
@@ -255,30 +272,47 @@
super.onFinishInflate();
mDividerBar = findViewById(R.id.divider_bar);
mHandle = findViewById(R.id.docked_divider_handle);
- mBackground = findViewById(R.id.docked_divider_background);
+ mCorners = findViewById(R.id.docked_divider_rounded_corner);
mTouchElevation = getResources().getDimensionPixelSize(
R.dimen.docked_stack_divider_lift_elevation);
mDoubleTapDetector = new GestureDetector(getContext(), new DoubleTapListener());
mInteractive = true;
setOnTouchListener(this);
mHandle.setAccessibilityDelegate(mHandleDelegate);
+ setWillNotDraw(false);
+ mPaint.setColor(getResources().getColor(R.color.split_divider_background, null));
+ mPaint.setAntiAlias(true);
+ mPaint.setStyle(Paint.Style.FILL);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mSetTouchRegion) {
- mTempRect.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(),
- mHandle.getBottom());
+ int startX = (mDividerBounds.width() - mHandleRegionWidth) / 2;
+ int startY = (mDividerBounds.height() - mHandleRegionHeight) / 2;
+ mTempRect.set(startX, startY, startX + mHandleRegionWidth,
+ startY + mHandleRegionHeight);
mSplitWindowManager.setTouchRegion(mTempRect);
mSetTouchRegion = false;
}
+
+ if (changed) {
+ boolean isHorizontalSplit = mSplitLayout.isLeftRightSplit();
+ int dividerSize = getResources().getDimensionPixelSize(R.dimen.split_divider_bar_width);
+ left = isHorizontalSplit ? (getWidth() - dividerSize) / 2 : 0;
+ top = isHorizontalSplit ? 0 : (getHeight() - dividerSize) / 2;
+ right = isHorizontalSplit ? left + dividerSize : getWidth();
+ bottom = isHorizontalSplit ? getHeight() : top + dividerSize;
+ mBackgroundRect.set(left, top, right, bottom);
+ }
}
@Override
public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
return PointerIcon.getSystemIcon(getContext(),
- isLandscape() ? TYPE_HORIZONTAL_DOUBLE_ARROW : TYPE_VERTICAL_DOUBLE_ARROW);
+ mSplitLayout.isLeftRightSplit() ? TYPE_HORIZONTAL_DOUBLE_ARROW
+ : TYPE_VERTICAL_DOUBLE_ARROW);
}
@Override
@@ -295,8 +329,8 @@
// moving divider bar and calculating dragging velocity.
event.setLocation(event.getRawX(), event.getRawY());
final int action = event.getAction() & MotionEvent.ACTION_MASK;
- final boolean isLandscape = isLandscape();
- final int touchPos = (int) (isLandscape ? event.getX() : event.getY());
+ final boolean isLeftRightSplit = mSplitLayout.isLeftRightSplit();
+ final int touchPos = (int) (isLeftRightSplit ? event.getX() : event.getY());
switch (action) {
case MotionEvent.ACTION_DOWN:
mVelocityTracker = VelocityTracker.obtain();
@@ -328,7 +362,7 @@
mVelocityTracker.addMovement(event);
mVelocityTracker.computeCurrentVelocity(1000 /* units */);
- final float velocity = isLandscape
+ final float velocity = isLeftRightSplit
? mVelocityTracker.getXVelocity()
: mVelocityTracker.getYVelocity();
final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos;
@@ -410,6 +444,11 @@
.start();
}
+ @Override
+ protected void onDraw(@NonNull Canvas canvas) {
+ canvas.drawRect(mBackgroundRect, mPaint);
+ }
+
@VisibleForTesting
void releaseHovering() {
mHandle.setHovering(false, true);
@@ -446,10 +485,6 @@
mHandle.setVisibility(!mInteractive && hideHandle ? View.INVISIBLE : View.VISIBLE);
}
- private boolean isLandscape() {
- return getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE;
- }
-
private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onDoubleTap(MotionEvent e) {
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 63cdb4f..b699533 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
@@ -79,7 +79,7 @@
* divide position changes.
*/
public final class SplitLayout implements DisplayInsetsController.OnInsetsChangedListener {
-
+ private static final String TAG = "SplitLayout";
public static final int PARALLAX_NONE = 0;
public static final int PARALLAX_DISMISSING = 1;
public static final int PARALLAX_ALIGN_CENTER = 2;
@@ -121,12 +121,15 @@
private int mDividerPosition;
private boolean mInitialized = false;
private boolean mFreezeDividerWindow = false;
+ private boolean mIsLargeScreen = false;
private int mOrientation;
private int mRotation;
private int mDensity;
private int mUiMode;
private final boolean mDimNonImeSide;
+ private final boolean mAllowLeftRightSplitInPortrait;
+ private boolean mIsLeftRightSplit;
private ValueAnimator mDividerFlingAnimator;
public SplitLayout(String windowName, Context context, Configuration configuration,
@@ -138,6 +141,7 @@
mOrientation = configuration.orientation;
mRotation = configuration.windowConfiguration.getRotation();
mDensity = configuration.densityDpi;
+ mIsLargeScreen = configuration.smallestScreenWidthDp >= 600;
mSplitLayoutHandler = splitLayoutHandler;
mDisplayController = displayController;
mDisplayImeController = displayImeController;
@@ -147,14 +151,17 @@
mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId());
mSurfaceEffectPolicy = new ResizingEffectPolicy(parallaxType);
+ final Resources res = mContext.getResources();
+ mDimNonImeSide = res.getBoolean(R.bool.config_dimNonImeAttachedSide);
+ mAllowLeftRightSplitInPortrait = SplitScreenUtils.allowLeftRightSplitInPortrait(res);
+ mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait,
+ configuration);
+
updateDividerConfig(mContext);
mRootBounds.set(configuration.windowConfiguration.getBounds());
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
resetDividerPosition();
-
- mDimNonImeSide = mContext.getResources().getBoolean(R.bool.config_dimNonImeAttachedSide);
-
updateInvisibleRect();
}
@@ -284,17 +291,17 @@
* Returns the divider position as a fraction from 0 to 1.
*/
public float getDividerPositionAsFraction() {
- return Math.min(1f, Math.max(0f, isLandscape()
+ return Math.min(1f, Math.max(0f, mIsLeftRightSplit
? (float) ((mBounds1.right + mBounds2.left) / 2f) / mBounds2.right
: (float) ((mBounds1.bottom + mBounds2.top) / 2f) / mBounds2.bottom));
}
private void updateInvisibleRect() {
mInvisibleBounds.set(mRootBounds.left, mRootBounds.top,
- isLandscape() ? mRootBounds.right / 2 : mRootBounds.right,
- isLandscape() ? mRootBounds.bottom : mRootBounds.bottom / 2);
- mInvisibleBounds.offset(isLandscape() ? mRootBounds.right : 0,
- isLandscape() ? 0 : mRootBounds.bottom);
+ mIsLeftRightSplit ? mRootBounds.right / 2 : mRootBounds.right,
+ mIsLeftRightSplit ? mRootBounds.bottom : mRootBounds.bottom / 2);
+ mInvisibleBounds.offset(mIsLeftRightSplit ? mRootBounds.right : 0,
+ mIsLeftRightSplit ? 0 : mRootBounds.bottom);
}
/** Applies new configuration, returns {@code false} if there's no effect to the layout. */
@@ -309,6 +316,7 @@
final int orientation = configuration.orientation;
final int density = configuration.densityDpi;
final int uiMode = configuration.uiMode;
+ final boolean wasLeftRightSplit = mIsLeftRightSplit;
if (mOrientation == orientation
&& mRotation == rotation
@@ -326,9 +334,12 @@
mRotation = rotation;
mDensity = density;
mUiMode = uiMode;
+ mIsLargeScreen = configuration.smallestScreenWidthDp >= 600;
+ mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait,
+ configuration);
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
updateDividerConfig(mContext);
- initDividerPosition(mTempRect);
+ initDividerPosition(mTempRect, wasLeftRightSplit);
updateInvisibleRect();
return true;
@@ -347,18 +358,27 @@
}
// We only need new bounds here, other configuration should be update later.
+ final boolean wasLeftRightSplit = SplitScreenUtils.isLeftRightSplit(
+ mAllowLeftRightSplitInPortrait, mIsLargeScreen,
+ mRootBounds.width() >= mRootBounds.height());
mTempRect.set(mRootBounds);
mRootBounds.set(tmpRect);
+ mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait,
+ mIsLargeScreen, mRootBounds.width() >= mRootBounds.height());
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
- initDividerPosition(mTempRect);
+ initDividerPosition(mTempRect, wasLeftRightSplit);
}
- private void initDividerPosition(Rect oldBounds) {
+ /**
+ * Updates the divider position to the position in the current orientation and bounds using the
+ * snap fraction calculated based on the previous orientation and bounds.
+ */
+ private void initDividerPosition(Rect oldBounds, boolean wasLeftRightSplit) {
final float snapRatio = (float) mDividerPosition
- / (float) (isLandscape(oldBounds) ? oldBounds.width() : oldBounds.height());
+ / (float) (wasLeftRightSplit ? oldBounds.width() : oldBounds.height());
// Estimate position by previous ratio.
final float length =
- (float) (isLandscape() ? mRootBounds.width() : mRootBounds.height());
+ (float) (mIsLeftRightSplit ? mRootBounds.width() : mRootBounds.height());
final int estimatePosition = (int) (length * snapRatio);
// Init divider position by estimated position using current bounds snap algorithm.
mDividerPosition = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(
@@ -376,8 +396,7 @@
dividerBounds.set(mRootBounds);
bounds1.set(mRootBounds);
bounds2.set(mRootBounds);
- final boolean isLandscape = isLandscape(mRootBounds);
- if (isLandscape) {
+ if (mIsLeftRightSplit) {
position += mRootBounds.left;
dividerBounds.left = position - mDividerInsets;
dividerBounds.right = dividerBounds.left + mDividerWindowWidth;
@@ -393,7 +412,7 @@
DockedDividerUtils.sanitizeStackBounds(bounds1, true /** topLeft */);
DockedDividerUtils.sanitizeStackBounds(bounds2, false /** topLeft */);
if (setEffectBounds) {
- mSurfaceEffectPolicy.applyDividerPosition(position, isLandscape);
+ mSurfaceEffectPolicy.applyDividerPosition(position, mIsLeftRightSplit);
}
}
@@ -563,13 +582,12 @@
}
private DividerSnapAlgorithm getSnapAlgorithm(Context context, Rect rootBounds) {
- final boolean isLandscape = isLandscape(rootBounds);
final Rect insets = getDisplayStableInsets(context);
// Make split axis insets value same as the larger one to avoid bounds1 and bounds2
// have difference for avoiding size-compat mode when switching unresizable apps in
// landscape while they are letterboxed.
- if (!isLandscape) {
+ if (!mIsLeftRightSplit) {
final int largerInsets = Math.max(insets.top, insets.bottom);
insets.set(insets.left, largerInsets, insets.right, largerInsets);
}
@@ -579,9 +597,9 @@
rootBounds.width(),
rootBounds.height(),
mDividerSize,
- !isLandscape,
+ !mIsLeftRightSplit,
insets,
- isLandscape ? DOCKED_LEFT : DOCKED_TOP /* dockSide */);
+ mIsLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP /* dockSide */);
}
/** Fling divider from current position to end or start position then exit */
@@ -643,13 +661,12 @@
/** Switch both surface position with animation. */
public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1,
SurfaceControl leash2, Consumer<Rect> finishCallback) {
- final boolean isLandscape = isLandscape();
final Rect insets = getDisplayStableInsets(mContext);
- insets.set(isLandscape ? insets.left : 0, isLandscape ? 0 : insets.top,
- isLandscape ? insets.right : 0, isLandscape ? 0 : insets.bottom);
+ insets.set(mIsLeftRightSplit ? insets.left : 0, mIsLeftRightSplit ? 0 : insets.top,
+ mIsLeftRightSplit ? insets.right : 0, mIsLeftRightSplit ? 0 : insets.bottom);
final int dividerPos = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(
- isLandscape ? mBounds2.width() : mBounds2.height()).position;
+ mIsLeftRightSplit ? mBounds2.width() : mBounds2.height()).position;
final Rect distBounds1 = new Rect();
final Rect distBounds2 = new Rect();
final Rect distDividerBounds = new Rect();
@@ -740,15 +757,12 @@
.toRect();
}
- private static boolean isLandscape(Rect bounds) {
- return bounds.width() > bounds.height();
- }
-
/**
- * Return if this layout is landscape.
+ * @return {@code true} if we should create a left-right split, {@code false} if we should
+ * create a top-bottom split.
*/
- public boolean isLandscape() {
- return isLandscape(mRootBounds);
+ public boolean isLeftRightSplit() {
+ return mIsLeftRightSplit;
}
/** Apply recorded surface layout to the {@link SurfaceControl.Transaction}. */
@@ -850,9 +864,13 @@
/** Dumps the current split bounds recorded in this layout. */
public void dump(@NonNull PrintWriter pw, String prefix) {
- pw.println(prefix + "bounds1=" + mBounds1.toShortString());
- pw.println(prefix + "dividerBounds=" + mDividerBounds.toShortString());
- pw.println(prefix + "bounds2=" + mBounds2.toShortString());
+ final String innerPrefix = prefix + "\t";
+ pw.println(prefix + TAG + ":");
+ pw.println(innerPrefix + "mAllowLeftRightSplitInPortrait=" + mAllowLeftRightSplitInPortrait);
+ pw.println(innerPrefix + "mIsLeftRightSplit=" + mIsLeftRightSplit);
+ pw.println(innerPrefix + "bounds1=" + mBounds1.toShortString());
+ pw.println(innerPrefix + "dividerBounds=" + mDividerBounds.toShortString());
+ pw.println(innerPrefix + "bounds2=" + mBounds2.toShortString());
}
/** Handles layout change event. */
@@ -937,32 +955,32 @@
* Applies a parallax to the task to hint dismissing progress.
*
* @param position the split position to apply dismissing parallax effect
- * @param isLandscape indicates whether it's splitting horizontally or vertically
+ * @param isLeftRightSplit indicates whether it's splitting horizontally or vertically
*/
- void applyDividerPosition(int position, boolean isLandscape) {
+ void applyDividerPosition(int position, boolean isLeftRightSplit) {
mDismissingSide = DOCKED_INVALID;
mParallaxOffset.set(0, 0);
mDismissingDimValue = 0;
int totalDismissingDistance = 0;
if (position < mDividerSnapAlgorithm.getFirstSplitTarget().position) {
- mDismissingSide = isLandscape ? DOCKED_LEFT : DOCKED_TOP;
+ mDismissingSide = isLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP;
totalDismissingDistance = mDividerSnapAlgorithm.getDismissStartTarget().position
- mDividerSnapAlgorithm.getFirstSplitTarget().position;
} else if (position > mDividerSnapAlgorithm.getLastSplitTarget().position) {
- mDismissingSide = isLandscape ? DOCKED_RIGHT : DOCKED_BOTTOM;
+ mDismissingSide = isLeftRightSplit ? DOCKED_RIGHT : DOCKED_BOTTOM;
totalDismissingDistance = mDividerSnapAlgorithm.getLastSplitTarget().position
- mDividerSnapAlgorithm.getDismissEndTarget().position;
}
- final boolean topLeftShrink = isLandscape
+ final boolean topLeftShrink = isLeftRightSplit
? position < mWinBounds1.right : position < mWinBounds1.bottom;
if (topLeftShrink) {
- mShrinkSide = isLandscape ? DOCKED_LEFT : DOCKED_TOP;
+ mShrinkSide = isLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP;
mContentBounds.set(mWinBounds1);
mSurfaceBounds.set(mBounds1);
} else {
- mShrinkSide = isLandscape ? DOCKED_RIGHT : DOCKED_BOTTOM;
+ mShrinkSide = isLeftRightSplit ? DOCKED_RIGHT : DOCKED_BOTTOM;
mContentBounds.set(mWinBounds2);
mSurfaceBounds.set(mBounds2);
}
@@ -973,7 +991,7 @@
mDismissingDimValue = DIM_INTERPOLATOR.getInterpolation(fraction);
if (mParallaxType == PARALLAX_DISMISSING) {
fraction = calculateParallaxDismissingFraction(fraction, mDismissingSide);
- if (isLandscape) {
+ if (isLeftRightSplit) {
mParallaxOffset.x = (int) (fraction * totalDismissingDistance);
} else {
mParallaxOffset.y = (int) (fraction * totalDismissingDistance);
@@ -982,7 +1000,7 @@
}
if (mParallaxType == PARALLAX_ALIGN_CENTER) {
- if (isLandscape) {
+ if (isLeftRightSplit) {
mParallaxOffset.x =
(mSurfaceBounds.width() - mContentBounds.width()) / 2;
} else {
@@ -1129,7 +1147,7 @@
// Calculate target bounds offset for IME
mLastYOffset = mYOffsetForIme;
final boolean needOffset = imeTargetPosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
- && !isFloating && !isLandscape(mRootBounds) && mImeShown;
+ && !isFloating && !mIsLeftRightSplit && mImeShown;
mTargetYOffset = needOffset ? getTargetYOffset() : 0;
if (mTargetYOffset != mLastYOffset) {
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 d7ea1c0..0693543 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,6 +16,8 @@
package com.android.wm.shell.common.split;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+
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;
@@ -25,9 +27,14 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.PendingIntent;
+import android.content.Context;
import android.content.Intent;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
import com.android.internal.util.ArrayUtils;
+import com.android.wm.shell.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
/** Helper utility class for split screen components to use. */
@@ -94,4 +101,38 @@
public static String splitFailureMessage(String caller, String reason) {
return "(" + caller + ") Splitscreen aborted: " + reason;
}
+
+ /**
+ * Returns whether left/right split is allowed in portrait.
+ */
+ public static boolean allowLeftRightSplitInPortrait(Resources res) {
+ return Flags.enableLeftRightSplitInPortrait() && res.getBoolean(
+ com.android.internal.R.bool.config_leftRightSplitInPortrait);
+ }
+
+ /**
+ * Returns whether left/right split is supported in the given configuration.
+ */
+ public static boolean isLeftRightSplit(boolean allowLeftRightSplitInPortrait,
+ Configuration config) {
+ // Compare the max bounds sizes as on near-square devices, the insets may result in a
+ // configuration in the other orientation
+ final boolean isLargeScreen = config.smallestScreenWidthDp >= 600;
+ final Rect maxBounds = config.windowConfiguration.getMaxBounds();
+ final boolean isLandscape = maxBounds.width() >= maxBounds.height();
+ return isLeftRightSplit(allowLeftRightSplitInPortrait, isLargeScreen, isLandscape);
+ }
+
+ /**
+ * Returns whether left/right split is supported in the given configuration state. This method
+ * is useful for cases where we need to calculate this given last saved state.
+ */
+ public static boolean isLeftRightSplit(boolean allowLeftRightSplitInPortrait,
+ boolean isLargeScreen, boolean isLandscape) {
+ if (allowLeftRightSplitInPortrait && isLargeScreen) {
+ return !isLandscape;
+ } else {
+ return isLandscape;
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 0bf8ec3..fdfb6f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -94,6 +94,7 @@
private ShellExecutor mMainExecutor;
private ArrayList<DragAndDropListener> mListeners = new ArrayList<>();
+ // Map of displayId -> per-display info
private final SparseArray<PerDisplay> mDisplayDropTargets = new SparseArray<>();
/**
@@ -362,7 +363,7 @@
*/
private boolean isReadyToHandleDrag() {
for (int i = 0; i < mDisplayDropTargets.size(); i++) {
- if (mDisplayDropTargets.valueAt(i).mHasDrawn) {
+ if (mDisplayDropTargets.valueAt(i).hasDrawn) {
return true;
}
}
@@ -398,8 +399,13 @@
* Dumps information about this controller.
*/
public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
- pw.println(prefix + " listeners=" + mListeners.size());
+ pw.println(innerPrefix + "listeners=" + mListeners.size());
+ pw.println(innerPrefix + "Per display:");
+ for (int i = 0; i < mDisplayDropTargets.size(); i++) {
+ mDisplayDropTargets.valueAt(i).dump(pw, innerPrefix);
+ }
}
/**
@@ -440,7 +446,7 @@
final FrameLayout rootView;
final DragLayout dragLayout;
// Tracks whether the window has fully drawn since it was last made visible
- boolean mHasDrawn;
+ boolean hasDrawn;
boolean isHandlingDrag;
// A count of the number of active drags in progress to ensure that we only hide the window
@@ -464,17 +470,29 @@
rootView.setVisibility(visibility);
if (visibility == View.VISIBLE) {
rootView.requestApplyInsets();
- if (!mHasDrawn && rootView.getViewRootImpl() != null) {
+ if (!hasDrawn && rootView.getViewRootImpl() != null) {
rootView.getViewRootImpl().registerRtFrameCallback(this);
}
} else {
- mHasDrawn = false;
+ hasDrawn = false;
}
}
@Override
public void onFrameDraw(long frame) {
- mHasDrawn = true;
+ hasDrawn = true;
+ }
+
+ /**
+ * Dumps information about this display's shell drop target.
+ */
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(innerPrefix + "displayId=" + displayId);
+ pw.println(innerPrefix + "hasDrawn=" + hasDrawn);
+ pw.println(innerPrefix + "isHandlingDrag=" + isHandlingDrag);
+ pw.println(innerPrefix + "activeDragCount=" + activeDragCount);
+ dragLayout.dump(pw, innerPrefix);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index e70768b..162ce19 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -138,7 +138,7 @@
final Rect displayRegion = new Rect(l, t, l + iw, t + ih);
final Rect fullscreenDrawRegion = new Rect(displayRegion);
final Rect fullscreenHitRegion = new Rect(displayRegion);
- final boolean inLandscape = mSession.displayLayout.isLandscape();
+ final boolean isLeftRightSplit = mSplitScreen != null && mSplitScreen.isLeftRightSplit();
final boolean inSplitScreen = mSplitScreen != null && mSplitScreen.isSplitScreenVisible();
final float dividerWidth = mContext.getResources().getDimensionPixelSize(
R.dimen.split_divider_bar_width);
@@ -155,7 +155,7 @@
topOrLeftBounds.intersect(displayRegion);
bottomOrRightBounds.intersect(displayRegion);
- if (inLandscape) {
+ if (isLeftRightSplit) {
final Rect leftHitRegion = new Rect();
final Rect rightHitRegion = new Rect();
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 205a455..445ba89 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
@@ -20,6 +20,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
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 com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -47,14 +48,18 @@
import android.view.WindowInsets.Type;
import android.widget.LinearLayout;
+import androidx.annotation.NonNull;
+
import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
+import java.io.PrintWriter;
import java.util.ArrayList;
/**
@@ -74,6 +79,11 @@
private final StatusBarManager mStatusBarManager;
private final Configuration mLastConfiguration = new Configuration();
+ // Whether this device supports left/right split in portrait
+ private final boolean mAllowLeftRightSplitInPortrait;
+ // Whether the device is currently in left/right split mode
+ private boolean mIsLeftRightSplit;
+
private DragAndDropPolicy.Target mCurrentTarget = null;
private DropZoneView mDropZoneView1;
private DropZoneView mDropZoneView2;
@@ -106,17 +116,18 @@
setLayoutDirection(LAYOUT_DIRECTION_LTR);
mDropZoneView1 = new DropZoneView(context);
mDropZoneView2 = new DropZoneView(context);
- addView(mDropZoneView1, new LinearLayout.LayoutParams(MATCH_PARENT,
- MATCH_PARENT));
- addView(mDropZoneView2, new LinearLayout.LayoutParams(MATCH_PARENT,
- MATCH_PARENT));
+ addView(mDropZoneView1, new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ addView(mDropZoneView2, new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
((LayoutParams) mDropZoneView1.getLayoutParams()).weight = 1;
((LayoutParams) mDropZoneView2.getLayoutParams()).weight = 1;
- int orientation = getResources().getConfiguration().orientation;
- setOrientation(orientation == Configuration.ORIENTATION_LANDSCAPE
- ? LinearLayout.HORIZONTAL
- : LinearLayout.VERTICAL);
- updateContainerMargins(getResources().getConfiguration().orientation);
+ // We don't use the configuration orientation here to determine landscape because
+ // near-square devices may report the same orietation with insets taken into account
+ mAllowLeftRightSplitInPortrait = SplitScreenUtils.allowLeftRightSplitInPortrait(
+ context.getResources());
+ mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait,
+ getResources().getConfiguration());
+ setOrientation(mIsLeftRightSplit ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
+ updateContainerMargins(mIsLeftRightSplit);
}
@Override
@@ -124,11 +135,12 @@
mInsets = insets.getInsets(Type.tappableElement() | Type.displayCutout());
recomputeDropTargets();
- final int orientation = getResources().getConfiguration().orientation;
- if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ boolean isLeftRightSplit = mSplitScreenController != null
+ && mSplitScreenController.isLeftRightSplit();
+ if (isLeftRightSplit) {
mDropZoneView1.setBottomInset(mInsets.bottom);
mDropZoneView2.setBottomInset(mInsets.bottom);
- } else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+ } else {
mDropZoneView1.setBottomInset(0);
mDropZoneView2.setBottomInset(mInsets.bottom);
}
@@ -136,14 +148,12 @@
}
public void onConfigChanged(Configuration newConfig) {
- if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE
- && getOrientation() != HORIZONTAL) {
- setOrientation(LinearLayout.HORIZONTAL);
- updateContainerMargins(newConfig.orientation);
- } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
- && getOrientation() != VERTICAL) {
- setOrientation(LinearLayout.VERTICAL);
- updateContainerMargins(newConfig.orientation);
+ boolean isLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait,
+ newConfig);
+ if (isLeftRightSplit != mIsLeftRightSplit) {
+ mIsLeftRightSplit = isLeftRightSplit;
+ setOrientation(mIsLeftRightSplit ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
+ updateContainerMargins(mIsLeftRightSplit);
}
final int diff = newConfig.diff(mLastConfiguration);
@@ -162,14 +172,14 @@
mDropZoneView2.setContainerMargin(0, 0, 0, 0);
}
- private void updateContainerMargins(int orientation) {
+ private void updateContainerMargins(boolean isLeftRightSplit) {
final float halfMargin = mDisplayMargin / 2f;
- if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ if (isLeftRightSplit) {
mDropZoneView1.setContainerMargin(
mDisplayMargin, mDisplayMargin, halfMargin, mDisplayMargin);
mDropZoneView2.setContainerMargin(
halfMargin, mDisplayMargin, mDisplayMargin, mDisplayMargin);
- } else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+ } else {
mDropZoneView1.setContainerMargin(
mDisplayMargin, mDisplayMargin, mDisplayMargin, halfMargin);
mDropZoneView2.setContainerMargin(
@@ -257,23 +267,21 @@
* @param bounds2 bounds to apply to the second dropzone view, null if split in half.
*/
private void updateDropZoneSizes(Rect bounds1, Rect bounds2) {
- final int orientation = getResources().getConfiguration().orientation;
- final boolean isPortrait = orientation == Configuration.ORIENTATION_PORTRAIT;
final int halfDivider = mDividerSize / 2;
final LinearLayout.LayoutParams dropZoneView1 =
(LayoutParams) mDropZoneView1.getLayoutParams();
final LinearLayout.LayoutParams dropZoneView2 =
(LayoutParams) mDropZoneView2.getLayoutParams();
- if (isPortrait) {
- dropZoneView1.width = MATCH_PARENT;
- dropZoneView2.width = MATCH_PARENT;
- dropZoneView1.height = bounds1 != null ? bounds1.height() + halfDivider : MATCH_PARENT;
- dropZoneView2.height = bounds2 != null ? bounds2.height() + halfDivider : MATCH_PARENT;
- } else {
+ if (mIsLeftRightSplit) {
dropZoneView1.width = bounds1 != null ? bounds1.width() + halfDivider : MATCH_PARENT;
dropZoneView2.width = bounds2 != null ? bounds2.width() + halfDivider : MATCH_PARENT;
dropZoneView1.height = MATCH_PARENT;
dropZoneView2.height = MATCH_PARENT;
+ } else {
+ dropZoneView1.width = MATCH_PARENT;
+ dropZoneView2.width = MATCH_PARENT;
+ dropZoneView1.height = bounds1 != null ? bounds1.height() + halfDivider : MATCH_PARENT;
+ dropZoneView2.height = bounds2 != null ? bounds2.height() + halfDivider : MATCH_PARENT;
}
dropZoneView1.weight = bounds1 != null ? 0 : 1;
dropZoneView2.weight = bounds2 != null ? 0 : 1;
@@ -371,7 +379,7 @@
// Reset the state if we previously force-ignore the bottom margin
mDropZoneView1.setForceIgnoreBottomMargin(false);
mDropZoneView2.setForceIgnoreBottomMargin(false);
- updateContainerMargins(getResources().getConfiguration().orientation);
+ updateContainerMargins(mIsLeftRightSplit);
mCurrentTarget = null;
}
@@ -481,4 +489,19 @@
final int taskBgColor = taskInfo.taskDescription.getBackgroundColor();
return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb();
}
+
+ /**
+ * Dumps information about this drag layout.
+ */
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + "DragLayout:");
+ pw.println(innerPrefix + "mIsLeftRightSplitInPortrait=" + mAllowLeftRightSplitInPortrait);
+ pw.println(innerPrefix + "mIsLeftRightSplit=" + mIsLeftRightSplit);
+ pw.println(innerPrefix + "mDisplayMargin=" + mDisplayMargin);
+ pw.println(innerPrefix + "mDividerSize=" + mDividerSize);
+ pw.println(innerPrefix + "mIsShowing=" + mIsShowing);
+ pw.println(innerPrefix + "mHasDropped=" + mHasDropped);
+ pw.println(innerPrefix + "mCurrentTarget=" + mCurrentTarget);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
index 478b6a9..353d702 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
@@ -18,31 +18,17 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.content.ClipDescription.EXTRA_PENDING_INTENT;
-import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
-import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
-import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
-import static android.content.Intent.EXTRA_USER;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
-import android.app.PendingIntent;
import android.app.WindowConfiguration;
import android.content.ClipData;
-import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
-import android.net.Uri;
-import android.os.UserHandle;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.Nullable;
import com.android.wm.shell.common.DisplayLayout;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 664d4491..37b24e5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -366,6 +366,14 @@
return mStageCoordinator.getStageOfTask(taskId);
}
+ /**
+ * @return {@code true} if we should create a left-right split, {@code false} if we should
+ * create a top-bottom split.
+ */
+ public boolean isLeftRightSplit() {
+ return mStageCoordinator.isLeftRightSplit();
+ }
+
/** Check split is foreground and task is under split or not by taskId. */
public boolean isTaskInSplitScreenForeground(int taskId) {
return isTaskInSplitScreen(taskId) && isSplitScreenVisible();
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 7a4834c..36e0eb4 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
@@ -1301,7 +1301,7 @@
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason);
mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
+ mSplitLayout.isLeftRightSplit());
}
void setSideStagePosition(@SplitPosition int sideStagePosition,
@@ -1659,7 +1659,7 @@
mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
getMainStagePosition(), mMainStage.getTopChildTaskUid(),
getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
+ mSplitLayout.isLeftRightSplit());
}
}
@@ -1749,10 +1749,10 @@
}
if (stage == STAGE_TYPE_MAIN) {
mLogger.logMainStageAppChange(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
+ mSplitLayout.isLeftRightSplit());
} else {
mLogger.logSideStageAppChange(getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
+ mSplitLayout.isLeftRightSplit());
}
if (present) {
updateRecentTasksSplitPair();
@@ -2113,7 +2113,7 @@
mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
getMainStagePosition(), mMainStage.getTopChildTaskUid(),
getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
+ mSplitLayout.isLeftRightSplit());
}
}
}
@@ -2205,8 +2205,12 @@
mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
}
- private boolean isLandscape() {
- return mSplitLayout.isLandscape();
+ /**
+ * @return {@code true} if we should create a left-right split, {@code false} if we should
+ * create a top-bottom split.
+ */
+ boolean isLeftRightSplit() {
+ return mSplitLayout.isLeftRightSplit();
}
/**
@@ -3177,6 +3181,7 @@
pw.println(innerPrefix + "mDividerVisible=" + mDividerVisible);
pw.println(innerPrefix + "isSplitActive=" + isSplitActive());
pw.println(innerPrefix + "isSplitVisible=" + isSplitScreenVisible());
+ pw.println(innerPrefix + "isLeftRightSplit=" + mSplitLayout.isLeftRightSplit());
pw.println(innerPrefix + "MainStage");
pw.println(childPrefix + "stagePosition=" + splitPositionToString(getMainStagePosition()));
pw.println(childPrefix + "isActive=" + mMainStage.isActive());
@@ -3188,10 +3193,7 @@
mSideStage.dump(pw, childPrefix);
pw.println(innerPrefix + "SideStageListener");
mSideStageListener.dump(pw, childPrefix);
- if (mMainStage.isActive()) {
- pw.println(innerPrefix + "SplitLayout");
- mSplitLayout.dump(pw, childPrefix);
- }
+ mSplitLayout.dump(pw, childPrefix);
if (!mPausingTasks.isEmpty()) {
pw.println(childPrefix + "mPausingTasks=" + mPausingTasks);
}
@@ -3243,7 +3245,7 @@
mLogger.logExit(exitReason,
SPLIT_POSITION_UNDEFINED, 0 /* mainStageUid */,
SPLIT_POSITION_UNDEFINED, 0 /* sideStageUid */,
- mSplitLayout.isLandscape());
+ mSplitLayout.isLeftRightSplit());
}
/**
@@ -3256,7 +3258,7 @@
toMainStage ? mMainStage.getTopChildTaskUid() : 0 /* mainStageUid */,
!toMainStage ? getSideStagePosition() : SPLIT_POSITION_UNDEFINED,
!toMainStage ? mSideStage.getTopChildTaskUid() : 0 /* sideStageUid */,
- mSplitLayout.isLandscape());
+ mSplitLayout.isLeftRightSplit());
}
class StageListenerImpl implements StageTaskListener.StageListenerCallbacks {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index 527dc01..1b347e0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -204,6 +204,7 @@
@Test
public void testDragAppOverFullscreenHome_expectOnlyFullscreenTarget() {
+ doReturn(true).when(mSplitScreenStarter).isLeftRightSplit();
setRunningTask(mHomeTask);
DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
mLandscapeDisplayLayout, mActivityClipData);
@@ -219,6 +220,7 @@
@Test
public void testDragAppOverFullscreenApp_expectSplitScreenTargets() {
+ doReturn(true).when(mSplitScreenStarter).isLeftRightSplit();
setRunningTask(mFullscreenAppTask);
DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
mLandscapeDisplayLayout, mActivityClipData);
@@ -239,6 +241,7 @@
@Test
public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenTargets() {
+ doReturn(false).when(mSplitScreenStarter).isLeftRightSplit();
setRunningTask(mFullscreenAppTask);
DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
mPortraitDisplayLayout, mActivityClipData);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenUtilsTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenUtilsTests.java
new file mode 100644
index 0000000..30847d3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenUtilsTests.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 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.splitscreen;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.res.Configuration;
+import android.graphics.Rect;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.split.SplitScreenUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+/** Tests for {@link com.android.wm.shell.common.split.SplitScreenUtils} */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SplitScreenUtilsTests extends ShellTestCase {
+
+ @Test
+ public void testIsLeftRightSplit() {
+ Configuration portraitTablet = new Configuration();
+ portraitTablet.smallestScreenWidthDp = 720;
+ portraitTablet.windowConfiguration.setMaxBounds(new Rect(0, 0, 500, 1000));
+ Configuration landscapeTablet = new Configuration();
+ landscapeTablet.smallestScreenWidthDp = 720;
+ landscapeTablet.windowConfiguration.setMaxBounds(new Rect(0, 0, 1000, 500));
+ Configuration portraitPhone = new Configuration();
+ portraitPhone.smallestScreenWidthDp = 420;
+ portraitPhone.windowConfiguration.setMaxBounds(new Rect(0, 0, 500, 1000));
+ Configuration landscapePhone = new Configuration();
+ landscapePhone.smallestScreenWidthDp = 420;
+ landscapePhone.windowConfiguration.setMaxBounds(new Rect(0, 0, 1000, 500));
+
+ // Allow L/R split in portrait = false
+ assertTrue(SplitScreenUtils.isLeftRightSplit(false /* allowLeftRightSplitInPortrait */,
+ landscapeTablet));
+ assertTrue(SplitScreenUtils.isLeftRightSplit(false /* allowLeftRightSplitInPortrait */,
+ landscapePhone));
+ assertFalse(SplitScreenUtils.isLeftRightSplit(false /* allowLeftRightSplitInPortrait */,
+ portraitTablet));
+ assertFalse(SplitScreenUtils.isLeftRightSplit(false /* allowLeftRightSplitInPortrait */,
+ portraitPhone));
+
+ // Allow L/R split in portrait = true, only affects large screens
+ assertFalse(SplitScreenUtils.isLeftRightSplit(true /* allowLeftRightSplitInPortrait */,
+ landscapeTablet));
+ assertTrue(SplitScreenUtils.isLeftRightSplit(true /* allowLeftRightSplitInPortrait */,
+ landscapePhone));
+ assertTrue(SplitScreenUtils.isLeftRightSplit(true /* allowLeftRightSplitInPortrait */,
+ portraitTablet));
+ assertFalse(SplitScreenUtils.isLeftRightSplit(true /* allowLeftRightSplitInPortrait */,
+ portraitPhone));
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index fff65f3..d819261 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -140,7 +140,7 @@
when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
when(mSplitLayout.getBounds2()).thenReturn(mBounds2);
when(mSplitLayout.getRootBounds()).thenReturn(mRootBounds);
- when(mSplitLayout.isLandscape()).thenReturn(false);
+ when(mSplitLayout.isLeftRightSplit()).thenReturn(false);
when(mSplitLayout.applyTaskChanges(any(), any(), any())).thenReturn(true);
when(mSplitLayout.getDividerLeash()).thenReturn(mDividerLeash);