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.systemui.dagger.qualifiers.Background
30 import com.android.systemui.log.LogBuffer
31 import com.android.systemui.log.core.Logger
32 import com.android.systemui.log.dagger.CommunalLog
33 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
34 import com.android.systemui.util.kotlin.getOrNull
35 import java.util.Optional
36 import javax.inject.Inject
37 import kotlinx.coroutines.CoroutineScope
38 import kotlinx.coroutines.flow.MutableStateFlow
39 import kotlinx.coroutines.flow.StateFlow
40 import kotlinx.coroutines.flow.asStateFlow
41 import kotlinx.coroutines.launch
42 
43 /**
44  * Widget host that interacts with AppWidget service and host to bind and provide info for widgets
45  * shown in the glanceable hub.
46  */
47 class CommunalWidgetHost
48 @Inject
49 constructor(
50     @Background private val bgScope: CoroutineScope,
51     private val appWidgetManager: Optional<AppWidgetManager>,
52     private val appWidgetHost: CommunalAppWidgetHost,
53     private val selectedUserInteractor: SelectedUserInteractor,
54     @CommunalLog logBuffer: LogBuffer,
55 ) : CommunalAppWidgetHost.Observer {
56     companion object {
57         private const val TAG = "CommunalWidgetHost"
58 
59         /** Returns whether a particular widget requires configuration when it is first added. */
60         fun requiresConfiguration(widgetInfo: AppWidgetProviderInfo): Boolean {
61             val featureFlags: Int = widgetInfo.widgetFeatures
62             // A widget's configuration is optional only if it's configuration is marked as optional
63             // AND it can be reconfigured later.
64             val configurationOptional =
65                 (featureFlags and WIDGET_FEATURE_CONFIGURATION_OPTIONAL != 0 &&
66                     featureFlags and WIDGET_FEATURE_RECONFIGURABLE != 0)
67             return widgetInfo.configure != null && !configurationOptional
68         }
69     }
70 
71     private val logger = Logger(logBuffer, TAG)
72 
73     private val _appWidgetProviders = MutableStateFlow(emptyMap<Int, AppWidgetProviderInfo?>())
74 
75     /**
76      * A flow of mappings between an appWidgetId and its corresponding [AppWidgetProviderInfo].
77      * These [AppWidgetProviderInfo]s represent app widgets that are actively bound to the
78      * [CommunalAppWidgetHost].
79      *
80      * The [AppWidgetProviderInfo] may be null in the case that the widget is bound but its provider
81      * is unavailable. For example, its package is not installed.
82      */
83     val appWidgetProviders: StateFlow<Map<Int, AppWidgetProviderInfo?>> =
84         _appWidgetProviders.asStateFlow()
85 
86     /**
87      * Allocate an app widget id and binds the widget with the provider and associated user.
88      *
89      * @param provider The component name of the provider.
90      * @param user User handle in which the provider resides. Default value is the current user.
91      * @return widgetId if binding is successful; otherwise return null
92      */
93     fun allocateIdAndBindWidget(provider: ComponentName, user: UserHandle? = null): Int? {
94         val id = appWidgetHost.allocateAppWidgetId()
95         if (
96             bindWidget(
97                 widgetId = id,
98                 user = user ?: UserHandle(selectedUserInteractor.getSelectedUserId()),
99                 provider = provider
100             )
101         ) {
102             logger.d("Successfully bound the widget $provider")
103             onProviderInfoUpdated(id, getAppWidgetInfo(id))
104             return id
105         }
106         appWidgetHost.deleteAppWidgetId(id)
107         logger.d("Failed to bind the widget $provider")
108         return null
109     }
110 
111     private fun bindWidget(widgetId: Int, user: UserHandle, provider: ComponentName): Boolean {
112         if (appWidgetManager.isPresent) {
113             val options =
114                 Bundle().apply {
115                     putInt(
116                         AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
117                         AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
118                     )
119                 }
120             return appWidgetManager
121                 .get()
122                 .bindAppWidgetIdIfAllowed(widgetId, user, provider, options)
123         }
124         return false
125     }
126 
127     @WorkerThread
128     fun getAppWidgetInfo(widgetId: Int): AppWidgetProviderInfo? {
129         return appWidgetManager.getOrNull()?.getAppWidgetInfo(widgetId)
130     }
131 
132     fun startObservingHost() {
133         appWidgetHost.addObserver(this@CommunalWidgetHost)
134     }
135 
136     fun stopObservingHost() {
137         appWidgetHost.removeObserver(this@CommunalWidgetHost)
138     }
139 
140     fun refreshProviders() {
141         bgScope.launch {
142             val newProviders = mutableMapOf<Int, AppWidgetProviderInfo?>()
143             appWidgetHost.appWidgetIds.forEach { appWidgetId ->
144                 // Listen for updates from each bound widget
145                 addListener(appWidgetId)
146 
147                 // Fetch provider info of the widget
148                 newProviders[appWidgetId] = getAppWidgetInfo(appWidgetId)
149             }
150 
151             _appWidgetProviders.value = newProviders.toMap()
152         }
153     }
154 
155     override fun onHostStartListening() {
156         refreshProviders()
157     }
158 
159     override fun onHostStopListening() {
160         // Remove listeners
161         _appWidgetProviders.value.keys.forEach { appWidgetId ->
162             appWidgetHost.removeListener(appWidgetId)
163         }
164 
165         // Clear providers
166         _appWidgetProviders.value = emptyMap()
167     }
168 
169     override fun onAllocateAppWidgetId(appWidgetId: Int) {
170         addListener(appWidgetId)
171     }
172 
173     override fun onDeleteAppWidgetId(appWidgetId: Int) {
174         appWidgetHost.removeListener(appWidgetId)
175         _appWidgetProviders.value =
176             _appWidgetProviders.value.toMutableMap().also { it.remove(appWidgetId) }
177     }
178 
179     private fun addListener(appWidgetId: Int) {
180         appWidgetHost.setListener(
181             appWidgetId,
182             CommunalAppWidgetHostListener(appWidgetId, this::onProviderInfoUpdated),
183         )
184     }
185 
186     private fun onProviderInfoUpdated(appWidgetId: Int, providerInfo: AppWidgetProviderInfo?) {
187         bgScope.launch {
188             _appWidgetProviders.value =
189                 _appWidgetProviders.value.toMutableMap().also { it[appWidgetId] = providerInfo }
190         }
191     }
192 
193     /** A [AppWidgetHostListener] for [appWidgetId]. */
194     private class CommunalAppWidgetHostListener(
195         private val appWidgetId: Int,
196         private val onUpdateProviderInfo: (Int, AppWidgetProviderInfo?) -> Unit,
197     ) : AppWidgetHostListener {
198         override fun onUpdateProviderInfo(providerInfo: AppWidgetProviderInfo?) {
199             onUpdateProviderInfo(appWidgetId, providerInfo)
200         }
201 
202         override fun onViewDataChanged(viewId: Int) {}
203 
204         override fun updateAppWidget(remoteViews: RemoteViews?) {}
205     }
206 }
207