diff --git a/aosp_diff/caas/art/0001-Throw-an-exception-in-JNI-NewObject-for-abstract-cla.patch b/aosp_diff/caas/art/0001-Throw-an-exception-in-JNI-NewObject-for-abstract-cla.patch new file mode 100644 index 0000000..67feb1d --- /dev/null +++ b/aosp_diff/caas/art/0001-Throw-an-exception-in-JNI-NewObject-for-abstract-cla.patch @@ -0,0 +1,199 @@ +From 763fa52693be2190fde38aa6db1cfc0f19a5d890 Mon Sep 17 00:00:00 2001 +From: Nicolas Geoffray +Date: Wed, 11 Jun 2025 14:48:25 +0100 +Subject: [PATCH] Throw an exception in JNI::NewObject for abstract classes. + +Test: 863-serialization +Bug: 421834866 +Flag: EXEMPT bugfix +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:a4826745b63bdab1db7536680e1c8e947a56f7be) +Merged-In: I4ccf22f85b4ae0325e9f8e29503149bbda533e86 +Change-Id: I4ccf22f85b4ae0325e9f8e29503149bbda533e86 +--- + runtime/jni/check_jni.cc | 10 ++-- + runtime/jni/jni_internal.cc | 21 ++++++++ + test/863-serialization/expected-stderr.txt | 0 + test/863-serialization/expected-stdout.txt | 0 + test/863-serialization/info.txt | 2 + + test/863-serialization/src/Main.java | 62 ++++++++++++++++++++++ + 6 files changed, 90 insertions(+), 5 deletions(-) + create mode 100644 test/863-serialization/expected-stderr.txt + create mode 100644 test/863-serialization/expected-stdout.txt + create mode 100644 test/863-serialization/info.txt + create mode 100644 test/863-serialization/src/Main.java + +diff --git a/runtime/jni/check_jni.cc b/runtime/jni/check_jni.cc +index a05a3e97f2..24e3256d81 100644 +--- a/runtime/jni/check_jni.cc ++++ b/runtime/jni/check_jni.cc +@@ -747,10 +747,10 @@ class ScopedCheck { + return true; + } + +- bool CheckInstantiableNonArray(ScopedObjectAccess& soa, jclass jc) ++ bool CheckNonArray(ScopedObjectAccess& soa, jclass jc) + REQUIRES_SHARED(Locks::mutator_lock_) { + ObjPtr c = soa.Decode(jc); +- if (!c->IsInstantiableNonArray()) { ++ if (c->IsArrayClass()) { + AbortF("can't make objects of type %s: %p", c->PrettyDescriptor().c_str(), c.Ptr()); + return false; + } +@@ -2195,7 +2195,7 @@ class CheckJNI { + ScopedObjectAccess soa(env); + ScopedCheck sc(kFlag_Default, __FUNCTION__); + JniValueType args[2] = {{.E = env}, {.c = c}}; +- if (sc.Check(soa, true, "Ec", args) && sc.CheckInstantiableNonArray(soa, c)) { ++ if (sc.Check(soa, true, "Ec", args) && sc.CheckNonArray(soa, c)) { + JniValueType result; + result.L = baseEnv(env)->AllocObject(env, c); + if (sc.Check(soa, false, "L", &result)) { +@@ -2211,7 +2211,7 @@ class CheckJNI { + ScopedCheck sc(kFlag_Default, __FUNCTION__); + VarArgs rest(mid, vargs); + JniValueType args[4] = {{.E = env}, {.c = c}, {.m = mid}, {.va = &rest}}; +- if (sc.Check(soa, true, "Ecm.", args) && sc.CheckInstantiableNonArray(soa, c) && ++ if (sc.Check(soa, true, "Ecm.", args) && sc.CheckNonArray(soa, c) && + sc.CheckConstructor(mid)) { + JniValueType result; + result.L = baseEnv(env)->NewObjectV(env, c, mid, vargs); +@@ -2237,7 +2237,7 @@ class CheckJNI { + ScopedCheck sc(kFlag_Default, __FUNCTION__); + VarArgs rest(mid, vargs); + JniValueType args[4] = {{.E = env}, {.c = c}, {.m = mid}, {.va = &rest}}; +- if (sc.Check(soa, true, "Ecm.", args) && sc.CheckInstantiableNonArray(soa, c) && ++ if (sc.Check(soa, true, "Ecm.", args) && sc.CheckNonArray(soa, c) && + sc.CheckConstructor(mid)) { + JniValueType result; + result.L = baseEnv(env)->NewObjectA(env, c, mid, vargs); +diff --git a/runtime/jni/jni_internal.cc b/runtime/jni/jni_internal.cc +index f1ea88da4e..1477b42a7d 100644 +--- a/runtime/jni/jni_internal.cc ++++ b/runtime/jni/jni_internal.cc +@@ -921,6 +921,13 @@ class JNI { + if (c == nullptr) { + return nullptr; + } ++ if (UNLIKELY(!c->IsInstantiable())) { ++ soa.Self()->ThrowNewExceptionF( ++ "Ljava/lang/InstantiationException;", "Can't instantiate %s %s", ++ c->IsInterface() ? "interface" : "abstract class", ++ c->PrettyDescriptor().c_str()); ++ return nullptr; ++ } + if (c->IsStringClass()) { + gc::AllocatorType allocator_type = Runtime::Current()->GetHeap()->GetCurrentAllocator(); + return soa.AddLocalReference( +@@ -948,6 +955,13 @@ class JNI { + if (c == nullptr) { + return nullptr; + } ++ if (UNLIKELY(!c->IsInstantiable())) { ++ soa.Self()->ThrowNewExceptionF( ++ "Ljava/lang/InstantiationException;", "Can't instantiate %s %s", ++ c->IsInterface() ? "interface" : "abstract class", ++ c->PrettyDescriptor().c_str()); ++ return nullptr; ++ } + if (c->IsStringClass()) { + // Replace calls to String. with equivalent StringFactory call. + jmethodID sf_mid = jni::EncodeArtMethod( +@@ -974,6 +988,13 @@ class JNI { + if (c == nullptr) { + return nullptr; + } ++ if (UNLIKELY(!c->IsInstantiable())) { ++ soa.Self()->ThrowNewExceptionF( ++ "Ljava/lang/InstantiationException;", "Can't instantiate %s %s", ++ c->IsInterface() ? "interface" : "abstract class", ++ c->PrettyDescriptor().c_str()); ++ return nullptr; ++ } + if (c->IsStringClass()) { + // Replace calls to String. with equivalent StringFactory call. + jmethodID sf_mid = jni::EncodeArtMethod( +diff --git a/test/863-serialization/expected-stderr.txt b/test/863-serialization/expected-stderr.txt +new file mode 100644 +index 0000000000..e69de29bb2 +diff --git a/test/863-serialization/expected-stdout.txt b/test/863-serialization/expected-stdout.txt +new file mode 100644 +index 0000000000..e69de29bb2 +diff --git a/test/863-serialization/info.txt b/test/863-serialization/info.txt +new file mode 100644 +index 0000000000..a9693a9f64 +--- /dev/null ++++ b/test/863-serialization/info.txt +@@ -0,0 +1,2 @@ ++Regression test for JNI::NewObject where we forgot to check if a class is ++instantiable. +diff --git a/test/863-serialization/src/Main.java b/test/863-serialization/src/Main.java +new file mode 100644 +index 0000000000..72cc01f896 +--- /dev/null ++++ b/test/863-serialization/src/Main.java +@@ -0,0 +1,62 @@ ++/* ++ * 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. ++ */ ++ ++import java.io.ByteArrayInputStream; ++import java.io.InvalidClassException; ++import java.io.ObjectInputStream; ++ ++public class Main { ++ ++ public static void main(String[] args) throws Exception { ++ deserializeHexToConcurrentHashMap(); ++ } ++ ++ public static byte[] hexStringToByteArray(String hexString) { ++ if (hexString == null || hexString.isEmpty()) { ++ return new byte[0]; ++ } ++ if (hexString.length() % 2 != 0) { ++ throw new IllegalArgumentException("Hex string must have an even number of characters."); ++ } ++ int len = hexString.length(); ++ byte[] data = new byte[len / 2]; ++ for (int i = 0; i < len; i += 2) { ++ int highNibble = Character.digit(hexString.charAt(i), 16); ++ int lowNibble = Character.digit(hexString.charAt(i + 1), 16); ++ if (highNibble == -1 || lowNibble == -1) { ++ throw new IllegalArgumentException( ++ "Invalid hex character in string: " + hexString.charAt(i) + hexString.charAt(i + 1)); ++ } ++ data[i / 2] = (byte) ((highNibble << 4) + lowNibble); ++ } ++ return data; ++ } ++ ++ public static void deserializeHexToConcurrentHashMap() throws Exception { ++ byte[] bytes = hexStringToByteArray("ACED0005737200266A6176612E7574696C2E636F6E63757272656E742E436F6E63757272656E74486173684D61706499DE129D87293D0300007870737200146A6176612E746578742E44617465466F726D6174642CA1E4C22615FC0200007870737200146A6176612E746578742E44617465466F726D6174642CA1E4C22615FC020000787070707878000000"); ++ ByteArrayInputStream bis = new ByteArrayInputStream(bytes); ++ ObjectInputStream ois = new ObjectInputStream(bis); ++ try { ++ Object deserializedObject = ois.readObject(); ++ throw new Error("Expected InvalidClassException"); ++ } catch (InvalidClassException e) { ++ // expected ++ if (!(e.getCause() instanceof InstantiationException)) { ++ throw new Error("Expected InstantiationException"); ++ } ++ } ++ } ++} +-- +2.34.1 + diff --git a/aosp_diff/caas/build/release/0002-Update-RELEASE_PLATFORM_SECURITY_PATCH-string.patch b/aosp_diff/caas/build/release/0001-Update-RELEASE_PLATFORM_SECURITY_PATCH-string_to_01_08_2025.patch similarity index 100% rename from aosp_diff/caas/build/release/0002-Update-RELEASE_PLATFORM_SECURITY_PATCH-string.patch rename to aosp_diff/caas/build/release/0001-Update-RELEASE_PLATFORM_SECURITY_PATCH-string_to_01_08_2025.patch diff --git a/aosp_diff/caas/build/release/0002-Update-RELEASE_PLATFORM_SECURITY_PATCH-string_to_01_09_2025.patch b/aosp_diff/caas/build/release/0002-Update-RELEASE_PLATFORM_SECURITY_PATCH-string_to_01_09_2025.patch new file mode 100644 index 0000000..160e55f --- /dev/null +++ b/aosp_diff/caas/build/release/0002-Update-RELEASE_PLATFORM_SECURITY_PATCH-string_to_01_09_2025.patch @@ -0,0 +1,23 @@ +From 7432c0a89478904b403355528f43008cb85054fd Mon Sep 17 00:00:00 2001 +From: lgundapx +Date: Sun, 12 Oct 2025 11:50:27 +0000 +Subject: [PATCH] Update RELEASE_PLATFORM_SECURITY_PATCH string + +Signed-off-by: lgundapx +--- + flag_values/bp1a/RELEASE_PLATFORM_SECURITY_PATCH.textproto | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/flag_values/bp1a/RELEASE_PLATFORM_SECURITY_PATCH.textproto b/flag_values/bp1a/RELEASE_PLATFORM_SECURITY_PATCH.textproto +index 063e19fd..b04a308e 100644 +--- a/flag_values/bp1a/RELEASE_PLATFORM_SECURITY_PATCH.textproto ++++ b/flag_values/bp1a/RELEASE_PLATFORM_SECURITY_PATCH.textproto +@@ -1,4 +1,4 @@ + name: "RELEASE_PLATFORM_SECURITY_PATCH" + value: { +- string_value: "2025-08-01" ++ string_value: "2025-09-01" + } +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/base/0018-RemoteViews-Always-load-new-ApplicationInfo-from-Pac.patch b/aosp_diff/caas/frameworks/base/0001-RemoteViews-Always-load-new-ApplicationInfo-from-Pac.patch similarity index 100% rename from aosp_diff/caas/frameworks/base/0018-RemoteViews-Always-load-new-ApplicationInfo-from-Pac.patch rename to aosp_diff/caas/frameworks/base/0001-RemoteViews-Always-load-new-ApplicationInfo-from-Pac.patch diff --git a/aosp_diff/caas/frameworks/base/0019-Handle-exceptions-from-querying-appinfo-in-RemoteVie.patch b/aosp_diff/caas/frameworks/base/0002-Handle-exceptions-from-querying-appinfo-in-RemoteVie.patch similarity index 100% rename from aosp_diff/caas/frameworks/base/0019-Handle-exceptions-from-querying-appinfo-in-RemoteVie.patch rename to aosp_diff/caas/frameworks/base/0002-Handle-exceptions-from-querying-appinfo-in-RemoteVie.patch diff --git a/aosp_diff/caas/frameworks/base/0020-Defer-remove-splash-screen-while-device-is-locked.patch b/aosp_diff/caas/frameworks/base/0003-Defer-remove-splash-screen-while-device-is-locked.patch similarity index 100% rename from aosp_diff/caas/frameworks/base/0020-Defer-remove-splash-screen-while-device-is-locked.patch rename to aosp_diff/caas/frameworks/base/0003-Defer-remove-splash-screen-while-device-is-locked.patch diff --git a/aosp_diff/caas/frameworks/base/0004-Normalize-home-intent.patch b/aosp_diff/caas/frameworks/base/0004-Normalize-home-intent.patch new file mode 100644 index 0000000..7932d6b --- /dev/null +++ b/aosp_diff/caas/frameworks/base/0004-Normalize-home-intent.patch @@ -0,0 +1,229 @@ +From 1541cfaaa066bc27c4427fc57f3803ffa7cdfd47 Mon Sep 17 00:00:00 2001 +From: Louis Chang +Date: Thu, 28 Nov 2024 09:13:29 +0000 +Subject: [PATCH 01/25] Normalize home intent + +In order to prevent home activities being started as a standard +type activity due to incorrect home intent format. + +Bug: 384656159 +Bug: 378505461 +Test: the same app on the bug +Test: ActivityStartInterceptorTest +Flag: com.android.window.flags.normalize_home_intent +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:67ace66f8c9507b701f55bd23a87638bbe1eec76) +Merged-In: I300f705798909014329f92118c266dbc04173e05 + +Change-Id: I300f705798909014329f92118c266dbc04173e05 +--- + .../server/wm/ActivityStartInterceptor.java | 96 +++++++++++++++++-- + .../android/server/wm/ActivityStarter.java | 3 +- + .../wm/ActivityStartInterceptorTest.java | 13 +++ + 3 files changed, 101 insertions(+), 11 deletions(-) + +diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java +index 1a9d21187ddb..afe6e91bb949 100644 +--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java ++++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java +@@ -25,6 +25,9 @@ import static android.app.PendingIntent.FLAG_ONE_SHOT; + import static android.app.admin.DevicePolicyManager.EXTRA_RESTRICTION; + import static android.app.admin.DevicePolicyManager.POLICY_SUSPEND_PACKAGES; + import static android.content.Context.KEYGUARD_SERVICE; ++import static android.content.Intent.ACTION_MAIN; ++import static android.content.Intent.CATEGORY_HOME; ++import static android.content.Intent.CATEGORY_SECONDARY_HOME; + import static android.content.Intent.EXTRA_INTENT; + import static android.content.Intent.EXTRA_PACKAGE_NAME; + import static android.content.Intent.EXTRA_TASK_ID; +@@ -40,6 +43,7 @@ import android.app.ActivityOptions; + import android.app.KeyguardManager; + import android.app.TaskInfo; + import android.app.admin.DevicePolicyManagerInternal; ++import android.content.ComponentName; + import android.content.Context; + import android.content.IIntentSender; + import android.content.Intent; +@@ -119,6 +123,11 @@ class ActivityStartInterceptor { + */ + TaskDisplayArea mPresumableLaunchDisplayArea; + ++ /** ++ * Whether the component is specified originally in the given Intent. ++ */ ++ boolean mComponentSpecified; ++ + ActivityStartInterceptor( + ActivityTaskManagerService service, ActivityTaskSupervisor supervisor) { + this(service, supervisor, service.mContext); +@@ -185,6 +194,14 @@ class ActivityStartInterceptor { + return TaskFragment.fromTaskFragmentToken(taskFragToken, mService); + } + ++ // TODO: consolidate this method with the one below since this is used for test only. ++ boolean intercept(Intent intent, ResolveInfo rInfo, ActivityInfo aInfo, String resolvedType, ++ Task inTask, TaskFragment inTaskFragment, int callingPid, int callingUid, ++ ActivityOptions activityOptions, TaskDisplayArea presumableLaunchDisplayArea) { ++ return intercept(intent, rInfo, aInfo, resolvedType, inTask, inTaskFragment, callingPid, ++ callingUid, activityOptions, presumableLaunchDisplayArea, false); ++ } ++ + /** + * Intercept the launch intent based on various signals. If an interception happened the + * internal variables get assigned and need to be read explicitly by the caller. +@@ -193,7 +210,8 @@ class ActivityStartInterceptor { + */ + boolean intercept(Intent intent, ResolveInfo rInfo, ActivityInfo aInfo, String resolvedType, + Task inTask, TaskFragment inTaskFragment, int callingPid, int callingUid, +- ActivityOptions activityOptions, TaskDisplayArea presumableLaunchDisplayArea) { ++ ActivityOptions activityOptions, TaskDisplayArea presumableLaunchDisplayArea, ++ boolean componentSpecified) { + mUserManager = UserManager.get(mServiceContext); + + mIntent = intent; +@@ -206,6 +224,7 @@ class ActivityStartInterceptor { + mInTaskFragment = inTaskFragment; + mActivityOptions = activityOptions; + mPresumableLaunchDisplayArea = presumableLaunchDisplayArea; ++ mComponentSpecified = componentSpecified; + + if (interceptQuietProfileIfNeeded()) { + // If work profile is turned off, skip the work challenge since the profile can only +@@ -230,7 +249,8 @@ class ActivityStartInterceptor { + } + if (interceptHomeIfNeeded()) { + // Replace primary home intents directed at displays that do not support primary home +- // but support secondary home with the relevant secondary home activity. ++ // but support secondary home with the relevant secondary home activity. Or the home ++ // intent is not in the correct format. + return true; + } + +@@ -479,9 +499,72 @@ class ActivityStartInterceptor { + if (mPresumableLaunchDisplayArea == null || mService.mRootWindowContainer == null) { + return false; + } +- if (!ActivityRecord.isHomeIntent(mIntent)) { ++ ++ boolean intercepted = false; ++ if (!ACTION_MAIN.equals(mIntent.getAction()) || (!mIntent.hasCategory(CATEGORY_HOME) ++ && !mIntent.hasCategory(CATEGORY_SECONDARY_HOME))) { ++ // not a home intent + return false; + } ++ ++ if (mComponentSpecified) { ++ final ComponentName homeComponent = mIntent.getComponent(); ++ final Intent homeIntent = mService.getHomeIntent(); ++ final ActivityInfo aInfo = mService.mRootWindowContainer.resolveHomeActivity( ++ mUserId, homeIntent); ++ if (!aInfo.getComponentName().equals(homeComponent)) { ++ // Do nothing if the intent is not for the default home component. ++ return false; ++ } ++ } ++ ++ if (!ActivityRecord.isHomeIntent(mIntent) || mComponentSpecified) { ++ // This is not a standard home intent, make it so if possible. ++ normalizeHomeIntent(); ++ intercepted = true; ++ } ++ ++ intercepted |= replaceToSecondaryHomeIntentIfNeeded(); ++ if (intercepted) { ++ mCallingPid = mRealCallingPid; ++ mCallingUid = mRealCallingUid; ++ mResolvedType = null; ++ ++ mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId, /* flags= */ 0, ++ mRealCallingUid, mRealCallingPid); ++ mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, /*profilerInfo=*/ ++ null); ++ } ++ return intercepted; ++ } ++ ++ private void normalizeHomeIntent() { ++ Slog.w(TAG, "The home Intent is not correctly formatted"); ++ if (mIntent.getCategories().size() > 1) { ++ Slog.d(TAG, "Purge home intent categories"); ++ boolean isSecondaryHome = false; ++ final Object[] categories = mIntent.getCategories().toArray(); ++ for (int i = categories.length - 1; i >= 0; i--) { ++ final String category = (String) categories[i]; ++ if (CATEGORY_SECONDARY_HOME.equals(category)) { ++ isSecondaryHome = true; ++ } ++ mIntent.removeCategory(category); ++ } ++ mIntent.addCategory(isSecondaryHome ? CATEGORY_SECONDARY_HOME : CATEGORY_HOME); ++ } ++ if (mIntent.getType() != null || mIntent.getData() != null) { ++ Slog.d(TAG, "Purge home intent data/type"); ++ mIntent.setType(null); ++ } ++ if (mComponentSpecified) { ++ Slog.d(TAG, "Purge home intent component, " + mIntent.getComponent()); ++ mIntent.setComponent(null); ++ } ++ mIntent.addFlags(FLAG_ACTIVITY_NEW_TASK); ++ } ++ ++ private boolean replaceToSecondaryHomeIntentIfNeeded() { + if (!mIntent.hasCategory(Intent.CATEGORY_HOME)) { + // Already a secondary home intent, leave it alone. + return false; +@@ -506,13 +589,6 @@ class ActivityStartInterceptor { + // and should not be moved to the caller's task. Also, activities cannot change their type, + // e.g. a standard activity cannot become a home activity. + mIntent.addFlags(FLAG_ACTIVITY_NEW_TASK); +- mCallingPid = mRealCallingPid; +- mCallingUid = mRealCallingUid; +- mResolvedType = null; +- +- mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId, /* flags= */ 0, +- mRealCallingUid, mRealCallingPid); +- mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, /*profilerInfo=*/ null); + return true; + } + +diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java +index 2781592c6b4f..c352575f0102 100644 +--- a/services/core/java/com/android/server/wm/ActivityStarter.java ++++ b/services/core/java/com/android/server/wm/ActivityStarter.java +@@ -1340,7 +1340,8 @@ class ActivityStarter { + callingPackage, + callingFeatureId); + if (mInterceptor.intercept(intent, rInfo, aInfo, resolvedType, inTask, inTaskFragment, +- callingPid, callingUid, checkedOptions, suggestedLaunchDisplayArea)) { ++ callingPid, callingUid, checkedOptions, suggestedLaunchDisplayArea, ++ request.componentSpecified)) { + // activity start was intercepted, e.g. because the target user is currently in quiet + // mode (turn off work) or the target application is suspended + intent = mInterceptor.mIntent; +diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java +index 670f9f697a5c..97e97a9e74e7 100644 +--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java ++++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java +@@ -236,6 +236,19 @@ public class ActivityStartInterceptorTest { + return dialogInfo; + } + ++ @Test ++ public void testInterceptIncorrectHomeIntent() { ++ // Create a non-standard home intent ++ final Intent homeIntent = new Intent(Intent.ACTION_MAIN); ++ homeIntent.addCategory(Intent.CATEGORY_HOME); ++ homeIntent.addCategory(Intent.CATEGORY_LAUNCHER); ++ ++ // Ensure the intent is intercepted and normalized to standard home intent. ++ assertTrue(mInterceptor.intercept(homeIntent, null, mAInfo, null, null, null, 0, 0, null, ++ mTaskDisplayArea, false)); ++ assertTrue(ActivityRecord.isHomeIntent(homeIntent)); ++ } ++ + @Test + public void testInterceptLockTaskModeViolationPackage() { + when(mLockTaskController.isActivityAllowed( +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/base/0005-Avoid-home-intent-normalization-if-it-is-from-truste.patch b/aosp_diff/caas/frameworks/base/0005-Avoid-home-intent-normalization-if-it-is-from-truste.patch new file mode 100644 index 0000000..cd40dfb --- /dev/null +++ b/aosp_diff/caas/frameworks/base/0005-Avoid-home-intent-normalization-if-it-is-from-truste.patch @@ -0,0 +1,61 @@ +From 3e01747dd707f39731855df43f5d042515b31c2d Mon Sep 17 00:00:00 2001 +From: Louis Chang +Date: Tue, 24 Dec 2024 05:00:05 +0000 +Subject: [PATCH 02/25] Avoid home intent normalization if it is from trusted + callers + +In order to be compatible with several places that are starting +home with component specified. +- Press home button from Taskbar +- RWC#startHomeOnTaskDisplayArea +- WmTests + +Bug: 385721820 +Bug: 384656159 +Test: RootWindowContainerTest +Flag: com.android.window.flags.normalize_home_intent +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:2da0fd3573767fe585bd9d349cc4b69058c77a6f) +Merged-In: Ie192b226eac970e9e897214059abd1615f1c5cf5 +Change-Id: Ie192b226eac970e9e897214059abd1615f1c5cf5 +--- + .../android/server/wm/ActivityStartInterceptor.java | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java +index afe6e91bb949..a33152c90456 100644 +--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java ++++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java +@@ -16,6 +16,7 @@ + + package com.android.server.wm; + ++import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS; + import static android.app.ActivityManager.INTENT_SENDER_ACTIVITY; + import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS; + import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; +@@ -35,6 +36,7 @@ import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; + import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; + import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; + import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED; ++import static android.content.pm.PackageManager.PERMISSION_GRANTED; + + import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; + +@@ -508,6 +510,14 @@ class ActivityStartInterceptor { + } + + if (mComponentSpecified) { ++ Slog.w(TAG, "Starting home with component specified, uid=" + mCallingUid); ++ if (mService.isCallerRecents(mCallingUid) ++ || ActivityTaskManagerService.checkPermission(MANAGE_ACTIVITY_TASKS, ++ mCallingPid, mCallingUid) == PERMISSION_GRANTED) { ++ // Allow home component specified from trusted callers. ++ return false; ++ } ++ + final ComponentName homeComponent = mIntent.getComponent(); + final Intent homeIntent = mService.getHomeIntent(); + final ActivityInfo aInfo = mService.mRootWindowContainer.resolveHomeActivity( +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/base/0006-Fix-starting-the-activity-with-incorrect-pid-uid.patch b/aosp_diff/caas/frameworks/base/0006-Fix-starting-the-activity-with-incorrect-pid-uid.patch new file mode 100644 index 0000000..9627f8b --- /dev/null +++ b/aosp_diff/caas/frameworks/base/0006-Fix-starting-the-activity-with-incorrect-pid-uid.patch @@ -0,0 +1,48 @@ +From 6a82f964ed66a4a67ae427f2b38012a207d5c44c Mon Sep 17 00:00:00 2001 +From: Louis Chang +Date: Thu, 24 Apr 2025 03:35:23 +0000 +Subject: [PATCH 03/25] Fix starting the activity with incorrect pid/uid + +Starts the activity in the same thread vs. in the frame callback. + +Bug: 406763872 +Test: am start-in-vsync +Flag: EXEMPT bugfix +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:fca9c788b3364017b021544ec9594b43c93c9d29) +Merged-In: Ida3648fb8a4c7e4801b024622f7e5f1110184a0d +Change-Id: Ida3648fb8a4c7e4801b024622f7e5f1110184a0d +--- + .../server/am/ActivityManagerShellCommand.java | 12 ++---------- + 1 file changed, 2 insertions(+), 10 deletions(-) + +diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +index 9a63546bf5a7..74d4dd9eca1c 100644 +--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java ++++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +@@ -250,21 +250,13 @@ final class ActivityManagerShellCommand extends ShellCommand { + return runStartActivity(pw); + case "start-in-vsync": + final ProgressWaiter waiter = new ProgressWaiter(0); +- final int[] startResult = new int[1]; +- startResult[0] = -1; + mInternal.mUiHandler.runWithScissors( + () -> Choreographer.getInstance().postFrameCallback(frameTimeNanos -> { +- try { +- startResult[0] = runStartActivity(pw); +- waiter.onFinished(0, null /* extras */); +- } catch (Exception ex) { +- getErrPrintWriter().println( +- "Error: unable to start activity, " + ex); +- } ++ waiter.onFinished(0, null /* extras */); + }), + USER_OPERATION_TIMEOUT_MS / 2); + waiter.waitForFinish(USER_OPERATION_TIMEOUT_MS); +- return startResult[0]; ++ return runStartActivity(pw); + case "startservice": + case "start-service": + return runStartService(pw, false); +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/base/0007-Unexport-ControlsActivity.patch b/aosp_diff/caas/frameworks/base/0007-Unexport-ControlsActivity.patch new file mode 100644 index 0000000..d423ebc --- /dev/null +++ b/aosp_diff/caas/frameworks/base/0007-Unexport-ControlsActivity.patch @@ -0,0 +1,85 @@ +From 524864e3cb6cb6ad4ad062146af2c7dfe301f344 Mon Sep 17 00:00:00 2001 +From: Behnam Heydarshahi +Date: Tue, 8 Apr 2025 13:59:15 -0700 +Subject: [PATCH 04/25] Unexport ControlsActivity + +Also do not draw non-system windows on top of ControlsSettingsDialog + +Fix: 404256832 +Flag: EXEMPT bugfix +Test: atest ControlsSettingsDialogManagerImplTest +Change-Id: I63e98794e1a93e3c208ee421654d73542d2bd34a +--- + packages/SystemUI/AndroidManifest.xml | 1 - + .../settings/ControlsSettingsDialogManager.kt | 3 +++ + .../ControlsSettingsDialogManagerImplTest.kt | 14 ++++++++++++++ + 3 files changed, 17 insertions(+), 1 deletion(-) + +diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml +index 11cb0703d353..8c37def7851c 100644 +--- a/packages/SystemUI/AndroidManifest.xml ++++ b/packages/SystemUI/AndroidManifest.xml +@@ -928,7 +928,6 @@ + android:launchMode="singleInstance" + android:configChanges="screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden|orientation" + android:visibleToInstantApps="true" +- android:exported="true" + /> + + +Date: Thu, 17 Apr 2025 16:24:22 +0000 +Subject: [PATCH] Don't show dismissible keyguard in app pinning mode + +On foldables, if the "Continue using apps on fold" is set +to "Swipe up to continue", when closing the device, +a dismissible keyguard will show. + +If this setting is set, and there's a pinned app, instead +of showing the dismissible keyguard on fold, we keep the +screen on with the app displayed. +This keeps the app pinned and in the same security state as +when it was unfolded. + +Bug: 404252173 +Flag: EXEMPT bugfix +Test: manually (given settings described above), pin app, fold; +observe app still shows with screen on + +Change-Id: I61e2014b9d21862859090707e5531a61bf966fc3 +(cherry picked from commit 14f874decfdd5616b9fb3804154dd2560ebad0a1) +--- + .../com/android/systemui/keyguard/KeyguardService.java | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +index d40fe468b0a5..13c72c67cdf4 100644 +--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java ++++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +@@ -331,6 +331,7 @@ public class KeyguardService extends Service { + } + }; + private final KeyguardServiceLockNowInteractor mKeyguardServiceLockNowInteractor; ++ private final ActivityManager mActivityManager; + + @Inject + public KeyguardService( +@@ -350,6 +351,7 @@ public class KeyguardService extends Service { + Lazy sceneInteractorLazy, + @Main Executor mainExecutor, + KeyguardInteractor keyguardInteractor, ++ ActivityManager activityManager, + KeyguardEnabledInteractor keyguardEnabledInteractor, + Lazy keyguardStateCallbackStartableLazy, + KeyguardWakeDirectlyToGoneInteractor keyguardWakeDirectlyToGoneInteractor, +@@ -386,6 +388,7 @@ public class KeyguardService extends Service { + + mWmOcclusionManager = windowManagerOcclusionManager; + mKeyguardEnabledInteractor = keyguardEnabledInteractor; ++ mActivityManager = activityManager; + mKeyguardWakeDirectlyToGoneInteractor = keyguardWakeDirectlyToGoneInteractor; + mKeyguardDismissInteractor = keyguardDismissInteractor; + mKeyguardServiceLockNowInteractor = keyguardServiceLockNowInteractor; +@@ -677,6 +680,11 @@ public class KeyguardService extends Service { + if (mFoldGracePeriodProvider.get().isEnabled()) { + mKeyguardInteractor.showDismissibleKeyguard(); + } ++ ++ if (mActivityManager.getLockTaskModeState() != ActivityManager.LOCK_TASK_MODE_NONE) { ++ return; ++ } ++ + mKeyguardViewMediator.showDismissibleKeyguard(); + + if (SceneContainerFlag.isEnabled() && mFoldGracePeriodProvider.get().isEnabled()) { +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/base/0009-Improve-audio-sharing-password-handling.patch b/aosp_diff/caas/frameworks/base/0009-Improve-audio-sharing-password-handling.patch new file mode 100644 index 0000000..7e7f9cb --- /dev/null +++ b/aosp_diff/caas/frameworks/base/0009-Improve-audio-sharing-password-handling.patch @@ -0,0 +1,62 @@ +From 832c1126d8547b245caeaba79f5295a4022b0438 Mon Sep 17 00:00:00 2001 +From: Brian Delwiche +Date: Thu, 27 Mar 2025 20:37:11 +0000 +Subject: [PATCH 06/25] Improve audio sharing password handling + +This is a backport of ag/31824318. ag/31424453 against the same bug is +superseded by these changes and need not be backported. + +Test: atest +Bug: 389127608 +Ignore-AOSP-First: security +Tag: #security +Change-Id: If2ad220dc2f4890ee67be62962a9b25d2f803e24 +--- + .../bluetooth/LocalBluetoothLeBroadcast.java | 17 ++++++++++++++--- + 1 file changed, 14 insertions(+), 3 deletions(-) + +diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java +index b52ed42d567f..441542dab10a 100644 +--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java ++++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java +@@ -62,6 +62,7 @@ import com.google.common.collect.ImmutableList; + import java.lang.annotation.Retention; + import java.lang.annotation.RetentionPolicy; + import java.nio.charset.StandardCharsets; ++import java.security.SecureRandom; + import java.util.ArrayList; + import java.util.Arrays; + import java.util.Collections; +@@ -103,6 +104,10 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { + private static final String SETTINGS_PKG = "com.android.settings"; + private static final String TAG = "LocalBluetoothLeBroadcast"; + private static final boolean DEBUG = BluetoothUtils.D; ++ private static final String VALID_PASSWORD_CHARACTERS = ++ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+[]{}|;:," ++ + ".<>?/"; ++ private static final int PASSWORD_LENGTH = 16; + + static final String NAME = "LE_AUDIO_BROADCAST"; + private static final String UNDERLINE = "_"; +@@ -1084,9 +1089,15 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { + } + + private String generateRandomPassword() { +- String randomUUID = UUID.randomUUID().toString(); +- // first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx +- return randomUUID.substring(0, 8) + randomUUID.substring(9, 13); ++ SecureRandom random = new SecureRandom(); ++ StringBuilder stringBuilder = new StringBuilder(PASSWORD_LENGTH); ++ ++ for (int i = 0; i < PASSWORD_LENGTH; i++) { ++ int randomIndex = random.nextInt(VALID_PASSWORD_CHARACTERS.length()); ++ stringBuilder.append(VALID_PASSWORD_CHARACTERS.charAt(randomIndex)); ++ } ++ ++ return stringBuilder.toString(); + } + + private void registerContentObserver() { +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/base/0010-Use-consistent-animation-when-forcibly-hiding-non-sy.patch b/aosp_diff/caas/frameworks/base/0010-Use-consistent-animation-when-forcibly-hiding-non-sy.patch new file mode 100644 index 0000000..0ce5dc1 --- /dev/null +++ b/aosp_diff/caas/frameworks/base/0010-Use-consistent-animation-when-forcibly-hiding-non-sy.patch @@ -0,0 +1,66 @@ +From a72542f1d8a861da98a62e48b49635cc97246239 Mon Sep 17 00:00:00 2001 +From: Riddle Hsu +Date: Mon, 19 May 2025 15:07:16 +0800 +Subject: [PATCH 07/25] Use consistent animation when forcibly hiding non + system overlay + +There are several non system overlay window types. When hiding the +windows with animation, their specified exit animations may be used. + +This avoids ugly jump cut and inconsistent duration when these +windows are requested to be hidden. + +Bug: 408215749 +Flag: EXEMPT bugfix +Test: Show multiple windows with different overlay types and + window animations by non system apps. Launch Settings + and check the windows are fading out in a short time. +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:dd71ab6ba3950978bd28dc38fecec236cf1153ea) +Merged-In: Ie7bb2663892d608715aa077e2170eae4c03a4e36 +Change-Id: Ie7bb2663892d608715aa077e2170eae4c03a4e36 +--- + services/core/java/com/android/server/wm/WindowState.java | 4 ++++ + .../core/java/com/android/server/wm/WindowStateAnimator.java | 5 +++++ + 2 files changed, 9 insertions(+) + +diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java +index 3fa1130d86a3..35d13a54feb9 100644 +--- a/services/core/java/com/android/server/wm/WindowState.java ++++ b/services/core/java/com/android/server/wm/WindowState.java +@@ -3159,6 +3159,10 @@ class WindowState extends WindowContainer implements WindowManagerP + return true; + } + ++ boolean isForceHiddenNonSystemOverlayWindow() { ++ return mForceHideNonSystemOverlayWindow; ++ } ++ + void setForceHideNonSystemOverlayWindowIfNeeded(boolean forceHide) { + final int baseType = getBaseType(); + if (mSession.mCanAddInternalSystemWindow +diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java +index 0154d95d888d..2363a572c3a1 100644 +--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java ++++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java +@@ -63,6 +63,7 @@ import android.view.Surface.OutOfResourcesException; + import android.view.SurfaceControl; + import android.view.WindowManager; + import android.view.WindowManager.LayoutParams; ++import android.view.animation.AlphaAnimation; + import android.view.animation.Animation; + import android.view.animation.AnimationUtils; + +@@ -611,6 +612,10 @@ class WindowStateAnimator { + a = AnimationUtils.loadAnimation(mContext, anim); + Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); + } ++ } else if (!isEntrance && mWin.isForceHiddenNonSystemOverlayWindow()) { ++ a = new AlphaAnimation(1f, 0f); ++ a.setDuration(mContext.getResources().getInteger( ++ com.android.internal.R.integer.config_shortAnimTime)); + } else { + switch (transit) { + case WindowManagerPolicy.TRANSIT_ENTER: +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/base/0011-Do-not-allow-custom-animation-of-untrusted-Toast.patch b/aosp_diff/caas/frameworks/base/0011-Do-not-allow-custom-animation-of-untrusted-Toast.patch new file mode 100644 index 0000000..41022b7 --- /dev/null +++ b/aosp_diff/caas/frameworks/base/0011-Do-not-allow-custom-animation-of-untrusted-Toast.patch @@ -0,0 +1,46 @@ +From 1185a22fb510def2d5de3041f77b6d626a743ea6 Mon Sep 17 00:00:00 2001 +From: Riddle Hsu +Date: Fri, 16 May 2025 11:59:46 +0800 +Subject: [PATCH 08/25] Do not allow custom animation of untrusted Toast + +Since target API level 30 (Android 11), regular text toast is shown +by SystemUI (See#CHANGE_TEXT_TOASTS_IN_THE_SYSTEM). While legacy +apps and deprecated custom view show the toast in its own processes, +so force the default animation style for these usages to avoid +unexpected animation duration. + +Such when setForceHideNonSystemOverlayWindowIfNeeded is triggered, +the regular Toast displayed by SystemUI won't be affected (because +SystemUI has internal permission), and the untrusted Toast can be +hidden in a short time (config_longAnimTime=500ms) instead of a +customized duration. + +Bug: 408215749 +Flag: EXEMPT bugfix +Test: Show a Toast with modified + WindowManager.LayoutParams#windowAnimations +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:3beed7b34e54f2147aef9e9e5bbb413527b399fa) +Merged-In: Ib2844dedebfa3ec9fc8333af3f66bb07cd4d3d7f +Change-Id: Ib2844dedebfa3ec9fc8333af3f66bb07cd4d3d7f +--- + services/core/java/com/android/server/wm/DisplayPolicy.java | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java +index 659bb6784c89..d8ab0cadeb48 100644 +--- a/services/core/java/com/android/server/wm/DisplayPolicy.java ++++ b/services/core/java/com/android/server/wm/DisplayPolicy.java +@@ -979,6 +979,10 @@ public class DisplayPolicy { + AccessibilityManager.FLAG_CONTENT_TEXT); + // Toasts can't be clickable + attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; ++ // Do not allow untrusted toast to customize animation. ++ if (!win.mSession.mCanAddInternalSystemWindow) { ++ attrs.windowAnimations = R.style.Animation_Toast; ++ } + break; + + case TYPE_BASE_APPLICATION: +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/base/0012-Limits-the-display-name-to-1024-characters.patch b/aosp_diff/caas/frameworks/base/0012-Limits-the-display-name-to-1024-characters.patch new file mode 100644 index 0000000..55b23c9 --- /dev/null +++ b/aosp_diff/caas/frameworks/base/0012-Limits-the-display-name-to-1024-characters.patch @@ -0,0 +1,41 @@ +From 14c9d167408055e4d0ef595a40fcca69cc88503e Mon Sep 17 00:00:00 2001 +From: Evan Chen +Date: Thu, 29 May 2025 17:51:03 +0000 +Subject: [PATCH 09/25] Limits the display name to 1024 characters + +Bug: 418773283 +Test: cts +Flag: EXEMPT bugfix +Change-Id: I29e050c498e813e291af213ce4ddc590559c8b7d +--- + core/java/android/companion/AssociationRequest.java | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java +index f368935a74c8..8a04fc863a71 100644 +--- a/core/java/android/companion/AssociationRequest.java ++++ b/core/java/android/companion/AssociationRequest.java +@@ -235,6 +235,8 @@ public final class AssociationRequest implements Parcelable { + */ + private boolean mSkipPrompt; + ++ private static final int DISPLAY_NAME_LENGTH_LIMIT = 1024; ++ + /** + * The device icon displayed in selfManaged association dialog. + * @hide +@@ -443,6 +445,11 @@ public final class AssociationRequest implements Parcelable { + public Builder setDisplayName(@NonNull CharSequence displayName) { + checkNotUsed(); + mDisplayName = requireNonNull(displayName); ++ if (displayName.length() > DISPLAY_NAME_LENGTH_LIMIT) { ++ throw new IllegalArgumentException("Length of the display name must be at most " ++ + DISPLAY_NAME_LENGTH_LIMIT + " characters"); ++ } ++ + return this; + } + +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/base/0013-Revert-Fix-biometric-prompt-appearing-above-shade.patch b/aosp_diff/caas/frameworks/base/0013-Revert-Fix-biometric-prompt-appearing-above-shade.patch new file mode 100644 index 0000000..ee29488 --- /dev/null +++ b/aosp_diff/caas/frameworks/base/0013-Revert-Fix-biometric-prompt-appearing-above-shade.patch @@ -0,0 +1,33 @@ +From a06537c7b17d6d607ea6bb8e6d2d207fa709bf5e Mon Sep 17 00:00:00 2001 +From: Austin Delgado +Date: Wed, 21 May 2025 12:03:10 -0700 +Subject: [PATCH 10/25] Revert "Fix biometric prompt appearing above shade" + +This reverts commit 93b417adaecdd26c2e99d7cdc5eacac84aa842e3. + +Reason for revert: Reverting on old branches + +Bug: 384727394 +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:1b19eeac213c8b93c2c2a745c666b683da62ccc0) +Merged-In: I0fb69ebb12809cc118c7cc716d670eacc007c5e7 +Change-Id: I0fb69ebb12809cc118c7cc716d670eacc007c5e7 +--- + .../src/com/android/systemui/biometrics/AuthContainerView.java | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +index b6537118324e..6e85d9e8bbc0 100644 +--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java ++++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +@@ -762,7 +762,7 @@ public class AuthContainerView extends LinearLayout + final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, +- WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, ++ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG, + windowFlags, + PixelFormat.TRANSLUCENT); + lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/base/0014-Avoid-mixups-between-different-CPSes-in-ZenModeCondi.patch b/aosp_diff/caas/frameworks/base/0014-Avoid-mixups-between-different-CPSes-in-ZenModeCondi.patch new file mode 100644 index 0000000..1db5802 --- /dev/null +++ b/aosp_diff/caas/frameworks/base/0014-Avoid-mixups-between-different-CPSes-in-ZenModeCondi.patch @@ -0,0 +1,422 @@ +From 941634feafeef3255a40dceea6787dd890d295c7 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= +Date: Tue, 27 May 2025 15:54:09 +0200 +Subject: [PATCH 11/25] Avoid mixups between different CPSes in + ZenModeConditions + +A couple of places in ZenModeConditions assumed that condition id uniquely determines ConditionProviderService, which is not correct. + +Additionally, verify that only system zen rules can be handled by system CPSes (schedule, event, etc). + +Bug: 391894257 +Test: atest ZenModeConditionsTest +Flag: EXEMPT Bug fix +Change-Id: I7bff4b04674b5f247bd3b8b6920af029ef8098f5 +Merged-In: I7bff4b04674b5f247bd3b8b6920af029ef8098f5 +(cherry picked from commit 5cb0ae9c43e2262ad37f599de4c65bb31841f936) +--- + .../notification/ConditionProviders.java | 9 +- + .../notification/ZenModeConditions.java | 48 ++++-- + .../notification/ZenModeConditionsTest.java | 163 ++++++++++++++++++ + .../notification/ZenModeHelperTest.java | 48 ++++-- + 4 files changed, 236 insertions(+), 32 deletions(-) + create mode 100644 services/tests/uiservicestests/src/com/android/server/notification/ZenModeConditionsTest.java + +diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java +index 0b40d64e3a09..6e79660ca86f 100644 +--- a/services/core/java/com/android/server/notification/ConditionProviders.java ++++ b/services/core/java/com/android/server/notification/ConditionProviders.java +@@ -290,6 +290,13 @@ public class ConditionProviders extends ManagedServices { + return rt; + } + ++ @VisibleForTesting ++ ConditionRecord getRecord(Uri id, ComponentName component) { ++ synchronized (mMutex) { ++ return getRecordLocked(id, component, false); ++ } ++ } ++ + private ConditionRecord getRecordLocked(Uri id, ComponentName component, boolean create) { + if (id == null || component == null) return null; + final int N = mRecords.size(); +@@ -492,7 +499,7 @@ public class ConditionProviders extends ManagedServices { + return removed; + } + +- private static class ConditionRecord { ++ static class ConditionRecord { + public final Uri id; + public final ComponentName component; + public Condition condition; +diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java +index 52d0c41614d5..d4b319d70651 100644 +--- a/services/core/java/com/android/server/notification/ZenModeConditions.java ++++ b/services/core/java/com/android/server/notification/ZenModeConditions.java +@@ -25,9 +25,10 @@ import android.service.notification.Condition; + import android.service.notification.IConditionProvider; + import android.service.notification.ZenModeConfig; + import android.service.notification.ZenModeConfig.ZenRule; +-import android.util.ArrayMap; + import android.util.ArraySet; + import android.util.Log; ++import android.util.Pair; ++import android.util.Slog; + + import com.android.internal.annotations.VisibleForTesting; + +@@ -45,7 +46,7 @@ public class ZenModeConditions implements ConditionProviders.Callback { + private final ConditionProviders mConditionProviders; + + @VisibleForTesting +- protected final ArrayMap mSubscriptions = new ArrayMap<>(); ++ protected final ArraySet> mSubscriptions = new ArraySet<>(); + + public ZenModeConditions(ZenModeHelper helper, ConditionProviders conditionProviders) { + mHelper = helper; +@@ -78,7 +79,7 @@ public class ZenModeConditions implements ConditionProviders.Callback { + if (DEBUG) Log.d(TAG, "evaluateConfig: clearing manual rule"); + config.manualRule = null; + } +- final ArraySet current = new ArraySet<>(); ++ final ArraySet> current = new ArraySet<>(); + evaluateRule(config.manualRule, current, null, processSubscriptions, true); + for (ZenRule automaticRule : config.automaticRules.values()) { + if (automaticRule.component != null) { +@@ -90,11 +91,11 @@ public class ZenModeConditions implements ConditionProviders.Callback { + synchronized (mSubscriptions) { + final int N = mSubscriptions.size(); + for (int i = N - 1; i >= 0; i--) { +- final Uri id = mSubscriptions.keyAt(i); +- final ComponentName component = mSubscriptions.valueAt(i); ++ final Pair subscription = mSubscriptions.valueAt(i); + if (processSubscriptions) { +- if (!current.contains(id)) { +- mConditionProviders.unsubscribeIfNecessary(component, id); ++ if (!current.contains(subscription)) { ++ mConditionProviders.unsubscribeIfNecessary(subscription.second, ++ subscription.first); + mSubscriptions.removeAt(i); + } + } +@@ -128,19 +129,32 @@ public class ZenModeConditions implements ConditionProviders.Callback { + } + + // Only valid for CPS backed rules +- private void evaluateRule(ZenRule rule, ArraySet current, ComponentName trigger, +- boolean processSubscriptions, boolean isManual) { ++ private void evaluateRule(ZenRule rule, ArraySet> current, ++ ComponentName trigger, boolean processSubscriptions, boolean isManual) { + if (rule == null || rule.conditionId == null) return; + if (rule.configurationActivity != null) return; + final Uri id = rule.conditionId; ++ boolean isSystemRule = isManual || ZenModeConfig.SYSTEM_AUTHORITY.equals(rule.getPkg()); ++ ++ if (!isSystemRule ++ && rule.component != null ++ && ZenModeConfig.SYSTEM_AUTHORITY.equals(rule.component.getPackageName())) { ++ Slog.w(TAG, "Rule " + rule.id + " belongs to package " + rule.getPkg() ++ + " but has component=" + rule.component + " which is not allowed!"); ++ return; ++ } ++ + boolean isSystemCondition = false; +- for (SystemConditionProviderService sp : mConditionProviders.getSystemProviders()) { +- if (sp.isValidConditionId(id)) { +- mConditionProviders.ensureRecordExists(sp.getComponent(), id, sp.asInterface()); +- rule.component = sp.getComponent(); +- isSystemCondition = true; ++ if (isSystemRule) { ++ for (SystemConditionProviderService sp : mConditionProviders.getSystemProviders()) { ++ if (sp.isValidConditionId(id)) { ++ mConditionProviders.ensureRecordExists(sp.getComponent(), id, sp.asInterface()); ++ rule.component = sp.getComponent(); ++ isSystemCondition = true; ++ } + } + } ++ + // ensure that we have a record of the rule if it's backed by an currently alive CPS + if (!isSystemCondition) { + final IConditionProvider cp = mConditionProviders.findConditionProvider(rule.component); +@@ -157,8 +171,10 @@ public class ZenModeConditions implements ConditionProviders.Callback { + } + return; + } ++ ++ Pair uriAndCps = new Pair<>(id, rule.component); + if (current != null) { +- current.add(id); ++ current.add(uriAndCps); + } + + // If the rule is bound by a CPS and the CPS is alive, tell them about the rule +@@ -167,7 +183,7 @@ public class ZenModeConditions implements ConditionProviders.Callback { + if (DEBUG) Log.d(TAG, "Subscribing to " + rule.component); + if (mConditionProviders.subscribeIfNecessary(rule.component, rule.conditionId)) { + synchronized (mSubscriptions) { +- mSubscriptions.put(rule.conditionId, rule.component); ++ mSubscriptions.add(uriAndCps); + } + } else { + rule.condition = null; +diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConditionsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConditionsTest.java +new file mode 100644 +index 000000000000..a53b7e011a80 +--- /dev/null ++++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConditionsTest.java +@@ -0,0 +1,163 @@ ++/* ++ * 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.server.notification; ++ ++import static com.google.common.truth.Truth.assertThat; ++ ++import static org.mockito.Mockito.mock; ++ ++import android.content.ComponentName; ++import android.content.ServiceConnection; ++import android.content.pm.IPackageManager; ++import android.net.Uri; ++import android.os.Build; ++import android.service.notification.ConditionProviderService; ++import android.service.notification.IConditionProvider; ++import android.service.notification.ZenModeConfig; ++import android.service.notification.ZenModeConfig.ZenRule; ++import android.testing.AndroidTestingRunner; ++import android.testing.TestableLooper; ++ ++import androidx.test.filters.SmallTest; ++ ++import com.android.server.UiServiceTestCase; ++import com.android.server.notification.ConditionProviders.ConditionRecord; ++ ++import org.junit.Before; ++import org.junit.Test; ++import org.junit.runner.RunWith; ++ ++import java.util.Set; ++ ++@SmallTest ++@RunWith(AndroidTestingRunner.class) ++@TestableLooper.RunWithLooper ++public class ZenModeConditionsTest extends UiServiceTestCase { ++ ++ private static final ComponentName SCHEDULE_CPS = new ComponentName("android", ++ "com.android.server.notification.ScheduleConditionProvider"); ++ private static final Uri SCHEDULE_CPS_CONDITION_ID = Uri.parse( ++ "condition://android/schedule?days=1.2.3&start=3.0&end=5.0&exitAtAlarm=true"); ++ ++ private static final String PACKAGE = "com.some.package"; ++ private static final ComponentName PACKAGE_CPS = new ComponentName(PACKAGE, ++ PACKAGE + ".TheConditionProviderService"); ++ ++ private ZenModeConditions mZenModeConditions; ++ private ConditionProviders mConditionProviders; ++ ++ @Before ++ public void setUp() { ++ mConditionProviders = new ConditionProviders(mContext, new ManagedServices.UserProfiles(), ++ mock(IPackageManager.class)); ++ mZenModeConditions = new ZenModeConditions(mock(ZenModeHelper.class), mConditionProviders); ++ ((Set) mConditionProviders.getSystemProviders()).clear(); // Hack, remove built-in CPSes ++ ++ ScheduleConditionProvider scheduleConditionProvider = new ScheduleConditionProvider(); ++ mConditionProviders.addSystemProvider(scheduleConditionProvider); ++ ++ ConditionProviderService packageConditionProvider = new PackageConditionProviderService(); ++ mConditionProviders.registerGuestService(mConditionProviders.new ManagedServiceInfo( ++ (IConditionProvider) packageConditionProvider.onBind(null), PACKAGE_CPS, ++ mContext.getUserId(), false, mock(ServiceConnection.class), ++ Build.VERSION_CODES.TIRAMISU, 44)); ++ } ++ ++ @Test ++ public void evaluateRule_systemRuleWithSystemConditionProvider_evaluates() { ++ ZenRule systemRule = newSystemZenRule("1", SCHEDULE_CPS, SCHEDULE_CPS_CONDITION_ID); ++ ZenModeConfig config = configWithRules(systemRule); ++ ++ mZenModeConditions.evaluateConfig(config, null, /* processSubscriptions= */ true); ++ ++ ConditionRecord conditionRecord = mConditionProviders.getRecord(SCHEDULE_CPS_CONDITION_ID, ++ SCHEDULE_CPS); ++ assertThat(conditionRecord).isNotNull(); ++ assertThat(conditionRecord.subscribed).isTrue(); ++ } ++ ++ @Test ++ public void evaluateConfig_packageRuleWithSystemConditionProvider_ignored() { ++ ZenRule packageRule = newPackageZenRule(PACKAGE, SCHEDULE_CPS, SCHEDULE_CPS_CONDITION_ID); ++ ZenModeConfig config = configWithRules(packageRule); ++ ++ mZenModeConditions.evaluateConfig(config, null, /* processSubscriptions= */ true); ++ ++ assertThat(mConditionProviders.getRecord(SCHEDULE_CPS_CONDITION_ID, SCHEDULE_CPS)) ++ .isNull(); ++ } ++ ++ @Test ++ public void evaluateConfig_packageRuleWithPackageCpsButSystemLikeConditionId_usesPackageCps() { ++ ZenRule packageRule = newPackageZenRule(PACKAGE, PACKAGE_CPS, ++ SCHEDULE_CPS_CONDITION_ID); ++ ZenModeConfig config = configWithRules(packageRule); ++ ++ mZenModeConditions.evaluateConfig(config, /* trigger= */ PACKAGE_CPS, ++ /* processSubscriptions= */ true); ++ ++ ConditionRecord packageCpsRecord = mConditionProviders.getRecord(SCHEDULE_CPS_CONDITION_ID, ++ PACKAGE_CPS); ++ assertThat(packageCpsRecord).isNotNull(); ++ assertThat(packageCpsRecord.subscribed).isTrue(); ++ ++ ConditionRecord systemCpsRecord = mConditionProviders.getRecord(SCHEDULE_CPS_CONDITION_ID, ++ SCHEDULE_CPS); ++ assertThat(systemCpsRecord).isNull(); ++ } ++ ++ private static ZenModeConfig configWithRules(ZenRule... zenRules) { ++ ZenModeConfig config = new ZenModeConfig(); ++ for (ZenRule zenRule : zenRules) { ++ config.automaticRules.put(zenRule.id, zenRule); ++ } ++ return config; ++ } ++ ++ private static ZenRule newSystemZenRule(String id, ComponentName component, Uri conditionId) { ++ ZenRule systemRule = new ZenRule(); ++ systemRule.id = id; ++ systemRule.name = "System Rule " + id; ++ systemRule.pkg = ZenModeConfig.SYSTEM_AUTHORITY; ++ systemRule.component = component; ++ systemRule.conditionId = conditionId; ++ return systemRule; ++ } ++ ++ private static ZenRule newPackageZenRule(String packageName, ComponentName component, ++ Uri conditionId) { ++ ZenRule packageRule = new ZenRule(); ++ packageRule.id = "id " + packageName; ++ packageRule.name = "Package Rule " + packageName; ++ packageRule.pkg = packageName; ++ packageRule.component = component; ++ packageRule.conditionId = conditionId; ++ return packageRule; ++ } ++ ++ private static class PackageConditionProviderService extends ConditionProviderService { ++ ++ @Override ++ public void onConnected() { } ++ ++ @Override ++ public void onSubscribe(Uri conditionId) { } ++ ++ @Override ++ public void onUnsubscribe(Uri conditionId) { } ++ } ++} +diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +index 09da0156eb82..191b3bcbfd76 100644 +--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java ++++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +@@ -135,6 +135,7 @@ import android.app.backup.BackupRestoreEventLogger; + import android.app.compat.CompatChanges; + import android.content.ComponentName; + import android.content.Context; ++import android.content.ServiceConnection; + import android.content.pm.ActivityInfo; + import android.content.pm.ApplicationInfo; + import android.content.pm.PackageInfo; +@@ -148,6 +149,7 @@ import android.media.AudioManagerInternal; + import android.media.AudioSystem; + import android.media.VolumePolicy; + import android.net.Uri; ++import android.os.Build; + import android.os.Parcel; + import android.os.SimpleClock; + import android.os.UserHandle; +@@ -158,7 +160,9 @@ import android.platform.test.flag.junit.SetFlagsRule; + import android.provider.Settings; + import android.provider.Settings.Global; + import android.service.notification.Condition; ++import android.service.notification.ConditionProviderService; + import android.service.notification.DeviceEffectsApplier; ++import android.service.notification.IConditionProvider; + import android.service.notification.SystemZenRules; + import android.service.notification.ZenAdapters; + import android.service.notification.ZenDeviceEffects; +@@ -2707,21 +2711,23 @@ public class ZenModeHelperTest extends UiServiceTestCase { + + @Test + public void testRulesWithSameUri() { +- // needs to be a valid schedule info object for the subscription to happen properly +- ScheduleInfo scheduleInfo = new ScheduleInfo(); +- scheduleInfo.days = new int[]{1, 2}; +- scheduleInfo.endHour = 1; +- Uri sharedUri = ZenModeConfig.toScheduleConditionId(scheduleInfo); +- AutomaticZenRule zenRule = new AutomaticZenRule("name", +- new ComponentName(mPkg, "ScheduleConditionProvider"), +- sharedUri, +- NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); +- String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, zenRule, +- ORIGIN_SYSTEM, "test", SYSTEM_UID); +- AutomaticZenRule zenRule2 = new AutomaticZenRule("name2", +- new ComponentName(mPkg, "ScheduleConditionProvider"), +- sharedUri, +- NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); ++ // Needs a "valid" CPS otherwise ZenModeConditions will balk and clear rule.condition ++ ComponentName packageCpsName = new ComponentName(mContext, ++ PackageConditionProviderService.class); ++ ConditionProviderService packageCps = new PackageConditionProviderService(); ++ mConditionProviders.registerGuestService(mConditionProviders.new ManagedServiceInfo( ++ (IConditionProvider) packageCps.onBind(null), packageCpsName, ++ mContext.getUserId(), false, mock(ServiceConnection.class), ++ Build.VERSION_CODES.TIRAMISU, 44)); ++ Uri sharedUri = Uri.parse("packageConditionId"); ++ ++ AutomaticZenRule zenRule = new AutomaticZenRule("name", packageCpsName, ++ sharedUri, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); ++ String id = mZenModeHelper.addAutomaticZenRule(mPkg, zenRule, ++ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); ++ AutomaticZenRule zenRule2 = new AutomaticZenRule("name2", packageCpsName, ++ sharedUri, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); ++ + String id2 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, zenRule2, + ORIGIN_SYSTEM, "test", SYSTEM_UID); + +@@ -7801,6 +7807,18 @@ public class ZenModeHelperTest extends UiServiceTestCase { + } + } + ++ private static class PackageConditionProviderService extends ConditionProviderService { ++ ++ @Override ++ public void onConnected() { } ++ ++ @Override ++ public void onSubscribe(Uri conditionId) { } ++ ++ @Override ++ public void onUnsubscribe(Uri conditionId) { } ++ } ++ + private static class TestClock extends SimpleClock { + private long mNowMillis = 441644400000L; + +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/base/0015--Don-t-allow-hiding-SysUi.patch b/aosp_diff/caas/frameworks/base/0015--Don-t-allow-hiding-SysUi.patch new file mode 100644 index 0000000..b9f4c8f --- /dev/null +++ b/aosp_diff/caas/frameworks/base/0015--Don-t-allow-hiding-SysUi.patch @@ -0,0 +1,40 @@ +From 4ff7af1715660e1e2bff62b58aebfefdd7c6ea9c Mon Sep 17 00:00:00 2001 +From: Pavel Grafov +Date: Wed, 21 May 2025 17:47:28 +0100 +Subject: [PATCH 12/25] Don't allow hiding SysUi + +Hiding SystemUI makes the phone impossible to use even when done inside +managed profile. + +Bug: 328182084 +Test: manual with custom DPC +Flag: EXEMPT bugfix +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:befa3c99b9571d8f28f4974e0c4adfe538eeba66) +Merged-In: I847cb6d69db4924b2b3ddb741ac61a8f065e7c78 +Change-Id: I847cb6d69db4924b2b3ddb741ac61a8f065e7c78 +--- + .../java/com/android/server/pm/PackageManagerService.java | 8 +++++--- + 1 file changed, 5 insertions(+), 3 deletions(-) + +diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java +index aadf11227d89..600bbbafddd6 100644 +--- a/services/core/java/com/android/server/pm/PackageManagerService.java ++++ b/services/core/java/com/android/server/pm/PackageManagerService.java +@@ -5958,9 +5958,11 @@ public class PackageManagerService implements PackageSender, TestUtilityService + return false; + } + +- // Do not allow "android" is being disabled +- if ("android".equals(packageName)) { +- Slog.w(TAG, "Cannot hide package: android"); ++ // Don't allow hiding "android" or SysUI as it makes device unusable. ++ if ("android".equals(packageName) ++ || LocalServices.getService(PackageManagerInternal.class) ++ .getSystemUiServiceComponent().getPackageName().equals(packageName)) { ++ Slog.w(TAG, "Cannot hide package: " + packageName); + return false; + } + +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/base/0016-Prevent-non-system-ShutdownActivity-from-being-launc.patch b/aosp_diff/caas/frameworks/base/0016-Prevent-non-system-ShutdownActivity-from-being-launc.patch new file mode 100644 index 0000000..a0423fa --- /dev/null +++ b/aosp_diff/caas/frameworks/base/0016-Prevent-non-system-ShutdownActivity-from-being-launc.patch @@ -0,0 +1,137 @@ +From f6c0e58793288885587b4dfe62187b9f2b690f4b Mon Sep 17 00:00:00 2001 +From: Dmitri Plotnikov +Date: Fri, 16 May 2025 16:51:28 -0700 +Subject: [PATCH 13/25] Prevent non-system ShutdownActivity from being launched + by BatteryService + +Bug: 380885270 +Test: adb shell dumpsys battery set temp 1001 +Flag: EXEMPT bug fix +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:8cda6fda96c420588a5f8f5112522cfde14659b4) +Merged-In: I6c00b47d424d81712bd9634c31de4b7d2e9cbe31 +Change-Id: I6c00b47d424d81712bd9634c31de4b7d2e9cbe31 +--- + .../com/android/server/BatteryService.java | 76 +++++++++++++------ + 1 file changed, 51 insertions(+), 25 deletions(-) + +diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java +index 4cf17ae3984d..9ba032b4df8e 100644 +--- a/services/core/java/com/android/server/BatteryService.java ++++ b/services/core/java/com/android/server/BatteryService.java +@@ -34,6 +34,9 @@ import android.app.BroadcastOptions; + import android.content.ContentResolver; + import android.content.Context; + import android.content.Intent; ++import android.content.pm.PackageManager; ++import android.content.pm.PackageManagerInternal; ++import android.content.pm.ResolveInfo; + import android.database.ContentObserver; + import android.hardware.health.HealthInfo; + import android.hardware.health.V2_1.BatteryCapacityLevel; +@@ -54,6 +57,7 @@ import android.os.IBinder; + import android.os.Looper; + import android.os.OsProtoEnums; + import android.os.PowerManager; ++import android.os.Process; + import android.os.RemoteException; + import android.os.ResultReceiver; + import android.os.ServiceManager; +@@ -89,6 +93,7 @@ import java.io.IOException; + import java.io.PrintWriter; + import java.util.ArrayDeque; + import java.util.ArrayList; ++import java.util.List; + import java.util.NoSuchElementException; + import java.util.Objects; + import java.util.concurrent.CopyOnWriteArraySet; +@@ -631,41 +636,62 @@ public final class BatteryService extends SystemService { + // shut down gracefully if our battery is critically low and we are not powered. + // wait until the system has booted before attempting to display the shutdown dialog. + if (shouldShutdownLocked()) { +- mHandler.post(new Runnable() { +- @Override +- public void run() { +- if (mActivityManagerInternal.isSystemReady()) { +- Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN); +- intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false); +- intent.putExtra(Intent.EXTRA_REASON, +- PowerManager.SHUTDOWN_LOW_BATTERY); +- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); +- mContext.startActivityAsUser(intent, UserHandle.CURRENT); +- } +- } +- }); ++ Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN); ++ intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false); ++ intent.putExtra(Intent.EXTRA_REASON, ++ PowerManager.SHUTDOWN_LOW_BATTERY); ++ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); ++ mHandler.post(() -> startShutdownActivity(intent)); + } + } + ++ + private void shutdownIfOverTempLocked() { + // shut down gracefully if temperature is too high (> 68.0C by default) + // wait until the system has booted before attempting to display the + // shutdown dialog. + if (mHealthInfo.batteryTemperatureTenthsCelsius > mShutdownBatteryTemperature) { +- mHandler.post(new Runnable() { +- @Override +- public void run() { +- if (mActivityManagerInternal.isSystemReady()) { +- Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN); +- intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false); +- intent.putExtra(Intent.EXTRA_REASON, +- PowerManager.SHUTDOWN_BATTERY_THERMAL_STATE); +- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); +- mContext.startActivityAsUser(intent, UserHandle.CURRENT); +- } ++ Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN); ++ intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false); ++ intent.putExtra(Intent.EXTRA_REASON, ++ PowerManager.SHUTDOWN_BATTERY_THERMAL_STATE); ++ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); ++ mHandler.post(() -> startShutdownActivity(intent)); ++ } ++ } ++ ++ private void startShutdownActivity(Intent intent) { ++ if (!mActivityManagerInternal.isSystemReady()) { ++ return; ++ } ++ ++ PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class); ++ if (pmi == null) { ++ return; ++ } ++ ++ // Find a target activity for the shutdown request that has android.permission.SHUTDOWN ++ List resolveInfos = pmi.queryIntentActivities(intent, null, 0, Process.myUid(), ++ mActivityManagerInternal.getCurrentUserId()); ++ if (resolveInfos != null) { ++ for (int i = 0; i < resolveInfos.size(); i++) { ++ ResolveInfo ri = resolveInfos.get(i); ++ if (ri.activityInfo == null || ri.activityInfo.applicationInfo == null) { ++ continue; + } +- }); ++ ++ if (mContext.checkPermission(android.Manifest.permission.SHUTDOWN, 0, ++ ri.activityInfo.applicationInfo.uid) != PackageManager.PERMISSION_GRANTED) { ++ Slog.w(TAG, "Shutdown activity " + ri.activityInfo.getComponentName() ++ + " does not have permission " + android.Manifest.permission.SHUTDOWN); ++ continue; ++ } ++ ++ intent.setComponent(ri.activityInfo.getComponentName()); ++ break; ++ } + } ++ mContext.startActivityAsUser(intent, UserHandle.CURRENT); + } + + /** +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/base/0017-Calculate-how-much-memory-is-used-per-account.patch b/aosp_diff/caas/frameworks/base/0017-Calculate-how-much-memory-is-used-per-account.patch new file mode 100644 index 0000000..a4c1a59 --- /dev/null +++ b/aosp_diff/caas/frameworks/base/0017-Calculate-how-much-memory-is-used-per-account.patch @@ -0,0 +1,250 @@ +From 1675d0e5bd73fa6afbc322d034d72ba88fcb1e93 Mon Sep 17 00:00:00 2001 +From: Dmitry Dementyev +Date: Wed, 4 Jun 2025 12:34:28 -0700 +Subject: [PATCH 14/25] Calculate how much memory is used per account. + +Prevent storing new data in AccountManager DB if too much storage is used. + +Test: manual +Bug:273501008 +Flag: EXEMPT security fix. + +Change-Id: I88a0fef8e2e7bc232768bd5f7aa3f4bf87cb1c2c +(cherry picked from commit 6b8138ca5279b89d99884092cd3a5ca777f027be) +--- + .../accounts/AccountManagerService.java | 103 ++++++++++++++++++ + 1 file changed, 103 insertions(+) + +diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java +index 9d7318641715..c325c1bcf12f 100644 +--- a/services/core/java/com/android/server/accounts/AccountManagerService.java ++++ b/services/core/java/com/android/server/accounts/AccountManagerService.java +@@ -205,6 +205,8 @@ public class AccountManagerService + final MessageHandler mHandler; + + private static final int TIMEOUT_DELAY_MS = 1000 * 60 * 15; ++ private static final int MAXIMUM_PASSWORD_LENGTH = 1000 * 1000; ++ private static final int STORAGE_LIMIT_PER_USER = 30 * 1000 * 1000; + // Messages that can be sent on mHandler + private static final int MESSAGE_TIMED_OUT = 3; + private static final int MESSAGE_COPY_SHARED_ACCOUNT = 4; +@@ -255,6 +257,8 @@ public class AccountManagerService + private final TokenCache accountTokenCaches = new TokenCache(); + /** protected by the {@link #cacheLock} */ + private final Map> visibilityCache = new HashMap<>(); ++ /** protected by the {@link #cacheLock} */ ++ private final Map mCacheSizeForAccount = new HashMap<>(); + + /** protected by the {@link #mReceiversForType}, + * type -> (packageName -> number of active receivers) +@@ -1155,6 +1159,65 @@ public class AccountManagerService + validateAccountsInternal(accounts, true /* invalidateAuthenticatorCache */); + } + ++ private int computeEntrySize(@Nullable String key, @Nullable String value) { ++ int keySize = key != null ? key.length() : 1; ++ int valueSize = value != null ? value.length() : 1; ++ return keySize + valueSize + 20; ++ } ++ ++ /** ++ * Restricts write operation if account uses too much storage. ++ * Protected by the {@code cacheLock} ++ */ ++ private boolean shouldBlockDatabaseWrite(UserAccounts accounts, Account account, ++ @Nullable String key, @Nullable String value) { ++ int usedStorage = accounts.mCacheSizeForAccount.getOrDefault(account, 0); ++ // Estimation is not precise for updates to existing values. ++ usedStorage = usedStorage + computeEntrySize(key, value); ++ accounts.mCacheSizeForAccount.put(account, usedStorage); ++ if (usedStorage < STORAGE_LIMIT_PER_USER / 100) { ++ return false; // 100 is the upper bound for total number of accounts. ++ } ++ long numberOfAccounts = 0; ++ for (Account[] accountsPerType : accounts.accountCache.values()) { ++ if (accountsPerType != null) { ++ numberOfAccounts = numberOfAccounts + accountsPerType.length; ++ } ++ } ++ numberOfAccounts = numberOfAccounts != 0 ? numberOfAccounts : 1; // avoid division by zero. ++ if (usedStorage < STORAGE_LIMIT_PER_USER / numberOfAccounts) { ++ return false; ++ } ++ // Get more precise estimation of the used storage before blocking operation. ++ recomputeCacheSizeForAccountLocked(accounts, account); ++ usedStorage = accounts.mCacheSizeForAccount.getOrDefault(account, 0); ++ usedStorage = usedStorage + computeEntrySize(key, value); ++ accounts.mCacheSizeForAccount.put(account, usedStorage); ++ if (usedStorage < STORAGE_LIMIT_PER_USER / numberOfAccounts) { ++ return false; ++ } ++ Log.w(TAG, "Account of type=" + account.type + " uses too much storage: " + usedStorage); ++ return true; ++ } ++ ++ /** protected by the {@code cacheLock} */ ++ private void recomputeCacheSizeForAccountLocked(UserAccounts accounts, Account account) { ++ Map userDataForAccount = accounts.userDataCache.get(account); ++ Map authTokensForAccount = accounts.authTokenCache.get(account); ++ int usedStorage = 0; ++ if (userDataForAccount != null) { ++ for (Map.Entry entry : userDataForAccount.entrySet()) { ++ usedStorage = usedStorage + computeEntrySize(entry.getKey(), entry.getValue()); ++ } ++ } ++ if (authTokensForAccount != null) { ++ for (Map.Entry entry : authTokensForAccount.entrySet()) { ++ usedStorage = usedStorage + computeEntrySize(entry.getKey(), entry.getValue()); ++ } ++ } ++ accounts.mCacheSizeForAccount.put(account, usedStorage); ++ } ++ + /** + * Validate internal set of accounts against installed authenticators for + * given user. Clear cached authenticators before validating when requested. +@@ -1284,6 +1347,7 @@ public class AccountManagerService + accounts.authTokenCache.remove(account); + accounts.accountTokenCaches.remove(account); + accounts.visibilityCache.remove(account); ++ accounts.mCacheSizeForAccount.remove(account); + + for (Entry packageToVisibility : + packagesToVisibility.entrySet()) { +@@ -1914,6 +1978,10 @@ public class AccountManagerService + Log.w(TAG, "Account cannot be added - Name longer than 200 chars"); + return false; + } ++ if (password != null && password.length() > MAXIMUM_PASSWORD_LENGTH) { ++ Log.w(TAG, "Account cannot be added - password is too long"); ++ return false; ++ } + if (!isLocalUnlockedUser(accounts.userId)) { + Log.w(TAG, "Account " + account.toSafeString() + " cannot be added - user " + + accounts.userId + " is locked. callingUid=" + callingUid); +@@ -2289,6 +2357,7 @@ public class AccountManagerService + renamedAccount, + new AtomicReference<>(accountToRename.name)); + resultAccount = renamedAccount; ++ recomputeCacheSizeForAccountLocked(accounts, renamedAccount); + + int parentUserId = accounts.userId; + if (canHaveProfile(parentUserId)) { +@@ -2707,6 +2776,10 @@ public class AccountManagerService + } + cancelNotification(getSigninRequiredNotificationId(accounts, account), accounts); + synchronized (accounts.dbLock) { ++ boolean shouldBlockWrite = false; ++ synchronized (accounts.cacheLock) { ++ shouldBlockWrite = shouldBlockDatabaseWrite(accounts, account, type, authToken); ++ } + accounts.accountsDb.beginTransaction(); + boolean updateCache = false; + try { +@@ -2715,6 +2788,11 @@ public class AccountManagerService + return false; + } + accounts.accountsDb.deleteAuthtokensByAccountIdAndType(accountId, type); ++ if (authToken != null && shouldBlockWrite) { ++ Log.w(TAG, "Too much storage is used - block token update for accountType=" ++ + account.type); ++ return false; // fail silently. ++ } + if (accounts.accountsDb.insertAuthToken(accountId, type, authToken) >= 0) { + accounts.accountsDb.setTransactionSuccessful(); + updateCache = true; +@@ -2824,6 +2902,10 @@ public class AccountManagerService + if (account == null) { + return; + } ++ if (password != null && password.length() > MAXIMUM_PASSWORD_LENGTH) { ++ Log.w(TAG, "New password is too long for accountType=" + account.type); ++ return; ++ } + boolean isChanged = false; + synchronized (accounts.dbLock) { + synchronized (accounts.cacheLock) { +@@ -2942,6 +3024,14 @@ public class AccountManagerService + private void setUserdataInternal(UserAccounts accounts, Account account, String key, + String value, int callingUid) { + synchronized (accounts.dbLock) { ++ synchronized (accounts.cacheLock) { ++ if (value != null && shouldBlockDatabaseWrite(accounts, account, key, value)) { ++ Log.w(TAG, "Too much storage is used - block user data update for accountType=" ++ + account.type); ++ return; // fail silently. ++ } ++ } ++ + accounts.accountsDb.beginTransaction(); + try { + long accountId = accounts.accountsDb.findDeAccountId(account); +@@ -6261,6 +6351,7 @@ public class AccountManagerService + accounts.authTokenCache.remove(account); + accounts.previousNameCache.remove(account); + accounts.visibilityCache.remove(account); ++ accounts.mCacheSizeForAccount.remove(account); + + AccountManager.invalidateLocalAccountsDataCaches(); + } +@@ -6438,15 +6529,20 @@ public class AccountManagerService + protected void writeUserDataIntoCacheLocked(UserAccounts accounts, + Account account, String key, String value) { + Map userDataForAccount = accounts.userDataCache.get(account); ++ boolean updateCacheSize = false; + if (userDataForAccount == null) { + userDataForAccount = accounts.accountsDb.findUserExtrasForAccount(account); + accounts.userDataCache.put(account, userDataForAccount); ++ updateCacheSize = true; + } + if (value == null) { + userDataForAccount.remove(key); + } else { + userDataForAccount.put(key, value); + } ++ if (updateCacheSize) { ++ recomputeCacheSizeForAccountLocked(accounts, account); ++ } + } + + protected TokenCache.Value readCachedTokenInternal( +@@ -6465,15 +6561,20 @@ public class AccountManagerService + protected void writeAuthTokenIntoCacheLocked(UserAccounts accounts, + Account account, String key, String value) { + Map authTokensForAccount = accounts.authTokenCache.get(account); ++ boolean updateCacheSize = false; + if (authTokensForAccount == null) { + authTokensForAccount = accounts.accountsDb.findAuthTokensByAccount(account); + accounts.authTokenCache.put(account, authTokensForAccount); ++ updateCacheSize = true; + } + if (value == null) { + authTokensForAccount.remove(key); + } else { + authTokensForAccount.put(key, value); + } ++ if (updateCacheSize) { ++ recomputeCacheSizeForAccountLocked(accounts, account); ++ } + } + + protected String readAuthTokenInternal(UserAccounts accounts, Account account, +@@ -6493,6 +6594,7 @@ public class AccountManagerService + // need to populate the cache for this account + authTokensForAccount = accounts.accountsDb.findAuthTokensByAccount(account); + accounts.authTokenCache.put(account, authTokensForAccount); ++ recomputeCacheSizeForAccountLocked(accounts, account); + } + return authTokensForAccount.get(authTokenType); + } +@@ -6514,6 +6616,7 @@ public class AccountManagerService + // need to populate the cache for this account + userDataForAccount = accounts.accountsDb.findUserExtrasForAccount(account); + accounts.userDataCache.put(account, userDataForAccount); ++ recomputeCacheSizeForAccountLocked(accounts, account); + } + } + } +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/base/0018-Don-t-allow-SdkSandbox-to-bypass-systemUid-check.patch b/aosp_diff/caas/frameworks/base/0018-Don-t-allow-SdkSandbox-to-bypass-systemUid-check.patch new file mode 100644 index 0000000..1d2538b --- /dev/null +++ b/aosp_diff/caas/frameworks/base/0018-Don-t-allow-SdkSandbox-to-bypass-systemUid-check.patch @@ -0,0 +1,33 @@ +From 77570b9f19366a459dea5d3f91bf1255ffac9217 Mon Sep 17 00:00:00 2001 +From: Aseem Kumar +Date: Wed, 11 Jun 2025 18:41:20 -0700 +Subject: [PATCH 15/25] Don't allow SdkSandbox to bypass systemUid check. + +Test: atest + +Flag: EXEMPT security fix + +Bug: 397438392 +Change-Id: I1003ac2a795c869aaeb292692b2681c7a5c8d12e +(cherry picked from commit d400709160c8374d83a15dc7623b11434c08c4c6) +--- + .../com/android/server/accounts/AccountManagerService.java | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java +index c325c1bcf12f..4477bf7660b5 100644 +--- a/services/core/java/com/android/server/accounts/AccountManagerService.java ++++ b/services/core/java/com/android/server/accounts/AccountManagerService.java +@@ -6128,6 +6128,9 @@ public class AccountManagerService + } + + private boolean isSystemUid(int callingUid) { ++ if (Process.isSdkSandboxUid(callingUid)) { ++ return false; ++ } + String[] packages = null; + final long ident = Binder.clearCallingIdentity(); + try { +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/base/0019-Prevent-root-from-getting-unverified-attributions-fr.patch b/aosp_diff/caas/frameworks/base/0019-Prevent-root-from-getting-unverified-attributions-fr.patch new file mode 100644 index 0000000..7ba861e --- /dev/null +++ b/aosp_diff/caas/frameworks/base/0019-Prevent-root-from-getting-unverified-attributions-fr.patch @@ -0,0 +1,136 @@ +From ef5a45a2efbcdd482e489eb11278e85bd691657a Mon Sep 17 00:00:00 2001 +From: Nate Myren +Date: Thu, 26 Jun 2025 14:33:51 -0700 +Subject: [PATCH 16/25] Prevent root from getting unverified attributions from + non system apps + +Similar to shell, system server, and other packages, the root UID +bypasses attribution tag registration requirements. This can be +exploited by a malicious proxy app. + +Also fixes a bug which caused an unverified proxy app's attribution tag +to be erroneously called "valid" if "finishProxyOp" was called for a +non-system proxy app, and one of the special proxied apps + +Bug: 416491779 +Test: atest AppOpsMemoryUsageTest +Flag: EXEMPT see bug +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:ab99cde450cf900767a641ddcf71f4a42e771334) +Merged-In: I9b44465554e10b803bc9b4ab76130aaf9933f605 +Change-Id: I9b44465554e10b803bc9b4ab76130aaf9933f605 +--- + .../android/server/appop/AppOpsService.java | 55 ++++++++++++++----- + 1 file changed, 41 insertions(+), 14 deletions(-) + +diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java +index 250ae36c3285..101271ad86b4 100644 +--- a/services/core/java/com/android/server/appop/AppOpsService.java ++++ b/services/core/java/com/android/server/appop/AppOpsService.java +@@ -4248,30 +4248,40 @@ public class AppOpsService extends IAppOpsService.Stub { + return null; + } + +- finishOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName, +- proxiedAttributionTag, proxyVirtualDeviceId); ++ finishOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName, ++ proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag, ++ proxyVirtualDeviceId); + + return null; + } ++ private void finishOperationUnchecked(IBinder clientId, int code, int uid, ++ String packageName, String attributionTag, int virtualDeviceId) { ++ finishOperationUnchecked(clientId, code, -1, null, uid, packageName, attributionTag, ++ virtualDeviceId); ++ } + +- private void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName, +- String attributionTag, int virtualDeviceId) { ++ private void finishOperationUnchecked(IBinder clientId, int code, int proxyUid, ++ String proxyPackageName, int proxiedUid, ++ String proxiedPackageName, String attributionTag, ++ int virtualDeviceId) { + PackageVerificationResult pvr; + try { +- pvr = verifyAndGetBypass(uid, packageName, attributionTag); ++ pvr = verifyAndGetBypass(proxiedUid, proxiedPackageName, attributionTag, ++ proxyPackageName); + if (!pvr.isAttributionTagValid) { + attributionTag = null; + } + } catch (SecurityException e) { +- logVerifyAndGetBypassFailure(uid, e, "finishOperation"); ++ logVerifyAndGetBypassFailure(proxiedUid, e, "finishOperation"); + return; + } + + synchronized (this) { +- Op op = getOpLocked(code, uid, packageName, attributionTag, pvr.isAttributionTagValid, +- pvr.bypass, /* edit */ true); ++ Op op = getOpLocked(code, proxiedUid, proxiedPackageName, attributionTag, ++ pvr.isAttributionTagValid, pvr.bypass, /* edit */ true); + if (op == null) { +- Slog.e(TAG, "Operation not found: uid=" + uid + " pkg=" + packageName + "(" ++ Slog.e(TAG, "Operation not found: uid=" + proxiedUid + " pkg=" + proxiedPackageName ++ + "(" + + attributionTag + ") op=" + AppOpsManager.opToName(code)); + return; + } +@@ -4279,7 +4289,8 @@ public class AppOpsService extends IAppOpsService.Stub { + op.mDeviceAttributedOps.getOrDefault(getPersistentId(virtualDeviceId), + new ArrayMap<>()).get(attributionTag); + if (attributedOp == null) { +- Slog.e(TAG, "Attribution not found: uid=" + uid + " pkg=" + packageName + "(" ++ Slog.e(TAG, "Attribution not found: uid=" + proxiedUid ++ + " pkg=" + proxiedPackageName + "(" + + attributionTag + ") op=" + AppOpsManager.opToName(code)); + return; + } +@@ -4287,7 +4298,8 @@ public class AppOpsService extends IAppOpsService.Stub { + if (attributedOp.isRunning() || attributedOp.isPaused()) { + attributedOp.finished(clientId); + } else { +- Slog.e(TAG, "Operation not started: uid=" + uid + " pkg=" + packageName + "(" ++ Slog.e(TAG, "Operation not started: uid=" + proxiedUid ++ + " pkg=" + proxiedPackageName + "(" + + attributionTag + ") op=" + AppOpsManager.opToName(code)); + } + } +@@ -4760,9 +4772,13 @@ public class AppOpsService extends IAppOpsService.Stub { + @Nullable String attributionTag, @Nullable String proxyPackageName, + boolean suppressErrorLogs) { + if (uid == Process.ROOT_UID) { +- // For backwards compatibility, don't check package name for root UID. +- return new PackageVerificationResult(null, +- /* isAttributionTagValid */ true); ++ // 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. ++ // We only allow bypassing the attribution tag verification if the proxy is a ++ // 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, uid)); + } + if (Process.isSdkSandboxUid(uid)) { + // SDK sandbox processes run in their own UID range, but their associated +@@ -4886,6 +4902,17 @@ public class AppOpsService extends IAppOpsService.Stub { + return new PackageVerificationResult(bypass, isAttributionTagValid); + } + ++ private boolean isPackageNullOrSystem(String packageName, int uid) { ++ if (packageName == null) { ++ return true; ++ } ++ int appId = UserHandle.getAppId(uid); ++ if (appId > 0 && appId < Process.FIRST_APPLICATION_UID) { ++ return true; ++ } ++ return mPackageManagerInternal.isSystemPackage(packageName); ++ } ++ + private boolean isAttributionInPackage(@Nullable AndroidPackage pkg, + @Nullable String attributionTag) { + if (pkg == null) { +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/base/0020-Disallow-PINNED-in-setLaunchWindowingMode.patch b/aosp_diff/caas/frameworks/base/0020-Disallow-PINNED-in-setLaunchWindowingMode.patch new file mode 100644 index 0000000..2f902c8 --- /dev/null +++ b/aosp_diff/caas/frameworks/base/0020-Disallow-PINNED-in-setLaunchWindowingMode.patch @@ -0,0 +1,124 @@ +From 6ff8286bc1f18381e676fdb02a7d40888ec1a90f Mon Sep 17 00:00:00 2001 +From: Hongwei Wang +Date: Wed, 25 Jun 2025 14:52:39 -0700 +Subject: [PATCH 17/25] Disallow PINNED in setLaunchWindowingMode + +- Throw security exception if setLaunchWindowingMode is called with + WINDOWING_MODE_PINNED +- Deprecated also test cases that are irrelevant after this change + +Flag: EXEMPT security fix +Bug: 388029380 +Test: atest PinnedStackTests \ + SafeActivityOptionsTest \ + TaskLaunchParamsModifierTests +Change-Id: I9d37d41af3d86f785fd5d85503b544552b39cac4 +--- + .../server/wm/SafeActivityOptions.java | 12 +++++++ + .../server/wm/SafeActivityOptionsTest.java | 5 +++ + .../wm/TaskLaunchParamsModifierTests.java | 36 ------------------- + 3 files changed, 17 insertions(+), 36 deletions(-) + +diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java +index 88e534351e2e..73c1357c4a24 100644 +--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java ++++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java +@@ -24,6 +24,7 @@ import static android.Manifest.permission.STATUS_BAR_SERVICE; + import static android.app.ActivityTaskManager.INVALID_TASK_ID; + import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; + import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; ++import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; + import static android.app.WindowConfiguration.activityTypeToString; + import static android.content.pm.PackageManager.PERMISSION_DENIED; + import static android.content.pm.PackageManager.PERMISSION_GRANTED; +@@ -372,6 +373,17 @@ public class SafeActivityOptions { + throw new SecurityException(msg); + } + ++ // setLaunchWindowingMode(PINNED) is not allowed, use ActivityOptions#makeLaunchIntoPip ++ // instead which is a public API. ++ if (options.getLaunchWindowingMode() == WINDOWING_MODE_PINNED) { ++ final String msg = "Permission Denial: starting " + getIntentString(intent) ++ + " from " + callerApp + " (pid=" + callingPid ++ + ", uid=" + callingUid + ") with" ++ + " setLaunchWindowingMode=PINNED"; ++ Slog.w(TAG, msg); ++ throw new SecurityException(msg); ++ } ++ + final int activityType = options.getLaunchActivityType(); + if (activityType != ACTIVITY_TYPE_UNDEFINED + && !isSystemOrSystemUI(callingPid, callingUid)) { +diff --git a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java +index a92fe3afbd78..76be76f85c39 100644 +--- a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java ++++ b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java +@@ -17,6 +17,7 @@ + package com.android.server.wm; + + import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; ++import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; + import static android.content.pm.PackageManager.PERMISSION_DENIED; + import static android.view.Display.DEFAULT_DISPLAY; + +@@ -197,6 +198,10 @@ public class SafeActivityOptionsTest { + activityOptions.setRemoteTransition(remoteTransition); + verifySecureExceptionThrown(activityOptions, taskSupervisor); + ++ activityOptions = ActivityOptions.makeBasic(); ++ activityOptions.setLaunchWindowingMode(WINDOWING_MODE_PINNED); ++ verifySecureExceptionThrown(activityOptions, taskSupervisor); ++ + verifySecureExceptionThrown(activityOptions, taskSupervisor, + mock(TaskDisplayArea.class)); + } finally { +diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java +index 4568c77204a5..24ca929dd2cd 100644 +--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java ++++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java +@@ -714,42 +714,6 @@ public class TaskLaunchParamsModifierTests extends + WINDOWING_MODE_FULLSCREEN); + } + +- +- @Test +- public void testKeepsPictureInPictureLaunchModeInOptions() { +- final TestDisplayContent freeformDisplay = createNewDisplayContent( +- WINDOWING_MODE_FREEFORM); +- +- final ActivityOptions options = ActivityOptions.makeBasic(); +- options.setLaunchWindowingMode(WINDOWING_MODE_PINNED); +- +- mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea(); +- +- assertEquals(RESULT_CONTINUE, +- new CalculateRequestBuilder().setOptions(options).calculate()); +- +- assertEquivalentWindowingMode(WINDOWING_MODE_PINNED, mResult.mWindowingMode, +- WINDOWING_MODE_FREEFORM); +- } +- +- @Test +- public void testKeepsPictureInPictureLaunchModeWithBoundsInOptions() { +- final TestDisplayContent freeformDisplay = createNewDisplayContent( +- WINDOWING_MODE_FREEFORM); +- +- final ActivityOptions options = ActivityOptions.makeBasic(); +- options.setLaunchWindowingMode(WINDOWING_MODE_PINNED); +- options.setLaunchBounds(new Rect(0, 0, 100, 100)); +- +- mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea(); +- +- assertEquals(RESULT_CONTINUE, +- new CalculateRequestBuilder().setOptions(options).calculate()); +- +- assertEquivalentWindowingMode(WINDOWING_MODE_PINNED, mResult.mWindowingMode, +- WINDOWING_MODE_FREEFORM); +- } +- + @Test + public void testKeepsFullscreenLaunchModeInOptionsOnNonFreeformDisplay() { + final ActivityOptions options = ActivityOptions.makeBasic(); +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/base/0021-Preflight-skip-datasource-validation.patch b/aosp_diff/caas/frameworks/base/0021-Preflight-skip-datasource-validation.patch new file mode 100644 index 0000000..abc5aa0 --- /dev/null +++ b/aosp_diff/caas/frameworks/base/0021-Preflight-skip-datasource-validation.patch @@ -0,0 +1,57 @@ +From 792910fd91498854839d0c4d368261f1336db310 Mon Sep 17 00:00:00 2001 +From: Atneya Nair +Date: Tue, 1 Jul 2025 10:16:33 -0700 +Subject: [PATCH 18/25] Preflight skip datasource validation + +The forDataDelivery logic always skips checking the first attribution in +the chain from datasources, either via singleReceiverFromDataSource, or +internal in startProxyOp, since skipProxy=true is passed for non-trivial +chains. + +Make the preflight check consistent with this behavior, by also skipping +a checkOp on the first entry in this case. This avoids cases where +the preflight fails when the delivery would succeed, which should never +happen. This is implicitly relied on by audioserver, as it happens to +fail checkOp due to not having a valid Uid/PackageState. + +Test: manual: start and stop recording with toggle restriction +Test: atest CtsMediaAudioPermissionTestCases +Test: atest RuntimePermissionsAppOpTrackingTest +Test: atest SensorPrivacyMicrophoneTest +Bug: 293603271 +Bug: 325912429 +Flag: EXEMPT bugfix +Change-Id: I509e7f8da501f5e32d336adb412662e078eab500 +--- + .../pm/permission/PermissionManagerService.java | 15 +++++++++++++-- + 1 file changed, 13 insertions(+), 2 deletions(-) + +diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +index 05bc69a9f1f0..fd8767714c14 100644 +--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java ++++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +@@ -1528,8 +1528,19 @@ public class PermissionManagerService extends IPermissionManager.Stub { + } + final AttributionSource resolvedAttributionSource = + accessorSource.withPackageName(resolvedAccessorPackageName); +- final int opMode = appOpsManager.unsafeCheckOpRawNoThrow(op, +- resolvedAttributionSource); ++ // Avoid checking the first attr in the chain in some cases for consistency with ++ // checks for data delivery. ++ // In particular, for chains of 2 or more, when skipProxyOperation is true, the ++ // for data delivery implementation does not actually check the first link in the ++ // chain. If the attribution is just a singleReceiverFromDatasource, this ++ // exemption does not apply, since it does not go through proxyOp flow, and the top ++ // of the chain is actually removed above. ++ // Skipping the check avoids situations where preflight checks fail since the data ++ // source itself does not have the op (e.g. audioserver). ++ final int opMode = (skipProxyOperation && !singleReceiverFromDatasource) ? ++ AppOpsManager.MODE_ALLOWED : ++ appOpsManager.unsafeCheckOpRawNoThrow(op, resolvedAttributionSource); ++ + final AttributionSource next = accessorSource.getNext(); + if (!selfAccess && opMode == AppOpsManager.MODE_ALLOWED && next != null) { + final String resolvedNextPackageName = resolvePackageName(context, next); +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/base/0022-Ensuring-valid-packageName-when-granting-slice-permi.patch b/aosp_diff/caas/frameworks/base/0022-Ensuring-valid-packageName-when-granting-slice-permi.patch new file mode 100644 index 0000000..878024a --- /dev/null +++ b/aosp_diff/caas/frameworks/base/0022-Ensuring-valid-packageName-when-granting-slice-permi.patch @@ -0,0 +1,61 @@ +From 94c09cee6578dfdca6c7b8d6007bd807e9624b3c Mon Sep 17 00:00:00 2001 +From: Sunny Goyal +Date: Mon, 16 Jun 2025 21:50:11 +0000 +Subject: [PATCH 19/25] Ensuring valid packageName when granting slice + permission + +Bug: 401256328 +Test: atest SliceManagerTest +Flag: EXEMPT bugfix +Change-Id: I8e28097c0570922d9fb9ec9588b45bf4361a5020 +--- + .../server/slice/SlicePermissionManager.java | 14 ++++++++++++++ + 1 file changed, 14 insertions(+) + +diff --git a/services/core/java/com/android/server/slice/SlicePermissionManager.java b/services/core/java/com/android/server/slice/SlicePermissionManager.java +index 343d2e353abb..27578a01959f 100644 +--- a/services/core/java/com/android/server/slice/SlicePermissionManager.java ++++ b/services/core/java/com/android/server/slice/SlicePermissionManager.java +@@ -16,11 +16,13 @@ package com.android.server.slice; + + import android.content.ContentProvider; + import android.content.Context; ++import android.content.pm.parsing.FrameworkParsingPackageUtils; + import android.net.Uri; + import android.os.Environment; + import android.os.Handler; + import android.os.Looper; + import android.os.Message; ++import android.text.TextUtils; + import android.text.format.DateUtils; + import android.util.ArrayMap; + import android.util.ArraySet; +@@ -410,6 +412,7 @@ public class SlicePermissionManager implements DirtyTracker { + public PkgUser(String pkg, int userId) { + mPkg = pkg; + mUserId = userId; ++ enforceValidPackage(); + } + + public PkgUser(String pkgUserStr) throws IllegalArgumentException { +@@ -420,6 +423,17 @@ public class SlicePermissionManager implements DirtyTracker { + } catch (Exception e) { + throw new IllegalArgumentException(e); + } ++ enforceValidPackage(); ++ } ++ ++ private void enforceValidPackage() { ++ String error = FrameworkParsingPackageUtils.validateName( ++ mPkg, ++ false /* requireSeparator */, ++ true /* requireFilename */); ++ if (!TextUtils.isEmpty(error)) { ++ throw new IllegalArgumentException((error)); ++ } + } + + public String getPkg() { +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/base/0023-DevicePolicyManager-ignore-invalid-proxy-settings.patch b/aosp_diff/caas/frameworks/base/0023-DevicePolicyManager-ignore-invalid-proxy-settings.patch new file mode 100644 index 0000000..84026f7 --- /dev/null +++ b/aosp_diff/caas/frameworks/base/0023-DevicePolicyManager-ignore-invalid-proxy-settings.patch @@ -0,0 +1,47 @@ +From 3f878a3d6681f4289afad47da9c1387e293d5b56 Mon Sep 17 00:00:00 2001 +From: Rubin Xu +Date: Tue, 3 Jun 2025 15:14:01 +0100 +Subject: [PATCH 20/25] DevicePolicyManager: ignore invalid proxy settings + +Ignore exceptions thrown when invalid proxy settings are being +applied, in order to avoid breaking callers performing potentially +security-sensitive operations (like removing an ActiveAdmin) + +Bug: 365975561 +Test: manual +Flag: EXEMPT bugfix +(cherry picked from commit 2ea32931ef9a06a762b8cf21584f156140672472) +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:ca2faa1036823e23907f2836d4893a1daf11202c) +Merged-In: I496b9ce7a0bbb1e6c245682be57b7357bb432d42 +Change-Id: I496b9ce7a0bbb1e6c245682be57b7357bb432d42 +--- + .../devicepolicy/DevicePolicyManagerService.java | 13 +++++++++---- + 1 file changed, 9 insertions(+), 4 deletions(-) + +diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +index 2627895b8c63..1a66c0bc8d91 100644 +--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java ++++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +@@ -8688,10 +8688,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + Slogf.e(LOG_TAG, "Invalid proxy properties, ignoring: " + proxyProperties.toString()); + return; + } +- mInjector.settingsGlobalPutString(Global.GLOBAL_HTTP_PROXY_HOST, data[0]); +- mInjector.settingsGlobalPutInt(Global.GLOBAL_HTTP_PROXY_PORT, proxyPort); +- mInjector.settingsGlobalPutString(Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST, +- exclusionList); ++ try { ++ mInjector.settingsGlobalPutString(Global.GLOBAL_HTTP_PROXY_HOST, data[0]); ++ mInjector.settingsGlobalPutInt(Global.GLOBAL_HTTP_PROXY_PORT, proxyPort); ++ mInjector.settingsGlobalPutString(Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST, ++ exclusionList); ++ } catch (Exception e) { ++ //Ignore any potential errors from SettingsProvider, b/365975561 ++ Slogf.w(LOG_TAG, "Fail to save proxy", e); ++ } + } + + /** +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/base/0024-Check-DPC-package-validity-during-package-updates.patch b/aosp_diff/caas/frameworks/base/0024-Check-DPC-package-validity-during-package-updates.patch new file mode 100644 index 0000000..37cbeca --- /dev/null +++ b/aosp_diff/caas/frameworks/base/0024-Check-DPC-package-validity-during-package-updates.patch @@ -0,0 +1,94 @@ +From 6d16f6489a079cabee0dbdb95ec116f93334873b Mon Sep 17 00:00:00 2001 +From: Rubin Xu +Date: Fri, 9 May 2025 16:36:34 +0100 +Subject: [PATCH 21/25] Check DPC package validity during package updates + +Bug: 384514657 +Bug: 414603411 +Test: manual +Flag: EXEMPT bugfix +Merged-In: I0c629c138059a71786e82b4653de9cef7e951aad +Merged-In: I1743c111f8e8b5f4c1f878a61b88b8f1ed6b86a1 +Change-Id: I1743c111f8e8b5f4c1f878a61b88b8f1ed6b86a1 +--- + .../android/app/admin/DeviceAdminInfo.java | 2 + + .../DevicePolicyManagerService.java | 41 +++++++++++++++---- + 2 files changed, 34 insertions(+), 9 deletions(-) + +diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java +index cb2b8adae0f9..d76979ae76a6 100644 +--- a/core/java/android/app/admin/DeviceAdminInfo.java ++++ b/core/java/android/app/admin/DeviceAdminInfo.java +@@ -409,6 +409,8 @@ public final class DeviceAdminInfo implements Parcelable { + } catch (NameNotFoundException e) { + throw new XmlPullParserException( + "Unable to create context for: " + mActivityInfo.packageName); ++ } catch (OutOfMemoryError e) { ++ throw new XmlPullParserException("Out of memory when parsing", null, e); + } finally { + if (parser != null) parser.close(); + } +diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +index 1a66c0bc8d91..537f78bb0e54 100644 +--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java ++++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +@@ -1466,6 +1466,33 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + } + } + ++ /** ++ * Check if the package hosting the given ActiveAdmin is still installed and well-formed. ++ */ ++ @GuardedBy("getLockObject()") ++ private boolean isActiveAdminPackageValid(ActiveAdmin admin) throws RemoteException { ++ final String adminPackage = admin.info.getPackageName(); ++ int userHandle = admin.getUserHandle().getIdentifier(); ++ if (mIPackageManager.getPackageInfo(adminPackage, 0, userHandle) == null) { ++ Slogf.e(LOG_TAG, adminPackage + " no longer installed"); ++ return false; ++ } ++ ActivityInfo ai = mIPackageManager.getReceiverInfo(admin.info.getComponent(), ++ GET_META_DATA | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, ++ userHandle); ++ if (ai == null) { ++ Slogf.e(LOG_TAG, adminPackage + " no longer has the receiver"); ++ return false; ++ } ++ try { ++ new DeviceAdminInfo(mContext, ai); ++ } catch (Exception e) { ++ Slogf.e(LOG_TAG, adminPackage + " contains malformed metadata", e); ++ return false; ++ } ++ return true; ++ } ++ + private void handlePackagesChanged(@Nullable String packageName, int userHandle) { + boolean removedAdmin = false; + String removedAdminPackage = null; +@@ -1479,17 +1506,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + ActiveAdmin aa = policy.mAdminList.get(i); + try { + // If we're checking all packages or if the specific one we're checking matches, +- // then check if the package and receiver still exist. ++ // then check if the package is still valid. + final String adminPackage = aa.info.getPackageName(); + if (packageName == null || packageName.equals(adminPackage)) { +- if (mIPackageManager.getPackageInfo(adminPackage, 0, userHandle) == null +- || mIPackageManager.getReceiverInfo(aa.info.getComponent(), +- MATCH_DIRECT_BOOT_AWARE +- | MATCH_DIRECT_BOOT_UNAWARE, +- userHandle) == null) { +- Slogf.e(LOG_TAG, String.format( +- "Admin package %s not found for user %d, removing active admin", +- packageName, userHandle)); ++ if (!isActiveAdminPackageValid(aa)) { ++ Slogf.e(LOG_TAG, "Admin package %s not found or invalid for user %d," ++ + " removing active admin", ++ packageName, userHandle); + removedAdmin = true; + removedAdminPackage = adminPackage; + policy.mAdminList.remove(i); +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/base/0025-Trim-oversized-strings-in-setId-and-setConversationI.patch b/aosp_diff/caas/frameworks/base/0025-Trim-oversized-strings-in-setId-and-setConversationI.patch new file mode 100644 index 0000000..cb7dd16 --- /dev/null +++ b/aosp_diff/caas/frameworks/base/0025-Trim-oversized-strings-in-setId-and-setConversationI.patch @@ -0,0 +1,84 @@ +From c9dfc02c1bcdcec7ba978b652bd07e0d3adcc4a3 Mon Sep 17 00:00:00 2001 +From: Alisa Hung +Date: Fri, 13 Jun 2025 05:52:18 -0700 +Subject: [PATCH 22/25] Trim oversized strings in setId and setConversationId + +Flag: EXEMPT bugfix +Test: android.app.NotificationChannelTest +Test: cts NotificationChannelTest +Bug: 419014146 +Change-Id: Id721d3123ee8d38753f550fe57ba0f5d15d743ac +(cherry picked from commit a9a1b4365b8d6def5b36fed7dd9caf2389fe0a51) +--- + .../java/android/app/NotificationChannel.java | 6 ++-- + .../android/app/NotificationChannelTest.java | 30 +++++++++++++++++++ + 2 files changed, 33 insertions(+), 3 deletions(-) + +diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java +index 73d26b834497..2ca6e97a8399 100644 +--- a/core/java/android/app/NotificationChannel.java ++++ b/core/java/android/app/NotificationChannel.java +@@ -621,7 +621,7 @@ public final class NotificationChannel implements Parcelable { + * @hide + */ + public void setId(String id) { +- mId = id; ++ mId = getTrimmedString(id); + } + + // Modifiable by apps on channel creation. +@@ -865,8 +865,8 @@ public final class NotificationChannel implements Parcelable { + */ + public void setConversationId(@NonNull String parentChannelId, + @NonNull String conversationId) { +- mParentId = parentChannelId; +- mConversationId = conversationId; ++ mParentId = getTrimmedString(parentChannelId); ++ mConversationId = getTrimmedString(conversationId); + } + + /** +diff --git a/core/tests/coretests/src/android/app/NotificationChannelTest.java b/core/tests/coretests/src/android/app/NotificationChannelTest.java +index e4b54071e892..cfe9b7afef54 100644 +--- a/core/tests/coretests/src/android/app/NotificationChannelTest.java ++++ b/core/tests/coretests/src/android/app/NotificationChannelTest.java +@@ -235,6 +235,36 @@ public class NotificationChannelTest { + fromParcel.getConversationId().length()); + } + ++ @Test ++ public void testSetId_longStringIsTrimmed() { ++ NotificationChannel channel = ++ new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT); ++ String longId = Strings.repeat("A", NotificationChannel.MAX_TEXT_LENGTH + 10); ++ ++ channel.setId(longId); ++ ++ assertThat(channel.getId()).hasLength(NotificationChannel.MAX_TEXT_LENGTH); ++ assertThat(channel.getId()) ++ .isEqualTo(longId.substring(0, NotificationChannel.MAX_TEXT_LENGTH)); ++ } ++ ++ @Test ++ public void testSetConversationId_longStringsAreTrimmed() { ++ NotificationChannel channel = ++ new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT); ++ String longParentId = Strings.repeat("P", NotificationChannel.MAX_TEXT_LENGTH + 10); ++ String longConversationId = Strings.repeat("C", NotificationChannel.MAX_TEXT_LENGTH + 10); ++ ++ channel.setConversationId(longParentId, longConversationId); ++ ++ assertThat(channel.getParentChannelId()).hasLength(NotificationChannel.MAX_TEXT_LENGTH); ++ assertThat(channel.getParentChannelId()) ++ .isEqualTo(longParentId.substring(0, NotificationChannel.MAX_TEXT_LENGTH)); ++ assertThat(channel.getConversationId()).hasLength(NotificationChannel.MAX_TEXT_LENGTH); ++ assertThat(channel.getConversationId()) ++ .isEqualTo(longConversationId.substring(0, NotificationChannel.MAX_TEXT_LENGTH)); ++ } ++ + @Test + public void testLongAlertFields() { + NotificationChannel channel = new NotificationChannel("id", "name", 3); +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/base/0026-cleanup-Fix-permission-protection-of-setObservedMoti.patch b/aosp_diff/caas/frameworks/base/0026-cleanup-Fix-permission-protection-of-setObservedMoti.patch new file mode 100644 index 0000000..de85a9d --- /dev/null +++ b/aosp_diff/caas/frameworks/base/0026-cleanup-Fix-permission-protection-of-setObservedMoti.patch @@ -0,0 +1,68 @@ +From ec0d0bfdaba9f70b23fb9784a5e38b40872cbe52 Mon Sep 17 00:00:00 2001 +From: Daniel Norman +Date: Wed, 21 May 2025 01:49:29 +0000 +Subject: [PATCH 23/25] cleanup: Fix permission protection of + setObservedMotionEventSources + +The previous permission protection was done inside a Binder clear +identity call, meaning that it used the permissions of system_server +instead of the permissions of the calling AccessibilityService. + +Bug: 419110583 +Test: atest AccessibilityServiceInfoTest +Flag: EXEMPT security bugfix +Change-Id: If64838388fa31bdc9abb0896d4011bfef8501a7c +--- + ...bstractAccessibilityServiceConnection.java | 22 ++++++++----------- + 1 file changed, 9 insertions(+), 13 deletions(-) + +diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +index 3441d94facda..926bbe9c78ad 100644 +--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java ++++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +@@ -16,6 +16,7 @@ + + package com.android.server.accessibility; + ++import static android.Manifest.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING; + import static android.accessibilityservice.AccessibilityService.ACCESSIBILITY_TAKE_SCREENSHOT_REQUEST_INTERVAL_TIMES_MS; + import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE; + import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER; +@@ -416,19 +417,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ + mNotificationTimeout = info.notificationTimeout; + mIsDefault = (info.flags & DEFAULT) != 0; + mGenericMotionEventSources = info.getMotionEventSources(); +- if (android.view.accessibility.Flags.motionEventObserving()) { +- if (mContext.checkCallingOrSelfPermission( +- android.Manifest.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING) +- == PackageManager.PERMISSION_GRANTED) { +- mObservedMotionEventSources = info.getObservedMotionEventSources(); +- } else { +- Slog.e( +- LOG_TAG, +- "Observing motion events requires" +- + " android.Manifest.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING."); +- mObservedMotionEventSources = 0; +- } +- } ++ mObservedMotionEventSources = info.getObservedMotionEventSources(); + + if (supportsFlagForNotImportantViews(info)) { + if ((info.flags & AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0) { +@@ -527,6 +516,13 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ + throw new IllegalStateException( + "Cannot update service info: size is larger than safe parcelable limits."); + } ++ if (info.getObservedMotionEventSources() != 0 ++ && mContext.checkCallingPermission(ACCESSIBILITY_MOTION_EVENT_OBSERVING) ++ != PackageManager.PERMISSION_GRANTED) { ++ Slog.e(LOG_TAG, "Observing motion events requires permission " ++ + ACCESSIBILITY_MOTION_EVENT_OBSERVING); ++ info.setObservedMotionEventSources(0); ++ } + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/base/0027-cleanup-Fix-permission-protection-of-setObservedMoti.patch b/aosp_diff/caas/frameworks/base/0027-cleanup-Fix-permission-protection-of-setObservedMoti.patch new file mode 100644 index 0000000..5e31de5 --- /dev/null +++ b/aosp_diff/caas/frameworks/base/0027-cleanup-Fix-permission-protection-of-setObservedMoti.patch @@ -0,0 +1,34 @@ +From e640abeb94d231a46efcc5dd4aa2af80bafa93be Mon Sep 17 00:00:00 2001 +From: Benjamin Gordon +Date: Wed, 2 Jul 2025 16:28:57 -0600 +Subject: [PATCH 24/25] PrintSpooler: Require empty output for PDF + +When saving to PDF, request a truncated output stream in case the user +selects to overwrite an existing file. + +Bug: 423815728 +Test: Overwrite existing PDF +Flag: EXEMPT bugfix +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:d65493c65a94415208d4be2454181f214fb2ed03) +Merged-In: Id598cd1a9d2456566f3905432eb7b5ffeab9d33e +Change-Id: Id598cd1a9d2456566f3905432eb7b5ffeab9d33e +--- + .../src/com/android/printspooler/model/RemotePrintDocument.java | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java +index b48c55ddfef0..0a9f0f0d17f1 100644 +--- a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java ++++ b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java +@@ -364,7 +364,7 @@ public final class RemotePrintDocument { + try { + file = mDocumentInfo.fileProvider.acquireFile(null); + in = new FileInputStream(file); +- out = contentResolver.openOutputStream(uri); ++ out = contentResolver.openOutputStream(uri, "wt"); + final byte[] buffer = new byte[8192]; + while (true) { + final int readByteCount = in.read(buffer); +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/base/0028-Implement-onNullBinding-in-autofill-service-connecti.patch b/aosp_diff/caas/frameworks/base/0028-Implement-onNullBinding-in-autofill-service-connecti.patch new file mode 100644 index 0000000..1fc4f5c --- /dev/null +++ b/aosp_diff/caas/frameworks/base/0028-Implement-onNullBinding-in-autofill-service-connecti.patch @@ -0,0 +1,40 @@ +From e5036e6261d2aa3eb9127b6d4cb727d54d955e48 Mon Sep 17 00:00:00 2001 +From: Haoran Zhang +Date: Thu, 12 Jun 2025 09:33:17 -0700 +Subject: [PATCH 25/25] Implement onNullBinding() in autofill service + connection + +With this fix, an autofill service which returns null in onBind() will no longer stay bound by system_server and will no longer be able to launch activities from the background. + +Flag: EXEMPT security bugfix +Test: sts-tradefed run sts-dynamic-develop -m StsHostTestCases -t android.security.sts.Bug_401545800 +Bug:b/401545800 +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:d883efa912c0a39c46437074576cffb7078ac455) +Merged-In: Ieb3e63fe2ac1f440be3d164730ef3110209ec1a6 +Change-Id: Ieb3e63fe2ac1f440be3d164730ef3110209ec1a6 +--- + .../com/android/server/autofill/RemoteFillService.java | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java +index f1e888400d32..8cb56a2817c2 100644 +--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java ++++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java +@@ -117,6 +117,14 @@ final class RemoteFillService extends ServiceConnector.Impl { + } + } + ++ @Override // from ServiceConnection ++ public void onNullBinding(@NonNull ComponentName name) { ++ if (sVerbose) { ++ Slog.v(TAG, "onNullBinding"); ++ } ++ unbind(); ++ } ++ + private void dispatchCancellationSignal(@Nullable ICancellationSignal signal) { + if (signal == null) { + return; +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/native/0003-update-vulkanhardware-version.patch b/aosp_diff/caas/frameworks/native/0002-update-vulkanhardware-version.patch similarity index 100% rename from aosp_diff/caas/frameworks/native/0003-update-vulkanhardware-version.patch rename to aosp_diff/caas/frameworks/native/0002-update-vulkanhardware-version.patch diff --git a/aosp_diff/caas/frameworks/native/0003-Protect-objects-in-Parcel-appendFrom.patch b/aosp_diff/caas/frameworks/native/0003-Protect-objects-in-Parcel-appendFrom.patch new file mode 100644 index 0000000..8713cc6 --- /dev/null +++ b/aosp_diff/caas/frameworks/native/0003-Protect-objects-in-Parcel-appendFrom.patch @@ -0,0 +1,151 @@ +From 64c02fab5768bb939ddc5082887c6d4e0e9aa79f Mon Sep 17 00:00:00 2001 +From: Devin Moore +Date: Mon, 17 Mar 2025 23:09:26 +0000 +Subject: [PATCH 1/5] Protect objects in Parcel::appendFrom + +* only aquire objects within the range supplied to appendFrom +* don't append over existing objects +* unset the mObjectsSorted flag a couple more cases +* keep mObjectPositions sorted + +Flag: EXEMPT bug fix +Ignore-AOSP-First: security fix +Test: binder_parcel_fuzzer +Bug: 402319736 +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:28e7af08b92e7b97f46d8ecd88ebd3f27a065e08) +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:71efd8fd6b717ec427a264fc901e3c62ef7de8ec) +Merged-In: I63715fdd81781aaf04f5fc0cb8bdb74c09d5d807 +Change-Id: I63715fdd81781aaf04f5fc0cb8bdb74c09d5d807 +--- + libs/binder/Parcel.cpp | 40 +++++++++++++++------- + libs/binder/tests/binderParcelUnitTest.cpp | 11 ++++++ + 2 files changed, 39 insertions(+), 12 deletions(-) + +diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp +index 9921c13973..879b32ed5a 100644 +--- a/libs/binder/Parcel.cpp ++++ b/libs/binder/Parcel.cpp +@@ -537,6 +537,11 @@ status_t Parcel::appendFrom(const Parcel* parcel, size_t offset, size_t len) { + return BAD_VALUE; + } + ++ // Make sure we aren't appending over objects. ++ if (status_t status = validateReadData(mDataPos + len); status != OK) { ++ return status; ++ } ++ + if ((mDataSize+len) > mDataCapacity) { + // grow data + err = growData(len); +@@ -560,17 +565,13 @@ status_t Parcel::appendFrom(const Parcel* parcel, size_t offset, size_t len) { + const binder_size_t* objects = otherKernelFields->mObjects; + size_t size = otherKernelFields->mObjectsSize; + // Count objects in range +- int firstIndex = -1, lastIndex = -2; ++ int numObjects = 0; + for (int i = 0; i < (int)size; i++) { +- size_t off = objects[i]; +- if ((off >= offset) && (off + sizeof(flat_binder_object) <= offset + len)) { +- if (firstIndex == -1) { +- firstIndex = i; +- } +- lastIndex = i; ++ size_t pos = objects[i]; ++ if ((pos >= offset) && (pos + sizeof(flat_binder_object) <= offset + len)) { ++ numObjects++; + } + } +- int numObjects = lastIndex - firstIndex + 1; + if (numObjects > 0) { + const sp proc(ProcessState::self()); + // grow objects +@@ -592,8 +593,12 @@ status_t Parcel::appendFrom(const Parcel* parcel, size_t offset, size_t len) { + + // append and acquire objects + int idx = kernelFields->mObjectsSize; +- for (int i = firstIndex; i <= lastIndex; i++) { +- size_t off = objects[i] - offset + startPos; ++ for (int i = 0; i < (int)size; i++) { ++ size_t pos = objects[i]; ++ if (!(pos >= offset) || !(pos + sizeof(flat_binder_object) <= offset + len)) { ++ continue; ++ } ++ size_t off = pos - offset + startPos; + kernelFields->mObjects[idx++] = off; + kernelFields->mObjectsSize++; + +@@ -613,6 +618,9 @@ status_t Parcel::appendFrom(const Parcel* parcel, size_t offset, size_t len) { + + acquire_object(proc, *flat, this); + } ++ // Always clear sorted flag. It is tricky to infer if the append ++ // result maintains the sort or not. ++ kernelFields->mObjectsSorted = false; + } + #else + LOG_ALWAYS_FATAL("Binder kernel driver disabled at build time"); +@@ -643,7 +651,10 @@ status_t Parcel::appendFrom(const Parcel* parcel, size_t offset, size_t len) { + const binder_size_t objPos = otherRpcFields->mObjectPositions[i]; + if (offset <= objPos && objPos < offset + len) { + size_t newDataPos = objPos - offset + startPos; +- rpcFields->mObjectPositions.push_back(newDataPos); ++ rpcFields->mObjectPositions ++ .insert(std::upper_bound(rpcFields->mObjectPositions.begin(), ++ rpcFields->mObjectPositions.end(), newDataPos), ++ newDataPos); + + mDataPos = newDataPos; + int32_t objectType; +@@ -1591,7 +1602,10 @@ status_t Parcel::writeFileDescriptor(int fd, bool takeOwnership) { + if (status_t err = writeInt32(rpcFields->mFds->size()); err != OK) { + return err; + } +- rpcFields->mObjectPositions.push_back(dataPos); ++ rpcFields->mObjectPositions ++ .insert(std::upper_bound(rpcFields->mObjectPositions.begin(), ++ rpcFields->mObjectPositions.end(), dataPos), ++ dataPos); + rpcFields->mFds->push_back(std::move(fdVariant)); + return OK; + } +@@ -1799,6 +1813,8 @@ restart_write: + kernelFields->mObjects[kernelFields->mObjectsSize] = mDataPos; + acquire_object(ProcessState::self(), val, this); + kernelFields->mObjectsSize++; ++ // Clear sorted flag if we aren't appending to the end. ++ kernelFields->mObjectsSorted &= mDataPos == mDataSize; + } + + return finishWrite(sizeof(flat_binder_object)); +diff --git a/libs/binder/tests/binderParcelUnitTest.cpp b/libs/binder/tests/binderParcelUnitTest.cpp +index 6259d9d2d2..782560ea0e 100644 +--- a/libs/binder/tests/binderParcelUnitTest.cpp ++++ b/libs/binder/tests/binderParcelUnitTest.cpp +@@ -26,6 +26,7 @@ using android::IPCThreadState; + using android::NO_ERROR; + using android::OK; + using android::Parcel; ++using android::PERMISSION_DENIED; + using android::sp; + using android::status_t; + using android::String16; +@@ -345,6 +346,16 @@ TEST(Parcel, AppendWithFdPartial) { + ASSERT_NE(-1, p1.readFileDescriptor()); + } + ++TEST(Parcel, AppendOverObject) { ++ Parcel p1; ++ p1.writeDupFileDescriptor(0); ++ Parcel p2; ++ p2.writeInt32(2); ++ ++ p1.setDataPosition(8); ++ ASSERT_EQ(PERMISSION_DENIED, p1.appendFrom(&p2, 0, p2.dataSize())); ++} ++ + // Tests a second operation results in a parcel at the same location as it + // started. + void parcelOpSameLength(const std::function& a, const std::function& b) { +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/native/0004-RPC-Binder-shutdown-on-ENOMEM.patch b/aosp_diff/caas/frameworks/native/0004-RPC-Binder-shutdown-on-ENOMEM.patch new file mode 100644 index 0000000..20a2013 --- /dev/null +++ b/aosp_diff/caas/frameworks/native/0004-RPC-Binder-shutdown-on-ENOMEM.patch @@ -0,0 +1,53 @@ +From 62c03cabf43a44fdc020c1d2c28a0fb4a7026e5b Mon Sep 17 00:00:00 2001 +From: Steven Moreland +Date: Fri, 9 May 2025 16:39:57 +0000 +Subject: [PATCH 2/5] RPC Binder: shutdown on ENOMEM + +We were expecting crashes in these case, but explicitly +shut down. More work needs to be done to ignore transactions +in out of memory conditions. + +Bug: 404210068 +Bug: 414720799 +Bug: 416734088 +Test: binderRpcTest +Change-Id: Iaf9a34b4031fb7b9807c962bcc67de8cd9102088 +Merged-In: Iaf9a34b4031fb7b9807c962bcc67de8cd9102088 +(cherry picked from commit cba4e3642a8a58d54481ed4c14f179bcc7f9ae70) +--- + libs/binder/RpcState.cpp | 12 +++++++++++- + 1 file changed, 11 insertions(+), 1 deletion(-) + +diff --git a/libs/binder/RpcState.cpp b/libs/binder/RpcState.cpp +index fe6e1a3318..12e0cfc00e 100644 +--- a/libs/binder/RpcState.cpp ++++ b/libs/binder/RpcState.cpp +@@ -684,7 +684,13 @@ status_t RpcState::waitForReply(const sp& connection, + memset(&rpcReply, 0, sizeof(RpcWireReply)); // zero because of potential short read + + CommandData data(command.bodySize - rpcReplyWireSize); +- if (!data.valid()) return NO_MEMORY; ++ if (!data.valid()) { ++ // b/404210068 - if we run out of memory, the wire protocol gets messed up. ++ // so shutdown. We would need to read all the transaction data anyway and ++ // send a reply still to gracefully recover. ++ (void)session->shutdownAndWait(false); ++ return NO_MEMORY; ++ } + + iovec iovs[]{ + {&rpcReply, rpcReplyWireSize}, +@@ -841,6 +847,10 @@ status_t RpcState::processTransact( + + CommandData transactionData(command.bodySize); + if (!transactionData.valid()) { ++ // b/404210068 - if we run out of memory, the wire protocol gets messed up. ++ // so shutdown. We would need to read all the transaction data anyway and ++ // send a reply still to gracefully recover. ++ (void)session->shutdownAndWait(false); + return NO_MEMORY; + } + iovec iov{transactionData.data(), transactionData.size()}; +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/native/0005-RPC-Binder-shutdown-on-SPAN-error.patch b/aosp_diff/caas/frameworks/native/0005-RPC-Binder-shutdown-on-SPAN-error.patch new file mode 100644 index 0000000..47192a7 --- /dev/null +++ b/aosp_diff/caas/frameworks/native/0005-RPC-Binder-shutdown-on-SPAN-error.patch @@ -0,0 +1,31 @@ +From bc074101b15cd4048f46035ec35bd05d32c961b7 Mon Sep 17 00:00:00 2001 +From: Steven Moreland +Date: Fri, 9 May 2025 23:05:54 +0000 +Subject: [PATCH 3/5] RPC Binder: shutdown on SPAN error. + +This error return is not recoverable. + +Bug: 416734088 +Test: binderRpcTest +Change-Id: If0b8a8f36f797dcf927bfc2b5ae51e37e915f2f6 +Merged-In: If0b8a8f36f797dcf927bfc2b5ae51e37e915f2f6 +(cherry picked from commit dddbc115f88d99379ad9e118e551924c84f16e3a) +--- + libs/binder/RpcState.cpp | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/libs/binder/RpcState.cpp b/libs/binder/RpcState.cpp +index 12e0cfc00e..b06c050dfc 100644 +--- a/libs/binder/RpcState.cpp ++++ b/libs/binder/RpcState.cpp +@@ -984,6 +984,7 @@ processTransactInternalTailCall: + " objectTableBytesSize=%zu. Terminating!", + transactionData.size(), sizeof(RpcWireTransaction), + transaction->parcelDataSize, objectTableBytes->size); ++ (void)session->shutdownAndWait(false); + return BAD_VALUE; + } + objectTableSpan = *maybeSpan; +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/native/0006-RPC-Binder-clearer-errors-for-wrong-transact-type.patch b/aosp_diff/caas/frameworks/native/0006-RPC-Binder-clearer-errors-for-wrong-transact-type.patch new file mode 100644 index 0000000..0fcfaa1 --- /dev/null +++ b/aosp_diff/caas/frameworks/native/0006-RPC-Binder-clearer-errors-for-wrong-transact-type.patch @@ -0,0 +1,40 @@ +From 5ba2d6520efad64440419f317aeb4abe9cf14408 Mon Sep 17 00:00:00 2001 +From: Steven Moreland +Date: Tue, 10 Jun 2025 21:27:46 +0000 +Subject: [PATCH 4/5] RPC Binder: clearer errors for wrong transact type + +Bug: 423127919 # this bug error is totally different + than this case. If we hit this case, we would have + gotten a different harder error. However, I found + this while looking at this bug. +Bug: 416734088 +Test: binderRpcTest +Flag: EXEMPT bug fix +Change-Id: I3e0db452ebeed1a520ffc0181ed3577eb38bea9a +Merged-In: I3e0db452ebeed1a520ffc0181ed3577eb38bea9a +(cherry picked from commit 51ed14eebf5a6b7e0d1d6f5b9c81b01a666b270a) +--- + libs/binder/RpcState.cpp | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/libs/binder/RpcState.cpp b/libs/binder/RpcState.cpp +index b06c050dfc..1f87a07494 100644 +--- a/libs/binder/RpcState.cpp ++++ b/libs/binder/RpcState.cpp +@@ -824,7 +824,12 @@ status_t RpcState::processCommand( + + switch (command.command) { + case RPC_COMMAND_TRANSACT: +- if (type != CommandType::ANY) return BAD_TYPE; ++ if (type != CommandType::ANY) { ++ ALOGE("CommandType %d, but got RPC command %d.", static_cast(type), ++ command.command); ++ (void)session->shutdownAndWait(false); ++ return BAD_TYPE; ++ } + return processTransact(connection, session, command, std::move(ancillaryFds)); + case RPC_COMMAND_DEC_STRONG: + return processDecStrong(connection, session, command); +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/native/0007-Don-t-blur-too-many-layers.patch b/aosp_diff/caas/frameworks/native/0007-Don-t-blur-too-many-layers.patch new file mode 100644 index 0000000..6c4bc8d --- /dev/null +++ b/aosp_diff/caas/frameworks/native/0007-Don-t-blur-too-many-layers.patch @@ -0,0 +1,230 @@ +From ea91e213807e327f99e1db014943f99b885603e3 Mon Sep 17 00:00:00 2001 +From: Alec Mouri +Date: Thu, 15 May 2025 16:39:49 +0000 +Subject: [PATCH 5/5] Don't blur too many layers + +An application requesting lots and lots of blurs: +a. Enables pixel stealing by measuring how long it takes to perform a +blur across windows +b. Probably isn't very valid anyways. + +So, just arbitrarily pick an upper bound for blur requests that a +display is allowed to manage (10), and disable everything else. +Arbitrarily, pick the 10 "front-most" blurs to be respected. + +Bug: 399120953 +Flag: EXEMPT security +Test: Security PoC no longer PoCs +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:acf65e7b90c8313b3cf939d14b8299818d77cc18) +Merged-In: Ie7195eb852b52aff2f58da8bd095d8684baceef6 +Change-Id: Ie7195eb852b52aff2f58da8bd095d8684baceef6 +--- + .../include/compositionengine/Output.h | 1 + + .../include/compositionengine/impl/Output.h | 1 + + .../impl/OutputLayerCompositionState.h | 3 +++ + .../impl/planner/CachedSet.h | 1 + + .../impl/planner/LayerState.h | 20 ++++++++++++------- + .../CompositionEngine/src/Output.cpp | 16 ++++++++++++--- + .../src/planner/CachedSet.cpp | 7 ++++++- + .../tests/planner/CachedSetTest.cpp | 4 ++-- + 8 files changed, 40 insertions(+), 13 deletions(-) + +diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h +index bda7856596..170a82ddcd 100644 +--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h ++++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h +@@ -159,6 +159,7 @@ public: + // only has a value if there's something needing it, like when a TrustedPresentationListener + // is set + std::optional aboveCoveredLayersExcludingOverlays; ++ int32_t aboveBlurRequests = 0; + }; + + virtual ~Output(); +diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h +index 0ccdd22919..fcf2c8bfa4 100644 +--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h ++++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h +@@ -175,6 +175,7 @@ protected: + private: + void dirtyEntireOutput(); + compositionengine::OutputLayer* findLayerRequestingBackgroundComposition() const; ++ void sanitizeOutputLayers() const; + void finishPrepareFrame(); + ui::Dataspace getBestDataspace(ui::Dataspace*, bool*) const; + compositionengine::Output::ColorProfile pickColorProfile( +diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h +index c558739464..3dc115e199 100644 +--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h ++++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h +@@ -105,6 +105,9 @@ struct OutputLayerCompositionState { + // The picture profile for this layer. + PictureProfileHandle pictureProfileHandle; + ++ // ignore blur requests if there's just too many on top of this layer ++ bool ignoreBlur{false}; ++ + // Overrides the buffer, acquire fence, and display frame stored in LayerFECompositionState + struct { + std::shared_ptr buffer = nullptr; +diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h +index 86bcf20677..e09e308a01 100644 +--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h ++++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h +@@ -42,6 +42,7 @@ public: + const std::string& getName() const { return mState->getName(); } + int32_t getBackgroundBlurRadius() const { return mState->getBackgroundBlurRadius(); } + Rect getDisplayFrame() const { return mState->getDisplayFrame(); } ++ bool hasBlurBehind() const { return mState->hasBlurBehind(); } + const Region& getVisibleRegion() const { return mState->getVisibleRegion(); } + const sp& getBuffer() const { + return mState->getOutputLayer()->getLayerFE().getCompositionState()->buffer; +diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h +index 5e3e3d8a31..eb942421d8 100644 +--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h ++++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h +@@ -75,6 +75,7 @@ enum class LayerStateField : uint32_t { + HasProtectedContent = 1u << 19, + CachingHint = 1u << 20, + DimmingEnabled = 1u << 21, ++ BlursDisabled = 1u << 22, + }; + // clang-format on + +@@ -236,7 +237,8 @@ public: + Rect getDisplayFrame() const { return mDisplayFrame.get(); } + const Region& getVisibleRegion() const { return mVisibleRegion.get(); } + bool hasBlurBehind() const { +- return mBackgroundBlurRadius.get() > 0 || !mBlurRegions.get().empty(); ++ return (mBackgroundBlurRadius.get() > 0 || !mBlurRegions.get().empty()) && ++ !mIsBlursDisabled.get(); + } + int32_t getBackgroundBlurRadius() const { return mBackgroundBlurRadius.get(); } + aidl::android::hardware::graphics::composer3::Composition getCompositionType() const { +@@ -508,7 +510,10 @@ private: + OutputLayerState mIsDimmingEnabled{ + [](auto layer) { return layer->getLayerFE().getCompositionState()->dimmingEnabled; }}; + +- static const constexpr size_t kNumNonUniqueFields = 20; ++ OutputLayerState mIsBlursDisabled{ ++ [](auto layer) { return layer->getState().ignoreBlur; }}; ++ ++ static const constexpr size_t kNumNonUniqueFields = 21; + + std::array getNonUniqueFields() { + std::array constFields = +@@ -522,11 +527,12 @@ private: + } + + std::array getNonUniqueFields() const { +- return {&mDisplayFrame, &mSourceCrop, &mBufferTransform, &mBlendMode, +- &mAlpha, &mLayerMetadata, &mVisibleRegion, &mOutputDataspace, +- &mPixelFormat, &mColorTransform, &mCompositionType, &mSidebandStream, +- &mBuffer, &mSolidColor, &mBackgroundBlurRadius, &mBlurRegions, +- &mFrameNumber, &mIsProtected, &mCachingHint, &mIsDimmingEnabled}; ++ return {&mDisplayFrame, &mSourceCrop, &mBufferTransform, &mBlendMode, ++ &mAlpha, &mLayerMetadata, &mVisibleRegion, &mOutputDataspace, ++ &mPixelFormat, &mColorTransform, &mCompositionType, &mSidebandStream, ++ &mBuffer, &mSolidColor, &mBackgroundBlurRadius, &mBlurRegions, ++ &mFrameNumber, &mIsProtected, &mCachingHint, &mIsDimmingEnabled, ++ &mIsBlursDisabled}; + } + }; + +diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp +index f9ed92d1ee..a9aa6a48f3 100644 +--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp ++++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp +@@ -772,6 +772,9 @@ void Output::ensureOutputLayerIfVisible(sp& layerFE, + // one, or create a new one if we do not. + auto outputLayer = ensureOutputLayer(prevOutputLayerIndex, layerFE); + ++ coverage.aboveBlurRequests += static_cast(layerFEState->backgroundBlurRadius > 0 || ++ !layerFEState->blurRegions.empty()); ++ + // Store the layer coverage information into the layer state as some of it + // is useful later. + auto& outputLayerState = outputLayer->editState(); +@@ -786,6 +789,11 @@ void Output::ensureOutputLayerIfVisible(sp& layerFE, + ? outputState.transform.transform( + transparentRegion.intersect(outputState.layerStackSpace.getContent())) + : Region(); ++ ++ // See b/399120953: blurs are so expensive that they may be susceptible to compression side ++ // channel attacks ++ static constexpr auto kMaxBlurRequests = 10; ++ outputLayerState.ignoreBlur = coverage.aboveBlurRequests > kMaxBlurRequests; + if (CC_UNLIKELY(computeAboveCoveredExcludingOverlays)) { + outputLayerState.coveredRegionExcludingDisplayOverlays = + std::move(coveredRegionExcludingDisplayOverlays); +@@ -1490,7 +1498,7 @@ std::vector Output::generateClientCompositionRequests( + const Region viewportRegion(outputState.layerStackSpace.getContent()); + bool firstLayer = true; + +- bool disableBlurs = false; ++ bool disableBlursWholesale = false; + uint64_t previousOverrideBufferId = 0; + + for (auto* layer : getOutputLayersOrderedByZ()) { +@@ -1507,7 +1515,8 @@ std::vector Output::generateClientCompositionRequests( + continue; + } + +- disableBlurs |= layerFEState->sidebandStream != nullptr; ++ disableBlursWholesale |= layerFEState->sidebandStream != nullptr; ++ bool disableBlurForLayer = layer->getState().ignoreBlur || disableBlursWholesale; + + const bool clientComposition = layer->requiresClientComposition(); + +@@ -1537,7 +1546,8 @@ std::vector Output::generateClientCompositionRequests( + layer->getLayerFE().getDebugName()); + } + } else { +- LayerFE::ClientCompositionTargetSettings::BlurSetting blurSetting = disableBlurs ++ LayerFE::ClientCompositionTargetSettings::BlurSetting blurSetting = ++ disableBlurForLayer + ? LayerFE::ClientCompositionTargetSettings::BlurSetting::Disabled + : (layer->getState().overrideInfo.disableBackgroundBlur + ? LayerFE::ClientCompositionTargetSettings::BlurSetting:: +diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp +index 409a206ace..a458b5ebd0 100644 +--- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp ++++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp +@@ -196,9 +196,14 @@ void CachedSet::render(renderengine::RenderEngine& renderEngine, TexturePool& te + std::vector layerSettings; + renderengine::LayerSettings highlight; + for (const auto& layer : mLayers) { ++ auto blurSettings = targetSettings; ++ if (!layer.hasBlurBehind()) { ++ blurSettings.blurSetting = ++ LayerFE::ClientCompositionTargetSettings::BlurSetting::Disabled; ++ } + if (auto clientCompositionSettings = + layer.getState()->getOutputLayer()->getLayerFE().prepareClientComposition( +- targetSettings)) { ++ blurSettings)) { + layerSettings.push_back(std::move(*clientCompositionSettings)); + } + } +diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp +index d61d7ba574..5b33407146 100644 +--- a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp ++++ b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp +@@ -1011,12 +1011,12 @@ TEST_F(CachedSetTest, addBlur) { + EXPECT_CALL(*layerFE1, + prepareClientComposition(ClientCompositionTargetSettingsBlurSettingsEq( + compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting:: +- Enabled))) ++ Disabled))) + .WillOnce(Return(clientComp1)); + EXPECT_CALL(*layerFE2, + prepareClientComposition(ClientCompositionTargetSettingsBlurSettingsEq( + compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting:: +- Enabled))) ++ Disabled))) + .WillOnce(Return(clientComp2)); + EXPECT_CALL(*layerFE3, + prepareClientComposition(ClientCompositionTargetSettingsBlurSettingsEq( +-- +2.34.1 + diff --git a/aosp_diff/caas/frameworks/opt/telephony/0001-Remove-get-set-of-voicemail-ringtone-uri-in-shared-p.patch b/aosp_diff/caas/frameworks/opt/telephony/0001-Remove-get-set-of-voicemail-ringtone-uri-in-shared-p.patch new file mode 100644 index 0000000..95fd0bc --- /dev/null +++ b/aosp_diff/caas/frameworks/opt/telephony/0001-Remove-get-set-of-voicemail-ringtone-uri-in-shared-p.patch @@ -0,0 +1,133 @@ +From da66ef1a43de268220366bf3121655738d5ac1a3 Mon Sep 17 00:00:00 2001 +From: Tyler Gunn +Date: Tue, 22 Apr 2025 22:01:54 +0000 +Subject: [PATCH] Remove get/set of voicemail ringtone uri in shared + preferences. + +Prior to Android P, TelephonyManager#setVoicemailRingtoneUri was used +by the dialer app to set the voicemail notification sound played when +the platform got a new voicemail notification. Likewise, +getVoicemailRingtoneUri was used to retrieve the set value. + +Prior to P this was just saved in the shared prefs, but after P a +migration was done to move the shared preference to the +NotificationChannel#getSound for the voicemail notification. If, however, +you called `setVoicemailRingtoneUri` it was still possible to change the +shared preference and have that migrated to be set on the notification +channel, causing a cross-profile exploit. + +In the current world, the notifications for voicemail are NOT posted in +Telephony any more, and are instead associated with the notification +channel for voicemail IN the dialer app. On the off chance a dialer does +not show the voicemail notification, Telephony can post it as well, but +at this point the related sound is expected to be associated with the +notification channel. + +To mitigate this cross-profile vulnerability: +1. Ensure TelephonyManager#setVoicemailRingtoneUri does not save to shared +preferences any more. +2. Ensure the TelephonyManager#getVoicemailRingtoneUrigetRingtoneUri ONLY +queries from the notification channel, and not from the shared +preferences since that is not used. This ensures we can never return a +bad URI set via the setter. +3. Remove the code in migrateVoicemailNotificationSettings which will take +the shared preference and migrate it over to the channel; this is not +needed as realistically ANY device from P would have updated LONG ago and +had its notification setting migrated to the channel anyways. + +Test: Change the default voicemail notification channel sound on +"phone services"; verify that Dialer can still get this value. +Test: Changed the voicemail notification channel in the dialer app so that +it has a different value; verify that voicemail notifications use the +correct sound. +Flag: EXEMPT security patch. +Bug: 325030433 +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:8e47af093625b997ffb8ca0379a4a56c02ddeb20) +Merged-In: I7252c692eb2a5ff4b4fcbddba77425cb423539f3 +Change-Id: I7252c692eb2a5ff4b4fcbddba77425cb423539f3 +--- + .../util/NotificationChannelController.java | 6 ----- + .../VoicemailNotificationSettingsUtil.java | 22 ++++--------------- + 2 files changed, 4 insertions(+), 24 deletions(-) + +diff --git a/src/java/com/android/internal/telephony/util/NotificationChannelController.java b/src/java/com/android/internal/telephony/util/NotificationChannelController.java +index de1ddd3026..ac6a385fa4 100644 +--- a/src/java/com/android/internal/telephony/util/NotificationChannelController.java ++++ b/src/java/com/android/internal/telephony/util/NotificationChannelController.java +@@ -23,7 +23,6 @@ import android.content.Context; + import android.content.Intent; + import android.content.IntentFilter; + import android.media.AudioAttributes; +-import android.net.Uri; + import android.provider.Settings; + import android.telephony.SubscriptionManager; + +@@ -138,7 +137,6 @@ public class NotificationChannelController { + + /** + * migrate deprecated voicemail notification settings to initial notification channel settings +- * {@link VoicemailNotificationSettingsUtil#getRingTonePreference(Context)}} + * {@link VoicemailNotificationSettingsUtil#getVibrationPreference(Context)} + * notification settings are based on subId, only migrate if sub id matches. + * otherwise fallback to predefined voicemail channel settings. +@@ -151,10 +149,6 @@ public class NotificationChannelController { + NotificationManager.IMPORTANCE_DEFAULT); + voiceMailChannel.enableVibration( + VoicemailNotificationSettingsUtil.getVibrationPreference(context)); +- Uri sound = VoicemailNotificationSettingsUtil.getRingTonePreference(context); +- voiceMailChannel.setSound( +- (sound == null) ? Settings.System.DEFAULT_NOTIFICATION_URI : sound, +- new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build()); + context.getSystemService(NotificationManager.class) + .createNotificationChannel(voiceMailChannel); + } +diff --git a/src/java/com/android/internal/telephony/util/VoicemailNotificationSettingsUtil.java b/src/java/com/android/internal/telephony/util/VoicemailNotificationSettingsUtil.java +index d8988e3230..3dd3d375c9 100644 +--- a/src/java/com/android/internal/telephony/util/VoicemailNotificationSettingsUtil.java ++++ b/src/java/com/android/internal/telephony/util/VoicemailNotificationSettingsUtil.java +@@ -21,10 +21,8 @@ import android.content.Context; + import android.content.SharedPreferences; + import android.net.Uri; + import android.preference.PreferenceManager; +-import android.provider.Settings; + import android.telephony.SubscriptionManager; + import android.telephony.TelephonyManager; +-import android.text.TextUtils; + + public class VoicemailNotificationSettingsUtil { + private static final String VOICEMAIL_NOTIFICATION_RINGTONE_SHARED_PREFS_KEY_PREFIX = +@@ -64,27 +62,15 @@ public class VoicemailNotificationSettingsUtil { + } + + public static void setRingtoneUri(Context context, Uri ringtoneUri) { +- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); +- String ringtoneUriStr = ringtoneUri != null ? ringtoneUri.toString() : ""; +- +- SharedPreferences.Editor editor = prefs.edit(); +- editor.putString(getVoicemailRingtoneSharedPrefsKey(), ringtoneUriStr); +- editor.commit(); ++ // Do nothing; we don't use the shared preference any more. + } + + public static Uri getRingtoneUri(Context context) { + final NotificationChannel channel = NotificationChannelController.getChannel( + NotificationChannelController.CHANNEL_ID_VOICE_MAIL, context); +- return (channel != null) ? channel.getSound() : getRingTonePreference(context); +- } +- +- public static Uri getRingTonePreference(Context context) { +- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); +- migrateVoicemailRingtoneSettingsIfNeeded(context, prefs); +- String uriString = prefs.getString( +- getVoicemailRingtoneSharedPrefsKey(), +- Settings.System.DEFAULT_NOTIFICATION_URI.toString()); +- return !TextUtils.isEmpty(uriString) ? Uri.parse(uriString) : null; ++ // Note: NEVER look at the shared preferences; this was migrated to the notification channel ++ // in Android P. ++ return (channel != null) ? channel.getSound() : null; + } + + /** +-- +2.34.1 + diff --git a/aosp_diff/caas/packages/apps/Contacts/0001-Show-account-selection-dialog-when-a-single-account-.patch b/aosp_diff/caas/packages/apps/Contacts/0001-Show-account-selection-dialog-when-a-single-account-.patch new file mode 100644 index 0000000..0e04cff --- /dev/null +++ b/aosp_diff/caas/packages/apps/Contacts/0001-Show-account-selection-dialog-when-a-single-account-.patch @@ -0,0 +1,39 @@ +From d0fe06a0761e2c4ef5ce2936440a38ca217da26d Mon Sep 17 00:00:00 2001 +From: Kelly Ng +Date: Tue, 3 Jun 2025 11:54:07 -0700 +Subject: [PATCH] Show account selection dialog when a single account exists. + +- Recommended fix by security team to prevent auto-importing when there is a single account (see b/388032224#comment4) + +FIX: 388032224 +Flag: EXEMPT bugfix +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:615666035723ff1828ff24674209012f30082de9) +Merged-In: I83f046c24d45e076aa783e5551b42dcbaacd0ad6 +Change-Id: I83f046c24d45e076aa783e5551b42dcbaacd0ad6 +--- + .../android/contacts/vcard/SelectAccountActivity.java | 9 --------- + 1 file changed, 9 deletions(-) + +diff --git a/src/com/android/contacts/vcard/SelectAccountActivity.java b/src/com/android/contacts/vcard/SelectAccountActivity.java +index 8ead5fab3..797fc8670 100644 +--- a/src/com/android/contacts/vcard/SelectAccountActivity.java ++++ b/src/com/android/contacts/vcard/SelectAccountActivity.java +@@ -65,15 +65,6 @@ public class SelectAccountActivity extends Activity { + Log.w(LOG_TAG, "Account does not exist"); + finish(); + return; +- } else if (accountList.size() == 1) { +- final AccountWithDataSet account = accountList.get(0); +- final Intent intent = new Intent(); +- intent.putExtra(ACCOUNT_NAME, account.name); +- intent.putExtra(ACCOUNT_TYPE, account.type); +- intent.putExtra(DATA_SET, account.dataSet); +- setResult(RESULT_OK, intent); +- finish(); +- return; + } + + Log.i(LOG_TAG, "The number of available accounts: " + accountList.size()); +-- +2.34.1 + diff --git a/aosp_diff/caas/packages/apps/DocumentsUI/0001-Trim-the-application-name-to-make-it-safe-for-presen.patch b/aosp_diff/caas/packages/apps/DocumentsUI/0001-Trim-the-application-name-to-make-it-safe-for-presen.patch new file mode 100644 index 0000000..eea2cf6 --- /dev/null +++ b/aosp_diff/caas/packages/apps/DocumentsUI/0001-Trim-the-application-name-to-make-it-safe-for-presen.patch @@ -0,0 +1,227 @@ +From 51f71234b5d4c7a8663b1fa6bfe8ac2c941f9cf4 Mon Sep 17 00:00:00 2001 +From: Ben Reich +Date: Wed, 9 Apr 2025 11:02:01 +1000 +Subject: [PATCH] Trim the application name to make it safe for presentation + +The application name is presented in the ConfirmFragment and as such we +don't want to allow for any length. This follows a similar approach to +PackageManager using the TextUtils.makeSafeForPresentation with a total +available character length of 500. + +This removes the unused getCallingAppName from the DirectoryFragment as +it was causing false positives from DirectoryFragment to avoid false +positives when trying to find who calls the Shared function. + +On top of this, add some quotation marks around the app name to avoid +the app name being a contination of the existing text in the dialog, +e.g. 'This will let app name access current and future content storage +in Alarms' will now be 'This will let "app name" access current and +future content storage'. + +Bug: 397216537 +Test: atest com.android.documentsui.picker.ApplicationNameTest +Flag: EXEMPT bug fix +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:c8ef2db3bb4645704384226976e59583b9e8d3d3) +Merged-In: Iad0d03de09b1e4ad953bd6bd46a619cfcc56d384 +Change-Id: Iad0d03de09b1e4ad953bd6bd46a619cfcc56d384 +--- + res/values/strings.xml | 2 +- + src/com/android/documentsui/base/Shared.java | 12 ++- + .../dirlist/DirectoryFragment.java | 5 - + .../documentsui/dirlist/DocumentsAdapter.java | 1 - + .../documentsui/dirlist/TestEnvironment.java | 5 - + .../documentsui/picker/ApplicationNameTest.kt | 91 +++++++++++++++++++ + 6 files changed, 102 insertions(+), 14 deletions(-) + create mode 100644 tests/unit/com/android/documentsui/picker/ApplicationNameTest.kt + +diff --git a/res/values/strings.xml b/res/values/strings.xml +index 89e40ac4c..72abecbb5 100644 +--- a/res/values/strings.xml ++++ b/res/values/strings.xml +@@ -551,7 +551,7 @@ + + Allow %1$s to access files in %2$s? + +- This will let %1$s access current and future content stored in %2$s. ++ This will let "%1$s" access current and future content stored in %2$s. + + Can\u2019t use this folder + +diff --git a/src/com/android/documentsui/base/Shared.java b/src/com/android/documentsui/base/Shared.java +index ac089999f..bb8a39393 100644 +--- a/src/com/android/documentsui/base/Shared.java ++++ b/src/com/android/documentsui/base/Shared.java +@@ -16,6 +16,9 @@ + + package com.android.documentsui.base; + ++import static android.text.TextUtils.SAFE_STRING_FLAG_SINGLE_LINE; ++import static android.text.TextUtils.SAFE_STRING_FLAG_TRIM; ++ + import static com.android.documentsui.base.SharedMinimal.TAG; + import static com.android.documentsui.ChangeIds.RESTRICT_STORAGE_ACCESS_FRAMEWORK; + +@@ -265,7 +268,7 @@ public final class Shared { + * @return the calling app name or general anonymous name if not found + */ + @NonNull +- public static String getCallingAppName(Activity activity) { ++ public static CharSequence getCallingAppName(Activity activity) { + final String anonymous = activity.getString(R.string.anonymous_application); + final String packageName = getCallingPackageName(activity); + if (TextUtils.isEmpty(packageName)) { +@@ -281,7 +284,12 @@ public final class Shared { + } + + CharSequence result = pm.getApplicationLabel(ai); +- return TextUtils.isEmpty(result) ? anonymous : result.toString(); ++ if (TextUtils.isEmpty(result)) { ++ return anonymous; ++ } ++ ++ return TextUtils.makeSafeForPresentation( ++ result.toString(), 500, 0, SAFE_STRING_FLAG_TRIM | SAFE_STRING_FLAG_SINGLE_LINE); + } + + /** +diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java +index e099ca734..22b7c5c62 100644 +--- a/src/com/android/documentsui/dirlist/DirectoryFragment.java ++++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java +@@ -1561,10 +1561,5 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On + public ActionHandler getActionHandler() { + return mActions; + } +- +- @Override +- public String getCallingAppName() { +- return Shared.getCallingAppName(mActivity); +- } + } + } +diff --git a/src/com/android/documentsui/dirlist/DocumentsAdapter.java b/src/com/android/documentsui/dirlist/DocumentsAdapter.java +index 41ce73c8c..b32c15335 100644 +--- a/src/com/android/documentsui/dirlist/DocumentsAdapter.java ++++ b/src/com/android/documentsui/dirlist/DocumentsAdapter.java +@@ -90,7 +90,6 @@ public abstract class DocumentsAdapter extends RecyclerView.Adapter ++ ++ @Before ++ fun setUp() { ++ MockitoAnnotations.openMocks(this) ++ whenever(mockActivity.resources).thenReturn(resources) ++ whenever(mockActivity.packageManager).thenReturn(pm) ++ whenever(resources.getString(R.string.anonymous_application)).thenReturn(ANONYMOUS_PACKAGE) ++ whenever(mockActivity.callingPackage).thenReturn(PACKAGE_NAME) ++ } ++ ++ @Test ++ fun testNameIsSanitized() { ++ val info = ApplicationInfo() ++ whenever(pm.getApplicationInfo(PACKAGE_NAME, 0)).thenReturn(info) ++ ++ whenever(pm.getApplicationLabel(eq(info))).thenReturn(testData.first) ++ assertEquals(Shared.getCallingAppName(mockActivity), testData.second) ++ } ++} +-- +2.34.1 + diff --git a/aosp_diff/caas/packages/apps/ManagedProvisioning/0001-Fix-confused-deputy-vulnerability-in-termsActivity-t.patch b/aosp_diff/caas/packages/apps/ManagedProvisioning/0001-Fix-confused-deputy-vulnerability-in-termsActivity-t.patch new file mode 100644 index 0000000..344b8e7 --- /dev/null +++ b/aosp_diff/caas/packages/apps/ManagedProvisioning/0001-Fix-confused-deputy-vulnerability-in-termsActivity-t.patch @@ -0,0 +1,122 @@ +From e3c24fb40a92bbd9d1f8f98dceab49a0ad412a6d Mon Sep 17 00:00:00 2001 +From: Shreya Singh +Date: Fri, 7 Mar 2025 16:15:57 -0800 +Subject: [PATCH 1/2] Fix confused deputy vulnerability in termsActivity to + access terms_disclaimer Uri only if the calling app has the permissions + +1-P doc at: go/termsDisclaimerVulnerability + +Before: https://hsv.googleplex.com/5163551739084800 +After: https://hsv.googleplex.com/5207829722955776 +https://paste.googleplex.com/5643054726512640 + +Flag: EXEMPT bug fix +Bug: 299928772 +Test: Manual using test app provided by the reporter +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:5d67902f6a7498d016ee588d6c00710fb2d3ab98) +Merged-In: I4c5ab64cb770c61db1cedc5169a4b8cdf0a4b0bd +Change-Id: I4c5ab64cb770c61db1cedc5169a4b8cdf0a4b0bd +--- + .../parser/DisclaimersParserImpl.java | 54 ++++++++++++++++++- + 1 file changed, 53 insertions(+), 1 deletion(-) + +diff --git a/src/com/android/managedprovisioning/parser/DisclaimersParserImpl.java b/src/com/android/managedprovisioning/parser/DisclaimersParserImpl.java +index fd20705fa..542bd7ce5 100644 +--- a/src/com/android/managedprovisioning/parser/DisclaimersParserImpl.java ++++ b/src/com/android/managedprovisioning/parser/DisclaimersParserImpl.java +@@ -20,10 +20,13 @@ import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DISCLAIME + import static com.android.managedprovisioning.common.StoreUtils.DIR_PROVISIONING_PARAMS_FILE_CACHE; + + import android.content.Context; ++import android.content.Intent; ++import android.content.pm.PackageManager; + import android.net.Uri; + import android.os.Bundle; + import android.os.Parcelable; + import androidx.annotation.Nullable; ++import android.os.Binder; + + import android.text.TextUtils; + import com.android.managedprovisioning.common.ProvisionLogger; +@@ -33,6 +36,7 @@ import com.android.managedprovisioning.model.DisclaimersParam.Disclaimer; + import java.io.File; + import java.util.ArrayList; + import java.util.List; ++import java.util.Objects; + + /** + * Parser for {@link EXTRA_PROVISIONING_DISCLAIMERS} into {@link DisclaimersParam} +@@ -40,6 +44,9 @@ import java.util.List; + */ + public class DisclaimersParserImpl implements DisclaimerParser { + private static final int MAX_LENGTH = 3; ++ private static final String SCHEME_ANDROID_RESOURCE = "android.resource"; ++ private static final String SCHEME_CONTENT = "content"; ++ + + private final Context mContext; + private final long mProvisioningId; +@@ -75,7 +82,17 @@ public class DisclaimersParserImpl implements DisclaimerParser { + ProvisionLogger.logw("Null disclaimer content uri in " + i + " element"); + continue; + } +- ++ try { ++ validateUriSchemeAndPermission(uri); ++ } catch (SecurityException e) { ++ ProvisionLogger.loge( ++ "Skipping disclaimer in " ++ + i ++ + " element due to URI validation failure: " ++ + e.getMessage(), ++ e); ++ continue; ++ } + File disclaimerFile = saveDisclaimerContentIntoFile(uri, i); + + if (disclaimerFile == null) { +@@ -89,6 +106,41 @@ public class DisclaimersParserImpl implements DisclaimerParser { + .setDisclaimers(disclaimers.toArray(new Disclaimer[disclaimers.size()])).build(); + } + ++ /** ++ * Validates a {@link Uri} extra pointing to disclaimer content. ++ * ++ *

It checks that the URI scheme is one of {@code content} ({@link ++ * android.content.ContentResolver#SCHEME_CONTENT}) or {@code ++ * android.resource} ({@link ++ * android.content.ContentResolver#SCHEME_ANDROID_RESOURCE}). If a {@code ++ * content:} URI is passed, it also checks that the caller has grant read ++ * permission ({@link Intent#FLAG_GRANT_READ_URI_PERMISSION}). ++ * ++ * @throws SecurityException if the URI scheme is invalid or the caller ++ * does not have permission to access the URI. ++ */ ++ private void validateUriSchemeAndPermission(Uri uri) throws SecurityException { ++ ProvisionLogger.logd("validateUriSchemeAndPermission: " + uri); ++ String scheme = uri.getScheme(); ++ if (!Objects.equals(scheme, SCHEME_ANDROID_RESOURCE) ++ && !Objects.equals(scheme, SCHEME_CONTENT)) { ++ String errorMessage = "Invalid URI scheme: " + scheme; ++ throw new SecurityException(errorMessage); ++ } ++ int permissionCheck = ++ mContext.checkUriPermission( ++ uri, ++ Binder.getCallingPid(), ++ Binder.getCallingUid(), ++ Intent.FLAG_GRANT_READ_URI_PERMISSION); ++ ++ if (permissionCheck != PackageManager.PERMISSION_GRANTED) { ++ String errorMessage = "Caller does not have permission to access" ++ + " disclaimer URI: " + uri; ++ throw new SecurityException(errorMessage); ++ } ++ } ++ + /** + * @return {@link File} if the uri content is saved into the file successfully. Otherwise, + * return null. +-- +2.34.1 + diff --git a/aosp_diff/caas/packages/apps/ManagedProvisioning/0002-Skip-permission-check-for-SCHEME_ANDROID_RESOURCE.patch b/aosp_diff/caas/packages/apps/ManagedProvisioning/0002-Skip-permission-check-for-SCHEME_ANDROID_RESOURCE.patch new file mode 100644 index 0000000..fecf263 --- /dev/null +++ b/aosp_diff/caas/packages/apps/ManagedProvisioning/0002-Skip-permission-check-for-SCHEME_ANDROID_RESOURCE.patch @@ -0,0 +1,54 @@ +From 2f1e56d9b25993ca324b998e1518dd8c0d86c075 Mon Sep 17 00:00:00 2001 +From: Shreya Singh +Date: Fri, 11 Apr 2025 08:05:47 -0700 +Subject: [PATCH 2/2] Skip permission check for SCHEME_ANDROID_RESOURCE + +Based on DPMS [documentation](https://source.corp.google.com/h/googleplex-android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/admin/DevicePolicyManager.java;l=1765;drc=33957a5a6a01261255c4d36e132fe2e526875534#:~:text=*%20%3Cp%3EIf%20a%20%3Ccode,%7D%20of%20the%20intent.) FLAG_GRANT_READ_URI_PERMISSION is only required for SCHEME_CONTENT. +Flag: EXEMPT bug fix +Bug: 299928772 +Test: Manual +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:5fe27956963c7217f4a46fbaf71e85581c15c75a) +Merged-In: Ie19576c1a2403c62ca94b164736983624ab12b58 +Change-Id: Ie19576c1a2403c62ca94b164736983624ab12b58 +--- + .../parser/DisclaimersParserImpl.java | 24 ++++++++++--------- + 1 file changed, 13 insertions(+), 11 deletions(-) + +diff --git a/src/com/android/managedprovisioning/parser/DisclaimersParserImpl.java b/src/com/android/managedprovisioning/parser/DisclaimersParserImpl.java +index 542bd7ce5..9d8bb3b9d 100644 +--- a/src/com/android/managedprovisioning/parser/DisclaimersParserImpl.java ++++ b/src/com/android/managedprovisioning/parser/DisclaimersParserImpl.java +@@ -127,17 +127,19 @@ public class DisclaimersParserImpl implements DisclaimerParser { + String errorMessage = "Invalid URI scheme: " + scheme; + throw new SecurityException(errorMessage); + } +- int permissionCheck = +- mContext.checkUriPermission( +- uri, +- Binder.getCallingPid(), +- Binder.getCallingUid(), +- Intent.FLAG_GRANT_READ_URI_PERMISSION); +- +- if (permissionCheck != PackageManager.PERMISSION_GRANTED) { +- String errorMessage = "Caller does not have permission to access" +- + " disclaimer URI: " + uri; +- throw new SecurityException(errorMessage); ++ if (Objects.equals(scheme, SCHEME_CONTENT)) { ++ int permissionCheck = ++ mContext.checkUriPermission( ++ uri, ++ Binder.getCallingPid(), ++ Binder.getCallingUid(), ++ Intent.FLAG_GRANT_READ_URI_PERMISSION); ++ ++ if (permissionCheck != PackageManager.PERMISSION_GRANTED) { ++ String errorMessage = "Caller does not have permission to access" ++ + " disclaimer URI: " + uri; ++ throw new SecurityException(errorMessage); ++ } + } + } + +-- +2.34.1 + diff --git a/aosp_diff/caas/packages/apps/Settings/0001-Add-ComponentName-explicitly-to-make-sure-arbitary-i.patch b/aosp_diff/caas/packages/apps/Settings/0001-Add-ComponentName-explicitly-to-make-sure-arbitary-i.patch new file mode 100644 index 0000000..582c556 --- /dev/null +++ b/aosp_diff/caas/packages/apps/Settings/0001-Add-ComponentName-explicitly-to-make-sure-arbitary-i.patch @@ -0,0 +1,37 @@ +From c1432de0323b56a5eea9b3565198e5476fe9c7a3 Mon Sep 17 00:00:00 2001 +From: Aseem Kumar +Date: Wed, 16 Apr 2025 11:22:07 -0700 +Subject: [PATCH 1/8] Add ComponentName explicitly to make sure arbitary + intents aren't launched from Settings. + +Bug: 378902342 +Flag: EXEMPT security fix +Change-Id: I0e67f1258cb427c5b998e40a8a0c104af3ead042 +(cherry picked from commit 6a896b6b26d445800773e1b4649895bea17eac1f) +--- + .../settings/accounts/AccountTypePreferenceLoader.java | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/src/com/android/settings/accounts/AccountTypePreferenceLoader.java b/src/com/android/settings/accounts/AccountTypePreferenceLoader.java +index 71c71346adb..8ca74c85dd8 100644 +--- a/src/com/android/settings/accounts/AccountTypePreferenceLoader.java ++++ b/src/com/android/settings/accounts/AccountTypePreferenceLoader.java +@@ -265,7 +265,14 @@ public class AccountTypePreferenceLoader { + try { + // Allows to launch only authenticator owned activities. + ApplicationInfo authenticatorAppInf = pm.getApplicationInfo(authDesc.packageName, 0); +- return resolvedAppInfo.uid == authenticatorAppInf.uid; ++ if (resolvedAppInfo.uid == authenticatorAppInf.uid) { ++ // Explicitly set the component to be same as authenticator to ++ // prevent launching arbitrary activities. ++ intent.setComponent(resolvedActivityInfo.getComponentName()); ++ return true; ++ } else { ++ return false; ++ } + } catch (NameNotFoundException e) { + Log.e(TAG, + "Intent considered unsafe due to exception.", +-- +2.34.1 + diff --git a/aosp_diff/caas/packages/apps/Settings/0002-AppRestrictions-use-vetted-component.patch b/aosp_diff/caas/packages/apps/Settings/0002-AppRestrictions-use-vetted-component.patch new file mode 100644 index 0000000..1e52b24 --- /dev/null +++ b/aosp_diff/caas/packages/apps/Settings/0002-AppRestrictions-use-vetted-component.patch @@ -0,0 +1,71 @@ +From f19dd0688286d1cca4251169dbd1113cb9cc199b Mon Sep 17 00:00:00 2001 +From: Adam Bookatz +Date: Mon, 17 Mar 2025 14:34:08 -0700 +Subject: [PATCH 2/8] AppRestrictions - use vetted component + +After vetting the intent, use the component we used for the vetting. + +Bug: 353680402 +Bug: 365739560 +Test: manual +Flag: EXEMPT bugfix +Change-Id: Iff0d820c1261c29eb6703bf89194339cba700688 +Merged-In: Iff0d820c1261c29eb6703bf89194339cba700688 +(cherry picked from commit d3e34060803c97ae05719fe9301026e5c54892c8) +--- + .../users/AppRestrictionsFragment.java | 20 ++++++++++++++++--- + 1 file changed, 17 insertions(+), 3 deletions(-) + +diff --git a/src/com/android/settings/users/AppRestrictionsFragment.java b/src/com/android/settings/users/AppRestrictionsFragment.java +index c42e2f57b1d..c4a01797d0e 100644 +--- a/src/com/android/settings/users/AppRestrictionsFragment.java ++++ b/src/com/android/settings/users/AppRestrictionsFragment.java +@@ -639,8 +639,11 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen + } else if (restrictionsIntent != null) { + preference.setRestrictions(restrictions); + if (invokeIfCustom && AppRestrictionsFragment.this.isResumed()) { ++ // We don't necessarily trust the given intent to launch its component. ++ // We will first check it, and only use parts of it that were indeed checked. ++ final Intent vettedIntent; + try { +- assertSafeToStartCustomActivity(restrictionsIntent); ++ vettedIntent = assertSafeToStartCustomActivity(restrictionsIntent); + } catch (ActivityNotFoundException | SecurityException e) { + // return without startActivity + Log.e(TAG, "Cannot start restrictionsIntent " + e); +@@ -651,12 +654,16 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen + int requestCode = generateCustomActivityRequestCode( + RestrictionsResultReceiver.this.preference); + AppRestrictionsFragment.this.startActivityForResult( +- new Intent(restrictionsIntent), requestCode); ++ vettedIntent, requestCode); + } + } + } + +- private void assertSafeToStartCustomActivity(Intent intent) { ++ /** ++ * Checks that it is safe to start the custom activity, and, if so, returns a copy of the ++ * Intent using its vetted components. ++ */ ++ private Intent assertSafeToStartCustomActivity(Intent intent) { + EventLog.writeEvent(0x534e4554, "223578534", -1 /* UID */, ""); + ResolveInfo resolveInfo = mPackageManager.resolveActivity( + intent, PackageManager.MATCH_DEFAULT_ONLY); +@@ -670,6 +677,13 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen + throw new SecurityException("Application " + packageName + + " is not allowed to start activity " + intent); + } ++ ++ // We were able to vet the given intent this time. Make a copy using the components ++ // that were used to do the vetting, since that's as much as we've verified is safe. ++ final Intent vettedIntent = new Intent(intent); ++ vettedIntent.setComponent(activityInfo.getComponentName()); ++ vettedIntent.setPackage(activityInfo.packageName); ++ return vettedIntent; + } + } + +-- +2.34.1 + diff --git a/aosp_diff/caas/packages/apps/Settings/0003-Do-not-enable-the-Content-Protection-toggle-for-non-.patch b/aosp_diff/caas/packages/apps/Settings/0003-Do-not-enable-the-Content-Protection-toggle-for-non-.patch new file mode 100644 index 0000000..81e889a --- /dev/null +++ b/aosp_diff/caas/packages/apps/Settings/0003-Do-not-enable-the-Content-Protection-toggle-for-non-.patch @@ -0,0 +1,74 @@ +From 3c16284c94302f3eb92fdb5613280c00cd11e1cf Mon Sep 17 00:00:00 2001 +From: Adam Bookatz +Date: Thu, 24 Apr 2025 09:53:42 -0700 +Subject: [PATCH 3/8] Do not enable the Content Protection toggle for non-admin + users. + +Flag: EXEMPT bugfix +Bug: 409318132 +Test: m -j256 Settings && atest +SettingsRoboTests:ContentProtectionTogglePreferenceControllerTest +Change-Id: I46609c795923d427a5b7fa10bc2e8b071fad72d6 +(cherry picked from commit ef801e1a8ec3a18ce9e0221fc7e1dfe495d0be8a) +--- + .../ContentProtectionTogglePreferenceController.java | 2 +- + ...ontentProtectionTogglePreferenceControllerTest.java | 10 ++++++---- + 2 files changed, 7 insertions(+), 5 deletions(-) + +diff --git a/src/com/android/settings/security/ContentProtectionTogglePreferenceController.java b/src/com/android/settings/security/ContentProtectionTogglePreferenceController.java +index 69ac6b100be..61987cdd8ad 100644 +--- a/src/com/android/settings/security/ContentProtectionTogglePreferenceController.java ++++ b/src/com/android/settings/security/ContentProtectionTogglePreferenceController.java +@@ -132,7 +132,7 @@ public class ContentProtectionTogglePreferenceController extends TogglePreferenc + + UserManager userManager = mContext.getSystemService(UserManager.class); + if (userManager != null +- && userManager.isGuestUser() ++ && !userManager.isAdminUser() + && mSwitchBar != null) { + mSwitchBar.setEnabled(false); + } +diff --git a/tests/robotests/src/com/android/settings/security/ContentProtectionTogglePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/security/ContentProtectionTogglePreferenceControllerTest.java +index 6514a4e4043..5f63da19fe6 100644 +--- a/tests/robotests/src/com/android/settings/security/ContentProtectionTogglePreferenceControllerTest.java ++++ b/tests/robotests/src/com/android/settings/security/ContentProtectionTogglePreferenceControllerTest.java +@@ -85,7 +85,7 @@ public class ContentProtectionTogglePreferenceControllerTest { + @Before + public void setUp() { + mShadowUserManager = ShadowUserManager.getShadow(); +- mShadowUserManager.setGuestUser(false); ++ mShadowUserManager.setIsAdminUser(true); + mController = new TestContentProtectionTogglePreferenceController(); + SettingsMainSwitchPreference switchPreference = new SettingsMainSwitchPreference(mContext); + when(mMockPreferenceScreen.findPreference(mController.getPreferenceKey())) +@@ -277,8 +277,8 @@ public class ContentProtectionTogglePreferenceControllerTest { + } + + @Test +- public void updateState_flagEnabled_noEnforcedAdmin_guestUser_switchBarDisabled() { +- mShadowUserManager.setGuestUser(true); ++ public void updateState_flagEnabled_noEnforcedAdmin_nonAdminUser_switchBarDisabled() { ++ mShadowUserManager.setIsAdminUser(false); + mSetFlagsRule.enableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED); + mContentProtectionPolicy = DevicePolicyManager.CONTENT_PROTECTION_ENABLED; + setupForUpdateState(); +@@ -289,13 +289,15 @@ public class ContentProtectionTogglePreferenceControllerTest { + } + + @Test +- public void updateState_flagEnabled_noEnforcedAdmin_nonGuestUser_switchBarEnabled() { ++ public void updateState_flagEnabled_noEnforcedAdmin_adminUser_switchBarEnabled() { ++ mShadowUserManager.setIsAdminUser(true); + mSetFlagsRule.enableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED); + mContentProtectionPolicy = DevicePolicyManager.CONTENT_PROTECTION_ENABLED; + setupForUpdateState(); + + mController.updateState(mMockSwitchPreference); + ++ // Verify that the switch bar is *not* set to disabled. + verify(mMockSwitchPreference, never()).setEnabled(false); + } + +-- +2.34.1 + diff --git a/aosp_diff/caas/packages/apps/Settings/0004-Drop-PendingIntent-extras-from-external-packages-dur.patch b/aosp_diff/caas/packages/apps/Settings/0004-Drop-PendingIntent-extras-from-external-packages-dur.patch new file mode 100644 index 0000000..e4b9eae --- /dev/null +++ b/aosp_diff/caas/packages/apps/Settings/0004-Drop-PendingIntent-extras-from-external-packages-dur.patch @@ -0,0 +1,200 @@ +From 617a4ba05b00ddba70b0104f52115f61cef3864c Mon Sep 17 00:00:00 2001 +From: Joe Bolinger +Date: Sat, 5 Apr 2025 02:30:30 +0000 +Subject: [PATCH 4/8] Drop PendingIntent extras from external packages during + enrollment. + +Bug: 388528350 +Flag: EXEMPT bugfix +Test: atest FingerprintEnrollIntroductionTest FaceEnrollIntroductionTest +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:4ccdeee849d5fef78498ba33cadc525523efcbd7) +Merged-In: I61281dcf95e53100a96d6a218f3f00fd1b4ea3f9 +Change-Id: I61281dcf95e53100a96d6a218f3f00fd1b4ea3f9 +--- + .../BiometricEnrollIntroduction.java | 20 +++++++++++++-- + .../face/FaceEnrollIntroductionTest.java | 21 ++++++++++++++++ + .../FingerprintEnrollIntroductionTest.java | 25 +++++++++++++++++++ + 3 files changed, 64 insertions(+), 2 deletions(-) + +diff --git a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java +index 1f7b3e512b2..79c7e3a563a 100644 +--- a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java ++++ b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java +@@ -50,6 +50,8 @@ import com.google.android.setupdesign.span.LinkSpan; + import com.google.android.setupdesign.template.RequireScrollMixin; + import com.google.android.setupdesign.util.DynamicColorPalette; + ++import java.util.List; ++ + /** + * Abstract base class for the intro onboarding activity for biometric enrollment. + */ +@@ -242,6 +244,19 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase + !isScrollNeeded && !enrollmentCompleted ? View.VISIBLE : View.INVISIBLE); + } + ++ @Override ++ protected void onStart() { ++ super.onStart(); ++ ++ if (!getPackageName().equals(getCallingPackage())) { ++ for (String key : List.of(MultiBiometricEnrollHelper.EXTRA_SKIP_PENDING_ENROLL, ++ MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE, ++ MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FINGERPRINT)) { ++ getIntent().removeExtra(key); ++ } ++ } ++ } ++ + @Override + protected void onResume() { + super.onResume(); +@@ -490,14 +505,15 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase + getIntent().removeExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FINGERPRINT); + } + +- protected void removeEnrollNextBiometricIfSkipEnroll(@Nullable Intent data) { ++ private void removeEnrollNextBiometricIfSkipEnroll(@Nullable Intent data) { + if (data != null + && data.getBooleanExtra( + MultiBiometricEnrollHelper.EXTRA_SKIP_PENDING_ENROLL, false)) { + removeEnrollNextBiometric(); + } + } +- protected void handleBiometricResultSkipOrFinished(int resultCode, @Nullable Intent data) { ++ ++ private void handleBiometricResultSkipOrFinished(int resultCode, @Nullable Intent data) { + removeEnrollNextBiometricIfSkipEnroll(data); + if (resultCode == RESULT_SKIP) { + onEnrollmentSkipped(data); +diff --git a/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollIntroductionTest.java b/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollIntroductionTest.java +index 81a72694592..984073f19b5 100644 +--- a/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollIntroductionTest.java ++++ b/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollIntroductionTest.java +@@ -32,11 +32,13 @@ import static com.google.common.truth.Truth.assertWithMessage; + import static org.mockito.ArgumentMatchers.any; + import static org.mockito.ArgumentMatchers.anyInt; + import static org.mockito.Mockito.doAnswer; ++import static org.mockito.Mockito.mock; + import static org.mockito.Mockito.spy; + import static org.mockito.Mockito.verify; + import static org.mockito.Mockito.when; + + import android.app.Activity; ++import android.app.PendingIntent; + import android.content.Context; + import android.content.DialogInterface; + import android.content.Intent; +@@ -64,6 +66,7 @@ import com.android.settings.R; + import com.android.settings.Settings; + import com.android.settings.biometrics.BiometricEnrollBase; + import com.android.settings.biometrics.BiometricUtils; ++import com.android.settings.biometrics.MultiBiometricEnrollHelper; + import com.android.settings.password.ChooseLockSettingsHelper; + import com.android.settings.testutils.FakeFeatureFactory; + import com.android.settings.testutils.shadow.SettingsShadowResources; +@@ -206,6 +209,12 @@ public class FaceEnrollIntroductionTest { + testIntent.putExtra(BiometricUtils.EXTRA_ENROLL_REASON, + FaceEnrollOptions.ENROLL_REASON_SETTINGS); + ++ testIntent.putExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE, ++ mock(PendingIntent.class)); ++ testIntent.putExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FINGERPRINT, ++ mock(PendingIntent.class)); ++ testIntent.putExtra(MultiBiometricEnrollHelper.EXTRA_SKIP_PENDING_ENROLL, false); ++ + when(mFakeFeatureFactory.mFaceFeatureProvider.getPostureGuidanceIntent(any())).thenReturn( + null /* Simulate no posture intent */); + mContext = spy(ApplicationProvider.getApplicationContext()); +@@ -690,4 +699,16 @@ public class FaceEnrollIntroductionTest { + .isEqualTo(FaceEnrollOptions.ENROLL_REASON_SETTINGS); + } + ++ @Test ++ public void drops_pendingIntents() { ++ setupActivity(); ++ ++ mController.start(); ++ Shadows.shadowOf(Looper.getMainLooper()).idle(); ++ ++ final Intent intent = mActivity.getIntent(); ++ assertThat(intent.hasExtra(MultiBiometricEnrollHelper.EXTRA_SKIP_PENDING_ENROLL)).isFalse(); ++ assertThat(intent.hasExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE)).isFalse(); ++ assertThat(intent.hasExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FINGERPRINT)).isFalse(); ++ } + } +diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroductionTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroductionTest.java +index edd50a6e6f6..24fb7165fd0 100644 +--- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroductionTest.java ++++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroductionTest.java +@@ -34,6 +34,7 @@ import static org.mockito.Mockito.mock; + import static org.mockito.Mockito.spy; + import static org.mockito.Mockito.when; + ++import android.app.PendingIntent; + import android.content.Context; + import android.content.Intent; + import android.content.res.Resources; +@@ -44,6 +45,7 @@ import android.hardware.fingerprint.FingerprintEnrollOptions; + import android.hardware.fingerprint.FingerprintManager; + import android.hardware.fingerprint.FingerprintSensorProperties; + import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; ++import android.os.Looper; + import android.os.UserManager; + import android.view.View; + +@@ -55,6 +57,7 @@ import com.android.internal.widget.VerifyCredentialResponse; + import com.android.settings.R; + import com.android.settings.biometrics.BiometricUtils; + import com.android.settings.biometrics.GatekeeperPasswordProvider; ++import com.android.settings.biometrics.MultiBiometricEnrollHelper; + + import com.google.android.setupcompat.util.WizardManagerHelper; + import com.google.android.setupdesign.GlifLayout; +@@ -70,6 +73,7 @@ import org.mockito.stubbing.Answer; + import org.robolectric.Robolectric; + import org.robolectric.RobolectricTestRunner; + import org.robolectric.RuntimeEnvironment; ++import org.robolectric.Shadows; + import org.robolectric.android.controller.ActivityController; + + import java.util.ArrayList; +@@ -353,7 +357,19 @@ public class FingerprintEnrollIntroductionTest { + false); + Assert.assertEquals(View.INVISIBLE, + mFingerprintEnrollIntroduction.getSecondaryFooterButton().getVisibility()); ++ } ++ ++ @Test ++ public void drops_pendingIntents() { ++ setupFingerprintEnrollIntroWith(newExternalPendingIntent()); ++ ++ mController.start(); ++ Shadows.shadowOf(Looper.getMainLooper()).idle(); + ++ final Intent intent = mFingerprintEnrollIntroduction.getIntent(); ++ assertThat(intent.hasExtra(MultiBiometricEnrollHelper.EXTRA_SKIP_PENDING_ENROLL)).isFalse(); ++ assertThat(intent.hasExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE)).isFalse(); ++ assertThat(intent.hasExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FINGERPRINT)).isFalse(); + } + + private Intent newTokenOnlyIntent() { +@@ -383,6 +399,15 @@ public class FingerprintEnrollIntroductionTest { + .putExtra(EXTRA_KEY_GK_PW_HANDLE, 1L); + } + ++ private Intent newExternalPendingIntent() { ++ return newTokenOnlyIntent() ++ .putExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE, ++ mock(PendingIntent.class)) ++ .putExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FINGERPRINT, ++ mock(PendingIntent.class)) ++ .putExtra(MultiBiometricEnrollHelper.EXTRA_SKIP_PENDING_ENROLL, false); ++ } ++ + private VerifyCredentialResponse newGoodCredential(long gkPwHandle, @NonNull byte[] hat) { + return new VerifyCredentialResponse.Builder() + .setGatekeeperPasswordHandle(gkPwHandle) +-- +2.34.1 + diff --git a/aosp_diff/caas/packages/apps/Settings/0005-Use-correct-API-to-get-calling-package-name-in-Crede.patch b/aosp_diff/caas/packages/apps/Settings/0005-Use-correct-API-to-get-calling-package-name-in-Crede.patch new file mode 100644 index 0000000..d5dfcba --- /dev/null +++ b/aosp_diff/caas/packages/apps/Settings/0005-Use-correct-API-to-get-calling-package-name-in-Crede.patch @@ -0,0 +1,63 @@ +From 9a18a6fcafc27fd408a0c990f0d1efcf57237fb7 Mon Sep 17 00:00:00 2001 +From: Rubin Xu +Date: Wed, 21 May 2025 15:34:51 +0100 +Subject: [PATCH 5/8] Use correct API to get calling package name in + CredentialStorage + +Activity.getCallingPackage() does not always return the package +name of the actual calling app. getLaunchedFromPackage() should +be used instead. + +Bug: 389681530 +Test: manual +Flag: EXEMPT bugfix +Merged-In: Ibdbc45e53f4aa46fae79fa234705b3735bfda4cd +Change-Id: Ibdbc45e53f4aa46fae79fa234705b3735bfda4cd +(cherry picked from commit 70bd3efe0674bccb0d454845d86fb2402779a7bf) +--- + .../settings/security/CredentialStorage.java | 15 +++++++++++++-- + 1 file changed, 13 insertions(+), 2 deletions(-) + +diff --git a/src/com/android/settings/security/CredentialStorage.java b/src/com/android/settings/security/CredentialStorage.java +index b1c65a7c3c0..5ea9b7ac21f 100644 +--- a/src/com/android/settings/security/CredentialStorage.java ++++ b/src/com/android/settings/security/CredentialStorage.java +@@ -17,6 +17,7 @@ + package com.android.settings.security; + + import android.app.Activity; ++import android.app.ActivityManager; + import android.app.admin.DevicePolicyManager; + import android.content.Context; + import android.content.DialogInterface; +@@ -322,15 +323,25 @@ public final class CredentialStorage extends FragmentActivity { + } + } + ++ private String getCallingPackageName() { ++ try { ++ return ActivityManager.getService().getLaunchedFromPackage(getActivityToken()); ++ } catch (RemoteException re) { ++ // Error talking to ActivityManager, just give up ++ return null; ++ } ++ } ++ + /** + * Check that the caller is either certinstaller or Settings running in a profile of this user. + */ + private boolean checkCallerIsCertInstallerOrSelfInProfile() { +- if (TextUtils.equals("com.android.certinstaller", getCallingPackage())) { ++ String callingPackage = getCallingPackageName(); ++ if (TextUtils.equals("com.android.certinstaller", callingPackage)) { + // CertInstaller is allowed to install credentials if it has the same signature as + // Settings package. + return getPackageManager().checkSignatures( +- getCallingPackage(), getPackageName()) == PackageManager.SIGNATURE_MATCH; ++ callingPackage, getPackageName()) == PackageManager.SIGNATURE_MATCH; + } + + final int launchedFromUserId; +-- +2.34.1 + diff --git a/aosp_diff/caas/packages/apps/Settings/0006-Hide-notification-content-in-history.patch b/aosp_diff/caas/packages/apps/Settings/0006-Hide-notification-content-in-history.patch new file mode 100644 index 0000000..abcf5a6 --- /dev/null +++ b/aosp_diff/caas/packages/apps/Settings/0006-Hide-notification-content-in-history.patch @@ -0,0 +1,234 @@ +From 8c4b1d9224fa71b138ec09b5777810e85fd53ab9 Mon Sep 17 00:00:00 2001 +From: Julia Reynolds +Date: Mon, 12 May 2025 14:50:40 -0400 +Subject: [PATCH 6/8] Hide notification content in history + +- if the user is locked +- and the user has chosen to hide sensistive content when locked + +Test: manual with a work profile with a different pin +Bug: 378088320 +Flag: EXEMPT bug fix +Change-Id: Ia70454d9859fb788ffa1f48f88760f88c354cdff +(cherry picked from commit 9df37c3f8be2dedd2e44e52da4de45fba33c6a6e) +--- + .../history/NotificationHistoryActivity.java | 32 ++++++++++++++++--- + .../history/NotificationHistoryAdapter.java | 22 ++++++++++--- + .../history/NotificationSbnAdapter.java | 15 +++++++-- + 3 files changed, 58 insertions(+), 11 deletions(-) + +diff --git a/src/com/android/settings/notification/history/NotificationHistoryActivity.java b/src/com/android/settings/notification/history/NotificationHistoryActivity.java +index 156df96e04e..701b94c749f 100644 +--- a/src/com/android/settings/notification/history/NotificationHistoryActivity.java ++++ b/src/com/android/settings/notification/history/NotificationHistoryActivity.java +@@ -16,6 +16,7 @@ + + package com.android.settings.notification.history; + ++import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS; + import static android.provider.Settings.Secure.NOTIFICATION_HISTORY_ENABLED; + + import static androidx.core.view.accessibility.AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUSED; +@@ -25,9 +26,11 @@ import android.annotation.ColorInt; + import android.app.ActionBar; + import android.app.ActivityManager; + import android.app.INotificationManager; ++import android.app.KeyguardManager; + import android.content.ComponentName; + import android.content.Context; + import android.content.pm.PackageManager; ++import android.content.pm.UserInfo; + import android.content.res.Resources; + import android.content.res.TypedArray; + import android.graphics.Outline; +@@ -58,6 +61,7 @@ import androidx.recyclerview.widget.RecyclerView; + import com.android.internal.logging.UiEvent; + import com.android.internal.logging.UiEventLogger; + import com.android.internal.logging.UiEventLoggerImpl; ++import com.android.internal.widget.LockPatternUtils; + import com.android.internal.widget.NotificationExpandButton; + import com.android.settings.R; + import com.android.settings.notification.NotificationBackend; +@@ -68,6 +72,7 @@ import com.android.settingslib.widget.MainSwitchBar; + + import java.util.ArrayList; + import java.util.Arrays; ++import java.util.List; + import java.util.concurrent.CountDownLatch; + import java.util.concurrent.Future; + import java.util.concurrent.TimeUnit; +@@ -113,6 +118,9 @@ public class NotificationHistoryActivity extends CollapsingToolbarBaseActivity { + }; + private UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); + ++ // List of users that have the setting "hide sensitive content" enabled on the lockscreen ++ private ArrayList mContentRestrictedUsers = new ArrayList<>(); ++ + enum NotificationHistoryEvent implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "User turned on notification history") + NOTIFICATION_HISTORY_ON(504), +@@ -212,14 +220,14 @@ public class NotificationHistoryActivity extends CollapsingToolbarBaseActivity { + + final NotificationHistoryRecyclerView rv = + viewForPackage.findViewById(R.id.notification_list); +- rv.setAdapter(new NotificationHistoryAdapter(mNm, rv, ++ rv.setAdapter(new NotificationHistoryAdapter(NotificationHistoryActivity.this, mNm, rv, + newCount -> { + count.setText(StringUtil.getIcuPluralsString(this, newCount, + R.string.notification_history_count)); + if (newCount == 0) { + viewForPackage.setVisibility(View.GONE); + } +- }, mUiEventLogger)); ++ }, mUiEventLogger, mContentRestrictedUsers)); + ((NotificationHistoryAdapter) rv.getAdapter()).onRebuildComplete( + new ArrayList<>(nhp.notifications)); + +@@ -263,6 +271,21 @@ public class NotificationHistoryActivity extends CollapsingToolbarBaseActivity { + + mPm = getPackageManager(); + mUm = getSystemService(UserManager.class); ++ ++ mContentRestrictedUsers.clear(); ++ List users = mUm.getProfiles(getUserId()); ++ mContentRestrictedUsers.clear(); ++ for (UserInfo user : users) { ++ if (Settings.Secure.getIntForUser(getContentResolver(), ++ LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, user.id) == 0) { ++ LockPatternUtils lpu = new LockPatternUtils(this); ++ KeyguardManager km = getSystemService(KeyguardManager.class); ++ if (lpu.isSecure(user.id) && km.isDeviceLocked(user.id)) { ++ mContentRestrictedUsers.add(user.id); ++ } ++ } ++ } ++ + // wait for history loading and recent/snooze loading + mCountdownLatch = new CountDownLatch(2); + +@@ -317,6 +340,7 @@ public class NotificationHistoryActivity extends CollapsingToolbarBaseActivity { + if (mCountdownFuture != null) { + mCountdownFuture.cancel(true); + } ++ mContentRestrictedUsers.clear(); + super.onDestroy(); + } + +@@ -406,7 +430,7 @@ public class NotificationHistoryActivity extends CollapsingToolbarBaseActivity { + mSnoozedRv.setLayoutManager(lm); + mSnoozedRv.setAdapter( + new NotificationSbnAdapter(NotificationHistoryActivity.this, mPm, mUm, +- true, mUiEventLogger)); ++ true, mUiEventLogger, mContentRestrictedUsers)); + mSnoozedRv.setNestedScrollingEnabled(false); + + if (snoozed == null || snoozed.length == 0) { +@@ -422,7 +446,7 @@ public class NotificationHistoryActivity extends CollapsingToolbarBaseActivity { + mDismissedRv.setLayoutManager(dismissLm); + mDismissedRv.setAdapter( + new NotificationSbnAdapter(NotificationHistoryActivity.this, mPm, mUm, +- false, mUiEventLogger)); ++ false, mUiEventLogger, mContentRestrictedUsers)); + mDismissedRv.setNestedScrollingEnabled(false); + + if (dismissed == null || dismissed.length == 0) { +diff --git a/src/com/android/settings/notification/history/NotificationHistoryAdapter.java b/src/com/android/settings/notification/history/NotificationHistoryAdapter.java +index 5368f25e76e..44e15442a1a 100644 +--- a/src/com/android/settings/notification/history/NotificationHistoryAdapter.java ++++ b/src/com/android/settings/notification/history/NotificationHistoryAdapter.java +@@ -22,6 +22,7 @@ import static android.provider.Settings.EXTRA_CONVERSATION_ID; + + import android.app.INotificationManager; + import android.app.NotificationHistory.HistoricalNotification; ++import android.content.Context; + import android.content.Intent; + import android.os.Bundle; + import android.os.RemoteException; +@@ -53,16 +54,23 @@ public class NotificationHistoryAdapter extends + private List mValues; + private OnItemDeletedListener mListener; + private UiEventLogger mUiEventLogger; +- public NotificationHistoryAdapter(INotificationManager nm, ++ private ArrayList mContentRestrictedUsers = new ArrayList<>(); ++ Context mContext; ++ ++ public NotificationHistoryAdapter(Context context, ++ INotificationManager nm, + NotificationHistoryRecyclerView listView, + OnItemDeletedListener listener, +- UiEventLogger uiEventLogger) { ++ UiEventLogger uiEventLogger, ++ ArrayList contentRestrictedUsers) { ++ mContext = context; + mValues = new ArrayList<>(); + setHasStableIds(true); + listView.setOnItemSwipeDeleteListener(this); + mNm = nm; + mListener = listener; + mUiEventLogger = uiEventLogger; ++ mContentRestrictedUsers = contentRestrictedUsers; + } + + @Override +@@ -81,8 +89,14 @@ public class NotificationHistoryAdapter extends + public void onBindViewHolder(final @NonNull NotificationHistoryViewHolder holder, + int position) { + final HistoricalNotification hn = mValues.get(position); +- holder.setTitle(hn.getTitle()); +- holder.setSummary(hn.getText()); ++ // Redact sensitive notification content if needed ++ if (mContentRestrictedUsers.contains(hn.getUserId())) { ++ holder.setSummary(mContext.getString( ++ com.android.internal.R.string.notification_hidden_text)); ++ } else { ++ holder.setTitle(hn.getTitle()); ++ holder.setSummary(hn.getText()); ++ } + holder.setPostedTime(hn.getPostedTimeMs()); + final View.OnClickListener onClick = v -> { + mUiEventLogger.logWithPosition(NotificationHistoryActivity.NotificationHistoryEvent +diff --git a/src/com/android/settings/notification/history/NotificationSbnAdapter.java b/src/com/android/settings/notification/history/NotificationSbnAdapter.java +index 0301d7b7fd5..844e9bc980f 100644 +--- a/src/com/android/settings/notification/history/NotificationSbnAdapter.java ++++ b/src/com/android/settings/notification/history/NotificationSbnAdapter.java +@@ -74,9 +74,11 @@ public class NotificationSbnAdapter extends + private List mEnabledProfiles = new ArrayList<>(); + private boolean mIsSnoozed; + private UiEventLogger mUiEventLogger; ++ private ArrayList mContentRestrictedUsers = new ArrayList<>(); + + public NotificationSbnAdapter(Context context, PackageManager pm, UserManager um, +- boolean isSnoozed, UiEventLogger uiEventLogger) { ++ boolean isSnoozed, UiEventLogger uiEventLogger, ++ ArrayList contentRestrictedUsers) { + mContext = context; + mPm = pm; + mUserBadgeCache = new HashMap<>(); +@@ -97,6 +99,7 @@ public class NotificationSbnAdapter extends + // If true, this is the panel for snoozed notifs, otherwise the one for dismissed notifs. + mIsSnoozed = isSnoozed; + mUiEventLogger = uiEventLogger; ++ mContentRestrictedUsers = contentRestrictedUsers; + } + + @Override +@@ -114,8 +117,14 @@ public class NotificationSbnAdapter extends + holder.setIconBackground(loadBackground(sbn)); + holder.setIcon(loadIcon(sbn)); + holder.setPackageLabel(loadPackageLabel(sbn.getPackageName()).toString()); +- holder.setTitle(getTitleString(sbn.getNotification())); +- holder.setSummary(getTextString(mContext, sbn.getNotification())); ++ // If the notification is from a content restricted user, show generic text. ++ if (mContentRestrictedUsers.contains(sbn.getNormalizedUserId())) { ++ holder.setSummary(mContext.getString( ++ com.android.internal.R.string.notification_hidden_text)); ++ } else { ++ holder.setTitle(getTitleString(sbn.getNotification())); ++ holder.setSummary(getTextString(mContext, sbn.getNotification())); ++ } + holder.setPostedTime(sbn.getPostTime()); + holder.setDividerVisible(position < (mValues.size() -1)); + int userId = normalizeUserId(sbn); +-- +2.34.1 + diff --git a/aosp_diff/caas/packages/apps/Settings/0007-startActivityForResult-with-earlier-new-Intent.patch b/aosp_diff/caas/packages/apps/Settings/0007-startActivityForResult-with-earlier-new-Intent.patch new file mode 100644 index 0000000..b01a6c7 --- /dev/null +++ b/aosp_diff/caas/packages/apps/Settings/0007-startActivityForResult-with-earlier-new-Intent.patch @@ -0,0 +1,46 @@ +From d2d5525c128cb11909889562381a0b8d3a630ad3 Mon Sep 17 00:00:00 2001 +From: Adam Bookatz +Date: Wed, 11 Dec 2024 17:22:46 -0800 +Subject: [PATCH 7/8] startActivityForResult with earlier new Intent + +We already make sure to use a copy of the Intent, but now we do so +earlier. See bug. + +Bug: 353680402 +Flag: EXEMPT bugfix +Test: manual +Test: atest com.android.settings.users.UserSettingsTest + com.android.settings.users.UserDetailsSettingsTest +Change-Id: I860e9e606de6b8d3c99fa52a63b72ba7a99ce179 +Merged-In: I860e9e606de6b8d3c99fa52a63b72ba7a99ce179 +(cherry picked from commit b7240e2f0c50455a1c8f3ae1fc4f27d55b86e89b) +--- + src/com/android/settings/users/AppRestrictionsFragment.java | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/com/android/settings/users/AppRestrictionsFragment.java b/src/com/android/settings/users/AppRestrictionsFragment.java +index c4a01797d0e..5b2a86fff42 100644 +--- a/src/com/android/settings/users/AppRestrictionsFragment.java ++++ b/src/com/android/settings/users/AppRestrictionsFragment.java +@@ -665,8 +665,9 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen + */ + private Intent assertSafeToStartCustomActivity(Intent intent) { + EventLog.writeEvent(0x534e4554, "223578534", -1 /* UID */, ""); ++ final Intent vettedIntent = new Intent(intent); + ResolveInfo resolveInfo = mPackageManager.resolveActivity( +- intent, PackageManager.MATCH_DEFAULT_ONLY); ++ vettedIntent, PackageManager.MATCH_DEFAULT_ONLY); + + if (resolveInfo == null) { + throw new ActivityNotFoundException("No result for resolving " + intent); +@@ -680,7 +681,6 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen + + // We were able to vet the given intent this time. Make a copy using the components + // that were used to do the vetting, since that's as much as we've verified is safe. +- final Intent vettedIntent = new Intent(intent); + vettedIntent.setComponent(activityInfo.getComponentName()); + vettedIntent.setPackage(activityInfo.packageName); + return vettedIntent; +-- +2.34.1 + diff --git a/aosp_diff/caas/packages/apps/Settings/0008-Ignore-face-settings-extras-when-called-by-an-extern.patch b/aosp_diff/caas/packages/apps/Settings/0008-Ignore-face-settings-extras-when-called-by-an-extern.patch new file mode 100644 index 0000000..73897cd --- /dev/null +++ b/aosp_diff/caas/packages/apps/Settings/0008-Ignore-face-settings-extras-when-called-by-an-extern.patch @@ -0,0 +1,59 @@ +From 9d01b28253b935ece58d2ecafc82ce95214a5db1 Mon Sep 17 00:00:00 2001 +From: Joe Bolinger +Date: Sat, 7 Jun 2025 03:02:15 +0000 +Subject: [PATCH 8/8] Ignore face settings extras when called by an external + package. + +Bug: 411418366 +Flag: EXEMPT bug fix +Test: manual from any user not 0 (adb shell am start -a android.settings.FACE_SETTINGS --ei android.intent.extra.USER_ID 0) +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:486947205e05e83314bd76e4822af442ca82be9c) +Merged-In: I06193e421a140a90568251fc25baa7fc81c12d78 +Change-Id: I06193e421a140a90568251fc25baa7fc81c12d78 +--- + .../biometrics/face/FaceSettings.java | 21 ++++++++++++------- + 1 file changed, 14 insertions(+), 7 deletions(-) + +diff --git a/src/com/android/settings/biometrics/face/FaceSettings.java b/src/com/android/settings/biometrics/face/FaceSettings.java +index 5a3949b8923..f69d8252627 100644 +--- a/src/com/android/settings/biometrics/face/FaceSettings.java ++++ b/src/com/android/settings/biometrics/face/FaceSettings.java +@@ -83,8 +83,8 @@ public class FaceSettings extends DashboardFragment { + private FaceManager mFaceManager; + private DevicePolicyManager mDevicePolicyManager; + private int mUserId; +- private int mSensorId; +- private long mChallenge; ++ private int mSensorId = -1; ++ private long mChallenge = 0; + private byte[] mToken; + private FaceSettingsAttentionPreferenceController mAttentionController; + private FaceSettingsRemoveButtonPreferenceController mRemoveController; +@@ -166,12 +166,19 @@ public class FaceSettings extends DashboardFragment { + mUserManager = context.getSystemService(UserManager.class); + mFaceManager = context.getSystemService(FaceManager.class); + mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class); +- mToken = getIntent().getByteArrayExtra(KEY_TOKEN); +- mSensorId = getIntent().getIntExtra(BiometricEnrollBase.EXTRA_KEY_SENSOR_ID, -1); +- mChallenge = getIntent().getLongExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, 0L); + +- mUserId = getActivity().getIntent().getIntExtra( +- Intent.EXTRA_USER_ID, UserHandle.myUserId()); ++ final SettingsActivity activity = (SettingsActivity) requireActivity(); ++ final String callingPackage = activity.getInitialCallingPackage(); ++ if (callingPackage == null || !callingPackage.equals(activity.getPackageName())) { ++ mUserId = UserHandle.myUserId(); ++ } else { ++ // only allow these extras when called internally by Settings ++ mToken = getIntent().getByteArrayExtra(KEY_TOKEN); ++ mSensorId = getIntent().getIntExtra(BiometricEnrollBase.EXTRA_KEY_SENSOR_ID, -1); ++ mChallenge = getIntent().getLongExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, 0L); ++ mUserId = getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId()); ++ } ++ + mFaceFeatureProvider = FeatureFactory.getFeatureFactory().getFaceFeatureProvider(); + + if (mUserManager.getUserInfo(mUserId).isManagedProfile()) { +-- +2.34.1 + diff --git a/aosp_diff/caas/packages/modules/Bluetooth/0001-Remove-flags-for-b-314331379.patch b/aosp_diff/caas/packages/modules/Bluetooth/0001-Remove-flags-for-b-314331379.patch new file mode 100644 index 0000000..ddf1c43 --- /dev/null +++ b/aosp_diff/caas/packages/modules/Bluetooth/0001-Remove-flags-for-b-314331379.patch @@ -0,0 +1,51 @@ +From 77d700ed6e9963da5950a8edb5fee9ffbef2fa3c Mon Sep 17 00:00:00 2001 +From: Brian Delwiche +Date: Tue, 2 Jul 2024 17:27:29 +0000 +Subject: [PATCH 1/2] Remove flags for b#314331379 + +bluffs_mitigation has passed soak and flags for it should be removed. + +Bug: 314331379 +Bug: 319541706 +Flag: bluffs_mitigation +Test: m libbluetooth +Tag: #security +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:9fc2e1e6d449e2665fc5d5d9af4ca87346863960) +(cherry picked from https://android-review.googlesource.com/q/commit:34a5ceced396f40eae30747d295e07d31f18e59d) +Merged-In: I2f2743e1bec281823cd0ca4cb67e281f3aa1df48 +Change-Id: I2f2743e1bec281823cd0ca4cb67e281f3aa1df48 +--- + system/stack/btm/btm_sec.cc | 10 +++++++--- + 1 file changed, 7 insertions(+), 3 deletions(-) + +diff --git a/system/stack/btm/btm_sec.cc b/system/stack/btm/btm_sec.cc +index 08e2a3371e..4a83501c63 100644 +--- a/system/stack/btm/btm_sec.cc ++++ b/system/stack/btm/btm_sec.cc +@@ -3475,7 +3475,13 @@ void btm_sec_encryption_change_evt(uint16_t handle, tHCI_STATUS status, uint8_t + handle, base::Bind(&read_encryption_key_size_complete_after_encryption_change)); + return; + } +- } ++ btm_acl_encrypt_change(handle, static_cast(status), encr_enable); ++ btm_sec_encrypt_change(handle, static_cast(status), encr_enable, 0); ++ } else { ++ btsnd_hcic_read_encryption_key_size( ++ handle, ++ base::Bind(&read_encryption_key_size_complete_after_encryption_change)); ++ } + + if (status == HCI_ERR_CONNECTION_TOUT) { + smp_cancel_start_encryption_attempt(); +@@ -3489,8 +3495,6 @@ void btm_sec_encryption_change_evt(uint16_t handle, tHCI_STATUS status, uint8_t + "stack::btu::btu_hcif::encryption_change_evt Encryption Failure"); + } + } +- btm_acl_encrypt_change(handle, static_cast(status), encr_enable); +- btm_sec_encrypt_change(handle, static_cast(status), encr_enable, 0); + } + /******************************************************************************* + * +-- +2.34.1 + diff --git a/aosp_diff/caas/packages/modules/Bluetooth/0002-Fix-use-after-free-in-acl_arbiter.patch b/aosp_diff/caas/packages/modules/Bluetooth/0002-Fix-use-after-free-in-acl_arbiter.patch new file mode 100644 index 0000000..2af1cf4 --- /dev/null +++ b/aosp_diff/caas/packages/modules/Bluetooth/0002-Fix-use-after-free-in-acl_arbiter.patch @@ -0,0 +1,39 @@ +From a7bb4ca2414c9aed8d754e98bad50dcc0a002fd2 Mon Sep 17 00:00:00 2001 +From: Brian Delwiche +Date: Thu, 3 Apr 2025 17:16:55 +0000 +Subject: [PATCH 2/2] Fix use after free in acl_arbiter + +In SendPacketToPeer of acl_arbiter.cc, a buffer length is logged in one +case after an intermediate call may free the buffer, leading to use +after free. + +Log instead from the buffer's source, which has not been freed at this +point in the code. + +Bug: 406785684 +Flag: EXEMPT obvious logic fix +Test: m libbluetooth +Test: researcher POC +Tag: #security +Change-Id: Idd13399c24399d01bcd668a4b779ef1980273691 +(cherry picked from commit 243d7484e59730c522640b616445b2747b3062e5) +--- + system/stack/arbiter/acl_arbiter.cc | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/system/stack/arbiter/acl_arbiter.cc b/system/stack/arbiter/acl_arbiter.cc +index a9c09deff4..5f37965e98 100644 +--- a/system/stack/arbiter/acl_arbiter.cc ++++ b/system/stack/arbiter/acl_arbiter.cc +@@ -115,7 +115,7 @@ void AclArbiter::SendPacketToPeer(uint8_t tcb_idx, ::rust::Vec buffer) + if (stack::l2cap::get_interface().L2CA_SendFixedChnlData(L2CAP_ATT_CID, p_tcb->peer_bda, + p_buf) != tL2CAP_DW_RESULT::SUCCESS) { + log::warn("Unable to send L2CAP data peer:{} fixed_cid:{} len:{}", p_tcb->peer_bda, +- L2CAP_ATT_CID, p_buf->len); ++ L2CAP_ATT_CID, buffer.size()); + } + } else { + log::error("Dropping packet since connection no longer exists"); +-- +2.34.1 + diff --git a/aosp_diff/caas/packages/modules/CellBroadcastService/0001-RESTRICT-AUTOMERGE-Update-getDefaultCBRPackageName-f.patch b/aosp_diff/caas/packages/modules/CellBroadcastService/0001-RESTRICT-AUTOMERGE-Update-getDefaultCBRPackageName-f.patch new file mode 100644 index 0000000..c5b7832 --- /dev/null +++ b/aosp_diff/caas/packages/modules/CellBroadcastService/0001-RESTRICT-AUTOMERGE-Update-getDefaultCBRPackageName-f.patch @@ -0,0 +1,69 @@ +From ff53b60428ad0459c0cdf827513b1bbf7e30190b Mon Sep 17 00:00:00 2001 +From: Hyein Yu +Date: Wed, 15 Jan 2025 01:35:39 +0000 +Subject: [PATCH] RESTRICT AUTOMERGE Update getDefaultCBRPackageName for fixing + security vulnerability + +Bug: 381419370 +Test: atest CellBroadcastServiceTests +Test: Install fake cellbroadcast app. Check if cellbroadcast message is +received +Flag: EXEMPT bug fix + +Change-Id: If2e797dd6d352627ddfac16f3d0447be0e172d25 +(cherry picked from commit f24f40e2512f450b6a23536a4209318cd3043d99) +Merged-In: If2e797dd6d352627ddfac16f3d0447be0e172d25 +--- + .../cellbroadcastservice/CellBroadcastHandler.java | 6 ++++-- + .../tests/CellBroadcastHandlerTest.java | 10 ++++++++++ + 2 files changed, 14 insertions(+), 2 deletions(-) + +diff --git a/src/com/android/cellbroadcastservice/CellBroadcastHandler.java b/src/com/android/cellbroadcastservice/CellBroadcastHandler.java +index 90686b9..e3b2a41 100644 +--- a/src/com/android/cellbroadcastservice/CellBroadcastHandler.java ++++ b/src/com/android/cellbroadcastservice/CellBroadcastHandler.java +@@ -855,9 +855,11 @@ public class CellBroadcastHandler extends WakeLockStateMachine { + * Find the name of the default CBR package. The criteria is that it belongs to CB apex and + * handles the given intent. + */ +- static String getDefaultCBRPackageName(Context context, Intent intent) { ++ @VisibleForTesting ++ public static String getDefaultCBRPackageName(Context context, Intent intent) { + PackageManager packageManager = context.getPackageManager(); +- List cbrPackages = packageManager.queryBroadcastReceivers(intent, 0); ++ List cbrPackages = packageManager.queryBroadcastReceivers(intent, ++ PackageManager.MATCH_SYSTEM_ONLY); + + // remove apps that don't live in the CellBroadcast apex + cbrPackages.removeIf(info -> +diff --git a/tests/src/com/android/cellbroadcastservice/tests/CellBroadcastHandlerTest.java b/tests/src/com/android/cellbroadcastservice/tests/CellBroadcastHandlerTest.java +index 3037c62..79648c6 100644 +--- a/tests/src/com/android/cellbroadcastservice/tests/CellBroadcastHandlerTest.java ++++ b/tests/src/com/android/cellbroadcastservice/tests/CellBroadcastHandlerTest.java +@@ -38,6 +38,7 @@ import android.content.ContentValues; + import android.content.Context; + import android.content.IIntentSender; + import android.content.Intent; ++import android.content.pm.PackageManager; + import android.content.res.Configuration; + import android.database.Cursor; + import android.database.MatrixCursor; +@@ -497,6 +498,15 @@ public class CellBroadcastHandlerTest extends CellBroadcastServiceTestBase { + cellBroadcastHandler.cleanup(); + } + ++ @Test ++ @SmallTest ++ public void testGetDefaultCBRPackageName() { ++ Intent intent = new Intent(Telephony.Sms.Intents.ACTION_SMS_EMERGENCY_CB_RECEIVED); ++ CellBroadcastHandler.getDefaultCBRPackageName(mMockedContext, intent); ++ verify(mMockedPackageManager, times(1)) ++ .queryBroadcastReceivers(intent, PackageManager.MATCH_SYSTEM_ONLY); ++ } ++ + /** + * Makes injecting a mock factory easy. + */ +-- +2.34.1 + diff --git a/aosp_diff/caas/packages/modules/IntentResolver/0001-Sanitize-cross-profile-intents.patch b/aosp_diff/caas/packages/modules/IntentResolver/0001-Sanitize-cross-profile-intents.patch new file mode 100644 index 0000000..181a051 --- /dev/null +++ b/aosp_diff/caas/packages/modules/IntentResolver/0001-Sanitize-cross-profile-intents.patch @@ -0,0 +1,248 @@ +From 6f685ddd54a2eb65ed46917eab0417a853324772 Mon Sep 17 00:00:00 2001 +From: Andrey Yepin +Date: Thu, 22 May 2025 10:00:58 -0700 +Subject: [PATCH 1/2] Sanitize cross-profile intents. + +Remove package or component information from payload intents (and their +selectors) for cross-profile sharing. + +Bug: 407764858 +Test: manual testing +Test: atest IntentResolver-test-unit +Test: ag/32976049 (checked out and ran locally) +Flag: EXEMPT bugfix +Change-Id: I1ebfd96b2aabba6665267722603c72cbe4aefe0f +(cherry picked from commit d605b5448615815cb6a7630637b9c55349ffe36e) +--- + .../intentresolver/ChooserActivity.java | 15 +++-- + .../data/model/ChooserRequest.kt | 13 ++++ + .../intentresolver/shared/model/Profile.kt | 6 +- + .../intentresolver/util/IntentUtils.kt | 37 +++++++++++ + .../intentresolver/util/IntentUtilsTest.kt | 62 +++++++++++++++++++ + 5 files changed, 121 insertions(+), 12 deletions(-) + create mode 100644 java/src/com/android/intentresolver/util/IntentUtils.kt + create mode 100644 tests/unit/src/com/android/intentresolver/util/IntentUtilsTest.kt + +diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java +index 54f575d7..6df9e948 100644 +--- a/java/src/com/android/intentresolver/ChooserActivity.java ++++ b/java/src/com/android/intentresolver/ChooserActivity.java +@@ -522,7 +522,6 @@ public class ChooserActivity extends Hilt_ChooserActivity implements + mProfiles, + mProfileRecords.values(), + mProfileAvailability, +- mRequest.getInitialIntents(), + mMaxTargetsPerRow); + + maybeDisableRecentsScreenshot(mProfiles, mProfileAvailability); +@@ -793,7 +792,6 @@ public class ChooserActivity extends Hilt_ChooserActivity implements + mProfiles, + mProfileRecords.values(), + mProfileAvailability, +- mRequest.getInitialIntents(), + mMaxTargetsPerRow); + mChooserMultiProfilePagerAdapter.setCurrentPage(currentPage); + for (int i = 0, count = mChooserMultiProfilePagerAdapter.getItemCount(); i < count; i++) { +@@ -1457,22 +1455,23 @@ public class ChooserActivity extends Hilt_ChooserActivity implements + ProfileHelper profileHelper, + Collection profileRecords, + ProfileAvailability profileAvailability, +- List initialIntents, + int maxTargetsPerRow) { + Log.d(TAG, "createMultiProfilePagerAdapter"); + + Profile launchedAs = profileHelper.getLaunchedAsProfile(); + +- Intent[] initialIntentArray = initialIntents.toArray(new Intent[0]); +- List payloadIntents = request.getPayloadIntents(); ++ Intent[] initialIntentArray = request.getInitialIntents().toArray(new Intent[0]); + + List> tabs = new ArrayList<>(); + for (ProfileRecord record : profileRecords) { + Profile profile = record.profile; ++ boolean isCrossProfile = !profile.equals(launchedAs); + ChooserGridAdapter adapter = createChooserGridAdapter( +- context, +- payloadIntents, +- profile.equals(launchedAs) ? initialIntentArray : null, ++ context, ++ isCrossProfile ++ ? request.getCrossProfilePayloadIntents() ++ : request.getPayloadIntents(), ++ isCrossProfile ? null : initialIntentArray, + profile.getPrimary().getHandle() + ); + tabs.add(new TabConfig<>( +diff --git a/java/src/com/android/intentresolver/data/model/ChooserRequest.kt b/java/src/com/android/intentresolver/data/model/ChooserRequest.kt +index c4aa2b98..2f8155fe 100644 +--- a/java/src/com/android/intentresolver/data/model/ChooserRequest.kt ++++ b/java/src/com/android/intentresolver/data/model/ChooserRequest.kt +@@ -29,6 +29,7 @@ import android.service.chooser.ChooserTarget + import androidx.annotation.StringRes + import com.android.intentresolver.ContentTypeHint + import com.android.intentresolver.ext.hasAction ++import com.android.intentresolver.util.sanitizePayloadIntents + + const val ANDROID_APP_SCHEME = "android-app" + +@@ -194,4 +195,16 @@ data class ChooserRequest( + } + + val payloadIntents = listOf(targetIntent) + additionalTargets ++ ++ /** ++ * Payload intents that should be used for cross-profile sharing. ++ * ++ * These intents are a copy of `payloadIntents`. For security reasons, explicit targeting ++ * information is removed from each [Intent] in the list, as well as from its ++ * [selector][Intent.getSelector]. Specifically, the values that would be returned by ++ * [Intent.getPackage] and [Intent.getComponent] are cleared for both the main intent and its ++ * selector. This sanitization is performed because explicit intents could otherwise be used to ++ * bypass the device's cross-profile sharing policy settings. ++ */ ++ val crossProfilePayloadIntents by lazy { sanitizePayloadIntents(payloadIntents) } + } +diff --git a/java/src/com/android/intentresolver/shared/model/Profile.kt b/java/src/com/android/intentresolver/shared/model/Profile.kt +index c557c151..ce705259 100644 +--- a/java/src/com/android/intentresolver/shared/model/Profile.kt ++++ b/java/src/com/android/intentresolver/shared/model/Profile.kt +@@ -16,8 +16,6 @@ + + package com.android.intentresolver.shared.model + +-import com.android.intentresolver.shared.model.Profile.Type +- + /** + * Associates [users][User] into a [Type] instance. + * +@@ -32,7 +30,7 @@ data class Profile( + * An optional [User] of which contains second instances of some applications installed for the + * personal user. This value may only be supplied when creating the PERSONAL profile. + */ +- val clone: User? = null ++ val clone: User? = null, + ) { + + init { +@@ -47,6 +45,6 @@ data class Profile( + enum class Type { + PERSONAL, + WORK, +- PRIVATE ++ PRIVATE, + } + } +diff --git a/java/src/com/android/intentresolver/util/IntentUtils.kt b/java/src/com/android/intentresolver/util/IntentUtils.kt +new file mode 100644 +index 00000000..c20479c8 +--- /dev/null ++++ b/java/src/com/android/intentresolver/util/IntentUtils.kt +@@ -0,0 +1,37 @@ ++/* ++ * Copyright 2025 The Android Open Source Project ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * https://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++@file:JvmName("IntentUtils") ++ ++package com.android.intentresolver.util ++ ++import android.content.Intent ++ ++fun sanitizePayloadIntents(intents: List): List = ++ intents.map { intent -> ++ Intent(intent).also { sanitized -> ++ sanitized.setPackage(null) ++ sanitized.setComponent(null) ++ sanitized.selector?.let { ++ sanitized.setSelector( ++ Intent(it).apply { ++ setPackage(null) ++ setComponent(null) ++ } ++ ) ++ } ++ } ++ } +diff --git a/tests/unit/src/com/android/intentresolver/util/IntentUtilsTest.kt b/tests/unit/src/com/android/intentresolver/util/IntentUtilsTest.kt +new file mode 100644 +index 00000000..8042b82e +--- /dev/null ++++ b/tests/unit/src/com/android/intentresolver/util/IntentUtilsTest.kt +@@ -0,0 +1,62 @@ ++/* ++ * Copyright 2025 The Android Open Source Project ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * https://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++package com.android.intentresolver.util ++ ++import android.content.ComponentName ++import android.content.Intent ++import android.content.Intent.ACTION_SEND ++import com.google.common.truth.Truth.assertThat ++import org.junit.Test ++ ++class IntentUtilsTest { ++ @Test ++ fun test_sanitizePayloadIntents() { ++ val intents = ++ listOf( ++ Intent(ACTION_SEND).apply { setPackage("org.test.example") }, ++ Intent(ACTION_SEND).apply { ++ setComponent( ++ ComponentName.unflattenFromString("org.test.example/.TestActivity") ++ ) ++ }, ++ Intent(ACTION_SEND).apply { ++ setSelector(Intent(ACTION_SEND).apply { setPackage("org.test.example") }) ++ }, ++ Intent(ACTION_SEND).apply { ++ setSelector( ++ Intent(ACTION_SEND).apply { ++ setComponent( ++ ComponentName.unflattenFromString("org.test.example/.TestActivity") ++ ) ++ } ++ ) ++ }, ++ ) ++ ++ val sanitized = sanitizePayloadIntents(intents) ++ ++ assertThat(sanitized).hasSize(intents.size) ++ for (i in sanitized) { ++ assertThat(i.getPackage()).isNull() ++ assertThat(i.getComponent()).isNull() ++ i.getSelector()?.let { ++ assertThat(it.getPackage()).isNull() ++ assertThat(it.getComponent()).isNull() ++ } ++ } ++ } ++} +-- +2.34.1 + diff --git a/aosp_diff/caas/packages/modules/IntentResolver/0002-Launch-image-editor-as-sharesheet-launching-user-fla.patch b/aosp_diff/caas/packages/modules/IntentResolver/0002-Launch-image-editor-as-sharesheet-launching-user-fla.patch new file mode 100644 index 0000000..17e94c1 --- /dev/null +++ b/aosp_diff/caas/packages/modules/IntentResolver/0002-Launch-image-editor-as-sharesheet-launching-user-fla.patch @@ -0,0 +1,93 @@ +From d535e858975c370b1760655c29fec083d7d9587c Mon Sep 17 00:00:00 2001 +From: Matt Casey +Date: Tue, 17 Jun 2025 19:30:30 +0000 +Subject: [PATCH 2/2] Launch image editor as sharesheet-launching user (flag + off) + +ag/34038177 but for the cases where use_preferred_image_editor is off or +does not exist. + +Bug: 407991863 +Test: Manual repro of bug and verification of fix. +Test: atest IntentResolver-tests-* +Flag: EXEMPT bugfix +Change-Id: Idf2d76615f4396f09e7de33becc87afaea782d41 +Merged-In: Idf2d76615f4396f09e7de33becc87afaea782d41 +(cherry picked from commit 29da3d663edd566f0b5c4dce434f40aa7e3877de) +--- + .../android/intentresolver/ChooserActionFactory.java | 10 +++++----- + .../com/android/intentresolver/ChooserActivity.java | 8 ++++---- + 2 files changed, 9 insertions(+), 9 deletions(-) + +diff --git a/java/src/com/android/intentresolver/ChooserActionFactory.java b/java/src/com/android/intentresolver/ChooserActionFactory.java +index 21ca3b73..160901b5 100644 +--- a/java/src/com/android/intentresolver/ChooserActionFactory.java ++++ b/java/src/com/android/intentresolver/ChooserActionFactory.java +@@ -68,16 +68,16 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio + * Request an activity launch for the provided target. Implementations may choose to exit + * the current activity when the target is launched. + */ +- void safelyStartActivityAsPersonalProfileUser(TargetInfo info); ++ void safelyStartActivityAsLaunchingUser(TargetInfo info); + + /** + * Request an activity launch for the provided target, optionally employing the specified + * shared element transition. Implementations may choose to exit the current activity when + * the target is launched. + */ +- default void safelyStartActivityAsPersonalProfileUserWithSharedElementTransition( ++ default void safelyStartActivityAsLaunchingUserWithSharedElementTransition( + TargetInfo info, View sharedElement, String sharedElementName) { +- safelyStartActivityAsPersonalProfileUser(info); ++ safelyStartActivityAsLaunchingUser(info); + } + } + +@@ -350,9 +350,9 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio + } catch (Exception e) { /* ignore */ } + // Action bar is user-independent; always start as primary. + if (firstImageView == null || !isFullyVisible(firstImageView)) { +- activityStarter.safelyStartActivityAsPersonalProfileUser(editSharingTarget); ++ activityStarter.safelyStartActivityAsLaunchingUser(editSharingTarget); + } else { +- activityStarter.safelyStartActivityAsPersonalProfileUserWithSharedElementTransition( ++ activityStarter.safelyStartActivityAsLaunchingUserWithSharedElementTransition( + editSharingTarget, firstImageView, IMAGE_EDITOR_SHARED_ELEMENT); + } + }; +diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java +index 6df9e948..f92f557f 100644 +--- a/java/src/com/android/intentresolver/ChooserActivity.java ++++ b/java/src/com/android/intentresolver/ChooserActivity.java +@@ -2236,10 +2236,10 @@ public class ChooserActivity extends Hilt_ChooserActivity implements + this::getFirstVisibleImgPreviewView, + new ChooserActionFactory.ActionActivityStarter() { + @Override +- public void safelyStartActivityAsPersonalProfileUser(TargetInfo targetInfo) { ++ public void safelyStartActivityAsLaunchingUser(TargetInfo targetInfo) { + safelyStartActivityAsUser( + targetInfo, +- mProfiles.getPersonalHandle() ++ mUserInteractor.getLaunchedAs() + ); + Log.d(TAG, "safelyStartActivityAsPersonalProfileUser(" + + targetInfo + "): finishing!"); +@@ -2247,13 +2247,13 @@ public class ChooserActivity extends Hilt_ChooserActivity implements + } + + @Override +- public void safelyStartActivityAsPersonalProfileUserWithSharedElementTransition( ++ public void safelyStartActivityAsLaunchingUserWithSharedElementTransition( + TargetInfo targetInfo, View sharedElement, String sharedElementName) { + ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation( + ChooserActivity.this, sharedElement, sharedElementName); + safelyStartActivityAsUser( + targetInfo, +- mProfiles.getPersonalHandle(), ++ mUserInteractor.getLaunchedAs(), + options.toBundle()); + // Can't finish right away because the shared element transition may not + // be ready to start. +-- +2.34.1 + diff --git a/aosp_diff/caas/packages/modules/Permission/0001-Stop-one-time-sessions-iff-when-no-one-time-permissi.patch b/aosp_diff/caas/packages/modules/Permission/0001-Stop-one-time-sessions-iff-when-no-one-time-permissi.patch new file mode 100644 index 0000000..fde262c --- /dev/null +++ b/aosp_diff/caas/packages/modules/Permission/0001-Stop-one-time-sessions-iff-when-no-one-time-permissi.patch @@ -0,0 +1,144 @@ +From cd1d222617b142955f5b413e65c49ad9a79416c8 Mon Sep 17 00:00:00 2001 +From: mrulhania +Date: Mon, 9 Jun 2025 18:05:20 -0700 +Subject: [PATCH] Stop one-time sessions iff when no one-time permission is + granted + +Stoping the session on one of one-time permission revoke termintes +the only session running for all one time permission grants. We +need to stop the session when no permission is granted as one time. + +Bug: 419105158 +Test: manual +Flag: EXEMPT bug fix +LOW_COVERAGE_REASON=NON_CODE_ONLY +Relnote: security bug fix +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:2b7a2c80f69a80163d35f45b5300d400884945a0) +Merged-In: I59d6f13e23e326b055e0221da52bb22bd54de5d7 +Change-Id: I59d6f13e23e326b055e0221da52bb22bd54de5d7 +--- + .../permission/model/AppPermissionGroup.java | 29 ++++++++++++++++++- + .../permission/utils/KotlinUtils.kt | 25 +++++++--------- + 2 files changed, 38 insertions(+), 16 deletions(-) + +diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/AppPermissionGroup.java b/PermissionController/src/com/android/permissioncontroller/permission/model/AppPermissionGroup.java +index 3b2cc7ee0d..f6c98da9ef 100644 +--- a/PermissionController/src/com/android/permissioncontroller/permission/model/AppPermissionGroup.java ++++ b/PermissionController/src/com/android/permissioncontroller/permission/model/AppPermissionGroup.java +@@ -23,12 +23,15 @@ import static android.app.AppOpsManager.MODE_ALLOWED; + import static android.app.AppOpsManager.MODE_FOREGROUND; + import static android.app.AppOpsManager.MODE_IGNORED; + import static android.app.AppOpsManager.OPSTR_LEGACY_STORAGE; ++import static android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME; ++import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT; + import static android.content.pm.PackageManager.PERMISSION_GRANTED; + import static android.health.connect.HealthPermissions.HEALTH_PERMISSION_GROUP; + + import static com.android.permissioncontroller.permission.utils.Utils.isHealthPermissionUiEnabled; + + import android.Manifest; ++import android.annotation.SuppressLint; + import android.app.ActivityManager; + import android.app.AppOpsManager; + import android.app.Application; +@@ -1662,12 +1665,36 @@ public final class AppPermissionGroup implements Comparable + } finally { + Binder.restoreCallingIdentity(token); + } +- } else { ++ } else if (!anyPermsOfPackageOneTimeGranted(getApp())) { ++ // Stop the session only when no permission in the package is granted as one time. + mContext.getSystemService(PermissionManager.class) + .stopOneTimePermissionSession(packageName); + } + } + ++ @SuppressLint("MissingPermission") ++ private boolean anyPermsOfPackageOneTimeGranted(PackageInfo packageInfo) { ++ if (packageInfo.requestedPermissions == null ++ || packageInfo.requestedPermissionsFlags == null) { ++ return false; ++ } ++ ++ for (int i = 0; i < packageInfo.requestedPermissions.length; i++) { ++ if ((packageInfo.requestedPermissionsFlags[i] & ++ PackageInfo.REQUESTED_PERMISSION_GRANTED) == 0) { ++ continue; ++ } ++ int flags = mPackageManager.getPermissionFlags( ++ packageInfo.requestedPermissions[i], packageInfo.packageName, getUser()); ++ boolean isGrantedOneTime = (flags & FLAG_PERMISSION_REVOKED_COMPAT) == 0 && ++ (flags & FLAG_PERMISSION_ONE_TIME) != 0; ++ if (isGrantedOneTime) { ++ return true; ++ } ++ } ++ return false; ++ } ++ + /** + * Check if permission group contains a runtime permission that split from an installed + * permission and the split happened in an Android version higher than app's targetSdk. +diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt +index fb33aaffc8..a3f378435e 100644 +--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt ++++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt +@@ -40,6 +40,7 @@ import android.content.Intent + import android.content.Intent.ACTION_MAIN + import android.content.Intent.CATEGORY_INFO + import android.content.Intent.CATEGORY_LAUNCHER ++import android.content.pm.PackageInfo + import android.content.pm.PackageManager + import android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKED + import android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME +@@ -1140,7 +1141,7 @@ object KotlinUtils { + group.specialLocationGrant + ) + +- if (wasOneTime && !anyPermsOfPackageOneTimeGranted(app, newGroup.packageInfo, newGroup)) { ++ if (wasOneTime && !anyPermsOfPackageOneTimeGranted(app, newGroup.packageInfo)) { + // Create a new context with the given deviceId so that permission updates will be bound + // to the device + val context = ContextCompat.createDeviceContext(app.applicationContext, deviceId) +@@ -1185,30 +1186,24 @@ object KotlinUtils { + * + * @param app The current application + * @param packageInfo The packageInfo we wish to examine +- * @param group Optional, the current app permission group we are examining + * @return true if any permission in the package is granted for one time, false otherwise + */ + @Suppress("MissingPermission") + private fun anyPermsOfPackageOneTimeGranted( + app: Application, + packageInfo: LightPackageInfo, +- group: LightAppPermGroup? = null + ): Boolean { +- val user = group?.userHandle ?: UserHandle.getUserHandleForUid(packageInfo.uid) +- if (group?.isOneTime == true) { +- return true +- } +- for ((idx, permName) in packageInfo.requestedPermissions.withIndex()) { +- if (permName in group?.permissions ?: emptyMap()) { ++ val user = UserHandle.getUserHandleForUid(packageInfo.uid) ++ for ((index, permName) in packageInfo.requestedPermissions.withIndex()) { ++ if ((packageInfo.requestedPermissionsFlags[index] and ++ PackageInfo.REQUESTED_PERMISSION_GRANTED) == 0) { + continue + } + val flags = +- app.packageManager.getPermissionFlags(permName, packageInfo.packageName, user) and +- FLAG_PERMISSION_ONE_TIME +- val granted = +- packageInfo.requestedPermissionsFlags[idx] == PackageManager.PERMISSION_GRANTED && +- (flags and FLAG_PERMISSION_REVOKED_COMPAT) == 0 +- if (granted && (flags and FLAG_PERMISSION_ONE_TIME) != 0) { ++ app.packageManager.getPermissionFlags(permName, packageInfo.packageName, user) ++ val isGrantedOneTime = (flags and FLAG_PERMISSION_REVOKED_COMPAT) == 0 && ++ (flags and FLAG_PERMISSION_ONE_TIME) != 0 ++ if (isGrantedOneTime) { + return true + } + } +-- +2.34.1 + diff --git a/aosp_diff/caas/packages/modules/Wifi/0001-Do-not-treat-SdkSandBox-as-privileges-App.patch b/aosp_diff/caas/packages/modules/Wifi/0001-Do-not-treat-SdkSandBox-as-privileges-App.patch new file mode 100644 index 0000000..aa76878 --- /dev/null +++ b/aosp_diff/caas/packages/modules/Wifi/0001-Do-not-treat-SdkSandBox-as-privileges-App.patch @@ -0,0 +1,32 @@ +From 4c887e6085631fd9c714a2aff7c89b8b2b081648 Mon Sep 17 00:00:00 2001 +From: Nate Jiang +Date: Thu, 29 May 2025 18:32:53 +0000 +Subject: [PATCH] Do not treat SdkSandBox as privileges App + +Flag: EXEMPT bug fix +Bug: 399885815 +Test: atest com.android.server.wifi +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:b8feb0cc861f6071e3d3e5e289ce969237901722) +Merged-In: I1ee7a09b7628a67dcb3502b29d99761bafd913b5 +Change-Id: I1ee7a09b7628a67dcb3502b29d99761bafd913b5 +--- + .../java/com/android/server/wifi/util/WifiPermissionsUtil.java | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java b/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java +index 614b0df42d..015073c982 100644 +--- a/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java ++++ b/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java +@@ -1126,6 +1126,9 @@ public class WifiPermissionsUtil { + public boolean isSystem(String packageName, int uid) { + long ident = Binder.clearCallingIdentity(); + try { ++ if (SdkLevel.isAtLeastT() && Process.isSdkSandboxUid(uid)) { ++ return false; ++ } + ApplicationInfo info = mContext.getPackageManager().getApplicationInfoAsUser( + packageName, 0, UserHandle.getUserHandleForUid(uid)); + return (info.flags & APP_INFO_FLAGS_SYSTEM_APP) != 0; +-- +2.34.1 + diff --git a/aosp_diff/caas/packages/providers/MediaProvider/0001-RESTRICT-AUTOMERGE-Refactor-PickerDbFacade-queryMedi.patch b/aosp_diff/caas/packages/providers/MediaProvider/0001-RESTRICT-AUTOMERGE-Refactor-PickerDbFacade-queryMedi.patch new file mode 100644 index 0000000..508127a --- /dev/null +++ b/aosp_diff/caas/packages/providers/MediaProvider/0001-RESTRICT-AUTOMERGE-Refactor-PickerDbFacade-queryMedi.patch @@ -0,0 +1,175 @@ +From f5248f530d35e3b7fda07b088529f28970f297b4 Mon Sep 17 00:00:00 2001 +From: Shubhi +Date: Thu, 6 Mar 2025 21:06:23 +0000 +Subject: [PATCH] RESTRICT AUTOMERGE Refactor + PickerDbFacade#queryMediaIdForApps implementation + +Bug: 389681152 +Test: atest PickerDbFacadeTest +Flag: EXEMPT bug fix +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:edf1da4372d8353b63799b9d015d34e08dff1418) +Merged-In: Ibe8a80d65c4dff6368352944e004c5c934430953 +Change-Id: Ibe8a80d65c4dff6368352944e004c5c934430953 +--- + .../photopicker/data/PickerDbFacade.java | 70 ++++++++++++------- + .../photopicker/data/PickerDbFacadeTest.java | 11 ++- + 2 files changed, 52 insertions(+), 29 deletions(-) + +diff --git a/src/com/android/providers/media/photopicker/data/PickerDbFacade.java b/src/com/android/providers/media/photopicker/data/PickerDbFacade.java +index fbb32ef42..f63496767 100644 +--- a/src/com/android/providers/media/photopicker/data/PickerDbFacade.java ++++ b/src/com/android/providers/media/photopicker/data/PickerDbFacade.java +@@ -35,6 +35,7 @@ import android.content.ContentUris; + import android.content.ContentValues; + import android.content.Context; + import android.database.Cursor; ++import android.database.DatabaseUtils; + import android.database.MatrixCursor; + import android.database.MergeCursor; + import android.database.sqlite.SQLiteConstraintException; +@@ -1144,11 +1145,31 @@ public class PickerDbFacade { + } + + private Cursor queryMediaIdForAppsLocked(@NonNull SQLiteQueryBuilder qb, +- @NonNull String[] projection, @NonNull String[] selectionArgs, ++ @NonNull String[] columns, @NonNull String[] selectionArgs, + String pickerSegmentType) { +- return qb.query(mDatabase, getMediaStoreProjectionLocked(projection, pickerSegmentType), +- /* selection */ null, selectionArgs, /* groupBy */ null, /* having */ null, +- /* orderBy */ null, /* limitStr */ null); ++ final Cursor cursor = ++ qb.query(mDatabase, getMediaStoreProjectionLocked(columns, pickerSegmentType), ++ /* selection */ null, selectionArgs, /* groupBy */ null, /* having */ null, ++ /* orderBy */ null, /* limitStr */ null); ++ ++ if (columns == null || columns.length == 0 || cursor.getColumnCount() == columns.length) { ++ return cursor; ++ } else { ++ // An unknown column was encountered. Populate it will null for backwards compatibility. ++ final MatrixCursor result = new MatrixCursor(columns); ++ if (cursor.moveToFirst()) { ++ do { ++ final ContentValues contentValues = new ContentValues(); ++ DatabaseUtils.cursorRowToContentValues(cursor, contentValues); ++ final MatrixCursor.RowBuilder rowBuilder = result.newRow(); ++ for (String column : columns) { ++ rowBuilder.add(column, contentValues.get(column)); ++ } ++ } while (cursor.moveToNext()); ++ } ++ cursor.close(); ++ return result; ++ } + } + + /** +@@ -1300,54 +1321,51 @@ public class PickerDbFacade { + } + + private String[] getMediaStoreProjectionLocked(String[] columns, String pickerSegmentType) { +- final String[] projection = new String[columns.length]; ++ final List projection = new ArrayList<>(); + +- for (int i = 0; i < projection.length; i++) { ++ for (int i = 0; i < columns.length; i++) { + switch (columns[i]) { + case PickerMediaColumns.DATA: +- projection[i] = getProjectionDataLocked(PickerMediaColumns.DATA, +- pickerSegmentType); ++ projection.add(getProjectionDataLocked(PickerMediaColumns.DATA, ++ pickerSegmentType)); + break; + case PickerMediaColumns.DISPLAY_NAME: +- projection[i] = +- getProjectionSimple( +- getDisplayNameSql(), PickerMediaColumns.DISPLAY_NAME); ++ projection.add(getProjectionSimple( ++ getDisplayNameSql(), PickerMediaColumns.DISPLAY_NAME)); + break; + case PickerMediaColumns.MIME_TYPE: +- projection[i] = +- getProjectionSimple(KEY_MIME_TYPE, PickerMediaColumns.MIME_TYPE); ++ projection.add(getProjectionSimple( ++ KEY_MIME_TYPE, PickerMediaColumns.MIME_TYPE)); + break; + case PickerMediaColumns.DATE_TAKEN: +- projection[i] = +- getProjectionSimple(KEY_DATE_TAKEN_MS, PickerMediaColumns.DATE_TAKEN); ++ projection.add(getProjectionSimple( ++ KEY_DATE_TAKEN_MS, PickerMediaColumns.DATE_TAKEN)); + break; + case PickerMediaColumns.SIZE: +- projection[i] = getProjectionSimple(KEY_SIZE_BYTES, PickerMediaColumns.SIZE); ++ projection.add(getProjectionSimple(KEY_SIZE_BYTES, PickerMediaColumns.SIZE)); + break; + case PickerMediaColumns.DURATION_MILLIS: +- projection[i] = +- getProjectionSimple( +- KEY_DURATION_MS, PickerMediaColumns.DURATION_MILLIS); ++ projection.add(getProjectionSimple( ++ KEY_DURATION_MS, PickerMediaColumns.DURATION_MILLIS)); + break; + case PickerMediaColumns.HEIGHT: +- projection[i] = getProjectionSimple(KEY_HEIGHT, PickerMediaColumns.HEIGHT); ++ projection.add(getProjectionSimple(KEY_HEIGHT, PickerMediaColumns.HEIGHT)); + break; + case PickerMediaColumns.WIDTH: +- projection[i] = getProjectionSimple(KEY_WIDTH, PickerMediaColumns.WIDTH); ++ projection.add(getProjectionSimple(KEY_WIDTH, PickerMediaColumns.WIDTH)); + break; + case PickerMediaColumns.ORIENTATION: +- projection[i] = +- getProjectionSimple(KEY_ORIENTATION, PickerMediaColumns.ORIENTATION); ++ projection.add(getProjectionSimple( ++ KEY_ORIENTATION, PickerMediaColumns.ORIENTATION)); + break; + default: +- projection[i] = getProjectionSimple("NULL", columns[i]); + // Ignore unsupported columns; we do not throw error here to support +- // backward compatibility ++ // backward compatibility for ACTION_GET_CONTENT takeover. + Log.w(TAG, "Unexpected Picker column: " + columns[i]); + } + } + +- return projection; ++ return projection.toArray(new String[0]); + } + + private String getProjectionAuthorityLocked() { +diff --git a/tests/src/com/android/providers/media/photopicker/data/PickerDbFacadeTest.java b/tests/src/com/android/providers/media/photopicker/data/PickerDbFacadeTest.java +index 68438b176..45069df81 100644 +--- a/tests/src/com/android/providers/media/photopicker/data/PickerDbFacadeTest.java ++++ b/tests/src/com/android/providers/media/photopicker/data/PickerDbFacadeTest.java +@@ -1459,7 +1459,7 @@ public class PickerDbFacadeTest { + } + + // Assert invalid projection column +- final String invalidColumn = "testInvalidColumn"; ++ final String invalidColumn = "test invalid column"; + final String[] invalidProjection = new String[]{ + PickerMediaColumns.DATE_TAKEN, + invalidColumn +@@ -1471,12 +1471,17 @@ public class PickerDbFacadeTest { + "Unexpected number of rows when asserting invalid projection column with " + + "cloud provider.") + .that(cr.getCount()).isEqualTo(1); ++ assertWithMessage("Unexpected number of columns in cursor") ++ .that(cr.getColumnCount()) ++ .isEqualTo(2); + + cr.moveToFirst(); +- assertWithMessage( +- "Unexpected value of the invalidColumn with cloud provider.") ++ assertWithMessage("Unexpected value of the invalidColumn with cloud provider.") + .that(cr.getLong(cr.getColumnIndexOrThrow(invalidColumn))) + .isEqualTo(0); ++ assertWithMessage("Unexpected value of the invalidColumn with cloud provider.") ++ .that(cr.getString(cr.getColumnIndexOrThrow(invalidColumn))) ++ .isEqualTo(null); + assertWithMessage( + "Unexpected value of PickerMediaColumns.DATE_TAKEN with cloud provider.") + .that(cr.getLong(cr.getColumnIndexOrThrow(PickerMediaColumns.DATE_TAKEN))) +-- +2.34.1 +