summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--java/src/com/android/intentresolver/ChooserActivity.java13
-rw-r--r--java/src/com/android/intentresolver/data/model/ChooserRequest.kt13
-rw-r--r--java/src/com/android/intentresolver/shared/model/Profile.kt6
-rw-r--r--java/src/com/android/intentresolver/util/IntentUtils.kt37
-rw-r--r--tests/unit/src/com/android/intentresolver/util/IntentUtilsTest.kt62
5 files changed, 120 insertions, 11 deletions
diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java
index aff34580..7b744721 100644
--- a/java/src/com/android/intentresolver/ChooserActivity.java
+++ b/java/src/com/android/intentresolver/ChooserActivity.java
@@ -525,7 +525,6 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
mProfiles,
mProfileRecords.values(),
mProfileAvailability,
- mRequest.getInitialIntents(),
mMaxTargetsPerRow);
maybeDisableRecentsScreenshot(mProfiles, mProfileAvailability);
@@ -840,7 +839,6 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
mProfiles,
mProfileRecords.values(),
mProfileAvailability,
- mRequest.getInitialIntents(),
mMaxTargetsPerRow);
mChooserMultiProfilePagerAdapter.setCurrentPage(currentPage);
for (int i = 0, count = mChooserMultiProfilePagerAdapter.getItemCount(); i < count; i++) {
@@ -1507,22 +1505,23 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
ProfileHelper profileHelper,
Collection<ProfileRecord> profileRecords,
ProfileAvailability profileAvailability,
- List<Intent> initialIntents,
int maxTargetsPerRow) {
Log.d(TAG, "createMultiProfilePagerAdapter");
Profile launchedAs = profileHelper.getLaunchedAsProfile();
- Intent[] initialIntentArray = initialIntents.toArray(new Intent[0]);
- List<Intent> payloadIntents = request.getPayloadIntents();
+ Intent[] initialIntentArray = request.getInitialIntents().toArray(new Intent[0]);
List<TabConfig<ChooserGridAdapter>> tabs = new ArrayList<>();
for (ProfileRecord record : profileRecords) {
Profile profile = record.profile;
+ boolean isCrossProfile = !profile.equals(launchedAs);
ChooserGridAdapter adapter = createChooserGridAdapter(
context,
- payloadIntents,
- profile.equals(launchedAs) ? initialIntentArray : null,
+ isCrossProfile
+ ? request.getCrossProfilePayloadIntents()
+ : request.getPayloadIntents(),
+ isCrossProfile ? null : initialIntentArray,
profile.getPrimary().getHandle()
);
tabs.add(new TabConfig<>(
diff --git a/java/src/com/android/intentresolver/data/model/ChooserRequest.kt b/java/src/com/android/intentresolver/data/model/ChooserRequest.kt
index ad338103..c177849c 100644
--- a/java/src/com/android/intentresolver/data/model/ChooserRequest.kt
+++ b/java/src/com/android/intentresolver/data/model/ChooserRequest.kt
@@ -30,6 +30,7 @@ import androidx.annotation.StringRes
import com.android.intentresolver.ContentTypeHint
import com.android.intentresolver.IChooserInteractiveSessionCallback
import com.android.intentresolver.ext.hasAction
+import com.android.intentresolver.util.sanitizePayloadIntents
import com.android.systemui.shared.Flags.screenshotContextUrl
const val ANDROID_APP_SCHEME = "android-app"
@@ -198,6 +199,18 @@ data class ChooserRequest(
val payloadIntents = listOf(targetIntent) + additionalTargets
+ /**
+ * Payload intents that should be used for cross-profile sharing.
+ *
+ * These intents are a copy of `payloadIntents`. For security reasons, explicit targeting
+ * information is removed from each [Intent] in the list, as well as from its
+ * [selector][Intent.getSelector]. Specifically, the values that would be returned by
+ * [Intent.getPackage] and [Intent.getComponent] are cleared for both the main intent and its
+ * selector. This sanitization is performed because explicit intents could otherwise be used to
+ * bypass the device's cross-profile sharing policy settings.
+ */
+ val crossProfilePayloadIntents by lazy { sanitizePayloadIntents(payloadIntents) }
+
val callerAllowsTextToggle =
screenshotContextUrl() && "com.android.systemui".equals(referrerPackage)
}
diff --git a/java/src/com/android/intentresolver/shared/model/Profile.kt b/java/src/com/android/intentresolver/shared/model/Profile.kt
index c557c151..ce705259 100644
--- a/java/src/com/android/intentresolver/shared/model/Profile.kt
+++ b/java/src/com/android/intentresolver/shared/model/Profile.kt
@@ -16,8 +16,6 @@
package com.android.intentresolver.shared.model
-import com.android.intentresolver.shared.model.Profile.Type
-
/**
* Associates [users][User] into a [Type] instance.
*
@@ -32,7 +30,7 @@ data class Profile(
* An optional [User] of which contains second instances of some applications installed for the
* personal user. This value may only be supplied when creating the PERSONAL profile.
*/
- val clone: User? = null
+ val clone: User? = null,
) {
init {
@@ -47,6 +45,6 @@ data class Profile(
enum class Type {
PERSONAL,
WORK,
- PRIVATE
+ PRIVATE,
}
}
diff --git a/java/src/com/android/intentresolver/util/IntentUtils.kt b/java/src/com/android/intentresolver/util/IntentUtils.kt
new file mode 100644
index 00000000..c20479c8
--- /dev/null
+++ b/java/src/com/android/intentresolver/util/IntentUtils.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:JvmName("IntentUtils")
+
+package com.android.intentresolver.util
+
+import android.content.Intent
+
+fun sanitizePayloadIntents(intents: List<Intent>): List<Intent> =
+ intents.map { intent ->
+ Intent(intent).also { sanitized ->
+ sanitized.setPackage(null)
+ sanitized.setComponent(null)
+ sanitized.selector?.let {
+ sanitized.setSelector(
+ Intent(it).apply {
+ setPackage(null)
+ setComponent(null)
+ }
+ )
+ }
+ }
+ }
diff --git a/tests/unit/src/com/android/intentresolver/util/IntentUtilsTest.kt b/tests/unit/src/com/android/intentresolver/util/IntentUtilsTest.kt
new file mode 100644
index 00000000..8042b82e
--- /dev/null
+++ b/tests/unit/src/com/android/intentresolver/util/IntentUtilsTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.intentresolver.util
+
+import android.content.ComponentName
+import android.content.Intent
+import android.content.Intent.ACTION_SEND
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class IntentUtilsTest {
+ @Test
+ fun test_sanitizePayloadIntents() {
+ val intents =
+ listOf(
+ Intent(ACTION_SEND).apply { setPackage("org.test.example") },
+ Intent(ACTION_SEND).apply {
+ setComponent(
+ ComponentName.unflattenFromString("org.test.example/.TestActivity")
+ )
+ },
+ Intent(ACTION_SEND).apply {
+ setSelector(Intent(ACTION_SEND).apply { setPackage("org.test.example") })
+ },
+ Intent(ACTION_SEND).apply {
+ setSelector(
+ Intent(ACTION_SEND).apply {
+ setComponent(
+ ComponentName.unflattenFromString("org.test.example/.TestActivity")
+ )
+ }
+ )
+ },
+ )
+
+ val sanitized = sanitizePayloadIntents(intents)
+
+ assertThat(sanitized).hasSize(intents.size)
+ for (i in sanitized) {
+ assertThat(i.getPackage()).isNull()
+ assertThat(i.getComponent()).isNull()
+ i.getSelector()?.let {
+ assertThat(it.getPackage()).isNull()
+ assertThat(it.getComponent()).isNull()
+ }
+ }
+ }
+}