Merge "Correctly handle activity starts from widgets in glanceable hub" into main
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 5a4e0a9..fff1410 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
@@ -326,7 +326,7 @@
elevation: Dp = 0.dp,
) {
when (model) {
- is CommunalContentModel.Widget -> WidgetContent(model, size, elevation, modifier)
+ is CommunalContentModel.Widget -> WidgetContent(viewModel, model, size, elevation, modifier)
is CommunalContentModel.WidgetPlaceholder -> WidgetPlaceholderContent(size)
is CommunalContentModel.Smartspace -> SmartspaceContent(model, modifier)
is CommunalContentModel.Tutorial -> TutorialContent(modifier)
@@ -347,6 +347,7 @@
@Composable
private fun WidgetContent(
+ viewModel: BaseCommunalViewModel,
model: CommunalContentModel.Widget,
size: SizeF,
elevation: Dp,
@@ -359,9 +360,18 @@
AndroidView(
modifier = modifier,
factory = { context ->
- model.appWidgetHost
- .createView(context, model.appWidgetId, model.providerInfo)
- .apply { updateAppWidgetSize(Bundle.EMPTY, listOf(size)) }
+ // The AppWidgetHostView will inherit the interaction handler from the
+ // AppWidgetHost. So set the interaction handler here before creating the view, and
+ // then clear it after the view is created. This is a workaround due to the fact
+ // that the interaction handler cannot be specified when creating the view,
+ // and there are race conditions if it is set after the view is created.
+ model.appWidgetHost.setInteractionHandler(viewModel.getInteractionHandler())
+ val view =
+ model.appWidgetHost
+ .createView(context, model.appWidgetId, model.providerInfo)
+ .apply { updateAppWidgetSize(Bundle.EMPTY, listOf(size)) }
+ model.appWidgetHost.setInteractionHandler(null)
+ view
},
// For reusing composition in lazy lists.
onReset = {},
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 314dfdf..f2f9705 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -33,14 +33,15 @@
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.shade.ShadeViewController
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import javax.inject.Provider
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -56,7 +57,8 @@
@Mock private lateinit var shadeViewController: ShadeViewController
@Mock private lateinit var powerManager: PowerManager
- private lateinit var testScope: TestScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var communalRepository: FakeCommunalRepository
@@ -71,8 +73,6 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- testScope = TestScope()
-
val withDeps = CommunalInteractorFactory.create()
keyguardRepository = withDeps.keyguardRepository
communalRepository = withDeps.communalRepository
@@ -130,4 +130,17 @@
assertThat(communalContent?.get(1))
.isInstanceOf(CommunalContentModel.Widget::class.java)
}
+
+ @Test
+ fun interactionHandlerIgnoresClicks() {
+ val interactionHandler = underTest.getInteractionHandler()
+ assertThat(
+ interactionHandler.onInteraction(
+ /* view = */ mock(),
+ /* pendingIntent = */ mock(),
+ /* response = */ mock()
+ )
+ )
+ .isEqualTo(false)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 8a71168..182cc5d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -31,6 +31,7 @@
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.communal.widgets.WidgetInteractionHandler
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.media.controls.ui.MediaHost
@@ -84,6 +85,7 @@
underTest =
CommunalViewModel(
withDeps.communalInteractor,
+ WidgetInteractionHandler(mock()),
withDeps.tutorialInteractor,
Provider { shadeViewController },
powerManager,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 577e404..c34a8df 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -20,6 +20,7 @@
import android.os.PowerManager
import android.os.SystemClock
import android.view.MotionEvent
+import android.widget.RemoteViews
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalSceneKey
@@ -101,4 +102,7 @@
/** Called as the UI requests opening the widget editor. */
open fun onOpenWidgetEditor() {}
+
+ /** Gets the interaction handler used to handle taps on a remote view */
+ abstract fun getInteractionHandler(): RemoteViews.InteractionHandler
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 368df9e..da7bd34 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.communal.ui.viewmodel
import android.os.PowerManager
+import android.widget.RemoteViews
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.dagger.SysUISingleton
@@ -49,4 +50,9 @@
override fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) =
communalInteractor.updateWidgetOrder(widgetIdToPriorityMap)
+
+ override fun getInteractionHandler(): RemoteViews.InteractionHandler {
+ // Ignore all interactions in edit mode.
+ return RemoteViews.InteractionHandler { _, _, _ -> false }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index abf1986..2fae8b5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -17,9 +17,11 @@
package com.android.systemui.communal.ui.viewmodel
import android.os.PowerManager
+import android.widget.RemoteViews
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.widgets.WidgetInteractionHandler
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.media.dagger.MediaModule
@@ -39,6 +41,7 @@
@Inject
constructor(
private val communalInteractor: CommunalInteractor,
+ private val interactionHandler: WidgetInteractionHandler,
tutorialInteractor: CommunalTutorialInteractor,
shadeViewController: Provider<ShadeViewController>,
powerManager: PowerManager,
@@ -60,4 +63,6 @@
}
override fun onOpenWidgetEditor() = communalInteractor.showWidgetEditor()
+
+ override fun getInteractionHandler(): RemoteViews.InteractionHandler = interactionHandler
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
new file mode 100644
index 0000000..c8db70b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.widgets
+
+import android.app.PendingIntent
+import android.view.View
+import android.widget.RemoteViews
+import com.android.systemui.plugins.ActivityStarter
+import javax.inject.Inject
+
+class WidgetInteractionHandler
+@Inject
+constructor(
+ private val activityStarter: ActivityStarter,
+) : RemoteViews.InteractionHandler {
+ override fun onInteraction(
+ view: View,
+ pendingIntent: PendingIntent,
+ response: RemoteViews.RemoteResponse
+ ): Boolean =
+ when {
+ pendingIntent.isActivity -> startActivity(pendingIntent)
+ else ->
+ RemoteViews.startPendingIntent(view, pendingIntent, response.getLaunchOptions(view))
+ }
+
+ private fun startActivity(pendingIntent: PendingIntent): Boolean {
+ activityStarter.startPendingIntentMaybeDismissingKeyguard(
+ /* intent = */ pendingIntent,
+ /* intentSentUiThreadCallback = */ null,
+ // TODO(b/318758390): Properly animate activities started from widgets.
+ /* animationController = */ null
+ )
+ return true
+ }
+}