<lambda>null1 package com.android.wallpaper.util.wallpaperconnection
2 
3 import android.app.Flags.liveWallpaperContentHandling
4 import android.app.WallpaperColors
5 import android.app.WallpaperInfo
6 import android.app.wallpaper.WallpaperDescription
7 import android.graphics.Point
8 import android.graphics.Rect
9 import android.graphics.RectF
10 import android.os.Bundle
11 import android.os.IBinder
12 import android.os.ParcelFileDescriptor
13 import android.os.RemoteException
14 import android.service.wallpaper.IWallpaperConnection
15 import android.service.wallpaper.IWallpaperEngine
16 import android.service.wallpaper.IWallpaperService
17 import android.util.Log
18 import android.view.SurfaceView
19 import android.view.WindowManager
20 import com.android.wallpaper.util.WallpaperConnection
21 import com.android.wallpaper.util.WallpaperConnection.WhichPreview
22 import java.lang.reflect.InvocationTargetException
23 import kotlinx.coroutines.CancellableContinuation
24 import kotlinx.coroutines.suspendCancellableCoroutine
25 
26 class WallpaperEngineConnection(
27     private val displayMetrics: Point,
28     private val whichPreview: WhichPreview,
29 ) : IWallpaperConnection.Stub() {
30 
31     var engine: IWallpaperEngine? = null
32     private var engineContinuation: CancellableContinuation<IWallpaperEngine>? = null
33     private var listener: WallpaperEngineConnectionListener? = null
34 
35     suspend fun getEngine(
36         wallpaperService: IWallpaperService,
37         destinationFlag: Int,
38         surfaceView: SurfaceView,
39         description: WallpaperDescription,
40     ): IWallpaperEngine {
41         return engine
42             ?: suspendCancellableCoroutine { k: CancellableContinuation<IWallpaperEngine> ->
43                 engineContinuation = k
44                 attachEngineConnection(
45                     wallpaperEngineConnection = this,
46                     wallpaperService = wallpaperService,
47                     destinationFlag = destinationFlag,
48                     surfaceView = surfaceView,
49                     description = description,
50                 )
51             }
52     }
53 
54     override fun attachEngine(engine: IWallpaperEngine?, displayId: Int) {
55         // Note that the engine in attachEngine callback is different from the one in engineShown
56         // callback, which is called after this attachEngine callback. We use the engine reference
57         // passed in attachEngine callback for WallpaperEngineConnection.
58         this.engine = engine
59         engine?.apply {
60             setVisibility(true)
61             resizePreview(Rect(0, 0, displayMetrics.x, displayMetrics.y))
62             requestWallpaperColors()
63         }
64     }
65 
66     override fun engineShown(engine: IWallpaperEngine?) {
67         if (engine != null) {
68             dispatchWallpaperCommand(engine)
69             // Note that the engines from the attachEngine and engineShown callbacks are different
70             // and we only use the reference of engine from the attachEngine callback.
71             this.engine?.let { engineContinuation?.resumeWith(Result.success(it)) }
72         }
73     }
74 
75     private fun dispatchWallpaperCommand(engine: IWallpaperEngine) {
76         val bundle = Bundle()
77         bundle.putInt(WHICH_PREVIEW, whichPreview.value)
78         try {
79             engine.dispatchWallpaperCommand(COMMAND_PREVIEW_INFO, 0, 0, 0, bundle)
80         } catch (e: RemoteException) {
81             Log.e(TAG, "Error dispatching wallpaper command: $whichPreview")
82         }
83     }
84 
85     override fun onLocalWallpaperColorsChanged(
86         area: RectF?,
87         colors: WallpaperColors?,
88         displayId: Int,
89     ) {
90         // Do nothing intended.
91     }
92 
93     override fun onWallpaperColorsChanged(colors: WallpaperColors?, displayId: Int) {
94         listener?.onWallpaperColorsChanged(colors, displayId)
95     }
96 
97     override fun setWallpaper(name: String?): ParcelFileDescriptor {
98         TODO("Not yet implemented")
99     }
100 
101     fun setListener(listener: WallpaperEngineConnectionListener) {
102         this.listener = listener
103     }
104 
105     fun removeListener() {
106         this.listener = null
107     }
108 
109     companion object {
110         const val TAG = "WallpaperEngineConnection"
111 
112         const val COMMAND_PREVIEW_INFO = "android.wallpaper.previewinfo"
113         const val WHICH_PREVIEW = "which_preview"
114 
115         /*
116          * Tries to call the attach method used in Android 14(U) and earlier, returning true on
117          * success otherwise false.
118          */
119         private fun tryPreUAttach(
120             wallpaperEngineConnection: WallpaperEngineConnection,
121             wallpaperService: IWallpaperService,
122             destinationFlag: Int,
123             surfaceView: SurfaceView,
124         ): Boolean {
125             try {
126                 val method =
127                     wallpaperService.javaClass.getMethod(
128                         "attach",
129                         IWallpaperConnection::class.java,
130                         IBinder::class.java,
131                         Int::class.javaPrimitiveType,
132                         Boolean::class.javaPrimitiveType,
133                         Int::class.javaPrimitiveType,
134                         Int::class.javaPrimitiveType,
135                         Rect::class.java,
136                         Int::class.javaPrimitiveType,
137                         Int::class.javaPrimitiveType,
138                     )
139                 method.invoke(
140                     wallpaperService,
141                     wallpaperEngineConnection,
142                     surfaceView.windowToken,
143                     WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA,
144                     true,
145                     surfaceView.width,
146                     surfaceView.height,
147                     Rect(0, 0, 0, 0),
148                     surfaceView.display.displayId,
149                     destinationFlag,
150                 )
151                 return true
152             } catch (e: Exception) {
153                 when (e) {
154                     is NoSuchMethodException,
155                     is InvocationTargetException,
156                     is IllegalAccessException -> {
157                         if (liveWallpaperContentHandling()) {
158                             Log.w(
159                                 TAG,
160                                 "live wallpaper content handling enabled, but pre-U attach method called",
161                             )
162                         }
163                         return false
164                     }
165 
166                     else -> throw e
167                 }
168             }
169         }
170 
171         /*
172          * Tries to call the attach method used in Android 16(B) and earlier, returning true on
173          * success otherwise false.
174          */
175         private fun tryPreBAttach(
176             wallpaperEngineConnection: WallpaperEngineConnection,
177             wallpaperService: IWallpaperService,
178             destinationFlag: Int,
179             surfaceView: SurfaceView,
180         ): Boolean {
181             try {
182                 val method =
183                     wallpaperService.javaClass.getMethod(
184                         "attach",
185                         IWallpaperConnection::class.java,
186                         IBinder::class.java,
187                         Int::class.javaPrimitiveType,
188                         Boolean::class.javaPrimitiveType,
189                         Int::class.javaPrimitiveType,
190                         Int::class.javaPrimitiveType,
191                         Rect::class.java,
192                         Int::class.javaPrimitiveType,
193                         Int::class.javaPrimitiveType,
194                         WallpaperInfo::class.java,
195                     )
196                 method.invoke(
197                     wallpaperService,
198                     wallpaperEngineConnection,
199                     surfaceView.windowToken,
200                     WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA,
201                     true,
202                     surfaceView.width,
203                     surfaceView.height,
204                     Rect(0, 0, 0, 0),
205                     surfaceView.display.displayId,
206                     destinationFlag,
207                     null,
208                 )
209                 return true
210             } catch (e: Exception) {
211                 when (e) {
212                     is NoSuchMethodException,
213                     is InvocationTargetException,
214                     is IllegalAccessException -> {
215                         if (liveWallpaperContentHandling()) {
216                             Log.w(
217                                 TAG,
218                                 "live wallpaper content handling enabled, but pre-B attach method called",
219                             )
220                         }
221                         return false
222                     }
223 
224                     else -> throw e
225                 }
226             }
227         }
228 
229         /*
230          * This method tries to call historical versions of IWallpaperService#attach since this code
231          * may be running against older versions of Android. We have no control over what versions
232          * of Android third party users of this code will be running.
233          */
234         private fun attachEngineConnection(
235             wallpaperEngineConnection: WallpaperEngineConnection,
236             wallpaperService: IWallpaperService,
237             destinationFlag: Int,
238             surfaceView: SurfaceView,
239             description: WallpaperDescription,
240         ) {
241             if (
242                 tryPreUAttach(
243                     wallpaperEngineConnection,
244                     wallpaperService,
245                     destinationFlag,
246                     surfaceView,
247                 )
248             ) {
249                 return
250             }
251             if (
252                 tryPreBAttach(
253                     wallpaperEngineConnection,
254                     wallpaperService,
255                     destinationFlag,
256                     surfaceView,
257                 )
258             ) {
259                 return
260             }
261 
262             wallpaperService.attach(
263                 wallpaperEngineConnection,
264                 surfaceView.windowToken,
265                 WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA,
266                 true,
267                 surfaceView.width,
268                 surfaceView.height,
269                 Rect(0, 0, 0, 0),
270                 surfaceView.display.displayId,
271                 destinationFlag,
272                 null,
273                 description,
274             )
275         }
276     }
277 
278     /** Interface to be notified of connect/disconnect events from [WallpaperConnection] */
279     interface WallpaperEngineConnectionListener {
280         /** Called after the wallpaper color is available or updated. */
281         fun onWallpaperColorsChanged(colors: WallpaperColors?, displayId: Int) {}
282     }
283 }
284