diff --git a/Patches/LineageOS-19.1/android_frameworks_base/392225.patch b/Patches/LineageOS-19.1/android_frameworks_base/392225.patch deleted file mode 100644 index 836b9286..00000000 --- a/Patches/LineageOS-19.1/android_frameworks_base/392225.patch +++ /dev/null @@ -1,162 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Valentin Iftime -Date: Mon, 16 Oct 2023 09:29:17 +0200 -Subject: [PATCH] Prioritize system toasts - - Insert toasts from system packages at the front of the queue - to ensure that apps can't spam with toast to delay system toasts from showing. - Also increase Clipboard paste warning toasts length to LENGTH_LONG. - -Test: atest NotificationManagerServiceTest -Bug: 293301736 -(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:58d89b491668663963e66906196fd93b9c73ee80) -Merged-In: I13547f853476bc88d12026c545aba9f857ce8724 -Change-Id: I13547f853476bc88d12026c545aba9f857ce8724 ---- - .../server/clipboard/ClipboardService.java | 2 +- - .../NotificationManagerService.java | 32 ++++++++- - .../NotificationManagerServiceTest.java | 68 +++++++++++++++++++ - 3 files changed, 99 insertions(+), 3 deletions(-) - -diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java -index 093ecd57124f..18f397551be8 100644 ---- a/services/core/java/com/android/server/clipboard/ClipboardService.java -+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java -@@ -1006,7 +1006,7 @@ public class ClipboardService extends SystemService { - getContext().getString(R.string.pasted_from_clipboard, callingAppLabel); - Slog.i(TAG, message); - Toast.makeText( -- getContext(), UiThread.get().getLooper(), message, Toast.LENGTH_SHORT) -+ getContext(), UiThread.get().getLooper(), message, Toast.LENGTH_LONG) - .show(); - } catch (PackageManager.NameNotFoundException e) { - // do nothing -diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java -index 1044611e1dad..438d7f67d775 100755 ---- a/services/core/java/com/android/server/notification/NotificationManagerService.java -+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java -@@ -3224,8 +3224,19 @@ public class NotificationManagerService extends SystemService { - null /* options */); - record = getToastRecord(callingUid, callingPid, pkg, isSystemToast, token, - text, callback, duration, windowToken, displayId, textCallback); -- mToastQueue.add(record); -- index = mToastQueue.size() - 1; -+ -+ // Insert system toasts at the front of the queue -+ int systemToastInsertIdx = mToastQueue.size(); -+ if (isSystemToast) { -+ systemToastInsertIdx = getInsertIndexForSystemToastLocked(); -+ } -+ if (systemToastInsertIdx < mToastQueue.size()) { -+ index = systemToastInsertIdx; -+ mToastQueue.add(index, record); -+ } else { -+ mToastQueue.add(record); -+ index = mToastQueue.size() - 1; -+ } - keepProcessAliveForToastIfNeededLocked(callingPid); - } - // If it's at index 0, it's the current toast. It doesn't matter if it's -@@ -3241,6 +3252,23 @@ public class NotificationManagerService extends SystemService { - } - } - -+ @GuardedBy("mToastQueue") -+ private int getInsertIndexForSystemToastLocked() { -+ // If there are other system toasts: insert after the last one -+ int idx = 0; -+ for (ToastRecord r : mToastQueue) { -+ if (idx == 0 && mIsCurrentToastShown) { -+ idx++; -+ continue; -+ } -+ if (!r.isSystemToast) { -+ return idx; -+ } -+ idx++; -+ } -+ return idx; -+ } -+ - private boolean checkCanEnqueueToast(String pkg, int callingUid, - boolean isAppRenderedToast, boolean isSystemToast) { - final boolean isPackageSuspended = isPackagePaused(pkg); -diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java -index dff3a1623403..afe3f4e93521 100755 ---- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java -+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java -@@ -6125,6 +6125,74 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { - assertEquals(NotificationManagerService.MAX_PACKAGE_TOASTS, mService.mToastQueue.size()); - } - -+ @Test -+ public void testPrioritizeSystemToasts() throws Exception { -+ // Insert non-system toasts -+ final String testPackage = "testPackageName"; -+ assertEquals(0, mService.mToastQueue.size()); -+ mService.isSystemUid = false; -+ mService.isSystemAppId = false; -+ setToastRateIsWithinQuota(true); -+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false); -+ -+ // package is not suspended -+ when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid))) -+ .thenReturn(false); -+ -+ INotificationManager nmService = (INotificationManager) mService.mService; -+ -+ // Enqueue maximum number of toasts for test package -+ for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_TOASTS; i++) { -+ nmService.enqueueTextToast(testPackage, new Binder(), "Text", 2000, 0, null); -+ } -+ -+ // Enqueue system toast -+ final String testPackageSystem = "testPackageNameSystem"; -+ mService.isSystemUid = true; -+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackageSystem, false); -+ when(mPackageManager.isPackageSuspendedForUser(testPackageSystem, UserHandle.getUserId(mUid))) -+ .thenReturn(false); -+ -+ nmService.enqueueToast(testPackageSystem, new Binder(), new TestableToastCallback(), 2000, 0); -+ -+ // System toast is inserted at the front of the queue, behind current showing toast -+ assertEquals(testPackageSystem, mService.mToastQueue.get(1).pkg); -+ } -+ -+ @Test -+ public void testPrioritizeSystemToasts_enqueueAfterExistingSystemToast() throws Exception { -+ // Insert system toasts -+ final String testPackageSystem1 = "testPackageNameSystem1"; -+ assertEquals(0, mService.mToastQueue.size()); -+ mService.isSystemUid = true; -+ setToastRateIsWithinQuota(true); -+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackageSystem1, false); -+ -+ // package is not suspended -+ when(mPackageManager.isPackageSuspendedForUser(testPackageSystem1, UserHandle.getUserId(mUid))) -+ .thenReturn(false); -+ -+ INotificationManager nmService = (INotificationManager) mService.mService; -+ -+ // Enqueue maximum number of toasts for test package -+ for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_TOASTS; i++) { -+ nmService.enqueueTextToast(testPackageSystem1, new Binder(), "Text", 2000, 0, null); -+ } -+ -+ // Enqueue another system toast -+ final String testPackageSystem2 = "testPackageNameSystem2"; -+ mService.isSystemUid = true; -+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackageSystem2, false); -+ when(mPackageManager.isPackageSuspendedForUser(testPackageSystem2, UserHandle.getUserId(mUid))) -+ .thenReturn(false); -+ -+ nmService.enqueueToast(testPackageSystem2, new Binder(), new TestableToastCallback(), 2000, 0); -+ -+ // System toast is inserted at the back of the queue, after the other system toasts -+ assertEquals(testPackageSystem2, -+ mService.mToastQueue.get(mService.mToastQueue.size() - 1).pkg); -+ } -+ - private void setAppInForegroundForToasts(int uid, boolean inForeground) { - int importance = (inForeground) ? IMPORTANCE_FOREGROUND : IMPORTANCE_NONE; - when(mActivityManager.getUidImportance(mUid)).thenReturn(importance); diff --git a/Patches/LineageOS-20.0/ASB-2024-06/fwb-01.patch b/Patches/LineageOS-20.0/ASB-2024-06/fwb-01.patch new file mode 100644 index 00000000..6b382b38 --- /dev/null +++ b/Patches/LineageOS-20.0/ASB-2024-06/fwb-01.patch @@ -0,0 +1,225 @@ +From 42c9c06f9c0f8d7212284c6555e3ffd25bd4ddbf Mon Sep 17 00:00:00 2001 +From: Pinyao Ting +Date: Thu, 30 Nov 2023 23:12:39 +0000 +Subject: [PATCH] Added throttle when reporting shortcut usage + +Bug: 304290201 +Test: manual +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:bd88f35c6797b1795d1150af92760531ff73f14f) +Merged-In: I96370cbd4f6a55f894c1a93307e5f82dfd394652 +Change-Id: I96370cbd4f6a55f894c1a93307e5f82dfd394652 +--- + .../android/server/pm/ShortcutPackage.java | 32 +++++++++++ + .../android/server/pm/ShortcutService.java | 18 ++---- + .../server/pm/ShortcutManagerTest1.java | 55 ++++++++++++++++++- + .../server/pm/ShortcutManagerTest2.java | 2 + + 4 files changed, 92 insertions(+), 15 deletions(-) + +diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java +index 5a662d9f3139..3b3c11e1209d 100644 +--- a/services/core/java/com/android/server/pm/ShortcutPackage.java ++++ b/services/core/java/com/android/server/pm/ShortcutPackage.java +@@ -33,6 +33,7 @@ + import android.app.appsearch.SearchResults; + import android.app.appsearch.SearchSpec; + import android.app.appsearch.SetSchemaRequest; ++import android.app.usage.UsageStatsManagerInternal; + import android.content.ComponentName; + import android.content.Intent; + import android.content.IntentFilter; +@@ -47,6 +48,7 @@ + import android.os.Binder; + import android.os.PersistableBundle; + import android.os.StrictMode; ++import android.os.SystemClock; + import android.text.format.Formatter; + import android.util.ArrayMap; + import android.util.ArraySet; +@@ -160,6 +162,9 @@ class ShortcutPackage extends ShortcutPackageItem { + private static final String KEY_BITMAPS = "bitmaps"; + private static final String KEY_BITMAP_BYTES = "bitmapBytes"; + ++ @VisibleForTesting ++ public static final int REPORT_USAGE_BUFFER_SIZE = 3; ++ + private final Executor mExecutor; + + /** +@@ -195,6 +200,9 @@ class ShortcutPackage extends ShortcutPackageItem { + + private long mLastKnownForegroundElapsedTime; + ++ @GuardedBy("mLock") ++ private List mLastReportedTime = new ArrayList<>(); ++ + @GuardedBy("mLock") + private boolean mIsAppSearchSchemaUpToDate; + +@@ -1677,6 +1685,30 @@ public boolean hasNonManifestShortcuts() { + return condition[0]; + } + ++ void reportShortcutUsed(@NonNull final UsageStatsManagerInternal usageStatsManagerInternal, ++ @NonNull final String shortcutId) { ++ synchronized (mLock) { ++ final long currentTS = SystemClock.elapsedRealtime(); ++ final ShortcutService s = mShortcutUser.mService; ++ if (mLastReportedTime.isEmpty() ++ || mLastReportedTime.size() < REPORT_USAGE_BUFFER_SIZE) { ++ mLastReportedTime.add(currentTS); ++ } else if (currentTS - mLastReportedTime.get(0) > s.mSaveDelayMillis) { ++ mLastReportedTime.remove(0); ++ mLastReportedTime.add(currentTS); ++ } else { ++ return; ++ } ++ final long token = s.injectClearCallingIdentity(); ++ try { ++ usageStatsManagerInternal.reportShortcutUsage(getPackageName(), shortcutId, ++ getUser().getUserId()); ++ } finally { ++ s.injectRestoreCallingIdentity(token); ++ } ++ } ++ } ++ + public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) { + pw.println(); + +diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java +index 7fd32d2a6b1b..84eb799e31ff 100644 +--- a/services/core/java/com/android/server/pm/ShortcutService.java ++++ b/services/core/java/com/android/server/pm/ShortcutService.java +@@ -365,7 +365,7 @@ public boolean test(PackageInfo pi) { + private CompressFormat mIconPersistFormat; + private int mIconPersistQuality; + +- private int mSaveDelayMillis; ++ int mSaveDelayMillis; + + private final IPackageManager mIPackageManager; + private final PackageManagerInternal mPackageManagerInternal; +@@ -2269,7 +2269,7 @@ public void pushDynamicShortcut(String packageName, ShortcutInfo shortcut, + + packageShortcutsChanged(ps, changedShortcuts, removedShortcuts); + +- reportShortcutUsedInternal(packageName, shortcut.getId(), userId); ++ ps.reportShortcutUsed(mUsageStatsManagerInternal, shortcut.getId()); + + verifyStates(); + } +@@ -2676,25 +2676,17 @@ public void reportShortcutUsed(String packageName, String shortcutId, int userId + Slog.d(TAG, String.format("reportShortcutUsed: Shortcut %s package %s used on user %d", + shortcutId, packageName, userId)); + } ++ final ShortcutPackage ps; + synchronized (mLock) { + throwIfUserLockedL(userId); +- final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); ++ ps = getPackageShortcutsForPublisherLocked(packageName, userId); + if (ps.findShortcutById(shortcutId) == null) { + Log.w(TAG, String.format("reportShortcutUsed: package %s doesn't have shortcut %s", + packageName, shortcutId)); + return; + } + } +- reportShortcutUsedInternal(packageName, shortcutId, userId); +- } +- +- private void reportShortcutUsedInternal(String packageName, String shortcutId, int userId) { +- final long token = injectClearCallingIdentity(); +- try { +- mUsageStatsManagerInternal.reportShortcutUsage(packageName, shortcutId, userId); +- } finally { +- injectRestoreCallingIdentity(token); +- } ++ ps.reportShortcutUsed(mUsageStatsManagerInternal, shortcutId); + } + + @Override +diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +index 867890f938ba..0f00fb877afe 100644 +--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java ++++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +@@ -402,8 +402,8 @@ public void testAddDynamicShortcuts() { + + public void testPushDynamicShortcut() { + // Change the max number of shortcuts. +- mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5"); +- ++ mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5," ++ + ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=1"); + setCaller(CALLING_PACKAGE_1, USER_0); + + final ShortcutInfo s1 = makeShortcut("s1"); +@@ -541,6 +541,57 @@ public void testPushDynamicShortcut() { + eq(CALLING_PACKAGE_1), eq("s9"), eq(USER_0)); + } + ++ public void testPushDynamicShortcut_CallsToUsageStatsManagerAreThrottled() ++ throws InterruptedException { ++ mService.updateConfigurationLocked( ++ ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=500"); ++ ++ // Verify calls to UsageStatsManagerInternal#reportShortcutUsage are throttled. ++ setCaller(CALLING_PACKAGE_1, USER_0); ++ for (int i = 0; i < ShortcutPackage.REPORT_USAGE_BUFFER_SIZE; i++) { ++ final ShortcutInfo si = makeShortcut("s" + i); ++ mManager.pushDynamicShortcut(si); ++ } ++ verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage( ++ eq(CALLING_PACKAGE_1), eq("s1"), eq(USER_0)); ++ Mockito.reset(mMockUsageStatsManagerInternal); ++ for (int i = ShortcutPackage.REPORT_USAGE_BUFFER_SIZE; i <= 10; i++) { ++ final ShortcutInfo si = makeShortcut("s" + i); ++ mManager.pushDynamicShortcut(si); ++ } ++ verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage( ++ any(), any(), anyInt()); ++ ++ // Verify pkg2 isn't blocked by pkg1, but consecutive calls from pkg2 are throttled as well. ++ setCaller(CALLING_PACKAGE_2, USER_0); ++ for (int i = 0; i < ShortcutPackage.REPORT_USAGE_BUFFER_SIZE; i++) { ++ final ShortcutInfo si = makeShortcut("s" + i); ++ mManager.pushDynamicShortcut(si); ++ } ++ verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage( ++ eq(CALLING_PACKAGE_2), eq("s1"), eq(USER_0)); ++ Mockito.reset(mMockUsageStatsManagerInternal); ++ for (int i = ShortcutPackage.REPORT_USAGE_BUFFER_SIZE; i <= 10; i++) { ++ final ShortcutInfo si = makeShortcut("s" + i); ++ mManager.pushDynamicShortcut(si); ++ } ++ verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage( ++ any(), any(), anyInt()); ++ ++ Mockito.reset(mMockUsageStatsManagerInternal); ++ // Let time passes which resets the throttle ++ Thread.sleep(505); ++ // Verify UsageStatsManagerInternal#reportShortcutUsed can be called again ++ setCaller(CALLING_PACKAGE_1, USER_0); ++ mManager.pushDynamicShortcut(makeShortcut("s10")); ++ setCaller(CALLING_PACKAGE_2, USER_0); ++ mManager.pushDynamicShortcut(makeShortcut("s10")); ++ verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage( ++ eq(CALLING_PACKAGE_1), any(), eq(USER_0)); ++ verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage( ++ eq(CALLING_PACKAGE_2), any(), eq(USER_0)); ++ } ++ + public void testUnlimitedCalls() { + setCaller(CALLING_PACKAGE_1, USER_0); + +diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java +index 86d4655e9d3a..ea134dc66e3f 100644 +--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java ++++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java +@@ -2175,6 +2175,8 @@ public void testThrottling_resetByInternalCall() throws Exception { + + public void testReportShortcutUsed() { + mRunningUsers.put(USER_10, true); ++ mService.updateConfigurationLocked( ++ ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=1"); + + runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { + reset(mMockUsageStatsManagerInternal); diff --git a/Patches/LineageOS-20.0/ASB-2024-06/fwb-02.patch b/Patches/LineageOS-20.0/ASB-2024-06/fwb-02.patch new file mode 100644 index 00000000..395bb88f --- /dev/null +++ b/Patches/LineageOS-20.0/ASB-2024-06/fwb-02.patch @@ -0,0 +1,134 @@ +From c0c16283997bbca63edc79d820652587cbde15bb Mon Sep 17 00:00:00 2001 +From: Valentin Iftime +Date: Thu, 1 Feb 2024 13:58:49 +0100 +Subject: [PATCH] Verify URI permission for channel sound update from + NotificationListenerService + + Check that a privileged NotificationListenerService (CDM) has the permission to access the sound URI + when updating a notification channel. + +Test: atest com.android.server.notification.NotificationManagerServiceTest#testUpdateNotificationChannelFromPrivilegedListener_noSoundUriPermission +Bug: 317357401 +(cherry picked from commit 9b7bbbf5ad542ecf9ecbf8cd819b468791b443c0) +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:2f26c0def503d3b8032c99adc8a11be87e35cdeb) +Merged-In: Ic7d2e96e43565e98d2aa29b8f2ba35c142387ba9 +Change-Id: Ic7d2e96e43565e98d2aa29b8f2ba35c142387ba9 +--- + .../NotificationManagerService.java | 22 +++++++ + .../NotificationManagerServiceTest.java | 63 +++++++++++++++++++ + 2 files changed, 85 insertions(+) + +diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java +index ecd446519c09..4d1abc61aa73 100755 +--- a/services/core/java/com/android/server/notification/NotificationManagerService.java ++++ b/services/core/java/com/android/server/notification/NotificationManagerService.java +@@ -5705,6 +5705,10 @@ public void updateNotificationChannelFromPrivilegedListener(INotificationListene + Objects.requireNonNull(user); + + verifyPrivilegedListener(token, user, false); ++ ++ final NotificationChannel originalChannel = mPreferencesHelper.getNotificationChannel( ++ pkg, getUidForPackageAndUser(pkg, user), channel.getId(), true); ++ verifyPrivilegedListenerUriPermission(Binder.getCallingUid(), channel, originalChannel); + updateNotificationChannelInt(pkg, getUidForPackageAndUser(pkg, user), channel, true); + } + +@@ -5796,6 +5800,24 @@ private void verifyPrivilegedListener(INotificationListener token, UserHandle us + } + } + ++ private void verifyPrivilegedListenerUriPermission(int sourceUid, ++ @NonNull NotificationChannel updateChannel, ++ @Nullable NotificationChannel originalChannel) { ++ // Check that the NLS has the required permissions to access the channel ++ final Uri soundUri = updateChannel.getSound(); ++ final Uri originalSoundUri = ++ (originalChannel != null) ? originalChannel.getSound() : null; ++ if (soundUri != null && !Objects.equals(originalSoundUri, soundUri)) { ++ Binder.withCleanCallingIdentity(() -> { ++ mUgmInternal.checkGrantUriPermission(sourceUid, null, ++ ContentProvider.getUriWithoutUserId(soundUri), ++ Intent.FLAG_GRANT_READ_URI_PERMISSION, ++ ContentProvider.getUserIdFromUri(soundUri, ++ UserHandle.getUserId(sourceUid))); ++ }); ++ } ++ } ++ + private int getUidForPackageAndUser(String pkg, UserHandle user) throws RemoteException { + int uid = INVALID_UID; + final long identity = Binder.clearCallingIdentity(); +diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +index 4f0a7ca0ad51..755bc1d35cf3 100755 +--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java ++++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +@@ -3319,6 +3319,69 @@ public void testUpdateNotificationChannelFromPrivilegedListener_badUser() throws + eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED)); + } + ++ @Test ++ public void testUpdateNotificationChannelFromPrivilegedListener_noSoundUriPermission() ++ throws Exception { ++ mService.setPreferencesHelper(mPreferencesHelper); ++ when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) ++ .thenReturn(singletonList(mock(AssociationInfo.class))); ++ when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(), ++ eq(mTestNotificationChannel.getId()), anyBoolean())) ++ .thenReturn(mTestNotificationChannel); ++ ++ final Uri soundUri = Uri.parse("content://media/test/sound/uri"); ++ final NotificationChannel updatedNotificationChannel = new NotificationChannel( ++ TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT); ++ updatedNotificationChannel.setSound(soundUri, ++ updatedNotificationChannel.getAudioAttributes()); ++ ++ doThrow(new SecurityException("no access")).when(mUgmInternal) ++ .checkGrantUriPermission(eq(Process.myUid()), any(), eq(soundUri), ++ anyInt(), eq(Process.myUserHandle().getIdentifier())); ++ ++ assertThrows(SecurityException.class, ++ () -> mBinderService.updateNotificationChannelFromPrivilegedListener(null, PKG, ++ Process.myUserHandle(), updatedNotificationChannel)); ++ ++ verify(mPreferencesHelper, never()).updateNotificationChannel( ++ anyString(), anyInt(), any(), anyBoolean()); ++ ++ verify(mListeners, never()).notifyNotificationChannelChanged(eq(PKG), ++ eq(Process.myUserHandle()), eq(mTestNotificationChannel), ++ eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED)); ++ } ++ ++ @Test ++ public void testUpdateNotificationChannelFromPrivilegedListener_noSoundUriPermission_sameSound() ++ throws Exception { ++ mService.setPreferencesHelper(mPreferencesHelper); ++ when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) ++ .thenReturn(singletonList(mock(AssociationInfo.class))); ++ when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(), ++ eq(mTestNotificationChannel.getId()), anyBoolean())) ++ .thenReturn(mTestNotificationChannel); ++ ++ final Uri soundUri = Settings.System.DEFAULT_NOTIFICATION_URI; ++ final NotificationChannel updatedNotificationChannel = new NotificationChannel( ++ TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT); ++ updatedNotificationChannel.setSound(soundUri, ++ updatedNotificationChannel.getAudioAttributes()); ++ ++ doThrow(new SecurityException("no access")).when(mUgmInternal) ++ .checkGrantUriPermission(eq(Process.myUid()), any(), eq(soundUri), ++ anyInt(), eq(Process.myUserHandle().getIdentifier())); ++ ++ mBinderService.updateNotificationChannelFromPrivilegedListener( ++ null, PKG, Process.myUserHandle(), updatedNotificationChannel); ++ ++ verify(mPreferencesHelper, times(1)).updateNotificationChannel( ++ anyString(), anyInt(), any(), anyBoolean()); ++ ++ verify(mListeners, never()).notifyNotificationChannelChanged(eq(PKG), ++ eq(Process.myUserHandle()), eq(mTestNotificationChannel), ++ eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED)); ++ } ++ + @Test + public void testGetNotificationChannelFromPrivilegedListener_cdm_success() throws Exception { + mService.setPreferencesHelper(mPreferencesHelper); diff --git a/Patches/LineageOS-20.0/ASB-2024-06/fwb-03.patch b/Patches/LineageOS-20.0/ASB-2024-06/fwb-03.patch new file mode 100644 index 00000000..f178622b --- /dev/null +++ b/Patches/LineageOS-20.0/ASB-2024-06/fwb-03.patch @@ -0,0 +1,463 @@ +From fcb02396749ec9318803887febffea13f726dfc2 Mon Sep 17 00:00:00 2001 +From: Valentin Iftime +Date: Thu, 22 Feb 2024 10:51:58 +0100 +Subject: [PATCH] Check for NLS bind permission when rebinding services + + Also, after updating packages with NLS components, check + the approved services and remove from approved list if missing permissions. + +Test: atest ManagedServicesTest +Bug: 321707289 + +(cherry picked from commit 24b13a64f9f5e5aa7f45a2132806d6c74e2c62dc) +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:f126be35f4b9f179dbb957ec56ca80b12f47abdc) +Merged-In: I11901755ec430c6e3145def9d67e4e63cda00806 +Change-Id: I11901755ec430c6e3145def9d67e4e63cda00806 +--- + .../server/notification/ManagedServices.java | 193 ++++++++++-------- + .../notification/ManagedServicesTest.java | 54 +++++ + 2 files changed, 164 insertions(+), 83 deletions(-) + +diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java +index d0cfa164b5af..c3691e717e9b 100644 +--- a/services/core/java/com/android/server/notification/ManagedServices.java ++++ b/services/core/java/com/android/server/notification/ManagedServices.java +@@ -132,7 +132,6 @@ abstract public class ManagedServices { + + // contains connections to all connected services, including app services + // and system services +- @GuardedBy("mMutex") + private final ArrayList mServices = new ArrayList<>(); + /** + * The services that have been bound by us. If the service is also connected, it will also +@@ -151,15 +150,13 @@ abstract public class ManagedServices { + = new ArraySet<>(); + // Just the packages from mEnabledServicesForCurrentProfiles + private ArraySet mEnabledServicesPackageNames = new ArraySet<>(); +- // Per user id, list of enabled packages that have nevertheless asked not to be run +- private final android.util.SparseSetArray mSnoozing = +- new android.util.SparseSetArray<>(); ++ // List of enabled packages that have nevertheless asked not to be run ++ private ArraySet mSnoozingForCurrentProfiles = new ArraySet<>(); + + // List of approved packages or components (by user, then by primary/secondary) that are + // allowed to be bound as managed services. A package or component appearing in this list does + // not mean that we are currently bound to said package/component. +- protected final ArrayMap>> mApproved = +- new ArrayMap<>(); ++ protected ArrayMap>> mApproved = new ArrayMap<>(); + + // List of packages or components (by user) that are configured to be enabled/disabled + // explicitly by the user +@@ -318,7 +315,6 @@ ArrayMap> resetComponents(String packageName, + return changes; + } + +- @GuardedBy("mApproved") + private boolean clearUserSetFlagLocked(ComponentName component, int userId) { + String approvedValue = getApprovedValue(component.flattenToString()); + ArraySet userSet = mUserSetServices.get(userId); +@@ -379,8 +375,8 @@ public void dump(PrintWriter pw, DumpFilter filter) { + pw.println(" " + cmpt); + } + ++ pw.println(" Live " + getCaption() + "s (" + mServices.size() + "):"); + synchronized (mMutex) { +- pw.println(" Live " + getCaption() + "s (" + mServices.size() + "):"); + for (ManagedServiceInfo info : mServices) { + if (filter != null && !filter.matches(info.component)) continue; + pw.println(" " + info.component +@@ -390,15 +386,10 @@ public void dump(PrintWriter pw, DumpFilter filter) { + } + } + +- synchronized (mSnoozing) { +- pw.println(" Snoozed " + getCaption() + "s (" +- + mSnoozing.size() + "):"); +- for (int i = 0; i < mSnoozing.size(); i++) { +- pw.println(" User: " + mSnoozing.keyAt(i)); +- for (ComponentName name : mSnoozing.valuesAt(i)) { +- pw.println(" " + name.flattenToShortString()); +- } +- } ++ pw.println(" Snoozed " + getCaption() + "s (" + ++ mSnoozingForCurrentProfiles.size() + "):"); ++ for (ComponentName name : mSnoozingForCurrentProfiles) { ++ pw.println(" " + name.flattenToShortString()); + } + } + +@@ -440,16 +431,8 @@ public void dump(ProtoOutputStream proto, DumpFilter filter) { + } + } + +- synchronized (mSnoozing) { +- for (int i = 0; i < mSnoozing.size(); i++) { +- long token = proto.start(ManagedServicesProto.SNOOZED); +- proto.write(ManagedServicesProto.SnoozedServices.USER_ID, +- mSnoozing.keyAt(i)); +- for (ComponentName name : mSnoozing.valuesAt(i)) { +- name.dumpDebug(proto, ManagedServicesProto.SnoozedServices.SNOOZED); +- } +- proto.end(token); +- } ++ for (ComponentName name : mSnoozingForCurrentProfiles) { ++ name.dumpDebug(proto, ManagedServicesProto.SNOOZED); + } + } + +@@ -918,6 +901,23 @@ protected boolean isPackageOrComponentAllowed(String pkgOrComponent, int userId) + return false; + } + ++ protected boolean isPackageOrComponentAllowedWithPermission(ComponentName component, ++ int userId) { ++ if (!(isPackageOrComponentAllowed(component.flattenToString(), userId) ++ || isPackageOrComponentAllowed(component.getPackageName(), userId))) { ++ return false; ++ } ++ return componentHasBindPermission(component, userId); ++ } ++ ++ private boolean componentHasBindPermission(ComponentName component, int userId) { ++ ServiceInfo info = getServiceInfo(component, userId); ++ if (info == null) { ++ return false; ++ } ++ return mConfig.bindPermission.equals(info.permission); ++ } ++ + boolean isPackageOrComponentUserSet(String pkgOrComponent, int userId) { + synchronized (mApproved) { + ArraySet services = mUserSetServices.get(userId); +@@ -975,6 +975,7 @@ public void onPackagesChanged(boolean removingPackage, String[] pkgList, int[] u + for (int uid : uidList) { + if (isPackageAllowed(pkgName, UserHandle.getUserId(uid))) { + anyServicesInvolved = true; ++ trimApprovedListsForInvalidServices(pkgName, UserHandle.getUserId(uid)); + } + } + } +@@ -992,9 +993,6 @@ public void onUserRemoved(int user) { + synchronized (mApproved) { + mApproved.remove(user); + } +- synchronized (mSnoozing) { +- mSnoozing.remove(user); +- } + rebindServices(true, user); + } + +@@ -1014,12 +1012,10 @@ private ManagedServiceInfo getServiceFromTokenLocked(IInterface service) { + return null; + } + final IBinder token = service.asBinder(); +- synchronized (mMutex) { +- final int nServices = mServices.size(); +- for (int i = 0; i < nServices; i++) { +- final ManagedServiceInfo info = mServices.get(i); +- if (info.service.asBinder() == token) return info; +- } ++ final int N = mServices.size(); ++ for (int i = 0; i < N; i++) { ++ final ManagedServiceInfo info = mServices.get(i); ++ if (info.service.asBinder() == token) return info; + } + return null; + } +@@ -1094,17 +1090,15 @@ protected void registerGuestService(ManagedServiceInfo guest) { + } + + protected void setComponentState(ComponentName component, int userId, boolean enabled) { +- synchronized (mSnoozing) { +- boolean previous = !mSnoozing.contains(userId, component); +- if (previous == enabled) { +- return; +- } ++ boolean previous = !mSnoozingForCurrentProfiles.contains(component); ++ if (previous == enabled) { ++ return; ++ } + +- if (enabled) { +- mSnoozing.remove(userId, component); +- } else { +- mSnoozing.add(userId, component); +- } ++ if (enabled) { ++ mSnoozingForCurrentProfiles.remove(component); ++ } else { ++ mSnoozingForCurrentProfiles.add(component); + } + + // State changed +@@ -1113,8 +1107,7 @@ protected void setComponentState(ComponentName component, int userId, boolean en + + synchronized (mMutex) { + if (enabled) { +- if (isPackageOrComponentAllowed(component.flattenToString(), userId) +- || isPackageOrComponentAllowed(component.getPackageName(), userId)) { ++ if (isPackageOrComponentAllowedWithPermission(component, userId)) { + registerServiceLocked(component, userId); + } else { + Slog.d(TAG, component + " no longer has permission to be bound"); +@@ -1252,6 +1245,33 @@ private boolean removeUninstalledItemsFromApprovedLists(int uninstalledUserId, S + return removed; + } + ++ private void trimApprovedListsForInvalidServices(String packageName, int userId) { ++ synchronized (mApproved) { ++ final ArrayMap> approvedByType = mApproved.get(userId); ++ if (approvedByType == null) { ++ return; ++ } ++ for (int i = 0; i < approvedByType.size(); i++) { ++ final ArraySet approved = approvedByType.valueAt(i); ++ for (int j = approved.size() - 1; j >= 0; j--) { ++ final String approvedPackageOrComponent = approved.valueAt(j); ++ if (TextUtils.equals(getPackageName(approvedPackageOrComponent), packageName)) { ++ final ComponentName component = ComponentName.unflattenFromString( ++ approvedPackageOrComponent); ++ if (component != null && !componentHasBindPermission(component, userId)) { ++ approved.removeAt(j); ++ if (DEBUG) { ++ Slog.v(TAG, "Removing " + approvedPackageOrComponent ++ + " from approved list; no bind permission found " ++ + mConfig.bindPermission); ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ + protected String getPackageName(String packageOrComponent) { + final ComponentName component = ComponentName.unflattenFromString(packageOrComponent); + if (component != null) { +@@ -1317,10 +1337,7 @@ protected void populateComponentsToBind(SparseArray> componen + } + + final Set add = new HashSet<>(userComponents); +- ArraySet snoozed = mSnoozing.get(userId); +- if (snoozed != null) { +- add.removeAll(snoozed); +- } ++ add.removeAll(mSnoozingForCurrentProfiles); + + componentsToBind.put(userId, add); + +@@ -1438,28 +1455,20 @@ private void bindToServices(SparseArray> componentsToBind) { + final int userId = componentsToBind.keyAt(i); + final Set add = componentsToBind.get(userId); + for (ComponentName component : add) { +- try { +- ServiceInfo info = mPm.getServiceInfo(component, +- PackageManager.GET_META_DATA +- | PackageManager.MATCH_DIRECT_BOOT_AWARE +- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, +- userId); +- if (info == null) { +- Slog.w(TAG, "Not binding " + getCaption() + " service " + component +- + ": service not found"); +- continue; +- } +- if (!mConfig.bindPermission.equals(info.permission)) { +- Slog.w(TAG, "Not binding " + getCaption() + " service " + component +- + ": it does not require the permission " + mConfig.bindPermission); +- continue; +- } +- Slog.v(TAG, +- "enabling " + getCaption() + " for " + userId + ": " + component); +- registerService(info, userId); +- } catch (RemoteException e) { +- e.rethrowFromSystemServer(); ++ ServiceInfo info = getServiceInfo(component, userId); ++ if (info == null) { ++ Slog.w(TAG, "Not binding " + getCaption() + " service " + component ++ + ": service not found"); ++ continue; + } ++ if (!mConfig.bindPermission.equals(info.permission)) { ++ Slog.w(TAG, "Not binding " + getCaption() + " service " + component ++ + ": it does not require the permission " + mConfig.bindPermission); ++ continue; ++ } ++ Slog.v(TAG, ++ "enabling " + getCaption() + " for " + userId + ": " + component); ++ registerService(info, userId); + } + } + } +@@ -1484,8 +1493,7 @@ void registerService(final ComponentName cn, final int userId) { + void reregisterService(final ComponentName cn, final int userId) { + // If rebinding a package that died, ensure it still has permission + // after the rebind delay +- if (isPackageOrComponentAllowed(cn.getPackageName(), userId) +- || isPackageOrComponentAllowed(cn.flattenToString(), userId)) { ++ if (isPackageOrComponentAllowedWithPermission(cn, userId)) { + registerService(cn, userId); + } + } +@@ -1499,12 +1507,10 @@ public void registerSystemService(final ComponentName name, final int userid) { + } + } + +- @GuardedBy("mMutex") + private void registerServiceLocked(final ComponentName name, final int userid) { + registerServiceLocked(name, userid, false /* isSystem */); + } + +- @GuardedBy("mMutex") + private void registerServiceLocked(final ComponentName name, final int userid, + final boolean isSystem) { + if (DEBUG) Slog.v(TAG, "registerService: " + name + " u=" + userid); +@@ -1590,9 +1596,12 @@ public void onBindingDied(ComponentName name) { + unbindService(this, name, userid); + if (!mServicesRebinding.contains(servicesBindingTag)) { + mServicesRebinding.add(servicesBindingTag); +- mHandler.postDelayed(() -> +- reregisterService(name, userid), +- ON_BINDING_DIED_REBIND_DELAY_MS); ++ mHandler.postDelayed(new Runnable() { ++ @Override ++ public void run() { ++ reregisterService(name, userid); ++ } ++ }, ON_BINDING_DIED_REBIND_DELAY_MS); + } else { + Slog.v(TAG, getCaption() + " not rebinding in user " + userid + + " as a previous rebind attempt was made: " + name); +@@ -1635,7 +1644,6 @@ private void unregisterService(ComponentName name, int userid) { + } + } + +- @GuardedBy("mMutex") + private void unregisterServiceLocked(ComponentName name, int userid) { + final int N = mServices.size(); + for (int i = N - 1; i >= 0; i--) { +@@ -1670,7 +1678,6 @@ private ManagedServiceInfo removeServiceImpl(IInterface service, final int useri + return serviceInfo; + } + +- @GuardedBy("mMutex") + private ManagedServiceInfo removeServiceLocked(int i) { + final ManagedServiceInfo info = mServices.remove(i); + onServiceRemovedLocked(info); +@@ -1724,6 +1731,19 @@ private void unbindService(ServiceConnection connection, ComponentName component + } + } + ++ private ServiceInfo getServiceInfo(ComponentName component, int userId) { ++ try { ++ return mPm.getServiceInfo(component, ++ PackageManager.GET_META_DATA ++ | PackageManager.MATCH_DIRECT_BOOT_AWARE ++ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, ++ userId); ++ } catch (RemoteException e) { ++ e.rethrowFromSystemServer(); ++ } ++ return null; ++ } ++ + public class ManagedServiceInfo implements IBinder.DeathRecipient { + public IInterface service; + public ComponentName component; +@@ -1823,7 +1843,7 @@ public boolean isEnabledForCurrentProfiles() { + * from receiving events from the profile. + */ + public boolean isPermittedForProfile(int userId) { +- if (!mUserProfiles.isProfileUser(userId)) { ++ if (!mUserProfiles.isManagedProfile(userId)) { + return true; + } + DevicePolicyManager dpm = +@@ -1899,6 +1919,13 @@ public boolean isCurrentProfile(int userId) { + } + } + ++ public boolean isManagedProfile(int userId) { ++ synchronized (mCurrentProfiles) { ++ UserInfo user = mCurrentProfiles.get(userId); ++ return user != null && user.isManagedProfile(); ++ } ++ } ++ + public boolean isProfileUser(int userId) { + synchronized (mCurrentProfiles) { + UserInfo user = mCurrentProfiles.get(userId); +diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +index 8b1384ed894f..7c7bb509ebb4 100644 +--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java ++++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +@@ -33,8 +33,10 @@ + import static org.mockito.Matchers.anyInt; + import static org.mockito.Matchers.anyLong; + import static org.mockito.Matchers.eq; ++import static org.mockito.Mockito.doReturn; + import static org.mockito.Mockito.mock; + import static org.mockito.Mockito.never; ++import static org.mockito.Mockito.spy; + import static org.mockito.Mockito.times; + import static org.mockito.Mockito.verify; + import static org.mockito.Mockito.when; +@@ -1117,6 +1119,58 @@ public void testUpgradeAppBindsNewServices() throws Exception { + } + } + ++ @Test ++ public void testUpgradeAppNoPermissionNoRebind() throws Exception { ++ Context context = spy(getContext()); ++ doReturn(true).when(context).bindServiceAsUser(any(), any(), anyInt(), any()); ++ ++ ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, ++ mIpm, ++ APPROVAL_BY_COMPONENT); ++ ++ List packages = new ArrayList<>(); ++ packages.add("package"); ++ addExpectedServices(service, packages, 0); ++ ++ final ComponentName unapprovedComponent = ComponentName.unflattenFromString("package/C1"); ++ final ComponentName approvedComponent = ComponentName.unflattenFromString("package/C2"); ++ ++ // Both components are approved initially ++ mExpectedPrimaryComponentNames.clear(); ++ mExpectedPrimaryPackages.clear(); ++ mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2"); ++ mExpectedSecondaryComponentNames.clear(); ++ mExpectedSecondaryPackages.clear(); ++ ++ loadXml(service); ++ ++ //Component package/C1 loses bind permission ++ when(mIpm.getServiceInfo(any(), anyLong(), anyInt())).thenAnswer( ++ (Answer) invocation -> { ++ ComponentName invocationCn = invocation.getArgument(0); ++ if (invocationCn != null) { ++ ServiceInfo serviceInfo = new ServiceInfo(); ++ serviceInfo.packageName = invocationCn.getPackageName(); ++ serviceInfo.name = invocationCn.getClassName(); ++ if (invocationCn.equals(unapprovedComponent)) { ++ serviceInfo.permission = "none"; ++ } else { ++ serviceInfo.permission = service.getConfig().bindPermission; ++ } ++ serviceInfo.metaData = null; ++ return serviceInfo; ++ } ++ return null; ++ } ++ ); ++ ++ // Trigger package update ++ service.onPackagesChanged(false, new String[]{"package"}, new int[]{0}); ++ ++ assertFalse(service.isComponentEnabledForCurrentProfiles(unapprovedComponent)); ++ assertTrue(service.isComponentEnabledForCurrentProfiles(approvedComponent)); ++ } ++ + @Test + public void testSetPackageOrComponentEnabled() throws Exception { + for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) { diff --git a/Patches/LineageOS-20.0/ASB-2024-06/fwb-04.patch b/Patches/LineageOS-20.0/ASB-2024-06/fwb-04.patch new file mode 100644 index 00000000..fcc305f8 --- /dev/null +++ b/Patches/LineageOS-20.0/ASB-2024-06/fwb-04.patch @@ -0,0 +1,31 @@ +From 88ea616cf95aff9169daec33a0a3d93a30b15727 Mon Sep 17 00:00:00 2001 +From: Yi-an Chen +Date: Wed, 21 Feb 2024 01:56:22 +0000 +Subject: [PATCH] Fix error handling for non-dynamic permissions + +We only allow removing dynamic permissions. When removePermission() is +called for a non-dynamic permission, in addition to logging it, we +should also return early to avoid the removePermission() call. + +Test: manual +Bug: 321555066 +Fixes: 321711213 +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:528a87e90ff9354581d54fd37fbe9f95cccbcdb1) +Merged-In: Ie2f43663bc71a06ffadb868d2d0eea5ee78f76e5 +Change-Id: Ie2f43663bc71a06ffadb868d2d0eea5ee78f76e5 +--- + .../server/pm/permission/PermissionManagerServiceImpl.java | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +index 5dc7e23c01e2..1ec3403a9d46 100644 +--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java ++++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +@@ -681,6 +681,7 @@ public void removePermission(String permName) { + // TODO: switch this back to SecurityException + Slog.wtf(TAG, "Not allowed to modify non-dynamic permission " + + permName); ++ return; + } + mRegistry.removePermission(permName); + } diff --git a/Patches/LineageOS-20.0/ASB-2024-06/fwb-05.patch b/Patches/LineageOS-20.0/ASB-2024-06/fwb-05.patch new file mode 100644 index 00000000..53b6e0b1 --- /dev/null +++ b/Patches/LineageOS-20.0/ASB-2024-06/fwb-05.patch @@ -0,0 +1,71 @@ +From 091715c8307fed95ba63870f0c54e74208a78332 Mon Sep 17 00:00:00 2001 +From: Lokesh Kumar Goel +Date: Tue, 27 Feb 2024 23:05:05 +0000 +Subject: [PATCH] Fix vulnerability in AttributionSource due to incorrect + Binder call + +AttributionSource uses Binder.getCallingUid to verify the UID of the +caller from another process. However, getCallingUid does not always +behave as expected. If the AttributionSource is unparceled outside a +transaction thread, which is quite possible, getCallingUid will return +the UID of the current process instead. If this is a system process, +the UID check gets bypassed entirely, meaning any uid can be provided. + +This patch fixes the vulnerability by emptying out the state of the +AttributionSource, so that the service checking its credentials will +fail to give permission to the app. + +Bug: 267231571 +Test: v2/android-virtual-infra/test_mapping/presubmit-avd +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:5d79e535b9a802680062545e15fc1faaf779c0bf) +Merged-In: I3f228064fbd62e1c907f1ebe870cb61102f788f0 +Change-Id: I3f228064fbd62e1c907f1ebe870cb61102f788f0 +--- + .../android/content/AttributionSource.java | 20 ++++++++++++++++--- + 1 file changed, 17 insertions(+), 3 deletions(-) + +diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java +index 3f2fa2188d24..16b18c85e790 100644 +--- a/core/java/android/content/AttributionSource.java ++++ b/core/java/android/content/AttributionSource.java +@@ -31,6 +31,7 @@ + import android.os.Process; + import android.permission.PermissionManager; + import android.util.ArraySet; ++import android.util.Log; + + import com.android.internal.annotations.Immutable; + +@@ -87,6 +88,8 @@ + */ + @Immutable + public final class AttributionSource implements Parcelable { ++ private static final String TAG = "AttributionSource"; ++ + private static final String DESCRIPTOR = "android.content.AttributionSource"; + + private static final Binder sDefaultToken = new Binder(DESCRIPTOR); +@@ -154,9 +157,20 @@ public AttributionSource(@NonNull AttributionSource current, @Nullable Attributi + AttributionSource(@NonNull Parcel in) { + this(AttributionSourceState.CREATOR.createFromParcel(in)); + +- // Since we just unpacked this object as part of it transiting a Binder +- // call, this is the perfect time to enforce that its UID and PID can be trusted +- enforceCallingUidAndPid(); ++ if (!Binder.isDirectlyHandlingTransaction()) { ++ Log.e(TAG, "Unable to verify calling UID #" + mAttributionSourceState.uid + " PID #" ++ + mAttributionSourceState.pid + " when not handling Binder transaction; " ++ + "clearing."); ++ mAttributionSourceState.pid = -1; ++ mAttributionSourceState.uid = -1; ++ mAttributionSourceState.packageName = null; ++ mAttributionSourceState.attributionTag = null; ++ mAttributionSourceState.next = null; ++ } else { ++ // Since we just unpacked this object as part of it transiting a Binder ++ // call, this is the perfect time to enforce that its UID and PID can be trusted ++ enforceCallingUidAndPid(); ++ } + } + + /** @hide */ diff --git a/Patches/LineageOS-20.0/ASB-2024-06/fwb-06.patch b/Patches/LineageOS-20.0/ASB-2024-06/fwb-06.patch new file mode 100644 index 00000000..a361be5b --- /dev/null +++ b/Patches/LineageOS-20.0/ASB-2024-06/fwb-06.patch @@ -0,0 +1,70 @@ +From b403e48e6aff45880c2970102555cc77cdcfe322 Mon Sep 17 00:00:00 2001 +From: Riddle Hsu +Date: Tue, 6 Feb 2024 17:19:37 +0800 +Subject: [PATCH] Hide window immediately if itself doesn't run hide animation + +The condition was overextended in commit 9bca6b4 which checks if the +parent container of the window is animating. That causes the window to +wait for animation finish to update visibility, but the animation +finish callback won't happen because itself is not animating. Then the +window that should be hidden remains on screen. + +Bug: 302431573 +Test: atest WindowStateTests#testIsOnScreen_hiddenByPolicy +(cherry picked from commit 9add9281ffc120c81a7d125892803f1beb5ddcb3) +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:10a7f0914c87f4af521b5cbb13e84a83dacebf82) +Merged-In: Iafc2b2c2a24d8fc8d147354ef2f0b4afeeb510c5 +Change-Id: Iafc2b2c2a24d8fc8d147354ef2f0b4afeeb510c5 +--- + .../com/android/server/wm/WindowState.java | 6 ++++-- + .../android/server/wm/WindowStateTests.java | 20 +++++++++++++++++++ + 2 files changed, 24 insertions(+), 2 deletions(-) + +diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java +index 6d6e84f611cf..0ba2f2f2f418 100644 +--- a/services/core/java/com/android/server/wm/WindowState.java ++++ b/services/core/java/com/android/server/wm/WindowState.java +@@ -3296,8 +3296,10 @@ boolean hide(boolean doAnimation, boolean requestAnim) { + return false; + } + if (doAnimation) { +- mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false); +- if (!isAnimating(TRANSITION | PARENTS)) { ++ // If a hide animation is applied, then let onAnimationFinished ++ // -> checkPolicyVisibilityChange hide the window. Otherwise make doAnimation false ++ // to commit invisible immediately. ++ if (!mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false /* isEntrance */)) { + doAnimation = false; + } + } +diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +index 219f4415c623..44ae8dde9bde 100644 +--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java ++++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +@@ -227,6 +227,26 @@ public void testIsOnScreen_hiddenByPolicy() { + assertTrue(window.isOnScreen()); + window.hide(false /* doAnimation */, false /* requestAnim */); + assertFalse(window.isOnScreen()); ++ ++ // Verifies that a window without animation can be hidden even if its parent is animating. ++ window.show(false /* doAnimation */, false /* requestAnim */); ++ assertTrue(window.isVisibleByPolicy()); ++ window.getParent().startAnimation(mTransaction, mock(AnimationAdapter.class), ++ false /* hidden */, SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION); ++ window.mAttrs.windowAnimations = 0; ++ window.hide(true /* doAnimation */, true /* requestAnim */); ++ assertFalse(window.isSelfAnimating(0, SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION)); ++ assertFalse(window.isVisibleByPolicy()); ++ assertFalse(window.isOnScreen()); ++ ++ // Verifies that a window with animation can be hidden after the hide animation is finished. ++ window.show(false /* doAnimation */, false /* requestAnim */); ++ window.mAttrs.windowAnimations = android.R.style.Animation_Dialog; ++ window.hide(true /* doAnimation */, true /* requestAnim */); ++ assertTrue(window.isSelfAnimating(0, SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION)); ++ assertTrue(window.isVisibleByPolicy()); ++ window.cancelAnimation(); ++ assertFalse(window.isVisibleByPolicy()); + } + + @Test diff --git a/Patches/LineageOS-20.0/ASB-2024-06/fwb-07.patch b/Patches/LineageOS-20.0/ASB-2024-06/fwb-07.patch new file mode 100644 index 00000000..c1834ee4 --- /dev/null +++ b/Patches/LineageOS-20.0/ASB-2024-06/fwb-07.patch @@ -0,0 +1,43 @@ +From ac134a1b6a0ace6bf43e83d414f6433f3cf40e53 Mon Sep 17 00:00:00 2001 +From: Dmitry Dementyev +Date: Tue, 26 Mar 2024 10:31:44 -0700 +Subject: [PATCH] Add more checkKeyIntent checks to AccountManagerService. + +Another verification is needed after Bundle modification. +Bug: 321941232 +Test: manual +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:36db8a1d61a881f89fdd3911886adcda6e1f0d7f) +Merged-In: I9e45d758a2320328da5664b6341eafe6f285f297 +Change-Id: I9e45d758a2320328da5664b6341eafe6f285f297 +--- + .../android/server/accounts/AccountManagerService.java | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java +index 1c3564bfdba2..bc13f106ce6e 100644 +--- a/services/core/java/com/android/server/accounts/AccountManagerService.java ++++ b/services/core/java/com/android/server/accounts/AccountManagerService.java +@@ -3561,6 +3561,11 @@ public void onResult(Bundle result) { + + // Strip auth token from result. + result.remove(AccountManager.KEY_AUTHTOKEN); ++ if (!checkKeyIntent(Binder.getCallingUid(), result)) { ++ onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, ++ "invalid intent in bundle returned"); ++ return; ++ } + + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, +@@ -5146,6 +5151,11 @@ public void onResult(Bundle result) { + } else { + if (mStripAuthTokenFromResult) { + result.remove(AccountManager.KEY_AUTHTOKEN); ++ if (!checkKeyIntent(Binder.getCallingUid(), result)) { ++ onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, ++ "invalid intent in bundle returned"); ++ return; ++ } + } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, getClass().getSimpleName() diff --git a/Patches/LineageOS-20.0/ASB-2024-06/fwb-08.patch b/Patches/LineageOS-20.0/ASB-2024-06/fwb-08.patch new file mode 100644 index 00000000..c14de248 --- /dev/null +++ b/Patches/LineageOS-20.0/ASB-2024-06/fwb-08.patch @@ -0,0 +1,70 @@ +From 5dd0b87bc501c83abbe2462d17fcc7f9f3dfab4d Mon Sep 17 00:00:00 2001 +From: Haoran Zhang +Date: Wed, 13 Mar 2024 17:08:00 +0000 +Subject: [PATCH] [DO NOT MERGE][Autofill Framework] Add in check for intent + filter when setting/updating service + +For test, I registered two tests around on ABTD. CtsAutoFillServiceTestCases module is passing except three known failures: + +Test run link: +- https://android-build.corp.google.com/builds/abtd/run/L33300030002610600 +- https://android-build.corp.google.com/builds/abtd/run/L58100030002616607 + + +Bug: b/324874908 +Test: atest CtsAutoFillServiceTestCases +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:141d9d050346bfc4673c429382deb1b3d210f6ad) +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:ee20adb4b4b2065e040167a4354c4fabaf06e35d) +Merged-In: I51c2e3788ac29ff4d6b86aa2a735ff2ea1463a77 +Change-Id: I51c2e3788ac29ff4d6b86aa2a735ff2ea1463a77 +--- + .../autofill/AutofillManagerServiceImpl.java | 27 +++++++++++++++++++ + 1 file changed, 27 insertions(+) + +diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +index 20b2a74f5be5..2cda39d4b065 100644 +--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java ++++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +@@ -32,8 +32,10 @@ + import android.annotation.Nullable; + import android.app.ActivityManagerInternal; + import android.content.ComponentName; ++import android.content.Intent; + import android.content.pm.PackageManager; + import android.content.pm.PackageManager.NameNotFoundException; ++import android.content.pm.ResolveInfo; + import android.content.pm.ServiceInfo; + import android.graphics.Rect; + import android.metrics.LogMaker; +@@ -239,6 +241,31 @@ protected boolean updateLocked(boolean disabled) { + @Override // from PerUserSystemService + protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent) + throws NameNotFoundException { ++ final List resolveInfos = ++ getContext().getPackageManager().queryIntentServicesAsUser( ++ new Intent(AutofillService.SERVICE_INTERFACE), ++ // The MATCH_INSTANT flag is added because curret autofill CTS module is ++ // defined in one apk, which makes the test autofill service installed in a ++ // instant app when the CTS tests are running in instant app mode. ++ // TODO: Remove MATCH_INSTANT flag after completing refactoring the CTS module ++ // to make the test autofill service a separate apk. ++ PackageManager.GET_META_DATA | PackageManager.MATCH_INSTANT, ++ mUserId); ++ boolean serviceHasAutofillIntentFilter = false; ++ for (ResolveInfo resolveInfo : resolveInfos) { ++ final ServiceInfo serviceInfo = resolveInfo.serviceInfo; ++ if (serviceInfo.getComponentName().equals(serviceComponent)) { ++ serviceHasAutofillIntentFilter = true; ++ break; ++ } ++ } ++ if (!serviceHasAutofillIntentFilter) { ++ Slog.w(TAG, ++ "Autofill service from '" + serviceComponent.getPackageName() + "' does" ++ + "not have intent filter " + AutofillService.SERVICE_INTERFACE); ++ throw new SecurityException("Service does not declare intent filter " ++ + AutofillService.SERVICE_INTERFACE); ++ } + mInfo = new AutofillServiceInfo(getContext(), serviceComponent, mUserId); + return mInfo.getServiceInfo(); + } diff --git a/Patches/LineageOS-20.0/ASB-2024-06/fwb-09.patch b/Patches/LineageOS-20.0/ASB-2024-06/fwb-09.patch new file mode 100644 index 00000000..fe8e92c4 --- /dev/null +++ b/Patches/LineageOS-20.0/ASB-2024-06/fwb-09.patch @@ -0,0 +1,47 @@ +From d0df12d2f498f441d87852580a89f8588380d902 Mon Sep 17 00:00:00 2001 +From: Hans Boehm +Date: Tue, 2 Jan 2024 16:53:13 -0800 +Subject: [PATCH] Check hidden API exemptions + +Refuse to deal with newlines and null characters in +HiddenApiSettings.update(). Also disallow nulls in process start +arguments. + +Bug: 316153291 +Test: Treehugger for now +(cherry picked from commit 7ba059e2cf0a2c20f9a849719cdc32b12c933a44) +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:60669aa49aba34c0950d6246bd95b54f91a3c8e8) +Merged-In: I83cd60e46407a4a082f9f3c80e937dbd522dbac4 +Change-Id: I83cd60e46407a4a082f9f3c80e937dbd522dbac4 +--- + core/java/android/os/ZygoteProcess.java | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java +index bf2898137967..63bcf5f119e1 100644 +--- a/core/java/android/os/ZygoteProcess.java ++++ b/core/java/android/os/ZygoteProcess.java +@@ -431,6 +431,8 @@ private Process.ProcessStartResult zygoteSendArgsAndGetResult( + throw new ZygoteStartFailedEx("Embedded newlines not allowed"); + } else if (arg.indexOf('\r') >= 0) { + throw new ZygoteStartFailedEx("Embedded carriage returns not allowed"); ++ } else if (arg.indexOf('\u0000') >= 0) { ++ throw new ZygoteStartFailedEx("Embedded nulls not allowed"); + } + } + +@@ -972,6 +974,14 @@ private boolean maybeSetApiDenylistExemptions(ZygoteState state, boolean sendIfE + return true; + } + ++ for (/* NonNull */ String s : mApiDenylistExemptions) { ++ // indexOf() is intrinsified and faster than contains(). ++ if (s.indexOf('\n') >= 0 || s.indexOf('\r') >= 0 || s.indexOf('\u0000') >= 0) { ++ Slog.e(LOG_TAG, "Failed to set API denylist exemptions: Bad character"); ++ mApiDenylistExemptions = Collections.emptyList(); ++ return false; ++ } ++ } + try { + state.mZygoteOutputWriter.write(Integer.toString(mApiDenylistExemptions.size() + 1)); + state.mZygoteOutputWriter.newLine(); diff --git a/Patches/LineageOS-20.0/ASB-2024-06/fwb-10.patch b/Patches/LineageOS-20.0/ASB-2024-06/fwb-10.patch new file mode 100644 index 00000000..fa65bba2 --- /dev/null +++ b/Patches/LineageOS-20.0/ASB-2024-06/fwb-10.patch @@ -0,0 +1,61 @@ +From cbf4c6352bf85e4d8289d6bc1135d66c95f0e1e1 Mon Sep 17 00:00:00 2001 +From: Guojing Yuan +Date: Thu, 14 Dec 2023 19:30:04 +0000 +Subject: [PATCH] [CDM][CMD] Check permissions for CDM shell commands + +Override handleShellCommand instead of onShellCommand because +Binder.onShellCommand checks the necessary permissions of the caller. + +Bug: 313428840 + +Test: manually tested CDM shell commands +(cherry picked from commit 1761a0fee9c2cd9787bbb7fbdbe30b4c2b03396e) +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:1ae3b43c248cdf5ee63311f06acd0ee19d93f0cd) +Merged-In: I5539b3594feb5544c458c0fd1061b51a0a808900 +Change-Id: I5539b3594feb5544c458c0fd1061b51a0a808900 +--- + .../CompanionDeviceManagerService.java | 20 ++++++++----------- + 1 file changed, 8 insertions(+), 12 deletions(-) + +diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +index 41546d2bdc38..05c29d7e446a 100644 +--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java ++++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +@@ -75,12 +75,11 @@ + import android.os.Handler; + import android.os.Message; + import android.os.Parcel; ++import android.os.ParcelFileDescriptor; + import android.os.PowerWhitelistManager; + import android.os.RemoteCallbackList; + import android.os.RemoteException; +-import android.os.ResultReceiver; + import android.os.ServiceManager; +-import android.os.ShellCallback; + import android.os.SystemProperties; + import android.os.UserHandle; + import android.os.UserManager; +@@ -827,16 +826,13 @@ public boolean canPairWithoutPrompt(String packageName, String macAddress, int u + } + + @Override +- public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, +- String[] args, ShellCallback callback, ResultReceiver resultReceiver) +- throws RemoteException { +- enforceCallerCanManageCompanionDevice(getContext(), "onShellCommand"); +- +- final CompanionDeviceShellCommand cmd = new CompanionDeviceShellCommand( +- CompanionDeviceManagerService.this, +- mAssociationStore, +- mDevicePresenceMonitor); +- cmd.exec(this, in, out, err, args, callback, resultReceiver); ++ public int handleShellCommand(@NonNull ParcelFileDescriptor in, ++ @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, ++ @NonNull String[] args) { ++ return new CompanionDeviceShellCommand(CompanionDeviceManagerService.this, ++ mAssociationStore, mDevicePresenceMonitor) ++ .exec(this, in.getFileDescriptor(), out.getFileDescriptor(), ++ err.getFileDescriptor(), args); + } + + @Override diff --git a/Patches/LineageOS-20.0/ASB-2024-06/fwb-11.patch b/Patches/LineageOS-20.0/ASB-2024-06/fwb-11.patch new file mode 100644 index 00000000..9b2fe592 --- /dev/null +++ b/Patches/LineageOS-20.0/ASB-2024-06/fwb-11.patch @@ -0,0 +1,912 @@ +From d444b292de11e14d41a5ce1897cd32360d065671 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= +Date: Fri, 22 Mar 2024 14:26:23 +0100 +Subject: [PATCH] Resolve message/conversation image Uris with the correct user + id + +Bug: 317503801 +Test: atest ExpandableNotificationRowTest +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:83975c515773d345c64cadac1cd639ae5c4a8397) +Merged-In: I11c5b39f2d9d8f0788acab43640a6d4abcd5a179 +Change-Id: I11c5b39f2d9d8f0788acab43640a6d4abcd5a179 +--- + .../row/ExpandableNotificationRow.java | 13 +- + .../row/NotificationInlineImageResolver.java | 7 +- + .../row/ExpandableNotificationRowTest.java | 554 +++++------------- + .../systemui/SysuiTestableContext.java | 22 + + 4 files changed, 194 insertions(+), 402 deletions(-) + +diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +index 11598e0c1f51..1cec84a5b02d 100644 +--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java ++++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +@@ -45,6 +45,7 @@ + import android.os.RemoteException; + import android.os.ServiceManager; + import android.os.Trace; ++import android.os.UserHandle; + import android.service.notification.StatusBarNotification; + import android.util.ArraySet; + import android.util.AttributeSet; +@@ -1669,8 +1670,6 @@ void logSkipAttachingKeepInParentChild( + */ + public ExpandableNotificationRow(Context context, AttributeSet attrs) { + super(context, attrs); +- mImageResolver = new NotificationInlineImageResolver(context, +- new NotificationInlineImageCache()); + float radius = getResources().getDimension(R.dimen.notification_corner_radius_small); + mSmallRoundness = radius / getMaxRadius(); + initDimens(); +@@ -1706,6 +1705,8 @@ public void initialize( + FeatureFlags featureFlags, + IStatusBarService statusBarService) { + mEntry = entry; ++ mImageResolver = new NotificationInlineImageResolver(userContextForEntry(mContext, entry), ++ new NotificationInlineImageCache()); + mAppName = appName; + if (mMenuRow == null) { + mMenuRow = new NotificationMenuRow(mContext, peopleNotificationIdentifier); +@@ -1743,6 +1744,14 @@ public void initialize( + mFeatureFlags = featureFlags; + } + ++ private static Context userContextForEntry(Context base, NotificationEntry entry) { ++ if (base.getUserId() == entry.getSbn().getNormalizedUserId()) { ++ return base; ++ } ++ return base.createContextAsUser( ++ UserHandle.of(entry.getSbn().getNormalizedUserId()), /* flags= */ 0); ++ } ++ + private void initDimens() { + mMaxSmallHeightBeforeN = NotificationUtils.getFontScaledHeight(mContext, + R.dimen.notification_min_height_legacy); +diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java +index c620f448b3b7..3e932aa616b8 100644 +--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java ++++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java +@@ -66,7 +66,7 @@ public class NotificationInlineImageResolver implements ImageResolver { + * @param imageCache The implementation of internal cache. + */ + public NotificationInlineImageResolver(Context context, ImageCache imageCache) { +- mContext = context.getApplicationContext(); ++ mContext = context; + mImageCache = imageCache; + + if (mImageCache != null) { +@@ -76,6 +76,11 @@ public NotificationInlineImageResolver(Context context, ImageCache imageCache) { + updateMaxImageSizes(); + } + ++ @VisibleForTesting ++ public Context getContext() { ++ return mContext; ++ } ++ + /** + * Check if this resolver has its internal cache implementation. + * @return True if has its internal cache, false otherwise. +diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +index 957b0f10ec1f..67aae77eee36 100644 +--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java ++++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +@@ -23,11 +23,14 @@ + import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; + import static com.android.systemui.statusbar.NotificationEntryHelper.modifySbn; + import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL; ++import static com.android.systemui.statusbar.notification.row.NotificationTestHelper.PKG; ++import static com.android.systemui.statusbar.notification.row.NotificationTestHelper.USER_HANDLE; + + import static com.google.common.truth.Truth.assertThat; + + import static org.junit.Assert.assertEquals; + import static org.junit.Assert.assertFalse; ++import static org.junit.Assert.assertNull; + import static org.junit.Assert.assertTrue; + import static org.mockito.ArgumentMatchers.any; + import static org.mockito.ArgumentMatchers.anyInt; +@@ -38,37 +41,29 @@ + import static org.mockito.Mockito.spy; + import static org.mockito.Mockito.times; + import static org.mockito.Mockito.verify; +-import static org.mockito.Mockito.verifyNoMoreInteractions; + import static org.mockito.Mockito.when; + + import android.app.Notification; + import android.app.NotificationChannel; +-import android.graphics.Color; +-import android.graphics.drawable.AnimatedVectorDrawable; +-import android.graphics.drawable.AnimationDrawable; +-import android.graphics.drawable.Drawable; ++import android.content.Context; ++import android.os.UserHandle; + import android.testing.AndroidTestingRunner; + import android.testing.TestableLooper; + import android.testing.TestableLooper.RunWithLooper; + import android.util.DisplayMetrics; + import android.view.View; +-import android.widget.ImageView; + + import androidx.test.filters.SmallTest; + + import com.android.internal.R; +-import com.android.internal.widget.CachingIconView; + import com.android.systemui.SysuiTestCase; +-import com.android.systemui.flags.FakeFeatureFlags; +-import com.android.systemui.flags.Flags; ++import com.android.systemui.SysuiTestableContext; + import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; + import com.android.systemui.plugins.statusbar.StatusBarStateController; + import com.android.systemui.statusbar.notification.AboveShelfChangedListener; + import com.android.systemui.statusbar.notification.FeedbackIcon; +-import com.android.systemui.statusbar.notification.SourceType; +-import com.android.systemui.statusbar.notification.collection.NotificationEntry; + import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener; +-import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; ++import com.android.systemui.statusbar.notification.collection.NotificationEntry; + import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer; + + import org.junit.Assert; +@@ -80,16 +75,20 @@ + import org.mockito.junit.MockitoJUnit; + import org.mockito.junit.MockitoRule; + +-import java.util.Arrays; + import java.util.List; +-import java.util.function.Consumer; + + @SmallTest + @RunWith(AndroidTestingRunner.class) + @RunWithLooper + public class ExpandableNotificationRowTest extends SysuiTestCase { + ++ private ExpandableNotificationRow mGroupRow; ++ private ExpandableNotificationRow mNotifRow; ++ private ExpandableNotificationRow mPublicRow; ++ + private NotificationTestHelper mNotificationTestHelper; ++ boolean mHeadsUpAnimatingAway = false; ++ + @Rule public MockitoRule mockito = MockitoJUnit.rule(); + + @Before +@@ -100,109 +99,87 @@ public void setUp() throws Exception { + mDependency, + TestableLooper.get(this)); + mNotificationTestHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL); ++ // create a standard private notification row ++ Notification normalNotif = mNotificationTestHelper.createNotification(); ++ normalNotif.publicVersion = null; ++ mNotifRow = mNotificationTestHelper.createRow(normalNotif); ++ // create a notification row whose public version is identical ++ Notification publicNotif = mNotificationTestHelper.createNotification(); ++ publicNotif.publicVersion = mNotificationTestHelper.createNotification(); ++ mPublicRow = mNotificationTestHelper.createRow(publicNotif); ++ // create a group row ++ mGroupRow = mNotificationTestHelper.createGroup(); ++ mGroupRow.setHeadsUpAnimatingAwayListener( ++ animatingAway -> mHeadsUpAnimatingAway = animatingAway); + +- FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags(); +- fakeFeatureFlags.set(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE, true); +- fakeFeatureFlags.set(Flags.SENSITIVE_REVEAL_ANIM, false); +- mNotificationTestHelper.setFeatureFlags(fakeFeatureFlags); +- } +- +- @Test +- public void testUpdateBackgroundColors_isRecursive() throws Exception { +- ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); +- group.setTintColor(Color.RED); +- group.getChildNotificationAt(0).setTintColor(Color.GREEN); +- group.getChildNotificationAt(1).setTintColor(Color.BLUE); +- +- assertThat(group.getCurrentBackgroundTint()).isEqualTo(Color.RED); +- assertThat(group.getChildNotificationAt(0).getCurrentBackgroundTint()) +- .isEqualTo(Color.GREEN); +- assertThat(group.getChildNotificationAt(1).getCurrentBackgroundTint()) +- .isEqualTo(Color.BLUE); +- +- group.updateBackgroundColors(); +- +- int resetTint = group.getCurrentBackgroundTint(); +- assertThat(resetTint).isNotEqualTo(Color.RED); +- assertThat(group.getChildNotificationAt(0).getCurrentBackgroundTint()) +- .isEqualTo(resetTint); +- assertThat(group.getChildNotificationAt(1).getCurrentBackgroundTint()) +- .isEqualTo(resetTint); + } + + @Test +- public void testSetSensitiveOnNotifRowNotifiesOfHeightChange() throws Exception { ++ public void testSetSensitiveOnNotifRowNotifiesOfHeightChange() throws InterruptedException { + // GIVEN a sensitive notification row that's currently redacted +- ExpandableNotificationRow row = mNotificationTestHelper.createRow(); +- measureAndLayout(row); +- row.setHideSensitiveForIntrinsicHeight(true); +- row.setSensitive(true, true); +- assertThat(row.getShowingLayout()).isSameInstanceAs(row.getPublicLayout()); +- assertThat(row.getIntrinsicHeight()).isGreaterThan(0); ++ measureAndLayout(mNotifRow); ++ mNotifRow.setHideSensitiveForIntrinsicHeight(true); ++ mNotifRow.setSensitive(true, true); ++ assertThat(mNotifRow.getShowingLayout()).isSameInstanceAs(mNotifRow.getPublicLayout()); ++ assertThat(mNotifRow.getIntrinsicHeight()).isGreaterThan(0); + + // GIVEN that the row has a height change listener + OnHeightChangedListener listener = mock(OnHeightChangedListener.class); +- row.setOnHeightChangedListener(listener); ++ mNotifRow.setOnHeightChangedListener(listener); + + // WHEN the row is set to no longer be sensitive +- row.setSensitive(false, true); ++ mNotifRow.setSensitive(false, true); + + // VERIFY that the height change listener is invoked +- assertThat(row.getShowingLayout()).isSameInstanceAs(row.getPrivateLayout()); +- assertThat(row.getIntrinsicHeight()).isGreaterThan(0); +- verify(listener).onHeightChanged(eq(row), eq(false)); ++ assertThat(mNotifRow.getShowingLayout()).isSameInstanceAs(mNotifRow.getPrivateLayout()); ++ assertThat(mNotifRow.getIntrinsicHeight()).isGreaterThan(0); ++ verify(listener).onHeightChanged(eq(mNotifRow), eq(false)); + } + + @Test +- public void testSetSensitiveOnGroupRowNotifiesOfHeightChange() throws Exception { ++ public void testSetSensitiveOnGroupRowNotifiesOfHeightChange() { + // GIVEN a sensitive group row that's currently redacted +- ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); +- measureAndLayout(group); +- group.setHideSensitiveForIntrinsicHeight(true); +- group.setSensitive(true, true); +- assertThat(group.getShowingLayout()).isSameInstanceAs(group.getPublicLayout()); +- assertThat(group.getIntrinsicHeight()).isGreaterThan(0); ++ measureAndLayout(mGroupRow); ++ mGroupRow.setHideSensitiveForIntrinsicHeight(true); ++ mGroupRow.setSensitive(true, true); ++ assertThat(mGroupRow.getShowingLayout()).isSameInstanceAs(mGroupRow.getPublicLayout()); ++ assertThat(mGroupRow.getIntrinsicHeight()).isGreaterThan(0); + + // GIVEN that the row has a height change listener + OnHeightChangedListener listener = mock(OnHeightChangedListener.class); +- group.setOnHeightChangedListener(listener); ++ mGroupRow.setOnHeightChangedListener(listener); + + // WHEN the row is set to no longer be sensitive +- group.setSensitive(false, true); ++ mGroupRow.setSensitive(false, true); + + // VERIFY that the height change listener is invoked +- assertThat(group.getShowingLayout()).isSameInstanceAs(group.getPrivateLayout()); +- assertThat(group.getIntrinsicHeight()).isGreaterThan(0); +- verify(listener).onHeightChanged(eq(group), eq(false)); ++ assertThat(mGroupRow.getShowingLayout()).isSameInstanceAs(mGroupRow.getPrivateLayout()); ++ assertThat(mGroupRow.getIntrinsicHeight()).isGreaterThan(0); ++ verify(listener).onHeightChanged(eq(mGroupRow), eq(false)); + } + + @Test +- public void testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange() throws Exception { +- // create a notification row whose public version is identical +- Notification publicNotif = mNotificationTestHelper.createNotification(); +- publicNotif.publicVersion = mNotificationTestHelper.createNotification(); +- ExpandableNotificationRow publicRow = mNotificationTestHelper.createRow(publicNotif); +- ++ public void testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange() { + // GIVEN a sensitive public row that's currently redacted +- measureAndLayout(publicRow); +- publicRow.setHideSensitiveForIntrinsicHeight(true); +- publicRow.setSensitive(true, true); +- assertThat(publicRow.getShowingLayout()).isSameInstanceAs(publicRow.getPublicLayout()); +- assertThat(publicRow.getIntrinsicHeight()).isGreaterThan(0); ++ measureAndLayout(mPublicRow); ++ mPublicRow.setHideSensitiveForIntrinsicHeight(true); ++ mPublicRow.setSensitive(true, true); ++ assertThat(mPublicRow.getShowingLayout()).isSameInstanceAs(mPublicRow.getPublicLayout()); ++ assertThat(mPublicRow.getIntrinsicHeight()).isGreaterThan(0); + + // GIVEN that the row has a height change listener + OnHeightChangedListener listener = mock(OnHeightChangedListener.class); +- publicRow.setOnHeightChangedListener(listener); ++ mPublicRow.setOnHeightChangedListener(listener); + + // WHEN the row is set to no longer be sensitive +- publicRow.setSensitive(false, true); ++ mPublicRow.setSensitive(false, true); + + // VERIFY that the height change listener is not invoked, because the height didn't change +- assertThat(publicRow.getShowingLayout()).isSameInstanceAs(publicRow.getPrivateLayout()); +- assertThat(publicRow.getIntrinsicHeight()).isGreaterThan(0); +- assertThat(publicRow.getPrivateLayout().getMinHeight()) +- .isEqualTo(publicRow.getPublicLayout().getMinHeight()); +- verify(listener, never()).onHeightChanged(eq(publicRow), eq(false)); ++ assertThat(mPublicRow.getShowingLayout()).isSameInstanceAs(mPublicRow.getPrivateLayout()); ++ assertThat(mPublicRow.getIntrinsicHeight()).isGreaterThan(0); ++ assertThat(mPublicRow.getPrivateLayout().getMinHeight()) ++ .isEqualTo(mPublicRow.getPublicLayout().getMinHeight()); ++ verify(listener, never()).onHeightChanged(eq(mPublicRow), eq(false)); + } + + private void measureAndLayout(ExpandableNotificationRow row) { +@@ -219,44 +196,39 @@ private void measureAndLayout(ExpandableNotificationRow row) { + } + + @Test +- public void testGroupSummaryNotShowingIconWhenPublic() throws Exception { +- ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); +- +- group.setSensitive(true, true); +- group.setHideSensitiveForIntrinsicHeight(true); +- assertTrue(group.isSummaryWithChildren()); +- assertFalse(group.isShowingIcon()); ++ public void testGroupSummaryNotShowingIconWhenPublic() { ++ mGroupRow.setSensitive(true, true); ++ mGroupRow.setHideSensitiveForIntrinsicHeight(true); ++ assertTrue(mGroupRow.isSummaryWithChildren()); ++ assertFalse(mGroupRow.isShowingIcon()); + } + + @Test +- public void testNotificationHeaderVisibleWhenAnimating() throws Exception { +- ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); +- +- group.setSensitive(true, true); +- group.setHideSensitive(true, false, 0, 0); +- group.setHideSensitive(false, true, 0, 0); +- assertEquals(View.VISIBLE, group.getChildrenContainer().getVisibleWrapper() ++ public void testNotificationHeaderVisibleWhenAnimating() { ++ mGroupRow.setSensitive(true, true); ++ mGroupRow.setHideSensitive(true, false, 0, 0); ++ mGroupRow.setHideSensitive(false, true, 0, 0); ++ assertEquals(View.VISIBLE, mGroupRow.getChildrenContainer().getVisibleWrapper() + .getNotificationHeader().getVisibility()); + } + + @Test +- public void testUserLockedResetEvenWhenNoChildren() throws Exception { +- ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); +- +- group.setUserLocked(true); +- group.setUserLocked(false); ++ public void testUserLockedResetEvenWhenNoChildren() { ++ mGroupRow.setUserLocked(true); ++ mGroupRow.removeAllChildren(); ++ mGroupRow.setUserLocked(false); + assertFalse("The childrencontainer should not be userlocked but is, the state " +- + "seems out of sync.", group.getChildrenContainer().isUserLocked()); ++ + "seems out of sync.", mGroupRow.getChildrenContainer().isUserLocked()); + } + + @Test +- public void testReinflatedOnDensityChange() throws Exception { +- ExpandableNotificationRow row = mNotificationTestHelper.createRow(); ++ public void testReinflatedOnDensityChange() { ++ mGroupRow.setUserLocked(true); ++ mGroupRow.removeAllChildren(); ++ mGroupRow.setUserLocked(false); + NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class); +- row.setChildrenContainer(mockContainer); +- +- row.onDensityOrFontScaleChanged(); +- ++ mGroupRow.setChildrenContainer(mockContainer); ++ mGroupRow.onDensityOrFontScaleChanged(); + verify(mockContainer).reInflateViews(any(), any()); + } + +@@ -269,6 +241,17 @@ public void testIconColorShouldBeUpdatedWhenSensitive() throws Exception { + verify(row).updateShelfIconColor(); + } + ++ @Test ++ public void setNeedsRedactionFreesViewWhenFalse() throws Exception { ++ ExpandableNotificationRow row = mNotificationTestHelper.createRow(FLAG_CONTENT_VIEW_ALL); ++ row.setNeedsRedaction(true); ++ row.getPublicLayout().setVisibility(View.GONE); ++ ++ row.setNeedsRedaction(false); ++ TestableLooper.get(this).processAllMessages(); ++ assertNull(row.getPublicLayout().getContractedChild()); ++ } ++ + @Test + public void testAboveShelfChangedListenerCalled() throws Exception { + ExpandableNotificationRow row = mNotificationTestHelper.createRow(); +@@ -298,73 +281,64 @@ public void testAboveShelfChangedListenerCalledHeadsUpGoingAway() throws Excepti + @Test + public void testAboveShelfChangedListenerCalledWhenGoingBelow() throws Exception { + ExpandableNotificationRow row = mNotificationTestHelper.createRow(); ++ row.setHeadsUp(true); + AboveShelfChangedListener listener = mock(AboveShelfChangedListener.class); + row.setAboveShelfChangedListener(listener); +- Mockito.reset(listener); +- row.setHeadsUp(true); + row.setAboveShelf(false); + verify(listener).onAboveShelfStateChanged(false); + } + + @Test + public void testClickSound() throws Exception { +- ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); +- +- assertTrue("Should play sounds by default.", group.isSoundEffectsEnabled()); ++ assertTrue("Should play sounds by default.", mGroupRow.isSoundEffectsEnabled()); + StatusBarStateController mock = mNotificationTestHelper.getStatusBarStateController(); + when(mock.isDozing()).thenReturn(true); +- group.setSecureStateProvider(()-> false); ++ mGroupRow.setSecureStateProvider(()-> false); + assertFalse("Shouldn't play sounds when dark and trusted.", +- group.isSoundEffectsEnabled()); +- group.setSecureStateProvider(()-> true); ++ mGroupRow.isSoundEffectsEnabled()); ++ mGroupRow.setSecureStateProvider(()-> true); + assertTrue("Should always play sounds when not trusted.", +- group.isSoundEffectsEnabled()); ++ mGroupRow.isSoundEffectsEnabled()); + } + + @Test +- public void testSetDismissed_longPressListenerRemoved() throws Exception { +- ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); +- ++ public void testSetDismissed_longPressListenerRemoved() { + ExpandableNotificationRow.LongPressListener listener = + mock(ExpandableNotificationRow.LongPressListener.class); +- group.setLongPressListener(listener); +- group.doLongClickCallback(0, 0); +- verify(listener, times(1)).onLongPress(eq(group), eq(0), eq(0), ++ mGroupRow.setLongPressListener(listener); ++ mGroupRow.doLongClickCallback(0,0); ++ verify(listener, times(1)).onLongPress(eq(mGroupRow), eq(0), eq(0), + any(NotificationMenuRowPlugin.MenuItem.class)); + reset(listener); + +- group.dismiss(true); +- group.doLongClickCallback(0, 0); +- verify(listener, times(0)).onLongPress(eq(group), eq(0), eq(0), ++ mGroupRow.dismiss(true); ++ mGroupRow.doLongClickCallback(0,0); ++ verify(listener, times(0)).onLongPress(eq(mGroupRow), eq(0), eq(0), + any(NotificationMenuRowPlugin.MenuItem.class)); + } + + @Test +- public void testFeedback_noHeader() throws Exception { +- ExpandableNotificationRow groupRow = mNotificationTestHelper.createGroup(); +- ++ public void testFeedback_noHeader() { + // public notification is custom layout - no header +- groupRow.setSensitive(true, true); +- groupRow.setOnFeedbackClickListener(null); +- groupRow.setFeedbackIcon(null); ++ mGroupRow.setSensitive(true, true); ++ mGroupRow.setOnFeedbackClickListener(null); ++ mGroupRow.setFeedbackIcon(null); + } + + @Test +- public void testFeedback_header() throws Exception { +- ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); +- ++ public void testFeedback_header() { + NotificationContentView publicLayout = mock(NotificationContentView.class); +- group.setPublicLayout(publicLayout); ++ mGroupRow.setPublicLayout(publicLayout); + NotificationContentView privateLayout = mock(NotificationContentView.class); +- group.setPrivateLayout(privateLayout); ++ mGroupRow.setPrivateLayout(privateLayout); + NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class); + when(mockContainer.getNotificationChildCount()).thenReturn(1); +- group.setChildrenContainer(mockContainer); ++ mGroupRow.setChildrenContainer(mockContainer); + + final boolean show = true; + final FeedbackIcon icon = new FeedbackIcon( + R.drawable.ic_feedback_alerted, R.string.notification_feedback_indicator_alerted); +- group.setFeedbackIcon(icon); ++ mGroupRow.setFeedbackIcon(icon); + + verify(mockContainer, times(1)).setFeedbackIcon(icon); + verify(privateLayout, times(1)).setFeedbackIcon(icon); +@@ -372,49 +346,43 @@ public void testFeedback_header() throws Exception { + } + + @Test +- public void testFeedbackOnClick() throws Exception { +- ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); +- ++ public void testFeedbackOnClick() { + ExpandableNotificationRow.CoordinateOnClickListener l = mock( + ExpandableNotificationRow.CoordinateOnClickListener.class); + View view = mock(View.class); + +- group.setOnFeedbackClickListener(l); ++ mGroupRow.setOnFeedbackClickListener(l); + +- group.getFeedbackOnClickListener().onClick(view); ++ mGroupRow.getFeedbackOnClickListener().onClick(view); + verify(l, times(1)).onClick(any(), anyInt(), anyInt(), any()); + } + + @Test +- public void testHeadsUpAnimatingAwayListener() throws Exception { +- ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); +- Consumer headsUpListener = mock(Consumer.class); +- AboveShelfChangedListener aboveShelfChangedListener = mock(AboveShelfChangedListener.class); +- group.setHeadsUpAnimatingAwayListener(headsUpListener); +- group.setAboveShelfChangedListener(aboveShelfChangedListener); +- +- group.setHeadsUpAnimatingAway(true); +- verify(headsUpListener).accept(true); +- verify(aboveShelfChangedListener).onAboveShelfStateChanged(true); +- +- group.setHeadsUpAnimatingAway(false); +- verify(headsUpListener).accept(false); +- verify(aboveShelfChangedListener).onAboveShelfStateChanged(false); ++ public void testHeadsUpAnimatingAwayListener() { ++ mGroupRow.setHeadsUpAnimatingAway(true); ++ Assert.assertEquals(true, mHeadsUpAnimatingAway); ++ mGroupRow.setHeadsUpAnimatingAway(false); ++ Assert.assertEquals(false, mHeadsUpAnimatingAway); + } + + @Test +- public void testGetNumUniqueChildren_defaultChannel() throws Exception { +- ExpandableNotificationRow groupRow = mNotificationTestHelper.createGroup(); ++ public void testIsBlockingHelperShowing_isCorrectlyUpdated() { ++ mGroupRow.setBlockingHelperShowing(true); ++ assertTrue(mGroupRow.isBlockingHelperShowing()); + +- assertEquals(1, groupRow.getNumUniqueChannels()); ++ mGroupRow.setBlockingHelperShowing(false); ++ assertFalse(mGroupRow.isBlockingHelperShowing()); + } + + @Test +- public void testGetNumUniqueChildren_multiChannel() throws Exception { +- ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); ++ public void testGetNumUniqueChildren_defaultChannel() { ++ assertEquals(1, mGroupRow.getNumUniqueChannels()); ++ } + ++ @Test ++ public void testGetNumUniqueChildren_multiChannel() { + List childRows = +- group.getChildrenContainer().getAttachedChildren(); ++ mGroupRow.getChildrenContainer().getAttachedChildren(); + // Give each child a unique channel id/name. + int i = 0; + for (ExpandableNotificationRow childRow : childRows) { +@@ -426,29 +394,25 @@ public void testGetNumUniqueChildren_multiChannel() throws Exception { + i++; + } + +- assertEquals(3, group.getNumUniqueChannels()); ++ assertEquals(3, mGroupRow.getNumUniqueChannels()); + } + + @Test + public void testIconScrollXAfterTranslationAndReset() throws Exception { +- ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); +- +- group.setDismissUsingRowTranslationX(false); +- group.setTranslation(50); +- assertEquals(50, -group.getEntry().getIcons().getShelfIcon().getScrollX()); ++ mGroupRow.setDismissUsingRowTranslationX(false); ++ mGroupRow.setTranslation(50); ++ assertEquals(50, -mGroupRow.getEntry().getIcons().getShelfIcon().getScrollX()); + +- group.resetTranslation(); +- assertEquals(0, group.getEntry().getIcons().getShelfIcon().getScrollX()); ++ mGroupRow.resetTranslation(); ++ assertEquals(0, mGroupRow.getEntry().getIcons().getShelfIcon().getScrollX()); + } + + @Test +- public void testIsExpanded_userExpanded() throws Exception { +- ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); +- +- group.setExpandable(true); +- Assert.assertFalse(group.isExpanded()); +- group.setUserExpanded(true); +- Assert.assertTrue(group.isExpanded()); ++ public void testIsExpanded_userExpanded() { ++ mGroupRow.setExpandable(true); ++ Assert.assertFalse(mGroupRow.isExpanded()); ++ mGroupRow.setUserExpanded(true); ++ Assert.assertTrue(mGroupRow.isExpanded()); + } + + @Test +@@ -492,230 +456,22 @@ public void testCannotDismissOngoing() throws Exception { + } + + @Test +- public void testAddChildNotification() throws Exception { +- ExpandableNotificationRow group = mNotificationTestHelper.createGroup(0); +- ExpandableNotificationRow child = mNotificationTestHelper.createRow(); ++ public void imageResolver_sameNotificationUser_usesContext() throws Exception { ++ ExpandableNotificationRow row = mNotificationTestHelper.createRow(PKG, ++ USER_HANDLE.getUid(1234), USER_HANDLE); + +- group.addChildNotification(child); +- +- Assert.assertEquals(child, group.getChildNotificationAt(0)); +- Assert.assertEquals(group, child.getNotificationParent()); +- Assert.assertTrue(child.isChildInGroup()); ++ assertThat(row.getImageResolver().getContext()).isSameInstanceAs(mContext); + } + + @Test +- public void testAddChildNotification_childSkipped() throws Exception { +- ExpandableNotificationRow group = mNotificationTestHelper.createGroup(0); +- ExpandableNotificationRow child = mNotificationTestHelper.createRow(); +- child.setKeepInParentForDismissAnimation(true); +- +- group.addChildNotification(child); +- +- Assert.assertTrue(group.getAttachedChildren().isEmpty()); +- Assert.assertNotEquals(group, child.getNotificationParent()); +- verify(mNotificationTestHelper.getMockLogger()).logSkipAttachingKeepInParentChild( +- /*child=*/ child.getEntry(), +- /*newParent=*/ group.getEntry() +- ); +- } ++ public void imageResolver_differentNotificationUser_createsUserContext() throws Exception { ++ UserHandle user = new UserHandle(33); ++ Context userContext = new SysuiTestableContext(mContext); ++ mContext.prepareCreateContextAsUser(user, userContext); + +- @Test +- public void testRemoveChildNotification() throws Exception { +- ExpandableNotificationRow group = mNotificationTestHelper.createGroup(1); +- ExpandableNotificationRow child = group.getAttachedChildren().get(0); +- child.setKeepInParentForDismissAnimation(true); +- +- group.removeChildNotification(child); ++ ExpandableNotificationRow row = mNotificationTestHelper.createRow(PKG, ++ user.getUid(1234), user); + +- Assert.assertNull(child.getParent()); +- Assert.assertNull(child.getNotificationParent()); +- Assert.assertFalse(child.keepInParentForDismissAnimation()); +- verifyNoMoreInteractions(mNotificationTestHelper.getMockLogger()); +- } +- +- @Test +- public void testRemoveChildrenWithKeepInParent_removesChildWithKeepInParent() throws Exception { +- ExpandableNotificationRow group = mNotificationTestHelper.createGroup(1); +- ExpandableNotificationRow child = group.getAttachedChildren().get(0); +- child.setKeepInParentForDismissAnimation(true); +- +- group.removeChildrenWithKeepInParent(); +- +- Assert.assertNull(child.getParent()); +- Assert.assertNull(child.getNotificationParent()); +- Assert.assertFalse(child.keepInParentForDismissAnimation()); +- verify(mNotificationTestHelper.getMockLogger()).logKeepInParentChildDetached( +- /*child=*/ child.getEntry(), +- /*oldParent=*/ group.getEntry() +- ); +- } +- +- @Test +- public void testRemoveChildrenWithKeepInParent_skipsChildrenWithoutKeepInParent() +- throws Exception { +- ExpandableNotificationRow group = mNotificationTestHelper.createGroup(1); +- ExpandableNotificationRow child = group.getAttachedChildren().get(0); +- +- group.removeChildrenWithKeepInParent(); +- +- Assert.assertEquals(group, child.getNotificationParent()); +- Assert.assertFalse(child.keepInParentForDismissAnimation()); +- verify(mNotificationTestHelper.getMockLogger(), never()).logKeepInParentChildDetached( +- /*child=*/ any(), +- /*oldParent=*/ any() +- ); +- } +- +- @Test +- public void applyRoundnessAndInv_should_be_immediately_applied_on_childrenContainer_legacy() +- throws Exception { +- ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); +- group.useRoundnessSourceTypes(false); +- Assert.assertEquals(0f, group.getBottomRoundness(), 0.001f); +- Assert.assertEquals(0f, group.getChildrenContainer().getBottomRoundness(), 0.001f); +- +- group.requestBottomRoundness(1f, SourceType.from(""), false); +- +- Assert.assertEquals(1f, group.getBottomRoundness(), 0.001f); +- Assert.assertEquals(1f, group.getChildrenContainer().getBottomRoundness(), 0.001f); +- } +- +- @Test +- public void applyRoundnessAndInvalidate_should_be_immediately_applied_on_childrenContainer() +- throws Exception { +- ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); +- group.useRoundnessSourceTypes(true); +- Assert.assertEquals(0f, group.getBottomRoundness(), 0.001f); +- Assert.assertEquals(0f, group.getChildrenContainer().getBottomRoundness(), 0.001f); +- +- group.requestBottomRoundness(1f, SourceType.from(""), false); +- +- Assert.assertEquals(1f, group.getBottomRoundness(), 0.001f); +- Assert.assertEquals(1f, group.getChildrenContainer().getBottomRoundness(), 0.001f); +- } +- +- @Test +- public void testSetContentAnimationRunning_Run() throws Exception { +- // Create views for the notification row. +- ExpandableNotificationRow row = mNotificationTestHelper.createRow(); +- NotificationContentView publicLayout = mock(NotificationContentView.class); +- row.setPublicLayout(publicLayout); +- NotificationContentView privateLayout = mock(NotificationContentView.class); +- row.setPrivateLayout(privateLayout); +- +- row.setAnimationRunning(true); +- verify(publicLayout, times(1)).setContentAnimationRunning(true); +- verify(privateLayout, times(1)).setContentAnimationRunning(true); +- } +- +- @Test +- public void testSetContentAnimationRunning_Stop() throws Exception { +- // Create views for the notification row. +- ExpandableNotificationRow row = mNotificationTestHelper.createRow(); +- NotificationContentView publicLayout = mock(NotificationContentView.class); +- row.setPublicLayout(publicLayout); +- NotificationContentView privateLayout = mock(NotificationContentView.class); +- row.setPrivateLayout(privateLayout); +- +- row.setAnimationRunning(false); +- verify(publicLayout, times(1)).setContentAnimationRunning(false); +- verify(privateLayout, times(1)).setContentAnimationRunning(false); +- } +- +- @Test +- public void testSetContentAnimationRunningInGroupChild_Run() throws Exception { +- // Creates parent views on groupRow. +- ExpandableNotificationRow groupRow = mNotificationTestHelper.createGroup(); +- NotificationContentView publicParentLayout = mock(NotificationContentView.class); +- groupRow.setPublicLayout(publicParentLayout); +- NotificationContentView privateParentLayout = mock(NotificationContentView.class); +- groupRow.setPrivateLayout(privateParentLayout); +- +- // Create child views on row. +- ExpandableNotificationRow row = mNotificationTestHelper.createRow(); +- NotificationContentView publicChildLayout = mock(NotificationContentView.class); +- row.setPublicLayout(publicChildLayout); +- NotificationContentView privateChildLayout = mock(NotificationContentView.class); +- row.setPrivateLayout(privateChildLayout); +- when(row.isGroupExpanded()).thenReturn(true); +- setMockChildrenContainer(groupRow, row); +- +- groupRow.setAnimationRunning(true); +- verify(publicParentLayout, times(1)).setContentAnimationRunning(true); +- verify(privateParentLayout, times(1)).setContentAnimationRunning(true); +- // The child layouts should be started too. +- verify(publicChildLayout, times(1)).setContentAnimationRunning(true); +- verify(privateChildLayout, times(1)).setContentAnimationRunning(true); +- } +- +- +- @Test +- public void testSetIconAnimationRunningGroup_Run() throws Exception { +- // Create views for a group row. +- ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); +- ExpandableNotificationRow child = mNotificationTestHelper.createRow(); +- NotificationContentView publicParentLayout = mock(NotificationContentView.class); +- group.setPublicLayout(publicParentLayout); +- NotificationContentView privateParentLayout = mock(NotificationContentView.class); +- group.setPrivateLayout(privateParentLayout); +- when(group.isGroupExpanded()).thenReturn(true); +- +- // Add the child to the group. +- NotificationContentView publicChildLayout = mock(NotificationContentView.class); +- child.setPublicLayout(publicChildLayout); +- NotificationContentView privateChildLayout = mock(NotificationContentView.class); +- child.setPrivateLayout(privateChildLayout); +- when(child.isGroupExpanded()).thenReturn(true); +- +- NotificationChildrenContainer mockContainer = +- setMockChildrenContainer(group, child); +- +- // Mock the children view wrappers, and give them each an icon. +- NotificationViewWrapper mockViewWrapper = mock(NotificationViewWrapper.class); +- when(mockContainer.getNotificationViewWrapper()).thenReturn(mockViewWrapper); +- CachingIconView mockIcon = mock(CachingIconView.class); +- when(mockViewWrapper.getIcon()).thenReturn(mockIcon); +- +- NotificationViewWrapper mockLowPriorityViewWrapper = mock(NotificationViewWrapper.class); +- when(mockContainer.getLowPriorityViewWrapper()).thenReturn(mockLowPriorityViewWrapper); +- CachingIconView mockLowPriorityIcon = mock(CachingIconView.class); +- when(mockLowPriorityViewWrapper.getIcon()).thenReturn(mockLowPriorityIcon); +- +- // Give the icon image views drawables, so we can make sure they animate. +- // We use both AnimationDrawables and AnimatedVectorDrawables to ensure both work. +- AnimationDrawable drawable = mock(AnimationDrawable.class); +- AnimatedVectorDrawable vectorDrawable = mock(AnimatedVectorDrawable.class); +- setDrawableIconsInImageView(mockIcon, drawable, vectorDrawable); +- +- AnimationDrawable lowPriDrawable = mock(AnimationDrawable.class); +- AnimatedVectorDrawable lowPriVectorDrawable = mock(AnimatedVectorDrawable.class); +- setDrawableIconsInImageView(mockLowPriorityIcon, lowPriDrawable, lowPriVectorDrawable); +- +- group.setAnimationRunning(true); +- verify(drawable, times(1)).start(); +- verify(vectorDrawable, times(1)).start(); +- verify(lowPriDrawable, times(1)).start(); +- verify(lowPriVectorDrawable, times(1)).start(); +- } +- +- private void setDrawableIconsInImageView(CachingIconView icon, Drawable iconDrawable, +- Drawable rightIconDrawable) { +- ImageView iconView = mock(ImageView.class); +- when(icon.findViewById(com.android.internal.R.id.icon)).thenReturn(iconView); +- when(iconView.getDrawable()).thenReturn(iconDrawable); +- +- ImageView rightIconView = mock(ImageView.class); +- when(icon.findViewById(com.android.internal.R.id.right_icon)).thenReturn(rightIconView); +- when(rightIconView.getDrawable()).thenReturn(rightIconDrawable); +- } +- +- private NotificationChildrenContainer setMockChildrenContainer( +- ExpandableNotificationRow parentRow, ExpandableNotificationRow childRow) { +- List rowList = Arrays.asList(childRow); +- NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class); +- when(mockContainer.getNotificationChildCount()).thenReturn(1); +- when(mockContainer.getAttachedChildren()).thenReturn(rowList); +- parentRow.setChildrenContainer(mockContainer); +- return mockContainer; ++ assertThat(row.getImageResolver().getContext()).isSameInstanceAs(userContext); + } + } +diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java +index 0674ea855d7f..fd2914e02a76 100644 +--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java ++++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java +@@ -14,6 +14,7 @@ + + package com.android.systemui; + ++import android.annotation.NonNull; + import android.content.BroadcastReceiver; + import android.content.Context; + import android.content.Intent; +@@ -28,12 +29,15 @@ + + import com.android.internal.annotations.GuardedBy; + ++import java.util.HashMap; ++import java.util.Map; + import java.util.Set; + + public class SysuiTestableContext extends TestableContext { + + @GuardedBy("mRegisteredReceivers") + private final Set mRegisteredReceivers = new ArraySet<>(); ++ private final Map mContextForUser = new HashMap<>(); + + public SysuiTestableContext(Context base) { + super(base); +@@ -146,4 +150,22 @@ public void unregisterReceiver(BroadcastReceiver receiver) { + } + super.unregisterReceiver(receiver); + } ++ ++ /** ++ * Sets a Context object that will be returned as the result of {@link #createContextAsUser} ++ * for a specific {@code user}. ++ */ ++ public void prepareCreateContextAsUser(UserHandle user, Context context) { ++ mContextForUser.put(user, context); ++ } ++ ++ @Override ++ @NonNull ++ public Context createContextAsUser(UserHandle user, int flags) { ++ Context userContext = mContextForUser.get(user); ++ if (userContext != null) { ++ return userContext; ++ } ++ return super.createContextAsUser(user, flags); ++ } + } diff --git a/Patches/LineageOS-20.0/ASB-2024-06/fwb-12.patch b/Patches/LineageOS-20.0/ASB-2024-06/fwb-12.patch new file mode 100644 index 00000000..a54c402e --- /dev/null +++ b/Patches/LineageOS-20.0/ASB-2024-06/fwb-12.patch @@ -0,0 +1,59 @@ +From e3979baab5718a69f6d58040ba42f67b4fd73d35 Mon Sep 17 00:00:00 2001 +From: Ameer Armaly +Date: Fri, 8 Mar 2024 19:41:06 +0000 +Subject: [PATCH] [RESTRICT AUTOMERGE] AccessibilityManagerService: remove + uninstalled services from enabled list after service update. + +Bug: 326485767 +Test: atest AccessibilityEndToEndTest#testUpdateServiceWithoutIntent_disablesService +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:766911c3312573196b33efd1c3c29ccece806846) +Merged-In: I958d58953b300c8093335a22e207baac471ae9f9 +Change-Id: I958d58953b300c8093335a22e207baac471ae9f9 +--- + .../AccessibilityManagerService.java | 22 +++++++++++++++++++ + 1 file changed, 22 insertions(+) + +diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +index 3818a884c94a..8271aed181c9 100644 +--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java ++++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +@@ -2161,10 +2161,13 @@ private void updateServicesLocked(AccessibilityUserState userState) { + boolean isUnlockingOrUnlocked = LocalServices.getService(UserManagerInternal.class) + .isUserUnlockingOrUnlocked(userState.mUserId); + ++ // Store the list of installed services. ++ mTempComponentNameSet.clear(); + for (int i = 0, count = userState.mInstalledServices.size(); i < count; i++) { + AccessibilityServiceInfo installedService = userState.mInstalledServices.get(i); + ComponentName componentName = ComponentName.unflattenFromString( + installedService.getId()); ++ mTempComponentNameSet.add(componentName); + + AccessibilityServiceConnection service = componentNameToServiceMap.get(componentName); + +@@ -2215,6 +2218,25 @@ this, getTraceManager(), mWindowManagerService, + audioManager.setAccessibilityServiceUids(mTempIntArray); + } + mActivityTaskManagerService.setAccessibilityServiceUids(mTempIntArray); ++ // If any services have been removed, remove them from the enabled list and the touch ++ // exploration granted list. ++ boolean anyServiceRemoved = ++ userState.mEnabledServices.removeIf((comp) -> !mTempComponentNameSet.contains(comp)) ++ || userState.mTouchExplorationGrantedServices.removeIf( ++ (comp) -> !mTempComponentNameSet.contains(comp)); ++ if (anyServiceRemoved) { ++ // Update the enabled services setting. ++ persistComponentNamesToSettingLocked( ++ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, ++ userState.mEnabledServices, ++ userState.mUserId); ++ // Update the touch exploration granted services setting. ++ persistComponentNamesToSettingLocked( ++ Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, ++ userState.mTouchExplorationGrantedServices, ++ userState.mUserId); ++ } ++ mTempComponentNameSet.clear(); + updateAccessibilityEnabledSettingLocked(userState); + } + diff --git a/Patches/LineageOS-20.0/ASB-2024-06/fwb-13.patch b/Patches/LineageOS-20.0/ASB-2024-06/fwb-13.patch new file mode 100644 index 00000000..7c1c1c29 --- /dev/null +++ b/Patches/LineageOS-20.0/ASB-2024-06/fwb-13.patch @@ -0,0 +1,274 @@ +From 56ef52af608d4c74da25da50023b63fb3680159f Mon Sep 17 00:00:00 2001 +From: Beth Thibodeau +Date: Tue, 19 Mar 2024 16:49:51 -0500 +Subject: [PATCH] Update media_controls_lock_screen setting behavior + +When the setting is disabled, hide the media carousel everywhere when +the device is on lockscreen, not just in the keyguard layout + +Bug: 314333719 +Test: manual +Test: atest MediaHierarchyManagerTest +Flag: NONE +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:a441f4acd62cce1059818ce8e9b7ab93b0079e50) +Merged-In: I4f618e4013db894291e6fca9d49bceb1cb7e4bd9 +Change-Id: I4f618e4013db894291e6fca9d49bceb1cb7e4bd9 +--- + .../controls/ui/KeyguardMediaController.kt | 40 ------------------- + .../controls/ui/MediaHierarchyManager.kt | 27 +++++++++---- + .../systemui/media/controls/ui/MediaHost.kt | 4 +- + .../ui/KeyguardMediaControllerTest.kt | 28 ------------- + 4 files changed, 22 insertions(+), 77 deletions(-) + +diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt +index 30ee147e302a..2a2882cf7108 100644 +--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt ++++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt +@@ -18,16 +18,10 @@ package com.android.systemui.media.controls.ui + + import android.content.Context + import android.content.res.Configuration +-import android.database.ContentObserver +-import android.net.Uri +-import android.os.Handler +-import android.os.UserHandle +-import android.provider.Settings + import android.view.View + import android.view.ViewGroup + import androidx.annotation.VisibleForTesting + import com.android.systemui.dagger.SysUISingleton +-import com.android.systemui.dagger.qualifiers.Main + import com.android.systemui.media.dagger.MediaModule.KEYGUARD + import com.android.systemui.plugins.statusbar.StatusBarStateController + import com.android.systemui.statusbar.StatusBarState +@@ -36,7 +30,6 @@ import com.android.systemui.statusbar.notification.stack.MediaContainerView + import com.android.systemui.statusbar.phone.KeyguardBypassController + import com.android.systemui.statusbar.policy.ConfigurationController + import com.android.systemui.util.LargeScreenUtils +-import com.android.systemui.util.settings.SecureSettings + import javax.inject.Inject + import javax.inject.Named + +@@ -52,8 +45,6 @@ constructor( + private val bypassController: KeyguardBypassController, + private val statusBarStateController: SysuiStatusBarStateController, + private val context: Context, +- private val secureSettings: SecureSettings, +- @Main private val handler: Handler, + configurationController: ConfigurationController, + ) { + +@@ -77,26 +68,6 @@ constructor( + } + ) + +- val settingsObserver: ContentObserver = +- object : ContentObserver(handler) { +- override fun onChange(selfChange: Boolean, uri: Uri?) { +- if (uri == lockScreenMediaPlayerUri) { +- allowMediaPlayerOnLockScreen = +- secureSettings.getBoolForUser( +- Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, +- true, +- UserHandle.USER_CURRENT +- ) +- refreshMediaPosition() +- } +- } +- } +- secureSettings.registerContentObserverForUser( +- Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, +- settingsObserver, +- UserHandle.USER_ALL +- ) +- + // First let's set the desired state that we want for this host + mediaHost.expansion = MediaHostState.EXPANDED + mediaHost.showsOnlyActiveMedia = true +@@ -133,16 +104,6 @@ constructor( + private set + private var splitShadeContainer: ViewGroup? = null + +- /** Track the media player setting status on lock screen. */ +- private var allowMediaPlayerOnLockScreen: Boolean = +- secureSettings.getBoolForUser( +- Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, +- true, +- UserHandle.USER_CURRENT +- ) +- private val lockScreenMediaPlayerUri = +- secureSettings.getUriFor(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN) +- + /** + * Attaches media container in single pane mode, situated at the top of the notifications list + */ +@@ -202,7 +163,6 @@ constructor( + mediaHost.visible && + !bypassController.bypassEnabled && + keyguardOrUserSwitcher && +- allowMediaPlayerOnLockScreen && + shouldBeVisibleForSplitShade() + if (visible) { + showMediaPlayer() +diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt +index 54237ce7cf25..f0ff1292311d 100644 +--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt ++++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt +@@ -104,7 +104,7 @@ constructor( + ) { + + /** Track the media player setting status on lock screen. */ +- private var allowMediaPlayerOnLockScreen: Boolean = true ++ private var allowMediaPlayerOnLockScreen: Boolean = getMediaLockScreenSetting() + private val lockScreenMediaPlayerUri = + secureSettings.getUriFor(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN) + +@@ -462,6 +462,7 @@ constructor( + } + mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = + isVisibleToUser() ++ mediaCarouselController.updateHostVisibility() + } + + override fun onDozeAmountChanged(linear: Float, eased: Float) { +@@ -538,7 +539,6 @@ constructor( + mediaCarouselController.updateHostVisibility = { + mediaHosts.forEach { it?.updateViewVisibility() } + } +- + panelEventsEvents.addShadeStateEventsListener( + object : ShadeStateEventsListener { + override fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) { +@@ -552,12 +552,8 @@ constructor( + object : ContentObserver(handler) { + override fun onChange(selfChange: Boolean, uri: Uri?) { + if (uri == lockScreenMediaPlayerUri) { +- allowMediaPlayerOnLockScreen = +- secureSettings.getBoolForUser( +- Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, +- true, +- UserHandle.USER_CURRENT +- ) ++ allowMediaPlayerOnLockScreen = getMediaLockScreenSetting() ++ mediaCarouselController.updateHostVisibility() + } + } + } +@@ -568,6 +564,14 @@ constructor( + ) + } + ++ private fun getMediaLockScreenSetting(): Boolean { ++ return secureSettings.getBoolForUser( ++ Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, ++ true, ++ UserHandle.USER_CURRENT ++ ) ++ } ++ + private fun updateConfiguration() { + distanceForFullShadeTransition = + context.resources.getDimensionPixelSize( +@@ -607,6 +611,13 @@ constructor( + mediaCarouselController.closeGuts() + } + ++ /** Return true if the carousel should be hidden because lockscreen is currently visible */ ++ fun isLockedAndHidden(): Boolean { ++ return !allowMediaPlayerOnLockScreen && ++ (statusbarState == StatusBarState.SHADE_LOCKED || ++ statusbarState == StatusBarState.KEYGUARD) ++ } ++ + private fun createUniqueObjectHost(): UniqueObjectHostView { + val viewHost = UniqueObjectHostView(context) + viewHost.addOnAttachStateChangeListener( +diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt +index be570b4a1119..26580e54cd62 100644 +--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt ++++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt +@@ -199,7 +199,9 @@ constructor( + */ + fun updateViewVisibility() { + state.visible = +- if (showsOnlyActiveMedia) { ++ if (mediaHierarchyManager.isLockedAndHidden()) { ++ false ++ } else if (showsOnlyActiveMedia) { + mediaDataManager.hasActiveMediaOrRecommendation() + } else { + mediaDataManager.hasAnyMediaOrRecommendation() +diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt +index b40ebc9bb156..41b3fe65599f 100644 +--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt ++++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt +@@ -16,7 +16,6 @@ + + package com.android.systemui.media.controls.ui + +-import android.provider.Settings + import android.test.suitebuilder.annotation.SmallTest + import android.testing.AndroidTestingRunner + import android.testing.TestableLooper +@@ -32,8 +31,6 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController + import com.android.systemui.statusbar.policy.ConfigurationController + import com.android.systemui.util.animation.UniqueObjectHostView + import com.android.systemui.util.mockito.whenever +-import com.android.systemui.util.settings.FakeSettings +-import com.android.systemui.utils.os.FakeHandler + import com.google.common.truth.Truth.assertThat + import junit.framework.Assert.assertTrue + import org.junit.Before +@@ -60,10 +57,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() { + + private val mediaContainerView: MediaContainerView = MediaContainerView(context, null) + private val hostView = UniqueObjectHostView(context) +- private val settings = FakeSettings() + private lateinit var keyguardMediaController: KeyguardMediaController +- private lateinit var testableLooper: TestableLooper +- private lateinit var fakeHandler: FakeHandler + private lateinit var statusBarStateListener: StatusBarStateController.StateListener + + @Before +@@ -79,16 +73,12 @@ class KeyguardMediaControllerTest : SysuiTestCase() { + whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) + whenever(mediaHost.hostView).thenReturn(hostView) + hostView.layoutParams = FrameLayout.LayoutParams(100, 100) +- testableLooper = TestableLooper.get(this) +- fakeHandler = FakeHandler(testableLooper.looper) + keyguardMediaController = + KeyguardMediaController( + mediaHost, + bypassController, + statusBarStateController, + context, +- settings, +- fakeHandler, + configurationController, + ) + keyguardMediaController.attachSinglePaneContainer(mediaContainerView) +@@ -117,24 +107,6 @@ class KeyguardMediaControllerTest : SysuiTestCase() { + assertThat(mediaContainerView.visibility).isEqualTo(visibility) + } + +- @Test +- fun testHiddenOnKeyguard_whenMediaOnLockScreenDisabled() { +- settings.putInt(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, 0) +- +- keyguardMediaController.refreshMediaPosition() +- +- assertThat(mediaContainerView.visibility).isEqualTo(GONE) +- } +- +- @Test +- fun testAvailableOnKeyguard_whenMediaOnLockScreenEnabled() { +- settings.putInt(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, 1) +- +- keyguardMediaController.refreshMediaPosition() +- +- assertThat(mediaContainerView.visibility).isEqualTo(VISIBLE) +- } +- + @Test + fun testActivatesSplitShadeContainerInSplitShadeMode() { + val splitShadeContainer = FrameLayout(context) diff --git a/Patches/LineageOS-20.0/ASB-2024-06/libfmq-01.patch b/Patches/LineageOS-20.0/ASB-2024-06/libfmq-01.patch new file mode 100644 index 00000000..4f92baaf --- /dev/null +++ b/Patches/LineageOS-20.0/ASB-2024-06/libfmq-01.patch @@ -0,0 +1,64 @@ +From 9a005ebebfa638ed42415301fec30b16bef30299 Mon Sep 17 00:00:00 2001 +From: Devin Moore +Date: Mon, 22 Jan 2024 17:52:16 +0000 +Subject: [PATCH] Use the values of the ptrs that we check + +Test: fmq_fuzzer +Bug: 321326147 +Bug: 321341508 +Bug: 321383085 +(cherry picked from https://android-review.googlesource.com/q/commit:38963310ad5789b625ca0bca9f9c2c8e24666651) +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:050952bf5f9bd035e469ce005300115d563e524a) +Merged-In: I56fe4fe72180e39ecef066353969c1ae9fbcd44e +Change-Id: I56fe4fe72180e39ecef066353969c1ae9fbcd44e +--- + include/fmq/MessageQueueBase.h | 24 ++++++++++++++++++++---- + 1 file changed, 20 insertions(+), 4 deletions(-) + +diff --git a/include/fmq/MessageQueueBase.h b/include/fmq/MessageQueueBase.h +index f4bf7e2..0bf0bb2 100644 +--- a/include/fmq/MessageQueueBase.h ++++ b/include/fmq/MessageQueueBase.h +@@ -1034,8 +1034,16 @@ bool MessageQueueBase::readBlocking(T* data, size_t + } + + template