1 /*
<lambda>null2  * Copyright (C) 2022 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 
18 package com.android.wallpaper.picker.customization.ui.section
19 
20 import android.annotation.SuppressLint
21 import android.app.Activity
22 import android.app.WallpaperManager
23 import android.content.Context
24 import android.os.Bundle
25 import android.view.Gravity
26 import android.view.LayoutInflater
27 import android.widget.FrameLayout
28 import androidx.cardview.widget.CardView
29 import androidx.lifecycle.LifecycleOwner
30 import androidx.lifecycle.lifecycleScope
31 import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants
32 import com.android.wallpaper.R
33 import com.android.wallpaper.model.CustomizationSectionController
34 import com.android.wallpaper.model.Screen
35 import com.android.wallpaper.model.WallpaperInfo
36 import com.android.wallpaper.model.WallpaperPreviewNavigator
37 import com.android.wallpaper.module.CurrentWallpaperInfoFactory
38 import com.android.wallpaper.picker.FixedWidthDisplayRatioFrameLayout
39 import com.android.wallpaper.picker.customization.data.repository.WallpaperColorsRepository
40 import com.android.wallpaper.picker.customization.domain.interactor.WallpaperInteractor
41 import com.android.wallpaper.picker.customization.ui.binder.ScreenPreviewBinder
42 import com.android.wallpaper.picker.customization.ui.viewmodel.CustomizationPickerViewModel
43 import com.android.wallpaper.picker.customization.ui.viewmodel.ScreenPreviewViewModel
44 import com.android.wallpaper.util.DisplayUtils
45 import com.android.wallpaper.util.PreviewUtils
46 import kotlinx.coroutines.Dispatchers
47 import kotlinx.coroutines.ExperimentalCoroutinesApi
48 import kotlinx.coroutines.launch
49 import kotlinx.coroutines.suspendCancellableCoroutine
50 import kotlinx.coroutines.withContext
51 
52 /** Controls the screen preview section. */
53 @OptIn(ExperimentalCoroutinesApi::class)
54 open class ScreenPreviewSectionController(
55     private val activity: Activity,
56     private val lifecycleOwner: LifecycleOwner,
57     private val screen: Screen,
58     private val wallpaperInfoFactory: CurrentWallpaperInfoFactory,
59     private val colorViewModel: WallpaperColorsRepository,
60     private val displayUtils: DisplayUtils,
61     private val wallpaperPreviewNavigator: WallpaperPreviewNavigator,
62     private val wallpaperInteractor: WallpaperInteractor,
63     private val wallpaperManager: WallpaperManager,
64     private val isTwoPaneAndSmallWidth: Boolean,
65     private val customizationPickerViewModel: CustomizationPickerViewModel,
66 ) : CustomizationSectionController<ScreenPreviewView> {
67 
68     protected val isOnLockScreen: Boolean = screen == Screen.LOCK_SCREEN
69 
70     protected var previewViewBinding: ScreenPreviewBinder.Binding? = null
71 
72     /** Override to hide the lock screen clock preview. */
73     open val hideLockScreenClockPreview = false
74 
75     override fun shouldRetainInstanceWhenSwitchingTabs(): Boolean {
76         return true
77     }
78 
79     override fun isAvailable(context: Context): Boolean {
80         // Assumption is that, if this section controller is included, we are using the revamped UI
81         // so it should always be shown.
82         return true
83     }
84 
85     override fun createView(context: Context): ScreenPreviewView {
86         return createView(context, CustomizationSectionController.ViewCreationParams())
87     }
88 
89     @SuppressLint("InflateParams")
90     override fun createView(
91         context: Context,
92         params: CustomizationSectionController.ViewCreationParams,
93     ): ScreenPreviewView {
94         val view =
95             LayoutInflater.from(context)
96                 .inflate(
97                     R.layout.screen_preview_section,
98                     /* parent= */ null,
99                 ) as ScreenPreviewView
100 
101         if (isTwoPaneAndSmallWidth) {
102             val previewHost =
103                 view.requireViewById<FixedWidthDisplayRatioFrameLayout>(R.id.preview_host)
104             val layoutParams =
105                 FrameLayout.LayoutParams(
106                     context.resources.getDimensionPixelSize(
107                         R.dimen.screen_preview_width_for_2_pane_small_width
108                     ),
109                     FrameLayout.LayoutParams.WRAP_CONTENT,
110                 )
111             layoutParams.gravity = Gravity.CENTER
112             previewHost.layoutParams = layoutParams
113         }
114 
115         val previewView: CardView = view.requireViewById(R.id.preview)
116 
117         bindScreenPreview(previewView, context, !params.isWallpaperVisibilityControlledByTab)
118         return view
119     }
120 
121     protected open fun bindScreenPreview(
122         previewView: CardView,
123         context: Context,
124         isWallpaperAlwaysVisible: Boolean,
125     ) {
126         previewViewBinding?.destroy()
127         previewViewBinding =
128             ScreenPreviewBinder.bind(
129                 activity = activity,
130                 previewView = previewView,
131                 viewModel = createScreenPreviewViewModel(context),
132                 lifecycleOwner = lifecycleOwner,
133                 offsetToStart = displayUtils.isSingleDisplayOrUnfoldedHorizontalHinge(activity),
134                 onWallpaperPreviewDirty = { activity.recreate() },
135                 animationStateViewModel = customizationPickerViewModel,
136                 isWallpaperAlwaysVisible = isWallpaperAlwaysVisible,
137                 onClick = {
138                     lifecycleOwner.lifecycleScope.launch {
139                         getWallpaperInfo(context)?.let { wallpaperInfo ->
140                             wallpaperPreviewNavigator.showViewOnlyPreview(
141                                 wallpaperInfo,
142                                 /* isAssetIdPresent= */ false,
143                             )
144                         }
145                     }
146                 },
147             )
148     }
149 
150     protected open fun createScreenPreviewViewModel(context: Context): ScreenPreviewViewModel {
151         return ScreenPreviewViewModel(
152             previewUtils =
153                 if (isOnLockScreen) {
154                     PreviewUtils(
155                         context = context,
156                         authority =
157                             context.getString(
158                                 R.string.lock_screen_preview_provider_authority,
159                             ),
160                     )
161                 } else {
162                     PreviewUtils(
163                         context = context,
164                         authorityMetadataKey =
165                             context.getString(
166                                 R.string.grid_control_metadata_name,
167                             ),
168                     )
169                 },
170             wallpaperInfoProvider = { forceReload ->
171                 suspendCancellableCoroutine { continuation ->
172                     wallpaperInfoFactory.createCurrentWallpaperInfos(
173                         context,
174                         forceReload,
175                     ) { homeWallpaper, lockWallpaper, _ ->
176                         val wallpaper =
177                             if (isOnLockScreen) {
178                                 lockWallpaper ?: homeWallpaper
179                             } else {
180                                 homeWallpaper ?: lockWallpaper
181                             }
182                         loadInitialColors(
183                             context = context,
184                             screen = screen,
185                         )
186                         continuation.resume(wallpaper, null)
187                     }
188                 }
189             },
190             onWallpaperColorChanged = { colors ->
191                 if (isOnLockScreen) {
192                     colorViewModel.setLockWallpaperColors(colors)
193                 } else {
194                     colorViewModel.setHomeWallpaperColors(colors)
195                 }
196             },
197             initialExtrasProvider = { getInitialExtras(isOnLockScreen) },
198             wallpaperInteractor = wallpaperInteractor,
199             screen = screen,
200         )
201     }
202 
203     protected fun loadInitialColors(
204         context: Context,
205         screen: Screen,
206     ) {
207         lifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
208             val colors =
209                 wallpaperManager.getWallpaperColors(
210                     if (screen == Screen.LOCK_SCREEN) {
211                         WallpaperManager.FLAG_LOCK
212                     } else {
213                         WallpaperManager.FLAG_SYSTEM
214                     }
215                 )
216             withContext(Dispatchers.Main) {
217                 if (colors != null) {
218                     if (screen == Screen.LOCK_SCREEN) {
219                         colorViewModel.setLockWallpaperColors(colors)
220                     } else {
221                         colorViewModel.setHomeWallpaperColors(colors)
222                     }
223                 }
224             }
225         }
226     }
227 
228     private suspend fun getWallpaperInfo(context: Context): WallpaperInfo? {
229         return suspendCancellableCoroutine { continuation ->
230             wallpaperInfoFactory.createCurrentWallpaperInfos(
231                 context,
232                 true,
233             ) { homeWallpaper, lockWallpaper, _ ->
234                 continuation.resume(
235                     if (isOnLockScreen) {
236                         lockWallpaper ?: homeWallpaper
237                     } else {
238                         homeWallpaper
239                     },
240                     null
241                 )
242             }
243         }
244     }
245 
246     protected fun getInitialExtras(isOnLockScreen: Boolean): Bundle? {
247         return if (isOnLockScreen) {
248             Bundle().apply {
249                 // Hide the clock from the system UI rendered preview so we can
250                 // place the carousel on top of it.
251                 putBoolean(
252                     ClockPreviewConstants.KEY_HIDE_CLOCK,
253                     hideLockScreenClockPreview,
254                 )
255             }
256         } else {
257             null
258         }
259     }
260 }
261