summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Nan Wu <wnan@google.com> 2024-11-26 14:48:09 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-11-26 14:48:09 +0000
commit312a7a089b9d51c4952f5ae6b3948ddc1370dbff (patch)
tree7f5ae67882ae2026c01bd2f18bda456dc56dcd91
parent0fd15aa6cf8255685fa9906c3c844e54616f815d (diff)
parent80b57afa7ee538ee0241cfa45d77eb49c4175e4c (diff)
Merge "Allow refresh creator token for edge case" into main
-rw-r--r--core/java/android/app/IActivityManager.aidl10
-rw-r--r--core/java/android/content/ClipData.java28
-rw-r--r--core/java/android/content/Intent.java60
-rw-r--r--core/tests/coretests/src/android/content/IntentTest.java113
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java41
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java40
6 files changed, 285 insertions, 7 deletions
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 0668958b2d5c..50486611a5a9 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -1028,4 +1028,14 @@ interface IActivityManager {
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.DEVICE_POWER)")
void noteAppRestrictionEnabled(in String packageName, int uid, int restrictionType,
boolean enabled, int reason, in String subReason, int source, long threshold);
+
+ /**
+ * Creates and returns a new IntentCreatorToken that keeps the creatorUid and refreshes key
+ * fields of the intent passed in.
+ *
+ * @param intent The intent with key fields out of sync of the IntentCreatorToken it contains.
+ * @hide
+ */
+ @EnforcePermission("INTERACT_ACROSS_USERS_FULL")
+ IBinder refreshIntentCreatorToken(in Intent intent);
}
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index cc57dc05d6b1..e271cf4f60ec 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -946,6 +946,34 @@ public class ClipData implements Parcelable {
}
/**
+ * Make a clone of ClipData that only contains URIs. This reduces the size of data transfer over
+ * IPC and only retains important information for the purpose of verifying creator token of an
+ * Intent.
+ * @return a copy of ClipData with only URIs remained.
+ * @hide
+ */
+ public ClipData cloneOnlyUriItems() {
+ ArrayList<Item> items = null;
+ final int N = mItems.size();
+ for (int i = 0; i < N; i++) {
+ Item item = mItems.get(i);
+ if (item.getUri() != null) {
+ if (items == null) {
+ items = new ArrayList<>(N);
+ }
+ items.add(new Item(item.getUri()));
+ } else if (item.getIntent() != null) {
+ if (items == null) {
+ items = new ArrayList<>(N);
+ }
+ items.add(new Item(item.getIntent().cloneForCreatorToken()));
+ }
+ }
+ if (items == null || items.isEmpty()) return null;
+ return new ClipData(new ClipDescription("", new String[0]), items);
+ }
+
+ /**
* Create a new ClipData holding data of the type
* {@link ClipDescription#MIMETYPE_TEXT_PLAIN}.
*
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 02eed1a7553f..d7660172a2f1 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -7970,6 +7970,24 @@ public class Intent implements Parcelable, Cloneable {
}
/**
+ * Make a copy of all members important to identify an intent with its creator token.
+ * @hide
+ */
+ public @NonNull Intent cloneForCreatorToken() {
+ Intent clone = new Intent()
+ .setAction(this.mAction)
+ .setDataAndType(this.mData, this.mType)
+ .setPackage(this.mPackage)
+ .setComponent(this.mComponent)
+ .setFlags(this.mFlags & IMMUTABLE_FLAGS);
+ if (this.mClipData != null) {
+ clone.setClipData(this.mClipData.cloneOnlyUriItems());
+ }
+ clone.mCreatorTokenInfo = this.mCreatorTokenInfo;
+ return clone;
+ }
+
+ /**
* Create an intent with a given action. All other fields (data, type,
* class) are null. Note that the action <em>must</em> be in a
* namespace because Intents are used globally in the system -- for
@@ -11684,7 +11702,7 @@ public class Intent implements Parcelable, Cloneable {
Log.w(TAG, "Failure filling in extras", e);
}
}
- mCreatorTokenInfo = other.mCreatorTokenInfo;
+ fillInCreatorTokenInfo(other.mCreatorTokenInfo, changes);
if (mayHaveCopiedUris && mContentUserHint == UserHandle.USER_CURRENT
&& other.mContentUserHint != UserHandle.USER_CURRENT) {
mContentUserHint = other.mContentUserHint;
@@ -11692,6 +11710,45 @@ public class Intent implements Parcelable, Cloneable {
return changes;
}
+ // keep original creator token and merge nested intent keys.
+ private void fillInCreatorTokenInfo(CreatorTokenInfo otherCreatorTokenInfo, int changes) {
+ if (otherCreatorTokenInfo != null && otherCreatorTokenInfo.mNestedIntentKeys != null) {
+ if (mCreatorTokenInfo == null) {
+ mCreatorTokenInfo = new CreatorTokenInfo();
+ }
+ ArraySet<NestedIntentKey> otherNestedIntentKeys =
+ otherCreatorTokenInfo.mNestedIntentKeys;
+ if (mCreatorTokenInfo.mNestedIntentKeys == null) {
+ mCreatorTokenInfo.mNestedIntentKeys = new ArraySet<>(otherNestedIntentKeys);
+ } else {
+ ArraySet<NestedIntentKey> otherKeys;
+ if ((changes & FILL_IN_CLIP_DATA) == 0) {
+ // If clip data is Not filled in from other, do not merge clip data keys.
+ otherKeys = new ArraySet<>();
+ int N = otherNestedIntentKeys.size();
+ for (int i = 0; i < N; i++) {
+ NestedIntentKey key = otherNestedIntentKeys.valueAt(i);
+ if (key.mType != NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA) {
+ otherKeys.add(key);
+ }
+ }
+ } else {
+ // If clip data is filled in from other, remove clip data keys from this
+ // creatorTokenInfo and then merge every key from the others.
+ int N = mCreatorTokenInfo.mNestedIntentKeys.size();
+ for (int i = N - 1; i >= 0; i--) {
+ NestedIntentKey key = mCreatorTokenInfo.mNestedIntentKeys.valueAt(i);
+ if (key.mType == NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA) {
+ mCreatorTokenInfo.mNestedIntentKeys.removeAt(i);
+ }
+ }
+ otherKeys = otherNestedIntentKeys;
+ }
+ mCreatorTokenInfo.mNestedIntentKeys.addAll(otherKeys);
+ }
+ }
+ }
+
/**
* Merge the extras data in this intent with that of other supplied intent using the
* strategy specified using {@code extrasMerger}.
@@ -12228,6 +12285,7 @@ public class Intent implements Parcelable, Cloneable {
private IBinder mCreatorToken;
// Stores all extra keys whose values are intents for a top level intent.
private ArraySet<NestedIntentKey> mNestedIntentKeys;
+
}
/**
diff --git a/core/tests/coretests/src/android/content/IntentTest.java b/core/tests/coretests/src/android/content/IntentTest.java
index 7bc4abd935b6..fdfb0c34cdeb 100644
--- a/core/tests/coretests/src/android/content/IntentTest.java
+++ b/core/tests/coretests/src/android/content/IntentTest.java
@@ -19,8 +19,9 @@ package android.content;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
-import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
@@ -29,6 +30,7 @@ import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.security.Flags;
+import android.util.ArraySet;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -40,6 +42,7 @@ import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Set;
/**
* Build/Install/Run:
@@ -51,7 +54,6 @@ import java.util.List;
public class IntentTest {
private static final String TEST_ACTION = "android.content.IntentTest_test";
private static final String TEST_EXTRA_NAME = "testExtraName";
- private static final Uri TEST_URI = Uri.parse("content://com.example/people");
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@@ -129,4 +131,111 @@ public class IntentTest {
}
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_PREVENT_INTENT_REDIRECT)
+ public void testFillInCreatorTokenInfo() {
+ // case 1: intent does not have creatorTokenInfo; fillinIntent contains creatorTokenInfo
+ Intent intent = new Intent();
+ Intent fillInIntent = new Intent();
+ fillInIntent.setCreatorToken(new Binder());
+ fillInIntent.putExtra("extraKey", new Intent());
+
+ fillInIntent.collectExtraIntentKeys();
+ intent.fillIn(fillInIntent, 0);
+
+ // extra intent keys are merged
+ assertThat(intent.getExtraIntentKeys()).isEqualTo(fillInIntent.getExtraIntentKeys());
+ // but creator token is not overwritten.
+ assertThat(intent.getCreatorToken()).isNull();
+
+
+ // case 2: Both intent and fillInIntent contains creatorToken, intent's creatorToken is not
+ // overwritten.
+ intent = new Intent();
+ IBinder creatorToken = new Binder();
+ intent.setCreatorToken(creatorToken);
+ fillInIntent = new Intent();
+ fillInIntent.setCreatorToken(new Binder());
+
+ intent.fillIn(fillInIntent, 0);
+
+ assertThat(intent.getCreatorToken()).isEqualTo(creatorToken);
+
+
+ // case 3: Contains duplicate extra keys
+ intent = new Intent();
+ intent.putExtra("key1", new Intent());
+ intent.putExtra("key2", new Intent());
+ fillInIntent = new Intent();
+ fillInIntent.putExtra("key1", new Intent());
+ fillInIntent.putExtra("key3", new Intent());
+
+ intent.collectExtraIntentKeys();
+ Set originalIntentKeys = new ArraySet<>(intent.getExtraIntentKeys());
+
+ fillInIntent.collectExtraIntentKeys();
+ intent.fillIn(fillInIntent, 0);
+
+ assertThat(intent.getExtraIntentKeys()).hasSize(3);
+ assertTrue(intent.getExtraIntentKeys().containsAll(originalIntentKeys));
+ assertTrue(intent.getExtraIntentKeys().containsAll(fillInIntent.getExtraIntentKeys()));
+
+
+ // case 4: Both contains a mixture of extras and clip data. NOT force to fill in clip data.
+ intent = new Intent();
+ ClipData clipData = ClipData.newIntent("clip", new Intent());
+ clipData.addItem(new ClipData.Item(new Intent()));
+ intent.setClipData(clipData);
+ intent.putExtra("key1", new Intent());
+ intent.putExtra("key2", new Intent());
+ fillInIntent = new Intent();
+ ClipData fillInClipData = ClipData.newIntent("clip", new Intent());
+ fillInClipData.addItem(new ClipData.Item(new Intent()));
+ fillInClipData.addItem(new ClipData.Item(new Intent()));
+ fillInIntent.setClipData(fillInClipData);
+ fillInIntent.putExtra("key1", new Intent());
+ fillInIntent.putExtra("key3", new Intent());
+
+ intent.collectExtraIntentKeys();
+ originalIntentKeys = new ArraySet<>(intent.getExtraIntentKeys());
+ fillInIntent.collectExtraIntentKeys();
+ intent.fillIn(fillInIntent, 0);
+
+ // size is 5 ( 3 extras merged from both + 2 clip data in the original.
+ assertThat(intent.getExtraIntentKeys()).hasSize(5);
+ // all keys from original are kept.
+ assertTrue(intent.getExtraIntentKeys().containsAll(originalIntentKeys));
+ // Not all keys from fillInIntent are kept - clip data keys are dropped.
+ assertFalse(intent.getExtraIntentKeys().containsAll(fillInIntent.getExtraIntentKeys()));
+
+
+ // case 5: Both contains a mixture of extras and clip data. Force to fill in clip data.
+ intent = new Intent();
+ clipData = ClipData.newIntent("clip", new Intent());
+ clipData.addItem(new ClipData.Item(new Intent()));
+ clipData.addItem(new ClipData.Item(new Intent()));
+ clipData.addItem(new ClipData.Item(new Intent()));
+ intent.setClipData(clipData);
+ intent.putExtra("key1", new Intent());
+ intent.putExtra("key2", new Intent());
+ fillInIntent = new Intent();
+ fillInClipData = ClipData.newIntent("clip", new Intent());
+ fillInClipData.addItem(new ClipData.Item(new Intent()));
+ fillInClipData.addItem(new ClipData.Item(new Intent()));
+ fillInIntent.setClipData(fillInClipData);
+ fillInIntent.putExtra("key1", new Intent());
+ fillInIntent.putExtra("key3", new Intent());
+
+ intent.collectExtraIntentKeys();
+ originalIntentKeys = new ArraySet<>(intent.getExtraIntentKeys());
+ fillInIntent.collectExtraIntentKeys();
+ intent.fillIn(fillInIntent, Intent.FILL_IN_CLIP_DATA);
+
+ // size is 6 ( 3 extras merged from both + 3 clip data in the fillInIntent.
+ assertThat(intent.getExtraIntentKeys()).hasSize(6);
+ // all keys from fillInIntent are kept.
+ assertTrue(intent.getExtraIntentKeys().containsAll(fillInIntent.getExtraIntentKeys()));
+ // Not all keys from intent are kept - clip data keys are dropped.
+ assertFalse(intent.getExtraIntentKeys().containsAll(originalIntentKeys));
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 01daceba5fb9..78dee3169161 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -192,6 +192,7 @@ import static com.android.systemui.shared.Flags.enableHomeDelay;
import android.Manifest;
import android.Manifest.permission;
+import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.PermissionMethod;
@@ -19228,6 +19229,11 @@ public class ActivityManagerService extends IActivityManager.Stub
return mKeyFields.mCreatorPackage;
}
+ @VisibleForTesting
+ public @NonNull Key getKeyFields() {
+ return mKeyFields;
+ }
+
public static boolean isValid(@NonNull Intent intent) {
IBinder binder = intent.getCreatorToken();
IntentCreatorToken token = null;
@@ -19271,9 +19277,13 @@ public class ActivityManagerService extends IActivityManager.Stub
this.mFlags = intent.getFlags() & Intent.IMMUTABLE_FLAGS;
ClipData clipData = intent.getClipData();
if (clipData != null) {
- this.mClipDataUris = new ArrayList<>(clipData.getItemCount());
- for (int i = 0; i < clipData.getItemCount(); i++) {
- this.mClipDataUris.add(clipData.getItemAt(i).getUri());
+ clipData = clipData.cloneOnlyUriItems();
+ if (clipData != null) {
+ List<Uri> clipDataUris = new ArrayList<>();
+ clipData.collectUris(clipDataUris);
+ if (!clipDataUris.isEmpty()) {
+ this.mClipDataUris = clipDataUris;
+ }
}
}
}
@@ -19375,11 +19385,34 @@ public class ActivityManagerService extends IActivityManager.Stub
String creatorPackage) {
if (IntentCreatorToken.isValid(intent)) return null;
IntentCreatorToken.Key key = new IntentCreatorToken.Key(creatorUid, creatorPackage, intent);
+ return createOrGetIntentCreatorToken(intent, key);
+ }
+
+ /**
+ * @hide
+ */
+ @EnforcePermission("android.permission.INTERACT_ACROSS_USERS_FULL")
+ public IBinder refreshIntentCreatorToken(Intent intent) {
+ refreshIntentCreatorToken_enforcePermission();
+ IBinder binder = intent.getCreatorToken();
+ if (binder instanceof IntentCreatorToken) {
+ IntentCreatorToken token = (IntentCreatorToken) binder;
+ IntentCreatorToken.Key key = new IntentCreatorToken.Key(token.getCreatorUid(),
+ token.getCreatorPackage(), intent);
+ return createOrGetIntentCreatorToken(intent, key);
+
+ } else {
+ throw new IllegalArgumentException("intent does not contain a creator token.");
+ }
+ }
+
+ private static IntentCreatorToken createOrGetIntentCreatorToken(Intent intent,
+ IntentCreatorToken.Key key) {
IntentCreatorToken token;
synchronized (sIntentCreatorTokenCache) {
WeakReference<IntentCreatorToken> ref = sIntentCreatorTokenCache.get(key);
if (ref == null || ref.get() == null) {
- token = new IntentCreatorToken(creatorUid, creatorPackage, intent);
+ token = new IntentCreatorToken(key.mCreatorUid, key.mCreatorPackage, intent);
sIntentCreatorTokenCache.put(key, token.mRef);
} else {
token = ref.get();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index d9332ec05697..6defadf44d05 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -89,6 +89,7 @@ import android.app.Notification;
import android.app.NotificationChannel;
import android.app.SyncNotedAppOp;
import android.app.backup.BackupAnnotations;
+import android.content.ClipData;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -100,6 +101,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ServiceInfo;
+import android.graphics.Rect;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Bundle;
@@ -204,6 +206,16 @@ public class ActivityManagerServiceTest {
private static final String TEST_AUTHORITY = "test_authority";
private static final String TEST_MIME_TYPE = "application/test_type";
+ private static final Uri TEST_URI = Uri.parse("content://com.example/people");
+ private static final int TEST_CREATOR_UID = 12345;
+ private static final String TEST_CREATOR_PACKAGE = "android.content.testCreatorPackage";
+ private static final String TEST_TYPE = "testType";
+ private static final String TEST_IDENTIFIER = "testIdentifier";
+ private static final String TEST_CATEGORY = "testCategory";
+ private static final String TEST_LAUNCH_TOKEN = "testLaunchToken";
+ private static final ComponentName TEST_COMPONENT = new ComponentName(TEST_PACKAGE,
+ "TestClass");
+ private static final int ALL_SET_FLAG = 0xFFFFFFFF;
private static final int[] UID_RECORD_CHANGES = {
UidRecord.CHANGE_PROCSTATE,
@@ -1414,6 +1426,34 @@ public class ActivityManagerServiceTest {
& Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isEqualTo(0);
}
+ @Test
+ public void testUseCloneForCreatorTokenAndOriginalIntent_createSameIntentCreatorToken() {
+ Intent testIntent = new Intent(TEST_ACTION1)
+ .setComponent(TEST_COMPONENT)
+ .setDataAndType(TEST_URI, TEST_TYPE)
+ .setIdentifier(TEST_IDENTIFIER)
+ .addCategory(TEST_CATEGORY);
+ testIntent.setOriginalIntent(new Intent(TEST_ACTION2));
+ testIntent.setSelector(new Intent(TEST_ACTION3));
+ testIntent.setSourceBounds(new Rect(0, 0, 100, 100));
+ testIntent.setLaunchToken(TEST_LAUNCH_TOKEN);
+ testIntent.addFlags(ALL_SET_FLAG)
+ .addExtendedFlags(ALL_SET_FLAG);
+ ClipData testClipData = ClipData.newHtmlText("label", "text", "<html/>");
+ testClipData.addItem(new ClipData.Item(new Intent(TEST_ACTION1)));
+ testClipData.addItem(new ClipData.Item(TEST_URI));
+ testIntent.putExtra(TEST_EXTRA_KEY1, TEST_EXTRA_VALUE1);
+
+ ActivityManagerService.IntentCreatorToken tokenForFullIntent =
+ new ActivityManagerService.IntentCreatorToken(TEST_CREATOR_UID,
+ TEST_CREATOR_PACKAGE, testIntent);
+ ActivityManagerService.IntentCreatorToken tokenForCloneIntent =
+ new ActivityManagerService.IntentCreatorToken(TEST_CREATOR_UID,
+ TEST_CREATOR_PACKAGE, testIntent.cloneForCreatorToken());
+
+ assertThat(tokenForFullIntent.getKeyFields()).isEqualTo(tokenForCloneIntent.getKeyFields());
+ }
+
private void verifyWaitingForNetworkStateUpdate(long curProcStateSeq,
long lastNetworkUpdatedProcStateSeq,
final long procStateSeqToWait, boolean expectWait) throws Exception {