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 20 import android.appwidget.AppWidgetHostView 21 import android.appwidget.AppWidgetProviderInfo 22 import android.content.Context 23 import android.os.Looper 24 import android.widget.RemoteViews 25 import com.android.systemui.log.LogBuffer 26 import com.android.systemui.log.core.Logger 27 import javax.annotation.concurrent.GuardedBy 28 import kotlinx.coroutines.CoroutineScope 29 import kotlinx.coroutines.flow.MutableSharedFlow 30 import kotlinx.coroutines.flow.SharedFlow 31 import kotlinx.coroutines.flow.asSharedFlow 32 import kotlinx.coroutines.launch 33 34 /** Communal app widget host that creates a [CommunalAppWidgetHostView]. */ 35 class CommunalAppWidgetHost( 36 context: Context, 37 private val backgroundScope: CoroutineScope, 38 hostId: Int, 39 interactionHandler: RemoteViews.InteractionHandler, 40 looper: Looper, 41 logBuffer: LogBuffer, 42 ) : AppWidgetHost(context, hostId, interactionHandler, looper) { 43 44 private val logger = Logger(logBuffer, TAG) 45 46 private val _appWidgetIdToRemove = MutableSharedFlow<Int>() 47 48 /** App widget ids that have been removed and no longer available. */ 49 val appWidgetIdToRemove: SharedFlow<Int> = _appWidgetIdToRemove.asSharedFlow() 50 51 @GuardedBy("observers") private val observers = mutableSetOf<Observer>() 52 53 override fun onCreateView( 54 context: Context, 55 appWidgetId: Int, 56 appWidget: AppWidgetProviderInfo? 57 ): AppWidgetHostView { 58 return CommunalAppWidgetHostView(context) 59 } 60 61 /** 62 * Creates and returns a [CommunalAppWidgetHostView]. This method does the same thing as 63 * `createView`. The only difference is that the returned value will be casted to 64 * [CommunalAppWidgetHostView]. 65 */ 66 fun createViewForCommunal( 67 context: Context?, 68 appWidgetId: Int, 69 appWidget: AppWidgetProviderInfo? 70 ): CommunalAppWidgetHostView { 71 // `createView` internally calls `onCreateView` to create the view. We cannot override 72 // `createView`, but we are sure that the hostView is `CommunalAppWidgetHostView` 73 return createView(context, appWidgetId, appWidget) as CommunalAppWidgetHostView 74 } 75 76 override fun onAppWidgetRemoved(appWidgetId: Int) { 77 backgroundScope.launch { 78 logger.i({ "App widget removed from system: $int1" }) { int1 = appWidgetId } 79 _appWidgetIdToRemove.emit(appWidgetId) 80 } 81 } 82 83 override fun allocateAppWidgetId(): Int { 84 return super.allocateAppWidgetId().also { appWidgetId -> 85 backgroundScope.launch { 86 synchronized(observers) { 87 observers.forEach { observer -> observer.onAllocateAppWidgetId(appWidgetId) } 88 } 89 } 90 } 91 } 92 93 override fun deleteAppWidgetId(appWidgetId: Int) { 94 super.deleteAppWidgetId(appWidgetId) 95 backgroundScope.launch { 96 synchronized(observers) { 97 observers.forEach { observer -> observer.onDeleteAppWidgetId(appWidgetId) } 98 } 99 } 100 } 101 102 override fun startListening() { 103 super.startListening() 104 backgroundScope.launch { 105 synchronized(observers) { 106 observers.forEach { observer -> observer.onHostStartListening() } 107 } 108 } 109 } 110 111 override fun stopListening() { 112 super.stopListening() 113 backgroundScope.launch { 114 synchronized(observers) { 115 observers.forEach { observer -> observer.onHostStopListening() } 116 } 117 } 118 } 119 120 fun addObserver(observer: Observer) { 121 synchronized(observers) { observers.add(observer) } 122 } 123 124 fun removeObserver(observer: Observer) { 125 synchronized(observers) { observers.remove(observer) } 126 } 127 128 /** 129 * Allows another class to observe the [CommunalAppWidgetHost] and handle any logic there. 130 * 131 * This is mainly for testability as it is difficult to test a real instance of [AppWidgetHost] 132 * which communicates with framework services. 133 * 134 * Note: all the callbacks are launched from the background scope. 135 */ 136 interface Observer { 137 /** Called immediately after the host has started listening for widget updates. */ 138 fun onHostStartListening() {} 139 140 /** Called immediately after the host has stopped listening for widget updates. */ 141 fun onHostStopListening() {} 142 143 /** Called immediately after a new app widget id has been allocated. */ 144 fun onAllocateAppWidgetId(appWidgetId: Int) {} 145 146 /** Called immediately after an app widget id is to be deleted. */ 147 fun onDeleteAppWidgetId(appWidgetId: Int) {} 148 } 149 150 companion object { 151 private const val TAG = "CommunalAppWidgetHost" 152 } 153 } 154