diff options
-rw-r--r-- | Android.bp | 3 | ||||
-rw-r--r-- | AndroidManifest.xml | 6 | ||||
-rw-r--r-- | java/src/com/android/intentresolver/ChooserActivity.java | 100 | ||||
-rw-r--r-- | java/src/com/android/intentresolver/ChooserHelper.java | 44 |
4 files changed, 134 insertions, 19 deletions
@@ -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); } } |