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

Commit 41693d0d authored by Massimo Carli's avatar Massimo Carli
Browse files

[6/n] Add Layout lifecycle

Defines the new properties for handling component
lifecycle.

Flag: com.android.window.flags.app_compat_ui_framework
Fixes: 323882186
Test: atest WMShellUnitTests:CompatUIComponentTest
Test: atest WMShellUnitTests:DefaultCompatUIHandlerTest

Change-Id: Id2bd831f54be7c909560f1a2fedc85a04847bf4e
parent 753160d9
Loading
Loading
Loading
Loading
+41 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ 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.
  -->
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:gravity="bottom|end">

    <include android:id="@+id/size_compat_hint"
        android:visibility="gone"
        android:layout_width="@dimen/compat_hint_width"
        android:layout_height="wrap_content"
        layout="@layout/compat_mode_hint"/>

    <ImageButton
        android:id="@+id/size_compat_restart_button"
        android:visibility="gone"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="@dimen/compat_button_margin"
        android:layout_marginBottom="@dimen/compat_button_margin"
        android:src="@drawable/size_compat_restart_button_ripple"
        android:background="@android:color/transparent"
        android:contentDescription="@string/restart_button_description"/>

</LinearLayout>
+203 −9
Original line number Diff line number Diff line
@@ -16,28 +16,222 @@

package com.android.wm.shell.compatui.api

import android.util.Log
import android.content.Context
import android.content.res.Configuration
import android.graphics.PixelFormat
import android.graphics.Point
import android.os.Binder
import android.view.IWindow
import android.view.SurfaceControl
import android.view.SurfaceControlViewHost
import android.view.SurfaceSession
import android.view.View
import android.view.WindowManager
import android.view.WindowlessWindowManager
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.SyncTransactionQueue

/**
 * The component created after a {@link CompatUISpec} definition
 */
