Compare commits
7 commits
76490a0dad
...
12ad124755
| Author | SHA1 | Date | |
|---|---|---|---|
| 12ad124755 | |||
| 65699c52f6 | |||
| 0baa221276 | |||
| 8ac4e813d3 | |||
| 1d8f2a5806 | |||
| 6754e87947 | |||
| 7d49387279 |
23 changed files with 13879 additions and 8 deletions
61
apply.sh
61
apply.sh
|
|
@ -2,7 +2,7 @@
|
|||
# Apply or reset patches
|
||||
# Usage: apply.sh <command> [options]
|
||||
# Commands:
|
||||
# apply Apply all patches from petergsi subdirectory
|
||||
# apply Apply all patches from asb/<year-month>/ and petergsi subdirectories
|
||||
# reset Reset all repos to their original state
|
||||
|
||||
set -e
|
||||
|
|
@ -12,8 +12,40 @@ pushd "$(dirname "$(realpath "$0")")"
|
|||
COMMAND="${1:-apply}"
|
||||
shift || true
|
||||
|
||||
apply_patches() {
|
||||
apply_asb_patches() {
|
||||
local asb_base="$(realpath asb 2>/dev/null || echo '')"
|
||||
if [ -z "$asb_base" ] || [ ! -d "$asb_base" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local patch_dirs
|
||||
patch_dirs=$(find "$asb_base" -name "*.patch" -printf "%h\n" | sort -u)
|
||||
|
||||
for patch_dir in $patch_dirs; do
|
||||
local repo_path="${patch_dir#$asb_base/}"
|
||||
repo_path="${repo_path#20*/}"
|
||||
local target_dir="$(dirname "$asb_base")/../$repo_path"
|
||||
|
||||
if [ ! -d "$target_dir" ]; then
|
||||
echo "Warning: Repository $repo_path not found for ASB patches, skipping"
|
||||
continue
|
||||
fi
|
||||
|
||||
pushd "$target_dir"
|
||||
|
||||
if ! git am -3 "$patch_dir"/*.patch 2>/dev/null; then
|
||||
echo "Failed to apply ASB patches to $repo_path"
|
||||
echo "ASB patch application failed, aborting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
popd
|
||||
done
|
||||
}
|
||||
|
||||
apply_petergsi_patches() {
|
||||
local patch_base="$(realpath petergsi)"
|
||||
echo "petergsi directory: $patch_base"
|
||||
local failed_file="/tmp/apply_failed_repos_$$.txt"
|
||||
|
||||
if [ ! -d "$patch_base" ]; then
|
||||
|
|
@ -23,9 +55,12 @@ apply_patches() {
|
|||
|
||||
: > "$failed_file"
|
||||
|
||||
find "$patch_base" -name "*.patch" -printf "%h\n" | sort -u | while read -r patch_dir; do
|
||||
repo_path="${patch_dir#$patch_base/}"
|
||||
target_dir="$(dirname "$patch_base")/../$repo_path"
|
||||
local patch_dirs
|
||||
patch_dirs=$(find "$patch_base" -name "*.patch" -printf "%h\n" | sort -u)
|
||||
|
||||
for patch_dir in $patch_dirs; do
|
||||
local repo_path="${patch_dir#$patch_base/}"
|
||||
local target_dir="$(dirname "$patch_base")/../$repo_path"
|
||||
|
||||
if [ ! -d "$target_dir" ]; then
|
||||
echo "Warning: Repository $repo_path not found, skipping"
|
||||
|
|
@ -34,7 +69,10 @@ apply_patches() {
|
|||
|
||||
cd "$target_dir"
|
||||
|
||||
if ! git am "$patch_dir"/*.patch 2>/dev/null; then
|
||||
git tag -d "before-petergsi" 2>/dev/null || true
|
||||
git tag "before-petergsi" 2>/dev/null || true
|
||||
|
||||
if ! git am -3 "$patch_dir"/*.patch 2>/dev/null; then
|
||||
echo "$repo_path" >> "$failed_file"
|
||||
echo "Failed to apply patches to $repo_path"
|
||||
fi
|
||||
|
|
@ -51,7 +89,14 @@ apply_patches() {
|
|||
rm -f "$failed_file"
|
||||
}
|
||||
|
||||
apply_patches() {
|
||||
apply_asb_patches
|
||||
apply_petergsi_patches
|
||||
}
|
||||
|
||||
reset_one() {
|
||||
[ "$(basename "$PWD")" == "patches" ] && return
|
||||
|
||||
check_baseline() {
|
||||
current=$(git rev-parse HEAD)
|
||||
baseline=$REPO_LREV
|
||||
|
|
@ -120,7 +165,7 @@ reset_patches() {
|
|||
|
||||
skipped_count=$(grep -c "^SKIP_REPO:" /tmp/reset_output.txt 2>/dev/null || echo 0)
|
||||
|
||||
if [ "$skipped_count" -gt 0 ]; then
|
||||
if [ "$skipped_count" != "0" ]; then
|
||||
echo ""
|
||||
echo "Skipped the following repositories (not reset):"
|
||||
grep "^SKIP_REPO:" /tmp/reset_output.txt | sed 's/SKIP_REPO://'
|
||||
|
|
@ -140,7 +185,7 @@ case "$COMMAND" in
|
|||
echo "Usage: $0 <apply|reset> [options]"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " apply Apply all patches from petergsi subdirectory"
|
||||
echo " apply Apply all patches from asb/<year-month>/ and petergsi subdirectories"
|
||||
echo " reset Reset all repos to their original state"
|
||||
echo ""
|
||||
echo "Options for reset:"
|
||||
|
|
|
|||
6175
asb/2026-03/external/dng_sdk/2026-01-07_21-35-05_-0800_Update_to_DNG_SDK_171_2410_6b5cf2a88ebd.patch
vendored
Normal file
6175
asb/2026-03/external/dng_sdk/2026-01-07_21-35-05_-0800_Update_to_DNG_SDK_171_2410_6b5cf2a88ebd.patch
vendored
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,36 @@
|
|||
From b51a58ecec96558e1c6b1d47728f45a8795dc7ab Mon Sep 17 00:00:00 2001
|
||||
From: Nate Myren <ntmyren@google.com>
|
||||
Date: Tue, 7 Oct 2025 14:03:49 -0700
|
||||
Subject: [PATCH] Ensure sandboxed UIDs are treated as untrusted in Appops
|
||||
|
||||
They should not be considered "system" app for the purposes of
|
||||
attribution tag vaildation
|
||||
|
||||
Bug: 443742082
|
||||
Test: atest AppOpsMemoryUsageTest
|
||||
Flag: EXEMPT CVE_FIX
|
||||
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:1bc6b146137f76589146dff5cd82363de7ccfb7d
|
||||
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:066eff80abf013531320b8280637d1d00dd553a1
|
||||
Merged-In: I0c4ac8eaa8966027ad01375dde58b05febec3ffb
|
||||
Change-Id: I0c4ac8eaa8966027ad01375dde58b05febec3ffb
|
||||
---
|
||||
services/core/java/com/android/server/appop/AppOpsService.java | 3 +++
|
||||
1 file changed, 3 insertions(+)
|
||||
|
||||
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
|
||||
index ae8a8d943ccc..9c9d338099dd 100644
|
||||
--- a/services/core/java/com/android/server/appop/AppOpsService.java
|
||||
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
|
||||
@@ -5043,6 +5043,9 @@ public class AppOpsService extends IAppOpsService.Stub {
|
||||
if (packageName == null) {
|
||||
return true;
|
||||
}
|
||||
+ if (Process.isSdkSandboxUid(uid)) {
|
||||
+ return false;
|
||||
+ }
|
||||
int appId = UserHandle.getAppId(uid);
|
||||
if (appId > 0 && appId < Process.FIRST_APPLICATION_UID) {
|
||||
return true;
|
||||
--
|
||||
2.53.0
|
||||
|
||||
|
|
@ -0,0 +1,313 @@
|
|||
From a4523e227733ae20eafe4ec3e85474a5b7ebf7c6 Mon Sep 17 00:00:00 2001
|
||||
From: Nate Myren <ntmyren@google.com>
|
||||
Date: Wed, 15 Oct 2025 15:48:49 -0700
|
||||
Subject: [PATCH] Prohibit untrusted proxys from specifying proxied attribution
|
||||
tags
|
||||
|
||||
Only trusted proxies should be allowed to specify tags. Also prevents
|
||||
startOperationDryRun from editing the know attribution tags, as the dry
|
||||
run should change no state.
|
||||
|
||||
Bug: 445917646
|
||||
Test: atest AttributionTest
|
||||
Flag: EXEMPT CVE_FIX
|
||||
(cherry picked from commit 110db0acb84cbd21f9da9391ab242d141ebf390c)
|
||||
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:94d32dce4639ba16b321667719678d2a6b42d9c3
|
||||
Merged-In: I14ab1389384fd28009edd9cceceaacdb97fb96e5
|
||||
Change-Id: I14ab1389384fd28009edd9cceceaacdb97fb96e5
|
||||
---
|
||||
.../android/server/appop/AppOpsService.java | 112 ++++++++++++++----
|
||||
.../android/server/appop/AttributedOp.java | 14 +++
|
||||
2 files changed, 105 insertions(+), 21 deletions(-)
|
||||
|
||||
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
|
||||
index 9c9d338099dd..85a5ac8bf3ca 100644
|
||||
--- a/services/core/java/com/android/server/appop/AppOpsService.java
|
||||
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
|
||||
@@ -39,6 +39,7 @@ import static android.app.AppOpsManager.OP_CAMERA_SANDBOXED;
|
||||
import static android.app.AppOpsManager.OP_FLAGS_ALL;
|
||||
import static android.app.AppOpsManager.OP_FLAG_SELF;
|
||||
import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
|
||||
+import static android.app.AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED;
|
||||
import static android.app.AppOpsManager.OP_NONE;
|
||||
import static android.app.AppOpsManager.OP_PLAY_AUDIO;
|
||||
import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
|
||||
@@ -3189,7 +3190,7 @@ public class AppOpsService extends IAppOpsService.Stub {
|
||||
public int checkPackage(int uid, String packageName) {
|
||||
Objects.requireNonNull(packageName);
|
||||
try {
|
||||
- verifyAndGetBypass(uid, packageName, null, Process.INVALID_UID, null, true);
|
||||
+ verifyAndGetBypass(uid, packageName, null, Process.INVALID_UID, null, true, true);
|
||||
// When the caller is the system, it's possible that the packageName is the special
|
||||
// one (e.g., "root") which isn't actually existed.
|
||||
if (resolveNonAppUid(packageName) == uid
|
||||
@@ -3407,8 +3408,10 @@ public class AppOpsService extends IAppOpsService.Stub {
|
||||
@OpFlags int flags, boolean shouldCollectAsyncNotedOp, @Nullable String message,
|
||||
boolean shouldCollectMessage, int notedCount) {
|
||||
PackageVerificationResult pvr;
|
||||
+ boolean proxyTrusted = (flags & OP_FLAG_UNTRUSTED_PROXIED) == 0;
|
||||
try {
|
||||
- pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName);
|
||||
+ pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName,
|
||||
+ proxyTrusted);
|
||||
if (!pvr.isAttributionTagValid) {
|
||||
attributionTag = null;
|
||||
}
|
||||
@@ -4072,8 +4075,10 @@ public class AppOpsService extends IAppOpsService.Stub {
|
||||
boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
|
||||
int attributionChainId) {
|
||||
PackageVerificationResult pvr;
|
||||
+ boolean proxyTrusted = (flags & OP_FLAG_UNTRUSTED_PROXIED) == 0;
|
||||
try {
|
||||
- pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName);
|
||||
+ pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName,
|
||||
+ proxyTrusted);
|
||||
if (!pvr.isAttributionTagValid) {
|
||||
attributionTag = null;
|
||||
}
|
||||
@@ -4206,8 +4211,10 @@ public class AppOpsService extends IAppOpsService.Stub {
|
||||
int proxyUid, String proxyPackageName, @OpFlags int flags,
|
||||
boolean startIfModeDefault) {
|
||||
PackageVerificationResult pvr;
|
||||
+ boolean proxyTrusted = (flags & OP_FLAG_UNTRUSTED_PROXIED) == 0;
|
||||
try {
|
||||
- pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName);
|
||||
+ pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName,
|
||||
+ proxyTrusted);
|
||||
if (!pvr.isAttributionTagValid) {
|
||||
attributionTag = null;
|
||||
}
|
||||
@@ -4223,7 +4230,9 @@ public class AppOpsService extends IAppOpsService.Stub {
|
||||
|
||||
boolean isRestricted = false;
|
||||
synchronized (this) {
|
||||
- final Ops ops = getOpsLocked(uid, packageName, attributionTag,
|
||||
+ // Edit is true (so we create the Ops object if needed), but attribution tag is given as
|
||||
+ // null, so we don't cache any information about it.
|
||||
+ final Ops ops = getOpsLocked(uid, packageName, null,
|
||||
pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
|
||||
if (ops == null) {
|
||||
if (DEBUG) {
|
||||
@@ -4402,8 +4411,11 @@ public class AppOpsService extends IAppOpsService.Stub {
|
||||
int virtualDeviceId) {
|
||||
PackageVerificationResult pvr;
|
||||
try {
|
||||
+ // assume the proxy is trusted, since we aren't sure. We'll search for the attribution
|
||||
+ // tag with trusted flags, and if we don't find it, search for a null tag with
|
||||
+ // untrusted flags
|
||||
pvr = verifyAndGetBypass(proxiedUid, proxiedPackageName, attributionTag,
|
||||
- proxyUid, proxyPackageName);
|
||||
+ proxyUid, proxyPackageName, /* isProxyTrusted */ true);
|
||||
if (!pvr.isAttributionTagValid) {
|
||||
attributionTag = null;
|
||||
}
|
||||
@@ -4413,8 +4425,9 @@ public class AppOpsService extends IAppOpsService.Stub {
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
+ boolean hasProxy = proxyUid != Process.INVALID_UID;
|
||||
Op op = getOpLocked(code, proxiedUid, proxiedPackageName, attributionTag,
|
||||
- pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
|
||||
+ pvr.isAttributionTagValid, pvr.bypass, /* edit */ false);
|
||||
if (op == null) {
|
||||
Slog.e(TAG, "Operation not found: uid=" + proxiedUid + " pkg=" + proxiedPackageName
|
||||
+ "("
|
||||
@@ -4422,9 +4435,8 @@ public class AppOpsService extends IAppOpsService.Stub {
|
||||
return;
|
||||
}
|
||||
final AttributedOp attributedOp =
|
||||
- op.mDeviceAttributedOps.getOrDefault(
|
||||
- getPersistentDeviceIdForOp(virtualDeviceId, code),
|
||||
- new ArrayMap<>()).get(attributionTag);
|
||||
+ getAttributedOpWithClientId(op, clientId, attributionTag, virtualDeviceId,
|
||||
+ hasProxy);
|
||||
if (attributedOp == null) {
|
||||
Slog.e(TAG, "Attribution not found: uid=" + proxiedUid
|
||||
+ " pkg=" + proxiedPackageName + "("
|
||||
@@ -4442,6 +4454,54 @@ public class AppOpsService extends IAppOpsService.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
+ private AttributedOp getAttributedOpWithClientId(Op op, IBinder clientId,
|
||||
+ String attributionTag, int virtualDeviceId, boolean hasProxy) {
|
||||
+ AttributedOp attributedOp =
|
||||
+ op.mDeviceAttributedOps.getOrDefault(
|
||||
+ getPersistentDeviceIdForOp(virtualDeviceId, op.op),
|
||||
+ new ArrayMap<>()).get(attributionTag);
|
||||
+ if (!hasProxy) {
|
||||
+ return attributedOp;
|
||||
+ }
|
||||
+ boolean hasTrustedInProgressEvent = attributedOp != null
|
||||
+ && attributedOp.hasInProgressEvent((event -> event.getClientId() == clientId
|
||||
+ && (event.getFlags() & OP_FLAG_UNTRUSTED_PROXIED) == 0));
|
||||
+ if (hasTrustedInProgressEvent) {
|
||||
+ return attributedOp;
|
||||
+ }
|
||||
+
|
||||
+ // We failed to find a trusted in progress event that matches the clientId. Check if the
|
||||
+ // tag is valid in the package, and look for an untrusted access matching that tag, if so
|
||||
+ boolean tagValid = false;
|
||||
+ try {
|
||||
+ tagValid = verifyAndGetBypass(op.uid, op.packageName, attributionTag)
|
||||
+ .isAttributionTagValid;
|
||||
+ } catch (SecurityException e) {
|
||||
+ // assume tag is invalid
|
||||
+ }
|
||||
+ if (tagValid) {
|
||||
+ boolean hasUntrustedInProgressEvent = attributedOp != null
|
||||
+ && attributedOp.hasInProgressEvent((event -> event.getClientId() == clientId
|
||||
+ && (event.getFlags() & OP_FLAG_UNTRUSTED_PROXIED) != 0));
|
||||
+ if (hasUntrustedInProgressEvent) {
|
||||
+ return attributedOp;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // The tag was not valid, or we failed to find an untrusted event. Look for an untrusted
|
||||
+ // event with the null attribution tag
|
||||
+ attributedOp = op.mDeviceAttributedOps.getOrDefault(
|
||||
+ getPersistentDeviceIdForOp(virtualDeviceId, op.op),
|
||||
+ new ArrayMap<>()).get(null);
|
||||
+ boolean hasUntrustedNullEvent = attributedOp != null
|
||||
+ && attributedOp.hasInProgressEvent((event -> event.getClientId() == clientId
|
||||
+ && (event.getFlags() & OP_FLAG_UNTRUSTED_PROXIED) != 0));
|
||||
+ if (hasUntrustedNullEvent) {
|
||||
+ return attributedOp;
|
||||
+ }
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull
|
||||
String packageName, @Nullable String attributionTag, int virtualDeviceId,
|
||||
boolean active, @AttributionFlags int attributionFlags, int attributionChainId) {
|
||||
@@ -4881,20 +4941,22 @@ public class AppOpsService extends IAppOpsService.Stub {
|
||||
}
|
||||
|
||||
/**
|
||||
- * @see #verifyAndGetBypass(int, String, String, int, String, boolean)
|
||||
+ * @see #verifyAndGetBypass(int, String, String, int, String, boolean, boolean)
|
||||
*/
|
||||
private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
|
||||
@Nullable String attributionTag) {
|
||||
- return verifyAndGetBypass(uid, packageName, attributionTag, Process.INVALID_UID, null);
|
||||
+ return verifyAndGetBypass(uid, packageName, attributionTag, Process.INVALID_UID, null,
|
||||
+ true);
|
||||
}
|
||||
|
||||
/**
|
||||
- * @see #verifyAndGetBypass(int, String, String, int, String, boolean)
|
||||
+ * @see #verifyAndGetBypass(int, String, String, int, String, boolean, boolean)
|
||||
*/
|
||||
private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
|
||||
- @Nullable String attributionTag, int proxyUid, @Nullable String proxyPackageName) {
|
||||
+ @Nullable String attributionTag, int proxyUid, @Nullable String proxyPackageName,
|
||||
+ boolean isProxyTrusted) {
|
||||
return verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName,
|
||||
- false);
|
||||
+ isProxyTrusted, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4907,6 +4969,8 @@ public class AppOpsService extends IAppOpsService.Stub {
|
||||
* @param attributionTag attribution tag or {@code null} if no need to verify
|
||||
* @param proxyUid The proxy uid, from which the attribution tag is to be pulled
|
||||
* @param proxyPackageName The proxy package, from which the attribution tag may be pulled
|
||||
+ * @param isProxyTrusted Whether or not the proxy package is trusted. If it isn't, then the
|
||||
+ * proxy attribution tag will not be used
|
||||
* @param suppressErrorLogs Whether to print to logcat about nonmatching parameters
|
||||
*
|
||||
* @return PackageVerificationResult containing {@link RestrictionBypass} and whether the
|
||||
@@ -4914,7 +4978,7 @@ public class AppOpsService extends IAppOpsService.Stub {
|
||||
*/
|
||||
private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
|
||||
@Nullable String attributionTag, int proxyUid, @Nullable String proxyPackageName,
|
||||
- boolean suppressErrorLogs) {
|
||||
+ boolean isProxyTrusted, boolean suppressErrorLogs) {
|
||||
if (uid == Process.ROOT_UID) {
|
||||
// For backwards compatibility, don't check package name for root UID, unless someone
|
||||
// is claiming to be a proxy for root, which should never happen in normal usage.
|
||||
@@ -4922,7 +4986,8 @@ public class AppOpsService extends IAppOpsService.Stub {
|
||||
// system app (or is null), in order to prevent abusive apps clogging the appops
|
||||
// system with unlimited attribution tags via proxy calls.
|
||||
return new PackageVerificationResult(null,
|
||||
- /* isAttributionTagValid */ isPackageNullOrSystem(proxyPackageName, proxyUid));
|
||||
+ /* isAttributionTagValid */ isProxyTrusted
|
||||
+ && isPackageNullOrSystem(proxyPackageName, proxyUid));
|
||||
}
|
||||
if (Process.isSdkSandboxUid(uid)) {
|
||||
// SDK sandbox processes run in their own UID range, but their associated
|
||||
@@ -4986,7 +5051,8 @@ public class AppOpsService extends IAppOpsService.Stub {
|
||||
// system app (or is null), in order to prevent abusive apps clogging the appops
|
||||
// system with unlimited attribution tags via proxy calls.
|
||||
return new PackageVerificationResult(RestrictionBypass.UNRESTRICTED,
|
||||
- /* isAttributionTagValid */ isPackageNullOrSystem(proxyPackageName, proxyUid));
|
||||
+ /* isAttributionTagValid */ isProxyTrusted
|
||||
+ && isPackageNullOrSystem(proxyPackageName, proxyUid));
|
||||
}
|
||||
|
||||
int userId = UserHandle.getUserId(uid);
|
||||
@@ -5007,8 +5073,9 @@ public class AppOpsService extends IAppOpsService.Stub {
|
||||
if (!isAttributionTagValid) {
|
||||
AndroidPackage proxyPkg = proxyPackageName != null
|
||||
? pmInt.getPackage(proxyPackageName) : null;
|
||||
- // Re-check in proxy.
|
||||
- isAttributionTagValid = isAttributionInPackage(proxyPkg, attributionTag);
|
||||
+ // Re-check in proxy, if trusted.
|
||||
+ isAttributionTagValid =
|
||||
+ isProxyTrusted && isAttributionInPackage(proxyPkg, attributionTag);
|
||||
String msg;
|
||||
if (pkg != null && isAttributionTagValid) {
|
||||
msg = "attributionTag " + attributionTag + " declared in manifest of the proxy"
|
||||
@@ -5035,7 +5102,6 @@ public class AppOpsService extends IAppOpsService.Stub {
|
||||
throw new SecurityException("Specified package \"" + packageName + "\" under uid " + uid
|
||||
+ otherUidMessage);
|
||||
}
|
||||
-
|
||||
return new PackageVerificationResult(bypass, isAttributionTagValid);
|
||||
}
|
||||
|
||||
@@ -5050,6 +5116,10 @@ public class AppOpsService extends IAppOpsService.Stub {
|
||||
if (appId > 0 && appId < Process.FIRST_APPLICATION_UID) {
|
||||
return true;
|
||||
}
|
||||
+ if (mPackageManagerInternal.getPackageUid(packageName, PackageManager.MATCH_ALL,
|
||||
+ UserHandle.getUserId(uid)) != uid) {
|
||||
+ return false;
|
||||
+ }
|
||||
return mPackageManagerInternal.isSystemPackage(packageName);
|
||||
}
|
||||
|
||||
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
|
||||
index b9bc27d9c696..73f9fede9ed6 100644
|
||||
--- a/services/core/java/com/android/server/appop/AttributedOp.java
|
||||
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
|
||||
@@ -40,6 +40,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.function.Consumer;
|
||||
+import java.util.function.Predicate;
|
||||
|
||||
final class AttributedOp {
|
||||
private final @NonNull AppOpsService mAppOpsService;
|
||||
@@ -620,6 +621,19 @@ final class AttributedOp {
|
||||
return mPausedInProgressEvents != null && !mPausedInProgressEvents.isEmpty();
|
||||
}
|
||||
|
||||
+ public boolean hasInProgressEvent(Predicate<InProgressStartOpEvent> predicate) {
|
||||
+ ArrayMap<IBinder, InProgressStartOpEvent> events =
|
||||
+ isPaused() ? mPausedInProgressEvents : mInProgressEvents;
|
||||
+ if (events == null || events.isEmpty()) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ for (int i = 0; i < events.size(); i++) {
|
||||
+ if (predicate.test(events.valueAt(i))) {
|
||||
+ return true;
|
||||
+ }
|
||||
+ }
|
||||
+ return false;
|
||||
+ }
|
||||
boolean hasAnyTime() {
|
||||
return (mAccessEvents != null && mAccessEvents.size() > 0)
|
||||
|| (mRejectEvents != null && mRejectEvents.size() > 0);
|
||||
--
|
||||
2.53.0
|
||||
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
From e770e9f0234158f4631c7147b64a1d70e0843d0b Mon Sep 17 00:00:00 2001
|
||||
From: Nate Myren <ntmyren@google.com>
|
||||
Date: Wed, 5 Nov 2025 14:36:49 -0800
|
||||
Subject: [PATCH] Trim permission, permission group names
|
||||
|
||||
Bug: 453649815
|
||||
Test: atest AppSecurityTests
|
||||
Flag: EXEMPT CVE_FIX
|
||||
(cherry picked from commit 595cf99ecd42927eebf804638a4623313f3f14db)
|
||||
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:08ea2a452c271ccf258d63efc0126c7fa13d3312
|
||||
Merged-In: I673ad83d05c9825177967e4f0a960e8841610b71
|
||||
Change-Id: I673ad83d05c9825177967e4f0a960e8841610b71
|
||||
---
|
||||
.../internal/pm/pkg/component/ParsedPermissionUtils.java | 7 ++++++-
|
||||
1 file changed, 6 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedPermissionUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedPermissionUtils.java
|
||||
index d4dabf51d4c7..0dd0969ae35e 100644
|
||||
--- a/core/java/com/android/internal/pm/pkg/component/ParsedPermissionUtils.java
|
||||
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedPermissionUtils.java
|
||||
@@ -152,6 +152,8 @@ public class ParsedPermissionUtils {
|
||||
}
|
||||
}
|
||||
|
||||
+ permission.setName(permission.getName().trim());
|
||||
+
|
||||
permission.setProtectionLevel(
|
||||
PermissionInfo.fixProtectionLevel(permission.getProtectionLevel()));
|
||||
|
||||
@@ -199,6 +201,8 @@ public class ParsedPermissionUtils {
|
||||
sa.recycle();
|
||||
}
|
||||
|
||||
+ permission.setName(permission.getName().trim());
|
||||
+
|
||||
int index = permission.getName().indexOf('.');
|
||||
if (index > 0) {
|
||||
index = permission.getName().indexOf('.', index + 1);
|
||||
@@ -248,7 +252,8 @@ public class ParsedPermissionUtils {
|
||||
.setBackgroundRequestDetailRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_backgroundRequestDetail, 0))
|
||||
.setRequestRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_request, 0))
|
||||
.setPriority(sa.getInt(R.styleable.AndroidManifestPermissionGroup_priority, 0))
|
||||
- .setFlags(sa.getInt(R.styleable.AndroidManifestPermissionGroup_permissionGroupFlags,0));
|
||||
+ .setFlags(sa.getInt(R.styleable.AndroidManifestPermissionGroup_permissionGroupFlags,0))
|
||||
+ .setName(permissionGroup.getName().trim());
|
||||
// @formatter:on
|
||||
} finally {
|
||||
sa.recycle();
|
||||
--
|
||||
2.53.0
|
||||
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
From cea235f00865ff73344f1efa9494e47beecc3fd5 Mon Sep 17 00:00:00 2001
|
||||
From: Shawn Lin <shawnlin@google.com>
|
||||
Date: Wed, 15 Oct 2025 12:18:38 +0000
|
||||
Subject: [PATCH] Fixed "Unlock your phone" unexpectedlly turned ON after OTA
|
||||
|
||||
If the new setting key is not set, we should use the value of the old
|
||||
ones as the default value.
|
||||
|
||||
Bug: 444673089
|
||||
Test: atest BiometricServiceTest
|
||||
Flag: EXEMPT BUGFIX
|
||||
(cherry picked from commit a3799e443e9fdbbcbc96824b8d2fc1e4c683bb7e)
|
||||
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:2b8bc2b15f2aceb4e2a379209997afce16427e76
|
||||
Merged-In: I902c7fd9781037ba05b30821b4fa22aa1509f3fd
|
||||
Change-Id: I902c7fd9781037ba05b30821b4fa22aa1509f3fd
|
||||
---
|
||||
.../server/biometrics/BiometricService.java | 45 ++++++++-
|
||||
.../biometrics/BiometricServiceTest.java | 91 +++++++++++++++++++
|
||||
2 files changed, 132 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
|
||||
index cf5fa9699ca9..7485929147e5 100644
|
||||
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
|
||||
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
|
||||
@@ -412,20 +412,39 @@ public class BiometricService extends SystemService {
|
||||
notifyEnabledOnKeyguardCallbacks(userId, TYPE_ANY_BIOMETRIC);
|
||||
}
|
||||
} else if (FACE_KEYGUARD_ENABLED.equals(uri)) {
|
||||
+ final int biometricKeyguardEnabled = Settings.Secure.getIntForUser(
|
||||
+ mContentResolver,
|
||||
+ Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED,
|
||||
+ -1 /* default */,
|
||||
+ userId);
|
||||
+ // For OTA case: if FACE_KEYGUARD_ENABLED is not set and BIOMETRIC_APP_ENABLED is
|
||||
+ // set, set the default value of the former to that of the latter.
|
||||
+ final boolean defaultValue = biometricKeyguardEnabled == -1
|
||||
+ ? DEFAULT_KEYGUARD_ENABLED : biometricKeyguardEnabled == 1;
|
||||
mFaceEnabledOnKeyguard.put(userId, Settings.Secure.getIntForUser(
|
||||
mContentResolver,
|
||||
Settings.Secure.FACE_KEYGUARD_ENABLED,
|
||||
- DEFAULT_KEYGUARD_ENABLED ? 1 : 0 /* default */,
|
||||
+ defaultValue ? 1 : 0 /* default */,
|
||||
userId) != 0);
|
||||
|
||||
if (userId == ActivityManager.getCurrentUser() && !selfChange) {
|
||||
notifyEnabledOnKeyguardCallbacks(userId, TYPE_FACE);
|
||||
}
|
||||
} else if (FINGERPRINT_KEYGUARD_ENABLED.equals(uri)) {
|
||||
+ final int biometricKeyguardEnabled = Settings.Secure.getIntForUser(
|
||||
+ mContentResolver,
|
||||
+ Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED,
|
||||
+ -1 /* default */,
|
||||
+ userId);
|
||||
+ // For OTA case: if FINGERPRINT_KEYGUARD_ENABLED is not set and
|
||||
+ // BIOMETRIC_APP_ENABLED is set, set the default value of the former to that of the
|
||||
+ // latter.
|
||||
+ final boolean defaultValue = biometricKeyguardEnabled == -1
|
||||
+ ? DEFAULT_KEYGUARD_ENABLED : biometricKeyguardEnabled == 1;
|
||||
mFingerprintEnabledOnKeyguard.put(userId, Settings.Secure.getIntForUser(
|
||||
mContentResolver,
|
||||
Settings.Secure.FINGERPRINT_KEYGUARD_ENABLED,
|
||||
- DEFAULT_KEYGUARD_ENABLED ? 1 : 0 /* default */,
|
||||
+ defaultValue ? 1 : 0 /* default */,
|
||||
userId) != 0);
|
||||
|
||||
if (userId == ActivityManager.getCurrentUser() && !selfChange) {
|
||||
@@ -438,16 +457,34 @@ public class BiometricService extends SystemService {
|
||||
DEFAULT_APP_ENABLED ? 1 : 0 /* default */,
|
||||
userId) != 0);
|
||||
} else if (FACE_APP_ENABLED.equals(uri)) {
|
||||
+ final int biometricAppEnabled = Settings.Secure.getIntForUser(
|
||||
+ mContentResolver,
|
||||
+ Settings.Secure.BIOMETRIC_APP_ENABLED,
|
||||
+ -1 /* default */,
|
||||
+ userId);
|
||||
+ // For OTA case: if FACE_APP_ENABLED is not set and BIOMETRIC_APP_ENABLED is set,
|
||||
+ // set the default value of the former to that of the latter.
|
||||
+ final boolean defaultValue = biometricAppEnabled == -1
|
||||
+ ? DEFAULT_APP_ENABLED : biometricAppEnabled == 1;
|
||||
mFaceEnabledForApps.put(userId, Settings.Secure.getIntForUser(
|
||||
mContentResolver,
|
||||
Settings.Secure.FACE_APP_ENABLED,
|
||||
- DEFAULT_APP_ENABLED ? 1 : 0 /* default */,
|
||||
+ defaultValue ? 1 : 0 /* default */,
|
||||
userId) != 0);
|
||||
} else if (FINGERPRINT_APP_ENABLED.equals(uri)) {
|
||||
+ final int biometricAppEnabled = Settings.Secure.getIntForUser(
|
||||
+ mContentResolver,
|
||||
+ Settings.Secure.BIOMETRIC_APP_ENABLED,
|
||||
+ -1 /* default */,
|
||||
+ userId);
|
||||
+ // For OTA case: if FINGERPRINT_APP_ENABLED is not set and BIOMETRIC_APP_ENABLED is
|
||||
+ // set, set the default value of the former to that of the latter.
|
||||
+ final boolean defaultValue = biometricAppEnabled == -1
|
||||
+ ? DEFAULT_APP_ENABLED : biometricAppEnabled == 1;
|
||||
mFingerprintEnabledForApps.put(userId, Settings.Secure.getIntForUser(
|
||||
mContentResolver,
|
||||
Settings.Secure.FINGERPRINT_APP_ENABLED,
|
||||
- DEFAULT_APP_ENABLED ? 1 : 0 /* default */,
|
||||
+ defaultValue ? 1 : 0 /* default */,
|
||||
userId) != 0);
|
||||
} else if (MANDATORY_BIOMETRICS_ENABLED.equals(uri)) {
|
||||
updateMandatoryBiometricsForAllProfiles(userId);
|
||||
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
|
||||
index 9918a9a35c33..3061bab24518 100644
|
||||
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
|
||||
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
|
||||
@@ -21,6 +21,12 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_CREDENTIAL
|
||||
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
|
||||
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
|
||||
import static android.hardware.biometrics.BiometricManager.Authenticators;
|
||||
+import static android.provider.Settings.Secure.BIOMETRIC_APP_ENABLED;
|
||||
+import static android.provider.Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED;
|
||||
+import static android.provider.Settings.Secure.FACE_APP_ENABLED;
|
||||
+import static android.provider.Settings.Secure.FACE_KEYGUARD_ENABLED;
|
||||
+import static android.provider.Settings.Secure.FINGERPRINT_APP_ENABLED;
|
||||
+import static android.provider.Settings.Secure.FINGERPRINT_KEYGUARD_ENABLED;
|
||||
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
|
||||
|
||||
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTHENTICATED_PENDING_SYSUI;
|
||||
@@ -40,6 +46,7 @@ import static junit.framework.TestCase.assertNotNull;
|
||||
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
+import static org.junit.Assume.assumeTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
@@ -2147,6 +2154,90 @@ public class BiometricServiceTest {
|
||||
invokeCanAuthenticate(mBiometricService, Authenticators.BIOMETRIC_STRONG));
|
||||
}
|
||||
|
||||
+ @Test
|
||||
+ @RequiresFlagsEnabled(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
|
||||
+ public void
|
||||
+ testEnabledForApps_biometricAppEnableOff_fpAppEnabledNotSet_returnFalse()
|
||||
+ throws Exception {
|
||||
+ final Context context = ApplicationProvider.getApplicationContext();
|
||||
+ final int value = Settings.Secure.getIntForUser(context.getContentResolver(),
|
||||
+ FINGERPRINT_APP_ENABLED, -1, context.getUserId());
|
||||
+ assumeTrue("FINGERPRINT_APP_ENABLED is set. Skipped", value == -1);
|
||||
+
|
||||
+ Settings.Secure.putIntForUser(context.getContentResolver(),
|
||||
+ BIOMETRIC_APP_ENABLED, 0, context.getUserId());
|
||||
+
|
||||
+ final BiometricService.SettingObserver settingObserver =
|
||||
+ new BiometricService.SettingObserver(
|
||||
+ context, mBiometricHandlerProvider.getBiometricCallbackHandler(),
|
||||
+ new ArrayList<>(), mUserManager, mFingerprintManager, mFaceManager);
|
||||
+
|
||||
+ assertFalse(settingObserver.getEnabledForApps(context.getUserId(), TYPE_FINGERPRINT));
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ @RequiresFlagsEnabled(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
|
||||
+ public void
|
||||
+ testKeyguardEnabled_biometricKeyguardEnableOff_fpKeyguardEnabledNotSet_returnFalse()
|
||||
+ throws Exception {
|
||||
+ final Context context = ApplicationProvider.getApplicationContext();
|
||||
+ final int value = Settings.Secure.getIntForUser(context.getContentResolver(),
|
||||
+ FINGERPRINT_KEYGUARD_ENABLED, -1, context.getUserId());
|
||||
+ assumeTrue("FINGERPRINT_KEYGUARD_ENABLED is set. Skipped", value == -1);
|
||||
+
|
||||
+ Settings.Secure.putIntForUser(context.getContentResolver(),
|
||||
+ BIOMETRIC_KEYGUARD_ENABLED, 0, context.getUserId());
|
||||
+
|
||||
+ final BiometricService.SettingObserver settingObserver =
|
||||
+ new BiometricService.SettingObserver(
|
||||
+ context, mBiometricHandlerProvider.getBiometricCallbackHandler(),
|
||||
+ new ArrayList<>(), mUserManager, mFingerprintManager, mFaceManager);
|
||||
+
|
||||
+ assertFalse(settingObserver.getEnabledOnKeyguard(context.getUserId(), TYPE_FINGERPRINT));
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ @RequiresFlagsEnabled(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
|
||||
+ public void
|
||||
+ testEnabledForApps_biometricAppEnableOff_faceAppEnabledNotSet_returnFalse()
|
||||
+ throws Exception {
|
||||
+ final Context context = ApplicationProvider.getApplicationContext();
|
||||
+ final int value = Settings.Secure.getIntForUser(context.getContentResolver(),
|
||||
+ FACE_APP_ENABLED, -1, context.getUserId());
|
||||
+ assumeTrue("FACE_APP_ENABLED is set. Skipped", value == -1);
|
||||
+
|
||||
+ Settings.Secure.putIntForUser(context.getContentResolver(),
|
||||
+ BIOMETRIC_APP_ENABLED, 0, context.getUserId());
|
||||
+
|
||||
+ final BiometricService.SettingObserver settingObserver =
|
||||
+ new BiometricService.SettingObserver(
|
||||
+ context, mBiometricHandlerProvider.getBiometricCallbackHandler(),
|
||||
+ new ArrayList<>(), mUserManager, mFingerprintManager, mFaceManager);
|
||||
+
|
||||
+ assertFalse(settingObserver.getEnabledForApps(context.getUserId(), TYPE_FACE));
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ @RequiresFlagsEnabled(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
|
||||
+ public void
|
||||
+ testKeyguardEnabled_biometricKeyguardEnableOff_faceKeyguardEnabledNotSet_returnFalse()
|
||||
+ throws Exception {
|
||||
+ final Context context = ApplicationProvider.getApplicationContext();
|
||||
+ final int value = Settings.Secure.getIntForUser(context.getContentResolver(),
|
||||
+ FACE_KEYGUARD_ENABLED, -1, context.getUserId());
|
||||
+ assumeTrue("FACE_KEYGUARD_ENABLED is set. Skipped", value == -1);
|
||||
+
|
||||
+ Settings.Secure.putIntForUser(context.getContentResolver(),
|
||||
+ BIOMETRIC_KEYGUARD_ENABLED, 0, context.getUserId());
|
||||
+
|
||||
+ final BiometricService.SettingObserver settingObserver =
|
||||
+ new BiometricService.SettingObserver(
|
||||
+ context, mBiometricHandlerProvider.getBiometricCallbackHandler(),
|
||||
+ new ArrayList<>(), mUserManager, mFingerprintManager, mFaceManager);
|
||||
+
|
||||
+ assertFalse(settingObserver.getEnabledOnKeyguard(context.getUserId(), TYPE_FACE));
|
||||
+ }
|
||||
+
|
||||
// Helper methods
|
||||
|
||||
private int invokeCanAuthenticate(BiometricService service, int authenticators)
|
||||
--
|
||||
2.53.0
|
||||
|
||||
|
|
@ -0,0 +1,496 @@
|
|||
From 304bb7de32dea642932c73e09ec1bb2ef6cbf3d5 Mon Sep 17 00:00:00 2001
|
||||
From: Hiroki Sato <hirokisato@google.com>
|
||||
Date: Mon, 20 Oct 2025 19:13:15 +0900
|
||||
Subject: [PATCH] Harden InputMethodInfo parsing against large metadata
|
||||
|
||||
An IME's metadata can reference arbitrarily large strings (e.g.,
|
||||
@string/large_text), which can lead to OOM or large Binder transactions
|
||||
during parsing. The previous check only validated the raw XML file
|
||||
size, failing to account for the size of these resolved string
|
||||
references.
|
||||
|
||||
This patch hardens the InputMethodInfo constructor by enforcing a 200KB
|
||||
cumulative limit on all resolved metadata attributes. A new
|
||||
MetadataReadBytesTracker now sums the actual size of all read
|
||||
attributes, including the full length of any strings, and parsing is
|
||||
aborted if this 200KB limit is exceeded.
|
||||
|
||||
Bug: 449416164
|
||||
Bug: 449181366
|
||||
Bug: 449393786
|
||||
Bug: 449227003
|
||||
Test: CtsInputMethodTestCases:{InputMethodRegistrationTest,InputMethodInfoTest}
|
||||
Test: InputMethodCoreTests:{InputMethodSubtypeArrayTest,InputMethodInfoTest}
|
||||
Flag: EXEMPT BUGFIX
|
||||
(cherry picked from commit 7afc13faace7cfafd0353482db33504c5e269d69)
|
||||
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:75fd0e67bd2945d7314b56c24850bd9f1c2c4dbf
|
||||
Merged-In: I43f7be8eb80abeb39863a3b01d3a606beb90120c
|
||||
Change-Id: I43f7be8eb80abeb39863a3b01d3a606beb90120c
|
||||
---
|
||||
.../view/inputmethod/InputMethodInfo.java | 280 ++++++++++++++----
|
||||
.../view/inputmethod/InputMethodInfoTest.java | 98 ++++++
|
||||
2 files changed, 321 insertions(+), 57 deletions(-)
|
||||
|
||||
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
|
||||
index c9485d7d3b0f4..184fea5be0f57 100644
|
||||
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
|
||||
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
|
||||
@@ -50,6 +50,8 @@
|
||||
import android.util.Xml;
|
||||
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
|
||||
|
||||
+import com.android.internal.annotations.VisibleForTesting;
|
||||
+
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
@@ -313,68 +315,70 @@ public InputMethodInfo(Context context, ResolveInfo service,
|
||||
"Meta-data does not start with input-method tag");
|
||||
}
|
||||
|
||||
- TypedArray sa = res.obtainAttributes(attrs,
|
||||
- com.android.internal.R.styleable.InputMethod);
|
||||
- settingsActivityComponent = sa.getString(
|
||||
- com.android.internal.R.styleable.InputMethod_settingsActivity);
|
||||
- languageSettingsActivityComponent = sa.getString(
|
||||
- com.android.internal.R.styleable.InputMethod_languageSettingsActivity);
|
||||
- if ((si.name != null && si.name.length() > COMPONENT_NAME_MAX_LENGTH)
|
||||
- || (settingsActivityComponent != null
|
||||
- && settingsActivityComponent.length()
|
||||
- > COMPONENT_NAME_MAX_LENGTH)
|
||||
- || (languageSettingsActivityComponent != null
|
||||
- && languageSettingsActivityComponent.length()
|
||||
- > COMPONENT_NAME_MAX_LENGTH)) {
|
||||
- throw new XmlPullParserException(
|
||||
- "Activity name exceeds maximum of 1000 characters");
|
||||
+ final MetadataReadBytesTracker readTracker = new MetadataReadBytesTracker();
|
||||
+ try (TypedArrayWrapper sa = TypedArrayWrapper.createForMethod(
|
||||
+ res.obtainAttributes(attrs, com.android.internal.R.styleable.InputMethod),
|
||||
+ readTracker)) {
|
||||
+ settingsActivityComponent = sa.getString(
|
||||
+ com.android.internal.R.styleable.InputMethod_settingsActivity);
|
||||
+ languageSettingsActivityComponent = sa.getString(
|
||||
+ com.android.internal.R.styleable.InputMethod_languageSettingsActivity);
|
||||
+ isVrOnly = sa.getBoolean(com.android.internal.R.styleable.InputMethod_isVrOnly,
|
||||
+ false);
|
||||
+ isVirtualDeviceOnly = sa.getBoolean(
|
||||
+ com.android.internal.R.styleable.InputMethod_isVirtualDeviceOnly, false);
|
||||
+ isDefaultResId = sa.getResourceId(
|
||||
+ com.android.internal.R.styleable.InputMethod_isDefault, 0);
|
||||
+ supportsSwitchingToNextInputMethod = sa.getBoolean(
|
||||
+ com.android.internal.R.styleable
|
||||
+ .InputMethod_supportsSwitchingToNextInputMethod,
|
||||
+ false);
|
||||
+ inlineSuggestionsEnabled = sa.getBoolean(
|
||||
+ com.android.internal.R.styleable.InputMethod_supportsInlineSuggestions,
|
||||
+ false);
|
||||
+ supportsInlineSuggestionsWithTouchExploration = sa.getBoolean(
|
||||
+ com.android.internal.R.styleable
|
||||
+ .InputMethod_supportsInlineSuggestionsWithTouchExploration, false);
|
||||
+ suppressesSpellChecker = sa.getBoolean(
|
||||
+ com.android.internal.R.styleable.InputMethod_suppressesSpellChecker, false);
|
||||
+ showInInputMethodPicker = sa.getBoolean(
|
||||
+ com.android.internal.R.styleable.InputMethod_showInInputMethodPicker, true);
|
||||
+ mHandledConfigChanges = sa.getInt(
|
||||
+ com.android.internal.R.styleable.InputMethod_configChanges, 0);
|
||||
+ mSupportsStylusHandwriting = sa.getBoolean(
|
||||
+ com.android.internal.R.styleable.InputMethod_supportsStylusHandwriting,
|
||||
+ false);
|
||||
+ mSupportsConnectionlessStylusHandwriting = sa.getBoolean(
|
||||
+ com.android.internal.R.styleable
|
||||
+ .InputMethod_supportsConnectionlessStylusHandwriting, false);
|
||||
+ stylusHandwritingSettingsActivity = sa.getString(
|
||||
+ com.android.internal.R.styleable
|
||||
+ .InputMethod_stylusHandwritingSettingsActivity);
|
||||
}
|
||||
|
||||
- isVrOnly = sa.getBoolean(com.android.internal.R.styleable.InputMethod_isVrOnly, false);
|
||||
- isVirtualDeviceOnly = sa.getBoolean(
|
||||
- com.android.internal.R.styleable.InputMethod_isVirtualDeviceOnly, false);
|
||||
- isDefaultResId = sa.getResourceId(
|
||||
- com.android.internal.R.styleable.InputMethod_isDefault, 0);
|
||||
- supportsSwitchingToNextInputMethod = sa.getBoolean(
|
||||
- com.android.internal.R.styleable.InputMethod_supportsSwitchingToNextInputMethod,
|
||||
- false);
|
||||
- inlineSuggestionsEnabled = sa.getBoolean(
|
||||
- com.android.internal.R.styleable.InputMethod_supportsInlineSuggestions, false);
|
||||
- supportsInlineSuggestionsWithTouchExploration = sa.getBoolean(
|
||||
- com.android.internal.R.styleable
|
||||
- .InputMethod_supportsInlineSuggestionsWithTouchExploration, false);
|
||||
- suppressesSpellChecker = sa.getBoolean(
|
||||
- com.android.internal.R.styleable.InputMethod_suppressesSpellChecker, false);
|
||||
- showInInputMethodPicker = sa.getBoolean(
|
||||
- com.android.internal.R.styleable.InputMethod_showInInputMethodPicker, true);
|
||||
- mHandledConfigChanges = sa.getInt(
|
||||
- com.android.internal.R.styleable.InputMethod_configChanges, 0);
|
||||
- mSupportsStylusHandwriting = sa.getBoolean(
|
||||
- com.android.internal.R.styleable.InputMethod_supportsStylusHandwriting, false);
|
||||
- mSupportsConnectionlessStylusHandwriting = sa.getBoolean(
|
||||
- com.android.internal.R.styleable
|
||||
- .InputMethod_supportsConnectionlessStylusHandwriting, false);
|
||||
- stylusHandwritingSettingsActivity = sa.getString(
|
||||
- com.android.internal.R.styleable.InputMethod_stylusHandwritingSettingsActivity);
|
||||
- sa.recycle();
|
||||
-
|
||||
final int depth = parser.getDepth();
|
||||
// Parse all subtypes
|
||||
while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
|
||||
&& type != XmlPullParser.END_DOCUMENT) {
|
||||
- if (type == XmlPullParser.START_TAG) {
|
||||
- nodeName = parser.getName();
|
||||
- if (!"subtype".equals(nodeName)) {
|
||||
- throw new XmlPullParserException(
|
||||
- "Meta-data in input-method does not start with subtype tag");
|
||||
- }
|
||||
- final TypedArray a = res.obtainAttributes(
|
||||
- attrs, com.android.internal.R.styleable.InputMethod_Subtype);
|
||||
+ if (type != XmlPullParser.START_TAG) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ nodeName = parser.getName();
|
||||
+ if (!"subtype".equals(nodeName)) {
|
||||
+ throw new XmlPullParserException(
|
||||
+ "Meta-data in input-method does not start with subtype tag");
|
||||
+ }
|
||||
+
|
||||
+ final InputMethodSubtype subtype;
|
||||
+ try (TypedArrayWrapper a = TypedArrayWrapper.createForSubtype(
|
||||
+ res.obtainAttributes(attrs,
|
||||
+ com.android.internal.R.styleable.InputMethod_Subtype),
|
||||
+ readTracker)) {
|
||||
String pkLanguageTag = a.getString(com.android.internal.R.styleable
|
||||
.InputMethod_Subtype_physicalKeyboardHintLanguageTag);
|
||||
String pkLayoutType = a.getString(com.android.internal.R.styleable
|
||||
.InputMethod_Subtype_physicalKeyboardHintLayoutType);
|
||||
- final InputMethodSubtype subtype = new InputMethodSubtypeBuilder()
|
||||
+ subtype = new InputMethodSubtypeBuilder()
|
||||
.setSubtypeNameResId(a.getResourceId(com.android.internal.R.styleable
|
||||
.InputMethod_Subtype_label, 0))
|
||||
.setSubtypeIconResId(a.getResourceId(com.android.internal.R.styleable
|
||||
@@ -399,12 +403,11 @@ public InputMethodInfo(Context context, ResolveInfo service,
|
||||
.InputMethod_Subtype_subtypeId, 0 /* use Arrays.hashCode */))
|
||||
.setIsAsciiCapable(a.getBoolean(com.android.internal.R.styleable
|
||||
.InputMethod_Subtype_isAsciiCapable, false)).build();
|
||||
- a.recycle();
|
||||
- if (!subtype.isAuxiliary()) {
|
||||
- isAuxIme = false;
|
||||
- }
|
||||
- subtypes.add(subtype);
|
||||
}
|
||||
+ if (!subtype.isAuxiliary()) {
|
||||
+ isAuxIme = false;
|
||||
+ }
|
||||
+ subtypes.add(subtype);
|
||||
}
|
||||
} catch (NameNotFoundException | IndexOutOfBoundsException | NumberFormatException e) {
|
||||
throw new XmlPullParserException(
|
||||
@@ -468,6 +471,11 @@ private static void validateXmlMetaData(@NonNull ServiceInfo si, @NonNull Resour
|
||||
return;
|
||||
}
|
||||
|
||||
+ if (si.name != null && si.name.length() > COMPONENT_NAME_MAX_LENGTH) {
|
||||
+ throw new XmlPullParserException(
|
||||
+ "Input method name exceeds " + COMPONENT_NAME_MAX_LENGTH + " characters");
|
||||
+ }
|
||||
+
|
||||
// Validate file size using InputStream.skip()
|
||||
long totalBytesSkipped = 0;
|
||||
// Loop to ensure we skip the required number of bytes, as a single
|
||||
@@ -1128,4 +1136,162 @@ public InputMethodInfo[] newArray(int size) {
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
+
|
||||
+ /**
|
||||
+ * A wrapper class for {@link TypedArray} that enforces limits on the size of the metadata
|
||||
+ * read from the XML. Methods throw an {@link XmlPullParserException} if the limit is surpassed.
|
||||
+ *
|
||||
+ * <p>This class works in conjunction with {@link MetadataReadBytesTracker} to:
|
||||
+ * <ul>
|
||||
+ * <li>Limit the length of individual string attributes. For
|
||||
+ * {@code settingsActivity} and {@code languageSettingsActivity}, the maximum length is
|
||||
+ * {@link #COMPONENT_NAME_MAX_LENGTH}. For other string attributes, the maximum length is
|
||||
+ * {@link #STRING_ATTRIBUTES_MAX_CHAR_LENGTH}.</li>
|
||||
+ * <li>Track the total amount of data read from the metadata XML. The
|
||||
+ * {@link MetadataReadBytesTracker} ensures that the cumulative size of all attributes
|
||||
+ * does not exceed {@link #MAX_METADATA_SIZE_BYTES}.
|
||||
+ * </ul>
|
||||
+ *
|
||||
+ * @hide
|
||||
+ */
|
||||
+ @VisibleForTesting
|
||||
+ public static final class TypedArrayWrapper implements AutoCloseable {
|
||||
+ /** The underlying {@link TypedArray} to read from. */
|
||||
+ @NonNull
|
||||
+ private final TypedArray mTypedArray;
|
||||
+ /** Tracker for enforcing metadata size limits. */
|
||||
+ @NonNull
|
||||
+ private final MetadataReadBytesTracker mReadTracker;
|
||||
+ /** {@code true} if parsing a {@code <subtype>} tag, {@code false} otherwise. */
|
||||
+ private final boolean mIsReadingSubtype;
|
||||
+
|
||||
+ /**
|
||||
+ * Creates a {@link TypedArrayWrapper} for parsing attributes of the main
|
||||
+ * {@code <input-method>} tag.
|
||||
+ *
|
||||
+ * @param wrapped The {@link TypedArray} obtained for the {@code <input-method>} tag.
|
||||
+ * @param readTracker The tracker for monitoring data size.
|
||||
+ * @return A new {@link TypedArrayWrapper} instance.
|
||||
+ */
|
||||
+ @NonNull
|
||||
+ @VisibleForTesting
|
||||
+ public static TypedArrayWrapper createForMethod(
|
||||
+ @NonNull TypedArray wrapped, @NonNull MetadataReadBytesTracker readTracker) {
|
||||
+ return new TypedArrayWrapper(wrapped, readTracker, false);
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * Creates a {@link TypedArrayWrapper} for parsing attributes of a {@code <subtype>} tag.
|
||||
+ *
|
||||
+ * @param wrapped The {@link TypedArray} obtained for the {@code <subtype>} tag.
|
||||
+ * @param readTracker The tracker for monitoring data size.
|
||||
+ * @return A new {@link TypedArrayWrapper} instance.
|
||||
+ */
|
||||
+ @NonNull
|
||||
+ @VisibleForTesting
|
||||
+ public static TypedArrayWrapper createForSubtype(
|
||||
+ @NonNull TypedArray wrapped, @NonNull MetadataReadBytesTracker readTracker) {
|
||||
+ return new TypedArrayWrapper(wrapped, readTracker, true);
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * Constructs a new wrapper.
|
||||
+ */
|
||||
+ private TypedArrayWrapper(@NonNull TypedArray wrapped,
|
||||
+ @NonNull MetadataReadBytesTracker readTracker, boolean isReadingSubtype) {
|
||||
+ mTypedArray = wrapped;
|
||||
+ mReadTracker = readTracker;
|
||||
+ mIsReadingSubtype = isReadingSubtype;
|
||||
+ }
|
||||
+
|
||||
+ /** Retrieves an integer value for the attribute at {@code index}. */
|
||||
+ @VisibleForTesting
|
||||
+ public int getInt(int index, int defaultValue) throws XmlPullParserException {
|
||||
+ if (!mTypedArray.hasValue(index)) {
|
||||
+ return defaultValue;
|
||||
+ }
|
||||
+ final int ret = mTypedArray.getInt(index, defaultValue);
|
||||
+ mReadTracker.onReadBytes(Integer.BYTES);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ /** Retrieves the string value for the attribute at {@code index}. */
|
||||
+ @VisibleForTesting
|
||||
+ public String getString(int index) throws XmlPullParserException {
|
||||
+ final String ret = mTypedArray.getString(index);
|
||||
+ final int maxLen = getMaxLength(index);
|
||||
+ if (ret != null && ret.length() > maxLen) {
|
||||
+ throw new XmlPullParserException(
|
||||
+ "String resources in input method exceed the length limit of "
|
||||
+ + maxLen + " characters");
|
||||
+ }
|
||||
+ mReadTracker.onReadBytes(ret == null ? 0 : ret.length() * Character.BYTES);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ /** Retrieves a boolean value for the attribute at {@code index}. */
|
||||
+ @VisibleForTesting
|
||||
+ public boolean getBoolean(int index, boolean defaultValue) throws XmlPullParserException {
|
||||
+ if (!mTypedArray.hasValue(index)) {
|
||||
+ return defaultValue;
|
||||
+ }
|
||||
+ final boolean ret = mTypedArray.getBoolean(index, defaultValue);
|
||||
+ mReadTracker.onReadBytes(1);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ /** Retrieves a resource identifier for the attribute at {@code index}. */
|
||||
+ @VisibleForTesting
|
||||
+ public int getResourceId(int index, int defaultValue) throws XmlPullParserException {
|
||||
+ if (!mTypedArray.hasValue(index)) {
|
||||
+ return defaultValue;
|
||||
+ }
|
||||
+ final int ret = mTypedArray.getResourceId(index, defaultValue);
|
||||
+ mReadTracker.onReadBytes(Integer.BYTES);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void close() {
|
||||
+ mTypedArray.recycle();
|
||||
+ }
|
||||
+
|
||||
+ private int getMaxLength(int index) {
|
||||
+ // Note that the Android resource has limit DEFAULT_MAX_STRING_ATTR_LENGTH = 32_768.
|
||||
+ if (mIsReadingSubtype) {
|
||||
+ // No limits for strings in subtype for now.
|
||||
+ return Integer.MAX_VALUE;
|
||||
+ } else {
|
||||
+ return switch (index) {
|
||||
+ // TODO(b/456008595): Consider to add
|
||||
+ // InputMethod_stylusHandwritingSettingsActivity
|
||||
+ case com.android.internal.R.styleable.InputMethod_settingsActivity,
|
||||
+ com.android.internal.R.styleable.InputMethod_languageSettingsActivity ->
|
||||
+ COMPONENT_NAME_MAX_LENGTH;
|
||||
+ default ->
|
||||
+ // TODO(b/456008595): Consider to introduce limits.
|
||||
+ Integer.MAX_VALUE;
|
||||
+ };
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ /** @hide */
|
||||
+ @VisibleForTesting
|
||||
+ public static final class MetadataReadBytesTracker {
|
||||
+ private int mRemainingBytes = MAX_METADATA_SIZE_BYTES;
|
||||
+
|
||||
+ @VisibleForTesting
|
||||
+ public MetadataReadBytesTracker() {
|
||||
+ }
|
||||
+
|
||||
+ private void onReadBytes(int bytes) throws XmlPullParserException {
|
||||
+ mRemainingBytes -= bytes;
|
||||
+ if (mRemainingBytes < 0) {
|
||||
+ throw new XmlPullParserException(
|
||||
+ "The input method service has metadata exceeds the "
|
||||
+ + MAX_METADATA_SIZE_BYTES + " byte limit");
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
|
||||
index 87333dd31b8d3..1ccc9e5483298 100644
|
||||
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
|
||||
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
|
||||
@@ -16,18 +16,27 @@
|
||||
|
||||
package android.view.inputmethod;
|
||||
|
||||
+import static android.view.inputmethod.InputMethodInfo.COMPONENT_NAME_MAX_LENGTH;
|
||||
+
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
+import static org.junit.Assert.assertThrows;
|
||||
+import static org.mockito.Mockito.mock;
|
||||
+import static org.mockito.Mockito.verify;
|
||||
+import static org.mockito.Mockito.when;
|
||||
|
||||
import android.annotation.XmlRes;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ServiceInfo;
|
||||
+import android.content.res.TypedArray;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
|
||||
import android.platform.test.flag.junit.SetFlagsRule;
|
||||
+import android.view.inputmethod.InputMethodInfo.MetadataReadBytesTracker;
|
||||
+import android.view.inputmethod.InputMethodInfo.TypedArrayWrapper;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
@@ -38,6 +47,7 @@
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
+import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@@ -131,6 +141,94 @@ public void testIsVirtualDeviceOnly() throws Exception {
|
||||
assertThat(clone.isVirtualDeviceOnly(), is(true));
|
||||
}
|
||||
|
||||
+ @Test
|
||||
+ public void testTypedArrayWrapper() throws Exception {
|
||||
+ final TypedArray mockTypedArray = mock(TypedArray.class);
|
||||
+ when(mockTypedArray.hasValue(0)).thenReturn(true);
|
||||
+ when(mockTypedArray.getInt(0, 0)).thenReturn(123);
|
||||
+ when(mockTypedArray.getString(1)).thenReturn("hello");
|
||||
+ when(mockTypedArray.hasValue(2)).thenReturn(true);
|
||||
+ when(mockTypedArray.getBoolean(2, false)).thenReturn(true);
|
||||
+ when(mockTypedArray.hasValue(3)).thenReturn(true);
|
||||
+ when(mockTypedArray.getResourceId(3, 0)).thenReturn(456);
|
||||
+
|
||||
+ try (TypedArrayWrapper wrapper = TypedArrayWrapper.createForMethod(mockTypedArray,
|
||||
+ new MetadataReadBytesTracker())) {
|
||||
+ assertThat(wrapper.getInt(0, 0), is(123));
|
||||
+ assertThat(wrapper.getString(1), is("hello"));
|
||||
+ assertThat(wrapper.getBoolean(2, false), is(true));
|
||||
+ assertThat(wrapper.getResourceId(3, 0), is(456));
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void testTypedArrayWrapper_getString_throwsExceptionWhenStringTooLong()
|
||||
+ throws Exception {
|
||||
+ final TypedArray mockTypedArray = mock(TypedArray.class);
|
||||
+ final String longStringA = "a".repeat(COMPONENT_NAME_MAX_LENGTH + 1);
|
||||
+ final String longStringB = "b".repeat(COMPONENT_NAME_MAX_LENGTH + 1);
|
||||
+ when(mockTypedArray.getString(
|
||||
+ com.android.internal.R.styleable.InputMethod_settingsActivity))
|
||||
+ .thenReturn(longStringA);
|
||||
+ when(mockTypedArray.getString(
|
||||
+ com.android.internal.R.styleable.InputMethod_languageSettingsActivity))
|
||||
+ .thenReturn(longStringB);
|
||||
+
|
||||
+ try (TypedArrayWrapper wrapper = TypedArrayWrapper.createForMethod(mockTypedArray,
|
||||
+ new MetadataReadBytesTracker())) {
|
||||
+ assertThrows(
|
||||
+ XmlPullParserException.class,
|
||||
+ () -> wrapper.getString(
|
||||
+ com.android.internal.R.styleable.InputMethod_settingsActivity));
|
||||
+ assertThrows(
|
||||
+ XmlPullParserException.class,
|
||||
+ () -> wrapper.getString(
|
||||
+ com.android.internal.R.styleable.InputMethod_languageSettingsActivity));
|
||||
+ }
|
||||
+
|
||||
+ // The same index can be used for method and subtype for different attributes.
|
||||
+ // This verifies the same index returns the correct string for subtypes.
|
||||
+ try (TypedArrayWrapper wrapper = TypedArrayWrapper.createForSubtype(mockTypedArray,
|
||||
+ new MetadataReadBytesTracker())) {
|
||||
+ assertThat(wrapper.getString(
|
||||
+ com.android.internal.R.styleable.InputMethod_settingsActivity),
|
||||
+ is(longStringA));
|
||||
+ assertThat(wrapper.getString(
|
||||
+ com.android.internal.R.styleable.InputMethod_languageSettingsActivity),
|
||||
+ is(longStringB));
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void testTypedArrayWrapper_closeRecyclesTypedArray() {
|
||||
+ final TypedArray mockTypedArray = mock(TypedArray.class);
|
||||
+ final TypedArrayWrapper wrapper = TypedArrayWrapper.createForMethod(mockTypedArray,
|
||||
+ new MetadataReadBytesTracker());
|
||||
+
|
||||
+ wrapper.close();
|
||||
+
|
||||
+ verify(mockTypedArray).recycle();
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void testTypedArrayWrapper_metadataReadBytesTracker_throwsExceptionWhenLimitExceeded() {
|
||||
+ final TypedArray mockTypedArray = mock(TypedArray.class);
|
||||
+ final String longString = "a".repeat(1000);
|
||||
+ when(mockTypedArray.getString(0)).thenReturn(longString);
|
||||
+
|
||||
+ try (TypedArrayWrapper wrapper = TypedArrayWrapper.createForMethod(mockTypedArray,
|
||||
+ new MetadataReadBytesTracker())) {
|
||||
+ assertThrows(XmlPullParserException.class, () -> {
|
||||
+ // Each character is 2 bytes. 1000 chars * 2 = 2000 bytes per call.
|
||||
+ // Limit is 200 * 1024 = 204800 bytes.
|
||||
+ // 204800 / 2000 = 102.4. So 103 calls will exceed the limit.
|
||||
+ for (int i = 0; i < 103; ++i) {
|
||||
+ wrapper.getString(0);
|
||||
+ }
|
||||
+ });
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
private InputMethodInfo buildInputMethodForTest(final @XmlRes int metaDataRes)
|
||||
throws Exception {
|
||||
final Context context = InstrumentationRegistry.getInstrumentation().getContext();
|
||||
|
|
@ -0,0 +1,837 @@
|
|||
From bec80a52aaab578aa0f18527b6e40e165bfb45ab Mon Sep 17 00:00:00 2001
|
||||
From: Hiroki Sato <hirokisato@google.com>
|
||||
Date: Tue, 4 Nov 2025 17:29:42 +0900
|
||||
Subject: [PATCH] Introduce InputMethodSubtypeSafeList
|
||||
|
||||
IMM#getEnabledInputMethodSubtypeList() can return a large list of
|
||||
subtypes, which may cause a TransactionTooLargeException.
|
||||
|
||||
This patch introduces InputMethodSubtypeSafeList to wrap the list as a
|
||||
byte array, avoiding the exception. This mirrors the existing
|
||||
InputMethodInfoSafeList pattern introduced in [1].
|
||||
|
||||
Additionally, this change extracts the common marshalling logic from
|
||||
InputMethodInfoSafeList into a new AbstractSafeList and refactors both
|
||||
SafeList classes to extend it.
|
||||
|
||||
[1] I0a7667070fcdf17d34b248a5988c38064588718a
|
||||
|
||||
DISABLE_TOPIC_PROTECTOR
|
||||
|
||||
Bug: 449416164
|
||||
Bug: 449181366
|
||||
Bug: 449393786
|
||||
Bug: 449227003
|
||||
Test: CtsInputMethodTestCases:{InputMethodRegistrationTest,InputMethodInfoTest}
|
||||
Test: InputMethodCoreTests
|
||||
Flag: EXEMPT BUGFIX
|
||||
(cherry picked from commit 1d68a1099be2b99e8410dad01822851287994682)
|
||||
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:46388aba14b1698df8c98e96d97b50130d1ce085
|
||||
Merged-In: Ied64a9f018fd3e79cfc51ccd82d361b43e5f29dc
|
||||
Change-Id: Ied64a9f018fd3e79cfc51ccd82d361b43e5f29dc
|
||||
---
|
||||
.../IInputMethodManagerGlobalInvoker.java | 6 +-
|
||||
.../inputmethod/AbstractSafeList.java | 127 +++++++++++++++++
|
||||
.../inputmethod/InputMethodInfoSafeList.java | 105 +++-----------
|
||||
.../InputMethodSubtypeSafeList.aidl | 19 +++
|
||||
.../InputMethodSubtypeSafeList.java | 87 ++++++++++++
|
||||
.../internal/view/IInputMethodManager.aidl | 3 +-
|
||||
.../inputmethod/AbstractSafeListTest.java | 98 ++++++++++++++
|
||||
.../InputMethodSubtypeSafeListTest.java | 128 ++++++++++++++++++
|
||||
.../inputmethod/IInputMethodManagerImpl.java | 7 +-
|
||||
.../InputMethodManagerService.java | 13 +-
|
||||
.../server/inputmethod/ZeroJankProxy.java | 4 +-
|
||||
11 files changed, 497 insertions(+), 100 deletions(-)
|
||||
create mode 100644 core/java/com/android/internal/inputmethod/AbstractSafeList.java
|
||||
create mode 100644 core/java/com/android/internal/inputmethod/InputMethodSubtypeSafeList.aidl
|
||||
create mode 100644 core/java/com/android/internal/inputmethod/InputMethodSubtypeSafeList.java
|
||||
create mode 100644 core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/AbstractSafeListTest.java
|
||||
create mode 100644 core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodSubtypeSafeListTest.java
|
||||
|
||||
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
|
||||
index fe5afe437834d..b679da84c24d1 100644
|
||||
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
|
||||
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
|
||||
@@ -44,6 +44,7 @@
|
||||
import com.android.internal.inputmethod.IRemoteComputerControlInputConnection;
|
||||
import com.android.internal.inputmethod.IRemoteInputConnection;
|
||||
import com.android.internal.inputmethod.InputMethodInfoSafeList;
|
||||
+import com.android.internal.inputmethod.InputMethodSubtypeSafeList;
|
||||
import com.android.internal.inputmethod.SoftInputShowHideReason;
|
||||
import com.android.internal.inputmethod.StartInputFlags;
|
||||
import com.android.internal.inputmethod.StartInputReason;
|
||||
@@ -250,8 +251,9 @@ static List<InputMethodSubtype> getEnabledInputMethodSubtypeList(@Nullable Strin
|
||||
return new ArrayList<>();
|
||||
}
|
||||
try {
|
||||
- return service.getEnabledInputMethodSubtypeList(imiId,
|
||||
- allowsImplicitlyEnabledSubtypes, userId);
|
||||
+ return InputMethodSubtypeSafeList.extractFrom(
|
||||
+ service.getEnabledInputMethodSubtypeList(imiId,
|
||||
+ allowsImplicitlyEnabledSubtypes, userId));
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
diff --git a/core/java/com/android/internal/inputmethod/AbstractSafeList.java b/core/java/com/android/internal/inputmethod/AbstractSafeList.java
|
||||
new file mode 100644
|
||||
index 0000000000000..697b153afecfe
|
||||
--- /dev/null
|
||||
+++ b/core/java/com/android/internal/inputmethod/AbstractSafeList.java
|
||||
@@ -0,0 +1,127 @@
|
||||
+/*
|
||||
+ * 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
|
||||
+ *
|
||||
+ * http://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.internal.inputmethod;
|
||||
+
|
||||
+import android.annotation.NonNull;
|
||||
+import android.annotation.Nullable;
|
||||
+import android.os.Parcel;
|
||||
+import android.os.Parcelable;
|
||||
+
|
||||
+import com.android.internal.annotations.VisibleForTesting;
|
||||
+
|
||||
+import java.util.ArrayList;
|
||||
+import java.util.List;
|
||||
+
|
||||
+/**
|
||||
+ * An abstract base class for creating a {@link Parcelable} container that can hold an arbitrary
|
||||
+ * number of {@link Parcelable} objects without worrying about
|
||||
+ * {@link android.os.TransactionTooLargeException}.
|
||||
+ *
|
||||
+ * @see Parcel#readBlob()
|
||||
+ * @see Parcel#writeBlob(byte[])
|
||||
+ *
|
||||
+ * @param <T> The type of the {@link Parcelable} objects.
|
||||
+ */
|
||||
+public abstract class AbstractSafeList<T extends Parcelable> implements Parcelable {
|
||||
+ @Nullable
|
||||
+ private byte[] mBuffer;
|
||||
+
|
||||
+ protected AbstractSafeList(@Nullable List<T> list) {
|
||||
+ if (list != null && !list.isEmpty()) {
|
||||
+ mBuffer = marshall(list);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ protected AbstractSafeList(@Nullable byte[] buffer) {
|
||||
+ mBuffer = buffer;
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * Extracts the list of {@link Parcelable} objects from a {@link AbstractSafeList}, and
|
||||
+ * clears the internal buffer of the list.
|
||||
+ *
|
||||
+ * @param from The {@link AbstractSafeList} to extract from.
|
||||
+ * @param creator The {@link Parcelable.Creator} for the {@link Parcelable} objects.
|
||||
+ * @param <T> The type of the {@link Parcelable} objects.
|
||||
+ * @return The list of {@link Parcelable} objects.
|
||||
+ */
|
||||
+ @NonNull
|
||||
+ protected static <T extends Parcelable> List<T> extractFrom(
|
||||
+ @Nullable AbstractSafeList<T> from, @NonNull Parcelable.Creator<T> creator) {
|
||||
+ if (from == null) {
|
||||
+ return new ArrayList<>();
|
||||
+ }
|
||||
+ final byte[] buf = from.mBuffer;
|
||||
+ from.mBuffer = null;
|
||||
+ if (buf != null) {
|
||||
+ final List<T> list = unmarshall(buf, creator);
|
||||
+ if (list != null) {
|
||||
+ return list;
|
||||
+ }
|
||||
+ }
|
||||
+ return new ArrayList<>();
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public int describeContents() {
|
||||
+ // As long as the parcelled classes return 0, we can also return 0 here.
|
||||
+ return 0;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
+ dest.writeBlob(mBuffer);
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * Marshalls a list of {@link Parcelable} objects into a byte array.
|
||||
+ */
|
||||
+ @Nullable
|
||||
+ @VisibleForTesting
|
||||
+ public static <T extends Parcelable> byte[] marshall(@NonNull List<T> list) {
|
||||
+ Parcel parcel = null;
|
||||
+ try {
|
||||
+ parcel = Parcel.obtain();
|
||||
+ parcel.writeTypedList(list);
|
||||
+ return parcel.marshall();
|
||||
+ } finally {
|
||||
+ if (parcel != null) {
|
||||
+ parcel.recycle();
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * Unmarshalls a byte array into a list of {@link Parcelable} objects.
|
||||
+ */
|
||||
+ @Nullable
|
||||
+ @VisibleForTesting
|
||||
+ public static <T extends Parcelable> List<T> unmarshall(
|
||||
+ @NonNull byte[] data, @NonNull Parcelable.Creator<T> creator) {
|
||||
+ Parcel parcel = null;
|
||||
+ try {
|
||||
+ parcel = Parcel.obtain();
|
||||
+ parcel.unmarshall(data, 0, data.length);
|
||||
+ parcel.setDataPosition(0);
|
||||
+ return parcel.createTypedArrayList(creator);
|
||||
+ } finally {
|
||||
+ if (parcel != null) {
|
||||
+ parcel.recycle();
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
diff --git a/core/java/com/android/internal/inputmethod/InputMethodInfoSafeList.java b/core/java/com/android/internal/inputmethod/InputMethodInfoSafeList.java
|
||||
index 9e720fb6cceea..a2ea5b08f13f3 100644
|
||||
--- a/core/java/com/android/internal/inputmethod/InputMethodInfoSafeList.java
|
||||
+++ b/core/java/com/android/internal/inputmethod/InputMethodInfoSafeList.java
|
||||
@@ -19,24 +19,24 @@
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.Parcel;
|
||||
-import android.os.Parcelable;
|
||||
import android.view.inputmethod.InputMethodInfo;
|
||||
|
||||
-import java.util.ArrayList;
|
||||
-import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
- * A {@link Parcelable} container that can holds an arbitrary number of {@link InputMethodInfo}
|
||||
- * without worrying about {@link android.os.TransactionTooLargeException} when passing across
|
||||
- * process boundary.
|
||||
- *
|
||||
- * @see Parcel#readBlob()
|
||||
- * @see Parcel#writeBlob(byte[])
|
||||
+ * A {@link android.os.Parcelable} container that can hold an arbitrary number of
|
||||
+ * {@link InputMethodInfo} without worrying about
|
||||
+ * {@link android.os.TransactionTooLargeException} when passing across process boundary.
|
||||
*/
|
||||
-public final class InputMethodInfoSafeList implements Parcelable {
|
||||
- @Nullable
|
||||
- private byte[] mBuffer;
|
||||
+public final class InputMethodInfoSafeList extends AbstractSafeList<InputMethodInfo> {
|
||||
+
|
||||
+ private InputMethodInfoSafeList(@Nullable byte[] buffer) {
|
||||
+ super(buffer);
|
||||
+ }
|
||||
+
|
||||
+ private InputMethodInfoSafeList(@Nullable List<InputMethodInfo> list) {
|
||||
+ super(list);
|
||||
+ }
|
||||
|
||||
/**
|
||||
* Instantiates a list of {@link InputMethodInfo} from the given {@link InputMethodInfoSafeList}
|
||||
@@ -53,81 +53,20 @@ public final class InputMethodInfoSafeList implements Parcelable {
|
||||
*/
|
||||
@NonNull
|
||||
public static List<InputMethodInfo> extractFrom(@Nullable InputMethodInfoSafeList from) {
|
||||
- final byte[] buf = from.mBuffer;
|
||||
- from.mBuffer = null;
|
||||
- if (buf != null) {
|
||||
- final InputMethodInfo[] array = unmarshall(buf);
|
||||
- if (array != null) {
|
||||
- return new ArrayList<>(Arrays.asList(array));
|
||||
- }
|
||||
- }
|
||||
- return new ArrayList<>();
|
||||
- }
|
||||
-
|
||||
- @NonNull
|
||||
- private static InputMethodInfo[] toArray(@Nullable List<InputMethodInfo> original) {
|
||||
- if (original == null) {
|
||||
- return new InputMethodInfo[0];
|
||||
- }
|
||||
- return original.toArray(new InputMethodInfo[0]);
|
||||
- }
|
||||
-
|
||||
- @Nullable
|
||||
- private static byte[] marshall(@NonNull InputMethodInfo[] array) {
|
||||
- Parcel parcel = null;
|
||||
- try {
|
||||
- parcel = Parcel.obtain();
|
||||
- parcel.writeTypedArray(array, 0);
|
||||
- return parcel.marshall();
|
||||
- } finally {
|
||||
- if (parcel != null) {
|
||||
- parcel.recycle();
|
||||
- }
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- @Nullable
|
||||
- private static InputMethodInfo[] unmarshall(byte[] data) {
|
||||
- Parcel parcel = null;
|
||||
- try {
|
||||
- parcel = Parcel.obtain();
|
||||
- parcel.unmarshall(data, 0, data.length);
|
||||
- parcel.setDataPosition(0);
|
||||
- return parcel.createTypedArray(InputMethodInfo.CREATOR);
|
||||
- } finally {
|
||||
- if (parcel != null) {
|
||||
- parcel.recycle();
|
||||
- }
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- private InputMethodInfoSafeList(@Nullable byte[] blob) {
|
||||
- mBuffer = blob;
|
||||
+ return AbstractSafeList.extractFrom(from, InputMethodInfo.CREATOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates {@link InputMethodInfoSafeList} from the given list of {@link InputMethodInfo}.
|
||||
*
|
||||
* @param list list of {@link InputMethodInfo} from which {@link InputMethodInfoSafeList} will
|
||||
- * be created
|
||||
+ * be created. Giving {@code null} will result in an empty
|
||||
+ * {@link InputMethodInfoSafeList}.
|
||||
* @return {@link InputMethodInfoSafeList} that stores the given list of {@link InputMethodInfo}
|
||||
*/
|
||||
@NonNull
|
||||
public static InputMethodInfoSafeList create(@Nullable List<InputMethodInfo> list) {
|
||||
- if (list == null || list.isEmpty()) {
|
||||
- return empty();
|
||||
- }
|
||||
- return new InputMethodInfoSafeList(marshall(toArray(list)));
|
||||
- }
|
||||
-
|
||||
- /**
|
||||
- * Creates an empty {@link InputMethodInfoSafeList}.
|
||||
- *
|
||||
- * @return {@link InputMethodInfoSafeList} that is empty
|
||||
- */
|
||||
- @NonNull
|
||||
- public static InputMethodInfoSafeList empty() {
|
||||
- return new InputMethodInfoSafeList(null);
|
||||
+ return new InputMethodInfoSafeList(list);
|
||||
}
|
||||
|
||||
public static final Creator<InputMethodInfoSafeList> CREATOR = new Creator<>() {
|
||||
@@ -141,16 +80,4 @@ public InputMethodInfoSafeList[] newArray(int size) {
|
||||
return new InputMethodInfoSafeList[size];
|
||||
}
|
||||
};
|
||||
-
|
||||
- @Override
|
||||
- public int describeContents() {
|
||||
- // As long as InputMethodInfo#describeContents() is guaranteed to return 0, we can always
|
||||
- // return 0 here.
|
||||
- return 0;
|
||||
- }
|
||||
-
|
||||
- @Override
|
||||
- public void writeToParcel(Parcel dest, int flags) {
|
||||
- dest.writeBlob(mBuffer);
|
||||
- }
|
||||
}
|
||||
diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSafeList.aidl b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSafeList.aidl
|
||||
new file mode 100644
|
||||
index 0000000000000..11000632eba54
|
||||
--- /dev/null
|
||||
+++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSafeList.aidl
|
||||
@@ -0,0 +1,19 @@
|
||||
+/*
|
||||
+ * 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
|
||||
+ *
|
||||
+ * http://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.internal.inputmethod;
|
||||
+
|
||||
+parcelable InputMethodSubtypeSafeList;
|
||||
diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSafeList.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSafeList.java
|
||||
new file mode 100644
|
||||
index 0000000000000..cd95088f5cf0d
|
||||
--- /dev/null
|
||||
+++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSafeList.java
|
||||
@@ -0,0 +1,87 @@
|
||||
+/*
|
||||
+ * 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
|
||||
+ *
|
||||
+ * http://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.internal.inputmethod;
|
||||
+
|
||||
+import android.annotation.NonNull;
|
||||
+import android.annotation.Nullable;
|
||||
+import android.os.Parcel;
|
||||
+import android.view.inputmethod.InputMethodSubtype;
|
||||
+
|
||||
+import java.util.List;
|
||||
+
|
||||
+/**
|
||||
+ * A {@link android.os.Parcelable} container that can hold an arbitrary number of
|
||||
+ * {@link InputMethodSubtype} without worrying about
|
||||
+ * {@link android.os.TransactionTooLargeException} when passing across process boundary.
|
||||
+ */
|
||||
+public final class InputMethodSubtypeSafeList extends AbstractSafeList<InputMethodSubtype> {
|
||||
+
|
||||
+ private InputMethodSubtypeSafeList(@Nullable byte[] buffer) {
|
||||
+ super(buffer);
|
||||
+ }
|
||||
+
|
||||
+ private InputMethodSubtypeSafeList(@Nullable List<InputMethodSubtype> list) {
|
||||
+ super(list);
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * Instantiates a list of {@link InputMethodSubtype} from the given
|
||||
+ * {@link InputMethodSubtypeSafeList} then clears the internal buffer of
|
||||
+ * {@link InputMethodSubtypeSafeList}.
|
||||
+ *
|
||||
+ * <p>Note that each {@link InputMethodSubtype} item is guaranteed to be a copy of the original
|
||||
+ * {@link InputMethodSubtype} object.</p>
|
||||
+ *
|
||||
+ * <p>Any subsequent call will return an empty list.</p>
|
||||
+ *
|
||||
+ * @param from {@link InputMethodSubtypeSafeList} from which the list of
|
||||
+ * {@link InputMethodSubtype} will be extracted
|
||||
+ * @return list of {@link InputMethodSubtype} stored in the given
|
||||
+ * {@link InputMethodSubtypeSafeList}
|
||||
+ */
|
||||
+ @NonNull
|
||||
+ public static List<InputMethodSubtype> extractFrom(@Nullable InputMethodSubtypeSafeList from) {
|
||||
+ return AbstractSafeList.extractFrom(from, InputMethodSubtype.CREATOR);
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * Instantiates {@link InputMethodSubtypeSafeList} from the given list of
|
||||
+ * {@link InputMethodSubtype}.
|
||||
+ *
|
||||
+ * @param list list of {@link InputMethodSubtype} from which
|
||||
+ * {@link InputMethodSubtypeSafeList} will be created. Giving {@code null} will
|
||||
+ * result in an empty {@link InputMethodSubtypeSafeList}.
|
||||
+ * @return {@link InputMethodSubtypeSafeList} that stores the given list of
|
||||
+ * {@link InputMethodSubtype}
|
||||
+ */
|
||||
+ @NonNull
|
||||
+ public static InputMethodSubtypeSafeList create(@Nullable List<InputMethodSubtype> list) {
|
||||
+ return new InputMethodSubtypeSafeList(list);
|
||||
+ }
|
||||
+
|
||||
+ public static final Creator<InputMethodSubtypeSafeList> CREATOR = new Creator<>() {
|
||||
+ @Override
|
||||
+ public InputMethodSubtypeSafeList createFromParcel(Parcel in) {
|
||||
+ return new InputMethodSubtypeSafeList(in.readBlob());
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public InputMethodSubtypeSafeList[] newArray(int size) {
|
||||
+ return new InputMethodSubtypeSafeList[size];
|
||||
+ }
|
||||
+ };
|
||||
+}
|
||||
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
|
||||
index 29363a533a036..3f6098c270d47 100644
|
||||
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
|
||||
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
|
||||
@@ -32,6 +32,7 @@ import com.android.internal.inputmethod.IRemoteComputerControlInputConnection;
|
||||
import com.android.internal.inputmethod.IRemoteInputConnection;
|
||||
import com.android.internal.inputmethod.InputBindResult;
|
||||
import com.android.internal.inputmethod.InputMethodInfoSafeList;
|
||||
+import com.android.internal.inputmethod.InputMethodSubtypeSafeList;
|
||||
|
||||
/**
|
||||
* Public interface to the global input method manager, used by all client applications.
|
||||
@@ -67,7 +68,7 @@ interface IInputMethodManager {
|
||||
|
||||
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
|
||||
+ "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
|
||||
- List<InputMethodSubtype> getEnabledInputMethodSubtypeList(in @nullable String imiId,
|
||||
+ InputMethodSubtypeSafeList getEnabledInputMethodSubtypeList(in @nullable String imiId,
|
||||
boolean allowsImplicitlyEnabledSubtypes, int userId);
|
||||
|
||||
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
|
||||
diff --git a/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/AbstractSafeListTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/AbstractSafeListTest.java
|
||||
new file mode 100644
|
||||
index 0000000000000..0f72f095dbe3c
|
||||
--- /dev/null
|
||||
+++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/AbstractSafeListTest.java
|
||||
@@ -0,0 +1,98 @@
|
||||
+/*
|
||||
+ * 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
|
||||
+ *
|
||||
+ * http://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.internal.inputmethod;
|
||||
+
|
||||
+import static org.junit.Assert.assertEquals;
|
||||
+import static org.junit.Assert.assertNotNull;
|
||||
+
|
||||
+import android.os.Parcel;
|
||||
+import android.os.Parcelable;
|
||||
+import android.platform.test.annotations.Presubmit;
|
||||
+
|
||||
+import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
+import androidx.test.filters.SmallTest;
|
||||
+
|
||||
+import org.junit.Test;
|
||||
+import org.junit.runner.RunWith;
|
||||
+
|
||||
+import java.util.List;
|
||||
+
|
||||
+@SmallTest
|
||||
+@Presubmit
|
||||
+@RunWith(AndroidJUnit4.class)
|
||||
+public class AbstractSafeListTest {
|
||||
+
|
||||
+ private static class TestParcelable implements Parcelable {
|
||||
+ final int mData;
|
||||
+
|
||||
+ TestParcelable(int data) {
|
||||
+ mData = data;
|
||||
+ }
|
||||
+
|
||||
+ TestParcelable(Parcel parcel) {
|
||||
+ mData = parcel.readInt();
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void writeToParcel(Parcel parcel, int flags) {
|
||||
+ parcel.writeInt(mData);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public int describeContents() {
|
||||
+ return 0;
|
||||
+ }
|
||||
+
|
||||
+ @SuppressWarnings("EffectivelyPrivate") // Parcelable must have CREATOR.
|
||||
+ public static final Creator<TestParcelable> CREATOR = new Creator<TestParcelable>() {
|
||||
+ @Override
|
||||
+ public TestParcelable createFromParcel(Parcel parcel) {
|
||||
+ return new TestParcelable(parcel);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public TestParcelable[] newArray(int size) {
|
||||
+ return new TestParcelable[size];
|
||||
+ }
|
||||
+ };
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void testMarshallThenUnmarshall() {
|
||||
+ List<TestParcelable> originalArray = List.of(new TestParcelable(1), new TestParcelable(2));
|
||||
+ byte[] marshalled = AbstractSafeList.marshall(originalArray);
|
||||
+ assertNotNull(marshalled);
|
||||
+ List<TestParcelable> unmarshalled =
|
||||
+ AbstractSafeList.unmarshall(marshalled, TestParcelable.CREATOR);
|
||||
+ assertNotNull(unmarshalled);
|
||||
+ assertEquals(originalArray.size(), unmarshalled.size());
|
||||
+ for (int i = 0; i < originalArray.size(); i++) {
|
||||
+ assertEquals(originalArray.get(i).mData, unmarshalled.get(i).mData);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void testMarshallEmptyArray() {
|
||||
+ List<TestParcelable> originalArray = List.of();
|
||||
+ byte[] marshalled = AbstractSafeList.marshall(originalArray);
|
||||
+ assertNotNull(marshalled);
|
||||
+ List<TestParcelable> unmarshalled =
|
||||
+ AbstractSafeList.unmarshall(marshalled, TestParcelable.CREATOR);
|
||||
+ assertNotNull(unmarshalled);
|
||||
+ assertEquals(0, unmarshalled.size());
|
||||
+ }
|
||||
+}
|
||||
diff --git a/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodSubtypeSafeListTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodSubtypeSafeListTest.java
|
||||
new file mode 100644
|
||||
index 0000000000000..089ffb80d7a90
|
||||
--- /dev/null
|
||||
+++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodSubtypeSafeListTest.java
|
||||
@@ -0,0 +1,128 @@
|
||||
+/*
|
||||
+ * 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
|
||||
+ *
|
||||
+ * http://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.internal.inputmethod;
|
||||
+
|
||||
+import static org.junit.Assert.assertEquals;
|
||||
+import static org.junit.Assert.assertNotNull;
|
||||
+import static org.junit.Assert.assertNotSame;
|
||||
+import static org.junit.Assert.assertTrue;
|
||||
+
|
||||
+import android.os.Parcel;
|
||||
+import android.platform.test.annotations.Presubmit;
|
||||
+import android.view.inputmethod.InputMethodSubtype;
|
||||
+
|
||||
+import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
+import androidx.test.filters.SmallTest;
|
||||
+
|
||||
+import org.junit.Test;
|
||||
+import org.junit.runner.RunWith;
|
||||
+
|
||||
+import java.util.ArrayList;
|
||||
+import java.util.Collections;
|
||||
+import java.util.List;
|
||||
+import java.util.function.Function;
|
||||
+
|
||||
+@SmallTest
|
||||
+@Presubmit
|
||||
+@RunWith(AndroidJUnit4.class)
|
||||
+public class InputMethodSubtypeSafeListTest {
|
||||
+
|
||||
+ private static InputMethodSubtype createFakeInputMethodSubtype(String locale, String mode) {
|
||||
+ return new InputMethodSubtype.InputMethodSubtypeBuilder()
|
||||
+ .setSubtypeLocale(locale)
|
||||
+ .setSubtypeMode(mode)
|
||||
+ .build();
|
||||
+ }
|
||||
+
|
||||
+ private static List<InputMethodSubtype> createTestInputMethodSubtypeList() {
|
||||
+ List<InputMethodSubtype> list = new ArrayList<>();
|
||||
+ list.add(createFakeInputMethodSubtype("en_US", "keyboard"));
|
||||
+ list.add(createFakeInputMethodSubtype("ja_JP", "keyboard"));
|
||||
+ list.add(createFakeInputMethodSubtype("en_GB", "voice"));
|
||||
+ return list;
|
||||
+ }
|
||||
+
|
||||
+ private static void assertItemsAfterExtract(
|
||||
+ List<InputMethodSubtype> originals,
|
||||
+ Function<List<InputMethodSubtype>, InputMethodSubtypeSafeList> factory) {
|
||||
+ InputMethodSubtypeSafeList list = factory.apply(originals);
|
||||
+ List<InputMethodSubtype> extracted = InputMethodSubtypeSafeList.extractFrom(list);
|
||||
+ assertEquals(originals.size(), extracted.size());
|
||||
+ for (int i = 0; i < originals.size(); i++) {
|
||||
+ assertNotSame(
|
||||
+ "InputMethodSubtypeSafeList.extractFrom() must clone each instance",
|
||||
+ originals.get(i), extracted.get(i));
|
||||
+ assertEquals(
|
||||
+ "Verify the cloned instances have the equal locale",
|
||||
+ originals.get(i).getLocale(), extracted.get(i).getLocale());
|
||||
+ assertEquals(
|
||||
+ "Verify the cloned instances have the equal mode",
|
||||
+ originals.get(i).getMode(), extracted.get(i).getMode());
|
||||
+ }
|
||||
+
|
||||
+ // Subsequent calls of InputMethodSubtypeSafeList.extractFrom() return an empty list.
|
||||
+ List<InputMethodSubtype> extracted2 = InputMethodSubtypeSafeList.extractFrom(list);
|
||||
+ assertTrue(extracted2.isEmpty());
|
||||
+ }
|
||||
+
|
||||
+ private static InputMethodSubtypeSafeList cloneViaParcel(InputMethodSubtypeSafeList original) {
|
||||
+ Parcel parcel = null;
|
||||
+ try {
|
||||
+ parcel = Parcel.obtain();
|
||||
+ original.writeToParcel(parcel, 0);
|
||||
+ parcel.setDataPosition(0);
|
||||
+ InputMethodSubtypeSafeList newInstance =
|
||||
+ InputMethodSubtypeSafeList.CREATOR.createFromParcel(parcel);
|
||||
+ assertNotNull(newInstance);
|
||||
+ return newInstance;
|
||||
+ } finally {
|
||||
+ if (parcel != null) {
|
||||
+ parcel.recycle();
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void testCreate() {
|
||||
+ assertNotNull(InputMethodSubtypeSafeList.create(createTestInputMethodSubtypeList()));
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void testExtract() {
|
||||
+ assertItemsAfterExtract(
|
||||
+ createTestInputMethodSubtypeList(),
|
||||
+ InputMethodSubtypeSafeList::create);
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void testExtractAfterParceling() {
|
||||
+ assertItemsAfterExtract(
|
||||
+ createTestInputMethodSubtypeList(),
|
||||
+ originals -> cloneViaParcel(InputMethodSubtypeSafeList.create(originals)));
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void testExtractEmptyList() {
|
||||
+ assertItemsAfterExtract(Collections.emptyList(), InputMethodSubtypeSafeList::create);
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void testExtractAfterParcelingEmptyList() {
|
||||
+ assertItemsAfterExtract(Collections.emptyList(),
|
||||
+ originals -> cloneViaParcel(InputMethodSubtypeSafeList.create(originals)));
|
||||
+ }
|
||||
+}
|
||||
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
|
||||
index baa1d1ca77c0f..419d37fd791a1 100644
|
||||
--- a/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
|
||||
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
|
||||
@@ -45,6 +45,7 @@
|
||||
import com.android.internal.inputmethod.IRemoteComputerControlInputConnection;
|
||||
import com.android.internal.inputmethod.IRemoteInputConnection;
|
||||
import com.android.internal.inputmethod.InputMethodInfoSafeList;
|
||||
+import com.android.internal.inputmethod.InputMethodSubtypeSafeList;
|
||||
import com.android.internal.inputmethod.StartInputFlags;
|
||||
import com.android.internal.inputmethod.StartInputReason;
|
||||
import com.android.internal.view.IInputMethodManager;
|
||||
@@ -104,7 +105,8 @@ List<InputMethodInfo> getInputMethodListLegacy(@UserIdInt int userId,
|
||||
@NonNull
|
||||
List<InputMethodInfo> getEnabledInputMethodListLegacy(@UserIdInt int userId);
|
||||
|
||||
- List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
|
||||
+ @NonNull
|
||||
+ InputMethodSubtypeSafeList getEnabledInputMethodSubtypeList(String imiId,
|
||||
boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId);
|
||||
|
||||
InputMethodSubtype getLastInputMethodSubtype(@UserIdInt int userId);
|
||||
@@ -255,8 +257,9 @@ public List<InputMethodInfo> getEnabledInputMethodListLegacy(@UserIdInt int user
|
||||
return mCallback.getEnabledInputMethodListLegacy(userId);
|
||||
}
|
||||
|
||||
+ @NonNull
|
||||
@Override
|
||||
- public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
|
||||
+ public InputMethodSubtypeSafeList getEnabledInputMethodSubtypeList(String imiId,
|
||||
boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) {
|
||||
return mCallback.getEnabledInputMethodSubtypeList(imiId, allowsImplicitlyEnabledSubtypes,
|
||||
userId);
|
||||
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
|
||||
index 355ca4048868a..1ec1c79497ee0 100644
|
||||
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
|
||||
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
|
||||
@@ -172,6 +172,7 @@
|
||||
import com.android.internal.inputmethod.InputMethodInfoSafeList;
|
||||
import com.android.internal.inputmethod.InputMethodNavButtonFlags;
|
||||
import com.android.internal.inputmethod.InputMethodSubtypeHandle;
|
||||
+import com.android.internal.inputmethod.InputMethodSubtypeSafeList;
|
||||
import com.android.internal.inputmethod.SoftInputShowHideReason;
|
||||
import com.android.internal.inputmethod.StartInputFlags;
|
||||
import com.android.internal.inputmethod.StartInputReason;
|
||||
@@ -1585,7 +1586,7 @@ public InputMethodInfoSafeList getInputMethodList(@UserIdInt int userId,
|
||||
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
|
||||
}
|
||||
if (!mUserManagerInternal.exists(userId)) {
|
||||
- return InputMethodInfoSafeList.empty();
|
||||
+ return InputMethodInfoSafeList.create(null);
|
||||
}
|
||||
final int callingUid = Binder.getCallingUid();
|
||||
final long ident = Binder.clearCallingIdentity();
|
||||
@@ -1606,7 +1607,7 @@ public InputMethodInfoSafeList getEnabledInputMethodList(@UserIdInt int userId)
|
||||
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
|
||||
}
|
||||
if (!mUserManagerInternal.exists(userId)) {
|
||||
- return InputMethodInfoSafeList.empty();
|
||||
+ return InputMethodInfoSafeList.create(null);
|
||||
}
|
||||
final int callingUid = Binder.getCallingUid();
|
||||
final long ident = Binder.clearCallingIdentity();
|
||||
@@ -1730,8 +1731,9 @@ private List<InputMethodInfo> getEnabledInputMethodListInternal(@UserIdInt int u
|
||||
* subtypes
|
||||
* @param userId the user ID to be queried about
|
||||
*/
|
||||
+ @NonNull
|
||||
@Override
|
||||
- public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
|
||||
+ public InputMethodSubtypeSafeList getEnabledInputMethodSubtypeList(String imiId,
|
||||
boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) {
|
||||
if (UserHandle.getCallingUserId() != userId) {
|
||||
mContext.enforceCallingOrSelfPermission(
|
||||
@@ -1741,8 +1743,9 @@ public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
|
||||
final int callingUid = Binder.getCallingUid();
|
||||
final long ident = Binder.clearCallingIdentity();
|
||||
try {
|
||||
- return getEnabledInputMethodSubtypeListInternal(imiId,
|
||||
- allowsImplicitlyEnabledSubtypes, userId, callingUid);
|
||||
+ return InputMethodSubtypeSafeList.create(
|
||||
+ getEnabledInputMethodSubtypeListInternal(imiId,
|
||||
+ allowsImplicitlyEnabledSubtypes, userId, callingUid));
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(ident);
|
||||
}
|
||||
diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
|
||||
index 910c9a688969b..e8a3da5f0199b 100644
|
||||
--- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
|
||||
+++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
|
||||
@@ -61,6 +61,7 @@
|
||||
import com.android.internal.inputmethod.IRemoteComputerControlInputConnection;
|
||||
import com.android.internal.inputmethod.IRemoteInputConnection;
|
||||
import com.android.internal.inputmethod.InputMethodInfoSafeList;
|
||||
+import com.android.internal.inputmethod.InputMethodSubtypeSafeList;
|
||||
import com.android.internal.inputmethod.StartInputFlags;
|
||||
import com.android.internal.inputmethod.StartInputReason;
|
||||
import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
|
||||
@@ -149,8 +150,9 @@ public List<InputMethodInfo> getEnabledInputMethodListLegacy(int userId) {
|
||||
return mInner.getEnabledInputMethodListLegacy(userId);
|
||||
}
|
||||
|
||||
+ @NonNull
|
||||
@Override
|
||||
- public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
|
||||
+ public InputMethodSubtypeSafeList getEnabledInputMethodSubtypeList(String imiId,
|
||||
boolean allowsImplicitlyEnabledSubtypes, int userId) {
|
||||
return mInner.getEnabledInputMethodSubtypeList(imiId, allowsImplicitlyEnabledSubtypes,
|
||||
userId);
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
From cee45869c491d4e39877918ee881eb60dec7d6e5 Mon Sep 17 00:00:00 2001
|
||||
From: Iustin Ventaniuc <iustiniv@google.com>
|
||||
Date: Thu, 13 Nov 2025 09:48:16 +0000
|
||||
Subject: [PATCH] Handle loadDescription OutOfMemoryError in DeviceAdminInfo
|
||||
|
||||
loadDescription was potentially vulnerable to an attack which causes a
|
||||
DoS exploit by injecting a maliciously large string into the Receiver's
|
||||
label.
|
||||
|
||||
Bug: 443062265
|
||||
Test: manual
|
||||
Flag: EXEMPT BUGFIX
|
||||
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:06a5b2327caa3aa8843496458e98b9bb070df6e5
|
||||
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:660101e3cdd2f8e8bc627517691dfb885a5c8302
|
||||
Merged-In: Icab26c4b77e73f0fcb9a560e3211482ebe2f37bf
|
||||
Change-Id: Icab26c4b77e73f0fcb9a560e3211482ebe2f37bf
|
||||
---
|
||||
core/java/android/app/admin/DeviceAdminInfo.java | 6 +++++-
|
||||
1 file changed, 5 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
|
||||
index 7b46db16f80a..7616d805c31c 100644
|
||||
--- a/core/java/android/app/admin/DeviceAdminInfo.java
|
||||
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
|
||||
@@ -473,8 +473,12 @@ public final class DeviceAdminInfo implements Parcelable {
|
||||
*/
|
||||
public CharSequence loadDescription(PackageManager pm) throws NotFoundException {
|
||||
if (mActivityInfo.descriptionRes != 0) {
|
||||
- return pm.getText(mActivityInfo.packageName,
|
||||
+ try {
|
||||
+ return pm.getText(mActivityInfo.packageName,
|
||||
mActivityInfo.descriptionRes, mActivityInfo.applicationInfo);
|
||||
+ } catch (OutOfMemoryError e) {
|
||||
+ throw new NotFoundException();
|
||||
+ }
|
||||
}
|
||||
throw new NotFoundException();
|
||||
}
|
||||
--
|
||||
2.53.0
|
||||
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
From 4264c2f606cdd538fc5825827ede4d1669015f66 Mon Sep 17 00:00:00 2001
|
||||
From: Annie Lin <theannielin@google.com>
|
||||
Date: Wed, 3 Dec 2025 17:18:51 -0800
|
||||
Subject: [PATCH] Prevent launchedFromPackage spoofing via
|
||||
FLAG_ACTIVITY_FORWARD_RESULT.
|
||||
|
||||
Bug: 457742426
|
||||
Test: atest ActivityStarterTests
|
||||
Test: Verified via test app
|
||||
Flag: EXEMPT CVE_FIX
|
||||
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:3bb240273822e41f3c6911c60d15983a600308f7
|
||||
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:8e6622267809c37fa7989e4f07af3834f39f09e2
|
||||
Merged-In: Ic9637c56803b00acc9fca59f8092ed02dd46a4fb
|
||||
Change-Id: Ic9637c56803b00acc9fca59f8092ed02dd46a4fb
|
||||
---
|
||||
.../android/server/wm/ActivityStarter.java | 20 +++--
|
||||
.../server/wm/ActivityStarterTests.java | 82 +++++++++++++++++++
|
||||
2 files changed, 97 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
|
||||
index 21638a31a629b..b0a80079682af 100644
|
||||
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
|
||||
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
|
||||
@@ -1131,14 +1131,24 @@ private int executeRequest(Request request) {
|
||||
// in the flow, and asking to forward its result back to the previous. In this
|
||||
// case the activity is serving as a trampoline between the two, so we also want
|
||||
// to update its launchedFromPackage to be the same as the previous activity.
|
||||
- // Note that this is safe, since we know these two packages come from the same
|
||||
- // uid; the caller could just as well have supplied that same package name itself
|
||||
- // . This specifially deals with the case of an intent picker/chooser being
|
||||
+ // This specifically deals with the case of an intent picker/chooser being
|
||||
// launched in the app flow to redirect to an activity picked by the user, where
|
||||
// we want the final activity to consider it to have been launched by the
|
||||
// previous app activity.
|
||||
- callingPackage = sourceRecord.launchedFromPackage;
|
||||
- callingFeatureId = sourceRecord.launchedFromFeatureId;
|
||||
+ final String launchedFromPackage = sourceRecord.launchedFromPackage;
|
||||
+ if (launchedFromPackage != null) {
|
||||
+ final PackageManagerInternal pmInternal =
|
||||
+ mService.getPackageManagerInternalLocked();
|
||||
+ final int packageUid = pmInternal.getPackageUid(
|
||||
+ launchedFromPackage, 0 /* flags */,
|
||||
+ UserHandle.getUserId(callingUid));
|
||||
+ // Only override callingPackage and callingFeatureId based on package UID check.
|
||||
+ // This is to prevent spoofing. See b/457742426.
|
||||
+ if (UserHandle.isSameApp(packageUid, callingUid)) {
|
||||
+ callingPackage = launchedFromPackage;
|
||||
+ callingFeatureId = sourceRecord.launchedFromFeatureId;
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
|
||||
index e4ad38689daa4..6d59fe5b21bda 100644
|
||||
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
|
||||
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
|
||||
@@ -1997,6 +1997,88 @@ private ActivityRecord createBubbledActivity() {
|
||||
.build();
|
||||
}
|
||||
|
||||
+ /**
|
||||
+ * This test simulates the following scenario:
|
||||
+ * 1. Privileged app (P) starts malicious app's activity (M1).
|
||||
+ * 2. M1 starts M2 (also in malicious app) using startNextMatchingActivity().
|
||||
+ * This causes M2's launchedFromPackage to be P.
|
||||
+ * 3. M2 starts an activity in P (P2) using startActivity() with
|
||||
+ * FLAG_ACTIVITY_FORWARD_RESULT.
|
||||
+ * The test verifies that P2's launchedFromPackage is M, not P.
|
||||
+ * See b/457742426 for details.
|
||||
+ */
|
||||
+ @Test
|
||||
+ public void testLaunchedFromPackage_nextMatchingActivity_forwardResult() {
|
||||
+ final String privilegedPackage = "com.test.privileged";
|
||||
+ final int privilegedUid = 10001;
|
||||
+ final String maliciousPackage = "com.test.malicious";
|
||||
+ final int maliciousUid = 10002;
|
||||
+
|
||||
+ // Setup P1 activity
|
||||
+ final ActivityRecord p1 = new ActivityBuilder(mAtm)
|
||||
+ .setComponent(new ComponentName(privilegedPackage, "P1Activity"))
|
||||
+ .setUid(privilegedUid)
|
||||
+ .setCreateTask(true)
|
||||
+ .build();
|
||||
+
|
||||
+ // Setup M1 activity, launched by P1
|
||||
+ final ActivityRecord m1 = new ActivityBuilder(mAtm)
|
||||
+ .setComponent(new ComponentName(maliciousPackage, "M1Activity"))
|
||||
+ .setUid(maliciousUid)
|
||||
+ .setCreateTask(true)
|
||||
+ .setLaunchedFromPackage(privilegedPackage)
|
||||
+ .setLaunchedFromUid(privilegedUid)
|
||||
+ .build();
|
||||
+ m1.resultTo = p1;
|
||||
+
|
||||
+ // Setup M2 activity, as if launched from M1 via startNextMatchingActivity()
|
||||
+ final ActivityRecord m2 = new ActivityBuilder(mAtm)
|
||||
+ .setComponent(new ComponentName(maliciousPackage, "M2Activity"))
|
||||
+ .setUid(maliciousUid)
|
||||
+ .setCreateTask(true)
|
||||
+ .setLaunchedFromPackage(privilegedPackage) // Spoofed package name
|
||||
+ .setLaunchedFromUid(maliciousUid)
|
||||
+ .build();
|
||||
+ m2.resultTo = p1; // result is forwarded
|
||||
+
|
||||
+ // M2 starts P2
|
||||
+ final ActivityStarter starter = prepareStarter(0);
|
||||
+ doReturn(privilegedUid).when(mMockPackageManager).getPackageUid(
|
||||
+ eq(privilegedPackage), anyLong(), anyInt());
|
||||
+ doReturn(maliciousUid).when(mMockPackageManager).getPackageUid(
|
||||
+ eq(maliciousPackage), anyLong(), anyInt());
|
||||
+ starter.setCallingPackage(maliciousPackage);
|
||||
+ starter.setCallingUid(maliciousUid);
|
||||
+
|
||||
+ final Intent p2Intent = new Intent();
|
||||
+ p2Intent.setComponent(new ComponentName(privilegedPackage, "P2Activity"));
|
||||
+ p2Intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
|
||||
+
|
||||
+ final ActivityInfo p2ActivityInfo = new ActivityInfo();
|
||||
+ p2ActivityInfo.applicationInfo = new ApplicationInfo();
|
||||
+ p2ActivityInfo.applicationInfo.packageName = privilegedPackage;
|
||||
+ p2ActivityInfo.applicationInfo.uid = privilegedUid;
|
||||
+ p2ActivityInfo.name = "P2Activity";
|
||||
+
|
||||
+ final ActivityRecord[] outActivity = new ActivityRecord[1];
|
||||
+
|
||||
+ // The request simulates M2 starting P2
|
||||
+ starter.setIntent(p2Intent)
|
||||
+ .setActivityInfo(p2ActivityInfo)
|
||||
+ .setResultTo(m2.token) // sourceRecord is m2
|
||||
+ .setRequestCode(-1) // for startActivity()
|
||||
+ .setOutActivity(outActivity)
|
||||
+ .execute();
|
||||
+
|
||||
+ final ActivityRecord p2 = outActivity[0];
|
||||
+
|
||||
+ assertNotNull(p2);
|
||||
+ assertEquals("launchedFromPackage should be the immediate caller",
|
||||
+ maliciousPackage, p2.launchedFromPackage);
|
||||
+ assertEquals("launchedFromUid should be the immediate caller",
|
||||
+ maliciousUid, p2.launchedFromUid);
|
||||
+ }
|
||||
+
|
||||
private static void startActivityInner(ActivityStarter starter, ActivityRecord target,
|
||||
ActivityRecord source, ActivityOptions options, Task inTask,
|
||||
TaskFragment inTaskFragment) {
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
From 09055276288a68cf35b0f84ba32e28822f74ecf9 Mon Sep 17 00:00:00 2001
|
||||
From: Sanjana Sunil <sanjanasunil@google.com>
|
||||
Date: Wed, 26 Nov 2025 14:40:09 +0000
|
||||
Subject: [PATCH] Explicitly unset INSTALL_FROM_MANAGED_USER_OR_PROFILE flag
|
||||
|
||||
If the flag is not explicitly unset, an app could set this flag, leading
|
||||
to unexpected behaviour if the install is not actually from a managed
|
||||
user or profile.
|
||||
|
||||
Bug: 459461121
|
||||
Test: atest PackageManagerShellCommandInstallTest#testSessionCreationWithManagedUserOrProfileFlag_notFromManagedProfile
|
||||
Flag: EXEMPT BUGFIX
|
||||
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:990428772d4718853382ec4c5feda2b7bd6f923f
|
||||
Merged-In: I21bbbf628e97244d469eb23ce0558dbf560b7618
|
||||
Change-Id: I21bbbf628e97244d469eb23ce0558dbf560b7618
|
||||
---
|
||||
.../java/com/android/server/pm/PackageInstallerService.java | 2 ++
|
||||
1 file changed, 2 insertions(+)
|
||||
|
||||
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
|
||||
index 2d0bb258e89f..a0ad5d1aaa30 100644
|
||||
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
|
||||
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
|
||||
@@ -1027,6 +1027,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
|
||||
}
|
||||
|
||||
final var dpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
|
||||
+ // Only the system should be able to set this flag - so ensure it is unset when not needed.
|
||||
+ params.installFlags &= ~PackageManager.INSTALL_FROM_MANAGED_USER_OR_PROFILE;
|
||||
if (dpmi != null && dpmi.isUserOrganizationManaged(userId)) {
|
||||
params.installFlags |= PackageManager.INSTALL_FROM_MANAGED_USER_OR_PROFILE;
|
||||
}
|
||||
--
|
||||
2.53.0
|
||||
|
||||
|
|
@ -0,0 +1,236 @@
|
|||
From 3aaff9fb5b1b5428d57297b168efa01072463321 Mon Sep 17 00:00:00 2001
|
||||
From: Julia Reynolds <juliacr@google.com>
|
||||
Date: Wed, 12 Nov 2025 12:09:31 -0500
|
||||
Subject: [PATCH] Be more strict about content types for message array
|
||||
|
||||
Now with fewer class cast exceptions
|
||||
|
||||
Test: ConversationNotification
|
||||
Test: NotificationManagerServiceTest
|
||||
Bug: 433746973
|
||||
Flag: EXEMPT BUGFIX
|
||||
(cherry picked from commit 71d4afae00c7d6d9238f8ec82303e1e13da50fbb)
|
||||
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:f64c1e377842d9a8df814bcbad831bd4ce01583d
|
||||
Merged-In: I3022e010de95f14dcd0d09d123684ee265101e0a
|
||||
Change-Id: I3022e010de95f14dcd0d09d123684ee265101e0a
|
||||
---
|
||||
core/java/android/app/Notification.java | 18 ++--
|
||||
.../NotificationManagerServiceTest.java | 102 +++++++++++++++++-
|
||||
2 files changed, 108 insertions(+), 12 deletions(-)
|
||||
|
||||
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
|
||||
index 354e594cf7303..85921a77c6b43 100644
|
||||
--- a/core/java/android/app/Notification.java
|
||||
+++ b/core/java/android/app/Notification.java
|
||||
@@ -3236,8 +3236,8 @@ public void visitUris(@NonNull Consumer<Uri> visitor) {
|
||||
person.visitUris(visitor);
|
||||
}
|
||||
|
||||
- final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES,
|
||||
- Parcelable.class);
|
||||
+ final Bundle[] messages =
|
||||
+ getParcelableArrayFromBundle(extras, EXTRA_MESSAGES, Bundle.class);
|
||||
if (!ArrayUtils.isEmpty(messages)) {
|
||||
for (MessagingStyle.Message message : MessagingStyle.Message
|
||||
.getMessagesFromBundleArray(messages)) {
|
||||
@@ -3245,8 +3245,8 @@ public void visitUris(@NonNull Consumer<Uri> visitor) {
|
||||
}
|
||||
}
|
||||
|
||||
- final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES,
|
||||
- Parcelable.class);
|
||||
+ final Parcelable[] historic =
|
||||
+ getParcelableArrayFromBundle(extras, EXTRA_HISTORIC_MESSAGES, Bundle.class);
|
||||
if (!ArrayUtils.isEmpty(historic)) {
|
||||
for (MessagingStyle.Message message : MessagingStyle.Message
|
||||
.getMessagesFromBundleArray(historic)) {
|
||||
@@ -8501,8 +8501,8 @@ public boolean showsChronometer() {
|
||||
*/
|
||||
public boolean hasImage() {
|
||||
if (isStyle(MessagingStyle.class) && extras != null) {
|
||||
- final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES,
|
||||
- Parcelable.class);
|
||||
+ final Bundle[] messages =
|
||||
+ getParcelableArrayFromBundle(extras, EXTRA_MESSAGES, Bundle.class);
|
||||
if (!ArrayUtils.isEmpty(messages)) {
|
||||
for (MessagingStyle.Message m : MessagingStyle.Message
|
||||
.getMessagesFromBundleArray(messages)) {
|
||||
@@ -9794,10 +9794,10 @@ protected void restoreFromExtras(Bundle extras) {
|
||||
mUser = user;
|
||||
}
|
||||
mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE);
|
||||
- Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES, Parcelable.class);
|
||||
+ Bundle[] messages = getParcelableArrayFromBundle(extras, EXTRA_MESSAGES, Bundle.class);
|
||||
mMessages = Message.getMessagesFromBundleArray(messages);
|
||||
- Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES,
|
||||
- Parcelable.class);
|
||||
+ Bundle[] histMessages = getParcelableArrayFromBundle(
|
||||
+ extras, EXTRA_HISTORIC_MESSAGES, Bundle.class);
|
||||
mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
|
||||
mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION);
|
||||
mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
|
||||
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
|
||||
index 7dc0921db4104..7be8cba2d23bf 100644
|
||||
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
|
||||
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
|
||||
@@ -30,6 +30,8 @@
|
||||
import static android.app.Flags.FLAG_NM_SUMMARIZATION_UI;
|
||||
import static android.app.Flags.FLAG_UI_RICH_ONGOING;
|
||||
import static android.app.Notification.EXTRA_ALLOW_DURING_SETUP;
|
||||
+import static android.app.Notification.EXTRA_MESSAGES;
|
||||
+import static android.app.Notification.EXTRA_MESSAGING_PERSON;
|
||||
import static android.app.Notification.EXTRA_PICTURE;
|
||||
import static android.app.Notification.EXTRA_PICTURE_ICON;
|
||||
import static android.app.Notification.EXTRA_PREFER_SMALL_ICON;
|
||||
@@ -87,6 +89,7 @@
|
||||
import static android.app.PendingIntent.FLAG_IMMUTABLE;
|
||||
import static android.app.PendingIntent.FLAG_MUTABLE;
|
||||
import static android.app.PendingIntent.FLAG_ONE_SHOT;
|
||||
+import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
|
||||
import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED;
|
||||
import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED;
|
||||
import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
|
||||
@@ -246,11 +249,13 @@
|
||||
import android.compat.testing.PlatformCompatChangeRule;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
+import android.content.ContentProvider;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.IIntentSender;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
+import android.content.UriPermission;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.IPackageManager;
|
||||
@@ -267,6 +272,7 @@
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
+import android.graphics.Rect;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.AudioManager;
|
||||
@@ -1535,6 +1541,96 @@ private void verifyToastShownForTestPackage(String text, int displayId) {
|
||||
eq(TOAST_DURATION), any(), eq(displayId));
|
||||
}
|
||||
|
||||
+ @Test
|
||||
+ public void testNoUriGrantsForBadMessagesList() throws RemoteException {
|
||||
+ Uri targetUri = Uri.parse("content://com.android.contacts/display_photo/1");
|
||||
+
|
||||
+ // create message person
|
||||
+ Person person = new Person.Builder()
|
||||
+ .setName("Name")
|
||||
+ .setIcon(Icon.createWithContentUri(targetUri))
|
||||
+ .setKey("user_123")
|
||||
+ .setBot(false)
|
||||
+ .build();
|
||||
+
|
||||
+ // create MessagingStyle
|
||||
+ Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle(person)
|
||||
+ .setConversationTitle("Bug discussion")
|
||||
+ .setGroupConversation(true)
|
||||
+ .addMessage("Hi,look my photo", System.currentTimeMillis() - 60000, person)
|
||||
+ .addMessage("Oho, you used my contacts photo",
|
||||
+ System.currentTimeMillis() - 30000, "Friend");
|
||||
+
|
||||
+ // create Notification
|
||||
+ Notification notification = new Notification.Builder(mContext, TEST_CHANNEL_ID)
|
||||
+ .setSmallIcon(R.drawable.sym_def_app_icon)
|
||||
+ .setContentTitle("")
|
||||
+ .setContentText("")
|
||||
+ .setAutoCancel(true)
|
||||
+ .setStyle(messagingStyle)
|
||||
+ .setCategory(Notification.CATEGORY_MESSAGE)
|
||||
+ .setFlag(Notification.FLAG_GROUP_SUMMARY, true)
|
||||
+ .build();
|
||||
+ notification.contentIntent = createPendingIntent("open");
|
||||
+
|
||||
+ notification.extras.remove(EXTRA_MESSAGING_PERSON);
|
||||
+
|
||||
+ // add BadClipDescription to avoid visitUri check uris in EXTRA_MESSAGES value
|
||||
+ ArrayList<Parcelable> parcelableArray =
|
||||
+ new ArrayList<>(List.of(notification.extras.getParcelableArray(EXTRA_MESSAGES)));
|
||||
+ parcelableArray.add(new MyParceledListSlice());
|
||||
+ notification.extras.putParcelableArray(
|
||||
+ EXTRA_MESSAGES, parcelableArray.toArray(new Parcelable[0]));
|
||||
+ try {
|
||||
+ mBinderService.enqueueNotificationWithTag(mPkg, mPkg,
|
||||
+ "testNoUriGrantsForBadMessagesList",
|
||||
+ 1, notification, mContext.getUserId());
|
||||
+ waitForIdle();
|
||||
+ fail("should have failed to parse messages");
|
||||
+ } catch (java.lang.ArrayStoreException e) {
|
||||
+ verify(mUgmInternal, never()).checkGrantUriPermission(
|
||||
+ anyInt(), any(), eq(ContentProvider.getUriWithoutUserId(targetUri)),
|
||||
+ anyInt(), anyInt());
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private class MyParceledListSlice extends Intent {
|
||||
+ @Override
|
||||
+ public void writeToParcel(Parcel dest, int i) {
|
||||
+ Parcel test = Parcel.obtain();
|
||||
+ test.writeString(this.getClass().getName());
|
||||
+ int strLength = test.dataSize();
|
||||
+ test.recycle();
|
||||
+ dest.setDataPosition(dest.dataPosition() - strLength);
|
||||
+ dest.writeString("android.content.pm.ParceledListSlice");
|
||||
+
|
||||
+ dest.writeInt(1);
|
||||
+ dest.writeString(UriPermission.class.getName());
|
||||
+ dest.writeInt(0); // use binder
|
||||
+ dest.writeStrongBinder(new Binder() {
|
||||
+ private int callingPid = -1;
|
||||
+ @Override
|
||||
+ public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
|
||||
+ throws RemoteException {
|
||||
+ if (code == 1) {
|
||||
+ reply.writeNoException();
|
||||
+ reply.writeInt(1);
|
||||
+ if (getCallingUid() == 1000 && callingPid == -1) {
|
||||
+ reply.writeParcelable(new Rect(), 0);
|
||||
+ callingPid = getCallingPid();
|
||||
+ } else {
|
||||
+ reply.writeInt(-1);
|
||||
+ reply.writeInt(-1);
|
||||
+ reply.writeLong(0);
|
||||
+ }
|
||||
+ return true;
|
||||
+ }
|
||||
+ return super.onTransact(code, data, reply, flags);
|
||||
+ }
|
||||
+ });
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
@Test
|
||||
public void testDefaultAssistant_overrideDefault() {
|
||||
final int userId = mContext.getUserId();
|
||||
@@ -8589,7 +8685,7 @@ public void testVisitUris() throws Exception {
|
||||
Bundle extras = new Bundle();
|
||||
extras.putParcelable(Notification.EXTRA_AUDIO_CONTENTS_URI, audioContents);
|
||||
extras.putString(Notification.EXTRA_BACKGROUND_IMAGE_URI, backgroundImage.toString());
|
||||
- extras.putParcelable(Notification.EXTRA_MESSAGING_PERSON, person1);
|
||||
+ extras.putParcelable(EXTRA_MESSAGING_PERSON, person1);
|
||||
extras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST,
|
||||
new ArrayList<>(Arrays.asList(person2, person3)));
|
||||
extras.putParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
|
||||
@@ -8727,13 +8823,13 @@ public void testVisitUris_styleExtrasWithoutStyle() {
|
||||
.setSmallIcon(android.R.drawable.sym_def_app_icon);
|
||||
|
||||
Bundle messagingExtras = new Bundle();
|
||||
- messagingExtras.putParcelable(Notification.EXTRA_MESSAGING_PERSON,
|
||||
+ messagingExtras.putParcelable(EXTRA_MESSAGING_PERSON,
|
||||
personWithIcon("content://user"));
|
||||
messagingExtras.putParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES,
|
||||
new Bundle[] { new Notification.MessagingStyle.Message("Heyhey!",
|
||||
System.currentTimeMillis() - 100,
|
||||
personWithIcon("content://historicalMessenger")).toBundle()});
|
||||
- messagingExtras.putParcelableArray(Notification.EXTRA_MESSAGES,
|
||||
+ messagingExtras.putParcelableArray(EXTRA_MESSAGES,
|
||||
new Bundle[] { new Notification.MessagingStyle.Message("Are you there?",
|
||||
System.currentTimeMillis(),
|
||||
personWithIcon("content://messenger")).toBundle()});
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
From 924df83d73d9f938fde025c2e793ca12646207e0 Mon Sep 17 00:00:00 2001
|
||||
From: Evan Chen <evanxinchen@google.com>
|
||||
Date: Tue, 18 Nov 2025 22:34:11 +0000
|
||||
Subject: [PATCH] Remove any revoked associations after reboot
|
||||
|
||||
Test: manually
|
||||
Bug: 442392902
|
||||
Flag: EXEMPT bugfix
|
||||
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:13714bcfaff6ef1c16d0aa3d359b1c8bc1859ac3
|
||||
Merged-In: I94b96d98608d6702e1d3a9581e135280149bf7e1
|
||||
Change-Id: I94b96d98608d6702e1d3a9581e135280149bf7e1
|
||||
---
|
||||
.../server/companion/CompanionDeviceManagerService.java | 6 ++++++
|
||||
1 file changed, 6 insertions(+)
|
||||
|
||||
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
|
||||
index 7ff1ddb7dc79..fdefbc412136 100644
|
||||
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
|
||||
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
|
||||
@@ -33,6 +33,7 @@ import static com.android.internal.util.CollectionUtils.any;
|
||||
import static com.android.internal.util.Preconditions.checkState;
|
||||
import static com.android.server.companion.association.DisassociationProcessor.REASON_API;
|
||||
import static com.android.server.companion.association.DisassociationProcessor.REASON_PKG_DATA_CLEARED;
|
||||
+import static com.android.server.companion.association.DisassociationProcessor.REASON_REVOKED;
|
||||
import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature;
|
||||
import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed;
|
||||
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
|
||||
@@ -197,6 +198,11 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
// Init association stores
|
||||
mAssociationStore.refreshCache();
|
||||
|
||||
+ // Remove any revoked associations after reboot.
|
||||
+ for (AssociationInfo ai : mAssociationStore.getRevokedAssociations()) {
|
||||
+ mDisassociationProcessor.disassociate(ai.getId(), REASON_REVOKED);
|
||||
+ }
|
||||
+
|
||||
// Init UUID store
|
||||
mObservableUuidStore.getObservableUuidsForUser(getContext().getUserId());
|
||||
|
||||
--
|
||||
2.53.0
|
||||
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
From dc8121842868bd90e04176ed42feab3f7e47956b Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= <matiashe@google.com>
|
||||
Date: Wed, 20 Aug 2025 15:57:28 +0200
|
||||
Subject: [PATCH] Limit the number of services (NLSes, etc) that can be
|
||||
approved per user
|
||||
|
||||
Trying to activate additional packages/components will be silently rejected.
|
||||
|
||||
Bug: 428701593
|
||||
Test: atest ManagedServicesTest NotificationManagerServiceTest
|
||||
Flag: com.android.server.notification.limit_managed_services_count
|
||||
(cherry picked from commit a132684a093d9e1750100b39d4e4168f2d27d349)
|
||||
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:182548fd95b0f245385e5dc45efd2cbd4cd35b57
|
||||
Merged-In: Iddd8044997c41f97369b768f4da5e49efc43ad06
|
||||
Change-Id: Iddd8044997c41f97369b768f4da5e49efc43ad06
|
||||
---
|
||||
.../com/android/server/notification/ManagedServices.java | 8 +++-----
|
||||
.../server/notification/NotificationManagerService.java | 6 +++---
|
||||
.../android/server/notification/ManagedServicesTest.java | 2 --
|
||||
.../notification/NotificationManagerServiceTest.java | 1 -
|
||||
4 files changed, 6 insertions(+), 11 deletions(-)
|
||||
|
||||
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
|
||||
index 54cf810fc0397..1dd298cb67b33 100644
|
||||
--- a/services/core/java/com/android/server/notification/ManagedServices.java
|
||||
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
|
||||
@@ -978,8 +978,7 @@ protected boolean setPackageOrComponentEnabled(String pkgOrComponent, int userId
|
||||
if (approvedItem != null) {
|
||||
int uid = getUidForPackageOrComponent(pkgOrComponent, userId);
|
||||
if (enabled) {
|
||||
- if (!Flags.limitManagedServicesCount()
|
||||
- || approved.size() < MAX_SERVICE_ENTRIES) {
|
||||
+ if (approved.size() < MAX_SERVICE_ENTRIES) {
|
||||
approved.add(approvedItem);
|
||||
if (uid != Process.INVALID_UID) {
|
||||
approvedUids.add(uid);
|
||||
@@ -1006,8 +1005,7 @@ protected boolean setPackageOrComponentEnabled(String pkgOrComponent, int userId
|
||||
mUserSetServices.put(userId, userSetServices);
|
||||
}
|
||||
if (userSet) {
|
||||
- if (!Flags.limitManagedServicesCount()
|
||||
- || userSetServices.size() < MAX_SERVICE_ENTRIES) {
|
||||
+ if (userSetServices.size() < MAX_SERVICE_ENTRIES) {
|
||||
userSetServices.add(pkgOrComponent);
|
||||
}
|
||||
} else {
|
||||
@@ -1016,7 +1014,7 @@ protected boolean setPackageOrComponentEnabled(String pkgOrComponent, int userId
|
||||
}
|
||||
}
|
||||
|
||||
- if (!Flags.limitManagedServicesCount() || changed) {
|
||||
+ if (changed) {
|
||||
rebindServices(false, userId);
|
||||
}
|
||||
|
||||
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
|
||||
index 1b2042fc8532c..11ea74dae665b 100644
|
||||
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
|
||||
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
|
||||
@@ -7006,7 +7006,7 @@ public void setNotificationPolicyAccessGrantedForUser(
|
||||
pkg, userId, mConditionProviders.getRequiredPermission())) {
|
||||
boolean changed = mConditionProviders.setPackageOrComponentEnabled(pkg, userId,
|
||||
/* isPrimary= */ true, granted);
|
||||
- if (Flags.limitManagedServicesCount() && !changed) {
|
||||
+ if (!changed) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7276,7 +7276,7 @@ public void setNotificationListenerAccessGrantedForUser(ComponentName listener,
|
||||
boolean changed = mListeners.setPackageOrComponentEnabled(
|
||||
listener.flattenToString(), userId, /* isPrimary= */ true, granted,
|
||||
userSet);
|
||||
- if (Flags.limitManagedServicesCount() && !changed) {
|
||||
+ if (!changed) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -13809,7 +13809,7 @@ protected boolean setPackageOrComponentEnabled(String pkgOrComponent, int userId
|
||||
boolean isPrimary, boolean enabled, boolean userSet) {
|
||||
boolean changed = super.setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary,
|
||||
enabled, userSet);
|
||||
- if (Flags.limitManagedServicesCount() && !changed) {
|
||||
+ if (!changed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
|
||||
index 97f9f9ceb4aff..0de1d377bd09a 100644
|
||||
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
|
||||
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
|
||||
@@ -2586,7 +2586,6 @@ public void isUidAllowed_multipleApprovedUids_returnsTrueForBoth() {
|
||||
}
|
||||
|
||||
@Test
|
||||
- @EnableFlags(Flags.FLAG_LIMIT_MANAGED_SERVICES_COUNT)
|
||||
public void setPackageOrComponentEnabled_tooManyPackages_stopsAdding() {
|
||||
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
|
||||
mIpm, APPROVAL_BY_PACKAGE);
|
||||
@@ -2614,7 +2613,6 @@ public void setPackageOrComponentEnabled_tooManyPackages_stopsAdding() {
|
||||
}
|
||||
|
||||
@Test
|
||||
- @EnableFlags(Flags.FLAG_LIMIT_MANAGED_SERVICES_COUNT)
|
||||
public void setPackageOrComponentEnabled_tooManyChanges_stopsAddingToUserSet() {
|
||||
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
|
||||
mIpm, APPROVAL_BY_PACKAGE);
|
||||
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
|
||||
index 7be8cba2d23bf..f96fd66caca6d 100644
|
||||
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
|
||||
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
|
||||
@@ -6478,7 +6478,6 @@ public void testSetListenerAccessForUser_revokeWithNameTooLong_okay() throws Exc
|
||||
}
|
||||
|
||||
@Test
|
||||
- @EnableFlags(Flags.FLAG_LIMIT_MANAGED_SERVICES_COUNT)
|
||||
public void testSetListenerAccessForUser_tooManyListeners_skipsFollowups() throws Exception {
|
||||
UserHandle user = UserHandle.of(mContext.getUserId() + 10);
|
||||
ComponentName c = ComponentName.unflattenFromString("package/Component");
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
From 51368785bd09cd652ed9851727b16545cb92c4e5 Mon Sep 17 00:00:00 2001
|
||||
From: Song Chun Fan <schfan@google.com>
|
||||
Date: Tue, 11 Nov 2025 00:03:48 +0000
|
||||
Subject: [PATCH] [UidMigration] fix update uninstallation with
|
||||
sharedUserMaxSdkVersion
|
||||
|
||||
When a system app is re-enabled after the update is uninstalled, when
|
||||
the system app has shared uid, the current code doesn't support
|
||||
directly reusing the disabled package setting. Instead, the preloaded
|
||||
version is re-scanned and installed as a new app, which can bring
|
||||
breaking behavior of changed UIDs when the manifest has
|
||||
sharedUserMaxSdkVersion.
|
||||
|
||||
This change fixes the bug where registerExistingAppId fails when it
|
||||
comes to shared uid, therefore directly reuses the disabled package
|
||||
setting, consistent with the behavior for non-shared-uid system apps.
|
||||
|
||||
FLAG: EXEMPT BUGFIX
|
||||
Test: manually with system-app-test.sh
|
||||
BUG: 454062218
|
||||
|
||||
|
||||
|
||||
(cherry picked from commit 6b5ea2f7fbf50313d46e54e0d8f8c18c398e4869)
|
||||
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:740256b41ba113708655f82dc5664291bf79edd0
|
||||
Merged-In: I417cec27697a210416027e862a5e5d207d268b82
|
||||
Change-Id: I417cec27697a210416027e862a5e5d207d268b82
|
||||
---
|
||||
.../core/java/com/android/server/pm/Settings.java | 14 +++++++++-----
|
||||
.../src/com/android/server/pm/MockSystem.kt | 2 +-
|
||||
2 files changed, 10 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
|
||||
index 92257f1ee2dd..ab13d7e766e8 100644
|
||||
--- a/services/core/java/com/android/server/pm/Settings.java
|
||||
+++ b/services/core/java/com/android/server/pm/Settings.java
|
||||
@@ -916,8 +916,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
|
||||
p.getPkgState().setUpdatedSystemApp(false);
|
||||
final AndroidPackageInternal pkg = p.getPkg();
|
||||
PackageSetting ret = addPackageLPw(name, p.getRealName(), p.getPath(), p.getAppId(),
|
||||
- p.getFlags(), p.getPrivateFlags(), mDomainVerificationManager.generateNewId(),
|
||||
- pkg == null ? false : pkg.isSdkLibrary());
|
||||
+ p.getFlags(), p.getPrivateFlags(), mDomainVerificationManager.generateNewId(),
|
||||
+ pkg == null ? false : pkg.isSdkLibrary(), p.hasSharedUser());
|
||||
if (ret != null) {
|
||||
ret.setLegacyNativeLibraryPath(p.getLegacyNativeLibraryPath());
|
||||
ret.setPrimaryCpuAbi(p.getPrimaryCpuAbiLegacy());
|
||||
@@ -937,6 +937,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
|
||||
ret.setRestrictUpdateHash(p.getRestrictUpdateHash());
|
||||
ret.setScannedAsStoppedSystemApp(p.isScannedAsStoppedSystemApp());
|
||||
ret.setInstallSource(p.getInstallSource());
|
||||
+ ret.setSharedUserAppId(p.getSharedUserAppId());
|
||||
}
|
||||
mDisabledSysPackages.remove(name);
|
||||
return ret;
|
||||
@@ -958,7 +959,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
|
||||
}
|
||||
|
||||
PackageSetting addPackageLPw(String name, String realName, File codePath, int uid,
|
||||
- int pkgFlags, int pkgPrivateFlags, @NonNull UUID domainSetId, boolean isSdkLibrary) {
|
||||
+ int pkgFlags, int pkgPrivateFlags, @NonNull UUID domainSetId, boolean isSdkLibrary,
|
||||
+ boolean hasSharedUser) {
|
||||
PackageSetting p = mPackages.get(name);
|
||||
if (p != null) {
|
||||
if (p.getAppId() == uid) {
|
||||
@@ -971,7 +973,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
|
||||
p = new PackageSetting(name, realName, codePath, pkgFlags, pkgPrivateFlags, domainSetId)
|
||||
.setAppId(uid);
|
||||
if ((uid == Process.INVALID_UID && isSdkLibrary && Flags.disallowSdkLibsToBeApps())
|
||||
- || mAppIds.registerExistingAppId(uid, p, name)) {
|
||||
+ || mAppIds.registerExistingAppId(uid, p, name)
|
||||
+ || hasSharedUser) {
|
||||
mPackages.put(name, p);
|
||||
return p;
|
||||
}
|
||||
@@ -4266,7 +4269,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
|
||||
} else if (appId > 0 || (appId == Process.INVALID_UID && isSdkLibrary
|
||||
&& Flags.disallowSdkLibsToBeApps())) {
|
||||
packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr),
|
||||
- appId, pkgFlags, pkgPrivateFlags, domainSetId, isSdkLibrary);
|
||||
+ appId, pkgFlags, pkgPrivateFlags, domainSetId, isSdkLibrary,
|
||||
+ /* hasSharedUser= */ false);
|
||||
if (PackageManagerService.DEBUG_SETTINGS)
|
||||
Log.i(PackageManagerService.TAG, "Reading package " + name + ": appId="
|
||||
+ appId + " pkg=" + packageSetting);
|
||||
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
|
||||
index 1b2ab2702d49..9a73ba3c155e 100644
|
||||
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
|
||||
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
|
||||
@@ -168,7 +168,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) {
|
||||
null
|
||||
}
|
||||
whenever(mocks.settings.addPackageLPw(nullable(), nullable(), nullable(), nullable(),
|
||||
- nullable(), nullable(), nullable(), nullable())) {
|
||||
+ nullable(), nullable(), nullable(), nullable(), nullable())) {
|
||||
val name: String = getArgument(0)
|
||||
val pendingAdd = mPendingPackageAdds.firstOrNull { it.first == name }
|
||||
?: return@whenever null
|
||||
--
|
||||
2.53.0
|
||||
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
From c81cf361489e3a3cd764c0a0c85c84958e25d63c Mon Sep 17 00:00:00 2001
|
||||
From: Alec Mouri <alecmouri@google.com>
|
||||
Date: Wed, 5 Nov 2025 21:29:10 +0000
|
||||
Subject: [PATCH] Clip to layer bounds when drawing blur regions
|
||||
|
||||
Otherwise blurs can "escape" layer bounds. This would make 1-2
|
||||
pixel-large layers fill the entire screen if the blur region is
|
||||
appropriately crafted, which is not great.
|
||||
|
||||
Bug: 455563813
|
||||
Flag: EXEMPT CVE_FIX
|
||||
Test: PoC app
|
||||
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:123f8fec995a3103acbc3a1191b9cef71523e013
|
||||
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:f3fecb02978030ae4066235cbe638250996b6a9a
|
||||
Merged-In: If59833f2d5060f5f81395d602e2dcb369a10fdbb
|
||||
Change-Id: If59833f2d5060f5f81395d602e2dcb369a10fdbb
|
||||
---
|
||||
libs/renderengine/skia/SkiaRenderEngine.cpp | 4 ++++
|
||||
1 file changed, 4 insertions(+)
|
||||
|
||||
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
|
||||
index 5b6edb4e30..46c58b0c27 100644
|
||||
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
|
||||
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
|
||||
@@ -907,6 +907,10 @@ void SkiaRenderEngine::drawLayersInternal(
|
||||
SkAutoCanvasRestore acr(canvas, true);
|
||||
if (!roundRectClip.isEmpty()) {
|
||||
canvas->clipRRect(roundRectClip, true);
|
||||
+ } else {
|
||||
+ // We need to clip bounds here since otherwise a client sending a bigger blur region
|
||||
+ // enables the blur to "escape" the layer bounds which is very bad for security
|
||||
+ canvas->clipRRect(bounds, true);
|
||||
}
|
||||
|
||||
// TODO(b/182216890): Filter out empty layers earlier
|
||||
--
|
||||
2.53.0
|
||||
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
From c6da9eeb710c6690d189cb2d1b80b44755860b55 Mon Sep 17 00:00:00 2001
|
||||
From: Kyle Hsiao <kylehsiao@google.com>
|
||||
Date: Wed, 1 Oct 2025 11:57:54 +0000
|
||||
Subject: [PATCH] [NFC] Fix use-after-free in eventCallback
|
||||
|
||||
Transform access to the static shared pointer 'Nfc::mCallback' to be thread-safe to prevent a use-after-free crash.
|
||||
|
||||
The use-after-free occurred when an asynchronous thread (eventCallback) attempted to call a method on 'mCallback' immediately after the main thread Nfc::open had destroyed the underlying object. The simple null check was insufficient due to the race condition.
|
||||
|
||||
This fix implements the correct shared pointer synchronization pattern:
|
||||
1. Protects the read/write of 'mCallback' using 'mCallbackLock'.
|
||||
2. Creates a local 'std::shared_ptr<INfcClientCallback> localCallback' inside the critical section. This local copy holds a temporary strong reference, guaranteeing the object's lifetime for the duration of the subsequent sendEvent() call.
|
||||
|
||||
Bug: 392699284
|
||||
Test: nfc_service_fuzzer
|
||||
Test: atest NfcNciUnitTests
|
||||
Test: atest CtsNfcTestCases
|
||||
Test: atest VtsAidlHalNfcTargetTest
|
||||
Test: atest NfcTestCases
|
||||
Test: atest NfcServiceTest
|
||||
(cherry picked from commit fc619c3348188ff0020cdeb7d4c4728a0246fe1a)
|
||||
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:33b3c128e0f480f6e421761b85c5f00289b62449
|
||||
Merged-In: I8e9e83be7f939bbfc183cb3879808ceacf931450
|
||||
Change-Id: I8e9e83be7f939bbfc183cb3879808ceacf931450
|
||||
---
|
||||
aidl/Nfc.h | 18 ++++++++++++++----
|
||||
1 file changed, 14 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/aidl/Nfc.h b/aidl/Nfc.h
|
||||
index 707bd65..fd23e24 100644
|
||||
--- a/aidl/Nfc.h
|
||||
+++ b/aidl/Nfc.h
|
||||
@@ -32,6 +32,8 @@ using ::aidl::android::hardware::nfc::NfcConfig;
|
||||
using ::aidl::android::hardware::nfc::NfcEvent;
|
||||
using ::aidl::android::hardware::nfc::NfcStatus;
|
||||
|
||||
+static pthread_mutex_t sCallbackLock = PTHREAD_MUTEX_INITIALIZER;
|
||||
+
|
||||
// Default implementation that reports no support NFC.
|
||||
struct Nfc : public BnNfc {
|
||||
public:
|
||||
@@ -52,7 +54,11 @@ struct Nfc : public BnNfc {
|
||||
binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
|
||||
|
||||
static void eventCallback(uint8_t event, uint8_t status) {
|
||||
- if (mCallback != nullptr) {
|
||||
+ std::shared_ptr<INfcClientCallback> localCallback;
|
||||
+ pthread_mutex_lock(&sCallbackLock);
|
||||
+ localCallback = mCallback;
|
||||
+ pthread_mutex_unlock(&sCallbackLock);
|
||||
+ if (localCallback != nullptr) {
|
||||
NfcEvent mEvent;
|
||||
NfcStatus mStatus;
|
||||
switch (event) {
|
||||
@@ -96,7 +102,7 @@ struct Nfc : public BnNfc {
|
||||
default:
|
||||
mStatus = NfcStatus::FAILED;
|
||||
}
|
||||
- auto ret = mCallback->sendEvent(mEvent, mStatus);
|
||||
+ auto ret = localCallback->sendEvent(mEvent, mStatus);
|
||||
if (!ret.isOk()) {
|
||||
LOG(ERROR) << "Failed to send event!";
|
||||
}
|
||||
@@ -104,9 +110,13 @@ struct Nfc : public BnNfc {
|
||||
}
|
||||
|
||||
static void dataCallback(uint16_t data_len, uint8_t* p_data) {
|
||||
+ std::shared_ptr<INfcClientCallback> localCallback;
|
||||
+ pthread_mutex_lock(&sCallbackLock);
|
||||
+ localCallback = mCallback;
|
||||
+ pthread_mutex_unlock(&sCallbackLock);
|
||||
std::vector<uint8_t> data(p_data, p_data + data_len);
|
||||
- if (mCallback != nullptr) {
|
||||
- auto ret = mCallback->sendData(data);
|
||||
+ if (localCallback != nullptr) {
|
||||
+ auto ret = localCallback->sendData(data);
|
||||
if (!ret.isOk()) {
|
||||
LOG(ERROR) << "Failed to send data!";
|
||||
}
|
||||
--
|
||||
2.53.0
|
||||
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
From 48af8a13dd12ecbd0569c328a56d1a7b61a59ca3 Mon Sep 17 00:00:00 2001
|
||||
From: Mill Chen <millchen@google.com>
|
||||
Date: Fri, 26 Sep 2025 09:02:26 +0000
|
||||
Subject: [PATCH] Check permission of the calling package in multi-pane devices
|
||||
|
||||
Bug: 430047417
|
||||
Test: manual test
|
||||
Flag: EXEMPT BUGFIX
|
||||
(cherry picked from commit bd4d57ade07792f2a9160acbe480603b30e79917)
|
||||
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:2860bd01810adb2d0f00fba8f327cdae3f20ab9d
|
||||
Merged-In: I91dafa77d07970fdf2628b4d9e89ca1c4b74194c
|
||||
Change-Id: I91dafa77d07970fdf2628b4d9e89ca1c4b74194c
|
||||
---
|
||||
AndroidManifest.xml | 2 +-
|
||||
.../settings/applications/AppInfoBase.java | 20 +++++++++++++++++++
|
||||
2 files changed, 21 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
|
||||
index ed19890456f..e2d249add13 100644
|
||||
--- a/AndroidManifest.xml
|
||||
+++ b/AndroidManifest.xml
|
||||
@@ -2488,7 +2488,7 @@
|
||||
android:name="Settings$AppUsageAccessSettingsActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/usage_access_title">
|
||||
- <intent-filter>
|
||||
+ <intent-filter android:priority="1">
|
||||
<action android:name="android.settings.USAGE_ACCESS_SETTINGS"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:scheme="package"/>
|
||||
diff --git a/src/com/android/settings/applications/AppInfoBase.java b/src/com/android/settings/applications/AppInfoBase.java
|
||||
index 02237b886d9..14e54eb5b03 100644
|
||||
--- a/src/com/android/settings/applications/AppInfoBase.java
|
||||
+++ b/src/com/android/settings/applications/AppInfoBase.java
|
||||
@@ -49,6 +49,7 @@ import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.android.settings.SettingsActivity;
|
||||
import com.android.settings.SettingsPreferenceFragment;
|
||||
+import com.android.settings.activityembedding.ActivityEmbeddingUtils;
|
||||
import com.android.settings.applications.manageapplications.ManageApplications;
|
||||
import com.android.settings.core.SubSettingLauncher;
|
||||
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
|
||||
@@ -178,6 +179,25 @@ public abstract class AppInfoBase extends SettingsPreferenceFragment
|
||||
if (!(activity instanceof SettingsActivity)) {
|
||||
return false;
|
||||
}
|
||||
+ // Check the permission of the calling package if the device supports multi-pane.
|
||||
+ if (ActivityEmbeddingUtils.isEmbeddingActivityEnabled(activity)) {
|
||||
+ final String callingPackageName =
|
||||
+ ((SettingsActivity) activity).getInitialCallingPackage();
|
||||
+
|
||||
+ if (TextUtils.isEmpty(callingPackageName)) {
|
||||
+ Log.w(TAG, "Not able to get calling package name for permission check");
|
||||
+ return false;
|
||||
+ }
|
||||
+ if (mPm.checkPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
|
||||
+ callingPackageName)
|
||||
+ != PackageManager.PERMISSION_GRANTED) {
|
||||
+ Log.w(TAG, "Package " + callingPackageName + " does not have required permission "
|
||||
+ + Manifest.permission.INTERACT_ACROSS_USERS_FULL);
|
||||
+ return false;
|
||||
+ }
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
try {
|
||||
int callerUid = ActivityManager.getService().getLaunchedFromUid(
|
||||
activity.getActivityToken());
|
||||
--
|
||||
2.53.0
|
||||
|
||||
|
|
@ -0,0 +1,597 @@
|
|||
From c56a925678e3d0030a5a8b0417451a684374abfa Mon Sep 17 00:00:00 2001
|
||||
From: Shawn Lin <shawnlin@google.com>
|
||||
Date: Tue, 14 Oct 2025 08:08:58 +0000
|
||||
Subject: [PATCH] Fixed "Unlock your phone" unexpectedlly turned ON after OTA
|
||||
|
||||
If the new settings key is not set, we should use the values of the old
|
||||
keys as default value.
|
||||
|
||||
Bug: 444673089
|
||||
Test: atest FaceSettingsAppsPreferenceControllerTest
|
||||
FaceSettingsKeyguardUnlockPreferenceControllerTest
|
||||
FingerprintSettingsAppsPreferenceControllerTest
|
||||
FingerprintSettingsKeyguardUnlockPreferenceControllerTest
|
||||
Flag: EXEMPT BUGFIX
|
||||
(cherry picked from commit 05f0884146a093e6311d7a30232d6850a28368ef)
|
||||
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:feb931006ee2d56c146156d5bb1491117841ccf3
|
||||
Merged-In: I345defc78500c244e29e8595f5fbc705b95f4ba6
|
||||
Change-Id: I345defc78500c244e29e8595f5fbc705b95f4ba6
|
||||
---
|
||||
.../FaceSettingsAppsPreferenceController.java | 17 ++-
|
||||
...ngsKeyguardUnlockPreferenceController.java | 13 ++
|
||||
...printSettingsAppsPreferenceController.java | 17 ++-
|
||||
...ngsKeyguardUnlockPreferenceController.java | 13 ++
|
||||
...eSettingsAppsPreferenceControllerTest.java | 116 ++++++++++++++++++
|
||||
...eyguardUnlockPreferenceControllerTest.java | 90 ++++++++++++++
|
||||
...tSettingsAppsPreferenceControllerTest.java | 76 ++++++++++++
|
||||
...eyguardUnlockPreferenceControllerTest.java | 90 ++++++++++++++
|
||||
8 files changed, 428 insertions(+), 4 deletions(-)
|
||||
create mode 100644 tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsAppsPreferenceControllerTest.java
|
||||
create mode 100644 tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsKeyguardUnlockPreferenceControllerTest.java
|
||||
create mode 100644 tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsAppsPreferenceControllerTest.java
|
||||
create mode 100644 tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceControllerTest.java
|
||||
|
||||
diff --git a/src/com/android/settings/biometrics/face/FaceSettingsAppsPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsAppsPreferenceController.java
|
||||
index 24b0127d2689..c46b0f02cdc4 100644
|
||||
--- a/src/com/android/settings/biometrics/face/FaceSettingsAppsPreferenceController.java
|
||||
+++ b/src/com/android/settings/biometrics/face/FaceSettingsAppsPreferenceController.java
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.settings.biometrics.face;
|
||||
|
||||
+import static android.provider.Settings.Secure.BIOMETRIC_APP_ENABLED;
|
||||
import static android.provider.Settings.Secure.FACE_APP_ENABLED;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
@@ -33,6 +34,7 @@
|
||||
|
||||
public class FaceSettingsAppsPreferenceController extends
|
||||
FaceSettingsPreferenceController {
|
||||
+ private static final int NOT_SET = -1;
|
||||
private static final int ON = 1;
|
||||
private static final int OFF = 0;
|
||||
private static final int DEFAULT = ON;
|
||||
@@ -53,12 +55,23 @@ public FaceSettingsAppsPreferenceController(@NonNull Context context, @NonNull S
|
||||
}
|
||||
}
|
||||
}
|
||||
+
|
||||
+ // For OTA case: if FACE_APP_ENABLED is not set and BIOMETRIC_APP_ENABLED is set, set the
|
||||
+ // default value of the former to that of the latter.
|
||||
+ final int defValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
|
||||
+ FACE_APP_ENABLED, NOT_SET, getUserId());
|
||||
+ final int oldDefValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
|
||||
+ BIOMETRIC_APP_ENABLED, NOT_SET, getUserId());
|
||||
+ if (defValue == NOT_SET && oldDefValue != NOT_SET) {
|
||||
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
|
||||
+ FACE_APP_ENABLED, oldDefValue, getUserId());
|
||||
+ }
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChecked() {
|
||||
- return Settings.Secure.getIntForUser(mContext.getContentResolver(), FACE_APP_ENABLED,
|
||||
- DEFAULT, getUserId()) == ON;
|
||||
+ return Settings.Secure.getIntForUser(mContext.getContentResolver(),
|
||||
+ FACE_APP_ENABLED, DEFAULT, getUserId()) == ON;
|
||||
}
|
||||
|
||||
@Override
|
||||
diff --git a/src/com/android/settings/biometrics/face/FaceSettingsKeyguardUnlockPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsKeyguardUnlockPreferenceController.java
|
||||
index 64b036b034cb..d8ad4f827ac5 100644
|
||||
--- a/src/com/android/settings/biometrics/face/FaceSettingsKeyguardUnlockPreferenceController.java
|
||||
+++ b/src/com/android/settings/biometrics/face/FaceSettingsKeyguardUnlockPreferenceController.java
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.settings.biometrics.face;
|
||||
|
||||
+import static android.provider.Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED;
|
||||
import static android.provider.Settings.Secure.FACE_KEYGUARD_ENABLED;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
@@ -32,6 +33,7 @@
|
||||
|
||||
public class FaceSettingsKeyguardUnlockPreferenceController extends
|
||||
FaceSettingsPreferenceController {
|
||||
+ private static final int NOT_SET = -1;
|
||||
private static final int ON = 1;
|
||||
private static final int OFF = 0;
|
||||
private static final int DEFAULT = ON;
|
||||
@@ -44,6 +46,17 @@ public FaceSettingsKeyguardUnlockPreferenceController(
|
||||
super(context, key);
|
||||
mFaceManager = Utils.getFaceManagerOrNull(context);
|
||||
mUserManager = context.getSystemService(UserManager.class);
|
||||
+
|
||||
+ // For OTA case: if FACE_KEYGUARD_ENABLED is not set and BIOMETRIC_KEYGUARD_ENABLED is set,
|
||||
+ // set the default value of the former to that of the latter.
|
||||
+ final int defValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
|
||||
+ FACE_KEYGUARD_ENABLED, NOT_SET, getUserId());
|
||||
+ final int oldDefValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
|
||||
+ BIOMETRIC_KEYGUARD_ENABLED, NOT_SET, getUserId());
|
||||
+ if (defValue == NOT_SET && oldDefValue != NOT_SET) {
|
||||
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
|
||||
+ FACE_KEYGUARD_ENABLED, oldDefValue, getUserId());
|
||||
+ }
|
||||
}
|
||||
|
||||
@Override
|
||||
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsAppsPreferenceController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsAppsPreferenceController.java
|
||||
index 63fc3dcef230..574d406e3821 100644
|
||||
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsAppsPreferenceController.java
|
||||
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsAppsPreferenceController.java
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.settings.biometrics.fingerprint;
|
||||
|
||||
+import static android.provider.Settings.Secure.BIOMETRIC_APP_ENABLED;
|
||||
import static android.provider.Settings.Secure.FINGERPRINT_APP_ENABLED;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
@@ -31,6 +32,7 @@
|
||||
|
||||
public class FingerprintSettingsAppsPreferenceController
|
||||
extends FingerprintSettingsPreferenceController {
|
||||
+ private static final int NOT_SET = -1;
|
||||
private static final int ON = 1;
|
||||
private static final int OFF = 0;
|
||||
private static final int DEFAULT = ON;
|
||||
@@ -41,12 +43,23 @@ public FingerprintSettingsAppsPreferenceController(
|
||||
@NonNull Context context, @NonNull String key) {
|
||||
super(context, key);
|
||||
mFingerprintManager = Utils.getFingerprintManagerOrNull(context);
|
||||
+
|
||||
+ // For OTA case: if FINGERPRINT_APP_ENABLED is not set and BIOMETRIC_APP_ENABLED is set,
|
||||
+ // set the default value of the former to that of the latter.
|
||||
+ final int defValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
|
||||
+ FINGERPRINT_APP_ENABLED, NOT_SET, getUserId());
|
||||
+ final int oldDefValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
|
||||
+ BIOMETRIC_APP_ENABLED, NOT_SET, getUserId());
|
||||
+ if (defValue == NOT_SET && oldDefValue != NOT_SET) {
|
||||
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
|
||||
+ FINGERPRINT_APP_ENABLED, oldDefValue, getUserId());
|
||||
+ }
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChecked() {
|
||||
- return Settings.Secure.getIntForUser(mContext.getContentResolver(), FINGERPRINT_APP_ENABLED,
|
||||
- DEFAULT, getUserId()) == ON;
|
||||
+ return Settings.Secure.getIntForUser(mContext.getContentResolver(),
|
||||
+ FINGERPRINT_APP_ENABLED, DEFAULT, getUserId()) == ON;
|
||||
}
|
||||
|
||||
@Override
|
||||
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceController.java
|
||||
index c572f2d85843..88f0a125d0aa 100644
|
||||
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceController.java
|
||||
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceController.java
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.settings.biometrics.fingerprint;
|
||||
|
||||
+import static android.provider.Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED;
|
||||
import static android.provider.Settings.Secure.FINGERPRINT_KEYGUARD_ENABLED;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
@@ -33,6 +34,7 @@
|
||||
public class FingerprintSettingsKeyguardUnlockPreferenceController
|
||||
extends FingerprintSettingsPreferenceController {
|
||||
|
||||
+ private static final int NOT_SET = -1;
|
||||
private static final int ON = 1;
|
||||
private static final int OFF = 0;
|
||||
private static final int DEFAULT = ON;
|
||||
@@ -45,6 +47,17 @@ public FingerprintSettingsKeyguardUnlockPreferenceController(
|
||||
super(context, key);
|
||||
mFingerprintManager = Utils.getFingerprintManagerOrNull(context);
|
||||
mUserManager = context.getSystemService(UserManager.class);
|
||||
+
|
||||
+ // For OTA case: if FINGERPRINT_KEYGUARD_ENABLED is not set and BIOMETRIC_KEYGUARD_ENABLED
|
||||
+ // is set, set the default value of the former to that of the latter.
|
||||
+ final int defValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
|
||||
+ FINGERPRINT_KEYGUARD_ENABLED, NOT_SET, getUserId());
|
||||
+ final int oldDefValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
|
||||
+ BIOMETRIC_KEYGUARD_ENABLED, NOT_SET, getUserId());
|
||||
+ if (defValue == NOT_SET && oldDefValue != NOT_SET) {
|
||||
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
|
||||
+ FINGERPRINT_KEYGUARD_ENABLED, oldDefValue, getUserId());
|
||||
+ }
|
||||
}
|
||||
|
||||
@Override
|
||||
diff --git a/tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsAppsPreferenceControllerTest.java
|
||||
new file mode 100644
|
||||
index 000000000000..676031c4951e
|
||||
--- /dev/null
|
||||
+++ b/tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsAppsPreferenceControllerTest.java
|
||||
@@ -0,0 +1,116 @@
|
||||
+/*
|
||||
+ * Copyright (C) 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
|
||||
+ *
|
||||
+ * http://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.settings.biometrics.face;
|
||||
+
|
||||
+import static android.provider.Settings.Secure.BIOMETRIC_APP_ENABLED;
|
||||
+import static android.provider.Settings.Secure.FACE_APP_ENABLED;
|
||||
+
|
||||
+import static com.google.common.truth.Truth.assertThat;
|
||||
+
|
||||
+import static org.mockito.Mockito.when;
|
||||
+
|
||||
+import android.content.Context;
|
||||
+import android.hardware.biometrics.ComponentInfoInternal;
|
||||
+import android.hardware.biometrics.SensorProperties;
|
||||
+import android.hardware.face.FaceManager;
|
||||
+import android.hardware.face.FaceSensorProperties;
|
||||
+import android.hardware.face.FaceSensorPropertiesInternal;
|
||||
+import android.provider.Settings;
|
||||
+
|
||||
+import androidx.test.core.app.ApplicationProvider;
|
||||
+
|
||||
+import com.android.settings.testutils.FakeFeatureFactory;
|
||||
+import com.android.settings.testutils.shadow.ShadowSecureSettings;
|
||||
+import com.android.settings.testutils.shadow.ShadowUtils;
|
||||
+
|
||||
+import org.junit.After;
|
||||
+import org.junit.Before;
|
||||
+import org.junit.Rule;
|
||||
+import org.junit.Test;
|
||||
+import org.junit.runner.RunWith;
|
||||
+import org.mockito.Mock;
|
||||
+import org.mockito.Spy;
|
||||
+import org.mockito.junit.MockitoJUnit;
|
||||
+import org.mockito.junit.MockitoRule;
|
||||
+import org.robolectric.RobolectricTestRunner;
|
||||
+import org.robolectric.annotation.Config;
|
||||
+
|
||||
+import java.util.ArrayList;
|
||||
+
|
||||
+@RunWith(RobolectricTestRunner.class)
|
||||
+@Config(shadows = {ShadowSecureSettings.class})
|
||||
+public class FaceSettingsAppsPreferenceControllerTest {
|
||||
+ @Rule
|
||||
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
+ @Spy
|
||||
+ private Context mContext = ApplicationProvider.getApplicationContext();
|
||||
+ @Mock
|
||||
+ private FaceManager mFaceManager;
|
||||
+ private FaceSettingsAppsPreferenceController mController;
|
||||
+
|
||||
+ private FaceSensorPropertiesInternal mConvenienceSensorProperty =
|
||||
+ new FaceSensorPropertiesInternal(
|
||||
+ 0 /* sensorId */,
|
||||
+ SensorProperties.STRENGTH_CONVENIENCE,
|
||||
+ 1 /* maxEnrollmentsPerUser */,
|
||||
+ new ArrayList<ComponentInfoInternal>(),
|
||||
+ FaceSensorProperties.TYPE_UNKNOWN,
|
||||
+ true /* supportsFaceDetection */,
|
||||
+ true /* supportsSelfIllumination */,
|
||||
+ true /* resetLockoutRequiresChallenge */);
|
||||
+ private FakeFeatureFactory mFeatureFactory;
|
||||
+
|
||||
+ @Before
|
||||
+ public void setUp() {
|
||||
+ when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager);
|
||||
+ final ArrayList<FaceSensorPropertiesInternal> list = new ArrayList<>();
|
||||
+ list.add(mConvenienceSensorProperty);
|
||||
+ when(mFaceManager.getSensorPropertiesInternal()).thenReturn(list);
|
||||
+ mFeatureFactory = FakeFeatureFactory.setupForTest();
|
||||
+ }
|
||||
+
|
||||
+ @After
|
||||
+ public void tearDown() {
|
||||
+ ShadowUtils.reset();
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void isChecked_BiometricAppEnableOff_FaceAppEnabledNotSet_returnFalse() {
|
||||
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
|
||||
+ BIOMETRIC_APP_ENABLED, 0, mContext.getUserId());
|
||||
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
|
||||
+ FACE_APP_ENABLED, -1, mContext.getUserId());
|
||||
+
|
||||
+ mController = new FaceSettingsAppsPreferenceController(
|
||||
+ mContext, "biometric_settings_face_app");
|
||||
+
|
||||
+ assertThat(mController.isChecked()).isFalse();
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void isChecked_BiometricAppEnableOff_FaceAppEnabledOn_returnTrue() {
|
||||
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
|
||||
+ BIOMETRIC_APP_ENABLED, 0, mContext.getUserId());
|
||||
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
|
||||
+ FACE_APP_ENABLED, 1, mContext.getUserId());
|
||||
+
|
||||
+ mController = new FaceSettingsAppsPreferenceController(
|
||||
+ mContext, "biometric_settings_face_app");
|
||||
+
|
||||
+ assertThat(mController.isChecked()).isTrue();
|
||||
+ }
|
||||
+}
|
||||
diff --git a/tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsKeyguardUnlockPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsKeyguardUnlockPreferenceControllerTest.java
|
||||
new file mode 100644
|
||||
index 000000000000..837f31e3bd6f
|
||||
--- /dev/null
|
||||
+++ b/tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsKeyguardUnlockPreferenceControllerTest.java
|
||||
@@ -0,0 +1,90 @@
|
||||
+/*
|
||||
+ * Copyright (C) 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
|
||||
+ *
|
||||
+ * http://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.settings.biometrics.face;
|
||||
+
|
||||
+import static android.provider.Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED;
|
||||
+import static android.provider.Settings.Secure.FACE_KEYGUARD_ENABLED;
|
||||
+
|
||||
+import static com.google.common.truth.Truth.assertThat;
|
||||
+
|
||||
+import static org.mockito.Mockito.when;
|
||||
+
|
||||
+import android.content.Context;
|
||||
+import android.os.UserManager;
|
||||
+import android.provider.Settings;
|
||||
+
|
||||
+import androidx.test.core.app.ApplicationProvider;
|
||||
+
|
||||
+import com.android.settings.testutils.FakeFeatureFactory;
|
||||
+import com.android.settings.testutils.shadow.ShadowSecureSettings;
|
||||
+
|
||||
+import org.junit.Before;
|
||||
+import org.junit.Rule;
|
||||
+import org.junit.Test;
|
||||
+import org.junit.runner.RunWith;
|
||||
+import org.mockito.Mock;
|
||||
+import org.mockito.Spy;
|
||||
+import org.mockito.junit.MockitoJUnit;
|
||||
+import org.mockito.junit.MockitoRule;
|
||||
+import org.robolectric.RobolectricTestRunner;
|
||||
+import org.robolectric.annotation.Config;
|
||||
+
|
||||
+@RunWith(RobolectricTestRunner.class)
|
||||
+@Config(shadows = {ShadowSecureSettings.class})
|
||||
+public class FaceSettingsKeyguardUnlockPreferenceControllerTest {
|
||||
+ @Rule
|
||||
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
+ @Spy
|
||||
+ Context mContext = ApplicationProvider.getApplicationContext();
|
||||
+ private FaceSettingsKeyguardUnlockPreferenceController mController;
|
||||
+ private FakeFeatureFactory mFeatureFactory;
|
||||
+ @Mock
|
||||
+ private UserManager mUserManager;
|
||||
+
|
||||
+ @Before
|
||||
+ public void setUp() {
|
||||
+
|
||||
+ mFeatureFactory = FakeFeatureFactory.setupForTest();
|
||||
+ when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void isChecked_BiometricKeyguardEnabledOff_FaceKeyguardEnabledNotSet_returnFalse() {
|
||||
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
|
||||
+ BIOMETRIC_KEYGUARD_ENABLED, 0, mContext.getUserId());
|
||||
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
|
||||
+ FACE_KEYGUARD_ENABLED, -1, mContext.getUserId());
|
||||
+
|
||||
+ mController = new FaceSettingsKeyguardUnlockPreferenceController(
|
||||
+ mContext, "biometric_settings_face_keyguard");
|
||||
+
|
||||
+ assertThat(mController.isChecked()).isFalse();
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void isChecked_BiometricKeyguardEnabledOff_FaceKeyguardEnabledOn_returnTrue() {
|
||||
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
|
||||
+ BIOMETRIC_KEYGUARD_ENABLED, 0, mContext.getUserId());
|
||||
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
|
||||
+ FACE_KEYGUARD_ENABLED, 1, mContext.getUserId());
|
||||
+
|
||||
+ mController = new FaceSettingsKeyguardUnlockPreferenceController(
|
||||
+ mContext, "biometric_settings_face_keyguard");
|
||||
+
|
||||
+ assertThat(mController.isChecked()).isTrue();
|
||||
+ }
|
||||
+}
|
||||
diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsAppsPreferenceControllerTest.java
|
||||
new file mode 100644
|
||||
index 000000000000..841cec2f3de6
|
||||
--- /dev/null
|
||||
+++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsAppsPreferenceControllerTest.java
|
||||
@@ -0,0 +1,76 @@
|
||||
+/*
|
||||
+ * Copyright (C) 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
|
||||
+ *
|
||||
+ * http://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.settings.biometrics.fingerprint;
|
||||
+
|
||||
+import static android.provider.Settings.Secure.BIOMETRIC_APP_ENABLED;
|
||||
+import static android.provider.Settings.Secure.FINGERPRINT_APP_ENABLED;
|
||||
+
|
||||
+import static com.google.common.truth.Truth.assertThat;
|
||||
+
|
||||
+import android.content.Context;
|
||||
+import android.provider.Settings;
|
||||
+
|
||||
+import androidx.test.core.app.ApplicationProvider;
|
||||
+
|
||||
+import com.android.settings.testutils.FakeFeatureFactory;
|
||||
+import com.android.settings.testutils.shadow.ShadowSecureSettings;
|
||||
+
|
||||
+import org.junit.Before;
|
||||
+import org.junit.Test;
|
||||
+import org.junit.runner.RunWith;
|
||||
+import org.robolectric.RobolectricTestRunner;
|
||||
+import org.robolectric.annotation.Config;
|
||||
+
|
||||
+@RunWith(RobolectricTestRunner.class)
|
||||
+@Config(shadows = {ShadowSecureSettings.class})
|
||||
+public class FingerprintSettingsAppsPreferenceControllerTest {
|
||||
+ private Context mContext;
|
||||
+ private FingerprintSettingsAppsPreferenceController mController;
|
||||
+ private FakeFeatureFactory mFeatureFactory;
|
||||
+
|
||||
+ @Before
|
||||
+ public void setUp() {
|
||||
+ mContext = ApplicationProvider.getApplicationContext();
|
||||
+ mFeatureFactory = FakeFeatureFactory.setupForTest();
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void isChecked_BiometricAppEnableOff_FingerprintAppEnabledNotSet_returnFalse() {
|
||||
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
|
||||
+ BIOMETRIC_APP_ENABLED, 0, mContext.getUserId());
|
||||
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
|
||||
+ FINGERPRINT_APP_ENABLED, -1, mContext.getUserId());
|
||||
+
|
||||
+ mController = new FingerprintSettingsAppsPreferenceController(
|
||||
+ mContext, "biometric_settings_fingerprint_app");
|
||||
+
|
||||
+ assertThat(mController.isChecked()).isFalse();
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void isChecked_BiometricAppEnableOff_FingerprintAppEnabledOn_returnTrue() {
|
||||
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
|
||||
+ BIOMETRIC_APP_ENABLED, 0, mContext.getUserId());
|
||||
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
|
||||
+ FINGERPRINT_APP_ENABLED, 1, mContext.getUserId());
|
||||
+
|
||||
+ mController = new FingerprintSettingsAppsPreferenceController(
|
||||
+ mContext, "biometric_settings_fingerprint_app");
|
||||
+
|
||||
+ assertThat(mController.isChecked()).isTrue();
|
||||
+ }
|
||||
+}
|
||||
diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceControllerTest.java
|
||||
new file mode 100644
|
||||
index 000000000000..119fda8bbf28
|
||||
--- /dev/null
|
||||
+++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceControllerTest.java
|
||||
@@ -0,0 +1,90 @@
|
||||
+/*
|
||||
+ * Copyright (C) 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
|
||||
+ *
|
||||
+ * http://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.settings.biometrics.fingerprint;
|
||||
+
|
||||
+import static android.provider.Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED;
|
||||
+import static android.provider.Settings.Secure.FINGERPRINT_KEYGUARD_ENABLED;
|
||||
+
|
||||
+import static com.google.common.truth.Truth.assertThat;
|
||||
+
|
||||
+import static org.mockito.Mockito.when;
|
||||
+
|
||||
+import android.content.Context;
|
||||
+import android.os.UserManager;
|
||||
+import android.provider.Settings;
|
||||
+
|
||||
+import androidx.test.core.app.ApplicationProvider;
|
||||
+
|
||||
+import com.android.settings.testutils.FakeFeatureFactory;
|
||||
+import com.android.settings.testutils.shadow.ShadowSecureSettings;
|
||||
+
|
||||
+import org.junit.Before;
|
||||
+import org.junit.Rule;
|
||||
+import org.junit.Test;
|
||||
+import org.junit.runner.RunWith;
|
||||
+import org.mockito.Mock;
|
||||
+import org.mockito.Spy;
|
||||
+import org.mockito.junit.MockitoJUnit;
|
||||
+import org.mockito.junit.MockitoRule;
|
||||
+import org.robolectric.RobolectricTestRunner;
|
||||
+import org.robolectric.annotation.Config;
|
||||
+
|
||||
+@RunWith(RobolectricTestRunner.class)
|
||||
+@Config(shadows = {ShadowSecureSettings.class})
|
||||
+public class FingerprintSettingsKeyguardUnlockPreferenceControllerTest {
|
||||
+ @Rule
|
||||
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
+ @Spy
|
||||
+ Context mContext = ApplicationProvider.getApplicationContext();
|
||||
+ private FingerprintSettingsKeyguardUnlockPreferenceController mController;
|
||||
+ private FakeFeatureFactory mFeatureFactory;
|
||||
+ @Mock
|
||||
+ private UserManager mUserManager;
|
||||
+
|
||||
+ @Before
|
||||
+ public void setUp() {
|
||||
+ mFeatureFactory = FakeFeatureFactory.setupForTest();
|
||||
+ when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void
|
||||
+ isChecked_BiometricKeyguardEnabledOff_FingerprintKeyguardEnabledNotSet_returnFalse() {
|
||||
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
|
||||
+ BIOMETRIC_KEYGUARD_ENABLED, 0, mContext.getUserId());
|
||||
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
|
||||
+ FINGERPRINT_KEYGUARD_ENABLED, -1, mContext.getUserId());
|
||||
+
|
||||
+ mController = new FingerprintSettingsKeyguardUnlockPreferenceController(
|
||||
+ mContext, "biometric_settings_fingerprint_keyguard");
|
||||
+
|
||||
+ assertThat(mController.isChecked()).isFalse();
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void isChecked_BiometricKeyguardEnabledOff_FingerprintKeyguardEnabledOn_returnTrue() {
|
||||
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
|
||||
+ BIOMETRIC_KEYGUARD_ENABLED, 0, mContext.getUserId());
|
||||
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
|
||||
+ FINGERPRINT_KEYGUARD_ENABLED, 1, mContext.getUserId());
|
||||
+
|
||||
+ mController = new FingerprintSettingsKeyguardUnlockPreferenceController(
|
||||
+ mContext, "biometric_settings_fingerprint_keyguard");
|
||||
+
|
||||
+ assertThat(mController.isChecked()).isTrue();
|
||||
+ }
|
||||
+}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
From f0271f36388ec9630d89ff8b3ee4cb22e2ca3eaf Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Pierre-Cl=C3=A9ment=20Tosi?= <ptosi@google.com>
|
||||
Date: Tue, 28 Oct 2025 09:46:06 +0000
|
||||
Subject: [PATCH] vmbase,pvmfw: aarch64: Clean dcache to PoC not PoU
|
||||
|
||||
Some SoCs (with a unified cache before the PoC) might not flush the data
|
||||
to main memory when performing CMOs to PoU so perform them to PoC.
|
||||
|
||||
Test: b/434562039#comment35
|
||||
Bug: 434562039
|
||||
Bug: 455777515
|
||||
Flag: EXEMPT CVE_FIX
|
||||
(cherry picked from commit a6f64040dba5a3aae3f67c68e2a754a5c946610d)
|
||||
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:ab5f74693d42e114af5ac238cca0bcb4b17698ac
|
||||
Merged-In: Id2398e9bcf8dcf7f7a10d254d8eb411d39e109db
|
||||
Change-Id: Id2398e9bcf8dcf7f7a10d254d8eb411d39e109db
|
||||
---
|
||||
guest/pvmfw/src/arch/aarch64/payload.rs | 6 +++---
|
||||
libs/libvmbase/src/arch.rs | 2 +-
|
||||
2 files changed, 4 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/guest/pvmfw/src/arch/aarch64/payload.rs b/guest/pvmfw/src/arch/aarch64/payload.rs
|
||||
index 77e9a31..8c2242e 100644
|
||||
--- a/guest/pvmfw/src/arch/aarch64/payload.rs
|
||||
+++ b/guest/pvmfw/src/arch/aarch64/payload.rs
|
||||
@@ -91,7 +91,7 @@ pub fn jump_to_payload(entrypoint: usize, slices: &MemorySlices) -> ! {
|
||||
"b.lo 0b",
|
||||
|
||||
// Flush d-cache over .data & .bss (including skipped region).
|
||||
- "0: dc cvau, {cache_line}",
|
||||
+ "0: dc cvac, {cache_line}",
|
||||
"add {cache_line}, {cache_line}, {dcache_line_size}",
|
||||
"cmp {cache_line}, {scratch_end}",
|
||||
"b.lo 0b",
|
||||
@@ -103,7 +103,7 @@ pub fn jump_to_payload(entrypoint: usize, slices: &MemorySlices) -> ! {
|
||||
"b.lo 0b",
|
||||
|
||||
// Flush d-cache over stack region.
|
||||
- "0: dc cvau, {cache_line}",
|
||||
+ "0: dc cvac, {cache_line}",
|
||||
"add {cache_line}, {cache_line}, {dcache_line_size}",
|
||||
"cmp {cache_line}, {stack_end}",
|
||||
"b.lo 0b",
|
||||
@@ -115,7 +115,7 @@ pub fn jump_to_payload(entrypoint: usize, slices: &MemorySlices) -> ! {
|
||||
"b.lo 0b",
|
||||
|
||||
// Flush d-cache over EH stack region.
|
||||
- "0: dc cvau, {cache_line}",
|
||||
+ "0: dc cvac, {cache_line}",
|
||||
"add {cache_line}, {cache_line}, {dcache_line_size}",
|
||||
"cmp {cache_line}, {eh_stack_end}",
|
||||
"b.lo 0b",
|
||||
diff --git a/libs/libvmbase/src/arch.rs b/libs/libvmbase/src/arch.rs
|
||||
index 29d3a32..e25745b 100644
|
||||
--- a/libs/libvmbase/src/arch.rs
|
||||
+++ b/libs/libvmbase/src/arch.rs
|
||||
@@ -47,7 +47,7 @@ pub(crate) fn flush_region(start: usize, size: usize) {
|
||||
let end = start + size;
|
||||
let start = crate::util::unchecked_align_down(start, line_size);
|
||||
for line in (start..end).step_by(line_size) {
|
||||
- crate::dc!("cvau", line);
|
||||
+ crate::dc!("cvac", line);
|
||||
}
|
||||
} else {
|
||||
compile_error!("Unsupported target_arch")
|
||||
--
|
||||
2.53.0
|
||||
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
From 69a25763cdb46c8f23fe9eb976132acbe2af82d6 Mon Sep 17 00:00:00 2001
|
||||
From: Himanshu Arora <hmarora@google.com>
|
||||
Date: Fri, 24 Oct 2025 16:47:37 +0000
|
||||
Subject: [PATCH] Fix ACCESS_MEDIA_LOCATION bypass via SAF picker
|
||||
|
||||
When an app uses a picker to access media files, the picker should respect the app's permissions. Currently, it is possible to bypass the ACCESS_MEDIA_LOCATION permission and get unredacted location data.
|
||||
|
||||
This change fixes this by having MediaProvider check the permissions of the app on whose behalf the media is being opened. When a `mediaCapabilitiesUid` is passed, MediaProvider now checks if that UID has the necessary permissions.
|
||||
|
||||
Bug: 326211886
|
||||
Test: manual
|
||||
Flag: EXEMPT BUGFIX
|
||||
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:e5e47f93838e1e9a3a3a520f7c89229fc041a8c3
|
||||
Merged-In: I7ad51535d5ae8a3803162f688c4794edbbcfb167
|
||||
Change-Id: I7ad51535d5ae8a3803162f688c4794edbbcfb167
|
||||
---
|
||||
.../providers/media/MediaProvider.java | 20 +++++++++++++++++--
|
||||
1 file changed, 18 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
|
||||
index 0d75628c5..4a09b7500 100644
|
||||
--- a/src/com/android/providers/media/MediaProvider.java
|
||||
+++ b/src/com/android/providers/media/MediaProvider.java
|
||||
@@ -10253,7 +10253,7 @@ public class MediaProvider extends ContentProvider {
|
||||
|
||||
// Figure out if we need to redact contents
|
||||
final boolean redactionNeeded = isRedactionNeededForOpenViaContentResolver(redactedUri,
|
||||
- ownerPackageName, file);
|
||||
+ ownerPackageName, file, opts);
|
||||
long[] redactionRanges;
|
||||
try {
|
||||
redactionRanges = redactionNeeded ? RedactionUtils.getRedactionRanges(file)
|
||||
@@ -10372,12 +10372,28 @@ public class MediaProvider extends ContentProvider {
|
||||
}
|
||||
|
||||
private boolean isRedactionNeededForOpenViaContentResolver(Uri redactedUri,
|
||||
- String ownerPackageName, File file) {
|
||||
+ String ownerPackageName, File file, Bundle opts) {
|
||||
// Redacted Uris should always redact information
|
||||
if (redactedUri != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
+ // If the caller provides a media capabilities UID, we check if that UID has the
|
||||
+ // PERMISSION_IS_REDACTION_NEEDED permission. If so, we redact the data. This is
|
||||
+ // used for cases where an app is acting on behalf of another app, and we need
|
||||
+ // to respect the capabilities of the app for which the action is being performed.
|
||||
+ if (opts != null) {
|
||||
+ final int mediaCapabilitiesUid = opts.getInt(MediaStore.EXTRA_MEDIA_CAPABILITIES_UID);
|
||||
+ if (mediaCapabilitiesUid > 0) {
|
||||
+ final LocalCallingIdentity identity = LocalCallingIdentity.fromExternal(
|
||||
+ getContext(),
|
||||
+ mUserCache, mediaCapabilitiesUid, null, null);
|
||||
+ if (identity.hasPermission(PERMISSION_IS_REDACTION_NEEDED)) {
|
||||
+ return true;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
final boolean callerIsOwner = Objects.equals(getCallingPackageOrSelf(), ownerPackageName);
|
||||
if (callerIsOwner) {
|
||||
return false;
|
||||
--
|
||||
2.53.0
|
||||
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
From 119013a3d7e8f1eab671bce4c6a85748752081ed Mon Sep 17 00:00:00 2001
|
||||
From: Garvita Jain <garvitajain@google.com>
|
||||
Date: Mon, 1 Dec 2025 11:02:37 +0000
|
||||
Subject: [PATCH] Throw exception on MediaStore createRequest for non-existent
|
||||
uri
|
||||
|
||||
Throw IllegalArgument exception if a caller requests
|
||||
CREATE_WRITE/DELETE/.._REQUEST for a uri that does not exist in the
|
||||
Files table at the time of request.
|
||||
|
||||
BUG: 418773439
|
||||
Test: atest MediaProviderTests
|
||||
Flag: EXEMPT bugfix
|
||||
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:268fc3fb0a438abbc710687a9590cb80b3c0e8bc
|
||||
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:973f11ad05506303f6bbb3fd6275c3a2b824b2e8
|
||||
Merged-In: I00a3b15d8dbcd19afaec3980f7d7a07122bef640
|
||||
Change-Id: I00a3b15d8dbcd19afaec3980f7d7a07122bef640
|
||||
---
|
||||
.../android/providers/media/MediaProvider.java | 16 ++++++++++++++++
|
||||
.../providers/media/MediaProviderTest.java | 18 ++++++++++++++++--
|
||||
2 files changed, 32 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
|
||||
index 4a09b7500..b9fc95667 100644
|
||||
--- a/src/com/android/providers/media/MediaProvider.java
|
||||
+++ b/src/com/android/providers/media/MediaProvider.java
|
||||
@@ -8383,6 +8383,22 @@ public class MediaProvider extends ContentProvider {
|
||||
}
|
||||
}
|
||||
|
||||
+ // Do not allow to create request if the list contains a uri which does not exist
|
||||
+ final LocalCallingIdentity token = clearLocalCallingIdentity();
|
||||
+ try {
|
||||
+ for (Uri uri : uris) {
|
||||
+ try (Cursor c = queryForSingleItem(uri, new String[]{FileColumns._ID}, null, null,
|
||||
+ null)) {
|
||||
+ // queryForSingleItem method throws FileNotFoundException if no items were
|
||||
+ // found, or multiple items were found, or there was trouble reading the data.
|
||||
+ } catch (FileNotFoundException e) {
|
||||
+ throw new IllegalArgumentException("Invalid Uri: " + uri, e);
|
||||
+ }
|
||||
+ }
|
||||
+ } finally {
|
||||
+ restoreLocalCallingIdentity(token);
|
||||
+ }
|
||||
+
|
||||
final Context context = getContext();
|
||||
final Intent intent = new Intent(method, null, context, PermissionActivity.class);
|
||||
extras.putInt(EXTRA_CALLING_PACKAGE_UID, getCallingUidOrSelf());
|
||||
diff --git a/tests/src/com/android/providers/media/MediaProviderTest.java b/tests/src/com/android/providers/media/MediaProviderTest.java
|
||||
index 21cb56e48..660276ce3 100644
|
||||
--- a/tests/src/com/android/providers/media/MediaProviderTest.java
|
||||
+++ b/tests/src/com/android/providers/media/MediaProviderTest.java
|
||||
@@ -39,6 +39,7 @@ import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
+import static org.junit.Assume.assumeTrue;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.ContentInterface;
|
||||
@@ -334,11 +335,24 @@ public class MediaProviderTest {
|
||||
*/
|
||||
@Test
|
||||
public void testCreateRequest() throws Exception {
|
||||
- final Collection<Uri> uris = Arrays.asList(
|
||||
- MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY, 42));
|
||||
+ final ContentValues values = new ContentValues();
|
||||
+ values.put(MediaColumns.DISPLAY_NAME, "test.mp3");
|
||||
+ values.put(MediaColumns.MIME_TYPE, "audio/mpeg");
|
||||
+ final Uri uri = sIsolatedResolver.insert(
|
||||
+ MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY), values);
|
||||
+ assumeTrue(uri != null);
|
||||
+ final Collection<Uri> uris = List.of(uri);
|
||||
assertNotNull(MediaStore.createWriteRequest(sIsolatedResolver, uris));
|
||||
}
|
||||
|
||||
+ @Test
|
||||
+ public void testCreateRequest_invalidUri_throwsException() throws Exception {
|
||||
+ final Collection<Uri> uris = List.of(
|
||||
+ MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY, 42));
|
||||
+ assertThrows(IllegalArgumentException.class,
|
||||
+ () -> MediaStore.createWriteRequest(sIsolatedResolver, uris));
|
||||
+ }
|
||||
+
|
||||
@Test
|
||||
public void testRequestThumbnail_noAccess_throwsSecurityException() throws Exception {
|
||||
final File dir = Environment
|
||||
--
|
||||
2.53.0
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue