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 17 18 import android.content.Context 19 import android.content.Intent 20 import android.content.pm.ActivityInfo 21 import android.graphics.Color 22 import android.os.Bundle 23 import android.view.Window 24 import android.widget.Toast 25 import androidx.activity.result.contract.ActivityResultContracts 26 import androidx.activity.viewModels 27 import androidx.core.view.WindowCompat 28 import androidx.lifecycle.lifecycleScope 29 import androidx.navigation.fragment.NavHostFragment 30 import com.android.wallpaper.R 31 import com.android.wallpaper.model.ImageWallpaperInfo 32 import com.android.wallpaper.model.WallpaperInfo 33 import com.android.wallpaper.module.InjectorProvider 34 import com.android.wallpaper.picker.AppbarFragment 35 import com.android.wallpaper.picker.BasePreviewActivity 36 import com.android.wallpaper.picker.data.WallpaperModel 37 import com.android.wallpaper.picker.di.modules.MainDispatcher 38 import com.android.wallpaper.picker.preview.data.repository.CreativeEffectsRepository 39 import com.android.wallpaper.picker.preview.data.repository.ImageEffectsRepository 40 import com.android.wallpaper.picker.preview.data.repository.WallpaperPreviewRepository 41 import com.android.wallpaper.picker.preview.data.util.LiveWallpaperDownloader 42 import com.android.wallpaper.picker.preview.ui.fragment.SmallPreviewFragment 43 import com.android.wallpaper.picker.preview.ui.viewmodel.PreviewActionsViewModel.Companion.getEditActivityIntent 44 import com.android.wallpaper.picker.preview.ui.viewmodel.PreviewActionsViewModel.Companion.isNewCreativeWallpaper 45 import com.android.wallpaper.picker.preview.ui.viewmodel.WallpaperPreviewViewModel 46 import com.android.wallpaper.util.ActivityUtils 47 import com.android.wallpaper.util.DisplayUtils 48 import com.android.wallpaper.util.WallpaperConnection 49 import com.android.wallpaper.util.converter.WallpaperModelFactory 50 import com.android.wallpaper.util.wallpaperconnection.WallpaperConnectionUtils 51 import dagger.hilt.android.AndroidEntryPoint 52 import dagger.hilt.android.qualifiers.ApplicationContext 53 import javax.inject.Inject 54 import kotlinx.coroutines.CoroutineScope 55 import kotlinx.coroutines.launch 56 57 /** This activity holds the flow for the preview screen. */ 58 @AndroidEntryPoint(BasePreviewActivity::class) 59 class WallpaperPreviewActivity : 60 Hilt_WallpaperPreviewActivity(), AppbarFragment.AppbarFragmentHost { 61 @ApplicationContext @Inject lateinit var appContext: Context 62 @Inject lateinit var displayUtils: DisplayUtils 63 @Inject lateinit var wallpaperModelFactory: WallpaperModelFactory 64 @Inject lateinit var wallpaperPreviewRepository: WallpaperPreviewRepository 65 @Inject lateinit var imageEffectsRepository: ImageEffectsRepository 66 @Inject lateinit var creativeEffectsRepository: CreativeEffectsRepository 67 @Inject lateinit var liveWallpaperDownloader: LiveWallpaperDownloader 68 @MainDispatcher @Inject lateinit var mainScope: CoroutineScope 69 70 private val wallpaperPreviewViewModel: WallpaperPreviewViewModel by viewModels() 71 72 override fun onCreate(savedInstanceState: Bundle?) { 73 window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) 74 super.onCreate(savedInstanceState) 75 enforcePortraitForHandheldAndFoldedDisplay() 76 wallpaperPreviewViewModel.updateDisplayConfiguration() 77 wallpaperPreviewViewModel.setIsWallpaperColorPreviewEnabled( 78 !InjectorProvider.getInjector().isCurrentSelectedColorPreset(appContext) 79 ) 80 window.navigationBarColor = Color.TRANSPARENT 81 window.statusBarColor = Color.TRANSPARENT 82 setContentView(R.layout.activity_wallpaper_preview) 83 val wallpaper = 84 checkNotNull(intent.getParcelableExtra(EXTRA_WALLPAPER_INFO, WallpaperInfo::class.java)) 85 .convertToWallpaperModel() 86 val navController = 87 (supportFragmentManager.findFragmentById(R.id.wallpaper_preview_nav_host) 88 as NavHostFragment) 89 .navController 90 val graph = navController.navInflater.inflate(R.navigation.wallpaper_preview_nav_graph) 91 val startDestinationArgs: Bundle? = 92 (wallpaper as? WallpaperModel.LiveWallpaperModel) 93 ?.let { 94 if (it.isNewCreativeWallpaper()) it.getNewCreativeWallpaperArgs() else null 95 } 96 ?.also { 97 // For creating a new creative wallpaper, replace the default start destination 98 // with CreativeEditPreviewFragment. 99 graph.setStartDestination(R.id.creativeEditPreviewFragment) 100 } 101 navController.setGraph(graph, startDestinationArgs) 102 // Fits screen to navbar and statusbar 103 WindowCompat.setDecorFitsSystemWindows(window, ActivityUtils.isSUWMode(this)) 104 val isAssetIdPresent = intent.getBooleanExtra(IS_ASSET_ID_PRESENT, false) 105 wallpaperPreviewViewModel.isNewTask = intent.getBooleanExtra(IS_NEW_TASK, false) 106 if (savedInstanceState == null) { 107 wallpaperPreviewRepository.setWallpaperModel(wallpaper) 108 } 109 val whichPreview = 110 if (isAssetIdPresent) WallpaperConnection.WhichPreview.EDIT_NON_CURRENT 111 else WallpaperConnection.WhichPreview.EDIT_CURRENT 112 wallpaperPreviewViewModel.setWhichPreview(whichPreview) 113 if (wallpaper is WallpaperModel.StaticWallpaperModel) { 114 wallpaper.staticWallpaperData.cropHints?.let { 115 wallpaperPreviewViewModel.setCropHints(it) 116 } 117 } 118 if ( 119 (wallpaper as? WallpaperModel.StaticWallpaperModel)?.downloadableWallpaperData != null 120 ) { 121 liveWallpaperDownloader.initiateDownloadableService( 122 this, 123 wallpaper, 124 registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {} 125 ) 126 } 127 128 val creativeWallpaperEffectData = 129 (wallpaper as? WallpaperModel.LiveWallpaperModel) 130 ?.creativeWallpaperData 131 ?.creativeWallpaperEffectsData 132 if (creativeWallpaperEffectData != null) { 133 lifecycleScope.launch { 134 creativeEffectsRepository.initializeEffect(creativeWallpaperEffectData) 135 } 136 } else if ( 137 (wallpaper as? WallpaperModel.StaticWallpaperModel)?.imageWallpaperData != null && 138 imageEffectsRepository.areEffectsAvailable() 139 ) { 140 lifecycleScope.launch { 141 imageEffectsRepository.initializeEffect( 142 staticWallpaperModel = wallpaper, 143 onWallpaperModelUpdated = { wallpaper -> 144 wallpaperPreviewRepository.setWallpaperModel(wallpaper) 145 }, 146 ) 147 } 148 } 149 } 150 151 override fun onUpArrowPressed() { 152 onBackPressedDispatcher.onBackPressed() 153 } 154 155 override fun isUpArrowSupported(): Boolean { 156 return !ActivityUtils.isSUWMode(baseContext) 157 } 158 159 override fun onResume() { 160 super.onResume() 161 if (isInMultiWindowMode) { 162 Toast.makeText(this, R.string.wallpaper_exit_split_screen, Toast.LENGTH_SHORT).show() 163 onBackPressedDispatcher.onBackPressed() 164 } 165 } 166 167 override fun onDestroy() { 168 imageEffectsRepository.destroy() 169 creativeEffectsRepository.destroy() 170 liveWallpaperDownloader.cleanup() 171 // TODO(b/333879532): Only disconnect when leaving the Activity without introducing black 172 // preview. If onDestroy is caused by an orientation change, we should keep the connection 173 // to avoid initiating the engines again. 174 // TODO(b/328302105): MainScope ensures the job gets done non-blocking even if the 175 // activity has been destroyed already. Consider making this part of 176 // WallpaperConnectionUtils. 177 (wallpaperPreviewViewModel.wallpaper.value as? WallpaperModel.LiveWallpaperModel)?.let { 178 // Keep a copy of current wallpaperPreviewViewModel.wallpaperDisplaySize as what we want 179 // to disconnect. There's a chance mainScope executes the job not until new activity 180 // is created and the wallpaperDisplaySize is updated to a new one, e.g. when 181 // orientation changed. 182 // TODO(b/328302105): maintain this state in WallpaperConnectionUtils. 183 val currentWallpaperDisplay = wallpaperPreviewViewModel.wallpaperDisplaySize.value 184 mainScope.launch { 185 WallpaperConnectionUtils.disconnect( 186 appContext, 187 it, 188 wallpaperPreviewViewModel.smallerDisplaySize 189 ) 190 WallpaperConnectionUtils.disconnect( 191 appContext, 192 it, 193 currentWallpaperDisplay, 194 ) 195 } 196 } 197 198 super.onDestroy() 199 } 200 201 private fun WallpaperInfo.convertToWallpaperModel(): WallpaperModel { 202 return wallpaperModelFactory.getWallpaperModel(appContext, this) 203 } 204 205 companion object { 206 /** 207 * Returns a new [Intent] that can be used to start [WallpaperPreviewActivity]. 208 * 209 * @param context application context. 210 * @param wallpaperInfo selected by user for editing preview. 211 * @param isNewTask true to launch at a new task. 212 * 213 * TODO(b/291761856): Use wallpaper model to replace wallpaper info. 214 */ 215 fun newIntent( 216 context: Context, 217 wallpaperInfo: WallpaperInfo, 218 isAssetIdPresent: Boolean, 219 isViewAsHome: Boolean = false, 220 isNewTask: Boolean = false, 221 ): Intent { 222 val intent = Intent(context.applicationContext, WallpaperPreviewActivity::class.java) 223 if (isNewTask) { 224 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) 225 } 226 intent.putExtra(EXTRA_WALLPAPER_INFO, wallpaperInfo) 227 intent.putExtra(IS_ASSET_ID_PRESENT, isAssetIdPresent) 228 intent.putExtra(EXTRA_VIEW_AS_HOME, isViewAsHome) 229 intent.putExtra(IS_NEW_TASK, isNewTask) 230 return intent 231 } 232 233 /** 234 * Returns a new [Intent] that can be used to start [WallpaperPreviewActivity], explicitly 235 * propagating any permissions on the wallpaper data to the new [Intent]. 236 * 237 * @param context application context. 238 * @param wallpaperInfo selected by user for editing preview. 239 * @param isNewTask true to launch at a new task. 240 * 241 * TODO(b/291761856): Use wallpaper model to replace wallpaper info. 242 */ 243 fun newIntent( 244 context: Context, 245 originalIntent: Intent, 246 isAssetIdPresent: Boolean, 247 isViewAsHome: Boolean = false, 248 isNewTask: Boolean = false, 249 ): Intent { 250 val data = originalIntent.data 251 val intent = 252 newIntent( 253 context, 254 ImageWallpaperInfo(data), 255 isAssetIdPresent, 256 isViewAsHome, 257 isNewTask 258 ) 259 // Both these lines are required for permission propagation 260 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) 261 intent.setData(data) 262 return intent 263 } 264 265 private fun WallpaperModel.LiveWallpaperModel.getNewCreativeWallpaperArgs() = 266 Bundle().apply { 267 putParcelable( 268 SmallPreviewFragment.ARG_EDIT_INTENT, 269 liveWallpaperData.getEditActivityIntent(true), 270 ) 271 } 272 } 273 274 /** 275 * If the display is a handheld display or a folded display from a foldable, we enforce the 276 * activity to be portrait. 277 * 278 * This method should be called upon initialization of this activity, and whenever there is a 279 * configuration change. 280 */ 281 private fun enforcePortraitForHandheldAndFoldedDisplay() { 282 val wantedOrientation = 283 if (displayUtils.isLargeScreenOrUnfoldedDisplay(this)) 284 ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED 285 else ActivityInfo.SCREEN_ORIENTATION_PORTRAIT 286 if (requestedOrientation != wantedOrientation) { 287 requestedOrientation = wantedOrientation 288 } 289 } 290 } 291