1 /*
<lambda>null2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.wallpaper.picker.preview.ui.binder
17 
18 import android.app.WallpaperColors
19 import android.os.Bundle
20 import android.os.Message
21 import android.util.Log
22 import android.view.SurfaceHolder
23 import android.view.SurfaceView
24 import androidx.core.os.bundleOf
25 import androidx.lifecycle.Lifecycle
26 import androidx.lifecycle.LifecycleOwner
27 import androidx.lifecycle.lifecycleScope
28 import androidx.lifecycle.repeatOnLifecycle
29 import com.android.wallpaper.picker.customization.shared.model.WallpaperColorsModel
30 import com.android.wallpaper.picker.preview.ui.util.SurfaceViewUtil
31 import com.android.wallpaper.picker.preview.ui.viewmodel.WallpaperPreviewViewModel
32 import com.android.wallpaper.picker.preview.ui.viewmodel.WorkspacePreviewConfigViewModel
33 import com.android.wallpaper.util.PreviewUtils
34 import com.android.wallpaper.util.SurfaceViewUtils
35 import kotlin.coroutines.resume
36 import kotlinx.coroutines.DisposableHandle
37 import kotlinx.coroutines.Job
38 import kotlinx.coroutines.flow.combine
39 import kotlinx.coroutines.launch
40 import kotlinx.coroutines.suspendCancellableCoroutine
41 
42 object WorkspacePreviewBinder {
43     fun bind(
44         surface: SurfaceView,
45         config: WorkspacePreviewConfigViewModel,
46         viewModel: WallpaperPreviewViewModel,
47         lifecycleOwner: LifecycleOwner,
48     ) {
49         var surfaceCallback: SurfaceViewUtil.SurfaceCallback? = null
50         lifecycleOwner.lifecycleScope.launch {
51             lifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
52                 surfaceCallback =
53                     bindSurface(
54                         surface = surface,
55                         viewModel = viewModel,
56                         config = config,
57                         lifecycleOwner = lifecycleOwner,
58                     )
59                 surface.setZOrderMediaOverlay(true)
60                 surface.holder.addCallback(surfaceCallback)
61             }
62             // When OnDestroy, release the surface
63             surfaceCallback?.let {
64                 surface.holder.removeCallback(it)
65                 surfaceCallback = null
66             }
67         }
68     }
69 
70     /**
71      * Create a surface callback that binds the surface when surface created. Note that we return
72      * the surface callback reference so that we can remove the callback from the surface when the
73      * screen is destroyed.
74      */
75     private fun bindSurface(
76         surface: SurfaceView,
77         viewModel: WallpaperPreviewViewModel,
78         config: WorkspacePreviewConfigViewModel,
79         lifecycleOwner: LifecycleOwner,
80     ): SurfaceViewUtil.SurfaceCallback {
81         return object : SurfaceViewUtil.SurfaceCallback {
82 
83             var job: Job? = null
84             var previewDisposableHandle: DisposableHandle? = null
85 
86             override fun surfaceCreated(holder: SurfaceHolder) {
87                 job =
88                     lifecycleOwner.lifecycleScope.launch {
89                         viewModel.wallpaperColorsModel.collect {
90                             if (it is WallpaperColorsModel.Loaded) {
91                                 val workspaceCallback =
92                                     renderWorkspacePreview(
93                                         surface = surface,
94                                         previewUtils = config.previewUtils,
95                                         displayId =
96                                             viewModel.getDisplayId(config.deviceDisplayType),
97                                         wallpaperColors = it.colors
98                                     )
99                                 // Dispose the previous preview on the renderer side.
100                                 previewDisposableHandle?.dispose()
101                                 previewDisposableHandle = DisposableHandle {
102                                     config.previewUtils.cleanUp(workspaceCallback)
103                                 }
104                             }
105                         }
106                     }
107             }
108 
109             override fun surfaceDestroyed(holder: SurfaceHolder) {
110                 job?.cancel()
111                 job = null
112                 previewDisposableHandle?.dispose()
113                 previewDisposableHandle = null
114             }
115         }
116     }
117 
118     /**
119      * Binds the workspace preview in the full screen, where we need to listen to the changes of the
120      * [WorkspacePreviewConfigViewModel] according to which small preview the user clicks on.
121      */
122     fun bindFullWorkspacePreview(
123         surface: SurfaceView,
124         viewModel: WallpaperPreviewViewModel,
125         lifecycleOwner: LifecycleOwner,
126     ) {
127         var surfaceCallback: SurfaceViewUtil.SurfaceCallback? = null
128         lifecycleOwner.lifecycleScope.launch {
129             lifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
130                 surfaceCallback =
131                     bindFullSurface(
132                         surface = surface,
133                         viewModel = viewModel,
134                         lifecycleOwner = lifecycleOwner,
135                     )
136                 surface.setZOrderMediaOverlay(true)
137                 surface.holder.addCallback(surfaceCallback)
138             }
139             // When OnDestroy, release the surface
140             surfaceCallback?.let {
141                 surface.holder.removeCallback(it)
142                 surfaceCallback = null
143             }
144         }
145     }
146 
147     /**
148      * Create a surface callback that binds the surface when surface created. Note that we return
149      * the surface callback reference so that we can remove the callback from the surface when the
150      * screen is destroyed.
151      */
152     private fun bindFullSurface(
153         surface: SurfaceView,
154         viewModel: WallpaperPreviewViewModel,
155         lifecycleOwner: LifecycleOwner,
156     ): SurfaceViewUtil.SurfaceCallback {
157         return object : SurfaceViewUtil.SurfaceCallback {
158 
159             var job: Job? = null
160             var previewDisposableHandle: DisposableHandle? = null
161 
162             override fun surfaceCreated(holder: SurfaceHolder) {
163                 job =
164                     lifecycleOwner.lifecycleScope.launch {
165                         combine(
166                                 viewModel.fullWorkspacePreviewConfigViewModel,
167                                 viewModel.wallpaperColorsModel
168                             ) { config, colorsModel ->
169                                 config to colorsModel
170                             }
171                             .collect { (config, colorsModel) ->
172                                 if (colorsModel is WallpaperColorsModel.Loaded) {
173                                     val workspaceCallback =
174                                         renderWorkspacePreview(
175                                             surface = surface,
176                                             previewUtils = config.previewUtils,
177                                             displayId =
178                                                 viewModel.getDisplayId(config.deviceDisplayType),
179                                             wallpaperColors = colorsModel.colors
180                                         )
181                                     // Dispose the previous preview on the renderer side.
182                                     previewDisposableHandle?.dispose()
183                                     previewDisposableHandle = DisposableHandle {
184                                         config.previewUtils.cleanUp(workspaceCallback)
185                                     }
186                                 }
187                             }
188                     }
189             }
190 
191             override fun surfaceDestroyed(holder: SurfaceHolder) {
192                 job?.cancel()
193                 job = null
194                 previewDisposableHandle?.dispose()
195                 previewDisposableHandle = null
196             }
197         }
198     }
199 
200     private suspend fun renderWorkspacePreview(
201         surface: SurfaceView,
202         previewUtils: PreviewUtils,
203         displayId: Int,
204         wallpaperColors: WallpaperColors? = null,
205     ): Message? {
206         var workspaceCallback: Message? = null
207         if (previewUtils.supportsPreview()) {
208             val extras = bundleOf(Pair(SurfaceViewUtils.KEY_DISPLAY_ID, displayId))
209             wallpaperColors?.let {
210                 extras.putParcelable(SurfaceViewUtils.KEY_WALLPAPER_COLORS, wallpaperColors)
211             }
212             val request =
213                 SurfaceViewUtils.createSurfaceViewRequest(
214                     surface,
215                     extras,
216                 )
217             workspaceCallback = suspendCancellableCoroutine { continuation ->
218                 previewUtils.renderPreview(
219                     request,
220                     object : PreviewUtils.WorkspacePreviewCallback {
221                         override fun onPreviewRendered(resultBundle: Bundle?) {
222                             if (resultBundle != null) {
223                                 SurfaceViewUtils.getSurfacePackage(resultBundle).apply {
224                                     if (this != null) {
225                                         surface.setChildSurfacePackage(this)
226                                     } else {
227                                         Log.w(
228                                             TAG,
229                                             "Result bundle from rendering preview does not contain " +
230                                                 "a child surface package."
231                                         )
232                                     }
233                                 }
234                                 continuation.resume(SurfaceViewUtils.getCallback(resultBundle))
235                             } else {
236                                 Log.w(TAG, "Result bundle from rendering preview is null.")
237                                 continuation.resume(null)
238                             }
239                         }
240                     }
241                 )
242             }
243         }
244         return workspaceCallback
245     }
246 
247     const val TAG = "WorkspacePreviewBinder"
248 }
249