summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Joshua Trask <joshtrask@google.com> 2021-11-17 15:35:03 -0500
committer Joshua Trask <joshtrask@google.com> 2021-11-18 10:10:40 -0500
commit6538ea6be7d6406852e850bac734c508471e85b3 (patch)
treecba8dcdec1fd208507ff8ad334f405dbd56569f1
parent3767ec9d6980d35a7300996a2d077c77559598d4 (diff)
Derive unbundled Chooser from system version.
This is a shortcut to bootstrap the unbundling work by providing an architectural equivalent to the next "phase" of unbundling while we continue investigating (i.e., in in this version, the unbundled ChooserActivity is responsible for showing the Sharesheet UI, and then it launches the user's selection via the startActivityAsCaller API). For more information about: * The "unbundling" project: go/sharesheet-unbundling * The architecture of this next (third) unbundling phase: go/sharesheet-unbundling-phases * The motivation for starting with this temporary inheritance-based implementation: b/202165481 Test: Manually tested (in combination with system-side change ag/16285975). I had to disable the auto-selection logic to see any UI (b/206831012), but I was able to get a sharesheet activity from userspace, where I selected a target that then got launched. This is *not* at parity, but it's a baseline we can use when we investigate the remaining issues. Bug: 202165481 Change-Id: Ia2b89cc0e2735c8be7ad4cfc46c626a93e7fdee3
-rw-r--r--Android.bp3
-rw-r--r--AndroidManifest.xml6
-rw-r--r--java/src/com/android/intentresolver/ChooserActivity.java100
-rw-r--r--java/src/com/android/intentresolver/ChooserHelper.java44
4 files changed, 134 insertions, 19 deletions
diff --git a/Android.bp b/Android.bp
index 41354b49..2a620448 100644
--- a/Android.bp
+++ b/Android.bp
@@ -58,6 +58,9 @@ android_app {
min_sdk_version: "18",
certificate: "platform",
privileged: true,
+ required: [
+ "privapp_whitelist_com.android.intentresolver",
+ ],
srcs: ["src/**/*.java"],
platform_apis: true,
static_libs: [
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 38967403..e96326a2 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -22,6 +22,11 @@
android:versionName="2021-11"
coreApp="true">
+
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+ <uses-permission android:name="android.permission.MANAGE_APP_PREDICTIONS" />
+ <uses-permission android:name="android.permission.MANAGE_USERS" />
+ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.START_ACTIVITY_AS_CALLER" />
<application
@@ -32,7 +37,6 @@
android:supportsRtl="true">
<activity android:name=".ChooserActivity"
- android:theme="@*android:style/Theme.NoDisplay"
android:finishOnCloseSystemDialogs="true"
android:excludeFromRecents="true"
android:documentLaunchMode="never"
diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java
index 3bd05b8e..32455513 100644
--- a/java/src/com/android/intentresolver/ChooserActivity.java
+++ b/java/src/com/android/intentresolver/ChooserActivity.java
@@ -16,20 +16,112 @@
package com.android.intentresolver;
-import android.app.Activity;
+import android.app.ActivityTaskManager;
+import android.content.Intent;
import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
/**
* Activity for selecting which application ought to handle an ACTION_SEND intent.
+ *
+ * TODO: this temporary implementation inherits from the system-side ChooserActivity to avoid
+ * duplicating code while we're validating the feasibility of the unbundling plans. Architecturally,
+ * this is almost exactly equivalent to the "unbundling phase" in which the chooser UI is
+ * implemented in the new project. Once we can verify that this works at (or near) parity, we can
+ * copy over the source code of the original ChooserActivity instead of inheriting from it here, and
+ * then we'll be able to make divergent changes much more quickly. See TODO comments in this file
+ * for notes on performing that refactoring step.
*/
-public final class ChooserActivity extends Activity {
+public final class ChooserActivity extends com.android.internal.app.ChooserActivity {
private static final String TAG = "ChooserActivity";
+ private IBinder mPermissionToken;
+
+ /* TODO: the first section of this file contains overrides for ChooserActivity methods that need
+ * to be implemented differently in the delegated version. When the two classes are merged
+ * together, the implementations given here should replace the originals. Rationales for the
+ * replacements are provided in implementation comments (which could be removed later). */
+
+ /* The unbundled chooser needs to use the permission-token-based API to start activities. */
+ @Override
+ public boolean startAsCallerImpl(Intent intent, Bundle options, boolean ignoreTargetSecurity,
+ int userId) {
+ ChooserHelper.onTargetSelected(
+ this, intent, options, mPermissionToken, ignoreTargetSecurity, userId);
+ return true;
+ }
+
+ /* TODO: the remaining methods below include some implementation details specifically related to
+ * the temporary inheritance-based design, which may need to be removed or adapted when the two
+ * classes are merged together. */
+
@Override
protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
+ boolean shouldShowUi = processIntent();
+ if (shouldShowUi) {
+ super.onCreate(savedInstanceState);
+ } else {
+ super_onCreate(savedInstanceState); // Skip up to Activity::onCreate().
+ finish();
+ }
+ }
+
+ /**
+ * Process the intent that was used to launch the unbundled chooser, and return true if the
+ * chooser should continue to initialize as in the full Sharesheet UI, or false if the activity
+ * should exit immediately.
+ */
+ private boolean processIntent() {
+ mPermissionToken = getIntent().getExtras().getBinder(
+ ActivityTaskManager.EXTRA_PERMISSION_TOKEN);
+
+ if (mPermissionToken == null) {
+ Log.e(TAG, "No permission token to launch activities from chooser");
+ return false;
+ }
+
+ Intent delegatedIntent = getIntent().getParcelableExtra(Intent.EXTRA_INTENT);
+ if (delegatedIntent == null) {
+ Log.e(TAG, "No delegated intent");
+ return false;
+ }
+
+ if (tryToHandleAsDelegatedSelectionImmediately(delegatedIntent)) {
+ return false;
+ }
+
+ // This activity was launched from the system-side "springboard" component, and the embedded
+ // extra intent is the one that the calling app originally used to launch the system-side
+ // component. Treat it as if that's the intent that launched *this* activity directly (as we
+ // expect it to be when the unbundling migration is complete). This allows the superclass
+ // ChooserActivity implementation to see the same Intent data as it normally would in the
+ // system-side implementation.
+ setIntent(delegatedIntent);
+
+ return true;
+ }
+
+ /**
+ * Try to handle the delegated intent in the style of the earlier unbundled implementations,
+ * where the user has already selected a target and we're just supposed to dispatch it. Return
+ * whether this was an Intent that we were able to handle in this way.
+ *
+ * TODO: we don't need to continue to support this usage as we make more progress on the
+ * unbundling migration, but before we remove it we should double-check that there's no code
+ * path that might result in a client seeing the USE_DELEGATE_CHOOSER flag set to true in
+ * DisplayResolveInfo even though they decided not to hand off to the unbundled UI at onCreate.
+ */
+ private boolean tryToHandleAsDelegatedSelectionImmediately(Intent delegatedIntent) {
+
+ if (Intent.ACTION_CHOOSER.equals(delegatedIntent.getAction())) {
+ // It looks like we're being invoked for a full chooser, not just the selection.
+ return false;
+ }
+
+ Log.i(TAG, "Dispatching selection delegated from system chooser");
ChooserHelper.onChoose(this);
- finish();
+ return true;
}
}
diff --git a/java/src/com/android/intentresolver/ChooserHelper.java b/java/src/com/android/intentresolver/ChooserHelper.java
index 5e3be849..0edaf595 100644
--- a/java/src/com/android/intentresolver/ChooserHelper.java
+++ b/java/src/com/android/intentresolver/ChooserHelper.java
@@ -25,15 +25,38 @@ import android.os.StrictMode;
import androidx.annotation.VisibleForTesting;
-/**
- * When a target is chosen from the SystemUI Chooser activity, unpack its arguments and
- * startActivityAsCaller to handle the now-chosen intent.
- */
+/** Utilities for executing the action that the user selected from a Chooser UI. */
public class ChooserHelper {
private static final String TAG = "ChooserHelper";
- /** Dispatch the selected target indicated in the intent that started the provided Activity. */
+ /** Launch the user's selected target. */
+ static void onTargetSelected(
+ Activity activity,
+ Intent chosenIntent,
+ Bundle options,
+ IBinder permissionToken,
+ boolean ignoreTargetSecurity,
+ int userId) {
+
+ // We're dispatching intents that might be coming from legacy apps, so
+ // (as in com.android.internal.app.ResolverActivity) exempt ourselves from death.
+ StrictMode.disableDeathOnFileUriExposure();
+ try {
+ activity.startActivityAsCaller(
+ chosenIntent, options, permissionToken, ignoreTargetSecurity, userId);
+ } finally {
+ StrictMode.enableDeathOnFileUriExposure();
+ }
+ }
+
+ /**
+ * Launch a pre-selected target. In the earliest versions of the unbundled chooser, the user
+ * has already selected their target from a system-side ChooserActivity UI, and the selection
+ * was delegated to the current Activity to dispatch immediately. Unpack the arguments from the
+ * Intent that was sent from the system-side ChooserActivity for this kind of delegated
+ * dispatch, and launch the user's selected target using the startActivityAsCaller API.
+ */
@VisibleForTesting
public static void onChoose(Activity activity) {
final Intent thisIntent = activity.getIntent();
@@ -46,14 +69,7 @@ public class ChooserHelper {
thisIntent.getBooleanExtra(ActivityTaskManager.EXTRA_IGNORE_TARGET_SECURITY, false);
final int userId = thisIntent.getIntExtra(Intent.EXTRA_USER_ID, -1);
- // We're dispatching intents that might be coming from legacy apps, so
- // (as in com.android.internal.app.ResolverActivity) exempt ourselves from death.
- StrictMode.disableDeathOnFileUriExposure();
- try {
- activity.startActivityAsCaller(
- chosenIntent, options, permissionToken, ignoreTargetSecurity, userId);
- } finally {
- StrictMode.enableDeathOnFileUriExposure();
- }
+ onTargetSelected(
+ activity, chosenIntent, options, permissionToken, ignoreTargetSecurity, userId);
}
}