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

Commit dc3fdf53 authored by Jordan Demeulenaere's avatar Jordan Demeulenaere Committed by Android (Google) Code Review
Browse files

Merge "Remove (Base)ComposeFacade" into main

parents d7c542d1 e8c26a68
Loading
Loading
Loading
Loading
+0 −250
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2022 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.systemui.compose

import android.content.Context
import android.graphics.Point
import android.view.View
import android.view.WindowInsets
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.lifecycle.LifecycleOwner
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.compose.theme.PlatformTheme
import com.android.compose.ui.platform.DensityAwareComposeView
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.systemui.bouncer.ui.BouncerDialogFactory
import com.android.systemui.bouncer.ui.composable.BouncerContent
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
import com.android.systemui.common.ui.compose.windowinsets.ScreenDecorProvider
import com.android.systemui.communal.ui.compose.CommunalContainer
import com.android.systemui.communal.ui.compose.CommunalHub
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.keyboard.stickykeys.ui.view.createStickyKeyIndicatorView
import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
import com.android.systemui.keyguard.ui.composable.LockscreenContent
import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import com.android.systemui.people.ui.compose.PeopleScreen
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.compose.FooterActions
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.scene.shared.model.Scene
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.ui.composable.ComposableScene
import com.android.systemui.scene.ui.composable.SceneContainer
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import com.android.systemui.volume.panel.ui.composable.VolumePanelRoot
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

/** The Compose facade, when Compose is available. */
object ComposeFacade : BaseComposeFacade {
    override fun isComposeAvailable(): Boolean = true

    override fun composeInitializer(): ComposeInitializer = ComposeInitializerImpl

    override fun setPeopleSpaceActivityContent(
        activity: ComponentActivity,
        viewModel: PeopleViewModel,
        onResult: (PeopleViewModel.Result) -> Unit,
    ) {
        activity.setContent { PlatformTheme { PeopleScreen(viewModel, onResult) } }
    }

    override fun setCommunalEditWidgetActivityContent(
        activity: ComponentActivity,
        viewModel: BaseCommunalViewModel,
        widgetConfigurator: WidgetConfigurator,
        onOpenWidgetPicker: () -> Unit,
        onEditDone: () -> Unit,
    ) {
        activity.setContent {
            PlatformTheme {
                Box(
                    modifier =
                        Modifier.fillMaxSize()
                            .background(LocalAndroidColorScheme.current.outlineVariant),
                ) {
                    CommunalHub(
                        viewModel = viewModel,
                        onOpenWidgetPicker = onOpenWidgetPicker,
                        widgetConfigurator = widgetConfigurator,
                        onEditDone = onEditDone,
                    )
                }
            }
        }
    }

    override fun setVolumePanelActivityContent(
        activity: ComponentActivity,
        viewModel: VolumePanelViewModel,
        onDismiss: () -> Unit,
    ) {
        activity.setContent {
            VolumePanelRoot(
                viewModel = viewModel,
                onDismiss = onDismiss,
            )
        }
    }

