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