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.content.Context
20 import android.graphics.Point
21 import android.view.LayoutInflater
22 import android.view.SurfaceHolder
23 import android.view.SurfaceView
24 import androidx.lifecycle.Lifecycle
25 import androidx.lifecycle.LifecycleOwner
26 import androidx.lifecycle.lifecycleScope
27 import androidx.lifecycle.repeatOnLifecycle
28 import com.android.wallpaper.R
29 import com.android.wallpaper.model.wallpaper.DeviceDisplayType
30 import com.android.wallpaper.picker.customization.shared.model.WallpaperColorsModel
31 import com.android.wallpaper.picker.data.WallpaperModel
32 import com.android.wallpaper.picker.preview.ui.util.SurfaceViewUtil
33 import com.android.wallpaper.picker.preview.ui.util.SurfaceViewUtil.attachView
34 import com.android.wallpaper.picker.preview.ui.viewmodel.WallpaperPreviewViewModel
35 import com.android.wallpaper.util.wallpaperconnection.WallpaperConnectionUtils
36 import com.android.wallpaper.util.wallpaperconnection.WallpaperConnectionUtils.shouldEnforceSingleEngine
37 import com.android.wallpaper.util.wallpaperconnection.WallpaperEngineConnection.WallpaperEngineConnectionListener
38 import kotlinx.coroutines.Job
39 import kotlinx.coroutines.launch
40 
41 /**
42  * Bind the [SurfaceView] with [WallpaperPreviewViewModel] for rendering static or live wallpaper
43  * preview, with regard to its underlying [WallpaperModel].
44  */
45 object SmallWallpaperPreviewBinder {
46     /**
47      * @param onFullResImageViewCreated This callback is only used when the wallpaperModel is a
48      *   [WallpaperModel.StaticWallpaperModel]. [FullWallpaperPreviewBinder] needs the callback to
49      *   further delegate the touch events and set the state change listener.
50      */
51     fun bind(
52         surface: SurfaceView,
53         viewModel: WallpaperPreviewViewModel,
54         displaySize: Point,
55         applicationContext: Context,
56         viewLifecycleOwner: LifecycleOwner,
57         deviceDisplayType: DeviceDisplayType,
58         isFirstBinding: Boolean,
59     ) {
60         var surfaceCallback: SurfaceViewUtil.SurfaceCallback? = null
61         viewLifecycleOwner.lifecycleScope.launch {
62             viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
63                 surfaceCallback =
64                     bindSurface(
65                         applicationContext = applicationContext,
66                         surface = surface,
67                         viewModel = viewModel,
68                         deviceDisplayType = deviceDisplayType,
69                         displaySize = displaySize,
70                         lifecycleOwner = viewLifecycleOwner,
71                         isFirstBinding
72                     )
73                 surface.setZOrderMediaOverlay(true)
74                 surfaceCallback?.let { surface.holder.addCallback(it) }
75             }
76             // When OnDestroy, release the surface
77             surfaceCallback?.let {
78                 surface.holder.removeCallback(it)
79                 surfaceCallback = null
80             }
81         }
82     }
83 
84     /**
85      * Create a surface callback that binds the surface when surface created. Note that we return
86      * the surface callback reference so that we can remove the callback from the surface when the
87      * screen is destroyed.
88      */
89     private fun bindSurface(
90         applicationContext: Context,
91         surface: SurfaceView,
92         viewModel: WallpaperPreviewViewModel,
93         deviceDisplayType: DeviceDisplayType,
94         displaySize: Point,
95         lifecycleOwner: LifecycleOwner,
96         isFirstBinding: Boolean,
97     ): SurfaceViewUtil.SurfaceCallback {
98 
99         return object : SurfaceViewUtil.SurfaceCallback {
100 
101             var job: Job? = null
102             var loadingAnimationBinding: PreviewEffectsLoadingBinder.Binding? = null
103 
104             override fun surfaceCreated(holder: SurfaceHolder) {
105                 job =
106                     lifecycleOwner.lifecycleScope.launch {
107                         viewModel.smallWallpaper.collect { (wallpaper, whichPreview) ->
108                             if (wallpaper is WallpaperModel.LiveWallpaperModel) {
109                                 WallpaperConnectionUtils.connect(
110                                     applicationContext,
111                                     wallpaper,
112                                     whichPreview,
113                                     viewModel.getWallpaperPreviewSource().toFlag(),
114                                     surface,
115                                     WallpaperConnectionUtils.EngineRenderingConfig(
116                                         wallpaper.shouldEnforceSingleEngine(),
117                                         deviceDisplayType = deviceDisplayType,
118                                         viewModel.smallerDisplaySize,
119                                         viewModel.wallpaperDisplaySize.value,
120                                     ),
121                                     isFirstBinding,
122                                     object : WallpaperEngineConnectionListener {
123                                         override fun onWallpaperColorsChanged(
124                                             colors: WallpaperColors?,
125                                             displayId: Int
126                                         ) {
127                                             viewModel.setWallpaperConnectionColors(
128                                                 WallpaperColorsModel.Loaded(colors)
129                                             )
130                                         }
131                                     },
132                                 )
133                             } else if (wallpaper is WallpaperModel.StaticWallpaperModel) {
134                                 val staticPreviewView =
135                                     LayoutInflater.from(applicationContext)
136                                         .inflate(R.layout.fullscreen_wallpaper_preview, null)
137                                 surface.attachView(staticPreviewView)
138                                 // Bind static wallpaper
139                                 StaticWallpaperPreviewBinder.bind(
140                                     lowResImageView =
141                                         staticPreviewView.requireViewById(R.id.low_res_image),
142                                     fullResImageView =
143                                         staticPreviewView.requireViewById(R.id.full_res_image),
144                                     viewModel = viewModel.staticWallpaperPreviewViewModel,
145                                     displaySize = displaySize,
146                                     parentCoroutineScope = this,
147                                 )
148                                 // This is to possibly shut down all live wallpaper services
149                                 // if they exist; otherwise static wallpaper can not show up.
150                                 WallpaperConnectionUtils.disconnectAllServices(applicationContext)
151 
152                                 loadingAnimationBinding =
153                                     PreviewEffectsLoadingBinder.bind(
154                                         view =
155                                             staticPreviewView.requireViewById(R.id.full_res_image),
156                                         viewModel = viewModel,
157                                         viewLifecycleOwner = lifecycleOwner,
158                                     )
159                             }
160                         }
161                     }
162             }
163 
164             override fun surfaceDestroyed(holder: SurfaceHolder) {
165                 job?.cancel()
166                 job = null
167                 loadingAnimationBinding?.destroy()
168                 loadingAnimationBinding = null
169                 // Note that we disconnect wallpaper connection for live wallpapers in
170                 // WallpaperPreviewActivity's onDestroy().
171                 // This is to reduce multiple times of connecting and disconnecting live
172                 // wallpaper services, when going back and forth small and full preview.
173             }
174         }
175     }
176 }
177