<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