summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt3
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt58
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt10
-rw-r--r--packages/SystemUI/res/values/ids.xml3
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt69
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalAppWidgetSection.kt105
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt60
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt3
8 files changed, 272 insertions, 39 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index 8c38253d6469..6fca1785d768 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -28,6 +28,7 @@ import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler
import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection
import com.android.systemui.communal.ui.compose.section.CommunalPopupSection
+import com.android.systemui.communal.ui.view.layout.sections.CommunalAppWidgetSection
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -46,6 +47,7 @@ constructor(
private val bottomAreaSection: BottomAreaSection,
private val ambientStatusBarSection: AmbientStatusBarSection,
private val communalPopupSection: CommunalPopupSection,
+ private val widgetSection: CommunalAppWidgetSection,
) {
@Composable
@@ -63,6 +65,7 @@ constructor(
viewModel = viewModel,
interactionHandler = interactionHandler,
dialogFactory = dialogFactory,
+ widgetSection = widgetSection,
modifier = Modifier.element(Communal.Elements.Grid)
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index b65b47123eaa..b93b0490816d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -19,10 +19,7 @@ package com.android.systemui.communal.ui.compose
import android.content.Context
import android.content.res.Configuration
import android.graphics.drawable.Icon
-import android.os.Bundle
import android.util.SizeF
-import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
-import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
import android.widget.FrameLayout
import android.widget.RemoteViews
import androidx.annotation.VisibleForTesting
@@ -105,7 +102,6 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
-import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.CornerRadius
@@ -169,6 +165,7 @@ import com.android.systemui.communal.ui.compose.extensions.allowGestures
import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture
import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
import com.android.systemui.communal.ui.compose.extensions.observeTaps
+import com.android.systemui.communal.ui.view.layout.sections.CommunalAppWidgetSection
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
@@ -180,11 +177,12 @@ import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import kotlinx.coroutines.launch
-@OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class)
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CommunalHub(
modifier: Modifier = Modifier,
viewModel: BaseCommunalViewModel,
+ widgetSection: CommunalAppWidgetSection,
interactionHandler: RemoteViews.InteractionHandler? = null,
dialogFactory: SystemUIDialogFactory? = null,
widgetConfigurator: WidgetConfigurator? = null,
@@ -383,6 +381,7 @@ fun CommunalHub(
selectedKey = selectedKey,
widgetConfigurator = widgetConfigurator,
interactionHandler = interactionHandler,
+ widgetSection = widgetSection,
)
}
}
@@ -632,6 +631,7 @@ private fun BoxScope.CommunalHubLazyGrid(
updateDragPositionForRemove: (offset: Offset) -> Boolean,
widgetConfigurator: WidgetConfigurator?,
interactionHandler: RemoteViews.InteractionHandler?,
+ widgetSection: CommunalAppWidgetSection,
) {
var gridModifier =
Modifier.align(Alignment.TopStart).onGloballyPositioned { setGridCoordinates(it) }
@@ -719,18 +719,20 @@ private fun BoxScope.CommunalHubLazyGrid(
index = index,
contentListState = contentListState,
interactionHandler = interactionHandler,
+ widgetSection = widgetSection,
)
}
} else {
CommunalContent(
- modifier = cardModifier.animateItem(),
model = list[index],
viewModel = viewModel,
size = size,
selected = false,
+ modifier = cardModifier.animateItem(),
index = index,
contentListState = contentListState,
interactionHandler = interactionHandler,
+ widgetSection = widgetSection,
)
}
}
@@ -972,6 +974,7 @@ private fun CommunalContent(
index: Int,
contentListState: ContentListState,
interactionHandler: RemoteViews.InteractionHandler?,
+ widgetSection: CommunalAppWidgetSection,
) {
when (model) {
is CommunalContentModel.WidgetContent.Widget ->
@@ -983,7 +986,8 @@ private fun CommunalContent(
widgetConfigurator,
modifier,
index,
- contentListState
+ contentListState,
+ widgetSection,
)
is CommunalContentModel.WidgetPlaceholder -> HighlightedItem(modifier)
is CommunalContentModel.WidgetContent.DisabledWidget ->
@@ -1116,9 +1120,9 @@ private fun WidgetContent(
modifier: Modifier = Modifier,
index: Int,
contentListState: ContentListState,
+ widgetSection: CommunalAppWidgetSection,
) {
val context = LocalContext.current
- val isFocusable by viewModel.isFocusable.collectAsStateWithLifecycle(initialValue = false)
val accessibilityLabel =
remember(model, context) {
model.providerInfo.loadLabel(context.packageManager).toString().trim()
@@ -1205,36 +1209,14 @@ private fun WidgetContent(
}
}
) {
- AndroidView(
- modifier = Modifier.fillMaxSize().allowGestures(allowed = !viewModel.isEditMode),
- factory = { context ->
- model.appWidgetHost
- .createViewForCommunal(context, model.appWidgetId, model.providerInfo)
- .apply {
- updateAppWidgetSize(
- /* newOptions = */ Bundle(),
- /* minWidth = */ size.width.toInt(),
- /* minHeight = */ size.height.toInt(),
- /* maxWidth = */ size.width.toInt(),
- /* maxHeight = */ size.height.toInt(),
- /* ignorePadding = */ true
- )
- accessibilityDelegate = viewModel.widgetAccessibilityDelegate
- }
- },
- update = {
- it.apply {
- importantForAccessibility =
- if (isFocusable) {
- IMPORTANT_FOR_ACCESSIBILITY_AUTO
- } else {
- IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
- }
- }
- },
- // For reusing composition in lazy lists.
- onReset = {},
- )
+ with(widgetSection) {
+ Widget(
+ viewModel = viewModel,
+ model = model,
+ size = size,
+ modifier = Modifier.fillMaxSize().allowGestures(allowed = !viewModel.isEditMode),
+ )
+ }
if (
viewModel is CommunalEditModeViewModel &&
model.reconfigurable &&
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
index 5886d7de47b9..6750e41009c7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
@@ -23,6 +23,7 @@ import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.communal.ui.view.layout.sections.CommunalAppWidgetSection
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.widgets.WidgetInteractionHandler
import com.android.systemui.dagger.SysUISingleton
@@ -42,6 +43,7 @@ constructor(
private val viewModel: CommunalViewModel,
private val dialogFactory: SystemUIDialogFactory,
private val interactionHandler: WidgetInteractionHandler,
+ private val widgetSection: CommunalAppWidgetSection,
) : ComposableScene {
override val key = Scenes.Communal
@@ -55,6 +57,12 @@ constructor(
@Composable
override fun SceneScope.Content(modifier: Modifier) {
- CommunalHub(modifier, viewModel, interactionHandler, dialogFactory)
+ CommunalHub(
+ modifier = modifier,
+ viewModel = viewModel,
+ interactionHandler = interactionHandler,
+ widgetSection = widgetSection,
+ dialogFactory = dialogFactory,
+ )
}
}
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index e4f900d3a31a..25bca25be732 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -278,4 +278,7 @@
<!-- Id for the udfps accessibility overlay -->
<item type="id" name="udfps_accessibility_overlay" />
<item type="id" name="udfps_accessibility_overlay_top_guideline" />
+
+ <!-- Ids for communal hub widgets -->
+ <item type="id" name="communal_widget_disposable_tag"/>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt
new file mode 100644
index 000000000000..7a0567190bd0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.systemui.communal.ui.binder
+
+import android.content.Context
+import android.util.SizeF
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import com.android.app.tracing.coroutines.launch
+import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.util.WidgetViewFactory
+import com.android.systemui.util.kotlin.DisposableHandles
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DisposableHandle
+
+object CommunalAppWidgetHostViewBinder {
+ private const val TAG = "CommunalAppWidgetHostViewBinder"
+
+ fun bind(
+ context: Context,
+ applicationScope: CoroutineScope,
+ container: FrameLayout,
+ model: CommunalContentModel.WidgetContent.Widget,
+ size: SizeF,
+ factory: WidgetViewFactory,
+ ): DisposableHandle {
+ val disposables = DisposableHandles()
+
+ val loadingJob =
+ applicationScope.launch("$TAG#createWidgetView") {
+ val widget = factory.createWidget(context, model, size)
+ // TODO(b/358662507): Remove this workaround
+ (container.parent as? ViewGroup)?.let { parent ->
+ val index = parent.indexOfChild(container)
+ parent.removeView(container)
+ parent.addView(container, index)
+ }
+ container.setView(widget)
+ }
+
+ disposables += DisposableHandle { loadingJob.cancel() }
+ disposables += DisposableHandle { container.removeAllViews() }
+
+ return disposables
+ }
+}
+
+private fun ViewGroup.setView(view: View) {
+ if (view.parent == this) {
+ return
+ }
+ (view.parent as? ViewGroup)?.removeView(view)
+ addView(view)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalAppWidgetSection.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalAppWidgetSection.kt
new file mode 100644
index 000000000000..56b769e7bc13
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalAppWidgetSection.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.systemui.communal.ui.view.layout.sections
+
+import android.util.SizeF
+import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+import android.widget.FrameLayout
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.ui.binder.CommunalAppWidgetHostViewBinder
+import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
+import com.android.systemui.communal.util.WidgetViewFactory
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DisposableHandle
+
+class CommunalAppWidgetSection
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ private val factory: WidgetViewFactory,
+) {
+
+ private companion object {
+ val DISPOSABLE_TAG = R.id.communal_widget_disposable_tag
+ }
+
+ @Composable
+ fun Widget(
+ viewModel: BaseCommunalViewModel,
+ model: CommunalContentModel.WidgetContent.Widget,
+ size: SizeF,
+ modifier: Modifier = Modifier,
+ ) {
+ val isFocusable by viewModel.isFocusable.collectAsStateWithLifecycle(initialValue = false)
+
+ AndroidView(
+ factory = { context ->
+ FrameLayout(context).apply {
+ layoutParams =
+ FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ )
+
+ // Need to attach the disposable handle to the view here instead of storing
+ // the state in the composable in order to properly support lazy lists. In a
+ // lazy list, when the composable is no longer in view - it will exit
+ // composition and any state inside the composable will be lost. However,
+ // the View instance will be re-used. Therefore we can store data on the view
+ // in order to preserve it.
+ setTag(
+ DISPOSABLE_TAG,
+ CommunalAppWidgetHostViewBinder.bind(
+ context = context,
+ container = this,
+ model = model,
+ size = size,
+ factory = factory,
+ applicationScope = applicationScope,
+ )
+ )
+
+ accessibilityDelegate = viewModel.widgetAccessibilityDelegate
+ }
+ },
+ update = { container ->
+ container.importantForAccessibility =
+ if (isFocusable) {
+ IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ } else {
+ IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ }
+ },
+ onRelease = { view ->
+ val disposable = (view.getTag(DISPOSABLE_TAG) as? DisposableHandle)
+ disposable?.dispose()
+ },
+ modifier = modifier,
+ // For reusing composition in lazy lists.
+ onReset = {},
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt b/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt
new file mode 100644
index 000000000000..0e39a99765bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.systemui.communal.util
+
+import android.content.Context
+import android.os.Bundle
+import android.util.SizeF
+import com.android.app.tracing.coroutines.withContext
+import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
+import com.android.systemui.communal.widgets.CommunalAppWidgetHostView
+import com.android.systemui.dagger.qualifiers.UiBackground
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+
+/** Factory for creating [CommunalAppWidgetHostView] in a background thread. */
+class WidgetViewFactory
+@Inject
+constructor(
+ @UiBackground private val uiBgContext: CoroutineContext,
+ private val appWidgetHost: CommunalAppWidgetHost,
+) {
+ suspend fun createWidget(
+ context: Context,
+ model: CommunalContentModel.WidgetContent.Widget,
+ size: SizeF,
+ ): CommunalAppWidgetHostView =
+ withContext("$TAG#createWidget", uiBgContext) {
+ appWidgetHost
+ .createViewForCommunal(context, model.appWidgetId, model.providerInfo)
+ .apply {
+ updateAppWidgetSize(
+ /* newOptions = */ Bundle(),
+ /* minWidth = */ size.width.toInt(),
+ /* minHeight = */ size.height.toInt(),
+ /* maxWidth = */ size.width.toInt(),
+ /* maxHeight = */ size.height.toInt(),
+ /* ignorePadding = */ true,
+ )
+ }
+ }
+
+ private companion object {
+ const val TAG = "WidgetViewFactory"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 2bcbc9aa74ac..668fef6130bb 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -42,6 +42,7 @@ import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.communal.ui.compose.CommunalHub
+import com.android.systemui.communal.ui.view.layout.sections.CommunalAppWidgetSection
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -60,6 +61,7 @@ constructor(
private var windowManagerService: IWindowManager? = null,
private val uiEventLogger: UiEventLogger,
private val widgetConfiguratorFactory: WidgetConfigurationController.Factory,
+ private val widgetSection: CommunalAppWidgetSection,
@CommunalLog logBuffer: LogBuffer,
) : ComponentActivity() {
companion object {
@@ -204,6 +206,7 @@ constructor(
onOpenWidgetPicker = ::onOpenWidgetPicker,
widgetConfigurator = widgetConfigurator,
onEditDone = ::onEditDone,
+ widgetSection = widgetSection,
)
}
}