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

Commit 89aab5a3 authored by William Leshner's avatar William Leshner Committed by Android (Google) Code Review
Browse files

Merge "Marshal data to Launcher to support widget recommendations." into main

parents 22fb8ef1 0dd4a95a
Loading
Loading
Loading
Loading
+79 −1
Original line number Diff line number Diff line
@@ -18,10 +18,16 @@ package com.android.systemui.communal.view.viewmodel

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.log.CommunalUiEvent
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.settings.fakeUserTracker
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 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
    @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 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
    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 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
                mediaHost,
                uiEventLogger,
                logcatLogBuffer("CommunalEditModeViewModelTest"),
                kosmos.testDispatcher,
            )
    }

@@ -217,7 +233,69 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
        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"
    }
}
+83 −3
Original line number Diff line number Diff line
@@ -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 @@ constructor(
    @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 @@ constructor(
        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"
    }
}
+11 −40
Original line number Diff line number Diff line
@@ -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.background
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.communal.util.WidgetPickerIntentUtils.getWidgetExtra
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 @@ constructor(
    @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 @@ constructor(
    }

    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
        lifecycleScope.launch {
            communalViewModel.onOpenWidgetPicker(
                resources,
                packageManager,
                addWidgetActivityLauncher
            )
                            )
                            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") }
    }

    private fun onEditDone() {