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