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