• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download

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