Merge "Marshal data to Launcher to support widget recommendations." into main
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 8e2e947..a7e98ea 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
@@ -18,10 +18,16 @@
import android.app.smartspace.SmartspaceTarget
import android.appwidget.AppWidgetProviderInfo
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
import android.content.pm.UserInfo
import android.os.UserHandle
import android.provider.Settings
import android.widget.RemoteViews
+import androidx.activity.result.ActivityResultLauncher
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
@@ -39,6 +45,7 @@
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.view.MediaHost
@@ -46,15 +53,19 @@
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
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.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -64,6 +75,8 @@
@Mock private lateinit var mediaHost: MediaHost
@Mock private lateinit var uiEventLogger: UiEventLogger
@Mock private lateinit var providerInfo: AppWidgetProviderInfo
+ @Mock private lateinit var packageManager: PackageManager
+ @Mock private lateinit var activityResultLauncher: ActivityResultLauncher<Intent>
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -73,6 +86,8 @@
private lateinit var smartspaceRepository: FakeSmartspaceRepository
private lateinit var mediaRepository: FakeCommunalMediaRepository
+ private val testableResources = context.orCreateTestableResources
+
private lateinit var underTest: CommunalEditModeViewModel
@Before
@@ -96,6 +111,7 @@
mediaHost,
uiEventLogger,
logcatLogBuffer("CommunalEditModeViewModelTest"),
+ kosmos.testDispatcher,
)
}
@@ -217,7 +233,69 @@
verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL)
}
+ @Test
+ fun onOpenWidgetPicker_launchesWidgetPickerActivity() {
+ testScope.runTest {
+ whenever(packageManager.resolveActivity(any(), anyInt())).then {
+ ResolveInfo().apply {
+ activityInfo = ActivityInfo().apply { packageName = WIDGET_PICKER_PACKAGE_NAME }
+ }
+ }
+
+ val success =
+ underTest.onOpenWidgetPicker(
+ testableResources.resources,
+ packageManager,
+ activityResultLauncher
+ )
+
+ verify(activityResultLauncher).launch(any())
+ assertTrue(success)
+ }
+ }
+
+ @Test
+ fun onOpenWidgetPicker_launcherActivityNotResolved_doesNotLaunchWidgetPickerActivity() {
+ testScope.runTest {
+ whenever(packageManager.resolveActivity(any(), anyInt())).thenReturn(null)
+
+ val success =
+ underTest.onOpenWidgetPicker(
+ testableResources.resources,
+ packageManager,
+ activityResultLauncher
+ )
+
+ verify(activityResultLauncher, never()).launch(any())
+ assertFalse(success)
+ }
+ }
+
+ @Test
+ fun onOpenWidgetPicker_activityLaunchThrowsException_failure() {
+ testScope.runTest {
+ whenever(packageManager.resolveActivity(any(), anyInt())).then {
+ ResolveInfo().apply {
+ activityInfo = ActivityInfo().apply { packageName = WIDGET_PICKER_PACKAGE_NAME }
+ }
+ }
+
+ whenever(activityResultLauncher.launch(any()))
+ .thenThrow(ActivityNotFoundException::class.java)
+
+ val success =
+ underTest.onOpenWidgetPicker(
+ testableResources.resources,
+ packageManager,
+ activityResultLauncher,
+ )
+
+ assertFalse(success)
+ }
+ }
+
private companion object {
val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+ const val WIDGET_PICKER_PACKAGE_NAME = "widget_picker_package_name"
}
}
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 bfe751a..afa7c37 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
@@ -16,24 +16,36 @@
package com.android.systemui.communal.ui.viewmodel
+import android.appwidget.AppWidgetManager
+import android.appwidget.AppWidgetProviderInfo
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.res.Resources
+import android.util.Log
+import androidx.activity.result.ActivityResultLauncher
import com.android.internal.logging.UiEventLogger
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.dagger.MediaModule
+import com.android.systemui.res.R
import javax.inject.Inject
import javax.inject.Named
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.withContext
/** The view model for communal hub in edit mode. */
@SysUISingleton
@@ -45,6 +57,7 @@
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
private val uiEventLogger: UiEventLogger,
@CommunalLog logBuffer: LogBuffer,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
) : BaseCommunalViewModel(communalInteractor, mediaHost) {
private val logger = Logger(logBuffer, "CommunalEditModeViewModel")
@@ -86,10 +99,77 @@
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL)
}
- /** Returns the widget categories to show on communal hub. */
- val getCommunalWidgetCategories: Int
- get() = communalSettingsInteractor.communalWidgetCategories.value
+ /** Launch the widget picker activity using the given {@link ActivityResultLauncher}. */
+ suspend fun onOpenWidgetPicker(
+ resources: Resources,
+ packageManager: PackageManager,
+ activityLauncher: ActivityResultLauncher<Intent>
+ ): Boolean =
+ withContext(backgroundDispatcher) {
+ val widgets = communalInteractor.widgetContent.first()
+ val excludeList = widgets.mapTo(ArrayList()) { it.providerInfo }
+ getWidgetPickerActivityIntent(resources, packageManager, excludeList)?.let {
+ try {
+ activityLauncher.launch(it)
+ return@withContext true
+ } catch (e: Exception) {
+ Log.e(TAG, "Failed to launch widget picker activity", e)
+ }
+ }
+ false
+ }
+
+ private fun getWidgetPickerActivityIntent(
+ resources: Resources,
+ packageManager: PackageManager,
+ excludeList: ArrayList<AppWidgetProviderInfo>
+ ): Intent? {
+ val packageName =
+ getLauncherPackageName(packageManager)
+ ?: run {
+ Log.e(TAG, "Couldn't resolve launcher package name")
+ return@getWidgetPickerActivityIntent null
+ }
+
+ return Intent(Intent.ACTION_PICK).apply {
+ setPackage(packageName)
+ putExtra(
+ EXTRA_DESIRED_WIDGET_WIDTH,
+ resources.getDimensionPixelSize(R.dimen.communal_widget_picker_desired_width)
+ )
+ putExtra(
+ EXTRA_DESIRED_WIDGET_HEIGHT,
+ resources.getDimensionPixelSize(R.dimen.communal_widget_picker_desired_height)
+ )
+ putExtra(
+ AppWidgetManager.EXTRA_CATEGORY_FILTER,
+ communalSettingsInteractor.communalWidgetCategories.value
+ )
+ putExtra(EXTRA_UI_SURFACE_KEY, EXTRA_UI_SURFACE_VALUE)
+ putParcelableArrayListExtra(EXTRA_ADDED_APP_WIDGETS_KEY, excludeList)
+ }
+ }
+
+ private fun getLauncherPackageName(packageManager: PackageManager): String? {
+ return packageManager
+ .resolveActivity(
+ Intent(Intent.ACTION_MAIN).also { it.addCategory(Intent.CATEGORY_HOME) },
+ PackageManager.MATCH_DEFAULT_ONLY
+ )
+ ?.activityInfo
+ ?.packageName
+ }
/** Sets whether edit mode is currently open */
fun setEditModeOpen(isOpen: Boolean) = communalInteractor.setEditModeOpen(isOpen)
+
+ companion object {
+ private const val TAG = "CommunalEditModeViewModel"
+
+ private const val EXTRA_DESIRED_WIDGET_WIDTH = "desired_widget_width"
+ private const val EXTRA_DESIRED_WIDGET_HEIGHT = "desired_widget_height"
+ private const val EXTRA_UI_SURFACE_KEY = "ui_surface"
+ private const val EXTRA_UI_SURFACE_VALUE = "widgets_hub"
+ const val EXTRA_ADDED_APP_WIDGETS_KEY = "added_app_widgets"
+ }
}
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 b6ad26b..ba18f01 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -16,9 +16,7 @@
package com.android.systemui.communal.widgets
-import android.appwidget.AppWidgetManager
import android.content.Intent
-import android.content.pm.PackageManager
import android.os.Bundle
import android.os.RemoteException
import android.util.Log
@@ -32,6 +30,8 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
+import androidx.lifecycle.lifecycleScope
+import com.android.app.tracing.coroutines.launch
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.compose.theme.PlatformTheme
import com.android.internal.logging.UiEventLogger
@@ -43,8 +43,8 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
-import com.android.systemui.res.R
import javax.inject.Inject
+import kotlinx.coroutines.launch
/** An Activity for editing the widgets that appear in hub mode. */
class EditWidgetsActivity
@@ -57,11 +57,8 @@
@CommunalLog logBuffer: LogBuffer,
) : ComponentActivity() {
companion object {
- private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag"
- private const val EXTRA_DESIRED_WIDGET_WIDTH = "desired_widget_width"
- private const val EXTRA_DESIRED_WIDGET_HEIGHT = "desired_widget_height"
-
private const val TAG = "EditWidgetsActivity"
+ private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag"
const val EXTRA_PRESELECTED_KEY = "preselected_key"
}
@@ -136,39 +133,13 @@
}
private fun onOpenWidgetPicker() {
- val intent = Intent(Intent.ACTION_MAIN).also { it.addCategory(Intent.CATEGORY_HOME) }
- packageManager
- .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
- ?.activityInfo
- ?.packageName
- ?.let { packageName ->
- try {
- addWidgetActivityLauncher.launch(
- Intent(Intent.ACTION_PICK).apply {
- setPackage(packageName)
- putExtra(
- EXTRA_DESIRED_WIDGET_WIDTH,
- resources.getDimensionPixelSize(
- R.dimen.communal_widget_picker_desired_width
- )
- )
- putExtra(
- EXTRA_DESIRED_WIDGET_HEIGHT,
- resources.getDimensionPixelSize(
- R.dimen.communal_widget_picker_desired_height
- )
- )
- putExtra(
- AppWidgetManager.EXTRA_CATEGORY_FILTER,
- communalViewModel.getCommunalWidgetCategories
- )
- }
- )
- } catch (e: Exception) {
- Log.e(TAG, "Failed to launch widget picker activity", e)
- }
- }
- ?: run { Log.e(TAG, "Couldn't resolve launcher package name") }
+ lifecycleScope.launch {
+ communalViewModel.onOpenWidgetPicker(
+ resources,
+ packageManager,
+ addWidgetActivityLauncher
+ )
+ }
}
private fun onEditDone() {