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.WallpaperColorsViewModel 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.module.CustomizationSections 39 import com.android.wallpaper.picker.FixedWidthDisplayRatioFrameLayout 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: CustomizationSections.Screen, 58 private val wallpaperInfoFactory: CurrentWallpaperInfoFactory, 59 private val colorViewModel: WallpaperColorsViewModel, 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 == CustomizationSections.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 view 116 .requireViewById<ScreenPreviewClickView>(R.id.screen_preview_click_view) 117 .setOnPreviewClickedListener { 118 lifecycleOwner.lifecycleScope.launch { 119 getWallpaperInfo()?.let { wallpaperInfo -> 120 wallpaperPreviewNavigator.showViewOnlyPreview( 121 wallpaperInfo, 122 /* isAssetIdPresent= */ false, 123 ) 124 } 125 } 126 } 127 128 val previewView: CardView = view.requireViewById(R.id.preview) 129 130 bindScreenPreview(previewView, context, !params.isWallpaperVisibilityControlledByTab) 131 return view 132 } 133 134 protected open fun bindScreenPreview( 135 previewView: CardView, 136 context: Context, 137 isWallpaperAlwaysVisible: Boolean, 138 ) { 139 previewViewBinding?.destroy() 140 previewViewBinding = 141 ScreenPreviewBinder.bind( 142 activity = activity, 143 previewView = previewView, 144 viewModel = createScreenPreviewViewModel(context), 145 lifecycleOwner = lifecycleOwner, 146 offsetToStart = displayUtils.isSingleDisplayOrUnfoldedHorizontalHinge(activity), 147 onWallpaperPreviewDirty = { activity.recreate() }, 148 animationStateViewModel = customizationPickerViewModel, 149 onWorkspacePreviewDirty = { 150 bindScreenPreview(previewView, context, isWallpaperAlwaysVisible) 151 }, 152 isWallpaperAlwaysVisible = isWallpaperAlwaysVisible, 153 ) 154 } 155 156 protected open fun createScreenPreviewViewModel(context: Context): ScreenPreviewViewModel { 157 return ScreenPreviewViewModel( 158 previewUtils = 159 if (isOnLockScreen) { 160 PreviewUtils( 161 context = context, 162 authority = 163 context.getString( 164 R.string.lock_screen_preview_provider_authority, 165 ), 166 ) 167 } else { 168 PreviewUtils( 169 context = context, 170 authorityMetadataKey = 171 context.getString( 172 R.string.grid_control_metadata_name, 173 ), 174 ) 175 }, 176 wallpaperInfoProvider = { forceReload -> 177 suspendCancellableCoroutine { continuation -> 178 wallpaperInfoFactory.createCurrentWallpaperInfos( 179 { homeWallpaper, lockWallpaper, _ -> 180 val wallpaper = 181 if (isOnLockScreen) { 182 lockWallpaper ?: homeWallpaper 183 } else { 184 homeWallpaper ?: lockWallpaper 185 } 186 loadInitialColors( 187 context = context, 188 screen = screen, 189 ) 190 continuation.resume(wallpaper, null) 191 }, 192 forceReload, 193 ) 194 } 195 }, 196 onWallpaperColorChanged = { colors -> 197 if (isOnLockScreen) { 198 colorViewModel.setLockWallpaperColors(colors) 199 } else { 200 colorViewModel.setHomeWallpaperColors(colors) 201 } 202 }, 203 initialExtrasProvider = { getInitialExtras(isOnLockScreen) }, 204 wallpaperInteractor = wallpaperInteractor, 205 screen = screen, 206 ) 207 } 208 209 protected fun loadInitialColors( 210 context: Context, 211 screen: CustomizationSections.Screen, 212 ) { 213 lifecycleOwner.lifecycleScope.launch(Dispatchers.IO) { 214 val colors = 215 wallpaperManager.getWallpaperColors( 216 if (screen == CustomizationSections.Screen.LOCK_SCREEN) { 217 WallpaperManager.FLAG_LOCK 218 } else { 219 WallpaperManager.FLAG_SYSTEM 220 } 221 ) 222 withContext(Dispatchers.Main) { 223 if (colors != null) { 224 if (screen == CustomizationSections.Screen.LOCK_SCREEN) { 225 colorViewModel.setLockWallpaperColors(colors) 226 } else { 227 colorViewModel.setHomeWallpaperColors(colors) 228 } 229 } 230 } 231 } 232 } 233 234 private suspend fun getWallpaperInfo(): WallpaperInfo? { 235 return suspendCancellableCoroutine { continuation -> 236 wallpaperInfoFactory.createCurrentWallpaperInfos( 237 { homeWallpaper, lockWallpaper, _ -> 238 continuation.resume( 239 if (isOnLockScreen) { 240 lockWallpaper ?: homeWallpaper 241 } else { 242 homeWallpaper 243 }, 244 null 245 ) 246 }, 247 /* forceRefresh= */ true, 248 ) 249 } 250 } 251 252 protected fun getInitialExtras(isOnLockScreen: Boolean): Bundle? { 253 return if (isOnLockScreen) { 254 Bundle().apply { 255 // Hide the clock from the system UI rendered preview so we can 256 // place the carousel on top of it. 257 putBoolean( 258 ClockPreviewConstants.KEY_HIDE_CLOCK, 259 hideLockScreenClockPreview, 260 ) 261 } 262 } else { 263 null 264 } 265 } 266 } 267