Merge "Chooser filtering and caller direct share targets" into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index 1c4f85b..8297988a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8685,6 +8685,7 @@
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 @@
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 @@
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 @@
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 039b9ad..19d4d32 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -9004,6 +9004,7 @@
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 @@
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 @@
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 @@
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 3febda1..5c975cf 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -8692,6 +8692,7 @@
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 @@
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 @@
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 @@
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 30f2c94..207b70a 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3730,6 +3730,31 @@
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 a4e489c..ed6ab56 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.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 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 @@
private IntentSender mChosenComponentSender;
private IntentSender mRefinementIntentSender;
private RefinementResultReceiver mRefinementResultReceiver;
+ private ChooserTarget[] mCallerChooserTargets;
private Intent mReferrerFillInIntent;
@@ -97,6 +99,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
}
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 @@
@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 @@
}
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 void destroy() {
synchronized (mLock) {
mChooserActivity = null;
+ mOriginalTarget = null;
}
}
@@ -1366,7 +1425,9 @@
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 ff680e2..f2bf9e1 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -105,6 +105,7 @@
private final ArrayList<Intent> mIntents = new ArrayList<>();
private ResolverComparator mResolverComparator;
private PickTargetOptionRequest mPickOptionRequest;
+ private ComponentName[] mFilteredComponents;
protected ResolverDrawerLayout mResolverDrawerLayout;
@@ -332,6 +333,24 @@
}
}
+ 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 @@
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);