summaryrefslogtreecommitdiff
path: root/java/src
diff options
context:
space:
mode:
author Joshua Trask <joshtrask@google.com> 2024-04-29 13:12:08 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-04-29 13:12:08 +0000
commit0c4db34dedb1886c13c3324306c1713b3934bbcc (patch)
treedf76f78d13f21324709d0faf05f30fb3693de2a0 /java/src
parent1010748117b06dfd1682dff78929b676f9e02745 (diff)
parent411f9e83c490df04e1e2befd7577923ea7093675 (diff)
Merge "System action refinement." into main
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/intentresolver/ChooserActivity.java130
-rw-r--r--java/src/com/android/intentresolver/ChooserRefinementManager.java117
2 files changed, 201 insertions, 46 deletions
diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java
index 9328baf5..56873302 100644
--- a/java/src/com/android/intentresolver/ChooserActivity.java
+++ b/java/src/com/android/intentresolver/ChooserActivity.java
@@ -98,6 +98,7 @@ import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.ViewPager;
+import com.android.intentresolver.ChooserRefinementManager.RefinementType;
import com.android.intentresolver.chooser.DisplayResolveInfo;
import com.android.intentresolver.chooser.MultiDisplayResolveInfo;
import com.android.intentresolver.chooser.TargetInfo;
@@ -569,23 +570,52 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
mRefinementManager = new ViewModelProvider(this).get(ChooserRefinementManager.class);
mRefinementManager.getRefinementCompletion().observe(this, completion -> {
if (completion.consume()) {
- TargetInfo targetInfo = completion.getTargetInfo();
- // targetInfo is non-null if the refinement process was successful.
- if (targetInfo != null) {
- maybeRemoveSharedText(targetInfo);
-
- // We already block suspended targets from going to refinement, and we probably
- // can't recover a Chooser session if that's the reason the refined target fails
- // to launch now. Fire-and-forget the refined launch; ignore the return value
- // and just make sure the Sharesheet session gets cleaned up regardless.
- final ResolveInfo ri = targetInfo.getResolveInfo();
- final Intent intent1 = targetInfo.getResolvedIntent();
-
- safelyStartActivity(targetInfo);
-
- // Rely on the ActivityManager to pop up a dialog regarding app suspension
- // and return false
- targetInfo.isSuspended();
+ if (completion.getRefinedIntent() == null) {
+ finish();
+ return;
+ }
+
+ // Prepare to regenerate our "system actions" based on the refined intent.
+ // TODO: optimize if needed. `TARGET_INFO` cases don't require a new action
+ // factory at all. And if we break up `ChooserActionFactory`, we could avoid
+ // resolving a new editor intent unless we're handling an `EDIT_ACTION`.
+ ChooserActionFactory refinedActionFactory =
+ createChooserActionFactory(completion.getRefinedIntent());
+ switch (completion.getType()) {
+ case TARGET_INFO: {
+ TargetInfo refinedTarget = completion
+ .getOriginalTargetInfo()
+ .tryToCloneWithAppliedRefinement(
+ completion.getRefinedIntent());
+ if (refinedTarget == null) {
+ Log.e(TAG, "Failed to apply refinement to any matching source intent");
+ } else {
+ maybeRemoveSharedText(refinedTarget);
+
+ // We already block suspended targets from going to refinement, and we
+ // probably can't recover a Chooser session if that's the reason the
+ // refined target fails to launch now. Fire-and-forget the refined
+ // launch, and make sure Sharesheet gets cleaned up regardless of the
+ // outcome of that launch.launch; ignore
+
+ safelyStartActivity(refinedTarget);
+ }
+ }
+ break;
+
+ case COPY_ACTION: {
+ if (refinedActionFactory.getCopyButtonRunnable() != null) {
+ refinedActionFactory.getCopyButtonRunnable().run();
+ }
+ }
+ break;
+
+ case EDIT_ACTION: {
+ if (refinedActionFactory.getEditButtonRunnable() != null) {
+ refinedActionFactory.getEditButtonRunnable().run();
+ }
+ }
+ break;
}
finish();
@@ -598,12 +628,15 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
mRequest.getTargetIntent(),
mRequest.getAdditionalContentUri(),
mChooserServiceFeatureFlags.chooserPayloadToggling());
+ ChooserContentPreviewUi.ActionFactory actionFactory =
+ decorateActionFactoryWithRefinement(
+ createChooserActionFactory(mRequest.getTargetIntent()));
mChooserContentPreviewUi = new ChooserContentPreviewUi(
getCoroutineScope(getLifecycle()),
previewViewModel.getPreviewDataProvider(),
mRequest.getTargetIntent(),
previewViewModel.getImageLoader(),
- createChooserActionFactory(),
+ actionFactory,
createModifyShareActionFactory(),
mEnterTransitionAnimationDelegate,
new HeadlineGeneratorImpl(this),
@@ -2110,10 +2143,67 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
return PreviewViewModel.Companion.getFactory();
}
- private ChooserActionFactory createChooserActionFactory() {
+ private ChooserContentPreviewUi.ActionFactory decorateActionFactoryWithRefinement(
+ ChooserContentPreviewUi.ActionFactory originalFactory) {
+ if (!mFeatureFlags.refineSystemActions()) {
+ return originalFactory;
+ }
+
+ return new ChooserContentPreviewUi.ActionFactory() {
+ @Override
+ @Nullable
+ public Runnable getEditButtonRunnable() {
+ return () -> {
+ if (!mRefinementManager.maybeHandleSelection(
+ RefinementType.EDIT_ACTION,
+ List.of(mRequest.getTargetIntent()),
+ null,
+ mRequest.getRefinementIntentSender(),
+ getApplication(),
+ getMainThreadHandler())) {
+ originalFactory.getEditButtonRunnable().run();
+ }
+ };
+ }
+
+ @Override
+ @Nullable
+ public Runnable getCopyButtonRunnable() {
+ return () -> {
+ if (!mRefinementManager.maybeHandleSelection(
+ RefinementType.COPY_ACTION,
+ List.of(mRequest.getTargetIntent()),
+ null,
+ mRequest.getRefinementIntentSender(),
+ getApplication(),
+ getMainThreadHandler())) {
+ originalFactory.getCopyButtonRunnable().run();
+ }
+ };
+ }
+
+ @Override
+ public List<ActionRow.Action> createCustomActions() {
+ return originalFactory.createCustomActions();
+ }
+
+ @Override
+ @Nullable
+ public ActionRow.Action getModifyShareAction() {
+ return originalFactory.getModifyShareAction();
+ }
+
+ @Override
+ public Consumer<Boolean> getExcludeSharedTextAction() {
+ return originalFactory.getExcludeSharedTextAction();
+ }
+ };
+ }
+
+ private ChooserActionFactory createChooserActionFactory(Intent targetIntent) {
return new ChooserActionFactory(
this,
- mRequest.getTargetIntent(),
+ targetIntent,
mRequest.getLaunchedFromPackage(),
mRequest.getChooserActions(),
mImageEditor,
diff --git a/java/src/com/android/intentresolver/ChooserRefinementManager.java b/java/src/com/android/intentresolver/ChooserRefinementManager.java
index 79484240..5c828a8e 100644
--- a/java/src/com/android/intentresolver/ChooserRefinementManager.java
+++ b/java/src/com/android/intentresolver/ChooserRefinementManager.java
@@ -59,22 +59,58 @@ public final class ChooserRefinementManager extends ViewModel {
private boolean mConfigurationChangeInProgress = false;
/**
+ * The types of selections that may be sent to refinement.
+ *
+ * The refinement flow results in a refined intent, but the interpretation of that intent
+ * depends on the type of selection that prompted the refinement.
+ */
+ public enum RefinementType {
+ TARGET_INFO, // A normal (`TargetInfo`) target.
+
+ // System actions derived from the refined intent (from `ChooserActionFactory`).
+ COPY_ACTION,
+ EDIT_ACTION
+ }
+
+ /**
* A token for the completion of a refinement process that can be consumed exactly once.
*/
public static class RefinementCompletion {
private TargetInfo mTargetInfo;
private boolean mConsumed;
+ private final RefinementType mType;
- RefinementCompletion(TargetInfo targetInfo) {
- mTargetInfo = targetInfo;
+ @Nullable
+ private final TargetInfo mOriginalTargetInfo;
+
+ @Nullable
+ private final Intent mRefinedIntent;
+
+ RefinementCompletion(
+ @Nullable RefinementType type,
+ @Nullable TargetInfo originalTargetInfo,
+ @Nullable Intent refinedIntent) {
+ mType = type;
+ mOriginalTargetInfo = originalTargetInfo;
+ mRefinedIntent = refinedIntent;
+ }
+
+ public RefinementType getType() {
+ return mType;
+ }
+
+ @Nullable
+ public TargetInfo getOriginalTargetInfo() {
+ return mOriginalTargetInfo;
}
/**
* @return The output of the completed refinement process. Null if the process was aborted
* or failed.
*/
- public TargetInfo getTargetInfo() {
- return mTargetInfo;
+ @Nullable
+ public Intent getRefinedIntent() {
+ return mRefinedIntent;
}
/**
@@ -105,14 +141,11 @@ public final class ChooserRefinementManager extends ViewModel {
* @return true if the selection should wait for a now-started refinement flow, or false if it
* can proceed by the default (non-refinement) logic.
*/
- public boolean maybeHandleSelection(TargetInfo selectedTarget,
- IntentSender refinementIntentSender, Application application, Handler mainHandler) {
- if (refinementIntentSender == null) {
- return false;
- }
- if (selectedTarget.getAllSourceIntents().isEmpty()) {
- return false;
- }
+ public boolean maybeHandleSelection(
+ TargetInfo selectedTarget,
+ IntentSender refinementIntentSender,
+ Application application,
+ Handler mainHandler) {
if (selectedTarget.isSuspended()) {
// We expect all launches to fail for this target, so don't make the user go through the
// refinement flow first. Besides, the default (non-refinement) handling displays a
@@ -121,27 +154,57 @@ public final class ChooserRefinementManager extends ViewModel {
return false;
}
+ return maybeHandleSelection(
+ RefinementType.TARGET_INFO,
+ selectedTarget.getAllSourceIntents(),
+ selectedTarget,
+ refinementIntentSender,
+ application,
+ mainHandler);
+ }
+
+ /**
+ * Delegate the user's selection of targets (with one or more matching {@code sourceIntents} to
+ * the refinement flow, if possible.
+ * @return true if the selection should wait for a now-started refinement flow, or false if it
+ * can proceed by the default (non-refinement) logic.
+ */
+ public boolean maybeHandleSelection(
+ RefinementType refinementType,
+ List<Intent> sourceIntents,
+ @Nullable TargetInfo originalTargetInfo,
+ IntentSender refinementIntentSender,
+ Application application,
+ Handler mainHandler) {
+ // Our requests have a non-null `originalTargetInfo` in exactly the
+ // cases when `refinementType == TARGET_INFO`.
+ assert ((originalTargetInfo == null) == (refinementType == RefinementType.TARGET_INFO));
+
+ if (refinementIntentSender == null) {
+ return false;
+ }
+ if (sourceIntents.isEmpty()) {
+ return false;
+ }
+
destroy(); // Terminate any prior sessions.
mRefinementResultReceiver = new RefinementResultReceiver(
+ refinementType,
refinedIntent -> {
destroy();
-
- TargetInfo refinedTarget =
- selectedTarget.tryToCloneWithAppliedRefinement(refinedIntent);
- if (refinedTarget != null) {
- mRefinementCompletion.setValue(new RefinementCompletion(refinedTarget));
- } else {
- Log.e(TAG, "Failed to apply refinement to any matching source intent");
- mRefinementCompletion.setValue(new RefinementCompletion(null));
- }
+ mRefinementCompletion.setValue(
+ new RefinementCompletion(
+ refinementType, originalTargetInfo, refinedIntent));
},
() -> {
destroy();
- mRefinementCompletion.setValue(new RefinementCompletion(null));
+ mRefinementCompletion.setValue(
+ new RefinementCompletion(
+ refinementType, originalTargetInfo, null));
},
mainHandler);
- Intent refinementRequest = makeRefinementRequest(mRefinementResultReceiver, selectedTarget);
+ Intent refinementRequest = makeRefinementRequest(mRefinementResultReceiver, sourceIntents);
try {
refinementIntentSender.sendIntent(application, 0, refinementRequest, null, null);
return true;
@@ -167,7 +230,7 @@ public final class ChooserRefinementManager extends ViewModel {
// into a valid Chooser session, so we'll treat it as a cancellation instead.
Log.w(TAG, "Chooser resumed while awaiting refinement result; aborting");
destroy();
- mRefinementCompletion.setValue(new RefinementCompletion(null));
+ mRefinementCompletion.setValue(new RefinementCompletion(null, null, null));
}
}
}
@@ -187,9 +250,8 @@ public final class ChooserRefinementManager extends ViewModel {
}
private static Intent makeRefinementRequest(
- RefinementResultReceiver resultReceiver, TargetInfo originalTarget) {
+ RefinementResultReceiver resultReceiver, List<Intent> sourceIntents) {
final Intent fillIn = new Intent();
- final List<Intent> sourceIntents = originalTarget.getAllSourceIntents();
fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0));
final int sourceIntentCount = sourceIntents.size();
if (sourceIntentCount > 1) {
@@ -204,16 +266,19 @@ public final class ChooserRefinementManager extends ViewModel {
}
private static class RefinementResultReceiver extends ResultReceiver {
+ private final RefinementType mType;
private final Consumer<Intent> mOnSelectionRefined;
private final Runnable mOnRefinementCancelled;
private boolean mDestroyed;
RefinementResultReceiver(
+ RefinementType type,
Consumer<Intent> onSelectionRefined,
Runnable onRefinementCancelled,
Handler handler) {
super(handler);
+ mType = type;
mOnSelectionRefined = onSelectionRefined;
mOnRefinementCancelled = onRefinementCancelled;
}