class CompatUIComponent(
    private val spec: CompatUISpec,
    private val id: String
    private val id: String,
    private var context: Context,
    private val state: CompatUIState,
    private var compatUIInfo: CompatUIInfo,
    private val syncQueue: SyncTransactionQueue,
    private var displayLayout: DisplayLayout?
) : WindowlessWindowManager(
    compatUIInfo.taskInfo.configuration,
    /* rootSurface */
    null,
    /* hostInputToken */
    null
) {

    private val tag
        get() = "CompatUI {id = $id}"

    private var leash: SurfaceControl? = null

    private var layout: View? = null

    /**
     * Utility class for adding and releasing a View hierarchy for this [ ] to `mLeash`.
     */
    protected var viewHost: SurfaceControlViewHost? = null

    override fun setConfiguration(configuration: Configuration?) {
        super.setConfiguration(configuration)
        configuration?.let {
            context = context.createConfigurationContext(it)
        }
    }

    /**
     * Invoked every time a new CompatUIInfo comes from core
     * @param newInfo The new CompatUIInfo object
     * @param sharedState The state shared between all the component
     */
    fun update(newInfo: CompatUIInfo, state: CompatUIState) {
        // TODO(b/322817374): To be removed when the implementation is provided.
        Log.d("CompatUIComponent", "update() newInfo: $newInfo state:$state")
    fun update(newInfo: CompatUIInfo) {
        updateComponentState(newInfo, state.stateForComponent(id))
        updateUI(state)
    }

    fun release() {
        // TODO(b/322817374): To be removed when the implementation is provided.
        Log.d("CompatUIComponent", "release()")
        spec.log("$tag releasing.....")
        // Implementation empty
        // Hiding before releasing to avoid flickering when transitioning to the Home screen.
        layout?.visibility = View.GONE
        layout = null
        spec.layout.viewReleaser()
        spec.log("$tag layout releaser invoked!")
        viewHost?.release()
        viewHost = null
        leash?.run {
            val localLeash: SurfaceControl = this
            syncQueue.runInSync { t: SurfaceControl.Transaction ->
                t.remove(
                    localLeash
                )
            }
            leash = null
            spec.log("$tag leash removed")
        }
        spec.log("$tag released")
    }

    override fun getParentSurface(
        window: IWindow,
        attrs: WindowManager.LayoutParams
    ): SurfaceControl? {
        val className = javaClass.simpleName
        val builder = SurfaceControl.Builder(SurfaceSession())
                .setContainerLayer()
                .setName(className + "Leash")
                .setHidden(false)
                .setCallsite("$className#attachToParentSurface")
        attachToParentSurface(builder)
        leash = builder.build()
        initSurface(leash)
        return leash
    }

    fun attachToParentSurface(builder: SurfaceControl.Builder) {
        compatUIInfo.listener?.attachChildSurfaceToTask(compatUIInfo.taskInfo.taskId, builder)
    }

    fun initLayout(newCompatUIInfo: CompatUIInfo) {
        compatUIInfo = newCompatUIInfo
        spec.log("$tag updating...")
        check(viewHost == null) { "A UI has already been created with this window manager." }
        val componentState: CompatUIComponentState? = state.stateForComponent(id)
        spec.log("$tag state: $componentState")
        // We inflate the layout
        layout = spec.layout.viewBuilder(context, compatUIInfo, componentState)
        spec.log("$tag layout: $layout")
        viewHost = createSurfaceViewHost().apply {
            spec.log("$tag adding view $layout to host $this")
            setView(layout!!, getWindowLayoutParams())
        }
        updateSurfacePosition()
    }

    /** Creates a [SurfaceControlViewHost] for this window manager.  */
    fun createSurfaceViewHost(): SurfaceControlViewHost =
        SurfaceControlViewHost(context, context.display, this, javaClass.simpleName)

    fun relayout() {
        spec.log("$tag relayout...")
        viewHost?.run {
            relayout(getWindowLayoutParams())
            updateSurfacePosition()
        }
    }

    protected fun updateSurfacePosition() {
        spec.log("$tag updateSurfacePosition on layout $layout")
        layout?.let {
            updateSurfacePosition(
                spec.layout.positionFactory(
                    it,
                    compatUIInfo,
                    state.sharedState,
                    state.stateForComponent(id)
                )
            )
        }
    }

    protected fun getWindowLayoutParams(width: Int, height: Int): WindowManager.LayoutParams {
        // Cannot be wrap_content as this determines the actual window size
        val winParams =
            WindowManager.LayoutParams(
                width,
                height,
                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
                spec.layout.layoutParamFlags,
                PixelFormat.TRANSLUCENT
            )
        winParams.token = Binder()
        winParams.title = javaClass.simpleName + compatUIInfo.taskInfo.taskId
        winParams.privateFlags =
            winParams.privateFlags or (WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
                    or WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
        spec.log("$tag getWindowLayoutParams $winParams")
        return winParams
    }

    /** Gets the layout params.  */
    protected fun getWindowLayoutParams(): WindowManager.LayoutParams =
        layout?.run {
            measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
            spec.log(
                "$tag getWindowLayoutParams size: ${measuredWidth}x$measuredHeight"
            )
            return getWindowLayoutParams(measuredWidth, measuredHeight)
        } ?: WindowManager.LayoutParams()

    protected fun updateSurfacePosition(position: Point) {
        spec.log("$tag updateSurfacePosition on leash $leash")
        leash?.run {
            syncQueue.runInSync { t: SurfaceControl.Transaction ->
                if (!isValid) {
                    spec.log("$tag The leash has been released.")
                    return@runInSync
                }
                spec.log("$tag settings position  $position")
                t.setPosition(this, position.x.toFloat(), position.y.toFloat())
            }
        }
    }

    private fun updateComponentState(
        newInfo: CompatUIInfo,
        componentState: CompatUIComponentState?
    ) {
        spec.log("$tag component state updating.... $componentState")
        compatUIInfo = newInfo
    }

    private fun updateUI(state: CompatUIState) {
        spec.log("$tag updating ui")
        setConfiguration(compatUIInfo.taskInfo.configuration)
        val componentState: CompatUIComponentState? = state.stateForComponent(id)
        layout?.run {
            spec.log("$tag viewBinder execution...")
            spec.layout.viewBinder(this, compatUIInfo, state.sharedState, componentState)
            relayout()
        }
    }

    private fun initSurface(leash: SurfaceControl?) {
        syncQueue.runInSync { t: SurfaceControl.Transaction ->
            if (leash == null || !leash.isValid) {
                spec.log("$tag The leash has been released.")
                return@runInSync
            }
            t.setLayer(leash, spec.layout.zOrder)
        }
    }
}
+30 −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.compatui.api

/**
 * Abstracts the component responsible for the creation of a component
 */
interface CompatUIComponentFactory {

    fun create(
        spec: CompatUISpec,
        compId: String,
        state: CompatUIState,
        compatUIInfo: CompatUIInfo,
    ): CompatUIComponent
}
+2 −3
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.wm.shell.compatui.api

/**
 * Abstraction of all the component specific state. Each
 * component can create its own state implementing this
 * tagging interface.
 * component can create its own state implementing this interface.
 */
interface CompatUIComponentState
+30 −1
Original line number Diff line number Diff line
@@ -16,6 +16,11 @@

package com.android.wm.shell.compatui.api

import android.content.Context
import android.graphics.Point
import android.view.View
import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.protolog.ShellProtoLogGroup

@@ -38,6 +43,28 @@ class CompatUILifecyclePredicates(
    ) -> CompatUIComponentState? = { _, _ -> null }
)

/**
 * Layout configuration
 */
data class CompatUILayout(
    val zOrder: Int = 0,
    val layoutParamFlags: Int = FLAG_NOT_FOCUSABLE or FLAG_NOT_TOUCH_MODAL,
    val viewBuilder: (Context, CompatUIInfo, CompatUIComponentState?) -> View,
    val viewBinder: (
        View,
        CompatUIInfo,
        CompatUISharedState,
        CompatUIComponentState?
    ) -> Unit = { _, _, _, _ -> },
    val positionFactory: (
        View,
        CompatUIInfo,
        CompatUISharedState,
        CompatUIComponentState?
    ) -> Point,
    val viewReleaser: () -> Unit = {}
)

/**
 * Describes each compat ui component to the framework.
 */
@@ -47,5 +74,7 @@ class CompatUISpec(
    // unique component identifier in the system.
    val name: String,
    // The lifecycle definition
    val lifecycle: CompatUILifecyclePredicates
    val lifecycle: CompatUILifecyclePredicates,
    // The layout definition
    val layout: CompatUILayout
)
Loading