diff options
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) }, |