Migrate extras to ClipData for image/video capture intents.
The intents ACTION_IMAGE_CAPTURE, ACTION_IMAGE_CAPTURE_SECURE and ACTION_VIDEO_CAPTURE are now handled in a way
similar to ACTION_SEND and ACTION_SEND_MULTIPLE.
Migrate the uri in the EXTRA_OUTPUT extra to clipData, and add the flag GRANT_WRITE_URI_PERMISSION.
The userIds are now added to extra uris in the process receiving the intent, (not in the system process), because the
system process may not be able to parcel/unparcel the extras.
BUG: 15534203
Change-Id: I8f79666b726bc6d7745bf777ad3c7518945c5cc3
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 0bab7b2..de5b9c4 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2205,6 +2205,7 @@
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
+ r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
@@ -2429,6 +2430,7 @@
for (int i=0; i<N; i++) {
Intent intent = intents.get(i);
intent.setExtrasClassLoader(r.activity.getClassLoader());
+ intent.prepareToEnterProcess();
r.activity.mFragments.noteStateNotSaved();
mInstrumentation.callActivityOnNewIntent(r.activity, intent);
}
@@ -2552,6 +2554,7 @@
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
data.intent.setExtrasClassLoader(cl);
+ data.intent.prepareToEnterProcess();
data.setExtrasClassLoader(cl);
receiver = (BroadcastReceiver)cl.loadClass(component).newInstance();
} catch (Exception e) {
@@ -2753,6 +2756,7 @@
if (s != null) {
try {
data.intent.setExtrasClassLoader(s.getClassLoader());
+ data.intent.prepareToEnterProcess();
try {
if (!data.rebind) {
IBinder binder = s.onBind(data.intent);
@@ -2781,6 +2785,7 @@
if (s != null) {
try {
data.intent.setExtrasClassLoader(s.getClassLoader());
+ data.intent.prepareToEnterProcess();
boolean doRebind = s.onUnbind(data.intent);
try {
if (doRebind) {
@@ -2856,6 +2861,7 @@
try {
if (data.args != null) {
data.args.setExtrasClassLoader(s.getClassLoader());
+ data.args.prepareToEnterProcess();
}
int res;
if (!data.taskRemoved) {
@@ -3506,6 +3512,7 @@
try {
if (ri.mData != null) {
ri.mData.setExtrasClassLoader(r.activity.getClassLoader());
+ ri.mData.prepareToEnterProcess();
}
if (DEBUG_RESULTS) Slog.v(TAG,
"Delivering result to activity " + r + " : " + ri);
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index 7a16ef8..d19604b 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -810,20 +810,16 @@
}
}
- /**
- * Prepare this {@link ClipData} to leave an app process.
- *
- * @hide
- */
- public void prepareToLeaveUser(int userId) {
+ /** @hide */
+ public void fixUris(int contentUserHint) {
final int size = mItems.size();
for (int i = 0; i < size; i++) {
final Item item = mItems.get(i);
if (item.mIntent != null) {
- item.mIntent.prepareToLeaveUser(userId);
+ item.mIntent.fixUris(contentUserHint);
}
if (item.mUri != null) {
- item.mUri = maybeAddUserId(item.mUri, userId);
+ item.mUri = maybeAddUserId(item.mUri, contentUserHint);
}
}
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 287ea35..ae60476 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -17,6 +17,7 @@
package android.content;
import android.content.pm.ApplicationInfo;
+import android.provider.MediaStore;
import android.util.ArraySet;
import org.xmlpull.v1.XmlPullParser;
@@ -38,6 +39,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.StrictMode;
+import android.os.UserHandle;
import android.provider.DocumentsContract;
import android.provider.DocumentsProvider;
import android.provider.OpenableColumns;
@@ -3937,6 +3939,7 @@
private Rect mSourceBounds;
private Intent mSelector;
private ClipData mClipData;
+ private int mContentUserHint = UserHandle.USER_CURRENT;
// ---------------------------------------------------------------------
@@ -3956,6 +3959,7 @@
this.mPackage = o.mPackage;
this.mComponent = o.mComponent;
this.mFlags = o.mFlags;
+ this.mContentUserHint = o.mContentUserHint;
if (o.mCategories != null) {
this.mCategories = new ArraySet<String>(o.mCategories);
}
@@ -4660,6 +4664,11 @@
return mClipData;
}
+ /** @hide */
+ public int getContentUserHint() {
+ return mContentUserHint;
+ }
+
/**
* Sets the ClassLoader that will be used when unmarshalling
* any Parcelable values from the extras of this Intent.
@@ -5679,6 +5688,16 @@
}
/**
+ * This is NOT a secure mechanism to identify the user who sent the intent.
+ * When the intent is sent to a different user, it is used to fix uris by adding the userId
+ * who sent the intent.
+ * @hide
+ */
+ public void setContentUserHint(int contentUserHint) {
+ mContentUserHint = contentUserHint;
+ }
+
+ /**
* Add extended data to the intent. The name must include a package
* prefix, for example the app com.android.contacts would use names
* like "com.android.contacts.ShowAll".
@@ -6731,6 +6750,7 @@
@FillInFlags
public int fillIn(Intent other, @FillInFlags int flags) {
int changes = 0;
+ boolean mayHaveCopiedUris = false;
if (other.mAction != null
&& (mAction == null || (flags&FILL_IN_ACTION) != 0)) {
mAction = other.mAction;
@@ -6742,6 +6762,7 @@
mData = other.mData;
mType = other.mType;
changes |= FILL_IN_DATA;
+ mayHaveCopiedUris = true;
}
if (other.mCategories != null
&& (mCategories == null || (flags&FILL_IN_CATEGORIES) != 0)) {
@@ -6771,6 +6792,7 @@
&& (mClipData == null || (flags&FILL_IN_CLIP_DATA) != 0)) {
mClipData = other.mClipData;
changes |= FILL_IN_CLIP_DATA;
+ mayHaveCopiedUris = true;
}
// Component is special: it can -only- be set if explicitly allowed,
// since otherwise the sender could force the intent somewhere the
@@ -6788,12 +6810,14 @@
if (mExtras == null) {
if (other.mExtras != null) {
mExtras = new Bundle(other.mExtras);
+ mayHaveCopiedUris = true;
}
} else if (other.mExtras != null) {
try {
Bundle newb = new Bundle(other.mExtras);
newb.putAll(mExtras);
mExtras = newb;
+ mayHaveCopiedUris = true;
} catch (RuntimeException e) {
// Modifying the extras can cause us to unparcel the contents
// of the bundle, and if we do this in the system process that
@@ -6803,6 +6827,10 @@
Log.w("Intent", "Failure filling in extras", e);
}
}
+ if (mayHaveCopiedUris && mContentUserHint == UserHandle.USER_CURRENT
+ && other.mContentUserHint != UserHandle.USER_CURRENT) {
+ mContentUserHint = other.mContentUserHint;
+ }
return changes;
}
@@ -7030,8 +7058,15 @@
first = false;
b.append("(has extras)");
}
+ if (mContentUserHint != UserHandle.USER_CURRENT) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ b.append("u=").append(mContentUserHint);
+ }
if (mSelector != null) {
- b.append(" sel={");
+ b.append(" sel=");
mSelector.toShortString(b, secure, comp, extras, clip);
b.append("}");
}
@@ -7208,7 +7243,7 @@
} else {
out.writeInt(0);
}
-
+ out.writeInt(mContentUserHint);
out.writeBundle(mExtras);
}
@@ -7257,7 +7292,7 @@
if (in.readInt() != 0) {
mClipData = new ClipData(in);
}
-
+ mContentUserHint = in.readInt();
mExtras = in.readBundle();
}
@@ -7467,39 +7502,50 @@
}
/**
- * Prepare this {@link Intent} to be sent to another user
- *
* @hide
*/
- public void prepareToLeaveUser(int userId) {
+ public void prepareToEnterProcess() {
+ if (mContentUserHint != UserHandle.USER_CURRENT) {
+ fixUris(mContentUserHint);
+ mContentUserHint = UserHandle.USER_CURRENT;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void fixUris(int contentUserHint) {
Uri data = getData();
if (data != null) {
- mData = maybeAddUserId(data, userId);
- }
- if (mSelector != null) {
- mSelector.prepareToLeaveUser(userId);
+ mData = maybeAddUserId(data, contentUserHint);
}
if (mClipData != null) {
- mClipData.prepareToLeaveUser(userId);
+ mClipData.fixUris(contentUserHint);
}
String action = getAction();
if (ACTION_SEND.equals(action)) {
final Uri stream = getParcelableExtra(EXTRA_STREAM);
if (stream != null) {
- putExtra(EXTRA_STREAM, maybeAddUserId(stream, userId));
+ putExtra(EXTRA_STREAM, maybeAddUserId(stream, contentUserHint));
}
- }
- if (ACTION_SEND_MULTIPLE.equals(action)) {
+ } else if (ACTION_SEND_MULTIPLE.equals(action)) {
final ArrayList<Uri> streams = getParcelableArrayListExtra(EXTRA_STREAM);
if (streams != null) {
ArrayList<Uri> newStreams = new ArrayList<Uri>();
for (int i = 0; i < streams.size(); i++) {
- newStreams.add(maybeAddUserId(streams.get(i), userId));
+ newStreams.add(maybeAddUserId(streams.get(i), contentUserHint));
}
putParcelableArrayListExtra(EXTRA_STREAM, newStreams);
}
+ } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)
+ || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(action)
+ || MediaStore.ACTION_VIDEO_CAPTURE.equals(action)) {
+ final Uri output = getParcelableExtra(MediaStore.EXTRA_OUTPUT);
+ if (output != null) {
+ putExtra(MediaStore.EXTRA_OUTPUT, maybeAddUserId(output, contentUserHint));
+ }
}
- }
+ }
/**
* Migrate any {@link #EXTRA_STREAM} in {@link #ACTION_SEND} and
@@ -7590,6 +7636,20 @@
}
} catch (ClassCastException e) {
}
+ } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)
+ || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(action)
+ || MediaStore.ACTION_VIDEO_CAPTURE.equals(action)) {
+ final Uri output;
+ try {
+ output = getParcelableExtra(MediaStore.EXTRA_OUTPUT);
+ } catch (ClassCastException e) {
+ return false;
+ }
+ if (output != null) {
+ setClipData(ClipData.newRawUri("", output));
+ addFlags(FLAG_GRANT_WRITE_URI_PERMISSION|FLAG_GRANT_READ_URI_PERMISSION);
+ return true;
+ }
}
return false;
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 325917e..d137f0c 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -256,6 +256,11 @@
* object in the extra field. This is useful for applications that only need a small image.
* If the EXTRA_OUTPUT is present, then the full-sized image will be written to the Uri
* value of EXTRA_OUTPUT.
+ * As of {@link android.os.Build.VERSION_CODES#L}, this uri can also be supplied through
+ * {@link android.content.Intent#setClipData(ClipData)}. If using this approach, you still must
+ * supply the uri through the EXTRA_OUTPUT field for compatibility with old applications.
+ * If you don't set a ClipData, it will be copied there for you when calling
+ * {@link Context#startActivity(Intent)}.
* @see #EXTRA_OUTPUT
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@@ -276,6 +281,11 @@
* object in the extra field. This is useful for applications that only need a small image.
* If the EXTRA_OUTPUT is present, then the full-sized image will be written to the Uri
* value of EXTRA_OUTPUT.
+ * As of {@link android.os.Build.VERSION_CODES#L}, this uri can also be supplied through
+ * {@link android.content.Intent#setClipData(ClipData)}. If using this approach, you still must
+ * supply the uri through the EXTRA_OUTPUT field for compatibility with old applications.
+ * If you don't set a ClipData, it will be copied there for you when calling
+ * {@link Context#startActivity(Intent)}.
*
* @see #ACTION_IMAGE_CAPTURE
* @see #EXTRA_OUTPUT
@@ -294,6 +304,11 @@
* where the video is written. If EXTRA_OUTPUT is not present the video will be
* written to the standard location for videos, and the Uri of that location will be
* returned in the data field of the Uri.
+ * As of {@link android.os.Build.VERSION_CODES#L}, this uri can also be supplied through
+ * {@link android.content.Intent#setClipData(ClipData)}. If using this approach, you still must
+ * supply the uri through the EXTRA_OUTPUT field for compatibility with old applications.
+ * If you don't set a ClipData, it will be copied there for you when calling
+ * {@link Context#startActivity(Intent)}.
* @see #EXTRA_OUTPUT
* @see #EXTRA_VIDEO_QUALITY
* @see #EXTRA_SIZE_LIMIT
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 0eadde1..1c01353 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -89,7 +89,7 @@
Slog.e(TAG, "PackageManagerService is dead?");
}
if (canForward) {
- newIntent.prepareToLeaveUser(callingUserId);
+ newIntent.setContentUserHint(callingUserId);
final android.content.pm.ResolveInfo ri = getPackageManager().resolveActivityAsUser(
newIntent, MATCH_DEFAULT_ONLY, userDest.getIdentifier());