summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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);
}
}