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