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 17 package com.android.wallpaper.picker.preview.ui.viewmodel 18 19 import android.app.Flags.liveWallpaperContentHandling 20 import android.content.ActivityNotFoundException 21 import android.content.ClipData 22 import android.content.ComponentName 23 import android.content.Context 24 import android.content.Intent 25 import android.net.ConnectivityManager 26 import android.net.Uri 27 import android.net.wifi.WifiManager 28 import android.service.wallpaper.WallpaperSettingsActivity 29 import android.util.Log 30 import androidx.activity.result.ActivityResultLauncher 31 import com.android.wallpaper.R 32 import com.android.wallpaper.effects.Effect 33 import com.android.wallpaper.effects.EffectsController.EffectEnumInterface 34 import com.android.wallpaper.model.WallpaperInfoContract.WALLPAPER_DESCRIPTION_CONTENT_HANDLING 35 import com.android.wallpaper.module.InjectorProvider 36 import com.android.wallpaper.picker.data.CreativeWallpaperData 37 import com.android.wallpaper.picker.data.LiveWallpaperData 38 import com.android.wallpaper.picker.data.WallpaperModel 39 import com.android.wallpaper.picker.data.WallpaperModel.LiveWallpaperModel 40 import com.android.wallpaper.picker.data.WallpaperModel.StaticWallpaperModel 41 import com.android.wallpaper.picker.preview.data.repository.ImageEffectsRepository.EffectStatus.EFFECT_APPLIED 42 import com.android.wallpaper.picker.preview.data.repository.ImageEffectsRepository.EffectStatus.EFFECT_APPLY_FAILED 43 import com.android.wallpaper.picker.preview.data.repository.ImageEffectsRepository.EffectStatus.EFFECT_APPLY_IN_PROGRESS 44 import com.android.wallpaper.picker.preview.data.repository.ImageEffectsRepository.EffectStatus.EFFECT_DISABLE 45 import com.android.wallpaper.picker.preview.data.repository.ImageEffectsRepository.EffectStatus.EFFECT_DOWNLOAD_FAILED 46 import com.android.wallpaper.picker.preview.data.repository.ImageEffectsRepository.EffectStatus.EFFECT_DOWNLOAD_IN_PROGRESS 47 import com.android.wallpaper.picker.preview.data.repository.ImageEffectsRepository.EffectStatus.EFFECT_DOWNLOAD_READY 48 import com.android.wallpaper.picker.preview.data.repository.ImageEffectsRepository.EffectStatus.EFFECT_READY 49 import com.android.wallpaper.picker.preview.domain.interactor.PreviewActionsInteractor 50 import com.android.wallpaper.picker.preview.domain.interactor.WallpaperPreviewInteractor 51 import com.android.wallpaper.picker.preview.shared.model.DownloadStatus 52 import com.android.wallpaper.picker.preview.shared.model.ImageEffectsModel 53 import com.android.wallpaper.picker.preview.ui.util.LiveWallpaperDeleteUtil 54 import com.android.wallpaper.picker.preview.ui.viewmodel.Action.CUSTOMIZE 55 import com.android.wallpaper.picker.preview.ui.viewmodel.Action.DELETE 56 import com.android.wallpaper.picker.preview.ui.viewmodel.Action.DOWNLOAD 57 import com.android.wallpaper.picker.preview.ui.viewmodel.Action.EDIT 58 import com.android.wallpaper.picker.preview.ui.viewmodel.Action.EFFECTS 59 import com.android.wallpaper.picker.preview.ui.viewmodel.Action.INFORMATION 60 import com.android.wallpaper.picker.preview.ui.viewmodel.Action.SHARE 61 import com.android.wallpaper.picker.preview.ui.viewmodel.floatingSheet.CreativeEffectFloatingSheetViewModel 62 import com.android.wallpaper.picker.preview.ui.viewmodel.floatingSheet.CustomizeFloatingSheetViewModel 63 import com.android.wallpaper.picker.preview.ui.viewmodel.floatingSheet.ImageEffectFloatingSheetViewModel 64 import com.android.wallpaper.picker.preview.ui.viewmodel.floatingSheet.InformationFloatingSheetViewModel 65 import com.android.wallpaper.picker.preview.ui.viewmodel.floatingSheet.PreviewFloatingSheetViewModel 66 import com.android.wallpaper.widget.floatingsheetcontent.WallpaperEffectsView2.EffectDownloadClickListener 67 import com.android.wallpaper.widget.floatingsheetcontent.WallpaperEffectsView2.EffectSwitchListener 68 import com.android.wallpaper.widget.floatingsheetcontent.WallpaperEffectsView2.Status.DOWNLOADING 69 import com.android.wallpaper.widget.floatingsheetcontent.WallpaperEffectsView2.Status.FAILED 70 import com.android.wallpaper.widget.floatingsheetcontent.WallpaperEffectsView2.Status.IDLE 71 import com.android.wallpaper.widget.floatingsheetcontent.WallpaperEffectsView2.Status.PROCESSING 72 import com.android.wallpaper.widget.floatingsheetcontent.WallpaperEffectsView2.Status.SHOW_DOWNLOAD_BUTTON 73 import com.android.wallpaper.widget.floatingsheetcontent.WallpaperEffectsView2.Status.SUCCESS 74 import dagger.hilt.android.qualifiers.ApplicationContext 75 import dagger.hilt.android.scopes.ViewModelScoped 76 import javax.inject.Inject 77 import kotlinx.coroutines.flow.Flow 78 import kotlinx.coroutines.flow.MutableStateFlow 79 import kotlinx.coroutines.flow.asStateFlow 80 import kotlinx.coroutines.flow.combine 81 import kotlinx.coroutines.flow.filterNotNull 82 import kotlinx.coroutines.flow.map 83 84 /** View model for the preview action buttons */ 85 @ViewModelScoped 86 class PreviewActionsViewModel 87 @Inject 88 constructor( 89 private val previewActionsInteractor: PreviewActionsInteractor, 90 wallpaperPreviewInteractor: WallpaperPreviewInteractor, 91 liveWallpaperDeleteUtil: LiveWallpaperDeleteUtil, 92 @ApplicationContext private val context: Context, 93 ) { 94 private val flags = InjectorProvider.getInjector().getFlags() 95 private val extendedWallpaperEffectPkgName = 96 context.getString(R.string.extended_wallpaper_effects_package) 97 private val extendedWallpaperEffectActivityName = 98 context.getString(R.string.extended_wallpaper_effects_activity) 99 100 /** [INFORMATION] */ 101 private val informationFloatingSheetViewModel: Flow<InformationFloatingSheetViewModel?> = 102 previewActionsInteractor.wallpaperModel.map { wallpaperModel -> 103 if (wallpaperModel == null || !wallpaperModel.shouldShowInformationFloatingSheet()) { 104 null 105 } else { 106 InformationFloatingSheetViewModel( 107 description = 108 (wallpaperModel as? LiveWallpaperModel)?.liveWallpaperData?.description, 109 attributions = wallpaperModel.commonWallpaperData.attributions, 110 actionUrl = 111 if (wallpaperModel.commonWallpaperData.exploreActionUrl.isNullOrEmpty()) { 112 null 113 } else { 114 wallpaperModel.commonWallpaperData.exploreActionUrl 115 }, 116 actionButtonTitle = 117 (wallpaperModel as? LiveWallpaperModel) 118 ?.liveWallpaperData 119 ?.contextDescription, 120 ) 121 } 122 } 123 124 val isInformationVisible: Flow<Boolean> = informationFloatingSheetViewModel.map { it != null } 125 126 private val _isInformationChecked: MutableStateFlow<Boolean> = MutableStateFlow(false) 127 val isInformationChecked: Flow<Boolean> = _isInformationChecked.asStateFlow() 128 129 val onInformationClicked: Flow<(() -> Unit)?> = 130 combine(isInformationVisible, isInformationChecked) { show, isChecked -> 131 if (show) { 132 { 133 if (!isChecked) { 134 uncheckAllOthersExcept(INFORMATION) 135 } 136 _isInformationChecked.value = !isChecked 137 } 138 } else { 139 null 140 } 141 } 142 143 /** [DOWNLOAD] */ 144 val isDownloadVisible: Flow<Boolean> = 145 previewActionsInteractor.downloadableWallpaperModel.map { 146 it.status == DownloadStatus.READY_TO_DOWNLOAD || it.status == DownloadStatus.DOWNLOADING 147 } 148 val isDownloading: Flow<Boolean> = 149 previewActionsInteractor.downloadableWallpaperModel.map { 150 it.status == DownloadStatus.DOWNLOADING 151 } 152 val isDownloadButtonEnabled: Flow<Boolean> = 153 previewActionsInteractor.downloadableWallpaperModel.map { 154 it.status == DownloadStatus.READY_TO_DOWNLOAD 155 } 156 157 fun downloadWallpaper() { 158 previewActionsInteractor.downloadWallpaper() 159 } 160 161 /** [DELETE] */ 162 private val liveWallpaperDeleteIntent: Flow<Intent?> = 163 previewActionsInteractor.wallpaperModel.map { 164 if (it is LiveWallpaperModel && it.creativeWallpaperData == null && it.canBeDeleted()) { 165 liveWallpaperDeleteUtil.getDeleteActionIntent( 166 it.liveWallpaperData.systemWallpaperInfo 167 ) 168 } else { 169 null 170 } 171 } 172 private val creativeWallpaperDeleteUri: Flow<Uri?> = 173 previewActionsInteractor.wallpaperModel.map { 174 val deleteUri = (it as? LiveWallpaperModel)?.creativeWallpaperData?.deleteUri 175 if (deleteUri != null && it.canBeDeleted()) { 176 deleteUri 177 } else { 178 null 179 } 180 } 181 val isDeleteVisible: Flow<Boolean> = 182 combine(liveWallpaperDeleteIntent, creativeWallpaperDeleteUri) { intent, uri -> 183 intent != null || uri != null 184 } 185 186 private val _isDeleteChecked: MutableStateFlow<Boolean> = MutableStateFlow(false) 187 val isDeleteChecked: Flow<Boolean> = _isDeleteChecked.asStateFlow() 188 189 // View model for delete confirmation dialog. Note that null means the dialog should show; 190 // otherwise, the dialog should hide. 191 val deleteConfirmationDialogViewModel: Flow<DeleteConfirmationDialogViewModel?> = 192 combine(isDeleteChecked, liveWallpaperDeleteIntent, creativeWallpaperDeleteUri) { 193 isChecked, 194 intent, 195 uri -> 196 if (isChecked && (intent != null || uri != null)) { 197 DeleteConfirmationDialogViewModel( 198 onDismiss = { _isDeleteChecked.value = false }, 199 liveWallpaperDeleteIntent = intent, 200 creativeWallpaperDeleteUri = uri, 201 ) 202 } else { 203 null 204 } 205 } 206 207 val onDeleteClicked: Flow<(() -> Unit)?> = 208 combine(isDeleteVisible, isDeleteChecked) { show, isChecked -> 209 if (show) { 210 { 211 if (!isChecked) { 212 uncheckAllOthersExcept(DELETE) 213 } 214 _isDeleteChecked.value = !isChecked 215 } 216 } else { 217 null 218 } 219 } 220 221 /** [EDIT] */ 222 val editIntent: Flow<Intent?> = 223 previewActionsInteractor.wallpaperModel.map { model -> 224 (model as? LiveWallpaperModel)?.liveWallpaperData?.getEditActivityIntent(false)?.let { 225 intent -> 226 if (intent.resolveActivityInfo(context.packageManager, 0) != null) intent else null 227 } 228 } 229 val isEditVisible: Flow<Boolean> = editIntent.map { it != null } 230 231 private val _isEditChecked: MutableStateFlow<Boolean> = MutableStateFlow(false) 232 val isEditChecked: Flow<Boolean> = _isEditChecked.asStateFlow() 233 234 /** [CUSTOMIZE] */ 235 private val customizeFloatingSheetViewModel: Flow<CustomizeFloatingSheetViewModel?> = 236 previewActionsInteractor.wallpaperModel.map { 237 (it as? LiveWallpaperModel) 238 ?.liveWallpaperData 239 ?.systemWallpaperInfo 240 ?.settingsSliceUri 241 ?.let { CustomizeFloatingSheetViewModel(it) } 242 } 243 val isCustomizeVisible: Flow<Boolean> = customizeFloatingSheetViewModel.map { it != null } 244 245 private val _isCustomizeChecked: MutableStateFlow<Boolean> = MutableStateFlow(false) 246 val isCustomizeChecked: Flow<Boolean> = _isCustomizeChecked.asStateFlow() 247 248 val onCustomizeClicked: Flow<(() -> Unit)?> = 249 combine(isCustomizeVisible, isCustomizeChecked) { show, isChecked -> 250 if (show) { 251 { 252 if (!isChecked) { 253 uncheckAllOthersExcept(CUSTOMIZE) 254 } 255 _isCustomizeChecked.value = !isChecked 256 } 257 } else { 258 null 259 } 260 } 261 262 /** [EFFECTS] */ 263 private val _imageEffectConfirmDownloadDialogViewModel: 264 MutableStateFlow<ImageEffectDialogViewModel?> = 265 MutableStateFlow(null) 266 // View model for the dialog that confirms downloading the effect ML model. 267 val imageEffectConfirmDownloadDialogViewModel = 268 _imageEffectConfirmDownloadDialogViewModel.asStateFlow() 269 270 private val imageEffectFloatingSheetViewModel: Flow<ImageEffectFloatingSheetViewModel?> = 271 combine(previewActionsInteractor.imageEffectsModel, previewActionsInteractor.imageEffect) { 272 imageEffectsModel, 273 imageEffect -> 274 imageEffect?.let { 275 when (imageEffectsModel.status) { 276 EFFECT_DISABLE -> { 277 null 278 } 279 else -> { 280 getImageEffectFloatingSheetViewModel(imageEffect, imageEffectsModel) 281 } 282 } 283 } 284 } 285 private val _imageEffectConfirmExitDialogViewModel: 286 MutableStateFlow<ImageEffectDialogViewModel?> = 287 MutableStateFlow(null) 288 val imageEffectConfirmExitDialogViewModel = _imageEffectConfirmExitDialogViewModel.asStateFlow() 289 val handleOnBackPressed: Flow<(() -> Boolean)?> = 290 combine( 291 imageEffectFloatingSheetViewModel, 292 previewActionsInteractor.imageEffect, 293 isDownloading, 294 ) { viewModel, effect, isDownloading -> 295 when { 296 viewModel?.status == DOWNLOADING -> { -> 297 _imageEffectConfirmExitDialogViewModel.value = 298 ImageEffectDialogViewModel( 299 onDismiss = { _imageEffectConfirmExitDialogViewModel.value = null }, 300 onContinue = { 301 // Continue to exit the screen. We should stop downloading. 302 effect?.let { 303 previewActionsInteractor.interruptEffectsModelDownload(it) 304 } 305 }, 306 ) 307 true 308 } 309 isDownloading -> { -> previewActionsInteractor.cancelDownloadWallpaper() } 310 else -> null 311 } 312 } 313 314 private val creativeEffectFloatingSheetViewModel: Flow<CreativeEffectFloatingSheetViewModel?> = 315 previewActionsInteractor.creativeEffectsModel.map { creativeEffectsModel -> 316 creativeEffectsModel?.let { 317 CreativeEffectFloatingSheetViewModel( 318 title = it.title, 319 subtitle = it.subtitle, 320 wallpaperActions = it.actions, 321 wallpaperEffectSwitchListener = { actionPosition -> 322 previewActionsInteractor.turnOnCreativeEffect(actionPosition) 323 }, 324 ) 325 } 326 } 327 328 private fun getImageEffectFloatingSheetViewModel( 329 effect: Effect, 330 imageEffectsModel: ImageEffectsModel, 331 ): ImageEffectFloatingSheetViewModel { 332 val floatingSheetViewStatus = 333 when (imageEffectsModel.status) { 334 EFFECT_DISABLE -> { 335 FAILED 336 } 337 EFFECT_READY -> { 338 IDLE 339 } 340 EFFECT_DOWNLOAD_READY -> { 341 SHOW_DOWNLOAD_BUTTON 342 } 343 EFFECT_DOWNLOAD_IN_PROGRESS -> { 344 DOWNLOADING 345 } 346 EFFECT_APPLY_IN_PROGRESS -> { 347 PROCESSING 348 } 349 EFFECT_APPLIED -> { 350 SUCCESS 351 } 352 EFFECT_DOWNLOAD_FAILED -> { 353 SHOW_DOWNLOAD_BUTTON 354 } 355 EFFECT_APPLY_FAILED -> { 356 FAILED 357 } 358 } 359 return ImageEffectFloatingSheetViewModel( 360 myPhotosClickListener = {}, 361 collapseFloatingSheetListener = {}, 362 object : EffectSwitchListener { 363 override fun onEffectSwitchChanged( 364 effect: EffectEnumInterface, 365 isChecked: Boolean, 366 ) { 367 if (previewActionsInteractor.isTargetEffect(effect)) { 368 if (isChecked) { 369 previewActionsInteractor.enableImageEffect(effect) 370 } else { 371 previewActionsInteractor.disableImageEffect() 372 } 373 } 374 } 375 }, 376 object : EffectDownloadClickListener { 377 override fun onEffectDownloadClick() { 378 if (isWifiOnAndConnected()) { 379 previewActionsInteractor.startEffectsModelDownload(effect) 380 } else { 381 _imageEffectConfirmDownloadDialogViewModel.value = 382 ImageEffectDialogViewModel( 383 onDismiss = { 384 _imageEffectConfirmDownloadDialogViewModel.value = null 385 }, 386 onContinue = { 387 // Continue to download the ML model 388 previewActionsInteractor.startEffectsModelDownload(effect) 389 }, 390 ) 391 } 392 } 393 }, 394 floatingSheetViewStatus, 395 imageEffectsModel.resultCode, 396 imageEffectsModel.errorMessage, 397 effect.title, 398 effect.type, 399 previewActionsInteractor.getEffectTextRes(), 400 ) 401 } 402 403 private fun isWifiOnAndConnected(): Boolean { 404 val wifiMgr = context.getSystemService(Context.WIFI_SERVICE) as WifiManager 405 return if (wifiMgr.isWifiEnabled) { // Wi-Fi adapter is ON 406 val connMgr = 407 context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 408 val wifiInfo = wifiMgr.connectionInfo 409 val wifi = connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI) 410 val signalLevel = wifiMgr.calculateSignalLevel(wifiInfo.rssi) 411 signalLevel > 0 && wifi!!.isConnectedOrConnecting 412 } else { 413 false 414 } 415 } 416 417 private val _isEffectsChecked: MutableStateFlow<Boolean> = MutableStateFlow(false) 418 val isEffectsChecked: Flow<Boolean> = _isEffectsChecked.asStateFlow() 419 420 private val extendedWallpaperIntent = 421 Intent().apply { 422 component = 423 ComponentName(extendedWallpaperEffectPkgName, extendedWallpaperEffectActivityName) 424 } 425 426 private val isExtendedEffectAvailable: Flow<Boolean> = 427 wallpaperPreviewInteractor.wallpaperModel.map { 428 flags.isExtendedWallpaperEnabled() && 429 ((it is StaticWallpaperModel && it.imageWallpaperData?.uri != null) || 430 (it is LiveWallpaperModel && it.liveWallpaperData.isEffectWallpaper)) && 431 extendedWallpaperIntent.resolveActivityInfo(context.packageManager, 0) != null 432 } 433 434 val isEffectsVisible: Flow<Boolean> = 435 combine( 436 imageEffectFloatingSheetViewModel, 437 creativeEffectFloatingSheetViewModel, 438 isExtendedEffectAvailable, 439 ) { imageEffect, creativeEffect, isExtendedEffect -> 440 isExtendedEffect || imageEffect != null || creativeEffect != null 441 } 442 443 val onEffectsClicked: Flow<((ActivityResultLauncher<Intent>) -> Unit)?> = 444 combine(isEffectsVisible, isEffectsChecked, isExtendedEffectAvailable) { 445 isVisible, 446 isChecked, 447 extendedEffectAvailable -> 448 if (isVisible) { 449 if (extendedEffectAvailable) { 450 { launcher -> launchExtendedWallpaperEffects(launcher) } 451 } else { 452 fun(_: ActivityResultLauncher<Intent>) { 453 if (!isChecked) { 454 uncheckAllOthersExcept(EFFECTS) 455 } 456 _isEffectsChecked.value = !isChecked 457 } 458 } 459 } else { 460 null 461 } 462 } 463 464 private fun launchExtendedWallpaperEffects(launcher: ActivityResultLauncher<Intent>) { 465 val wallpaperModel = previewActionsInteractor.wallpaperModel.value 466 if (isExtendedEffectWallpaperModel(wallpaperModel)) { 467 // Extended effect wallpaper, launch with description 468 extendedWallpaperIntent.putExtra( 469 WALLPAPER_DESCRIPTION_CONTENT_HANDLING, 470 (wallpaperModel as LiveWallpaperModel).liveWallpaperData.description, 471 ) 472 } else { 473 val photoUri = (wallpaperModel as? StaticWallpaperModel)?.imageWallpaperData?.uri 474 Log.d(TAG, "PhotoURI is: $photoUri") 475 photoUri?.let { 476 context.grantUriPermission( 477 extendedWallpaperEffectPkgName, 478 photoUri, 479 Intent.FLAG_GRANT_READ_URI_PERMISSION, 480 ) 481 extendedWallpaperIntent.putExtra("PHOTO_URI", it) 482 } 483 } 484 485 try { 486 launcher.launch(extendedWallpaperIntent) 487 } catch (ex: ActivityNotFoundException) { 488 Log.e(TAG, "Extended Wallpaper Activity is not available", ex) 489 } 490 } 491 492 val effectDownloadFailureToastText: Flow<String> = 493 previewActionsInteractor.imageEffectsModel 494 .map { if (it.status == EFFECT_DOWNLOAD_FAILED) it.errorMessage else null } 495 .filterNotNull() 496 497 /** [SHARE] */ 498 val shareIntent: Flow<Intent?> = 499 previewActionsInteractor.wallpaperModel.map { model -> 500 (model as? LiveWallpaperModel)?.creativeWallpaperData?.let { data -> 501 if (data.shareUri == null || data.shareUri == Uri.EMPTY) null 502 else data.getShareIntent() 503 } 504 } 505 val isShareVisible: Flow<Boolean> = shareIntent.map { it != null } 506 507 // Floating sheet contents for the bottom sheet dialog. If content is null, the bottom sheet 508 // should collapse, otherwise, expended. 509 val previewFloatingSheetViewModel: Flow<PreviewFloatingSheetViewModel?> = 510 combine7( 511 isInformationChecked, 512 isEffectsChecked, 513 isCustomizeChecked, 514 informationFloatingSheetViewModel, 515 imageEffectFloatingSheetViewModel, 516 creativeEffectFloatingSheetViewModel, 517 customizeFloatingSheetViewModel, 518 ) { 519 isInformationChecked, 520 isEffectsChecked, 521 isCustomizeChecked, 522 informationFloatingSheetViewModel, 523 imageEffectFloatingSheetViewModel, 524 creativeEffectFloatingSheetViewModel, 525 customizeFloatingSheetViewModel -> 526 if (isInformationChecked && informationFloatingSheetViewModel != null) { 527 PreviewFloatingSheetViewModel( 528 informationFloatingSheetViewModel = informationFloatingSheetViewModel 529 ) 530 } else if (isEffectsChecked && imageEffectFloatingSheetViewModel != null) { 531 PreviewFloatingSheetViewModel( 532 imageEffectFloatingSheetViewModel = imageEffectFloatingSheetViewModel 533 ) 534 } else if (isEffectsChecked && creativeEffectFloatingSheetViewModel != null) { 535 PreviewFloatingSheetViewModel( 536 creativeEffectFloatingSheetViewModel = creativeEffectFloatingSheetViewModel 537 ) 538 } else if (isCustomizeChecked && customizeFloatingSheetViewModel != null) { 539 PreviewFloatingSheetViewModel( 540 customizeFloatingSheetViewModel = customizeFloatingSheetViewModel 541 ) 542 } else { 543 null 544 } 545 } 546 547 fun onFloatingSheetCollapsed() { 548 // When floating collapsed, we should look for those actions that expand the floating sheet 549 // and see which is checked, and uncheck it. 550 if (_isInformationChecked.value) { 551 _isInformationChecked.value = false 552 } 553 554 if (_isEffectsChecked.value) { 555 _isEffectsChecked.value = false 556 } 557 558 if (_isCustomizeChecked.value) { 559 _isCustomizeChecked.value = false 560 } 561 } 562 563 fun isAnyActionChecked(): Boolean = 564 _isInformationChecked.value || 565 _isDeleteChecked.value || 566 _isEditChecked.value || 567 _isCustomizeChecked.value || 568 _isEffectsChecked.value 569 570 val isActionChecked: Flow<Boolean> = 571 combine( 572 isInformationChecked, 573 isDeleteChecked, 574 isEditChecked, 575 isCustomizeChecked, 576 isEffectsChecked, 577 ) { 578 isInformationChecked, 579 isDeleteChecked, 580 isEditChecked, 581 isCustomizeChecked, 582 isEffectsChecked -> 583 isInformationChecked || 584 isDeleteChecked || 585 isEditChecked || 586 isCustomizeChecked || 587 isEffectsChecked 588 } 589 590 private fun uncheckAllOthersExcept(action: Action) { 591 if (action != INFORMATION) { 592 _isInformationChecked.value = false 593 } 594 if (action != DELETE) { 595 _isDeleteChecked.value = false 596 } 597 if (action != EDIT) { 598 _isEditChecked.value = false 599 } 600 if (action != CUSTOMIZE) { 601 _isCustomizeChecked.value = false 602 } 603 if (action != EFFECTS) { 604 _isEffectsChecked.value = false 605 } 606 } 607 608 private fun isExtendedEffectWallpaperModel(model: WallpaperModel?): Boolean = 609 flags.isExtendedWallpaperEnabled() && 610 model is LiveWallpaperModel && 611 model.liveWallpaperData.isEffectWallpaper 612 613 companion object { 614 private const val TAG = "PreviewActionsViewModel" 615 const val EXTRA_KEY_IS_CREATE_NEW = "is_create_new" 616 const val EXTRA_WALLPAPER_DESCRIPTION = "wp_description" 617 618 private fun WallpaperModel.shouldShowInformationFloatingSheet(): Boolean { 619 if ( 620 this is LiveWallpaperModel && 621 !liveWallpaperData.systemWallpaperInfo.showMetadataInPreview 622 ) { 623 // If the live wallpaper's flag of showMetadataInPreview is false, do not show the 624 // information floating sheet. 625 return false 626 } 627 val attributions = commonWallpaperData.attributions 628 val description = (this as? LiveWallpaperModel)?.liveWallpaperData?.description 629 val hasDescription = 630 liveWallpaperContentHandling() && 631 description != null && 632 (description.description.isNotEmpty() || 633 !description.title.isNullOrEmpty() || 634 description.contextUri != null) 635 // Show information floating sheet when any of the following contents exists 636 // 1. Attributions/Description: Any of the list values is not null nor empty 637 // 2. Explore action URL 638 return (!attributions.isNullOrEmpty() && attributions.any { it.isNotEmpty() }) || 639 !commonWallpaperData.exploreActionUrl.isNullOrEmpty() || 640 hasDescription 641 } 642 643 private fun CreativeWallpaperData.getShareIntent(): Intent { 644 val shareIntent = Intent(Intent.ACTION_SEND) 645 shareIntent.putExtra(Intent.EXTRA_STREAM, shareUri) 646 shareIntent.setType("image/*") 647 shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) 648 shareIntent.clipData = ClipData.newRawUri(null, shareUri) 649 return Intent.createChooser(shareIntent, null) 650 } 651 652 private fun LiveWallpaperModel.canBeDeleted(): Boolean { 653 return if (creativeWallpaperData != null) { 654 !liveWallpaperData.isApplied && 655 !creativeWallpaperData.isCurrent && 656 creativeWallpaperData.deleteUri.toString().isNotEmpty() 657 } else { 658 !liveWallpaperData.isApplied 659 } 660 } 661 662 /** 663 * @param isCreateNew: True means creating a new creative wallpaper. False means editing an 664 * existing wallpaper. 665 */ 666 fun LiveWallpaperData.getEditActivityIntent(isCreateNew: Boolean): Intent? { 667 val settingsActivity = systemWallpaperInfo.settingsActivity 668 if (settingsActivity.isNullOrEmpty()) { 669 return null 670 } 671 val intent = 672 Intent().apply { 673 component = ComponentName(systemWallpaperInfo.packageName, settingsActivity) 674 putExtra(WallpaperSettingsActivity.EXTRA_PREVIEW_MODE, true) 675 putExtra(EXTRA_KEY_IS_CREATE_NEW, isCreateNew) 676 description.content.let { putExtra(EXTRA_WALLPAPER_DESCRIPTION, it) } 677 } 678 return intent 679 } 680 681 fun LiveWallpaperModel.isNewCreativeWallpaper(): Boolean { 682 return if ( 683 InjectorProvider.getInjector().getFlags().isNewCreativeWallpaperCategoryEnabled() 684 ) { 685 creativeWallpaperData?.isNewCreativeWallpaper ?: false 686 } else { 687 creativeWallpaperData?.deleteUri?.toString()?.isEmpty() == true 688 } 689 } 690 691 /** The original combine function can only take up to 5 flows. */ 692 inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine7( 693 flow: Flow<T1>, 694 flow2: Flow<T2>, 695 flow3: Flow<T3>, 696 flow4: Flow<T4>, 697 flow5: Flow<T5>, 698 flow6: Flow<T6>, 699 flow7: Flow<T7>, 700 crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R, 701 ): Flow<R> { 702 return combine(flow, flow2, flow3, flow4, flow5, flow6, flow7) { args: Array<*> -> 703 @Suppress("UNCHECKED_CAST") 704 transform( 705 args[0] as T1, 706 args[1] as T2, 707 args[2] as T3, 708 args[3] as T4, 709 args[4] as T5, 710 args[5] as T6, 711 args[6] as T7, 712 ) 713 } 714 } 715 } 716 } 717 718 enum class Action { 719 INFORMATION, 720 DOWNLOAD, 721 DELETE, 722 EDIT, 723 CUSTOMIZE, 724 EFFECTS, 725 SHARE, 726 } 727