Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt +75 −14 Original line number Diff line number Diff line Loading @@ -18,10 +18,12 @@ package com.android.wm.shell.windowdecor.tiling import android.content.Context import android.content.res.Configuration import android.graphics.Path import android.graphics.PixelFormat import android.graphics.Rect import android.graphics.Region import android.os.Binder import android.util.Size import android.view.LayoutInflater import android.view.MotionEvent import android.view.RoundedCorner Loading @@ -40,7 +42,6 @@ import android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER import android.view.WindowlessWindowManager import com.android.wm.shell.R import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopModeEventLogger import java.util.function.Supplier /** Loading @@ -48,7 +49,7 @@ import java.util.function.Supplier * when two tasks are tiled on left and right to resize them simultaneously. */ class DesktopTilingDividerWindowManager( private val config: Configuration, config: Configuration, private val windowName: String, private val context: Context, private val leash: SurfaceControl, Loading @@ -61,7 +62,11 @@ class DesktopTilingDividerWindowManager( private lateinit var viewHost: SurfaceControlViewHost private var tilingDividerView: TilingDividerView? = null private var dividerShown = false private var handleRegionWidth: Int = -1 private var handleRegionSize: Size = Size( context.resources.getDimensionPixelSize(R.dimen.split_divider_handle_region_width), context.resources.getDimensionPixelSize(R.dimen.split_divider_handle_region_height), ) private var setTouchRegion = true private val maxRoundedCornerRadius = getMaxRoundedCornerRadius() Loading @@ -74,9 +79,62 @@ class DesktopTilingDividerWindowManager( rect.set(dividerBounds) } /** Sets the touch region for the SurfaceControlViewHost. */ fun setTouchRegion(region: Rect) { setTouchRegion(viewHost.windowToken.asBinder(), Region(region)) /** * Sets the touch region for the SurfaceControlViewHost. * * The region includes the area around the handle (for accessibility), the divider itself and * the rounded corners (to prevent click reaching windows behind). */ fun setTouchRegion(handle: Rect, divider: Rect, cornerRadius: Float) { val path = Path() path.fillType = Path.FillType.WINDING // The UI starts on the top-left corner, the region will be: // // cornerLeft cornerRight // c1Top +--------+ // |corners | // c1Bottom +--+ +--+ // | | // handleLeft| | handleRight // handleTop +----+ +----+ // | handle | // handleBot +----+ +----+ // | | // | | // c2Top +--+ +--+ // |corners | // c2Bottom +--------+ val cornerLeft = 0f val centerX = cornerRadius + divider.width() / 2f val centerY = divider.height() val cornerRight = divider.width() + 2 * cornerRadius val handleLeft = centerX - handle.width() / 2f val handleRight = handleLeft + handle.width() val dividerLeft = centerY - divider.width() / 2f val dividerRight = dividerLeft + divider.width() val c1Top = 0f val c1Bottom = cornerRadius val handleTop = centerY - handle.height() / 2f val handleBottom = handleTop + handle.height() val c2Top = divider.height() - cornerRadius val c2Bottom = divider.height().toFloat() // Top corners path.addRect(cornerLeft, c1Top, cornerRight, c1Bottom, Path.Direction.CCW) // Bottom corners path.addRect(cornerLeft, c1Top, cornerRight, c2Bottom, Path.Direction.CCW) // Handle path.addRect(handleLeft, handleTop, handleRight, handleBottom, Path.Direction.CCW) // Divider path.addRect(dividerLeft, c2Top, dividerRight, c2Bottom, Path.Direction.CCW) val clip = Rect(handleLeft.toInt(), c1Top.toInt(), handleRight.toInt(), c2Bottom.toInt()) val region = Region() region.setPath(path, Region(clip)) setTouchRegion(viewHost.windowToken.asBinder(), region) } /** Loading @@ -96,7 +154,7 @@ class DesktopTilingDividerWindowManager( surfaceControlViewHost.setView(dividerView, lp) val tmpDividerBounds = Rect() getDividerBounds(tmpDividerBounds) dividerView.setup(this, tmpDividerBounds) dividerView.setup(this, tmpDividerBounds, handleRegionSize) t.setRelativeLayer(leash, relativeLeash, 1) .setPosition( leash, Loading @@ -112,7 +170,7 @@ class DesktopTilingDividerWindowManager( viewHost = surfaceControlViewHost dividerView.addOnLayoutChangeListener(this) tilingDividerView = dividerView handleRegionWidth = dividerView.handleRegionWidth updateTouchRegion() } /** Hides the divider bar. */ Loading Loading @@ -176,8 +234,8 @@ class DesktopTilingDividerWindowManager( private fun getWindowManagerParams(): WindowManager.LayoutParams { val lp = WindowManager.LayoutParams( dividerBounds.width() + 2 * maxRoundedCornerRadius, dividerBounds.height(), /* w= */ dividerBounds.width() + 2 * maxRoundedCornerRadius, /* h= */ dividerBounds.height(), TYPE_DOCK_DIVIDER, FLAG_NOT_FOCUSABLE or FLAG_NOT_TOUCH_MODAL or Loading Loading @@ -216,13 +274,16 @@ class DesktopTilingDividerWindowManager( ) { if (!setTouchRegion) return val startX = (dividerBounds.width() - handleRegionWidth) / 2 val startY = 0 val tempRect = Rect(startX, startY, startX + handleRegionWidth, dividerBounds.height()) setTouchRegion(tempRect) updateTouchRegion() setTouchRegion = false } private fun updateTouchRegion() { val startX = -handleRegionSize.width / 2 val handle = Rect(startX, 0, startX + handleRegionSize.width, dividerBounds.height()) setTouchRegion(handle, dividerBounds, maxRoundedCornerRadius.toFloat()) } private fun setSlippery(slippery: Boolean) { val lp = tilingDividerView?.layoutParams as WindowManager.LayoutParams val isSlippery = (lp.flags and FLAG_SLIPPERY) != 0 Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt +18 −13 Original line number Diff line number Diff line Loading @@ -21,8 +21,10 @@ import android.graphics.Paint import android.graphics.Rect import android.provider.DeviceConfig import android.util.AttributeSet import android.util.Size import android.view.MotionEvent import android.view.PointerIcon import android.view.RoundedCorner import android.view.View import android.view.ViewConfiguration import android.widget.FrameLayout Loading @@ -42,6 +44,7 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion private lateinit var callback: DividerMoveCallback private lateinit var handle: DividerHandleView private lateinit var corners: DividerRoundedCorner private var cornersRadius: Int = 0 private var touchElevation = 0 private var moving = false Loading @@ -49,8 +52,7 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion var handleRegionWidth: Int = 0 private var handleRegionHeight = 0 private var lastAcceptedPos = 0 @VisibleForTesting var handleStartY = 0 @VisibleForTesting var handleEndY = 0 @VisibleForTesting var handleY: IntRange = 0..0 private var canResize = false private var resized = false /** Loading Loading @@ -79,16 +81,19 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion ) : super(context, attrs, defStyleAttr, defStyleRes) /** Sets up essential dependencies of the divider bar. */ fun setup(dividerMoveCallback: DividerMoveCallback, dividerBounds: Rect) { fun setup( dividerMoveCallback: DividerMoveCallback, dividerBounds: Rect, handleRegionSize: Size, ) { callback = dividerMoveCallback this.dividerBounds.set(dividerBounds) handle.setIsLeftRightSplit(true) corners.setIsLeftRightSplit(true) handleRegionHeight = resources.getDimensionPixelSize(R.dimen.split_divider_handle_region_width) handleRegionWidth = resources.getDimensionPixelSize(R.dimen.split_divider_handle_region_height) handleRegionHeight = handleRegionSize.height handleRegionWidth = handleRegionSize.width cornersRadius = context.display.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT)?.radius ?: 0 initHandleYCoordinates() dragDetector = DragDetector( Loading Loading @@ -241,17 +246,17 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion return true } private fun isWithinHandleRegion(touchYPos: Int): Boolean { return touchYPos in handleStartY..handleEndY } private fun isWithinHandleRegion(touchYPos: Int): Boolean = touchYPos in handleY private fun initHandleYCoordinates() { handleStartY = (dividerBounds.height() - handleRegionHeight) / 2 handleEndY = handleStartY + handleRegionHeight val handleStartY = (dividerBounds.height() - handleRegionHeight) / 2 val handleEndY = handleStartY + handleRegionHeight handleY = handleStartY..handleEndY } companion object { const val TOUCH_ANIMATION_DURATION: Long = 150 const val TOUCH_RELEASE_ANIMATION_DURATION: Long = 200 private val TAG = TilingDividerView::class.java.simpleName } } libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt +1 −1 Original line number Diff line number Diff line Loading @@ -122,6 +122,6 @@ class DesktopTilingDividerWindowManagerTest : ShellTestCase() { companion object { private val BOUNDS = Rect(1, 2, 3, 4) private val CORNER_RADIUS = 28 private const val CORNER_RADIUS = 28 } } libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt +12 −4 Original line number Diff line number Diff line Loading @@ -19,9 +19,12 @@ package com.android.wm.shell.windowdecor.tiling import android.graphics.Rect import android.os.SystemClock import android.testing.AndroidTestingRunner import android.util.Size import android.view.Display import android.view.InputDevice import android.view.LayoutInflater import android.view.MotionEvent import android.view.RoundedCorner import android.view.View import androidx.test.annotation.UiThreadTest import androidx.test.filters.SmallTest Loading @@ -46,16 +49,19 @@ class TilingDividerViewTest : ShellTestCase() { private val dividerMoveCallbackMock = mock<DividerMoveCallback>() private val viewMock = mock<View>() private val display = mock<Display>() private val roundedCorner = mock<RoundedCorner>() @Before @UiThreadTest fun setUp() { whenever(display.getRoundedCorner(any())).thenReturn(roundedCorner) whenever(roundedCorner.radius).thenReturn(CORNER_RADIUS) tilingDividerView = LayoutInflater.from(mContext).inflate(R.layout.tiling_split_divider, /* root= */ null) as TilingDividerView tilingDividerView.setup(dividerMoveCallbackMock, BOUNDS) tilingDividerView.handleStartY = 0 tilingDividerView.handleEndY = 1500 tilingDividerView.setup(dividerMoveCallbackMock, DIVIDER_BOUNDS, HANDLE_SIZE) tilingDividerView.handleY = 0..1500 } @Test Loading Loading @@ -130,6 +136,8 @@ class TilingDividerViewTest : ShellTestCase() { } companion object { private val BOUNDS = Rect(0, 0, 1500, 1500) private val DIVIDER_BOUNDS = Rect(15, 0, 35, 1500) private val HANDLE_SIZE = Size(800, 300) private const val CORNER_RADIUS = 15 } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt +75 −14 Original line number Diff line number Diff line Loading @@ -18,10 +18,12 @@ package com.android.wm.shell.windowdecor.tiling import android.content.Context import android.content.res.Configuration import android.graphics.Path import android.graphics.PixelFormat import android.graphics.Rect import android.graphics.Region import android.os.Binder import android.util.Size import android.view.LayoutInflater import android.view.MotionEvent import android.view.RoundedCorner Loading @@ -40,7 +42,6 @@ import android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER import android.view.WindowlessWindowManager import com.android.wm.shell.R import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopModeEventLogger import java.util.function.Supplier /** Loading @@ -48,7 +49,7 @@ import java.util.function.Supplier * when two tasks are tiled on left and right to resize them simultaneously. */ class DesktopTilingDividerWindowManager( private val config: Configuration, config: Configuration, private val windowName: String, private val context: Context, private val leash: SurfaceControl, Loading @@ -61,7 +62,11 @@ class DesktopTilingDividerWindowManager( private lateinit var viewHost: SurfaceControlViewHost private var tilingDividerView: TilingDividerView? = null private var dividerShown = false private var handleRegionWidth: Int = -1 private var handleRegionSize: Size = Size( context.resources.getDimensionPixelSize(R.dimen.split_divider_handle_region_width), context.resources.getDimensionPixelSize(R.dimen.split_divider_handle_region_height), ) private var setTouchRegion = true private val maxRoundedCornerRadius = getMaxRoundedCornerRadius() Loading @@ -74,9 +79,62 @@ class DesktopTilingDividerWindowManager( rect.set(dividerBounds) } /** Sets the touch region for the SurfaceControlViewHost. */ fun setTouchRegion(region: Rect) { setTouchRegion(viewHost.windowToken.asBinder(), Region(region)) /** * Sets the touch region for the SurfaceControlViewHost. * * The region includes the area around the handle (for accessibility), the divider itself and * the rounded corners (to prevent click reaching windows behind). */ fun setTouchRegion(handle: Rect, divider: Rect, cornerRadius: Float) { val path = Path() path.fillType = Path.FillType.WINDING // The UI starts on the top-left corner, the region will be: // // cornerLeft cornerRight // c1Top +--------+ // |corners | // c1Bottom +--+ +--+ // | | // handleLeft| | handleRight // handleTop +----+ +----+ // | handle | // handleBot +----+ +----+ // | | // | | // c2Top +--+ +--+ // |corners | // c2Bottom +--------+ val cornerLeft = 0f val centerX = cornerRadius + divider.width() / 2f val centerY = divider.height() val cornerRight = divider.width() + 2 * cornerRadius val handleLeft = centerX - handle.width() / 2f val handleRight = handleLeft + handle.width() val dividerLeft = centerY - divider.width() / 2f val dividerRight = dividerLeft + divider.width() val c1Top = 0f val c1Bottom = cornerRadius val handleTop = centerY - handle.height() / 2f val handleBottom = handleTop + handle.height() val c2Top = divider.height() - cornerRadius val c2Bottom = divider.height().toFloat() // Top corners path.addRect(cornerLeft, c1Top, cornerRight, c1Bottom, Path.Direction.CCW) // Bottom corners path.addRect(cornerLeft, c1Top, cornerRight, c2Bottom, Path.Direction.CCW) // Handle path.addRect(handleLeft, handleTop, handleRight, handleBottom, Path.Direction.CCW) // Divider path.addRect(dividerLeft, c2Top, dividerRight, c2Bottom, Path.Direction.CCW) val clip = Rect(handleLeft.toInt(), c1Top.toInt(), handleRight.toInt(), c2Bottom.toInt()) val region = Region() region.setPath(path, Region(clip)) setTouchRegion(viewHost.windowToken.asBinder(), region) } /** Loading @@ -96,7 +154,7 @@ class DesktopTilingDividerWindowManager( surfaceControlViewHost.setView(dividerView, lp) val tmpDividerBounds = Rect() getDividerBounds(tmpDividerBounds) dividerView.setup(this, tmpDividerBounds) dividerView.setup(this, tmpDividerBounds, handleRegionSize) t.setRelativeLayer(leash, relativeLeash, 1) .setPosition( leash, Loading @@ -112,7 +170,7 @@ class DesktopTilingDividerWindowManager( viewHost = surfaceControlViewHost dividerView.addOnLayoutChangeListener(this) tilingDividerView = dividerView handleRegionWidth = dividerView.handleRegionWidth updateTouchRegion() } /** Hides the divider bar. */ Loading Loading @@ -176,8 +234,8 @@ class DesktopTilingDividerWindowManager( private fun getWindowManagerParams(): WindowManager.LayoutParams { val lp = WindowManager.LayoutParams( dividerBounds.width() + 2 * maxRoundedCornerRadius, dividerBounds.height(), /* w= */ dividerBounds.width() + 2 * maxRoundedCornerRadius, /* h= */ dividerBounds.height(), TYPE_DOCK_DIVIDER, FLAG_NOT_FOCUSABLE or FLAG_NOT_TOUCH_MODAL or Loading Loading @@ -216,13 +274,16 @@ class DesktopTilingDividerWindowManager( ) { if (!setTouchRegion) return val startX = (dividerBounds.width() - handleRegionWidth) / 2 val startY = 0 val tempRect = Rect(startX, startY, startX + handleRegionWidth, dividerBounds.height()) setTouchRegion(tempRect) updateTouchRegion() setTouchRegion = false } private fun updateTouchRegion() { val startX = -handleRegionSize.width / 2 val handle = Rect(startX, 0, startX + handleRegionSize.width, dividerBounds.height()) setTouchRegion(handle, dividerBounds, maxRoundedCornerRadius.toFloat()) } private fun setSlippery(slippery: Boolean) { val lp = tilingDividerView?.layoutParams as WindowManager.LayoutParams val isSlippery = (lp.flags and FLAG_SLIPPERY) != 0 Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt +18 −13 Original line number Diff line number Diff line Loading @@ -21,8 +21,10 @@ import android.graphics.Paint import android.graphics.Rect import android.provider.DeviceConfig import android.util.AttributeSet import android.util.Size import android.view.MotionEvent import android.view.PointerIcon import android.view.RoundedCorner import android.view.View import android.view.ViewConfiguration import android.widget.FrameLayout Loading @@ -42,6 +44,7 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion private lateinit var callback: DividerMoveCallback private lateinit var handle: DividerHandleView private lateinit var corners: DividerRoundedCorner private var cornersRadius: Int = 0 private var touchElevation = 0 private var moving = false Loading @@ -49,8 +52,7 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion var handleRegionWidth: Int = 0 private var handleRegionHeight = 0 private var lastAcceptedPos = 0 @VisibleForTesting var handleStartY = 0 @VisibleForTesting var handleEndY = 0 @VisibleForTesting var handleY: IntRange = 0..0 private var canResize = false private var resized = false /** Loading Loading @@ -79,16 +81,19 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion ) : super(context, attrs, defStyleAttr, defStyleRes) /** Sets up essential dependencies of the divider bar. */ fun setup(dividerMoveCallback: DividerMoveCallback, dividerBounds: Rect) { fun setup( dividerMoveCallback: DividerMoveCallback, dividerBounds: Rect, handleRegionSize: Size, ) { callback = dividerMoveCallback this.dividerBounds.set(dividerBounds) handle.setIsLeftRightSplit(true) corners.setIsLeftRightSplit(true) handleRegionHeight = resources.getDimensionPixelSize(R.dimen.split_divider_handle_region_width) handleRegionWidth = resources.getDimensionPixelSize(R.dimen.split_divider_handle_region_height) handleRegionHeight = handleRegionSize.height handleRegionWidth = handleRegionSize.width cornersRadius = context.display.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT)?.radius ?: 0 initHandleYCoordinates() dragDetector = DragDetector( Loading Loading @@ -241,17 +246,17 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion return true } private fun isWithinHandleRegion(touchYPos: Int): Boolean { return touchYPos in handleStartY..handleEndY } private fun isWithinHandleRegion(touchYPos: Int): Boolean = touchYPos in handleY private fun initHandleYCoordinates() { handleStartY = (dividerBounds.height() - handleRegionHeight) / 2 handleEndY = handleStartY + handleRegionHeight val handleStartY = (dividerBounds.height() - handleRegionHeight) / 2 val handleEndY = handleStartY + handleRegionHeight handleY = handleStartY..handleEndY } companion object { const val TOUCH_ANIMATION_DURATION: Long = 150 const val TOUCH_RELEASE_ANIMATION_DURATION: Long = 200 private val TAG = TilingDividerView::class.java.simpleName } }
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt +1 −1 Original line number Diff line number Diff line Loading @@ -122,6 +122,6 @@ class DesktopTilingDividerWindowManagerTest : ShellTestCase() { companion object { private val BOUNDS = Rect(1, 2, 3, 4) private val CORNER_RADIUS = 28 private const val CORNER_RADIUS = 28 } }
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt +12 −4 Original line number Diff line number Diff line Loading @@ -19,9 +19,12 @@ package com.android.wm.shell.windowdecor.tiling import android.graphics.Rect import android.os.SystemClock import android.testing.AndroidTestingRunner import android.util.Size import android.view.Display import android.view.InputDevice import android.view.LayoutInflater import android.view.MotionEvent import android.view.RoundedCorner import android.view.View import androidx.test.annotation.UiThreadTest import androidx.test.filters.SmallTest Loading @@ -46,16 +49,19 @@ class TilingDividerViewTest : ShellTestCase() { private val dividerMoveCallbackMock = mock<DividerMoveCallback>() private val viewMock = mock<View>() private val display = mock<Display>() private val roundedCorner = mock<RoundedCorner>() @Before @UiThreadTest fun setUp() { whenever(display.getRoundedCorner(any())).thenReturn(roundedCorner) whenever(roundedCorner.radius).thenReturn(CORNER_RADIUS) tilingDividerView = LayoutInflater.from(mContext).inflate(R.layout.tiling_split_divider, /* root= */ null) as TilingDividerView tilingDividerView.setup(dividerMoveCallbackMock, BOUNDS) tilingDividerView.handleStartY = 0 tilingDividerView.handleEndY = 1500 tilingDividerView.setup(dividerMoveCallbackMock, DIVIDER_BOUNDS, HANDLE_SIZE) tilingDividerView.handleY = 0..1500 } @Test Loading Loading @@ -130,6 +136,8 @@ class TilingDividerViewTest : ShellTestCase() { } companion object { private val BOUNDS = Rect(0, 0, 1500, 1500) private val DIVIDER_BOUNDS = Rect(15, 0, 35, 1500) private val HANDLE_SIZE = Size(800, 300) private const val CORNER_RADIUS = 15 } }