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