diff options
author | 2024-08-26 21:16:57 -0700 | |
---|---|---|
committer | 2024-10-16 13:10:09 -0700 | |
commit | 971b4d127f288ee179f1f35f9e7d200ba961797a (patch) | |
tree | a007e7f020553ec7eb15d7d207fe772becf713b8 /java/src/com | |
parent | 66c6c352139fbfcf89951ea4d8e93b92d07609ca (diff) |
Save Shareousel state.
Fix: 362347212
Test: manual testing with injected debug logging
Flag: com.android.intentresolver.save_shareousel_state
Change-Id: Ibe393e84c0d7884fb1b7611e72df0c7779afce34
Diffstat (limited to 'java/src/com')
5 files changed, 123 insertions, 29 deletions
diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateChooserRequestInteractor.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateChooserRequestInteractor.kt index 4fe5e8d5..fc193eca 100644 --- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateChooserRequestInteractor.kt +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateChooserRequestInteractor.kt @@ -17,14 +17,13 @@ package com.android.intentresolver.contentpreview.payloadtoggle.domain.interactor import android.content.Intent -import com.android.intentresolver.Flags.shareouselUpdateExcludeComponentsExtra import com.android.intentresolver.contentpreview.payloadtoggle.domain.intent.CustomAction import com.android.intentresolver.contentpreview.payloadtoggle.domain.intent.PendingIntentSender import com.android.intentresolver.contentpreview.payloadtoggle.domain.intent.toCustomActionModel import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.ShareouselUpdate -import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.getOrDefault import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.onValue import com.android.intentresolver.data.repository.ChooserRequestRepository +import com.android.intentresolver.domain.updateWith import javax.inject.Inject import kotlinx.coroutines.flow.update @@ -36,28 +35,7 @@ constructor( @CustomAction private val pendingIntentSender: PendingIntentSender, ) { fun applyUpdate(targetIntent: Intent, update: ShareouselUpdate) { - repository.chooserRequest.update { current -> - current.copy( - targetIntent = targetIntent, - callerChooserTargets = - update.callerTargets.getOrDefault(current.callerChooserTargets), - modifyShareAction = - update.modifyShareAction.getOrDefault(current.modifyShareAction), - additionalTargets = update.alternateIntents.getOrDefault(current.additionalTargets), - chosenComponentSender = - update.resultIntentSender.getOrDefault(current.chosenComponentSender), - refinementIntentSender = - update.refinementIntentSender.getOrDefault(current.refinementIntentSender), - metadataText = update.metadataText.getOrDefault(current.metadataText), - chooserActions = update.customActions.getOrDefault(current.chooserActions), - filteredComponentNames = - if (shareouselUpdateExcludeComponentsExtra()) { - update.excludeComponents.getOrDefault(current.filteredComponentNames) - } else { - current.filteredComponentNames - } - ) - } + repository.chooserRequest.update { it.updateWith(targetIntent, update) } update.customActions.onValue { actions -> repository.customActions.value = actions.map { it.toCustomActionModel(pendingIntentSender) } diff --git a/java/src/com/android/intentresolver/domain/ChooserRequestExt.kt b/java/src/com/android/intentresolver/domain/ChooserRequestExt.kt new file mode 100644 index 00000000..5ca3ad20 --- /dev/null +++ b/java/src/com/android/intentresolver/domain/ChooserRequestExt.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.domain + +import android.content.Intent +import android.content.Intent.EXTRA_ALTERNATE_INTENTS +import android.content.Intent.EXTRA_CHOOSER_CUSTOM_ACTIONS +import android.content.Intent.EXTRA_CHOOSER_MODIFY_SHARE_ACTION +import android.content.Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER +import android.content.Intent.EXTRA_CHOOSER_RESULT_INTENT_SENDER +import android.content.Intent.EXTRA_CHOOSER_TARGETS +import android.content.Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER +import android.content.Intent.EXTRA_EXCLUDE_COMPONENTS +import android.content.Intent.EXTRA_INTENT +import android.content.Intent.EXTRA_METADATA_TEXT +import android.os.Bundle +import com.android.intentresolver.Flags.shareouselUpdateExcludeComponentsExtra +import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.ShareouselUpdate +import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.getOrDefault +import com.android.intentresolver.data.model.ChooserRequest + +/** Creates a new ChooserRequest with the target intent and updates from a Shareousel callback */ +fun ChooserRequest.updateWith(targetIntent: Intent, update: ShareouselUpdate): ChooserRequest = + copy( + targetIntent = targetIntent, + callerChooserTargets = update.callerTargets.getOrDefault(callerChooserTargets), + modifyShareAction = update.modifyShareAction.getOrDefault(modifyShareAction), + additionalTargets = update.alternateIntents.getOrDefault(additionalTargets), + chosenComponentSender = update.resultIntentSender.getOrDefault(chosenComponentSender), + refinementIntentSender = update.refinementIntentSender.getOrDefault(refinementIntentSender), + metadataText = update.metadataText.getOrDefault(metadataText), + chooserActions = update.customActions.getOrDefault(chooserActions), + filteredComponentNames = + if (shareouselUpdateExcludeComponentsExtra()) { + update.excludeComponents.getOrDefault(filteredComponentNames) + } else { + filteredComponentNames + }, + ) + +/** Save ChooserRequest values that can be updated by the Shareousel into a Bundle */ +fun ChooserRequest.saveUpdates(bundle: Bundle): Bundle { + bundle.putParcelable(EXTRA_INTENT, targetIntent) + bundle.putParcelableArray(EXTRA_CHOOSER_TARGETS, callerChooserTargets.toTypedArray()) + bundle.putParcelable(EXTRA_CHOOSER_MODIFY_SHARE_ACTION, modifyShareAction) + bundle.putParcelableArray(EXTRA_ALTERNATE_INTENTS, additionalTargets.toTypedArray()) + bundle.putParcelable(EXTRA_CHOOSER_RESULT_INTENT_SENDER, chosenComponentSender) + bundle.putParcelable(EXTRA_CHOSEN_COMPONENT_INTENT_SENDER, chosenComponentSender) + bundle.putParcelable(EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER, refinementIntentSender) + bundle.putCharSequence(EXTRA_METADATA_TEXT, metadataText) + bundle.putParcelableArray(EXTRA_CHOOSER_CUSTOM_ACTIONS, chooserActions.toTypedArray()) + if (shareouselUpdateExcludeComponentsExtra()) { + bundle.putParcelableArray(EXTRA_EXCLUDE_COMPONENTS, filteredComponentNames.toTypedArray()) + } + return bundle +} diff --git a/java/src/com/android/intentresolver/inject/ActivityModelModule.kt b/java/src/com/android/intentresolver/inject/ActivityModelModule.kt index 7201bd2b..5b92c05b 100644 --- a/java/src/com/android/intentresolver/inject/ActivityModelModule.kt +++ b/java/src/com/android/intentresolver/inject/ActivityModelModule.kt @@ -18,9 +18,13 @@ package com.android.intentresolver.inject import android.content.Intent import android.net.Uri +import android.os.Bundle import android.service.chooser.ChooserAction +import androidx.lifecycle.SavedStateHandle +import com.android.intentresolver.Flags.saveShareouselState import com.android.intentresolver.data.model.ChooserRequest import com.android.intentresolver.data.repository.ActivityModelRepository +import com.android.intentresolver.ui.viewmodel.CHOOSER_REQUEST_KEY import com.android.intentresolver.ui.viewmodel.readChooserRequest import com.android.intentresolver.util.ownedByCurrentUser import com.android.intentresolver.validation.Valid @@ -44,8 +48,13 @@ object ActivityModelModule { @ViewModelScoped fun provideInitialRequest( activityModelRepo: ActivityModelRepository, + savedStateHandle: SavedStateHandle, flags: ChooserServiceFlags, - ): ValidationResult<ChooserRequest> = readChooserRequest(activityModelRepo.value, flags) + ): ValidationResult<ChooserRequest> { + val activityModel = activityModelRepo.value + val extras = restoreChooserRequestExtras(activityModel.intent.extras, savedStateHandle) + return readChooserRequest(activityModel, flags, extras) + } @Provides fun provideChooserRequest(initialRequest: ValidationResult<ChooserRequest>): ChooserRequest = @@ -117,3 +126,18 @@ private val Intent.contentUris: Sequence<Uri> } } } + +private fun restoreChooserRequestExtras( + initialExtras: Bundle?, + savedStateHandle: SavedStateHandle, +): Bundle = + if (saveShareouselState()) { + savedStateHandle.get<Bundle>(CHOOSER_REQUEST_KEY)?.let { savedSateBundle -> + Bundle().apply { + initialExtras?.let { putAll(it) } + putAll(savedSateBundle) + } + } ?: initialExtras + } else { + initialExtras + } ?: Bundle() diff --git a/java/src/com/android/intentresolver/ui/viewmodel/ChooserRequestReader.kt b/java/src/com/android/intentresolver/ui/viewmodel/ChooserRequestReader.kt index 13cadf37..846cae9e 100644 --- a/java/src/com/android/intentresolver/ui/viewmodel/ChooserRequestReader.kt +++ b/java/src/com/android/intentresolver/ui/viewmodel/ChooserRequestReader.kt @@ -70,10 +70,10 @@ internal fun Intent.maybeAddSendActionFlags() = fun readChooserRequest( model: ActivityModel, flags: ChooserServiceFlags, + savedState: Bundle = model.intent.extras ?: Bundle(), ): ValidationResult<ChooserRequest> { - val extras = model.intent.extras ?: Bundle() @Suppress("DEPRECATION") - return validateFrom(extras::get) { + return validateFrom(savedState::get) { val targetIntent = required(IntentOrUri(EXTRA_INTENT)).maybeAddSendActionFlags() val isSendAction = targetIntent.hasSendAction() diff --git a/java/src/com/android/intentresolver/ui/viewmodel/ChooserViewModel.kt b/java/src/com/android/intentresolver/ui/viewmodel/ChooserViewModel.kt index fe7e9109..2292a63c 100644 --- a/java/src/com/android/intentresolver/ui/viewmodel/ChooserViewModel.kt +++ b/java/src/com/android/intentresolver/ui/viewmodel/ChooserViewModel.kt @@ -16,9 +16,12 @@ package com.android.intentresolver.ui.viewmodel import android.content.ContentInterface +import android.os.Bundle import android.util.Log +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.android.intentresolver.Flags.saveShareouselState import com.android.intentresolver.contentpreview.ImageLoader import com.android.intentresolver.contentpreview.PreviewDataProvider import com.android.intentresolver.contentpreview.payloadtoggle.domain.interactor.FetchPreviewsInteractor @@ -27,6 +30,7 @@ import com.android.intentresolver.contentpreview.payloadtoggle.ui.viewmodel.Shar import com.android.intentresolver.data.model.ChooserRequest import com.android.intentresolver.data.repository.ActivityModelRepository import com.android.intentresolver.data.repository.ChooserRequestRepository +import com.android.intentresolver.domain.saveUpdates import com.android.intentresolver.inject.Background import com.android.intentresolver.inject.ChooserServiceFlags import com.android.intentresolver.shared.model.ActivityModel @@ -43,11 +47,13 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.plus private const val TAG = "ChooserViewModel" +const val CHOOSER_REQUEST_KEY = "chooser-request" @HiltViewModel class ChooserViewModel @Inject constructor( + savedStateHandle: SavedStateHandle, activityModelRepository: ActivityModelRepository, private val shareouselViewModelProvider: Lazy<ShareouselViewModel>, private val processUpdatesInteractor: Lazy<ProcessTargetIntentUpdatesInteractor>, @@ -99,8 +105,24 @@ constructor( } init { - if (initialRequest is Invalid) { - Log.w(TAG, "initialRequest is Invalid, initialization failed") + when (initialRequest) { + is Invalid -> { + Log.w(TAG, "initialRequest is Invalid, initialization failed") + } + is Valid<ChooserRequest> -> { + if (saveShareouselState()) { + val isRestored = + savedStateHandle.get<Bundle>(CHOOSER_REQUEST_KEY)?.takeIf { !it.isEmpty } != + null + savedStateHandle.setSavedStateProvider(CHOOSER_REQUEST_KEY) { + Bundle().also { result -> + request.value + .takeIf { isRestored || it != initialRequest.value } + ?.saveUpdates(result) + } + } + } + } } } } |