diff options
| author | 2024-02-05 12:18:13 -0500 | |
|---|---|---|
| committer | 2024-02-12 15:39:18 -0500 | |
| commit | a5162406bf48d155d3927c33e51aeee4368a24ff (patch) | |
| tree | f17de311ae2b6bdc2758d2ddca0ce3c0208e291f /java/src | |
| parent | 452240b2fa39855b059c3fe084362fd5b9d07ee1 (diff) | |
Additional Details for Sharesheet App Callbacks
Bug: 263474465
Test: atest ShareResultSenderImplTest
Change-Id: Icb61fa49dd2989cc50d7024da19d863e6c2fc189
Diffstat (limited to 'java/src')
5 files changed, 238 insertions, 23 deletions
diff --git a/java/src/com/android/intentresolver/v2/ChooserActionFactory.java b/java/src/com/android/intentresolver/v2/ChooserActionFactory.java index db840387..70a2b58e 100644 --- a/java/src/com/android/intentresolver/v2/ChooserActionFactory.java +++ b/java/src/com/android/intentresolver/v2/ChooserActionFactory.java @@ -40,6 +40,8 @@ import com.android.intentresolver.chooser.DisplayResolveInfo; import com.android.intentresolver.chooser.TargetInfo; import com.android.intentresolver.contentpreview.ChooserContentPreviewUi; import com.android.intentresolver.logging.EventLog; +import com.android.intentresolver.v2.ui.ShareResultSender; +import com.android.intentresolver.v2.ui.model.ShareAction; import com.android.intentresolver.widget.ActionRow; import com.android.internal.annotations.VisibleForTesting; @@ -97,12 +99,12 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio private final Context mContext; - @Nullable - private final Runnable mCopyButtonRunnable; - private final Runnable mEditButtonRunnable; + @Nullable private Runnable mCopyButtonRunnable; + private Runnable mEditButtonRunnable; private final ImmutableList<ChooserAction> mCustomActions; - private final @Nullable ChooserAction mModifyShareAction; + @Nullable private final ChooserAction mModifyShareAction; private final Consumer<Boolean> mExcludeSharedTextAction; + @Nullable private final ShareResultSender mShareResultSender; private final Consumer</* @Nullable */ Integer> mFinishCallback; private final EventLog mLog; @@ -122,12 +124,13 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio Intent targetIntent, String referrerPackageName, List<ChooserAction> chooserActions, - ChooserAction modifyShareAction, + @Nullable ChooserAction modifyShareAction, Optional<ComponentName> imageEditor, EventLog log, Consumer<Boolean> onUpdateSharedTextIsExcluded, Callable</* @Nullable */ View> firstVisibleImageQuery, ActionActivityStarter activityStarter, + @Nullable ShareResultSender shareResultSender, Consumer</* @Nullable */ Integer> finishCallback) { this( context, @@ -149,7 +152,9 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio modifyShareAction, onUpdateSharedTextIsExcluded, log, + shareResultSender, finishCallback); + } @VisibleForTesting @@ -161,6 +166,7 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio @Nullable ChooserAction modifyShareAction, Consumer<Boolean> onUpdateSharedTextIsExcluded, EventLog log, + @Nullable ShareResultSender shareResultSender, Consumer</* @Nullable */ Integer> finishCallback) { mContext = context; mCopyButtonRunnable = copyButtonRunnable; @@ -169,7 +175,22 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio mModifyShareAction = modifyShareAction; mExcludeSharedTextAction = onUpdateSharedTextIsExcluded; mLog = log; + mShareResultSender = shareResultSender; mFinishCallback = finishCallback; + + if (mShareResultSender != null) { + mEditButtonRunnable = () -> { + mShareResultSender.onActionSelected(ShareAction.SYSTEM_EDIT); + mEditButtonRunnable.run(); + }; + if (mCopyButtonRunnable != null) { + mCopyButtonRunnable = () -> { + mShareResultSender.onActionSelected(ShareAction.SYSTEM_COPY); + //noinspection DataFlowIssue + mCopyButtonRunnable.run(); + }; + } + } } @Override @@ -353,12 +374,12 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio } @Nullable - private static ActionRow.Action createCustomAction( + private ActionRow.Action createCustomAction( Context context, - ChooserAction action, + @Nullable ChooserAction action, Consumer<Integer> finishCallback, Runnable loggingRunnable) { - if (action == null || action.getAction() == null) { + if (action == null) { return null; } Drawable icon = action.getIcon().loadDrawable(context); @@ -388,6 +409,9 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio if (loggingRunnable != null) { loggingRunnable.run(); } + if (mShareResultSender != null) { + mShareResultSender.onActionSelected(ShareAction.APPLICATION_DEFINED); + } finishCallback.accept(Activity.RESULT_OK); } ); diff --git a/java/src/com/android/intentresolver/v2/ChooserActivity.java b/java/src/com/android/intentresolver/v2/ChooserActivity.java index 35812071..30845818 100644 --- a/java/src/com/android/intentresolver/v2/ChooserActivity.java +++ b/java/src/com/android/intentresolver/v2/ChooserActivity.java @@ -37,7 +37,6 @@ import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNullElse; -import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.ActivityThread; @@ -148,6 +147,8 @@ import com.android.intentresolver.v2.platform.AppPredictionAvailable; import com.android.intentresolver.v2.platform.ImageEditor; import com.android.intentresolver.v2.platform.NearbyShare; import com.android.intentresolver.v2.ui.ActionTitle; +import com.android.intentresolver.v2.ui.ShareResultSender; +import com.android.intentresolver.v2.ui.ShareResultSenderFactory; import com.android.intentresolver.v2.ui.model.ActivityLaunch; import com.android.intentresolver.v2.ui.model.ChooserRequest; import com.android.intentresolver.v2.ui.viewmodel.ChooserViewModel; @@ -275,6 +276,9 @@ public class ChooserActivity extends Hilt_ChooserActivity implements @Inject public DevicePolicyResources mDevicePolicyResources; @Inject public PackageManager mPackageManager; @Inject public IntentForwarding mIntentForwarding; + @Inject public ShareResultSenderFactory mShareResultSenderFactory; + @Nullable + private ShareResultSender mShareResultSender; private ChooserRefinementManager mRefinementManager; @@ -354,6 +358,12 @@ public class ChooserActivity extends Hilt_ChooserActivity implements finish(); return; } + IntentSender chosenComponentSender = + mViewModel.getChooserRequest().getChosenComponentSender(); + if (chosenComponentSender != null) { + mShareResultSender = mShareResultSenderFactory + .create(mActivityLaunch.getFromUid(), chosenComponentSender); + } mLogic = createActivityLogic(); init(); } @@ -819,13 +829,13 @@ public class ChooserActivity extends Hilt_ChooserActivity implements } try { if (cti.startAsCaller(this, options, user.getIdentifier())) { - onActivityStarted(cti); + maybeSendShareResult(cti); maybeLogCrossProfileTargetLaunch(cti, user); } } catch (RuntimeException e) { Slog.wtf(TAG, "Unable to launch as uid " + mActivityLaunch.getFromUid() - + " package " + getLaunchedFromPackage() + ", while running in " + + " package " + mActivityLaunch.getFromPackage() + ", while running in " + ActivityThread.currentProcessName(), e); } } @@ -1586,19 +1596,11 @@ public class ChooserActivity extends Hilt_ChooserActivity implements return result; } - public void onActivityStarted(TargetInfo cti) { - ChooserRequest chooserRequest = mViewModel.getChooserRequest(); - if (chooserRequest.getChosenComponentSender() != null) { + private void maybeSendShareResult(TargetInfo cti) { + if (mShareResultSender != null) { final ComponentName target = cti.getResolvedComponentName(); if (target != null) { - final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target); - try { - chooserRequest.getChosenComponentSender().sendIntent( - this, Activity.RESULT_OK, fillIn, null, null); - } catch (IntentSender.SendIntentException e) { - Slog.e(TAG, "Unable to launch supplied IntentSender to report " - + "the chosen component: " + e); - } + mShareResultSender.onComponentSelected(target, cti.isChooserTargetInfo()); } } } @@ -2121,6 +2123,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements mFinishWhenStopped = true; } }, + mShareResultSender, (status) -> { if (status != null) { setResult(status); diff --git a/java/src/com/android/intentresolver/v2/ui/ShareResultSender.kt b/java/src/com/android/intentresolver/v2/ui/ShareResultSender.kt new file mode 100644 index 00000000..2b01b5e7 --- /dev/null +++ b/java/src/com/android/intentresolver/v2/ui/ShareResultSender.kt @@ -0,0 +1,163 @@ +/* + * Copyright (C) 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.v2.ui + +import android.app.Activity +import android.app.compat.CompatChanges +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.IntentSender +import android.service.chooser.ChooserResult +import android.service.chooser.ChooserResult.CHOOSER_RESULT_COPY +import android.service.chooser.ChooserResult.CHOOSER_RESULT_EDIT +import android.service.chooser.ChooserResult.CHOOSER_RESULT_SELECTED_COMPONENT +import android.service.chooser.ChooserResult.CHOOSER_RESULT_UNKNOWN +import android.service.chooser.ChooserResult.ResultType +import android.util.Log +import com.android.intentresolver.inject.Background +import com.android.intentresolver.inject.ChooserServiceFlags +import com.android.intentresolver.inject.Main +import com.android.intentresolver.v2.ui.model.ShareAction +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import dagger.hilt.android.qualifiers.ActivityContext +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +private const val TAG = "ShareResultSender" + +/** Reports the result of a share to another process across binder, via an [IntentSender] */ +interface ShareResultSender { + /** Reports user selection of an activity to launch from the provided choices. */ + fun onComponentSelected(component: ComponentName, directShare: Boolean) + + /** Reports user invocation of a built-in system action. See [ShareAction]. */ + fun onActionSelected(action: ShareAction) +} + +@AssistedFactory +interface ShareResultSenderFactory { + fun create(callerUid: Int, chosenComponentSender: IntentSender): ShareResultSenderImpl +} + +/** Dispatches Intents via IntentSender */ +fun interface IntentSenderDispatcher { + fun dispatchIntent(intentSender: IntentSender, intent: Intent) +} + +class ShareResultSenderImpl( + private val flags: ChooserServiceFlags, + @Main private val scope: CoroutineScope, + @Background val backgroundDispatcher: CoroutineDispatcher, + private val callerUid: Int, + private val resultSender: IntentSender, + private val intentDispatcher: IntentSenderDispatcher +) : ShareResultSender { + @AssistedInject + constructor( + @ActivityContext context: Context, + flags: ChooserServiceFlags, + @Main scope: CoroutineScope, + @Background backgroundDispatcher: CoroutineDispatcher, + @Assisted callerUid: Int, + @Assisted chosenComponentSender: IntentSender, + ) : this( + flags, + scope, + backgroundDispatcher, + callerUid, + chosenComponentSender, + IntentSenderDispatcher { sender, intent -> sender.dispatchIntent(context, intent) } + ) + + override fun onComponentSelected(component: ComponentName, directShare: Boolean) { + Log.i(TAG, "onComponentSelected: $component directShare=$directShare") + scope.launch { + val intent = createChosenComponentIntent(component, directShare) + intentDispatcher.dispatchIntent(resultSender, intent) + } + } + + override fun onActionSelected(action: ShareAction) { + Log.i(TAG, "onActionSelected: $action") + scope.launch { + if (flags.enableChooserResult() && chooserResultSupported(callerUid)) { + @ResultType val chosenAction = shareActionToChooserResult(action) + val intent: Intent = createSelectedActionIntent(chosenAction) + intentDispatcher.dispatchIntent(resultSender, intent) + } else { + Log.i(TAG, "Not sending SelectedAction") + } + } + } + + private suspend fun createChosenComponentIntent( + component: ComponentName, + direct: Boolean, + ): Intent { + // Add extra with component name for backwards compatibility. + val intent: Intent = Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, component) + + // Add ChooserResult value for Android V+ + if (flags.enableChooserResult() && chooserResultSupported(callerUid)) { + intent.putExtra( + Intent.EXTRA_CHOOSER_RESULT, + ChooserResult(CHOOSER_RESULT_SELECTED_COMPONENT, component, direct) + ) + } else { + Log.i(TAG, "Not including ${Intent.EXTRA_CHOOSER_RESULT}") + } + return intent + } + + @ResultType + private fun shareActionToChooserResult(action: ShareAction) = + when (action) { + ShareAction.SYSTEM_COPY -> CHOOSER_RESULT_COPY + ShareAction.SYSTEM_EDIT -> CHOOSER_RESULT_EDIT + ShareAction.APPLICATION_DEFINED -> CHOOSER_RESULT_UNKNOWN + } + + private fun createSelectedActionIntent(@ResultType result: Int): Intent { + return Intent().putExtra(Intent.EXTRA_CHOOSER_RESULT, ChooserResult(result, null, false)) + } + + private suspend fun chooserResultSupported(uid: Int): Boolean { + return withContext(backgroundDispatcher) { + // background -> Binder call to system_server + CompatChanges.isChangeEnabled(ChooserResult.SEND_CHOOSER_RESULT, uid) + } + } +} + +private fun IntentSender.dispatchIntent(context: Context, intent: Intent) { + try { + sendIntent( + /* context = */ context, + /* code = */ Activity.RESULT_OK, + /* intent = */ intent, + /* onFinished = */ null, + /* handler = */ null + ) + } catch (e: IntentSender.SendIntentException) { + Log.e(TAG, "Failed to send intent to IntentSender", e) + } +} diff --git a/java/src/com/android/intentresolver/v2/ui/model/ShareAction.kt b/java/src/com/android/intentresolver/v2/ui/model/ShareAction.kt new file mode 100644 index 00000000..e13ef101 --- /dev/null +++ b/java/src/com/android/intentresolver/v2/ui/model/ShareAction.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 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.v2.ui.model + +enum class ShareAction { + SYSTEM_COPY, + SYSTEM_EDIT, + APPLICATION_DEFINED +} diff --git a/java/src/com/android/intentresolver/v2/ui/viewmodel/ChooserRequestReader.kt b/java/src/com/android/intentresolver/v2/ui/viewmodel/ChooserRequestReader.kt index cb1ef1ae..0269168e 100644 --- a/java/src/com/android/intentresolver/v2/ui/viewmodel/ChooserRequestReader.kt +++ b/java/src/com/android/intentresolver/v2/ui/viewmodel/ChooserRequestReader.kt @@ -23,6 +23,7 @@ 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 @@ -111,7 +112,8 @@ fun readChooserRequest( ?: emptyList() val chosenComponentSender = - optional(value<IntentSender>(EXTRA_CHOSEN_COMPONENT_INTENT_SENDER)) + optional(value<IntentSender>(EXTRA_CHOOSER_RESULT_INTENT_SENDER)) + ?: optional(value<IntentSender>(EXTRA_CHOSEN_COMPONENT_INTENT_SENDER)) val refinementIntentSender = optional(value<IntentSender>(EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER)) |