summaryrefslogtreecommitdiff
path: root/java/src
diff options
context:
space:
mode:
author Mark Renouf <mrenouf@google.com> 2024-02-05 12:18:13 -0500
committer Mark Renouf <mrenouf@google.com> 2024-02-12 15:39:18 -0500
commita5162406bf48d155d3927c33e51aeee4368a24ff (patch)
treef17de311ae2b6bdc2758d2ddca0ce3c0208e291f /java/src
parent452240b2fa39855b059c3fe084362fd5b9d07ee1 (diff)
Additional Details for Sharesheet App Callbacks
Bug: 263474465 Test: atest ShareResultSenderImplTest Change-Id: Icb61fa49dd2989cc50d7024da19d863e6c2fc189
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/intentresolver/v2/ChooserActionFactory.java40
-rw-r--r--java/src/com/android/intentresolver/v2/ChooserActivity.java31
-rw-r--r--java/src/com/android/intentresolver/v2/ui/ShareResultSender.kt163
-rw-r--r--java/src/com/android/intentresolver/v2/ui/model/ShareAction.kt23
-rw-r--r--java/src/com/android/intentresolver/v2/ui/viewmodel/ChooserRequestReader.kt4
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))