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

Commit 769510be authored by Coco Duan's avatar Coco Duan Committed by Android (Google) Code Review
Browse files

Merge "Support widgets under work profile on glanceable hub" into main

parents 96623fce 112fb0cd
Loading
Loading
Loading
Loading
+14 −5
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.communal.ui.compose

import android.content.ComponentName
import android.os.UserHandle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.toMutableStateList
@@ -33,9 +34,10 @@ fun rememberContentListState(
    return remember(communalContent) {
        ContentListState(
            communalContent,
            { componentName, priority ->
            { componentName, user, priority ->
                viewModel.onAddWidget(
                    componentName,
                    user,
                    priority,
                    widgetConfigurator,
                )
@@ -54,7 +56,8 @@ fun rememberContentListState(
class ContentListState
internal constructor(
    communalContent: List<CommunalContentModel>,
    private val onAddWidget: (componentName: ComponentName, priority: Int) -> Unit,
    private val onAddWidget:
        (componentName: ComponentName, user: UserHandle, priority: Int) -> Unit,
    private val onDeleteWidget: (id: Int) -> Unit,
    private val onReorderWidgets: (widgetIdToPriorityMap: Map<Int, Int>) -> Unit,
) {
@@ -81,10 +84,16 @@ internal constructor(
     *
     * @param newItemComponentName name of the new widget that was dropped into the list; null if no
     *   new widget was added.
     * @param newItemUser user profile associated with the new widget that was dropped into the
     *   list; null if no new widget was added.
     * @param newItemIndex index at which the a new widget was dropped into the list; null if no new
     *   widget was dropped.
     */
    fun onSaveList(newItemComponentName: ComponentName? = null, newItemIndex: Int? = null) {
    fun onSaveList(
        newItemComponentName: ComponentName? = null,
        newItemUser: UserHandle? = null,
        newItemIndex: Int? = null
    ) {
        // filters placeholder, but, maintains the indices of the widgets as if the placeholder was
        // in the list. When persisted in DB, this leaves space for the new item (to be added) at
        // the correct priority.
@@ -100,8 +109,8 @@ internal constructor(
                .toMap()
        // reorder and then add the new widget
        onReorderWidgets(widgetIdToPriorityMap)
        if (newItemComponentName != null && newItemIndex != null) {
            onAddWidget(newItemComponentName, /*priority=*/ list.size - newItemIndex)
        if (newItemComponentName != null && newItemUser != null && newItemIndex != null) {
            onAddWidget(newItemComponentName, newItemUser, /*priority=*/ list.size - newItemIndex)
        }
    }

+10 −11
Original line number Diff line number Diff line
@@ -17,8 +17,6 @@
package com.android.systemui.communal.ui.compose

import android.content.ClipDescription
import android.content.ComponentName
import android.content.Intent
import android.view.DragEvent
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.draganddrop.dragAndDropTarget
@@ -44,6 +42,8 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.ui.compose.extensions.plus
import com.android.systemui.communal.util.WidgetPickerIntentUtils
import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
@@ -201,12 +201,14 @@ internal class DragAndDropTargetState(
            return false
        }
        return placeHolderIndex?.let { dropIndex ->
            val componentName = event.maybeWidgetComponentName()
            if (componentName != null) {
            val widgetExtra = event.maybeWidgetExtra() ?: return false
            val (componentName, user) = widgetExtra
            if (componentName != null && user != null) {
                // Placeholder isn't removed yet to allow the setting the right priority for items
                // before adding in the new item.
                contentListState.onSaveList(
                    newItemComponentName = componentName,
                    newItemUser = user,
                    newItemIndex = dropIndex
                )
                return@let true
@@ -260,15 +262,12 @@ internal class DragAndDropTargetState(
    }

    /**
     * Parses and returns the component name of the widget that was dropped into the communal grid.
     * Parses and returns the intent extra associated with the widget that is dropped into the grid.
     *
     * Returns null if the drop event didn't include the widget information.
     * Returns null if the drop event didn't include intent information.
     */
    private fun DragAndDropEvent.maybeWidgetComponentName(): ComponentName? {
    private fun DragAndDropEvent.maybeWidgetExtra(): WidgetPickerIntentUtils.WidgetExtra? {
        val clipData = this.toAndroidDragEvent().clipData.takeIf { it.itemCount != 0 }
        return clipData
            ?.getItemAt(0)
            ?.intent
            ?.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName::class.java)
        return clipData?.getItemAt(0)?.intent?.let { intent -> getWidgetExtraFromIntent(intent) }
    }
}
+42 −13
Original line number Diff line number Diff line
@@ -21,15 +21,16 @@ import android.appwidget.AppWidgetProviderInfo
import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL
import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE
import android.content.ComponentName
import android.os.UserHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.db.CommunalItemRank
import com.android.systemui.communal.data.db.CommunalWidgetDao
import com.android.systemui.communal.data.db.CommunalWidgetItem
import com.android.systemui.communal.shared.CommunalWidgetHost
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.CommunalWidgetHost
import com.android.systemui.communal.widgets.widgetConfiguratorFail
import com.android.systemui.communal.widgets.widgetConfiguratorSuccess
import com.android.systemui.coroutines.collectLastValue
@@ -136,14 +137,20 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
            val provider = ComponentName("pkg_name", "cls_name")
            val id = 1
            val priority = 1
            val user = UserHandle(0)
            whenever(communalWidgetHost.getAppWidgetInfo(id))
                .thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
            whenever(communalWidgetHost.allocateIdAndBindWidget(any<ComponentName>()))
            whenever(
                    communalWidgetHost.allocateIdAndBindWidget(
                        any<ComponentName>(),
                        any<UserHandle>()
                    )
                )
                .thenReturn(id)
            underTest.addWidget(provider, priority, kosmos.widgetConfiguratorSuccess)
            underTest.addWidget(provider, user, priority, kosmos.widgetConfiguratorSuccess)
            runCurrent()

            verify(communalWidgetHost).allocateIdAndBindWidget(provider)
            verify(communalWidgetHost).allocateIdAndBindWidget(provider, user)
            verify(communalWidgetDao).addWidget(id, provider, priority)
        }

@@ -153,13 +160,20 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
            val provider = ComponentName("pkg_name", "cls_name")
            val id = 1
            val priority = 1
            val user = UserHandle(0)
            whenever(communalWidgetHost.getAppWidgetInfo(id))
                .thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
            whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id)
            underTest.addWidget(provider, priority, kosmos.widgetConfiguratorFail)
            whenever(
                    communalWidgetHost.allocateIdAndBindWidget(
                        any<ComponentName>(),
                        any<UserHandle>()
                    )
                )
                .thenReturn(id)
            underTest.addWidget(provider, user, priority, kosmos.widgetConfiguratorFail)
            runCurrent()

            verify(communalWidgetHost).allocateIdAndBindWidget(provider)
            verify(communalWidgetHost).allocateIdAndBindWidget(provider, user)
            verify(communalWidgetDao, never()).addWidget(id, provider, priority)
            verify(appWidgetHost).deleteAppWidgetId(id)
        }
@@ -170,13 +184,22 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
            val provider = ComponentName("pkg_name", "cls_name")
            val id = 1
            val priority = 1
            val user = UserHandle(0)
            whenever(communalWidgetHost.getAppWidgetInfo(id))
                .thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
            whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id)
            underTest.addWidget(provider, priority) { throw IllegalStateException("some error") }
            whenever(
                    communalWidgetHost.allocateIdAndBindWidget(
                        any<ComponentName>(),
                        any<UserHandle>()
                    )
                )
                .thenReturn(id)
            underTest.addWidget(provider, user, priority) {
                throw IllegalStateException("some error")
            }
            runCurrent()

            verify(communalWidgetHost).allocateIdAndBindWidget(provider)
            verify(communalWidgetHost).allocateIdAndBindWidget(provider, user)
            verify(communalWidgetDao, never()).addWidget(id, provider, priority)
            verify(appWidgetHost).deleteAppWidgetId(id)
        }
@@ -187,14 +210,20 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
            val provider = ComponentName("pkg_name", "cls_name")
            val id = 1
            val priority = 1
            val user = UserHandle(0)
            whenever(communalWidgetHost.getAppWidgetInfo(id))
                .thenReturn(PROVIDER_INFO_CONFIGURATION_OPTIONAL)
            whenever(communalWidgetHost.allocateIdAndBindWidget(any<ComponentName>()))
            whenever(
                    communalWidgetHost.allocateIdAndBindWidget(
                        any<ComponentName>(),
                        any<UserHandle>()
                    )
                )
                .thenReturn(id)
            underTest.addWidget(provider, priority, kosmos.widgetConfiguratorFail)
            underTest.addWidget(provider, user, priority, kosmos.widgetConfiguratorFail)
            runCurrent()

            verify(communalWidgetHost).allocateIdAndBindWidget(provider)
            verify(communalWidgetHost).allocateIdAndBindWidget(provider, user)
            verify(communalWidgetDao).addWidget(id, provider, priority)
        }

+166 −0
Original line number Diff line number Diff line
/*
 * 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.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.pm.UserInfo
import android.os.UserHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.testKosmos
import com.android.systemui.user.data.model.SelectedUserModel
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.user.domain.interactor.selectedUserInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations

@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class CommunalWidgetHostTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope

    @Mock private lateinit var appWidgetManager: AppWidgetManager
    @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
    private val selectedUserInteractor: SelectedUserInteractor by lazy {
        kosmos.selectedUserInteractor
    }

    private lateinit var underTest: CommunalWidgetHost

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)

        underTest =
            CommunalWidgetHost(
                Optional.of(appWidgetManager),
                appWidgetHost,
                selectedUserInteractor,
                logcatLogBuffer("CommunalWidgetHostTest"),
            )
    }

    @Test
    fun allocateIdAndBindWidget_withCurrentUser() =
        testScope.runTest {
            val provider = ComponentName("pkg_name", "cls_name")
            val widgetId = 1
            val userId by collectLastValue(selectedUserInteractor.selectedUser)
            selectUser()
            runCurrent()

            val user = UserHandle(checkNotNull(userId))
            whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(widgetId)
            whenever(
                    appWidgetManager.bindAppWidgetIdIfAllowed(
                        any<Int>(),
                        any<UserHandle>(),
                        any<ComponentName>(),
                        nullable()
                    )
                )
                .thenReturn(true)

            // bind the widget with the current user when no user is explicitly set
            val result = underTest.allocateIdAndBindWidget(provider)

            verify(appWidgetHost).allocateAppWidgetId()
            verify(appWidgetManager).bindAppWidgetIdIfAllowed(widgetId, user, provider, null)
            assertThat(result).isEqualTo(widgetId)
        }

    @Test
    fun allocateIdAndBindWidget_onSuccess() =
        testScope.runTest {
            val provider = ComponentName("pkg_name", "cls_name")
            val widgetId = 1
            val user = UserHandle(0)

            whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(widgetId)
            whenever(
                    appWidgetManager.bindAppWidgetIdIfAllowed(
                        any<Int>(),
                        any<UserHandle>(),
                        any<ComponentName>(),
                        nullable()
                    )
                )
                .thenReturn(true)

            // provider and user handle are both set
            val result = underTest.allocateIdAndBindWidget(provider, user)

            verify(appWidgetHost).allocateAppWidgetId()
            verify(appWidgetManager).bindAppWidgetIdIfAllowed(widgetId, user, provider, null)
            assertThat(result).isEqualTo(widgetId)
        }

    @Test
    fun allocateIdAndBindWidget_onFailure() =
        testScope.runTest {
            val provider = ComponentName("pkg_name", "cls_name")
            val widgetId = 1
            val user = UserHandle(0)

            whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(widgetId)
            // failed to bind widget
            whenever(
                    appWidgetManager.bindAppWidgetIdIfAllowed(
                        any<Int>(),
                        any<UserHandle>(),
                        any<ComponentName>(),
                        nullable()
                    )
                )
                .thenReturn(false)
            val result = underTest.allocateIdAndBindWidget(provider, user)

            verify(appWidgetHost).allocateAppWidgetId()
            verify(appWidgetManager).bindAppWidgetIdIfAllowed(widgetId, user, provider, null)
            verify(appWidgetHost).deleteAppWidgetId(widgetId)
            assertThat(result).isNull()
        }

    private fun selectUser() {
        kosmos.fakeUserRepository.selectedUser.value =
            SelectedUserModel(
                userInfo = UserInfo(0, "Current user", 0),
                selectionStatus = SelectionStatus.SELECTION_COMPLETE
            )
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import com.android.systemui.communal.data.repository.CommunalRepositoryModule
import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryModule
import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
import com.android.systemui.communal.widgets.CommunalWidgetModule
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.communal.widgets.EditWidgetsActivityStarterImpl
import dagger.Binds
@@ -36,6 +37,7 @@ import dagger.Module
            CommunalTutorialRepositoryModule::class,
            CommunalWidgetRepositoryModule::class,
            CommunalDatabaseModule::class,
            CommunalWidgetModule::class,
            CommunalPrefsRepositoryModule::class,
            CommunalSettingsRepositoryModule::class,
        ]
Loading