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