• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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