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