diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 75e71e414262435f417434ace60c7c6f709621df..9ac1e9f58dbca396aa65d4b21b723b8c6dfd560c 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -730,6 +730,9 @@ <!-- Whether the communal service should be enabled --> <bool name="config_communalServiceEnabled">false</bool> + <!-- Component names of allowed communal widgets --> + <string-array name="config_communalWidgetAllowlist" translatable="false" /> + <!-- Component name of communal source service --> <string name="config_communalSourceComponent" translatable="false">@null</string> diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt new file mode 100644 index 0000000000000000000000000000000000000000..f9c4f29afee9e28b3c2723dd3e05519fb6c96d01 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 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.data.model + +import com.android.systemui.communal.shared.CommunalContentSize + +/** Metadata for the default widgets */ +data class CommunalWidgetMetadata( + /* Widget provider component name */ + val componentName: String, + + /* Defines the order in which the widget will be rendered in the grid. */ + val priority: Int, + + /* Supported sizes */ + val sizes: List<CommunalContentSize> +) diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt index e2a7d077a32c7adb399b0c6d28f72a611fdee94a..f13b62fbfeb90a8dc5a5a5d3cf0b91c4dc563ddf 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt @@ -27,13 +27,17 @@ import android.content.pm.PackageManager import android.os.UserManager import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.communal.data.model.CommunalWidgetMetadata import com.android.systemui.communal.shared.CommunalAppWidgetInfo +import com.android.systemui.communal.shared.CommunalContentSize import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags 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 com.android.systemui.settings.UserTracker import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose @@ -45,15 +49,20 @@ import kotlinx.coroutines.flow.map interface CommunalWidgetRepository { /** A flow of provider info for the stopwatch widget, or null if widget is unavailable. */ val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> + + /** Widgets that are allowed to render in the glanceable hub */ + val communalWidgetAllowlist: List<CommunalWidgetMetadata> } @SysUISingleton class CommunalWidgetRepositoryImpl @Inject constructor( + @Application private val applicationContext: Context, private val appWidgetManager: AppWidgetManager, private val appWidgetHost: AppWidgetHost, broadcastDispatcher: BroadcastDispatcher, + communalRepository: CommunalRepository, private val packageManager: PackageManager, private val userManager: UserManager, private val userTracker: UserTracker, @@ -64,12 +73,18 @@ constructor( const val TAG = "CommunalWidgetRepository" const val WIDGET_LABEL = "Stopwatch" } + override val communalWidgetAllowlist: List<CommunalWidgetMetadata> private val logger = Logger(logBuffer, TAG) // Whether the [AppWidgetHost] is listening for updates. private var isHostListening = false + init { + communalWidgetAllowlist = + if (communalRepository.isCommunalEnabled) getWidgetAllowlist() else emptyList() + } + // Widgets that should be rendered in communal mode. private val widgets: HashMap<Int, CommunalAppWidgetInfo> = hashMapOf() @@ -129,6 +144,18 @@ constructor( return@map addWidget(providerInfo) } + private fun getWidgetAllowlist(): List<CommunalWidgetMetadata> { + val componentNames = + applicationContext.resources.getStringArray(R.array.config_communalWidgetAllowlist) + return componentNames.mapIndexed { index, name -> + CommunalWidgetMetadata( + componentName = name, + priority = componentNames.size - index, + sizes = listOf(CommunalContentSize.HALF) + ) + } + } + private fun startListening() { if (isHostListening) { return diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt new file mode 100644 index 0000000000000000000000000000000000000000..0bd7d86c972d28c125108d4f577878cd6a03179d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt @@ -0,0 +1,8 @@ +package com.android.systemui.communal.shared + +/** Supported sizes for communal content in the layout grid. */ +enum class CommunalContentSize { + FULL, + HALF, + THIRD, +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt index 3df9cbb29e4aeb17a0897983913a7fbbdae6f9c4..91409a37655685dec6049d9b49857dfe6454b0ac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt @@ -11,11 +11,14 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.communal.data.model.CommunalWidgetMetadata +import com.android.systemui.communal.shared.CommunalContentSize import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.FakeLogBuffer +import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.kotlinArgumentCaptor @@ -59,9 +62,12 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var stopwatchProviderInfo: AppWidgetProviderInfo + private lateinit var communalRepository: FakeCommunalRepository + private lateinit var logBuffer: LogBuffer private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) @Before @@ -71,6 +77,14 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { logBuffer = FakeLogBuffer.Factory.create() featureFlagEnabled(true) + communalRepository = FakeCommunalRepository() + communalRepository.setIsCommunalEnabled(true) + + overrideResource( + R.array.config_communalWidgetAllowlist, + arrayOf(componentName1, componentName2) + ) + whenever(stopwatchProviderInfo.loadLabel(any())).thenReturn("Stopwatch") whenever(userTracker.userHandle).thenReturn(userHandle) } @@ -219,11 +233,36 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { Mockito.verify(appWidgetHost).stopListening() } + @Test + fun getCommunalWidgetAllowList_onInit() { + testScope.runTest { + val repository = initCommunalWidgetRepository() + val communalWidgetAllowlist = repository.communalWidgetAllowlist + assertThat( + listOf( + CommunalWidgetMetadata( + componentName = componentName1, + priority = 2, + sizes = listOf(CommunalContentSize.HALF) + ), + CommunalWidgetMetadata( + componentName = componentName2, + priority = 1, + sizes = listOf(CommunalContentSize.HALF) + ) + ) + ) + .containsExactly(*communalWidgetAllowlist.toTypedArray()) + } + } + private fun initCommunalWidgetRepository(): CommunalWidgetRepositoryImpl { return CommunalWidgetRepositoryImpl( + context, appWidgetManager, appWidgetHost, broadcastDispatcher, + communalRepository, packageManager, userManager, userTracker, @@ -282,4 +321,9 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { private fun installedProviders(providers: List<AppWidgetProviderInfo>) { whenever(appWidgetManager.installedProviders).thenReturn(providers) } + + companion object { + const val componentName1 = "component name 1" + const val componentName2 = "component name 2" + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt index 1a8c5830e453e0aceba2aaa93eb634fd78268762..30132f7747b7e2356d1658a466bb3b7893b72fbb 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt @@ -1,5 +1,6 @@ package com.android.systemui.communal.data.repository +import com.android.systemui.communal.data.model.CommunalWidgetMetadata import com.android.systemui.communal.shared.CommunalAppWidgetInfo import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -8,6 +9,7 @@ import kotlinx.coroutines.flow.MutableStateFlow class FakeCommunalWidgetRepository : CommunalWidgetRepository { private val _stopwatchAppWidgetInfo = MutableStateFlow<CommunalAppWidgetInfo?>(null) override val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> = _stopwatchAppWidgetInfo + override var communalWidgetAllowlist: List<CommunalWidgetMetadata> = emptyList() fun setStopwatchAppWidgetInfo(appWidgetInfo: CommunalAppWidgetInfo) { _stopwatchAppWidgetInfo.value = appWidgetInfo