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