20.0: June 2024 ASB picks

Signed-off-by: Tavi <tavi@divested.dev>
This commit is contained in:
Tavi 2024-06-13 15:08:09 -04:00
parent 0d7fbddc87
commit 7f00fd1dde
No known key found for this signature in database
GPG Key ID: E599F62ECBAEAF2E
19 changed files with 2622 additions and 169 deletions

View File

@ -1,162 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Valentin Iftime <valiiftime@google.com>
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);

View File

@ -0,0 +1,225 @@
From 42c9c06f9c0f8d7212284c6555e3ffd25bd4ddbf Mon Sep 17 00:00:00 2001
From: Pinyao Ting <pinyaoting@google.com>
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<Long> 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);

View File

@ -0,0 +1,134 @@
From c0c16283997bbca63edc79d820652587cbde15bb Mon Sep 17 00:00:00 2001
From: Valentin Iftime <valiiftime@google.com>
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);

View File

@ -0,0 +1,463 @@
From fcb02396749ec9318803887febffea13f726dfc2 Mon Sep 17 00:00:00 2001
From: Valentin Iftime <valiiftime@google.com>
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<ManagedServiceInfo> 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<String> mEnabledServicesPackageNames = new ArraySet<>();
- // Per user id, list of enabled packages that have nevertheless asked not to be run
- private final android.util.SparseSetArray<ComponentName> mSnoozing =
- new android.util.SparseSetArray<>();
+ // List of enabled packages that have nevertheless asked not to be run
+ private ArraySet<ComponentName> 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<Integer, ArrayMap<Boolean, ArraySet<String>>> mApproved =
- new ArrayMap<>();
+ protected ArrayMap<Integer, ArrayMap<Boolean, ArraySet<String>>> 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<Boolean, ArrayList<ComponentName>> resetComponents(String packageName,
return changes;
}
- @GuardedBy("mApproved")
private boolean clearUserSetFlagLocked(ComponentName component, int userId) {
String approvedValue = getApprovedValue(component.flattenToString());
ArraySet<String> 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<String> 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<Boolean, ArraySet<String>> approvedByType = mApproved.get(userId);
+ if (approvedByType == null) {
+ return;
+ }
+ for (int i = 0; i < approvedByType.size(); i++) {
+ final ArraySet<String> 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<Set<ComponentName>> componen
}
final Set<ComponentName> add = new HashSet<>(userComponents);
- ArraySet<ComponentName> 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<Set<ComponentName>> componentsToBind) {
final int userId = componentsToBind.keyAt(i);
final Set<ComponentName> 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<String> 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<ServiceInfo>) 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}) {

View File

@ -0,0 +1,31 @@
From 88ea616cf95aff9169daec33a0a3d93a30b15727 Mon Sep 17 00:00:00 2001
From: Yi-an Chen <theianchen@google.com>
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);
}

View File

@ -0,0 +1,71 @@
From 091715c8307fed95ba63870f0c54e74208a78332 Mon Sep 17 00:00:00 2001
From: Lokesh Kumar Goel <lokeshgoel@google.com>
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 */

View File

@ -0,0 +1,70 @@
From b403e48e6aff45880c2970102555cc77cdcfe322 Mon Sep 17 00:00:00 2001
From: Riddle Hsu <riddlehsu@google.com>
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

View File

@ -0,0 +1,43 @@
From ac134a1b6a0ace6bf43e83d414f6433f3cf40e53 Mon Sep 17 00:00:00 2001
From: Dmitry Dementyev <dementyev@google.com>
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()

View File

@ -0,0 +1,70 @@
From 5dd0b87bc501c83abbe2462d17fcc7f9f3dfab4d Mon Sep 17 00:00:00 2001
From: Haoran Zhang <haoranzhang@google.com>
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<ResolveInfo> 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();
}

View File

@ -0,0 +1,47 @@
From d0df12d2f498f441d87852580a89f8588380d902 Mon Sep 17 00:00:00 2001
From: Hans Boehm <hboehm@google.com>
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();

View File

@ -0,0 +1,61 @@
From cbf4c6352bf85e4d8289d6bc1135d66c95f0e1e1 Mon Sep 17 00:00:00 2001
From: Guojing Yuan <guojing@google.com>
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

View File

