<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