diff options
| -rw-r--r-- | api/current.txt | 4 | ||||
| -rw-r--r-- | api/system-current.txt | 4 | ||||
| -rw-r--r-- | api/test-current.txt | 4 | ||||
| -rw-r--r-- | core/java/android/content/Intent.java | 25 | ||||
| -rw-r--r-- | core/java/com/android/internal/app/ChooserActivity.java | 79 | ||||
| -rw-r--r-- | core/java/com/android/internal/app/ResolverActivity.java | 22 |
6 files changed, 122 insertions, 16 deletions
diff --git a/api/current.txt b/api/current.txt index 1c4f85bc7abe..8297988a83b2 100644 --- a/api/current.txt +++ b/api/current.txt @@ -8685,6 +8685,7 @@ package android.content { field public static final java.lang.String EXTRA_CHANGED_PACKAGE_LIST = "android.intent.extra.changed_package_list"; field public static final java.lang.String EXTRA_CHANGED_UID_LIST = "android.intent.extra.changed_uid_list"; field public static final java.lang.String EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER = "android.intent.extra.CHOOSER_REFINEMENT_INTENT_SENDER"; + field public static final java.lang.String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS"; field public static final java.lang.String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT"; field public static final java.lang.String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER"; field public static final java.lang.String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED"; @@ -8696,6 +8697,7 @@ package android.content { field public static final int EXTRA_DOCK_STATE_UNDOCKED = 0; // 0x0 field public static final java.lang.String EXTRA_DONT_KILL_APP = "android.intent.extra.DONT_KILL_APP"; field public static final java.lang.String EXTRA_EMAIL = "android.intent.extra.EMAIL"; + field public static final java.lang.String EXTRA_EXCLUDE_COMPONENTS = "android.intent.extra.EXCLUDE_COMPONENTS"; field public static final java.lang.String EXTRA_HTML_TEXT = "android.intent.extra.HTML_TEXT"; field public static final java.lang.String EXTRA_INDEX = "android.intent.extra.INDEX"; field public static final java.lang.String EXTRA_INITIAL_INTENTS = "android.intent.extra.INITIAL_INTENTS"; @@ -51977,7 +51979,6 @@ package java.lang.reflect { public final class Method extends java.lang.reflect.AccessibleObject implements java.lang.reflect.GenericDeclaration java.lang.reflect.Member { method public boolean equals(java.lang.Object); method public A getAnnotation(java.lang.Class<A>); - method public java.lang.annotation.Annotation[] getDeclaredAnnotations(); method public java.lang.Class<?> getDeclaringClass(); method public java.lang.Object getDefaultValue(); method public java.lang.Class<?>[] getExceptionTypes(); @@ -51991,7 +51992,6 @@ package java.lang.reflect { method public java.lang.Class<?> getReturnType(); method public java.lang.reflect.TypeVariable<java.lang.reflect.Method>[] getTypeParameters(); method public java.lang.Object invoke(java.lang.Object, java.lang.Object...) throws java.lang.IllegalAccessException, java.lang.IllegalArgumentException, java.lang.reflect.InvocationTargetException; - method public boolean isAnnotationPresent(java.lang.Class<? extends java.lang.annotation.Annotation>); method public boolean isBridge(); method public boolean isDefault(); method public boolean isSynthetic(); diff --git a/api/system-current.txt b/api/system-current.txt index 039b9adc24a2..19d4d3217d39 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -9004,6 +9004,7 @@ package android.content { field public static final java.lang.String EXTRA_CHANGED_PACKAGE_LIST = "android.intent.extra.changed_package_list"; field public static final java.lang.String EXTRA_CHANGED_UID_LIST = "android.intent.extra.changed_uid_list"; field public static final java.lang.String EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER = "android.intent.extra.CHOOSER_REFINEMENT_INTENT_SENDER"; + field public static final java.lang.String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS"; field public static final java.lang.String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT"; field public static final java.lang.String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER"; field public static final java.lang.String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED"; @@ -9017,6 +9018,7 @@ package android.content { field public static final java.lang.String EXTRA_EMAIL = "android.intent.extra.EMAIL"; field public static final java.lang.String EXTRA_EPHEMERAL_FAILURE = "android.intent.extra.EPHEMERAL_FAILURE"; field public static final java.lang.String EXTRA_EPHEMERAL_SUCCESS = "android.intent.extra.EPHEMERAL_SUCCESS"; + field public static final java.lang.String EXTRA_EXCLUDE_COMPONENTS = "android.intent.extra.EXCLUDE_COMPONENTS"; field public static final java.lang.String EXTRA_HTML_TEXT = "android.intent.extra.HTML_TEXT"; field public static final java.lang.String EXTRA_INDEX = "android.intent.extra.INDEX"; field public static final java.lang.String EXTRA_INITIAL_INTENTS = "android.intent.extra.INITIAL_INTENTS"; @@ -55074,7 +55076,6 @@ package java.lang.reflect { public final class Method extends java.lang.reflect.AccessibleObject implements java.lang.reflect.GenericDeclaration java.lang.reflect.Member { method public boolean equals(java.lang.Object); method public A getAnnotation(java.lang.Class<A>); - method public java.lang.annotation.Annotation[] getDeclaredAnnotations(); method public java.lang.Class<?> getDeclaringClass(); method public java.lang.Object getDefaultValue(); method public java.lang.Class<?>[] getExceptionTypes(); @@ -55088,7 +55089,6 @@ package java.lang.reflect { method public java.lang.Class<?> getReturnType(); method public java.lang.reflect.TypeVariable<java.lang.reflect.Method>[] getTypeParameters(); method public java.lang.Object invoke(java.lang.Object, java.lang.Object...) throws java.lang.IllegalAccessException, java.lang.IllegalArgumentException, java.lang.reflect.InvocationTargetException; - method public boolean isAnnotationPresent(java.lang.Class<? extends java.lang.annotation.Annotation>); method public boolean isBridge(); method public boolean isDefault(); method public boolean isSynthetic(); diff --git a/api/test-current.txt b/api/test-current.txt index 3febda193f3d..5c975cf73a49 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -8692,6 +8692,7 @@ package android.content { field public static final java.lang.String EXTRA_CHANGED_PACKAGE_LIST = "android.intent.extra.changed_package_list"; field public static final java.lang.String EXTRA_CHANGED_UID_LIST = "android.intent.extra.changed_uid_list"; field public static final java.lang.String EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER = "android.intent.extra.CHOOSER_REFINEMENT_INTENT_SENDER"; + field public static final java.lang.String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS"; field public static final java.lang.String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT"; field public static final java.lang.String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER"; field public static final java.lang.String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED"; @@ -8703,6 +8704,7 @@ package android.content { field public static final int EXTRA_DOCK_STATE_UNDOCKED = 0; // 0x0 field public static final java.lang.String EXTRA_DONT_KILL_APP = "android.intent.extra.DONT_KILL_APP"; field public static final java.lang.String EXTRA_EMAIL = "android.intent.extra.EMAIL"; + field public static final java.lang.String EXTRA_EXCLUDE_COMPONENTS = "android.intent.extra.EXCLUDE_COMPONENTS"; field public static final java.lang.String EXTRA_HTML_TEXT = "android.intent.extra.HTML_TEXT"; field public static final java.lang.String EXTRA_INDEX = "android.intent.extra.INDEX"; field public static final java.lang.String EXTRA_INITIAL_INTENTS = "android.intent.extra.INITIAL_INTENTS"; @@ -52053,7 +52055,6 @@ package java.lang.reflect { public final class Method extends java.lang.reflect.AccessibleObject implements java.lang.reflect.GenericDeclaration java.lang.reflect.Member { method public boolean equals(java.lang.Object); method public A getAnnotation(java.lang.Class<A>); - method public java.lang.annotation.Annotation[] getDeclaredAnnotations(); method public java.lang.Class<?> getDeclaringClass(); method public java.lang.Object getDefaultValue(); method public java.lang.Class<?>[] getExceptionTypes(); @@ -52067,7 +52068,6 @@ package java.lang.reflect { method public java.lang.Class<?> getReturnType(); method public java.lang.reflect.TypeVariable<java.lang.reflect.Method>[] getTypeParameters(); method public java.lang.Object invoke(java.lang.Object, java.lang.Object...) throws java.lang.IllegalAccessException, java.lang.IllegalArgumentException, java.lang.reflect.InvocationTargetException; - method public boolean isAnnotationPresent(java.lang.Class<? extends java.lang.annotation.Annotation>); method public boolean isBridge(); method public boolean isDefault(); method public boolean isSynthetic(); diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 30f2c948df96..207b70af3f60 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -3730,6 +3730,31 @@ public class Intent implements Parcelable, Cloneable { public static final String EXTRA_ALTERNATE_INTENTS = "android.intent.extra.ALTERNATE_INTENTS"; /** + * A {@link ComponentName ComponentName[]} describing components that should be filtered out + * and omitted from a list of components presented to the user. + * + * <p>When used with {@link #ACTION_CHOOSER}, the chooser will omit any of the components + * in this array if it otherwise would have shown them. Useful for omitting specific targets + * from your own package or other apps from your organization if the idea of sending to those + * targets would be redundant with other app functionality. Filtered components will not + * be able to present targets from an associated <code>ChooserTargetService</code>.</p> + */ + public static final String EXTRA_EXCLUDE_COMPONENTS + = "android.intent.extra.EXCLUDE_COMPONENTS"; + + /** + * A {@link android.service.chooser.ChooserTarget ChooserTarget[]} for {@link #ACTION_CHOOSER} + * describing additional high-priority deep-link targets for the chooser to present to the user. + * + * <p>Targets provided in this way will be presented inline with all other targets provided + * by services from other apps. They will be prioritized before other service targets, but + * after those targets provided by sources that the user has manually pinned to the front.</p> + * + * @see #ACTION_CHOOSER + */ + public static final String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS"; + + /** * An {@link IntentSender} for an Activity that will be invoked when the user makes a selection * from the chooser activity presented by {@link #ACTION_CHOOSER}. * diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index a4e489c5ccbf..ed6ab5674d71 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -41,12 +41,12 @@ import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Parcelable; +import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.StorageManager; -import android.provider.DocumentsContract; import android.service.chooser.ChooserTarget; import android.service.chooser.ChooserTargetService; import android.service.chooser.IChooserTargetResult; @@ -70,6 +70,7 @@ import android.widget.ListView; import com.android.internal.R; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.MetricsProto.MetricsEvent; +import com.google.android.collect.Lists; import java.io.File; import java.util.ArrayList; @@ -89,6 +90,7 @@ public class ChooserActivity extends ResolverActivity { private IntentSender mChosenComponentSender; private IntentSender mRefinementIntentSender; private RefinementResultReceiver mRefinementResultReceiver; + private ChooserTarget[] mCallerChooserTargets; private Intent mReferrerFillInIntent; @@ -97,6 +99,7 @@ public class ChooserActivity extends ResolverActivity { private SharedPreferences mPinnedSharedPrefs; private static final float PINNED_TARGET_SCORE_BOOST = 1000.f; + private static final float CALLER_TARGET_SCORE_BOOST = 900.f; private static final String PINNED_SHARED_PREFS_NAME = "chooser_pin_settings"; private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment"; @@ -219,6 +222,34 @@ public class ChooserActivity extends ResolverActivity { Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER); setSafeForwardingMode(true); + pa = intent.getParcelableArrayExtra(Intent.EXTRA_EXCLUDE_COMPONENTS); + if (pa != null) { + ComponentName[] names = new ComponentName[pa.length]; + for (int i = 0; i < pa.length; i++) { + if (!(pa[i] instanceof ComponentName)) { + Log.w(TAG, "Filtered component #" + i + " not a ComponentName: " + pa[i]); + names = null; + break; + } + names[i] = (ComponentName) pa[i]; + } + setFilteredComponents(names); + } + + pa = intent.getParcelableArrayExtra(Intent.EXTRA_CHOOSER_TARGETS); + if (pa != null) { + ChooserTarget[] targets = new ChooserTarget[pa.length]; + for (int i = 0; i < pa.length; i++) { + if (!(pa[i] instanceof ChooserTarget)) { + Log.w(TAG, "Chooser target #" + i + " not a ChooserTarget: " + pa[i]); + targets = null; + break; + } + targets[i] = (ChooserTarget) pa[i]; + } + mCallerChooserTargets = targets; + } + mPinnedSharedPrefs = getPinnedSharedPrefs(this); super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents, null, false); @@ -292,6 +323,9 @@ public class ChooserActivity extends ResolverActivity { boolean alwaysUseOption) { final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null; mChooserListAdapter = (ChooserListAdapter) adapter; + if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) { + mChooserListAdapter.addServiceResults(null, Lists.newArrayList(mCallerChooserTargets)); + } mChooserRowAdapter = new ChooserRowAdapter(mChooserListAdapter); mChooserRowAdapter.registerDataSetObserver(new OffsetDataSetObserver(adapterView)); adapterView.setAdapter(mChooserRowAdapter); @@ -427,13 +461,19 @@ public class ChooserActivity extends ResolverActivity { continue; } } catch (NameNotFoundException e) { - Log.e(TAG, "Could not look up service " + serviceComponent, e); + Log.e(TAG, "Could not look up service " + serviceComponent + + "; component name not found"); continue; } final ChooserTargetServiceConnection conn = new ChooserTargetServiceConnection(this, dri); - if (bindService(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND)) { + + // Explicitly specify Process.myUserHandle instead of calling bindService + // to avoid the warning from calling from the system process without an explicit + // user handle + if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND, + Process.myUserHandle())) { if (DEBUG) { Log.d(TAG, "Binding service connection for target " + dri + " intent " + serviceIntent); @@ -635,7 +675,11 @@ public class ChooserActivity extends ResolverActivity { if (mSourceInfo != null) { return mSourceInfo.getResolvedIntent(); } - return getTargetIntent(); + + final Intent targetIntent = new Intent(getTargetIntent()); + targetIntent.setComponent(mChooserTarget.getComponentName()); + targetIntent.putExtras(mChooserTarget.getIntentExtras()); + return targetIntent; } @Override @@ -650,8 +694,7 @@ public class ChooserActivity extends ResolverActivity { } private Intent getBaseIntentToSend() { - Intent result = mSourceInfo != null - ? mSourceInfo.getResolvedIntent() : getTargetIntent(); + Intent result = getResolvedIntent(); if (result == null) { Log.e(TAG, "ChooserTargetInfo: no base intent available to send"); } else { @@ -677,7 +720,19 @@ public class ChooserActivity extends ResolverActivity { } intent.setComponent(mChooserTarget.getComponentName()); intent.putExtras(mChooserTarget.getIntentExtras()); - activity.startActivityAsCaller(intent, options, true, userId); + + // Important: we will ignore the target security checks in ActivityManager + // if and only if the ChooserTarget's target package is the same package + // where we got the ChooserTargetService that provided it. This lets a + // ChooserTargetService provide a non-exported or permission-guarded target + // to the chooser for the user to pick. + // + // If mSourceInfo is null, we got this ChooserTarget from the caller or elsewhere + // so we'll obey the caller's normal security checks. + final boolean ignoreTargetSecurity = mSourceInfo != null + && mSourceInfo.getResolvedComponentName().getPackageName() + .equals(mChooserTarget.getComponentName().getPackageName()); + activity.startActivityAsCaller(intent, options, ignoreTargetSecurity, userId); return true; } @@ -810,6 +865,9 @@ public class ChooserActivity extends ResolverActivity { @Override public float getScore(DisplayResolveInfo target) { + if (target == null) { + return CALLER_TARGET_SCORE_BOOST; + } float score = super.getScore(target); if (target.isPinned()) { score += PINNED_TARGET_SCORE_BOOST; @@ -1281,7 +1339,7 @@ public class ChooserActivity extends ResolverActivity { } static class ChooserTargetServiceConnection implements ServiceConnection { - private final DisplayResolveInfo mOriginalTarget; + private DisplayResolveInfo mOriginalTarget; private ComponentName mConnectedComponent; private ChooserActivity mChooserActivity; private final Object mLock = new Object(); @@ -1359,6 +1417,7 @@ public class ChooserActivity extends ResolverActivity { public void destroy() { synchronized (mLock) { mChooserActivity = null; + mOriginalTarget = null; } } @@ -1366,7 +1425,9 @@ public class ChooserActivity extends ResolverActivity { public String toString() { return "ChooserTargetServiceConnection{service=" + mConnectedComponent + ", activity=" - + mOriginalTarget.getResolveInfo().activityInfo.toString() + "}"; + + (mOriginalTarget != null + ? mOriginalTarget.getResolveInfo().activityInfo.toString() + : "<connection destroyed>") + "}"; } } diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index ff680e2002ac..f2bf9e1d9695 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -105,6 +105,7 @@ public class ResolverActivity extends Activity { private final ArrayList<Intent> mIntents = new ArrayList<>(); private ResolverComparator mResolverComparator; private PickTargetOptionRequest mPickOptionRequest; + private ComponentName[] mFilteredComponents; protected ResolverDrawerLayout mResolverDrawerLayout; @@ -332,6 +333,24 @@ public class ResolverActivity extends Activity { } } + public final void setFilteredComponents(ComponentName[] components) { + mFilteredComponents = components; + } + + public final boolean isComponentFiltered(ComponentInfo component) { + if (mFilteredComponents == null) { + return false; + } + + final ComponentName checkName = component.getComponentName(); + for (ComponentName name : mFilteredComponents) { + if (name.equals(checkName)) { + return true; + } + } + return false; + } + /** * Perform any initialization needed for voice interaction. */ @@ -1269,7 +1288,8 @@ public class ResolverActivity extends Activity { ai.applicationInfo.uid, ai.exported); boolean suspended = (ai.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0; - if (granted != PackageManager.PERMISSION_GRANTED || suspended) { + if (granted != PackageManager.PERMISSION_GRANTED || suspended + || isComponentFiltered(ai)) { // Access not allowed! if (mOrigResolveList == currentResolveList) { mOrigResolveList = new ArrayList<>(mOrigResolveList); |