Loading services/core/java/com/android/server/notification/NotificationManagerService.java +42 −11 Original line number Diff line number Diff line Loading @@ -2612,7 +2612,8 @@ public class NotificationManagerService extends SystemService { private int pullNotificationStates(int atomTag, List<StatsEvent> data) { switch(atomTag) { case PACKAGE_NOTIFICATION_PREFERENCES: mPreferencesHelper.pullPackagePreferencesStats(data); mPreferencesHelper.pullPackagePreferencesStats(data, getAllUsersNotificationPermissions()); break; case PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES: mPreferencesHelper.pullPackageChannelPreferencesStats(data); Loading Loading @@ -5065,16 +5066,18 @@ public class NotificationManagerService extends SystemService { final DumpFilter filter = DumpFilter.parseFromArguments(args); final long token = Binder.clearCallingIdentity(); try { final ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions = getAllUsersNotificationPermissions(); if (filter.stats) { dumpJson(pw, filter); dumpJson(pw, filter, pkgPermissions); } else if (filter.rvStats) { dumpRemoteViewStats(pw, filter); } else if (filter.proto) { dumpProto(fd, filter); dumpProto(fd, filter, pkgPermissions); } else if (filter.criticalPriority) { dumpNotificationRecords(pw, filter); } else { dumpImpl(pw, filter); dumpImpl(pw, filter, pkgPermissions); } } finally { Binder.restoreCallingIdentity(token); Loading Loading @@ -5896,12 +5899,38 @@ public class NotificationManagerService extends SystemService { return null; } private void dumpJson(PrintWriter pw, @NonNull DumpFilter filter) { // Gets packages that have requested notification permission, and whether that has been // allowed/denied, for all users on the device. // Returns a single map containing that info keyed by (uid, package name) for all users. // Because this calls into mPermissionHelper, this method must never be called with a lock held. @VisibleForTesting protected ArrayMap<Pair<Integer, String>, Boolean> getAllUsersNotificationPermissions() { // don't bother if migration is not enabled if (!mEnableAppSettingMigration) { return null; } ArrayMap<Pair<Integer, String>, Boolean> allPermissions = new ArrayMap<>(); final List<UserInfo> allUsers = mUm.getUsers(); // for each of these, get the package notification permissions that are associated // with this user and add it to the map for (UserInfo ui : allUsers) { ArrayMap<Pair<Integer, String>, Boolean> userPermissions = mPermissionHelper.getNotificationPermissionValues( ui.getUserHandle().getIdentifier()); for (Pair<Integer, String> pair : userPermissions.keySet()) { allPermissions.put(pair, userPermissions.get(pair)); } } return allPermissions; } private void dumpJson(PrintWriter pw, @NonNull DumpFilter filter, ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) { JSONObject dump = new JSONObject(); try { dump.put("service", "Notification Manager"); dump.put("bans", mPreferencesHelper.dumpBansJson(filter)); dump.put("ranking", mPreferencesHelper.dumpJson(filter)); dump.put("bans", mPreferencesHelper.dumpBansJson(filter, pkgPermissions)); dump.put("ranking", mPreferencesHelper.dumpJson(filter, pkgPermissions)); dump.put("stats", mUsageStats.dumpJson(filter)); dump.put("channels", mPreferencesHelper.dumpChannelsJson(filter)); } catch (JSONException e) { Loading @@ -5919,7 +5948,8 @@ public class NotificationManagerService extends SystemService { stats.dump(REPORT_REMOTE_VIEWS, pw, filter); } private void dumpProto(FileDescriptor fd, @NonNull DumpFilter filter) { private void dumpProto(FileDescriptor fd, @NonNull DumpFilter filter, ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) { final ProtoOutputStream proto = new ProtoOutputStream(fd); synchronized (mNotificationLock) { int N = mNotificationList.size(); Loading Loading @@ -5986,7 +6016,7 @@ public class NotificationManagerService extends SystemService { long rankingToken = proto.start(NotificationServiceDumpProto.RANKING_CONFIG); mRankingHelper.dump(proto, filter); mPreferencesHelper.dump(proto, filter); mPreferencesHelper.dump(proto, filter, pkgPermissions); proto.end(rankingToken); } Loading @@ -6009,7 +6039,8 @@ public class NotificationManagerService extends SystemService { } } void dumpImpl(PrintWriter pw, @NonNull DumpFilter filter) { void dumpImpl(PrintWriter pw, @NonNull DumpFilter filter, ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) { pw.print("Current Notification Manager state"); if (filter.filtered) { pw.print(" (filtered to "); pw.print(filter); pw.print(")"); Loading Loading @@ -6088,7 +6119,7 @@ public class NotificationManagerService extends SystemService { mRankingHelper.dump(pw, " ", filter); pw.println("\n Notification Preferences:"); mPreferencesHelper.dump(pw, " ", filter); mPreferencesHelper.dump(pw, " ", filter, pkgPermissions); pw.println("\n Notification listeners:"); mListeners.dump(pw, filter); Loading services/core/java/com/android/server/notification/PreferencesHelper.java +189 −23 Original line number Diff line number Diff line Loading @@ -1931,31 +1931,41 @@ public class PreferencesHelper implements RankingConfig { } public void dump(PrintWriter pw, String prefix, @NonNull NotificationManagerService.DumpFilter filter) { @NonNull NotificationManagerService.DumpFilter filter, ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) { pw.print(prefix); pw.println("per-package config version: " + XML_VERSION); pw.println("PackagePreferences:"); synchronized (mPackagePreferences) { dumpPackagePreferencesLocked(pw, prefix, filter, mPackagePreferences); dumpPackagePreferencesLocked(pw, prefix, filter, mPackagePreferences, pkgPermissions); } pw.println("Restored without uid:"); dumpPackagePreferencesLocked(pw, prefix, filter, mRestoredWithoutUids); dumpPackagePreferencesLocked(pw, prefix, filter, mRestoredWithoutUids, null); } public void dump(ProtoOutputStream proto, @NonNull NotificationManagerService.DumpFilter filter) { @NonNull NotificationManagerService.DumpFilter filter, ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) { synchronized (mPackagePreferences) { dumpPackagePreferencesLocked(proto, RankingHelperProto.RECORDS, filter, mPackagePreferences); mPackagePreferences, pkgPermissions); } dumpPackagePreferencesLocked(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter, mRestoredWithoutUids); mRestoredWithoutUids, null); } private void dumpPackagePreferencesLocked(PrintWriter pw, String prefix, @NonNull NotificationManagerService.DumpFilter filter, ArrayMap<String, PackagePreferences> packagePreferences) { ArrayMap<String, PackagePreferences> packagePreferences, ArrayMap<Pair<Integer, String>, Boolean> packagePermissions) { // Used for tracking which package preferences we've seen already for notification // permission reasons; after handling packages with local preferences, we'll want to dump // the ones with notification permissions set but not local prefs. Set<Pair<Integer, String>> pkgsWithPermissionsToHandle = null; if (packagePermissions != null) { pkgsWithPermissionsToHandle = packagePermissions.keySet(); } final int N = packagePreferences.size(); for (int i = 0; i < N; i++) { final PackagePreferences r = packagePreferences.valueAt(i); Loading @@ -1966,9 +1976,21 @@ public class PreferencesHelper implements RankingConfig { pw.print(" ("); pw.print(r.uid == UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid)); pw.print(')'); if (!mPermissionHelper.isMigrationEnabled() && r.importance != DEFAULT_IMPORTANCE) { if (!mPermissionHelper.isMigrationEnabled()) { if (r.importance != DEFAULT_IMPORTANCE) { pw.print(" importance="); pw.print(NotificationListenerService.Ranking.importanceToString( r.importance)); } } else { Pair<Integer, String> key = new Pair<>(r.uid, r.pkg); if (packagePermissions != null && pkgsWithPermissionsToHandle.contains(key)) { pw.print(" importance="); pw.print(NotificationListenerService.Ranking.importanceToString(r.importance)); pw.print(NotificationListenerService.Ranking.importanceToString( packagePermissions.get(key) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE)); pkgsWithPermissionsToHandle.remove(key); } } if (r.priority != DEFAULT_PRIORITY) { pw.print(" priority="); Loading Loading @@ -2007,11 +2029,34 @@ public class PreferencesHelper implements RankingConfig { } } } // Handle any remaining packages with permissions if (mPermissionHelper.isMigrationEnabled() && pkgsWithPermissionsToHandle != null) { for (Pair<Integer, String> p : pkgsWithPermissionsToHandle) { // p.first is the uid of this package; p.second is the package name if (filter.matches(p.second)) { pw.print(prefix); pw.print(" AppSettings: "); pw.print(p.second); pw.print(" ("); pw.print(p.first == UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(p.first)); pw.print(')'); pw.print(" importance="); pw.print(NotificationListenerService.Ranking.importanceToString( packagePermissions.get(p) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE)); } } } } private static void dumpPackagePreferencesLocked(ProtoOutputStream proto, long fieldId, private void dumpPackagePreferencesLocked(ProtoOutputStream proto, long fieldId, @NonNull NotificationManagerService.DumpFilter filter, ArrayMap<String, PackagePreferences> packagePreferences) { ArrayMap<String, PackagePreferences> packagePreferences, ArrayMap<Pair<Integer, String>, Boolean> packagePermissions) { Set<Pair<Integer, String>> pkgsWithPermissionsToHandle = null; if (packagePermissions != null) { pkgsWithPermissionsToHandle = packagePermissions.keySet(); } final int N = packagePreferences.size(); long fToken; for (int i = 0; i < N; i++) { Loading @@ -2021,7 +2066,16 @@ public class PreferencesHelper implements RankingConfig { proto.write(RankingHelperProto.RecordProto.PACKAGE, r.pkg); proto.write(RankingHelperProto.RecordProto.UID, r.uid); if (mPermissionHelper.isMigrationEnabled()) { Pair<Integer, String> key = new Pair<>(r.uid, r.pkg); if (packagePermissions != null && pkgsWithPermissionsToHandle.contains(key)) { proto.write(RankingHelperProto.RecordProto.IMPORTANCE, packagePermissions.get(key) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE); pkgsWithPermissionsToHandle.remove(key); } } else { proto.write(RankingHelperProto.RecordProto.IMPORTANCE, r.importance); } proto.write(RankingHelperProto.RecordProto.PRIORITY, r.priority); proto.write(RankingHelperProto.RecordProto.VISIBILITY, r.visibility); proto.write(RankingHelperProto.RecordProto.SHOW_BADGE, r.showBadge); Loading @@ -2036,28 +2090,82 @@ public class PreferencesHelper implements RankingConfig { proto.end(fToken); } } if (mPermissionHelper.isMigrationEnabled() && pkgsWithPermissionsToHandle != null) { for (Pair<Integer, String> p : pkgsWithPermissionsToHandle) { if (filter.matches(p.second)) { fToken = proto.start(fieldId); proto.write(RankingHelperProto.RecordProto.PACKAGE, p.second); proto.write(RankingHelperProto.RecordProto.UID, p.first); proto.write(RankingHelperProto.RecordProto.IMPORTANCE, packagePermissions.get(p) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE); proto.end(fToken); } } } } /** * Fills out {@link PackageNotificationPreferences} proto and wraps it in a {@link StatsEvent}. */ public void pullPackagePreferencesStats(List<StatsEvent> events) { public void pullPackagePreferencesStats(List<StatsEvent> events, ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) { Set<Pair<Integer, String>> pkgsWithPermissionsToHandle = null; if (pkgPermissions != null) { pkgsWithPermissionsToHandle = pkgPermissions.keySet(); } int pulledEvents = 0; synchronized (mPackagePreferences) { for (int i = 0; i < mPackagePreferences.size(); i++) { if (i > NOTIFICATION_PREFERENCES_PULL_LIMIT) { if (pulledEvents > NOTIFICATION_PREFERENCES_PULL_LIMIT) { break; } pulledEvents++; SysUiStatsEvent.Builder event = mStatsEventBuilderFactory.newBuilder() .setAtomId(PACKAGE_NOTIFICATION_PREFERENCES); final PackagePreferences r = mPackagePreferences.valueAt(i); event.writeInt(r.uid); event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true); if (mPermissionHelper.isMigrationEnabled()) { // Even if this package's data is not present, we need to write something; // so default to IMPORTANCE_NONE, since if PM doesn't know about the package // for some reason, notifications are not allowed. int importance = IMPORTANCE_NONE; Pair<Integer, String> key = new Pair<>(r.uid, r.pkg); if (pkgPermissions != null && pkgsWithPermissionsToHandle.contains(key)) { importance = pkgPermissions.get(key) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE; pkgsWithPermissionsToHandle.remove(key); } event.writeInt(importance); } else { event.writeInt(r.importance); } event.writeInt(r.visibility); event.writeInt(r.lockedAppFields); events.add(event.build()); } } // handle remaining packages with PackageManager permissions but not local settings if (mPermissionHelper.isMigrationEnabled() && pkgPermissions != null) { for (Pair<Integer, String> p : pkgsWithPermissionsToHandle) { if (pulledEvents > NOTIFICATION_PREFERENCES_PULL_LIMIT) { break; } pulledEvents++; SysUiStatsEvent.Builder event = mStatsEventBuilderFactory.newBuilder() .setAtomId(PACKAGE_NOTIFICATION_PREFERENCES); event.writeInt(p.first); event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true); event.writeInt(pkgPermissions.get(p) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE); // fill out the rest of the fields with default values so as not to confuse the // builder event.writeInt(DEFAULT_VISIBILITY); event.writeInt(DEFAULT_LOCKED_APP_FIELDS); events.add(event.build()); } } } /** Loading Loading @@ -2126,7 +2234,8 @@ public class PreferencesHelper implements RankingConfig { } } public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) { public JSONObject dumpJson(NotificationManagerService.DumpFilter filter, ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) { JSONObject ranking = new JSONObject(); JSONArray PackagePreferencess = new JSONArray(); try { Loading @@ -2134,6 +2243,13 @@ public class PreferencesHelper implements RankingConfig { } catch (JSONException e) { // pass } // Track data that we've handled from the permissions-based list Set<Pair<Integer, String>> pkgsWithPermissionsToHandle = null; if (pkgPermissions != null) { pkgsWithPermissionsToHandle = pkgPermissions.keySet(); } synchronized (mPackagePreferences) { final int N = mPackagePreferences.size(); for (int i = 0; i < N; i++) { Loading @@ -2143,11 +2259,23 @@ public class PreferencesHelper implements RankingConfig { try { PackagePreferences.put("userId", UserHandle.getUserId(r.uid)); PackagePreferences.put("packageName", r.pkg); if (mPermissionHelper.isMigrationEnabled()) { Pair<Integer, String> key = new Pair<>(r.uid, r.pkg); if (pkgPermissions != null && pkgsWithPermissionsToHandle.contains(key)) { PackagePreferences.put("importance", NotificationListenerService.Ranking.importanceToString( pkgPermissions.get(key) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE)); pkgsWithPermissionsToHandle.remove(key); } } else { if (r.importance != DEFAULT_IMPORTANCE) { PackagePreferences.put("importance", NotificationListenerService.Ranking.importanceToString( r.importance)); } } if (r.priority != DEFAULT_PRIORITY) { PackagePreferences.put("priority", Notification.priorityToString(r.priority)); Loading Loading @@ -2176,6 +2304,27 @@ public class PreferencesHelper implements RankingConfig { } } } // handle packages for which there are permissions but no local settings if (mPermissionHelper.isMigrationEnabled() && pkgsWithPermissionsToHandle != null) { for (Pair<Integer, String> p : pkgsWithPermissionsToHandle) { if (filter == null || filter.matches(p.second)) { JSONObject PackagePreferences = new JSONObject(); try { PackagePreferences.put("userId", UserHandle.getUserId(p.first)); PackagePreferences.put("packageName", p.second); PackagePreferences.put("importance", NotificationListenerService.Ranking.importanceToString( pkgPermissions.get(p) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE)); } catch (JSONException e) { // pass } PackagePreferencess.put(PackagePreferences); } } } try { ranking.put("PackagePreferencess", PackagePreferencess); } catch (JSONException e) { Loading @@ -2193,9 +2342,11 @@ public class PreferencesHelper implements RankingConfig { * @param filter * @return */ public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) { public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter, ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) { JSONArray bans = new JSONArray(); Map<Integer, String> packageBans = getPackageBans(); Map<Integer, String> packageBans = mPermissionHelper.isMigrationEnabled() ? getPermissionBasedPackageBans(pkgPermissions) : getPackageBans(); for (Map.Entry<Integer, String> ban : packageBans.entrySet()) { final int userId = UserHandle.getUserId(ban.getKey()); final String packageName = ban.getValue(); Loading Loading @@ -2228,6 +2379,21 @@ public class PreferencesHelper implements RankingConfig { } } // Same functionality as getPackageBans by extracting the set of packages from the provided // map that are disallowed from sending notifications. protected Map<Integer, String> getPermissionBasedPackageBans( ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) { ArrayMap<Integer, String> packageBans = new ArrayMap<>(); if (pkgPermissions != null) { for (Pair<Integer, String> p : pkgPermissions.keySet()) { if (!pkgPermissions.get(p)) { packageBans.put(p.first, p.second); } } } return packageBans; } /** * Dump only the channel information as structured JSON for the stats collector. * Loading services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +6 −0 Original line number Diff line number Diff line Loading @@ -8507,4 +8507,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue(); } @Test public void testGetAllUsersNotificationPermissions_migrationNotEnabled() { // make sure we don't bother if the migration is not enabled assertThat(mService.getAllUsersNotificationPermissions()).isNull(); } } services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java +34 −3 Original line number Diff line number Diff line Loading @@ -20,9 +20,7 @@ import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_IGNORED; import static android.app.NotificationManager.EXTRA_BLOCKED_STATE; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; import static android.app.PendingIntent.FLAG_MUTABLE; import static android.app.PendingIntent.FLAG_ONE_SHOT; import static android.content.pm.PackageManager.FEATURE_WATCH; Loading @@ -38,6 +36,7 @@ import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; import static org.mockito.ArgumentMatchers.anyBoolean; Loading Loading @@ -84,6 +83,7 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutServiceInternal; import android.content.pm.UserInfo; import android.content.res.Resources; import android.media.AudioManager; import android.media.session.MediaSession; Loading @@ -105,8 +105,10 @@ import android.testing.TestableContext; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.testing.TestablePermissions; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; import android.util.Pair; import androidx.test.InstrumentationRegistry; Loading @@ -128,7 +130,6 @@ import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; Loading Loading @@ -812,4 +813,34 @@ public class NotificationPermissionMigrationTest extends UiServiceTestCase { verify(mUsageStats, never()).registerBlocked(any()); verify(mUsageStats).registerPostedByApp(any()); } @Test public void testGetAllUsersNotificationPermissions() { // In this case, there are multiple users each with notification permissions (and also, // for good measure, some without). // make sure the collection returned contains info for all of them final List<UserInfo> userInfos = new ArrayList<>(); userInfos.add(new UserInfo(0, "user0", 0)); userInfos.add(new UserInfo(1, "user1", 0)); userInfos.add(new UserInfo(2, "user2", 0)); when(mUm.getUsers()).thenReturn(userInfos); // construct the permissions for each of them ArrayMap<Pair<Integer, String>, Boolean> permissions0 = new ArrayMap<>(), permissions1 = new ArrayMap<>(); permissions0.put(new Pair<>(10, "package1"), true); permissions0.put(new Pair<>(20, "package2"), false); permissions1.put(new Pair<>(11, "package1"), false); permissions1.put(new Pair<>(21, "package2"), true); when(mPermissionHelper.getNotificationPermissionValues(0)).thenReturn(permissions0); when(mPermissionHelper.getNotificationPermissionValues(1)).thenReturn(permissions1); when(mPermissionHelper.getNotificationPermissionValues(2)).thenReturn(new ArrayMap<>()); ArrayMap<Pair<Integer, String>, Boolean> combinedPermissions = mService.getAllUsersNotificationPermissions(); assertTrue(combinedPermissions.get(new Pair<>(10, "package1"))); assertFalse(combinedPermissions.get(new Pair<>(20, "package2"))); assertFalse(combinedPermissions.get(new Pair<>(11, "package1"))); assertTrue(combinedPermissions.get(new Pair<>(21, "package2"))); } } services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +496 −3 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
services/core/java/com/android/server/notification/NotificationManagerService.java +42 −11 Original line number Diff line number Diff line Loading @@ -2612,7 +2612,8 @@ public class NotificationManagerService extends SystemService { private int pullNotificationStates(int atomTag, List<StatsEvent> data) { switch(atomTag) { case PACKAGE_NOTIFICATION_PREFERENCES: mPreferencesHelper.pullPackagePreferencesStats(data); mPreferencesHelper.pullPackagePreferencesStats(data, getAllUsersNotificationPermissions()); break; case PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES: mPreferencesHelper.pullPackageChannelPreferencesStats(data); Loading Loading @@ -5065,16 +5066,18 @@ public class NotificationManagerService extends SystemService { final DumpFilter filter = DumpFilter.parseFromArguments(args); final long token = Binder.clearCallingIdentity(); try { final ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions = getAllUsersNotificationPermissions(); if (filter.stats) { dumpJson(pw, filter); dumpJson(pw, filter, pkgPermissions); } else if (filter.rvStats) { dumpRemoteViewStats(pw, filter); } else if (filter.proto) { dumpProto(fd, filter); dumpProto(fd, filter, pkgPermissions); } else if (filter.criticalPriority) { dumpNotificationRecords(pw, filter); } else { dumpImpl(pw, filter); dumpImpl(pw, filter, pkgPermissions); } } finally { Binder.restoreCallingIdentity(token); Loading Loading @@ -5896,12 +5899,38 @@ public class NotificationManagerService extends SystemService { return null; } private void dumpJson(PrintWriter pw, @NonNull DumpFilter filter) { // Gets packages that have requested notification permission, and whether that has been // allowed/denied, for all users on the device. // Returns a single map containing that info keyed by (uid, package name) for all users. // Because this calls into mPermissionHelper, this method must never be called with a lock held. @VisibleForTesting protected ArrayMap<Pair<Integer, String>, Boolean> getAllUsersNotificationPermissions() { // don't bother if migration is not enabled if (!mEnableAppSettingMigration) { return null; } ArrayMap<Pair<Integer, String>, Boolean> allPermissions = new ArrayMap<>(); final List<UserInfo> allUsers = mUm.getUsers(); // for each of these, get the package notification permissions that are associated // with this user and add it to the map for (UserInfo ui : allUsers) { ArrayMap<Pair<Integer, String>, Boolean> userPermissions = mPermissionHelper.getNotificationPermissionValues( ui.getUserHandle().getIdentifier()); for (Pair<Integer, String> pair : userPermissions.keySet()) { allPermissions.put(pair, userPermissions.get(pair)); } } return allPermissions; } private void dumpJson(PrintWriter pw, @NonNull DumpFilter filter, ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) { JSONObject dump = new JSONObject(); try { dump.put("service", "Notification Manager"); dump.put("bans", mPreferencesHelper.dumpBansJson(filter)); dump.put("ranking", mPreferencesHelper.dumpJson(filter)); dump.put("bans", mPreferencesHelper.dumpBansJson(filter, pkgPermissions)); dump.put("ranking", mPreferencesHelper.dumpJson(filter, pkgPermissions)); dump.put("stats", mUsageStats.dumpJson(filter)); dump.put("channels", mPreferencesHelper.dumpChannelsJson(filter)); } catch (JSONException e) { Loading @@ -5919,7 +5948,8 @@ public class NotificationManagerService extends SystemService { stats.dump(REPORT_REMOTE_VIEWS, pw, filter); } private void dumpProto(FileDescriptor fd, @NonNull DumpFilter filter) { private void dumpProto(FileDescriptor fd, @NonNull DumpFilter filter, ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) { final ProtoOutputStream proto = new ProtoOutputStream(fd); synchronized (mNotificationLock) { int N = mNotificationList.size(); Loading Loading @@ -5986,7 +6016,7 @@ public class NotificationManagerService extends SystemService { long rankingToken = proto.start(NotificationServiceDumpProto.RANKING_CONFIG); mRankingHelper.dump(proto, filter); mPreferencesHelper.dump(proto, filter); mPreferencesHelper.dump(proto, filter, pkgPermissions); proto.end(rankingToken); } Loading @@ -6009,7 +6039,8 @@ public class NotificationManagerService extends SystemService { } } void dumpImpl(PrintWriter pw, @NonNull DumpFilter filter) { void dumpImpl(PrintWriter pw, @NonNull DumpFilter filter, ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) { pw.print("Current Notification Manager state"); if (filter.filtered) { pw.print(" (filtered to "); pw.print(filter); pw.print(")"); Loading Loading @@ -6088,7 +6119,7 @@ public class NotificationManagerService extends SystemService { mRankingHelper.dump(pw, " ", filter); pw.println("\n Notification Preferences:"); mPreferencesHelper.dump(pw, " ", filter); mPreferencesHelper.dump(pw, " ", filter, pkgPermissions); pw.println("\n Notification listeners:"); mListeners.dump(pw, filter); Loading
services/core/java/com/android/server/notification/PreferencesHelper.java +189 −23 Original line number Diff line number Diff line Loading @@ -1931,31 +1931,41 @@ public class PreferencesHelper implements RankingConfig { } public void dump(PrintWriter pw, String prefix, @NonNull NotificationManagerService.DumpFilter filter) { @NonNull NotificationManagerService.DumpFilter filter, ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) { pw.print(prefix); pw.println("per-package config version: " + XML_VERSION); pw.println("PackagePreferences:"); synchronized (mPackagePreferences) { dumpPackagePreferencesLocked(pw, prefix, filter, mPackagePreferences); dumpPackagePreferencesLocked(pw, prefix, filter, mPackagePreferences, pkgPermissions); } pw.println("Restored without uid:"); dumpPackagePreferencesLocked(pw, prefix, filter, mRestoredWithoutUids); dumpPackagePreferencesLocked(pw, prefix, filter, mRestoredWithoutUids, null); } public void dump(ProtoOutputStream proto, @NonNull NotificationManagerService.DumpFilter filter) { @NonNull NotificationManagerService.DumpFilter filter, ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) { synchronized (mPackagePreferences) { dumpPackagePreferencesLocked(proto, RankingHelperProto.RECORDS, filter, mPackagePreferences); mPackagePreferences, pkgPermissions); } dumpPackagePreferencesLocked(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter, mRestoredWithoutUids); mRestoredWithoutUids, null); } private void dumpPackagePreferencesLocked(PrintWriter pw, String prefix, @NonNull NotificationManagerService.DumpFilter filter, ArrayMap<String, PackagePreferences> packagePreferences) { ArrayMap<String, PackagePreferences> packagePreferences, ArrayMap<Pair<Integer, String>, Boolean> packagePermissions) { // Used for tracking which package preferences we've seen already for notification // permission reasons; after handling packages with local preferences, we'll want to dump // the ones with notification permissions set but not local prefs. Set<Pair<Integer, String>> pkgsWithPermissionsToHandle = null; if (packagePermissions != null) { pkgsWithPermissionsToHandle = packagePermissions.keySet(); } final int N = packagePreferences.size(); for (int i = 0; i < N; i++) { final PackagePreferences r = packagePreferences.valueAt(i); Loading @@ -1966,9 +1976,21 @@ public class PreferencesHelper implements RankingConfig { pw.print(" ("); pw.print(r.uid == UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid)); pw.print(')'); if (!mPermissionHelper.isMigrationEnabled() && r.importance != DEFAULT_IMPORTANCE) { if (!mPermissionHelper.isMigrationEnabled()) { if (r.importance != DEFAULT_IMPORTANCE) { pw.print(" importance="); pw.print(NotificationListenerService.Ranking.importanceToString( r.importance)); } } else { Pair<Integer, String> key = new Pair<>(r.uid, r.pkg); if (packagePermissions != null && pkgsWithPermissionsToHandle.contains(key)) { pw.print(" importance="); pw.print(NotificationListenerService.Ranking.importanceToString(r.importance)); pw.print(NotificationListenerService.Ranking.importanceToString( packagePermissions.get(key) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE)); pkgsWithPermissionsToHandle.remove(key); } } if (r.priority != DEFAULT_PRIORITY) { pw.print(" priority="); Loading Loading @@ -2007,11 +2029,34 @@ public class PreferencesHelper implements RankingConfig { } } } // Handle any remaining packages with permissions if (mPermissionHelper.isMigrationEnabled() && pkgsWithPermissionsToHandle != null) { for (Pair<Integer, String> p : pkgsWithPermissionsToHandle) { // p.first is the uid of this package; p.second is the package name if (filter.matches(p.second)) { pw.print(prefix); pw.print(" AppSettings: "); pw.print(p.second); pw.print(" ("); pw.print(p.first == UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(p.first)); pw.print(')'); pw.print(" importance="); pw.print(NotificationListenerService.Ranking.importanceToString( packagePermissions.get(p) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE)); } } } } private static void dumpPackagePreferencesLocked(ProtoOutputStream proto, long fieldId, private void dumpPackagePreferencesLocked(ProtoOutputStream proto, long fieldId, @NonNull NotificationManagerService.DumpFilter filter, ArrayMap<String, PackagePreferences> packagePreferences) { ArrayMap<String, PackagePreferences> packagePreferences, ArrayMap<Pair<Integer, String>, Boolean> packagePermissions) { Set<Pair<Integer, String>> pkgsWithPermissionsToHandle = null; if (packagePermissions != null) { pkgsWithPermissionsToHandle = packagePermissions.keySet(); } final int N = packagePreferences.size(); long fToken; for (int i = 0; i < N; i++) { Loading @@ -2021,7 +2066,16 @@ public class PreferencesHelper implements RankingConfig { proto.write(RankingHelperProto.RecordProto.PACKAGE, r.pkg); proto.write(RankingHelperProto.RecordProto.UID, r.uid); if (mPermissionHelper.isMigrationEnabled()) { Pair<Integer, String> key = new Pair<>(r.uid, r.pkg); if (packagePermissions != null && pkgsWithPermissionsToHandle.contains(key)) { proto.write(RankingHelperProto.RecordProto.IMPORTANCE, packagePermissions.get(key) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE); pkgsWithPermissionsToHandle.remove(key); } } else { proto.write(RankingHelperProto.RecordProto.IMPORTANCE, r.importance); } proto.write(RankingHelperProto.RecordProto.PRIORITY, r.priority); proto.write(RankingHelperProto.RecordProto.VISIBILITY, r.visibility); proto.write(RankingHelperProto.RecordProto.SHOW_BADGE, r.showBadge); Loading @@ -2036,28 +2090,82 @@ public class PreferencesHelper implements RankingConfig { proto.end(fToken); } } if (mPermissionHelper.isMigrationEnabled() && pkgsWithPermissionsToHandle != null) { for (Pair<Integer, String> p : pkgsWithPermissionsToHandle) { if (filter.matches(p.second)) { fToken = proto.start(fieldId); proto.write(RankingHelperProto.RecordProto.PACKAGE, p.second); proto.write(RankingHelperProto.RecordProto.UID, p.first); proto.write(RankingHelperProto.RecordProto.IMPORTANCE, packagePermissions.get(p) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE); proto.end(fToken); } } } } /** * Fills out {@link PackageNotificationPreferences} proto and wraps it in a {@link StatsEvent}. */ public void pullPackagePreferencesStats(List<StatsEvent> events) { public void pullPackagePreferencesStats(List<StatsEvent> events, ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) { Set<Pair<Integer, String>> pkgsWithPermissionsToHandle = null; if (pkgPermissions != null) { pkgsWithPermissionsToHandle = pkgPermissions.keySet(); } int pulledEvents = 0; synchronized (mPackagePreferences) { for (int i = 0; i < mPackagePreferences.size(); i++) { if (i > NOTIFICATION_PREFERENCES_PULL_LIMIT) { if (pulledEvents > NOTIFICATION_PREFERENCES_PULL_LIMIT) { break; } pulledEvents++; SysUiStatsEvent.Builder event = mStatsEventBuilderFactory.newBuilder() .setAtomId(PACKAGE_NOTIFICATION_PREFERENCES); final PackagePreferences r = mPackagePreferences.valueAt(i); event.writeInt(r.uid); event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true); if (mPermissionHelper.isMigrationEnabled()) { // Even if this package's data is not present, we need to write something; // so default to IMPORTANCE_NONE, since if PM doesn't know about the package // for some reason, notifications are not allowed. int importance = IMPORTANCE_NONE; Pair<Integer, String> key = new Pair<>(r.uid, r.pkg); if (pkgPermissions != null && pkgsWithPermissionsToHandle.contains(key)) { importance = pkgPermissions.get(key) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE; pkgsWithPermissionsToHandle.remove(key); } event.writeInt(importance); } else { event.writeInt(r.importance); } event.writeInt(r.visibility); event.writeInt(r.lockedAppFields); events.add(event.build()); } } // handle remaining packages with PackageManager permissions but not local settings if (mPermissionHelper.isMigrationEnabled() && pkgPermissions != null) { for (Pair<Integer, String> p : pkgsWithPermissionsToHandle) { if (pulledEvents > NOTIFICATION_PREFERENCES_PULL_LIMIT) { break; } pulledEvents++; SysUiStatsEvent.Builder event = mStatsEventBuilderFactory.newBuilder() .setAtomId(PACKAGE_NOTIFICATION_PREFERENCES); event.writeInt(p.first); event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true); event.writeInt(pkgPermissions.get(p) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE); // fill out the rest of the fields with default values so as not to confuse the // builder event.writeInt(DEFAULT_VISIBILITY); event.writeInt(DEFAULT_LOCKED_APP_FIELDS); events.add(event.build()); } } } /** Loading Loading @@ -2126,7 +2234,8 @@ public class PreferencesHelper implements RankingConfig { } } public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) { public JSONObject dumpJson(NotificationManagerService.DumpFilter filter, ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) { JSONObject ranking = new JSONObject(); JSONArray PackagePreferencess = new JSONArray(); try { Loading @@ -2134,6 +2243,13 @@ public class PreferencesHelper implements RankingConfig { } catch (JSONException e) { // pass } // Track data that we've handled from the permissions-based list Set<Pair<Integer, String>> pkgsWithPermissionsToHandle = null; if (pkgPermissions != null) { pkgsWithPermissionsToHandle = pkgPermissions.keySet(); } synchronized (mPackagePreferences) { final int N = mPackagePreferences.size(); for (int i = 0; i < N; i++) { Loading @@ -2143,11 +2259,23 @@ public class PreferencesHelper implements RankingConfig { try { PackagePreferences.put("userId", UserHandle.getUserId(r.uid)); PackagePreferences.put("packageName", r.pkg); if (mPermissionHelper.isMigrationEnabled()) { Pair<Integer, String> key = new Pair<>(r.uid, r.pkg); if (pkgPermissions != null && pkgsWithPermissionsToHandle.contains(key)) { PackagePreferences.put("importance", NotificationListenerService.Ranking.importanceToString( pkgPermissions.get(key) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE)); pkgsWithPermissionsToHandle.remove(key); } } else { if (r.importance != DEFAULT_IMPORTANCE) { PackagePreferences.put("importance", NotificationListenerService.Ranking.importanceToString( r.importance)); } } if (r.priority != DEFAULT_PRIORITY) { PackagePreferences.put("priority", Notification.priorityToString(r.priority)); Loading Loading @@ -2176,6 +2304,27 @@ public class PreferencesHelper implements RankingConfig { } } } // handle packages for which there are permissions but no local settings if (mPermissionHelper.isMigrationEnabled() && pkgsWithPermissionsToHandle != null) { for (Pair<Integer, String> p : pkgsWithPermissionsToHandle) { if (filter == null || filter.matches(p.second)) { JSONObject PackagePreferences = new JSONObject(); try { PackagePreferences.put("userId", UserHandle.getUserId(p.first)); PackagePreferences.put("packageName", p.second); PackagePreferences.put("importance", NotificationListenerService.Ranking.importanceToString( pkgPermissions.get(p) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE)); } catch (JSONException e) { // pass } PackagePreferencess.put(PackagePreferences); } } } try { ranking.put("PackagePreferencess", PackagePreferencess); } catch (JSONException e) { Loading @@ -2193,9 +2342,11 @@ public class PreferencesHelper implements RankingConfig { * @param filter * @return */ public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) { public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter, ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) { JSONArray bans = new JSONArray(); Map<Integer, String> packageBans = getPackageBans(); Map<Integer, String> packageBans = mPermissionHelper.isMigrationEnabled() ? getPermissionBasedPackageBans(pkgPermissions) : getPackageBans(); for (Map.Entry<Integer, String> ban : packageBans.entrySet()) { final int userId = UserHandle.getUserId(ban.getKey()); final String packageName = ban.getValue(); Loading Loading @@ -2228,6 +2379,21 @@ public class PreferencesHelper implements RankingConfig { } } // Same functionality as getPackageBans by extracting the set of packages from the provided // map that are disallowed from sending notifications. protected Map<Integer, String> getPermissionBasedPackageBans( ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) { ArrayMap<Integer, String> packageBans = new ArrayMap<>(); if (pkgPermissions != null) { for (Pair<Integer, String> p : pkgPermissions.keySet()) { if (!pkgPermissions.get(p)) { packageBans.put(p.first, p.second); } } } return packageBans; } /** * Dump only the channel information as structured JSON for the stats collector. * Loading
services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +6 −0 Original line number Diff line number Diff line Loading @@ -8507,4 +8507,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue(); } @Test public void testGetAllUsersNotificationPermissions_migrationNotEnabled() { // make sure we don't bother if the migration is not enabled assertThat(mService.getAllUsersNotificationPermissions()).isNull(); } }
services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java +34 −3 Original line number Diff line number Diff line Loading @@ -20,9 +20,7 @@ import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_IGNORED; import static android.app.NotificationManager.EXTRA_BLOCKED_STATE; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; import static android.app.PendingIntent.FLAG_MUTABLE; import static android.app.PendingIntent.FLAG_ONE_SHOT; import static android.content.pm.PackageManager.FEATURE_WATCH; Loading @@ -38,6 +36,7 @@ import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; import static org.mockito.ArgumentMatchers.anyBoolean; Loading Loading @@ -84,6 +83,7 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutServiceInternal; import android.content.pm.UserInfo; import android.content.res.Resources; import android.media.AudioManager; import android.media.session.MediaSession; Loading @@ -105,8 +105,10 @@ import android.testing.TestableContext; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.testing.TestablePermissions; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; import android.util.Pair; import androidx.test.InstrumentationRegistry; Loading @@ -128,7 +130,6 @@ import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; Loading Loading @@ -812,4 +813,34 @@ public class NotificationPermissionMigrationTest extends UiServiceTestCase { verify(mUsageStats, never()).registerBlocked(any()); verify(mUsageStats).registerPostedByApp(any()); } @Test public void testGetAllUsersNotificationPermissions() { // In this case, there are multiple users each with notification permissions (and also, // for good measure, some without). // make sure the collection returned contains info for all of them final List<UserInfo> userInfos = new ArrayList<>(); userInfos.add(new UserInfo(0, "user0", 0)); userInfos.add(new UserInfo(1, "user1", 0)); userInfos.add(new UserInfo(2, "user2", 0)); when(mUm.getUsers()).thenReturn(userInfos); // construct the permissions for each of them ArrayMap<Pair<Integer, String>, Boolean> permissions0 = new ArrayMap<>(), permissions1 = new ArrayMap<>(); permissions0.put(new Pair<>(10, "package1"), true); permissions0.put(new Pair<>(20, "package2"), false); permissions1.put(new Pair<>(11, "package1"), false); permissions1.put(new Pair<>(21, "package2"), true); when(mPermissionHelper.getNotificationPermissionValues(0)).thenReturn(permissions0); when(mPermissionHelper.getNotificationPermissionValues(1)).thenReturn(permissions1); when(mPermissionHelper.getNotificationPermissionValues(2)).thenReturn(new ArrayMap<>()); ArrayMap<Pair<Integer, String>, Boolean> combinedPermissions = mService.getAllUsersNotificationPermissions(); assertTrue(combinedPermissions.get(new Pair<>(10, "package1"))); assertFalse(combinedPermissions.get(new Pair<>(20, "package2"))); assertFalse(combinedPermissions.get(new Pair<>(11, "package1"))); assertTrue(combinedPermissions.get(new Pair<>(21, "package2"))); } }
services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +496 −3 File changed.Preview size limit exceeded, changes collapsed. Show changes