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