diff options
| author | 2024-03-12 17:50:15 -0700 | |
|---|---|---|
| committer | 2024-03-14 14:02:17 -0700 | |
| commit | e3d2e850f9caeb4df9389ef40d17a650b7bb505a (patch) | |
| tree | 504f66105bd8d89e2ca3c781ca945eaf7234cd84 /java/src | |
| parent | 3f1677e45effe9a4b0a3d7c6b83cd6f6cfc00fcf (diff) | |
Update remaining payload change callback values
Moves ShareouselUpdate out of SelectionChangeCallback into the model
package. Receives result intent sender and metadata text from the
callback.
Propagate chooser requst changes from the callback to the
ChooserViewModel; update result sender in ChooserActivity.
Bug: 302691505
Test: IntentResolver-tests-unit
Test: manual test with the updated CTS-V test
Change-Id: I3b05cb35b94cb820c5f4fdc9fbc336dd072c36bd
Diffstat (limited to 'java/src')
6 files changed, 176 insertions, 27 deletions
diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/data/repository/ChooserParamsUpdateRepository.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/data/repository/ChooserParamsUpdateRepository.kt new file mode 100644 index 00000000..1a4f2b83 --- /dev/null +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/data/repository/ChooserParamsUpdateRepository.kt @@ -0,0 +1,34 @@ +/* + * 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 + * + * http://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.contentpreview.payloadtoggle.data.repository + +import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.ShareouselUpdate +import dagger.hilt.android.scopes.ViewModelScoped +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow + +/** Chooser parameters Updates received from the sharing application payload change callback */ +// TODO: a scaffolding repository to deliver chooser parameter updates before we developed some +// other, more thought-through solution. +@ViewModelScoped +class ChooserParamsUpdateRepository @Inject constructor() { + val updates = MutableStateFlow<ShareouselUpdate?>(null) + + fun setUpdates(update: ShareouselUpdate) { + updates.tryEmit(update) + } +} diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateTargetIntentInteractor.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateTargetIntentInteractor.kt index 4619e478..4cb1f5b6 100644 --- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateTargetIntentInteractor.kt +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateTargetIntentInteractor.kt @@ -18,6 +18,7 @@ package com.android.intentresolver.contentpreview.payloadtoggle.domain.interactor +import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.ChooserParamsUpdateRepository import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.PreviewSelectionsRepository import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.TargetIntentRepository import com.android.intentresolver.contentpreview.payloadtoggle.domain.intent.CustomAction @@ -30,6 +31,7 @@ import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.launch @@ -38,6 +40,7 @@ class UpdateTargetIntentInteractor @Inject constructor( private val intentRepository: TargetIntentRepository, + private val chooserParamsUpdateRepository: ChooserParamsUpdateRepository, @CustomAction private val pendingIntentSender: PendingIntentSender, private val selectionCallback: SelectionChangeCallback, private val selectionRepo: PreviewSelectionsRepository, @@ -47,12 +50,13 @@ constructor( suspend fun launch(): Unit = coroutineScope { launch { intentRepository.targetIntent - .mapLatest { targetIntent -> - selectionCallback.onSelectionChanged(targetIntent)?.customActions ?: emptyList() - } - .collect { actions -> + .mapLatest { targetIntent -> selectionCallback.onSelectionChanged(targetIntent) } + .filterNotNull() + .collect { updates -> + val actions = updates.customActions ?: emptyList() intentRepository.customActions.value = actions.map { it.toCustomActionModel(pendingIntentSender) } + chooserParamsUpdateRepository.setUpdates(updates) } } launch { diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/model/ShareouselUpdate.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/model/ShareouselUpdate.kt new file mode 100644 index 00000000..41a34d1a --- /dev/null +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/model/ShareouselUpdate.kt @@ -0,0 +1,34 @@ +/* + * 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 + * + * http://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.contentpreview.payloadtoggle.domain.model + +import android.content.Intent +import android.content.IntentSender +import android.service.chooser.ChooserAction +import android.service.chooser.ChooserTarget + +/** Sharing session updates provided by the sharing app from the payload change callback */ +data class ShareouselUpdate( + // for all properties, null value means no change + val customActions: List<ChooserAction>? = null, + val modifyShareAction: ChooserAction? = null, + val alternateIntents: List<Intent>? = null, + val callerTargets: List<ChooserTarget>? = null, + val refinementIntentSender: IntentSender? = null, + val resultIntentSender: IntentSender? = null, + val metadataText: CharSequence? = null, +) diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallback.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallback.kt index 03295a31..e7644dc5 100644 --- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallback.kt +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallback.kt @@ -20,17 +20,20 @@ import android.content.ContentInterface import android.content.Intent 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_INTENT +import android.content.Intent.EXTRA_METADATA_TEXT import android.content.IntentSender import android.net.Uri import android.os.Bundle import android.service.chooser.AdditionalContentContract.MethodNames.ON_SELECTION_CHANGED import android.service.chooser.ChooserAction import android.service.chooser.ChooserTarget -import com.android.intentresolver.contentpreview.payloadtoggle.domain.update.SelectionChangeCallback.ShareouselUpdate +import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.ShareouselUpdate import com.android.intentresolver.inject.AdditionalContent import com.android.intentresolver.inject.ChooserIntent +import com.android.intentresolver.inject.ChooserServiceFlags import com.android.intentresolver.v2.ui.viewmodel.readAlternateIntents import com.android.intentresolver.v2.ui.viewmodel.readChooserActions import com.android.intentresolver.v2.validation.Invalid @@ -56,15 +59,6 @@ private const val TAG = "SelectionChangeCallback" */ fun interface SelectionChangeCallback { suspend fun onSelectionChanged(targetIntent: Intent): ShareouselUpdate? - - data class ShareouselUpdate( - // for all properties, null value means no change - val customActions: List<ChooserAction>? = null, - val modifyShareAction: ChooserAction? = null, - val alternateIntents: List<Intent>? = null, - val callerTargets: List<ChooserTarget>? = null, - val refinementIntentSender: IntentSender? = null, - ) } class SelectionChangeCallbackImpl @@ -73,6 +67,7 @@ constructor( @AdditionalContent private val uri: Uri, @ChooserIntent private val chooserIntent: Intent, private val contentResolver: ContentInterface, + private val flags: ChooserServiceFlags, ) : SelectionChangeCallback { private val mutex = Mutex() @@ -92,7 +87,7 @@ constructor( ) } ?.let { bundle -> - return when (val result = readCallbackResponse(bundle)) { + return when (val result = readCallbackResponse(bundle, flags)) { is Valid -> result.value is Invalid -> { result.errors.forEach { it.log(TAG) } @@ -102,7 +97,10 @@ constructor( } } -private fun readCallbackResponse(bundle: Bundle): ValidationResult<ShareouselUpdate> { +private fun readCallbackResponse( + bundle: Bundle, + flags: ChooserServiceFlags +): ValidationResult<ShareouselUpdate> { return validateFrom(bundle::get) { val customActions = readChooserActions() val modifyShareAction = optional(value<ChooserAction>(EXTRA_CHOOSER_MODIFY_SHARE_ACTION)) @@ -110,6 +108,13 @@ private fun readCallbackResponse(bundle: Bundle): ValidationResult<ShareouselUpd val callerTargets = optional(array<ChooserTarget>(EXTRA_CHOOSER_TARGETS)) val refinementIntentSender = optional(value<IntentSender>(EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER)) + val resultIntentSender = optional(value<IntentSender>(EXTRA_CHOOSER_RESULT_INTENT_SENDER)) + val metadataText = + if (flags.enableSharesheetMetadataExtra()) { + optional(value<CharSequence>(EXTRA_METADATA_TEXT)) + } else { + null + } ShareouselUpdate( customActions, @@ -117,6 +122,8 @@ private fun readCallbackResponse(bundle: Bundle): ValidationResult<ShareouselUpd alternateIntents, callerTargets, refinementIntentSender, + resultIntentSender, + metadataText, ) } } diff --git a/java/src/com/android/intentresolver/v2/ChooserActivity.java b/java/src/com/android/intentresolver/v2/ChooserActivity.java index bda393b8..110983d0 100644 --- a/java/src/com/android/intentresolver/v2/ChooserActivity.java +++ b/java/src/com/android/intentresolver/v2/ChooserActivity.java @@ -490,11 +490,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements mLatencyTracker.onActionStart(ACTION_LOAD_SHARE_SHEET); mPinnedSharedPrefs = getPinnedSharedPrefs(this); - IntentSender chosenComponentSender = mRequest.getChosenComponentSender(); - if (chosenComponentSender != null) { - mShareResultSender = mShareResultSenderFactory.create( - mViewModel.getActivityModel().getLaunchedFromUid(), chosenComponentSender); - } + updateShareResultSender(); mMaxTargetsPerRow = getResources().getInteger(R.integer.config_chooser_max_targets_per_row); @@ -661,11 +657,37 @@ public class ChooserActivity extends Hilt_ChooserActivity implements } private void onChooserRequestChanged(ChooserRequest chooserRequest) { + // intentional reference comarison if (mRequest == chooserRequest) { return; } + boolean recreateAdapters = shouldUpdateAdapters(mRequest, chooserRequest); mRequest = chooserRequest; - recreatePagerAdapter(); + updateShareResultSender(); + if (recreateAdapters) { + recreatePagerAdapter(); + } + } + + private void updateShareResultSender() { + IntentSender chosenComponentSender = mRequest.getChosenComponentSender(); + if (chosenComponentSender != null) { + mShareResultSender = mShareResultSenderFactory.create( + mViewModel.getActivityModel().getLaunchedFromUid(), chosenComponentSender); + } else { + mShareResultSender = null; + } + } + + private boolean shouldUpdateAdapters( + ChooserRequest oldChooserRequest, ChooserRequest newChooserRequest) { + Intent oldTargetIntent = oldChooserRequest.getTargetIntent(); + Intent newTargetIntent = newChooserRequest.getTargetIntent(); + + // TODO: a workaround for the unnecessary target reloading caused by multiple flow updates - + // an artifact of the current implementation; revisit. + // reference comparison is intentional + return oldTargetIntent != newTargetIntent; } private void recreatePagerAdapter() { diff --git a/java/src/com/android/intentresolver/v2/domain/interactor/ChooserRequestUpdateInteractor.kt b/java/src/com/android/intentresolver/v2/domain/interactor/ChooserRequestUpdateInteractor.kt index 99da5c81..534b2be3 100644 --- a/java/src/com/android/intentresolver/v2/domain/interactor/ChooserRequestUpdateInteractor.kt +++ b/java/src/com/android/intentresolver/v2/domain/interactor/ChooserRequestUpdateInteractor.kt @@ -18,7 +18,9 @@ package com.android.intentresolver.v2.domain.interactor import android.content.Intent import android.util.Log +import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.ChooserParamsUpdateRepository import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.TargetIntentRepository +import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.ShareouselUpdate import com.android.intentresolver.inject.ChooserServiceFlags import com.android.intentresolver.inject.TargetIntent import com.android.intentresolver.v2.ui.model.ActivityModel @@ -30,8 +32,12 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import dagger.hilt.android.scopes.ViewModelScoped +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch private const val TAG = "ChooserRequestUpdate" @@ -43,17 +49,27 @@ constructor( private val activityModel: ActivityModel, @TargetIntent private val initialIntent: Intent, private val targetIntentRepository: TargetIntentRepository, + private val paramsUpdateRepository: ChooserParamsUpdateRepository, // TODO: replace with a proper repository, when available @Assisted private val chooserRequestRepository: MutableStateFlow<ChooserRequest>, private val flags: ChooserServiceFlags, ) { suspend fun launch() { - targetIntentRepository.targetIntent - // TODO: maybe find a better way to exclude the initial intent (as here it's compared by - // reference) - .filter { it !== initialIntent } - .collect(::updateTargetIntent) + coroutineScope { + launch { + targetIntentRepository.targetIntent + // TODO: maybe find a better way to exclude the initial intent (as here it's + // compared by + // reference) + .filter { it !== initialIntent } + .collect(::updateTargetIntent) + } + + launch { + paramsUpdateRepository.updates.filterNotNull().collect(::updateChooserParameters) + } + } } private fun updateTargetIntent(targetIntent: Intent) { @@ -64,6 +80,38 @@ constructor( } } + private fun updateChooserParameters(update: ShareouselUpdate) { + chooserRequestRepository.update { current -> + ChooserRequest( + current.targetIntent, + current.targetAction, + current.isSendActionTarget, + current.targetType, + current.launchedFromPackage, + current.title, + current.defaultTitleResource, + current.referrer, + current.filteredComponentNames, + update.callerTargets ?: current.callerChooserTargets, + // chooser actions are handled separately + current.chooserActions, + update.modifyShareAction ?: current.modifyShareAction, + current.shouldRetainInOnStop, + update.alternateIntents ?: current.additionalTargets, + current.replacementExtras, + current.initialIntents, + update.resultIntentSender ?: current.chosenComponentSender, + update.refinementIntentSender ?: current.refinementIntentSender, + current.sharedText, + current.shareTargetFilter, + current.additionalContentUri, + current.focusedItemPosition, + current.contentTypeHint, + update.metadataText ?: current.metadataText, + ) + } + } + private fun ActivityModel.updateWithTargetIntent(targetIntent: Intent) = ActivityModel( Intent(intent).apply { putExtra(Intent.EXTRA_INTENT, targetIntent) }, |