Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4485a41a authored by Omar Elmekkawy's avatar Omar Elmekkawy
Browse files

[2/n] Add TilingDividerView and its WindowlessWindowManager

TilingDividerView will be drawn upon tiling tasks left and right in
DesktopMode on SurfaceControlViewHost hosted on
DesktopTilingDividerWindowManager which is a WindowlessWindowManager
utilizing the Display's root leash.

TilingDividerView is mostly a copy of DividerView, with changes to
input handling, accessibility and bounds calculations.

It is necessary to maintain DividerView and TilingDividerView separately
as they will diverge significantly in the upcoming updates, to introduce
rounded corners in a different bounds, current input handling and future
accessibility requirements.

Flag: com.android.window.flags.enable_tile_resizing
Test: tests are WIP, but will mostly be copied from DividerView
Bug: 368878003

Change-Id: I82984d71c998e092e34dcba57f14818a89f61a6a
parent e62e7c83
Loading
Loading
Loading
Loading
+37 −0
Original line number Diff line number Diff line
<!--
  ~ 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.
  -->

<com.android.wm.shell.windowdecor.tiling.TilingDividerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    android:id="@+id/divider_bar">

        <com.android.wm.shell.common.split.DividerHandleView
            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"/>


</com.android.wm.shell.windowdecor.tiling.TilingDividerView>
 No newline at end of file