    override fun createFooterActionsView(
        context: Context,
        viewModel: FooterActionsViewModel,
        qsVisibilityLifecycleOwner: LifecycleOwner,
    ): View {
        return DensityAwareComposeView(context).apply {
            setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } }
        }
    }

    override fun createSceneContainerView(
        scope: CoroutineScope,
        context: Context,
        viewModel: SceneContainerViewModel,
        windowInsets: StateFlow<WindowInsets?>,
        sceneByKey: Map<SceneKey, Scene>,
        dataSourceDelegator: SceneDataSourceDelegator,
    ): View {
        return ComposeView(context).apply {
            setContent {
                PlatformTheme {
                    ScreenDecorProvider(
                        displayCutout = displayCutoutFromWindowInsets(scope, context, windowInsets),
                        screenCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
                    ) {
                        SceneContainer(
                            viewModel = viewModel,
                            sceneByKey =
                                sceneByKey.mapValues { (_, scene) -> scene as ComposableScene },
                            dataSourceDelegator = dataSourceDelegator,
                        )
                    }
                }
            }
        }
    }

    override fun createStickyKeysIndicatorContent(
        context: Context,
        viewModel: StickyKeysIndicatorViewModel
    ): View {
        return createStickyKeyIndicatorView(context, viewModel)
    }

    override fun createCommunalView(
        context: Context,
        viewModel: BaseCommunalViewModel,
    ): View {
        return ComposeView(context).apply {
            setContent { PlatformTheme { CommunalHub(viewModel = viewModel) } }
        }
    }

    override fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View {
        return ComposeView(context).apply {
            setContent { PlatformTheme { CommunalContainer(viewModel = viewModel) } }
        }
    }

    // TODO(b/298525212): remove once Compose exposes window inset bounds.
    private fun displayCutoutFromWindowInsets(
        scope: CoroutineScope,
        context: Context,
        windowInsets: StateFlow<WindowInsets?>,
    ): StateFlow<DisplayCutout> =
        windowInsets
            .map {
                val boundingRect = it?.displayCutout?.boundingRectTop
                val width = boundingRect?.let { boundingRect.right - boundingRect.left } ?: 0
                val left = boundingRect?.left?.toDp(context) ?: 0.dp
                val top = boundingRect?.top?.toDp(context) ?: 0.dp
                val right = boundingRect?.right?.toDp(context) ?: 0.dp
                val bottom = boundingRect?.bottom?.toDp(context) ?: 0.dp
                val location =
                    when {
                        width <= 0f -> CutoutLocation.NONE
                        left <= 0.dp -> CutoutLocation.LEFT
                        right >= getDisplayWidth(context) -> CutoutLocation.RIGHT
                        else -> CutoutLocation.CENTER
                    }
                DisplayCutout(
                    left,
                    top,
                    right,
                    bottom,
                    location,
                )
            }
            .stateIn(scope, SharingStarted.WhileSubscribed(), DisplayCutout())

    // TODO(b/298525212): remove once Compose exposes window inset bounds.
    private fun getDisplayWidth(context: Context): Dp {
        val point = Point()
        checkNotNull(context.display).getRealSize(point)
        return point.x.dp
    }

    // TODO(b/298525212): remove once Compose exposes window inset bounds.
    private fun Int.toDp(context: Context): Dp {
        return (this.toFloat() / context.resources.displayMetrics.density).dp
    }

    override fun createBouncer(
        context: Context,
        viewModel: BouncerViewModel,
        dialogFactory: BouncerDialogFactory,
    ): View {
        return ComposeView(context).apply {
            setContent { PlatformTheme { BouncerContent(viewModel, dialogFactory) } }
        }
    }

    override fun createLockscreen(
        context: Context,
        viewModel: LockscreenContentViewModel,
        blueprints: Set<@JvmSuppressWildcards LockscreenSceneBlueprint>,
    ): View {
        val sceneBlueprints =
            blueprints.mapNotNull { it as? ComposableLockscreenSceneBlueprint }.toSet()
        return ComposeView(context).apply {
            setContent {
                LockscreenContent(viewModel = viewModel, blueprints = sceneBlueprints)
                    .Content(modifier = Modifier.fillMaxSize())
            }
        }
    }
}
+0 −77
Original line number Original line Diff line number Diff line
/*
 * 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.systemui.compose

import android.view.View
import androidx.lifecycle.findViewTreeLifecycleOwner
import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.lifecycle.Lifecycle
import androidx.savedstate.SavedStateRegistryController
import androidx.savedstate.SavedStateRegistryOwner
import com.android.compose.animation.ViewTreeSavedStateRegistryOwner
import com.android.systemui.lifecycle.ViewLifecycleOwner

internal object ComposeInitializerImpl : ComposeInitializer {
    override fun onAttachedToWindow(root: View) {
        if (root.findViewTreeLifecycleOwner() != null) {
            error("root $root already has a LifecycleOwner")
        }

        val parent = root.parent
        if (parent is View && parent.id != android.R.id.content) {
            error(
                "ComposeInitializer.onAttachedToWindow(View) must be called on the content child." +
                    "Outside of activities and dialogs, this is usually the top-most View of a " +
                    "window."
            )
        }

        // The lifecycle owner, which is STARTED when [root] is visible and RESUMED when [root] is
        // both visible and focused.
        val lifecycleOwner = ViewLifecycleOwner(root)

        // We create a trivial implementation of [SavedStateRegistryOwner] that does not do any save
        // or restore because SystemUI process is always running and top-level windows using this
        // initializer are created once, when the process is started.
        val savedStateRegistryOwner =
            object : SavedStateRegistryOwner {
                private val savedStateRegistryController =
                    SavedStateRegistryController.create(this).apply { performRestore(null) }

                override val savedStateRegistry = savedStateRegistryController.savedStateRegistry

                override val lifecycle: Lifecycle
                    get() = lifecycleOwner.lifecycle
            }

        // We must call [ViewLifecycleOwner.onCreate] after creating the [SavedStateRegistryOwner]
        // because `onCreate` might move the lifecycle state to STARTED which will make
        // [SavedStateRegistryController.performRestore] throw.
        lifecycleOwner.onCreate()

        // Set the owners on the root. They will be reused by any ComposeView inside the root
        // hierarchy.
        root.setViewTreeLifecycleOwner(lifecycleOwner)
        ViewTreeSavedStateRegistryOwner.set(root, savedStateRegistryOwner)
    }

    override fun onDetachedFromWindow(root: View) {
        (root.findViewTreeLifecycleOwner() as ViewLifecycleOwner).onDestroy()
        root.setViewTreeLifecycleOwner(null)
        ViewTreeSavedStateRegistryOwner.set(root, null)
    }
}
+1 −6
Original line number Original line Diff line number Diff line
@@ -11,7 +11,6 @@ import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
import com.android.systemui.bouncer.ui.BouncerDialogFactory
import com.android.systemui.bouncer.ui.BouncerDialogFactory
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.compose.ComposeFacade
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.Flags.COMPOSE_BOUNCER_ENABLED
import com.android.systemui.flags.Flags.COMPOSE_BOUNCER_ENABLED
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
@@ -60,11 +59,7 @@ constructor(
    private val composeBouncerDependencies: Lazy<ComposeBouncerDependencies>,
    private val composeBouncerDependencies: Lazy<ComposeBouncerDependencies>,
) {
) {
    fun bind(view: ViewGroup) {
    fun bind(view: ViewGroup) {
        if (
        if (COMPOSE_BOUNCER_ENABLED && composeBouncerFlags.isOnlyComposeBouncerEnabled()) {
            COMPOSE_BOUNCER_ENABLED &&
                composeBouncerFlags.isOnlyComposeBouncerEnabled() &&
                ComposeFacade.isComposeAvailable()
        ) {
            val deps = composeBouncerDependencies.get()
            val deps = composeBouncerDependencies.get()
            ComposeBouncerViewBinder.bind(
            ComposeBouncerViewBinder.bind(
                view,
                view,
+7 −6
Original line number Original line Diff line number Diff line
package com.android.systemui.bouncer.ui.binder
package com.android.systemui.bouncer.ui.binder


import android.view.ViewGroup
import android.view.ViewGroup
import androidx.compose.ui.platform.ComposeView
import androidx.core.view.isVisible
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.compose.theme.PlatformTheme
import com.android.keyguard.ViewMediatorCallback
import com.android.keyguard.ViewMediatorCallback
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.bouncer.ui.BouncerDialogFactory
import com.android.systemui.bouncer.ui.BouncerDialogFactory
import com.android.systemui.bouncer.ui.composable.BouncerContent
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.compose.ComposeFacade
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.collectLatest
@@ -27,12 +29,11 @@ object ComposeBouncerViewBinder {
        viewMediatorCallback: ViewMediatorCallback?,
        viewMediatorCallback: ViewMediatorCallback?,
    ) {
    ) {
        view.addView(
        view.addView(
            ComposeFacade.createBouncer(
            ComposeView(view.context).apply {
                view.context,
                setContent { PlatformTheme { BouncerContent(viewModel, dialogFactory) } }
                viewModel,
            }
                dialogFactory,
            )
        )
        )

        view.repeatWhenAttached {
        view.repeatWhenAttached {
            repeatOnLifecycle(Lifecycle.State.CREATED) {
            repeatOnLifecycle(Lifecycle.State.CREATED) {
                launch {
                launch {
+66 −47
Original line number Original line Diff line number Diff line
@@ -25,14 +25,21 @@ import android.util.Log
import android.view.IWindowManager
import android.view.IWindowManager
import android.view.WindowInsets
import android.view.WindowInsets
import androidx.activity.ComponentActivity
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.compose.theme.PlatformTheme
import com.android.internal.logging.UiEventLogger
import com.android.internal.logging.UiEventLogger
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.ui.compose.CommunalHub
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent
import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent
import com.android.systemui.compose.ComposeFacade.setCommunalEditWidgetActivityContent
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
import com.android.systemui.log.dagger.CommunalLog
@@ -110,13 +117,26 @@ constructor(
        val preselectedKey = intent.getStringExtra(EXTRA_PRESELECTED_KEY)
        val preselectedKey = intent.getStringExtra(EXTRA_PRESELECTED_KEY)
        communalViewModel.setSelectedKey(preselectedKey)
        communalViewModel.setSelectedKey(preselectedKey)


        setCommunalEditWidgetActivityContent(
        setContent {
            activity = this,
            PlatformTheme {
                Box(
                    modifier =
                        Modifier.fillMaxSize()
                            .background(LocalAndroidColorScheme.current.outlineVariant),
                ) {
                    CommunalHub(
                        viewModel = communalViewModel,
                        viewModel = communalViewModel,
                        onOpenWidgetPicker = ::onOpenWidgetPicker,
                        widgetConfigurator = widgetConfigurator,
                        widgetConfigurator = widgetConfigurator,
            onOpenWidgetPicker = {
                        onEditDone = ::onEditDone,
                val intent =
                    )
                    Intent(Intent.ACTION_MAIN).also { it.addCategory(Intent.CATEGORY_HOME) }
                }
            }
        }
    }

    private fun onOpenWidgetPicker() {
        val intent = Intent(Intent.ACTION_MAIN).also { it.addCategory(Intent.CATEGORY_HOME) }
        packageManager
        packageManager
            .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
            .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
            ?.activityInfo
            ?.activityInfo
@@ -149,8 +169,9 @@ constructor(
                }
                }
            }
            }
            ?: run { Log.e(TAG, "Couldn't resolve launcher package name") }
            ?: run { Log.e(TAG, "Couldn't resolve launcher package name") }
            },
    }
            onEditDone = {

    private fun onEditDone() {
        try {
        try {
            communalViewModel.onSceneChanged(CommunalSceneKey.Communal)
            communalViewModel.onSceneChanged(CommunalSceneKey.Communal)
            checkNotNull(windowManagerService).lockNow(/* options */ null)
            checkNotNull(windowManagerService).lockNow(/* options */ null)
@@ -159,8 +180,6 @@ constructor(
            Log.e(TAG, "Couldn't lock the device as WindowManager is dead.")
            Log.e(TAG, "Couldn't lock the device as WindowManager is dead.")
        }
        }
    }
    }
        )
    }


    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        super.onActivityResult(requestCode, resultCode, data)
Loading