• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.intentresolver.ui.viewmodel
17 
18 import android.content.ContentInterface
19 import android.os.Bundle
20 import android.util.Log
21 import androidx.lifecycle.SavedStateHandle
22 import androidx.lifecycle.ViewModel
23 import androidx.lifecycle.viewModelScope
24 import com.android.intentresolver.Flags.interactiveSession
25 import com.android.intentresolver.Flags.saveShareouselState
26 import com.android.intentresolver.contentpreview.ImageLoader
27 import com.android.intentresolver.contentpreview.PreviewDataProvider
28 import com.android.intentresolver.contentpreview.payloadtoggle.domain.interactor.FetchPreviewsInteractor
29 import com.android.intentresolver.contentpreview.payloadtoggle.domain.interactor.ProcessTargetIntentUpdatesInteractor
30 import com.android.intentresolver.contentpreview.payloadtoggle.ui.viewmodel.ShareouselViewModel
31 import com.android.intentresolver.data.model.ChooserRequest
32 import com.android.intentresolver.data.repository.ActivityModelRepository
33 import com.android.intentresolver.data.repository.ChooserRequestRepository
34 import com.android.intentresolver.domain.saveUpdates
35 import com.android.intentresolver.inject.Background
36 import com.android.intentresolver.interactive.domain.interactor.InteractiveSessionInteractor
37 import com.android.intentresolver.shared.model.ActivityModel
38 import com.android.intentresolver.validation.Invalid
39 import com.android.intentresolver.validation.Valid
40 import com.android.intentresolver.validation.ValidationResult
41 import dagger.Lazy
42 import dagger.hilt.android.lifecycle.HiltViewModel
43 import javax.inject.Inject
44 import kotlinx.coroutines.CoroutineDispatcher
45 import kotlinx.coroutines.flow.StateFlow
46 import kotlinx.coroutines.flow.asStateFlow
47 import kotlinx.coroutines.launch
48 import kotlinx.coroutines.plus
49 
50 private const val TAG = "ChooserViewModel"
51 const val CHOOSER_REQUEST_KEY = "chooser-request"
52 
53 @HiltViewModel
54 class ChooserViewModel
55 @Inject
56 constructor(
57     savedStateHandle: SavedStateHandle,
58     activityModelRepository: ActivityModelRepository,
59     private val shareouselViewModelProvider: Lazy<ShareouselViewModel>,
60     private val processUpdatesInteractor: Lazy<ProcessTargetIntentUpdatesInteractor>,
61     private val fetchPreviewsInteractor: Lazy<FetchPreviewsInteractor>,
62     @Background private val bgDispatcher: CoroutineDispatcher,
63     /**
64      * Provided only for the express purpose of early exit in the event of an invalid request.
65      *
66      * Note: [request] can only be safely accessed after checking if this value is [Valid].
67      */
68     val initialRequest: ValidationResult<ChooserRequest>,
69     private val chooserRequestRepository: Lazy<ChooserRequestRepository>,
70     private val contentResolver: ContentInterface,
71     val imageLoader: ImageLoader,
72     private val interactiveSessionInteractorLazy: Lazy<InteractiveSessionInteractor>,
73 ) : ViewModel() {
74 
75     /** Parcelable-only references provided from the creating Activity */
76     val activityModel: ActivityModel = activityModelRepository.value
77 
78     val shareouselViewModel: ShareouselViewModel by lazy {
79         // TODO: consolidate this logic, this would require a consolidated preview view model but
80         //  for now just postpone starting the payload selection preview machinery until it's needed
81         viewModelScope.launch(bgDispatcher) { processUpdatesInteractor.get().activate() }
82         viewModelScope.launch(bgDispatcher) { fetchPreviewsInteractor.get().activate() }
83         shareouselViewModelProvider.get()
84     }
85 
86     /**
87      * A [StateFlow] of [ChooserRequest].
88      *
89      * Note: Only safe to access after checking if [initialRequest] is [Valid].
90      */
91     val request: StateFlow<ChooserRequest>
92         get() = chooserRequestRepository.get().chooserRequest.asStateFlow()
93 
94     val previewDataProvider by lazy {
95         val chooserRequest = (initialRequest as Valid<ChooserRequest>).value
96         PreviewDataProvider(
97             viewModelScope + bgDispatcher,
98             chooserRequest.targetIntent,
99             chooserRequest.additionalContentUri,
100             contentResolver,
101         )
102     }
103 
104     val interactiveSessionInteractor: InteractiveSessionInteractor
105         get() = interactiveSessionInteractorLazy.get()
106 
107     init {
108         when (initialRequest) {
109             is Invalid -> {
110                 Log.w(TAG, "initialRequest is Invalid, initialization failed")
111             }
112             is Valid<ChooserRequest> -> {
113                 if (saveShareouselState()) {
114                     val isRestored =
115                         savedStateHandle.get<Bundle>(CHOOSER_REQUEST_KEY)?.takeIf { !it.isEmpty } !=
116                             null
117                     savedStateHandle.setSavedStateProvider(CHOOSER_REQUEST_KEY) {
118                         Bundle().also { result ->
119                             request.value
120                                 .takeIf { isRestored || it != initialRequest.value }
121                                 ?.saveUpdates(result)
122                         }
123                     }
124                 }
125                 if (interactiveSession()) {
126                     viewModelScope.launch(bgDispatcher) { interactiveSessionInteractor.activate() }
127                 }
128             }
129         }
130     }
131 }
132