<lambda>null1 package com.android.wallpaper.util.wallpaperconnection
2 
3 import android.app.WallpaperInfo
4 import android.app.WallpaperManager
5 import android.content.ContentValues
6 import android.content.Context
7 import android.content.Intent
8 import android.content.ServiceConnection
9 import android.graphics.Matrix
10 import android.graphics.Point
11 import android.net.Uri
12 import android.os.RemoteException
13 import android.service.wallpaper.IWallpaperEngine
14 import android.service.wallpaper.IWallpaperService
15 import android.service.wallpaper.WallpaperService
16 import android.util.Log
17 import android.view.MotionEvent
18 import android.view.SurfaceControl
19 import android.view.SurfaceView
20 import com.android.app.tracing.TraceUtils.traceAsync
21 import com.android.wallpaper.model.wallpaper.DeviceDisplayType
22 import com.android.wallpaper.picker.data.WallpaperModel.LiveWallpaperModel
23 import com.android.wallpaper.util.WallpaperConnection
24 import com.android.wallpaper.util.WallpaperConnection.WhichPreview
25 import java.util.concurrent.ConcurrentHashMap
26 import kotlinx.coroutines.CancellableContinuation
27 import kotlinx.coroutines.Deferred
28 import kotlinx.coroutines.async
29 import kotlinx.coroutines.coroutineScope
30 import kotlinx.coroutines.suspendCancellableCoroutine
31 import kotlinx.coroutines.sync.Mutex
32 import kotlinx.coroutines.sync.withLock
33 
34 object WallpaperConnectionUtils {
35 
36     const val TAG = "WallpaperConnectionUtils"
37 
38     // engineMap and surfaceControlMap are used for disconnecting wallpaper services.
39     private val engineMap =
40         ConcurrentHashMap<String, Deferred<Pair<ServiceConnection, WallpaperEngineConnection>>>()
41     // Note that when one wallpaper engine's render is mirrored to a new surface view, we call
42     // engine.mirrorSurfaceControl() and will have a new surface control instance.
43     private val surfaceControlMap = mutableMapOf<String, MutableList<SurfaceControl>>()
44     // Track the currently used creative wallpaper config preview URI to avoid unnecessary multiple
45     // update queries for the same preview.
46     private val creativeWallpaperConfigPreviewUriMap = mutableMapOf<String, Uri>()
47 
48     private val mutex = Mutex()
49 
50     /** Only call this function when the surface view is attached. */
51     suspend fun connect(
52         context: Context,
53         wallpaperModel: LiveWallpaperModel,
54         whichPreview: WhichPreview,
55         destinationFlag: Int,
56         surfaceView: SurfaceView,
57         engineRenderingConfig: EngineRenderingConfig,
58         isFirstBinding: Boolean,
59         listener: WallpaperEngineConnection.WallpaperEngineConnectionListener? = null,
60     ) {
61         val wallpaperInfo = wallpaperModel.liveWallpaperData.systemWallpaperInfo
62         val engineDisplaySize = engineRenderingConfig.getEngineDisplaySize()
63         val engineKey = wallpaperInfo.getKey(engineDisplaySize)
64 
65         traceAsync(TAG, "connect") {
66             // Update the creative wallpaper uri before starting the service.
67             wallpaperModel.creativeWallpaperData?.configPreviewUri?.let {
68                 val uriKey = wallpaperInfo.getKey()
69                 if (!creativeWallpaperConfigPreviewUriMap.containsKey(uriKey)) {
70                     mutex.withLock {
71                         if (!creativeWallpaperConfigPreviewUriMap.containsKey(uriKey)) {
72                             // First time binding wallpaper should initialize wallpaper preview.
73                             if (isFirstBinding) {
74                                 context.contentResolver.update(it, ContentValues(), null)
75                             }
76                             creativeWallpaperConfigPreviewUriMap[uriKey] = it
77                         }
78                     }
79                 }
80             }
81 
82             if (!engineMap.containsKey(engineKey)) {
83                 mutex.withLock {
84                     if (!engineMap.containsKey(engineKey)) {
85                         engineMap[engineKey] = coroutineScope {
86                             async {
87                                 initEngine(
88                                     context,
89                                     wallpaperModel.getWallpaperServiceIntent(),
90                                     engineDisplaySize,
91                                     destinationFlag,
92                                     whichPreview,
93                                     surfaceView,
94                                     listener,
95                                 )
96                             }
97                         }
98                     }
99                 }
100             }
101 
102             engineMap[engineKey]?.await()?.let { (_, engineConnection) ->
103                 engineConnection.engine?.let {
104                     mirrorAndReparent(
105                         engineKey,
106                         it,
107                         surfaceView,
108                         engineRenderingConfig.getEngineDisplaySize(),
109                         engineRenderingConfig.enforceSingleEngine,
110                     )
111                 }
112             }
113         }
114     }
115 
116     suspend fun disconnect(
117         context: Context,
118         wallpaperModel: LiveWallpaperModel,
119         displaySize: Point,
120     ) {
121         val engineKey = wallpaperModel.liveWallpaperData.systemWallpaperInfo.getKey(displaySize)
122 
123         traceAsync(TAG, "disconnect") {
124             if (engineMap.containsKey(engineKey)) {
125                 mutex.withLock {
126                     engineMap.remove(engineKey)?.await()?.let {
127                         (serviceConnection, engineConnection) ->
128                         engineConnection.engine?.destroy()
129                         engineConnection.removeListener()
130                         context.unbindService(serviceConnection)
131                     }
132                 }
133             }
134 
135             if (surfaceControlMap.containsKey(engineKey)) {
136                 mutex.withLock {
137                     surfaceControlMap.remove(engineKey)?.let { surfaceControls ->
138                         surfaceControls.forEach { it.release() }
139                         surfaceControls.clear()
140                     }
141                 }
142             }
143 
144             val uriKey = wallpaperModel.liveWallpaperData.systemWallpaperInfo.getKey()
145             if (creativeWallpaperConfigPreviewUriMap.containsKey(uriKey)) {
146                 mutex.withLock {
147                     if (creativeWallpaperConfigPreviewUriMap.containsKey(uriKey)) {
148                         creativeWallpaperConfigPreviewUriMap.remove(uriKey)
149                     }
150                 }
151             }
152         }
153     }
154 
155     /**
156      * Disconnect all live wallpaper services without releasing and clear surface controls. This
157      * function is called before binding static wallpapers. We have cases that user switch between
158      * live wan static wallpapers. When switching from live to static wallpapers, we need to
159      * disconnect the live wallpaper services to have the static wallpapers show up. But we can not
160      * clear the surface controls yet, because we will need them to render the live wallpapers again
161      * when switching from static to live wallpapers again.
162      */
163     suspend fun disconnectAllServices(context: Context) {
164         engineMap.keys.map { key ->
165             mutex.withLock {
166                 engineMap.remove(key)?.await()?.let { (serviceConnection, engineConnection) ->
167                     engineConnection.engine?.destroy()
168                     engineConnection.removeListener()
169                     context.unbindService(serviceConnection)
170                 }
171             }
172         }
173 
174         creativeWallpaperConfigPreviewUriMap.clear()
175     }
176 
177     suspend fun dispatchTouchEvent(
178         wallpaperModel: LiveWallpaperModel,
179         engineRenderingConfig: EngineRenderingConfig,
180         event: MotionEvent,
181     ) {
182         val engine =
183             wallpaperModel.liveWallpaperData.systemWallpaperInfo
184                 .getKey(engineRenderingConfig.getEngineDisplaySize())
185                 .let { engineKey -> engineMap[engineKey]?.await()?.second?.engine }
186 
187         if (engine != null) {
188             val action: Int = event.actionMasked
189             val dup = MotionEvent.obtainNoHistory(event).also { it.setLocation(event.x, event.y) }
190             val pointerIndex = event.actionIndex
191             try {
192                 engine.dispatchPointer(dup)
193                 if (action == MotionEvent.ACTION_UP) {
194                     engine.dispatchWallpaperCommand(
195                         WallpaperManager.COMMAND_TAP,
196                         event.x.toInt(),
197                         event.y.toInt(),
198                         0,
199                         null
200                     )
201                 } else if (action == MotionEvent.ACTION_POINTER_UP) {
202                     engine.dispatchWallpaperCommand(
203                         WallpaperManager.COMMAND_SECONDARY_TAP,
204                         event.getX(pointerIndex).toInt(),
205                         event.getY(pointerIndex).toInt(),
206                         0,
207                         null
208                     )
209                 }
210             } catch (e: RemoteException) {
211                 Log.e(TAG, "Remote exception of wallpaper connection", e)
212             }
213         }
214     }
215 
216     private fun LiveWallpaperModel.getWallpaperServiceIntent(): Intent {
217         return liveWallpaperData.systemWallpaperInfo.let {
218             Intent(WallpaperService.SERVICE_INTERFACE).setClassName(it.packageName, it.serviceName)
219         }
220     }
221 
222     private suspend fun initEngine(
223         context: Context,
224         wallpaperIntent: Intent,
225         displayMetrics: Point,
226         destinationFlag: Int,
227         whichPreview: WhichPreview,
228         surfaceView: SurfaceView,
229         listener: WallpaperEngineConnection.WallpaperEngineConnectionListener?,
230     ): Pair<ServiceConnection, WallpaperEngineConnection> {
231         // Bind service and get service connection and wallpaper service
232         val (serviceConnection, wallpaperService) = bindWallpaperService(context, wallpaperIntent)
233         val engineConnection = WallpaperEngineConnection(displayMetrics, whichPreview)
234         listener?.let { engineConnection.setListener(it) }
235         // Attach wallpaper connection to service and get wallpaper engine
236         engineConnection.getEngine(wallpaperService, destinationFlag, surfaceView)
237         return Pair(serviceConnection, engineConnection)
238     }
239 
240     private fun WallpaperInfo.getKey(displaySize: Point? = null): String {
241         val keyWithoutSizeInformation = this.packageName.plus(":").plus(this.serviceName)
242         return if (displaySize != null) {
243             keyWithoutSizeInformation.plus(":").plus("${displaySize.x}x${displaySize.y}")
244         } else {
245             keyWithoutSizeInformation
246         }
247     }
248 
249     private suspend fun bindWallpaperService(
250         context: Context,
251         intent: Intent
252     ): Pair<ServiceConnection, IWallpaperService> =
253         suspendCancellableCoroutine {
254             k: CancellableContinuation<Pair<ServiceConnection, IWallpaperService>> ->
255             val serviceConnection =
256                 WallpaperServiceConnection(
257                     object : WallpaperServiceConnection.WallpaperServiceConnectionListener {
258                         override fun onWallpaperServiceConnected(
259                             serviceConnection: ServiceConnection,
260                             wallpaperService: IWallpaperService
261                         ) {
262                             k.resumeWith(Result.success(Pair(serviceConnection, wallpaperService)))
263                         }
264                     }
265                 )
266             val success =
267                 context.bindService(
268                     intent,
269                     serviceConnection,
270                     Context.BIND_AUTO_CREATE or
271                         Context.BIND_IMPORTANT or
272                         Context.BIND_ALLOW_ACTIVITY_STARTS
273                 )
274             if (!success) {
275                 k.resumeWith(Result.failure(Exception("Fail to bind the live wallpaper service.")))
276             }
277         }
278 
279     private suspend fun mirrorAndReparent(
280         engineKey: String,
281         engine: IWallpaperEngine,
282         parentSurface: SurfaceView,
283         displayMetrics: Point,
284         enforceSingleEngine: Boolean,
285     ) {
286         fun logError(e: Exception) {
287             Log.e(WallpaperConnection::class.simpleName, "Fail to reparent wallpaper surface", e)
288         }
289 
290         try {
291             val parentSurfaceControl = parentSurface.surfaceControl
292             val wallpaperSurfaceControl = engine.mirrorSurfaceControl() ?: return
293             // Add surface control reference for later release when disconnected
294             addSurfaceControlReference(engineKey, wallpaperSurfaceControl)
295 
296             val values = getScale(parentSurface, displayMetrics)
297             SurfaceControl.Transaction().use { t ->
298                 t.setMatrix(
299                     wallpaperSurfaceControl,
300                     if (enforceSingleEngine) values[Matrix.MSCALE_Y] else values[Matrix.MSCALE_X],
301                     values[Matrix.MSKEW_X],
302                     values[Matrix.MSKEW_Y],
303                     values[Matrix.MSCALE_Y],
304                 )
305                 t.reparent(wallpaperSurfaceControl, parentSurfaceControl)
306                 t.show(wallpaperSurfaceControl)
307                 t.apply()
308             }
309         } catch (e: RemoteException) {
310             logError(e)
311         } catch (e: NullPointerException) {
312             logError(e)
313         }
314     }
315 
316     private suspend fun addSurfaceControlReference(
317         engineKey: String,
318         wallpaperSurfaceControl: SurfaceControl,
319     ) {
320         val surfaceControls = surfaceControlMap[engineKey]
321         if (surfaceControls == null) {
322             mutex.withLock {
323                 surfaceControlMap[engineKey] =
324                     (surfaceControlMap[engineKey] ?: mutableListOf()).apply {
325                         add(wallpaperSurfaceControl)
326                     }
327             }
328         } else {
329             surfaceControls.add(wallpaperSurfaceControl)
330         }
331     }
332 
333     private fun getScale(parentSurface: SurfaceView, displayMetrics: Point): FloatArray {
334         val metrics = Matrix()
335         val values = FloatArray(9)
336         val surfacePosition = parentSurface.holder.surfaceFrame
337         metrics.postScale(
338             surfacePosition.width().toFloat() / displayMetrics.x,
339             surfacePosition.height().toFloat() / displayMetrics.y
340         )
341         metrics.getValues(values)
342         return values
343     }
344 
345     data class EngineRenderingConfig(
346         val enforceSingleEngine: Boolean,
347         val deviceDisplayType: DeviceDisplayType,
348         val smallDisplaySize: Point,
349         val wallpaperDisplaySize: Point,
350     ) {
351         fun getEngineDisplaySize(): Point {
352             // If we need to enforce single engine, always return the larger screen's preview
353             return if (enforceSingleEngine) {
354                 return wallpaperDisplaySize
355             } else {
356                 getPreviewDisplaySize()
357             }
358         }
359 
360         private fun getPreviewDisplaySize(): Point {
361             return when (deviceDisplayType) {
362                 DeviceDisplayType.SINGLE -> wallpaperDisplaySize
363                 DeviceDisplayType.FOLDED -> smallDisplaySize
364                 DeviceDisplayType.UNFOLDED -> wallpaperDisplaySize
365             }
366         }
367     }
368 
369     fun LiveWallpaperModel.shouldEnforceSingleEngine(): Boolean {
370         return when {
371             creativeWallpaperData != null -> false
372             liveWallpaperData.isEffectWallpaper -> false
373             else -> true // Only fallback to single engine rendering for legacy live wallpapers
374         }
375     }
376 }
377