@ -0,0 +1,912 @@
From d444b292de11e14d41a5ce1897cd32360d065671 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= <matiashe@google.com>
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<Boolean> 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<ExpandableNotificationRow> 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<ExpandableNotificationRow> 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<BroadcastReceiver> mRegisteredReceivers = new ArraySet<>();
+ private final Map<UserHandle, Context> 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);
+ }
}

View File

@ -0,0 +1,59 @@
From e3979baab5718a69f6d58040ba42f67b4fd73d35 Mon Sep 17 00:00:00 2001
From: Ameer Armaly <aarmaly@google.com>
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);
}

View File

@ -0,0 +1,274 @@
From 56ef52af608d4c74da25da50023b63fb3680159f Mon Sep 17 00:00:00 2001
From: Beth Thibodeau <ethibodeau@google.com>
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)

View File

@ -0,0 +1,64 @@
From 9a005ebebfa638ed42415301fec30b16bef30299 Mon Sep 17 00:00:00 2001
From: Devin Moore <devinmoore@google.com>
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<MQDescriptorType, T, flavor>::readBlocking(T* data, size_t
}
template <template <typename, MQFlavor> typename MQDescriptorType, typename T, MQFlavor flavor>
-size_t MessageQueueBase<MQDescriptorType, T, flavor>::availableToWriteBytes() const {
- return mDesc->getSize() - availableToReadBytes();
+inline size_t MessageQueueBase<MQDescriptorType, T, flavor>::availableToWriteBytes() const {
+ size_t queueSizeBytes = mDesc->getSize();
+ size_t availableBytes = availableToReadBytes();
+ if (queueSizeBytes < availableBytes) {
+ hardware::details::logError(
+ "The write or read pointer has become corrupted. Reading from the queue is no "
+ "longer possible.");
+ return 0;
+ }
+ return queueSizeBytes - availableBytes;
}
template <template <typename, MQFlavor> typename MQDescriptorType, typename T, MQFlavor flavor>
@@ -1117,13 +1125,21 @@ MessageQueueBase<MQDescriptorType, T, flavor>::commitWrite(size_t nMessages) {
}
template <template <typename, MQFlavor> typename MQDescriptorType, typename T, MQFlavor flavor>
-size_t MessageQueueBase<MQDescriptorType, T, flavor>::availableToReadBytes() const {
+inline size_t MessageQueueBase<MQDescriptorType, T, flavor>::availableToReadBytes() const {
/*
* This method is invoked by implementations of both read() and write() and
* hence requires a memory_order_acquired load for both mReadPtr and
* mWritePtr.
*/
- return mWritePtr->load(std::memory_order_acquire) - mReadPtr->load(std::memory_order_acquire);
+ uint64_t writePtr = mWritePtr->load(std::memory_order_acquire);
+ uint64_t readPtr = mReadPtr->load(std::memory_order_acquire);
+ if (writePtr < readPtr) {
+ hardware::details::logError(
+ "The write or read pointer has become corrupted. Reading from the queue is no "
+ "longer possible.");
+ return 0;
+ }
+ return writePtr - readPtr;
}
template <template <typename, MQFlavor> typename MQDescriptorType, typename T, MQFlavor flavor>

View File

@ -0,0 +1,87 @@
From 7c36c8764f78a135a4d273e8ff7200beef767e18 Mon Sep 17 00:00:00 2001
From: Vova Sharaienko <sharaienko@google.com>
Date: Wed, 27 Mar 2024 23:21:03 +0000
Subject: [PATCH] [libstatssocket] Added validation for adding new data into
StatsEvent
Bug: 330054251
Test: atest StatsEventTest#TestHeapBufferOverflowError
Ignore-AOSP-First: security
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:4f6c56889356a5a422b59c71e9142875d00e43bf)
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:e440696c4fd6d44d21451467294b3e31c6867e08)
Merged-In: I27c69d9875b494f98a2a961cdd4fe139cd809387
Change-Id: I27c69d9875b494f98a2a961cdd4fe139cd809387
---
lib/libstatssocket/stats_event.c | 3 ++
lib/libstatssocket/tests/stats_event_test.cpp | 44 +++++++++++++++++++
2 files changed, 47 insertions(+)
diff --git a/lib/libstatssocket/stats_event.c b/lib/libstatssocket/stats_event.c
index 9bb4c52c4..828ff3e4f 100644
--- a/lib/libstatssocket/stats_event.c
+++ b/lib/libstatssocket/stats_event.c
@@ -330,6 +330,9 @@ void AStatsEvent_writeStringArray(AStatsEvent* event, const char* const* element
// Side-effect: modifies event->errors if field has too many annotations
static void increment_annotation_count(AStatsEvent* event) {
+ if (event->lastFieldPos >= event->bufSize) {
+ return;
+ }
uint8_t fieldType = event->buf[event->lastFieldPos] & 0x0F;
uint32_t oldAnnotationCount = (event->buf[event->lastFieldPos] & 0xF0) >> 4;
uint32_t newAnnotationCount = oldAnnotationCount + 1;
diff --git a/lib/libstatssocket/tests/stats_event_test.cpp b/lib/libstatssocket/tests/stats_event_test.cpp
index 93a99f1bf..dea81c256 100644
--- a/lib/libstatssocket/tests/stats_event_test.cpp
+++ b/lib/libstatssocket/tests/stats_event_test.cpp
@@ -536,6 +536,50 @@ TEST(StatsEventTest, TestPushOverflowError) {
AStatsEvent_release(event);
}
+TEST(StatsEventTest, TestHeapBufferOverflowError) {
+ const std::string testString(4039, 'A');
+ const std::string testString2(47135, 'B');
+
+ AStatsEvent* event = AStatsEvent_obtain();
+ AStatsEvent_setAtomId(event, 100);
+
+ AStatsEvent_writeString(event, testString.c_str());
+ size_t bufferSize = 0;
+ AStatsEvent_getBuffer(event, &bufferSize);
+ EXPECT_EQ(bufferSize, 4060);
+ uint32_t errors = AStatsEvent_getErrors(event);
+ EXPECT_EQ(errors, 0);
+
+ // expand the buffer and fill with data up to the very last byte
+ AStatsEvent_writeString(event, testString2.c_str());
+ bufferSize = 0;
+ AStatsEvent_getBuffer(event, &bufferSize);
+ EXPECT_EQ(bufferSize, 50 * 1024);
+
+ errors = AStatsEvent_getErrors(event);
+ EXPECT_EQ(errors, 0);
+
+ // this write is no-op due to buffer reached its max capacity
+ // should set the overflow flag
+ AStatsEvent_writeString(event, testString2.c_str());
+ bufferSize = 0;
+ AStatsEvent_getBuffer(event, &bufferSize);
+ EXPECT_EQ(bufferSize, 50 * 1024);
+
+ errors = AStatsEvent_getErrors(event);
+ EXPECT_EQ(errors & ERROR_OVERFLOW, ERROR_OVERFLOW);
+
+ // here should be crash
+ AStatsEvent_addBoolAnnotation(event, 1, false);
+
+ AStatsEvent_write(event);
+
+ errors = AStatsEvent_getErrors(event);
+ EXPECT_EQ(errors & ERROR_OVERFLOW, ERROR_OVERFLOW);
+
+ AStatsEvent_release(event);
+}
+
TEST(StatsEventTest, TestPullOverflowError) {
const uint32_t atomId = 10100;
const vector<uint8_t> bytes(430 /* number of elements */, 1 /* value of each element */);

@ -1 +1 @@
Subproject commit 2fc97e971f72f95e21f7b5685f16a839a77a977a
Subproject commit bc2e6dd93df6ce9ad928f8d75bd2584c940461ac

View File

@ -95,7 +95,6 @@ applyPatch "$DOS_PATCHES_COMMON/android_build/0001-verity-openssl3.patch"; #Fix
sed -i '75i$(my_res_package): PRIVATE_AAPT_FLAGS += --auto-add-overlay' core/aapt2.mk; #Enable auto-add-overlay for packages, this allows the vendor overlay to easily work across all branches.
awk -i inplace '!/updatable_apex.mk/' target/product/generic_system.mk; #Disable APEX
sed -i 's/PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION := 23/PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION := 28/' core/version_defaults.mk; #Set the minimum supported target SDK to Pie (GrapheneOS)
sed -i 's/2024-04-05/2024-05-05/' core/version_defaults.mk; #Bump Security String #x_asb_2024-05
fi;
if enterAndClear "build/soong"; then
@ -125,12 +124,7 @@ sed -i '/LOCAL_MODULE/s/Camera/SecureCamera/' Android.mk; #Change module name
sed -i '11iLOCAL_OVERRIDES_PACKAGES := Camera Camera2 LegacyCamera Snap OpenCamera' Android.mk; #Replace the others
fi;
if enterAndClear "external/sonivox"; then
applyPatch "$DOS_PATCHES_COMMON/android_external_sonivox/392224.patch"; #T_asb_2024-05 Fix buffer overrun in eas_wtengine
fi;
if enterAndClear "frameworks/base"; then
applyPatch "$DOS_PATCHES/android_frameworks_base/392225.patch"; #T_asb_2024-05 Prioritize system toasts
git revert --no-edit 83fe523914728a3674debba17a6019cb74803045; #Reverts "Allow signature spoofing for microG Companion/Services" in favor of below patch
applyPatch "$DOS_PATCHES/android_frameworks_base/344888-backport.patch"; #fixup! fw/b: Add support for allowing/disallowing apps on cellular, vpn and wifi networks (CalyxOS)
applyPatch "$DOS_PATCHES/android_frameworks_base/0007-Always_Restict_Serial.patch"; #Always restrict access to Build.SERIAL (GrapheneOS)

View File

@ -94,6 +94,7 @@ applyPatch "$DOS_PATCHES/android_build/0003-Exec_Based_Spawning.patch"; #Add exe
applyPatch "$DOS_PATCHES/android_build/0004-Selective_APEX.patch"; #Only enable APEX on 6th/7th gen Pixel devices (GrapheneOS)
sed -i '75i$(my_res_package): PRIVATE_AAPT_FLAGS += --auto-add-overlay' core/aapt2.mk; #Enable auto-add-overlay for packages, this allows the vendor overlay to easily work across all branches.
sed -i 's/PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION := 23/PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION := 28/' core/version_util.mk; #Set the minimum supported target SDK to Pie (GrapheneOS)
sed -i 's/2024-05-05/2024-06-05/' core/version_defaults.mk; #Bump Security String #x_asb_2024-06
fi;
if enterAndClear "build/soong"; then
@ -122,6 +123,7 @@ git am $DOS_PATCHES/ASB-2023-10/av-*.patch;
fi;
if enterAndClear "frameworks/base"; then
git am $DOS_PATCHES/ASB-2024-06/fwb-*.patch;
git revert --no-edit d36faad3267522c6d3ff91ba9dcca8f6274bccd1; #Reverts "JobScheduler: Respect allow-in-power-save perm" in favor of below patch
git revert --no-edit 90d6826548189ca850d91692e71fcc1be426f453; #Reverts "Remove sensitive info from SUPL requests" in favor of below patch
git revert --no-edit 6d2955f0bd55e9938d5d49415182c27b50900b95; #Reverts "Allow signature spoofing for microG Companion/Services" in favor of below patch
@ -372,6 +374,10 @@ applyPatch "$DOS_PATCHES/android_packages_modules_Permission/0005-Browser_No_Loc
applyPatch "$DOS_PATCHES/android_packages_modules_Permission/0006-Location_Indicators.patch"; #SystemUI: Use new privacy indicators for location (GrapheneOS)
fi;
if enterAndClear "packages/modules/StatsD"; then
git am $DOS_PATCHES/ASB-2024-06/statsd-*.patch;
fi;
if enterAndClear "packages/modules/Wifi"; then
applyPatch "$DOS_PATCHES/android_packages_modules_Wifi/344228.patch"; #wifi: resurrect mWifiLinkLayerStatsSupported counter (sassmann)
applyPatch "$DOS_PATCHES/android_packages_modules_Wifi/0001-Random_MAC.patch"; #Add support for always generating new random MAC (GrapheneOS)
@ -399,6 +405,10 @@ if enterAndClear "system/extras"; then
applyPatch "$DOS_PATCHES/android_system_extras/0001-ext4_pad_filenames.patch"; #FBE: pad filenames more (GrapheneOS)
fi;
if enterAndClear "system/libfmq"; then
git am $DOS_PATCHES/ASB-2024-06/libfmq-*.patch;
fi;
if enterAndClear "system/sepolicy"; then
applyPatch "$DOS_PATCHES/android_system_sepolicy/0002-protected_files.patch"; #Label protected_{fifos,regular} as proc_security (GrapheneOS)
applyPatch "$DOS_PATCHES/android_system_sepolicy/0003-ptrace_scope-1.patch"; #Allow init to control kernel.yama.ptrace_scope (GrapheneOS)