• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.communal.widgets
18 
19 import android.appwidget.AppWidgetHost.AppWidgetHostListener
20 import android.appwidget.AppWidgetManager
21 import android.appwidget.AppWidgetProviderInfo
22 import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL
23 import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE
24 import android.content.ComponentName
25 import android.os.Bundle
26 import android.os.UserHandle
27 import android.widget.RemoteViews
28 import androidx.annotation.WorkerThread
29 import com.android.app.tracing.coroutines.launchTraced as launch
30 import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
31 import com.android.systemui.dagger.qualifiers.Background
32 import com.android.systemui.log.LogBuffer
33 import com.android.systemui.log.core.Logger
34 import com.android.systemui.log.dagger.CommunalLog
35 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
36 import com.android.systemui.util.kotlin.getOrNull
37 import java.util.Optional
38 import javax.inject.Inject
39 import kotlinx.coroutines.CoroutineScope
40 import kotlinx.coroutines.flow.MutableStateFlow
41 import kotlinx.coroutines.flow.StateFlow
42 import kotlinx.coroutines.flow.asStateFlow
43 
44 /**
45  * Widget host that interacts with AppWidget service and host to bind and provide info for widgets
46  * shown in the glanceable hub.
47  */
48 class CommunalWidgetHost
49 @Inject
50 constructor(
51     @Background private val bgScope: CoroutineScope,
52     private val appWidgetManager: Optional<AppWidgetManager>,
53     private val appWidgetHost: CommunalAppWidgetHost,
54     private val selectedUserInteractor: SelectedUserInteractor,
55     @CommunalLog logBuffer: LogBuffer,
56     glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper,
57 ) : CommunalAppWidgetHost.Observer {
58 
59     init {
60         // The communal widget host should never be accessed from a headless system user.
61         glanceableHubMultiUserHelper.assertNotInHeadlessSystemUser()
62     }
63 
64     companion object {
65         private const val TAG = "CommunalWidgetHost"
66 
67         /** Returns whether a particular widget requires configuration when it is first added. */
68         fun requiresConfiguration(widgetInfo: AppWidgetProviderInfo): Boolean {
69             val featureFlags: Int = widgetInfo.widgetFeatures
70             // A widget's configuration is optional only if it's configuration is marked as optional
71             // AND it can be reconfigured later.
72             val configurationOptional =
73                 (featureFlags and WIDGET_FEATURE_CONFIGURATION_OPTIONAL != 0 &&
74                     featureFlags and WIDGET_FEATURE_RECONFIGURABLE != 0)
75             return widgetInfo.configure != null && !configurationOptional
76         }
77     }
78 
79     private val logger = Logger(logBuffer, TAG)
80 
81     private val _appWidgetProviders = MutableStateFlow(emptyMap<Int, AppWidgetProviderInfo?>())
82 
83     /**
84      * A flow of mappings between an appWidgetId and its corresponding [AppWidgetProviderInfo].
85      * These [AppWidgetProviderInfo]s represent app widgets that are actively bound to the
86      * [CommunalAppWidgetHost].
87      *
88      * The [AppWidgetProviderInfo] may be null in the case that the widget is bound but its provider
89      * is unavailable. For example, its package is not installed.
90      */
91     val appWidgetProviders: StateFlow<Map<Int, AppWidgetProviderInfo?>> =
92         _appWidgetProviders.asStateFlow()
93 
94     /**
95      * Allocate an app widget id and binds the widget with the provider and associated user.
96      *
97      * @param provider The component name of the provider.
98      * @param user User handle in which the provider resides. Default value is the current user.
99      * @return widgetId if binding is successful; otherwise return null
100      */
101     fun allocateIdAndBindWidget(provider: ComponentName, user: UserHandle? = null): Int? {
102         val id = appWidgetHost.allocateAppWidgetId()
103         if (
104             bindWidget(
105                 widgetId = id,
106                 user = user ?: UserHandle(selectedUserInteractor.getSelectedUserId()),
107                 provider = provider,
108             )
109         ) {
110             logger.d("Successfully bound the widget $provider")
111             onProviderInfoUpdated(id, getAppWidgetInfo(id))
112             return id
113         }
114         appWidgetHost.deleteAppWidgetId(id)
115         logger.d("Failed to bind the widget $provider")
116         return null
117     }
118 
119     private fun bindWidget(widgetId: Int, user: UserHandle, provider: ComponentName): Boolean {
120         if (appWidgetManager.isPresent) {
121             val options =
122                 Bundle().apply {
123                     putInt(
124                         AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
125                         AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
126                     )
127                 }
128             return appWidgetManager
129                 .get()
130                 .bindAppWidgetIdIfAllowed(widgetId, user, provider, options)
131         }
132         return false
133     }
134 
135     @WorkerThread
136     fun getAppWidgetInfo(widgetId: Int): AppWidgetProviderInfo? {
137         return appWidgetManager.getOrNull()?.getAppWidgetInfo(widgetId)
138     }
139 
140     fun startObservingHost() {
141         appWidgetHost.addObserver(this@CommunalWidgetHost)
142     }
143 
144     fun stopObservingHost() {
145         appWidgetHost.removeObserver(this@CommunalWidgetHost)
146     }
147 
148     fun refreshProviders() {
149         bgScope.launch {
150             val newProviders = mutableMapOf<Int, AppWidgetProviderInfo?>()
151             appWidgetHost.appWidgetIds.forEach { appWidgetId ->
152                 // Listen for updates from each bound widget
153                 addListener(appWidgetId)
154 
155                 // Fetch provider info of the widget
156                 newProviders[appWidgetId] = getAppWidgetInfo(appWidgetId)
157             }
158 
159             _appWidgetProviders.value = newProviders.toMap()
160         }
161     }
162 
163     override fun onHostStartListening() {
164         refreshProviders()
165     }
166 
167     override fun onHostStopListening() {
168         // Remove listeners
169         _appWidgetProviders.value.keys.forEach { appWidgetId ->
170             appWidgetHost.removeListener(appWidgetId)
171         }
172 
173         // Clear providers
174         _appWidgetProviders.value = emptyMap()
175     }
176 
177     override fun onAllocateAppWidgetId(appWidgetId: Int) {
178         addListener(appWidgetId)
179     }
180 
181     override fun onDeleteAppWidgetId(appWidgetId: Int) {
182         appWidgetHost.removeListener(appWidgetId)
183         _appWidgetProviders.value =
184             _appWidgetProviders.value.toMutableMap().also { it.remove(appWidgetId) }
185     }
186 
187     private fun addListener(appWidgetId: Int) {
188         appWidgetHost.setListener(
189             appWidgetId,
190             CommunalAppWidgetHostListener(appWidgetId, this::onProviderInfoUpdated),
191         )
192     }
193 
194     private fun onProviderInfoUpdated(appWidgetId: Int, providerInfo: AppWidgetProviderInfo?) {
195         bgScope.launch {
196             _appWidgetProviders.value =
197                 _appWidgetProviders.value.toMutableMap().also { it[appWidgetId] = providerInfo }
198         }
199     }
200 
201     /** A [AppWidgetHostListener] for [appWidgetId]. */
202     private class CommunalAppWidgetHostListener(
203         private val appWidgetId: Int,
204         private val onUpdateProviderInfo: (Int, AppWidgetProviderInfo?) -> Unit,
205     ) : AppWidgetHostListener {
206         override fun onUpdateProviderInfo(providerInfo: AppWidgetProviderInfo?) {
207             onUpdateProviderInfo(appWidgetId, providerInfo)
208         }
209 
210         override fun onViewDataChanged(viewId: Int) {}
211 
212         override fun updateAppWidget(remoteViews: RemoteViews?) {}
213     }
214 }
215