+2 −1
Original line number Diff line number Diff line
@@ -103,7 +103,8 @@ public class DividerHandleView extends View {
        mHoveringHeight = mHeight > mWidth ? ((int) (mHeight * 1.5f)) : mHeight;
    }

    void setIsLeftRightSplit(boolean isLeftRightSplit) {
    /** sets whether it's a left/right or top/bottom split */
    public void setIsLeftRightSplit(boolean isLeftRightSplit) {
        mIsLeftRightSplit = isLeftRightSplit;
        updateDimens();
    }
+5 −1
Original line number Diff line number Diff line
@@ -98,7 +98,11 @@ public class DividerRoundedCorner extends View {
        return false;
    }

    void setIsLeftRightSplit(boolean isLeftRightSplit) {
    /**
     * Set whether the rounded corner is for a left/right split.
     * @param isLeftRightSplit whether it's a left/right split or top/bottom split.
     */
    public void setIsLeftRightSplit(boolean isLeftRightSplit) {
        mIsLeftRightSplit = isLeftRightSplit;
    }

+228 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.wm.shell.windowdecor.tiling

import android.content.Context
import android.content.res.Configuration
import android.graphics.PixelFormat
import android.graphics.Rect
import android.graphics.Region
import android.os.Binder
import android.view.LayoutInflater
import android.view.SurfaceControl
import android.view.SurfaceControlViewHost
import android.view.View
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
import android.view.WindowManager.LayoutParams.FLAG_SLIPPERY
import android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
import android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
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 java.util.function.Supplier

/**
 * a [WindowlessWindowManaer] responsible for hosting the [TilingDividerView] on the display root
 * when two tasks are tiled on left and right to resize them simultaneously.
 */
class DesktopTilingDividerWindowManager(
    private val config: Configuration,
    private val windowName: String,
    private val context: Context,
    private val leash: SurfaceControl,
    private val syncQueue: SyncTransactionQueue,
    private val transitionHandler: DesktopTilingWindowDecoration,
    private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
    private var dividerBounds: Rect,
) : WindowlessWindowManager(config, leash, null), DividerMoveCallback, View.OnLayoutChangeListener {
    private lateinit var viewHost: SurfaceControlViewHost
    private var tilingDividerView: TilingDividerView? = null
    private var dividerShown = false
    private var handleRegionWidth: Int = -1
    private var setTouchRegion = true

    /**
     * Gets bounds of divider window with screen based coordinate on the param Rect.
     *
     * @param rect bounds for the [TilingDividerView]
     */
    fun getDividerBounds(rect: Rect) {
        rect.set(dividerBounds)
    }

    /** Sets the touch region for the SurfaceControlViewHost. */
    fun setTouchRegion(region: Rect) {
        setTouchRegion(viewHost.windowToken.asBinder(), Region(region))
    }

    /**
     * Builds a view host upon tiling two tasks left and right, and shows the divider view in the
     * middle of the screen between both tasks.
     *
     * @param relativeLeash the task leash that the TilingDividerView should be shown on top of.
     */
    fun generateViewHost(relativeLeash: SurfaceControl) {
        val t = transactionSupplier.get()
        val surfaceControlViewHost =
            SurfaceControlViewHost(context, context.display, this, "DesktopTilingManager")
        val dividerView =
            LayoutInflater.from(context).inflate(R.layout.tiling_split_divider, /* root= */ null)
                as TilingDividerView
        val lp = getWindowManagerParams()
        surfaceControlViewHost.setView(dividerView, lp)
        val tmpDividerBounds = Rect()
        getDividerBounds(tmpDividerBounds)
        dividerView.setup(this, tmpDividerBounds)
        t.setRelativeLayer(leash, relativeLeash, 1)
            .setPosition(leash, dividerBounds.left.toFloat(), dividerBounds.top.toFloat())
            .show(leash)
        syncQueue.runInSync { transaction ->
            transaction.merge(t)
            t.close()
        }
        dividerShown = true
        viewHost = surfaceControlViewHost
        dividerView.addOnLayoutChangeListener(this)
        tilingDividerView = dividerView
        handleRegionWidth = dividerView.handleRegionWidth
    }

    /** Hides the divider bar. */
    fun hideDividerBar() {
        if (!dividerShown) {
            return
        }
        val t = transactionSupplier.get()
        t.hide(leash)
        t.apply()
        dividerShown = false
    }

    /** Shows the divider bar. */
    fun showDividerBar() {
        if (dividerShown) return
        val t = transactionSupplier.get()
        t.show(leash)
        t.apply()
        dividerShown = true
    }

    /**
     * When the tiled task on top changes, the divider bar's Z access should change to be on top of
     * the latest focused task.
     */
    fun onRelativeLeashChanged(relativeLeash: SurfaceControl, t: SurfaceControl.Transaction) {
        t.setRelativeLayer(leash, relativeLeash, 1)
    }

    override fun onDividerMoveStart(pos: Int) {
        setSlippery(false)
    }

    /**
     * Moves the divider view to a new position after touch, gets called from the
     * [TilingDividerView] onTouch function.
     */
    override fun onDividerMove(pos: Int): Boolean {
        val t = transactionSupplier.get()
        t.setPosition(leash, pos.toFloat(), dividerBounds.top.toFloat())
        val dividerWidth = dividerBounds.width()
        dividerBounds.set(pos, dividerBounds.top, pos + dividerWidth, dividerBounds.bottom)
        return transitionHandler.onDividerHandleMoved(dividerBounds, t)
    }

    /**
     * Notifies the transition handler of tiling operations ending, which might result in resizing
     * WindowContainerTransactions if the sizes of the tiled tasks changed.
     */
    override fun onDividerMovedEnd(pos: Int) {
        setSlippery(true)
        val t = transactionSupplier.get()
        t.setPosition(leash, pos.toFloat(), dividerBounds.top.toFloat())
        val dividerWidth = dividerBounds.width()
        dividerBounds.set(pos, dividerBounds.top, pos + dividerWidth, dividerBounds.bottom)
        transitionHandler.onDividerHandleDragEnd(dividerBounds, t)
    }

    private fun getWindowManagerParams(): WindowManager.LayoutParams {
        val lp =
            WindowManager.LayoutParams(
                dividerBounds.width(),
                dividerBounds.height(),
                TYPE_DOCK_DIVIDER,
                FLAG_NOT_FOCUSABLE or
                    FLAG_NOT_TOUCH_MODAL or
                    FLAG_WATCH_OUTSIDE_TOUCH or
                    FLAG_SPLIT_TOUCH or
                    FLAG_SLIPPERY,
                PixelFormat.TRANSLUCENT,
            )
        lp.token = Binder()
        lp.title = windowName
        lp.privateFlags =
            lp.privateFlags or (PRIVATE_FLAG_NO_MOVE_ANIMATION or PRIVATE_FLAG_TRUSTED_OVERLAY)
        return lp
    }

    /**
     * Releases the surface control of the current [TilingDividerView] and tear down the view
     * hierarchy.y.
     */
    fun release() {
        tilingDividerView = null
        viewHost.release()
        transactionSupplier.get().hide(leash).remove(leash).apply()
    }

    override fun onLayoutChange(
        v: View?,
        left: Int,
        top: Int,
        right: Int,
        bottom: Int,
        oldLeft: Int,
        oldTop: Int,
        oldRight: Int,
        oldBottom: Int,
    ) {
        if (!setTouchRegion) return

        val startX = (dividerBounds.width() - handleRegionWidth) / 2
        val startY = 0
        val tempRect = Rect(startX, startY, startX + handleRegionWidth, dividerBounds.height())
        setTouchRegion(tempRect)
        setTouchRegion = false
    }

    private fun setSlippery(slippery: Boolean) {
        val lp = tilingDividerView?.layoutParams as WindowManager.LayoutParams
        val isSlippery = (lp.flags and FLAG_SLIPPERY) != 0
        if (isSlippery == slippery) return

        if (slippery) {
            lp.flags = lp.flags or FLAG_SLIPPERY
        } else {
            lp.flags = lp.flags and FLAG_SLIPPERY.inv()
        }
        viewHost.relayout(lp)
    }
}
+28 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.wm.shell.windowdecor.tiling

/** Divider move callback to whichever entity that handles the moving logic. */
interface DividerMoveCallback {
    /** Called on the divider move start gesture. */
    fun onDividerMoveStart(pos: Int)

    /** Called on the divider moved by dragging it. */
    fun onDividerMove(pos: Int): Boolean

    /** Called on divider move gesture end. */
    fun onDividerMovedEnd(pos: Int)
}
Loading