<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