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