1 /*
<lambda>null2  * Copyright (C) 2024 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 
17 package com.android.wallpaper.picker.common.preview.ui.binder
18 
19 import android.app.WallpaperColors
20 import android.content.Context
21 import android.graphics.Point
22 import android.view.LayoutInflater
23 import android.view.SurfaceHolder
24 import android.view.SurfaceView
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.R
30 import com.android.wallpaper.model.Screen
31 import com.android.wallpaper.model.wallpaper.DeviceDisplayType
32 import com.android.wallpaper.picker.common.preview.ui.viewmodel.BasePreviewViewModel
33 import com.android.wallpaper.picker.customization.shared.model.WallpaperColorsModel
34 import com.android.wallpaper.picker.data.WallpaperModel
35 import com.android.wallpaper.util.SurfaceViewUtils
36 import com.android.wallpaper.util.SurfaceViewUtils.attachView
37 import com.android.wallpaper.util.wallpaperconnection.WallpaperConnectionUtils
38 import com.android.wallpaper.util.wallpaperconnection.WallpaperConnectionUtils.Companion.shouldEnforceSingleEngine
39 import com.android.wallpaper.util.wallpaperconnection.WallpaperEngineConnection
40 import kotlinx.coroutines.CompletableDeferred
41 import kotlinx.coroutines.CoroutineScope
42 import kotlinx.coroutines.Job
43 import kotlinx.coroutines.launch
44 
45 /**
46  * Bind the [SurfaceView] with [BasePreviewViewModel] for rendering static or live wallpaper
47  * preview, with regard to its underlying [WallpaperModel].
48  */
49 // Based on SmallWallpaperPreviewBinder, mostly unchanged, except with LoadingAnimationBinding
50 // removed. Also we enable a screen to be defined during binding rather than reading from
51 // viewModel.isViewAsHome. In addition the call to WallpaperConnectionUtils.disconnectAllServices at
52 // the end of the static wallpaper binding is removed since it interferes with previewing one live
53 // and one static wallpaper side by side, but should be re-visited when integrating into
54 // WallpaperPreviewActivity for the cinematic wallpaper toggle case.
55 object WallpaperPreviewBinder {
56     fun bind(
57         applicationContext: Context,
58         surfaceView: SurfaceView,
59         viewModel: BasePreviewViewModel,
60         screen: Screen,
61         displaySize: Point,
62         deviceDisplayType: DeviceDisplayType,
63         mainScope: CoroutineScope,
64         viewLifecycleOwner: LifecycleOwner,
65         wallpaperConnectionUtils: WallpaperConnectionUtils,
66         isFirstBindingDeferred: CompletableDeferred<Boolean>,
67     ) {
68         var surfaceCallback: SurfaceViewUtils.SurfaceCallback? = null
69         viewLifecycleOwner.lifecycleScope.launch {
70             viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
71                 surfaceCallback =
72                     bindSurface(
73                         applicationContext = applicationContext,
74                         surfaceView = surfaceView,
75                         viewModel = viewModel,
76                         screen = screen,
77                         deviceDisplayType = deviceDisplayType,
78                         displaySize = displaySize,
79                         mainScope = mainScope,
80                         lifecycleOwner = viewLifecycleOwner,
81                         wallpaperConnectionUtils = wallpaperConnectionUtils,
82                         isFirstBindingDeferred = isFirstBindingDeferred,
83                     )
84                 surfaceView.setZOrderMediaOverlay(true)
85                 surfaceCallback?.let { surfaceView.holder.addCallback(it) }
86             }
87             // When OnDestroy, release the surface
88             surfaceCallback?.let {
89                 surfaceView.holder.removeCallback(it)
90                 surfaceCallback = null
91             }
92         }
93     }
94 
95     /**
96      * Create a surface callback that binds the surface when surface created. Note that we return
97      * the surface callback reference so that we can remove the callback from the surface when the
98      * screen is destroyed.
99      */
100     private fun bindSurface(
101         applicationContext: Context,
102         surfaceView: SurfaceView,
103         viewModel: BasePreviewViewModel,
104         screen: Screen,
105         deviceDisplayType: DeviceDisplayType,
106         displaySize: Point,
107         mainScope: CoroutineScope,
108         lifecycleOwner: LifecycleOwner,
109         wallpaperConnectionUtils: WallpaperConnectionUtils,
110         isFirstBindingDeferred: CompletableDeferred<Boolean>,
111     ): SurfaceViewUtils.SurfaceCallback {
112 
113         return object : SurfaceViewUtils.SurfaceCallback {
114 
115             var job: Job? = null
116 
117             override fun surfaceCreated(holder: SurfaceHolder) {
118                 job =
119                     // Ensure the wallpaper connection is connected / disconnected in [mainScope].
120                     mainScope.launch {
121                         viewModel.wallpapersAndWhichPreview.collect { (wallpapers, whichPreview) ->
122                             val wallpaper =
123                                 if (screen == Screen.HOME_SCREEN) wallpapers.homeWallpaper
124                                 else wallpapers.lockWallpaper ?: wallpapers.homeWallpaper
125                             if (wallpaper is WallpaperModel.LiveWallpaperModel) {
126                                 val engineRenderingConfig =
127                                     WallpaperConnectionUtils.Companion.EngineRenderingConfig(
128                                         wallpaper.shouldEnforceSingleEngine(),
129                                         deviceDisplayType = deviceDisplayType,
130                                         viewModel.smallerDisplaySize,
131                                         viewModel.wallpaperDisplaySize.value,
132                                     )
133                                 val listener =
134                                     object :
135                                         WallpaperEngineConnection.WallpaperEngineConnectionListener {
136                                         override fun onWallpaperColorsChanged(
137                                             colors: WallpaperColors?,
138                                             displayId: Int
139                                         ) {
140                                             viewModel.setWallpaperConnectionColors(
141                                                 WallpaperColorsModel.Loaded(colors)
142                                             )
143                                         }
144                                     }
145                                 wallpaperConnectionUtils.connect(
146                                     applicationContext,
147                                     wallpaper,
148                                     whichPreview,
149                                     screen.toFlag(),
150                                     surfaceView,
151                                     engineRenderingConfig,
152                                     isFirstBindingDeferred,
153                                     listener,
154                                 )
155                             } else if (wallpaper is WallpaperModel.StaticWallpaperModel) {
156                                 val staticPreviewView =
157                                     LayoutInflater.from(applicationContext)
158                                         .inflate(R.layout.fullscreen_wallpaper_preview, null)
159                                 // surfaceView.width and surfaceFrame.width here can be different,
160                                 // one represents the size of the view and the other represents the
161                                 // size of the surface. When setting a view to the surface host,
162                                 // we want to set it based on the surface's size not the view's size
163                                 val surfacePosition = surfaceView.holder.surfaceFrame
164                                 surfaceView.attachView(
165                                     staticPreviewView,
166                                     surfacePosition.width(),
167                                     surfacePosition.height()
168                                 )
169                                 // Bind static wallpaper
170                                 StaticPreviewBinder.bind(
171                                     lowResImageView =
172                                         staticPreviewView.requireViewById(R.id.low_res_image),
173                                     fullResImageView =
174                                         staticPreviewView.requireViewById(R.id.full_res_image),
175                                     viewModel =
176                                         if (
177                                             screen == Screen.LOCK_SCREEN &&
178                                                 wallpapers.lockWallpaper != null
179                                         ) {
180                                             // Only if home and lock screen are different, use lock
181                                             // view model, otherwise, re-use home view model for
182                                             // lock.
183                                             viewModel.staticLockWallpaperPreviewViewModel
184                                         } else {
185                                             viewModel.staticHomeWallpaperPreviewViewModel
186                                         },
187                                     displaySize = displaySize,
188                                     parentCoroutineScope = this,
189                                 )
190                                 // TODO (b/348462236): investigate cinematic wallpaper toggle case
191                                 // Previously all live wallpaper services are shut down to enable
192                                 // static photos wallpaper to show up when cinematic effect is
193                                 // toggled off, using WallpaperConnectionUtils.disconnectAllServices
194                                 // This cannot work when previewing current wallpaper, and one
195                                 // wallpaper is live and the other is static--it causes live
196                                 // wallpaper to black screen occasionally.
197                             }
198                         }
199                     }
200             }
201 
202             override fun surfaceDestroyed(holder: SurfaceHolder) {
203                 job?.cancel()
204                 job = null
205                 // Note that we disconnect wallpaper connection for live wallpapers in
206                 // WallpaperPreviewActivity's onDestroy().
207                 // This is to reduce multiple times of connecting and disconnecting live
208                 // wallpaper services, when going back and forth small and full preview.
209             }
210         }
211     }
212 }
213