From 763bb16dc8ed77eaa260d669cf4c9b2c78ecd87e Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Tue, 2 Nov 2021 12:21:41 -0400 Subject: [PATCH] Handle upgrade and B&R compatibilty If loading xml that's an earlier version (either from B&R or an OS upgrade), then migrate the app importance setting to permissionmanager. When writing XML for B&R, include app level permission values from permissionmanager rather than local storage. Although permissions are B&Red separately this information needs to be included in the notifications backup in case it's restored onto an earlier OS. Test: PermissionHelperTest/PreferencesHelperTest Bug: 194833441 Change-Id: Id1271c538f524b90ee8c9b4eb447f872958b322c --- .../server/notification/PermissionHelper.java | 78 +++- .../notification/PreferencesHelper.java | 199 +++++---- .../notification/PermissionHelperTest.java | 65 ++- .../notification/PreferencesHelperTest.java | 394 ++++++++++++++++++ 4 files changed, 637 insertions(+), 99 deletions(-) diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java index 35aa6bd0d92ce..f53bb756265cb 100644 --- a/services/core/java/com/android/server/notification/PermissionHelper.java +++ b/services/core/java/com/android/server/notification/PermissionHelper.java @@ -21,22 +21,27 @@ import static android.content.pm.PackageManager.GET_PERMISSIONS; import static android.permission.PermissionManager.PERMISSION_GRANTED; import android.Manifest; +import android.annotation.NonNull; import android.annotation.UserIdInt; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.os.RemoteException; -import android.os.UserHandle; import android.permission.IPermissionManager; +import android.util.ArrayMap; +import android.util.Pair; import android.util.Slog; import com.android.server.pm.permission.PermissionManagerServiceInternal; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Set; /** * NotificationManagerService helper for querying/setting the app-level notification permission @@ -44,7 +49,7 @@ import java.util.Map; public final class PermissionHelper { private static final String TAG = "PermissionHelper"; - private static String NOTIFICATION_PERMISSION = Manifest.permission.POST_NOTIFICATIONS; + private static final String NOTIFICATION_PERMISSION = Manifest.permission.POST_NOTIFICATIONS; private final PermissionManagerServiceInternal mPmi; private final IPackageManager mPackageManager; @@ -77,9 +82,9 @@ public final class PermissionHelper { * Returns all of the apps that have requested the notification permission in a given user. * Must not be called with a lock held. Format: uid, packageName */ - public Map getAppsRequestingPermission(int userId) { + Set> getAppsRequestingPermission(int userId) { assertFlag(); - Map requested = new HashMap<>(); + Set> requested = new HashSet<>(); List pkgs = getInstalledPackages(userId); for (PackageInfo pi : pkgs) { // when data was stored in PreferencesHelper, we only had data for apps that @@ -90,7 +95,7 @@ public final class PermissionHelper { } for (String perm : pi.requestedPermissions) { if (NOTIFICATION_PERMISSION.equals(perm)) { - requested.put(pi.applicationInfo.uid, pi.packageName); + requested.add(new Pair<>(pi.applicationInfo.uid, pi.packageName)); break; } } @@ -115,9 +120,9 @@ public final class PermissionHelper { * Returns a list of apps that hold the notification permission. Must not be called * with a lock held. Format: uid, packageName. */ - public Map getAppsGrantedPermission(int userId) { + Set> getAppsGrantedPermission(int userId) { assertFlag(); - Map granted = new HashMap<>(); + Set> granted = new HashSet<>(); ParceledListSlice parceledList = null; try { parceledList = mPackageManager.getPackagesHoldingPermissions( @@ -129,11 +134,24 @@ public final class PermissionHelper { return granted; } for (PackageInfo pi : parceledList.getList()) { - granted.put(pi.applicationInfo.uid, pi.packageName); + granted.add(new Pair<>(pi.applicationInfo.uid, pi.packageName)); } return granted; } + public @NonNull + ArrayMap, Boolean> getNotificationPermissionValues( + int userId) { + assertFlag(); + ArrayMap, Boolean> notifPermissions = new ArrayMap<>(); + Set> allRequestingUids = getAppsRequestingPermission(userId); + Set> allApprovedUids = getAppsGrantedPermission(userId); + for (Pair pair : allRequestingUids) { + notifPermissions.put(pair, allApprovedUids.contains(pair)); + } + return notifPermissions; + } + /** * Grants or revokes the notification permission for a given package/user. UserSet should * only be true if this method is being called to migrate existing user choice, because it @@ -159,6 +177,12 @@ public final class PermissionHelper { } } + public void setNotificationPermission(PackagePermission pkgPerm) { + assertFlag(); + setNotificationPermission( + pkgPerm.packageName, pkgPerm.userId, pkgPerm.granted, pkgPerm.userSet); + } + public boolean isPermissionFixed(String packageName, @UserIdInt int userId) { assertFlag(); try { @@ -177,4 +201,42 @@ public final class PermissionHelper { throw new IllegalStateException("Method called without checking flag value"); } } + + public static class PackagePermission { + public final String packageName; + public final @UserIdInt int userId; + public final boolean granted; + public final boolean userSet; + + public PackagePermission(String pkg, int userId, boolean granted, boolean userSet) { + this.packageName = pkg; + this.userId = userId; + this.granted = granted; + this.userSet = userSet; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PackagePermission that = (PackagePermission) o; + return userId == that.userId && granted == that.granted && userSet == that.userSet + && Objects.equals(packageName, that.packageName); + } + + @Override + public int hashCode() { + return Objects.hash(packageName, userId, granted, userSet); + } + + @Override + public String toString() { + return "PackagePermission{" + + "packageName='" + packageName + '\'' + + ", userId=" + userId + + ", granted=" + granted + + ", userSet=" + userSet + + '}'; + } + } } diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 8a3d9143918fc..16fe4b60cfb02 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -21,6 +21,7 @@ import static android.app.NotificationChannel.PLACEHOLDER_CONVERSATION_ID; import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE; import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL; import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE; +import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; import static android.util.StatsLog.ANNOTATION_ID_IS_UID; @@ -68,6 +69,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; +import com.android.server.notification.PermissionHelper.PackagePermission; import org.json.JSONArray; import org.json.JSONException; @@ -89,7 +91,7 @@ import java.util.concurrent.ConcurrentHashMap; public class PreferencesHelper implements RankingConfig { private static final String TAG = "NotificationPrefHelper"; - private static final int XML_VERSION = 2; + private final int XML_VERSION; /** What version to check to do the upgrade for bubbles. */ private static final int XML_VERSION_BUBBLES_UPGRADE = 1; @VisibleForTesting @@ -201,6 +203,12 @@ public class PreferencesHelper implements RankingConfig { mAppOps = appOpsManager; mStatsEventBuilderFactory = statsEventBuilderFactory; + if (mPermissionHelper.isMigrationEnabled()) { + XML_VERSION = 3; + } else { + XML_VERSION = 2; + } + updateBadgingEnabled(); updateBubblesEnabled(); updateMediaNotificationFilteringEnabled(); @@ -217,11 +225,13 @@ public class PreferencesHelper implements RankingConfig { final int xmlVersion = parser.getAttributeInt(null, ATT_VERSION, -1); boolean upgradeForBubbles = xmlVersion == XML_VERSION_BUBBLES_UPGRADE; + boolean migrateToPermission = (xmlVersion < XML_VERSION); + ArrayList pkgPerms = new ArrayList<>(); synchronized (mPackagePreferences) { while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { tag = parser.getName(); if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) { - return; + break; } if (type == XmlPullParser.START_TAG) { if (TAG_STATUS_ICONS.equals(tag)) { @@ -252,11 +262,12 @@ public class PreferencesHelper implements RankingConfig { ? BUBBLE_PREFERENCE_ALL : parser.getAttributeInt( null, ATT_ALLOW_BUBBLE, DEFAULT_BUBBLE_PREFERENCE); + int appImportance = parser.getAttributeInt( + null, ATT_IMPORTANCE, DEFAULT_IMPORTANCE); PackagePreferences r = getOrCreatePackagePreferencesLocked( name, userId, uid, - parser.getAttributeInt( - null, ATT_IMPORTANCE, DEFAULT_IMPORTANCE), + appImportance, parser.getAttributeInt( null, ATT_PRIORITY, DEFAULT_PRIORITY), parser.getAttributeInt( @@ -264,8 +275,6 @@ public class PreferencesHelper implements RankingConfig { parser.getAttributeBoolean( null, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE), bubblePref); - r.importance = parser.getAttributeInt( - null, ATT_IMPORTANCE, DEFAULT_IMPORTANCE); r.priority = parser.getAttributeInt( null, ATT_PRIORITY, DEFAULT_PRIORITY); r.visibility = parser.getAttributeInt( @@ -340,6 +349,7 @@ public class PreferencesHelper implements RankingConfig { } } } + // Delegate if (TAG_DELEGATE.equals(tagName)) { int delegateId = @@ -367,12 +377,30 @@ public class PreferencesHelper implements RankingConfig { } catch (PackageManager.NameNotFoundException e) { Slog.e(TAG, "deleteDefaultChannelIfNeededLocked - Exception: " + e); } + + if (migrateToPermission) { + boolean hasChangedChannel = false; + for (NotificationChannel channel : r.channels.values()) { + if (channel.getUserLockedFields() != 0) { + hasChangedChannel = true; + break; + } + } + PackagePermission pkgPerm = new PackagePermission( + r.pkg, userId, appImportance != IMPORTANCE_NONE, + hasChangedChannel || appImportance == IMPORTANCE_NONE); + pkgPerms.add(pkgPerm); + } else { + r.importance = appImportance; + } } } } } } - throw new IllegalStateException("Failed to reach END_DOCUMENT"); + for (PackagePermission p : pkgPerms) { + mPermissionHelper.setNotificationPermission(p); + } } private boolean isShortcutOk(NotificationChannel channel) { @@ -402,18 +430,12 @@ public class PreferencesHelper implements RankingConfig { private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg, int uid) { + // TODO (b/194833441): use permissionhelper instead of DEFAULT_IMPORTANCE return getOrCreatePackagePreferencesLocked(pkg, UserHandle.getUserId(uid), uid, DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE, DEFAULT_BUBBLE_PREFERENCE); } - private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg, - @UserIdInt int userId, int uid) { - return getOrCreatePackagePreferencesLocked(pkg, userId, uid, - DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE, - DEFAULT_BUBBLE_PREFERENCE); - } - private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg, @UserIdInt int userId, int uid, int importance, int priority, int visibility, boolean showBadge, int bubblePreference) { @@ -535,6 +557,10 @@ public class PreferencesHelper implements RankingConfig { out.attributeBoolean(null, ATT_HIDE_SILENT, mHideSilentStatusBarIcons); out.endTag(null, TAG_STATUS_ICONS); } + ArrayMap, Boolean> notifPermissions = new ArrayMap<>(); + if (mPermissionHelper.isMigrationEnabled() && forBackup) { + notifPermissions = mPermissionHelper.getNotificationPermissionValues(userId); + } synchronized (mPackagePreferences) { final int N = mPackagePreferences.size(); @@ -543,78 +569,81 @@ public class PreferencesHelper implements RankingConfig { if (forBackup && UserHandle.getUserId(r.uid) != userId) { continue; } - final boolean hasNonDefaultSettings = - r.importance != DEFAULT_IMPORTANCE - || r.priority != DEFAULT_PRIORITY - || r.visibility != DEFAULT_VISIBILITY - || r.showBadge != DEFAULT_SHOW_BADGE - || r.lockedAppFields != DEFAULT_LOCKED_APP_FIELDS - || r.channels.size() > 0 - || r.groups.size() > 0 - || r.delegate != null - || r.bubblePreference != DEFAULT_BUBBLE_PREFERENCE - || r.hasSentInvalidMessage - || r.userDemotedMsgApp - || r.hasSentValidMessage; - if (hasNonDefaultSettings) { - out.startTag(null, TAG_PACKAGE); - out.attribute(null, ATT_NAME, r.pkg); + out.startTag(null, TAG_PACKAGE); + out.attribute(null, ATT_NAME, r.pkg); + if (!notifPermissions.isEmpty()) { + Pair app = new Pair(r.uid, r.pkg); + out.attributeInt(null, ATT_IMPORTANCE, notifPermissions.get(app) + ? IMPORTANCE_DEFAULT + : IMPORTANCE_NONE); + notifPermissions.remove(app); + } else { if (r.importance != DEFAULT_IMPORTANCE) { out.attributeInt(null, ATT_IMPORTANCE, r.importance); } - if (r.priority != DEFAULT_PRIORITY) { - out.attributeInt(null, ATT_PRIORITY, r.priority); - } - if (r.visibility != DEFAULT_VISIBILITY) { - out.attributeInt(null, ATT_VISIBILITY, r.visibility); - } - if (r.bubblePreference != DEFAULT_BUBBLE_PREFERENCE) { - out.attributeInt(null, ATT_ALLOW_BUBBLE, r.bubblePreference); - } - out.attributeBoolean(null, ATT_SHOW_BADGE, r.showBadge); - out.attributeInt(null, ATT_APP_USER_LOCKED_FIELDS, - r.lockedAppFields); - out.attributeBoolean(null, ATT_SENT_INVALID_MESSAGE, - r.hasSentInvalidMessage); - out.attributeBoolean(null, ATT_SENT_VALID_MESSAGE, - r.hasSentValidMessage); - out.attributeBoolean(null, ATT_USER_DEMOTED_INVALID_MSG_APP, - r.userDemotedMsgApp); - - if (!forBackup) { - out.attributeInt(null, ATT_UID, r.uid); - } - - if (r.delegate != null) { - out.startTag(null, TAG_DELEGATE); - - out.attribute(null, ATT_NAME, r.delegate.mPkg); - out.attributeInt(null, ATT_UID, r.delegate.mUid); - if (r.delegate.mEnabled != Delegate.DEFAULT_ENABLED) { - out.attributeBoolean(null, ATT_ENABLED, r.delegate.mEnabled); - } - if (r.delegate.mUserAllowed != Delegate.DEFAULT_USER_ALLOWED) { - out.attributeBoolean(null, ATT_USER_ALLOWED, r.delegate.mUserAllowed); - } - out.endTag(null, TAG_DELEGATE); - } - - for (NotificationChannelGroup group : r.groups.values()) { - group.writeXml(out); - } - - for (NotificationChannel channel : r.channels.values()) { - if (forBackup) { - if (!channel.isDeleted()) { - channel.writeXmlForBackup(out, mContext); - } - } else { - channel.writeXml(out); - } - } - - out.endTag(null, TAG_PACKAGE); } + if (r.priority != DEFAULT_PRIORITY) { + out.attributeInt(null, ATT_PRIORITY, r.priority); + } + if (r.visibility != DEFAULT_VISIBILITY) { + out.attributeInt(null, ATT_VISIBILITY, r.visibility); + } + if (r.bubblePreference != DEFAULT_BUBBLE_PREFERENCE) { + out.attributeInt(null, ATT_ALLOW_BUBBLE, r.bubblePreference); + } + out.attributeBoolean(null, ATT_SHOW_BADGE, r.showBadge); + out.attributeInt(null, ATT_APP_USER_LOCKED_FIELDS, + r.lockedAppFields); + out.attributeBoolean(null, ATT_SENT_INVALID_MESSAGE, + r.hasSentInvalidMessage); + out.attributeBoolean(null, ATT_SENT_VALID_MESSAGE, + r.hasSentValidMessage); + out.attributeBoolean(null, ATT_USER_DEMOTED_INVALID_MSG_APP, + r.userDemotedMsgApp); + + if (!forBackup) { + out.attributeInt(null, ATT_UID, r.uid); + } + + if (r.delegate != null) { + out.startTag(null, TAG_DELEGATE); + + out.attribute(null, ATT_NAME, r.delegate.mPkg); + out.attributeInt(null, ATT_UID, r.delegate.mUid); + if (r.delegate.mEnabled != Delegate.DEFAULT_ENABLED) { + out.attributeBoolean(null, ATT_ENABLED, r.delegate.mEnabled); + } + if (r.delegate.mUserAllowed != Delegate.DEFAULT_USER_ALLOWED) { + out.attributeBoolean(null, ATT_USER_ALLOWED, r.delegate.mUserAllowed); + } + out.endTag(null, TAG_DELEGATE); + } + + for (NotificationChannelGroup group : r.groups.values()) { + group.writeXml(out); + } + + for (NotificationChannel channel : r.channels.values()) { + if (forBackup) { + if (!channel.isDeleted()) { + channel.writeXmlForBackup(out, mContext); + } + } else { + channel.writeXml(out); + } + } + + out.endTag(null, TAG_PACKAGE); + } + } + // Some apps have permissions set but don't have expanded notification settings + if (!notifPermissions.isEmpty()) { + for (Pair app : notifPermissions.keySet()) { + out.startTag(null, TAG_PACKAGE); + out.attribute(null, ATT_NAME, app.second); + out.attributeInt(null, ATT_IMPORTANCE, + notifPermissions.get(app) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE); + out.endTag(null, TAG_PACKAGE); } } out.endTag(null, TAG_RANKING); @@ -1904,7 +1933,7 @@ public class PreferencesHelper implements RankingConfig { public void dump(PrintWriter pw, String prefix, @NonNull NotificationManagerService.DumpFilter filter) { pw.print(prefix); - pw.println("per-package config:"); + pw.println("per-package config version: " + XML_VERSION); pw.println("PackagePreferences:"); synchronized (mPackagePreferences) { @@ -1924,7 +1953,7 @@ public class PreferencesHelper implements RankingConfig { mRestoredWithoutUids); } - private static void dumpPackagePreferencesLocked(PrintWriter pw, String prefix, + private void dumpPackagePreferencesLocked(PrintWriter pw, String prefix, @NonNull NotificationManagerService.DumpFilter filter, ArrayMap packagePreferences) { final int N = packagePreferences.size(); @@ -1937,7 +1966,7 @@ public class PreferencesHelper implements RankingConfig { pw.print(" ("); pw.print(r.uid == UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid)); pw.print(')'); - if (r.importance != DEFAULT_IMPORTANCE) { + if (!mPermissionHelper.isMigrationEnabled() && r.importance != DEFAULT_IMPORTANCE) { pw.print(" importance="); pw.print(NotificationListenerService.Ranking.importanceToString(r.importance)); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java index 84bd93f91c677..4cdae88604093 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java @@ -38,10 +38,11 @@ import android.Manifest; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.permission.IPermissionManager; import android.test.suitebuilder.annotation.SmallTest; +import android.util.Pair; +import android.util.Slog; import androidx.test.runner.AndroidJUnit4; @@ -50,6 +51,7 @@ import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import org.junit.Before; @@ -66,6 +68,7 @@ import java.lang.reflect.Type; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; @SmallTest @RunWith(AndroidJUnit4.class) @@ -107,6 +110,9 @@ public class PermissionHelperTest extends UiServiceTestCase { args.add(false); } else if (type.getTypeName().equals("int")) { args.add(1); + } else if (type.getTypeName().equals( + "com.android.server.notification.PermissionHelper$PackagePermission")) { + args.add(null); } } try { @@ -158,15 +164,16 @@ public class PermissionHelperTest extends UiServiceTestCase { aiSecond.uid = 2; second.applicationInfo = aiSecond; - Map expected = ImmutableMap.of(1, "first", 2, "second"); + Set> expected = + ImmutableSet.of(new Pair(1, "first"), new Pair(2, "second")); ParceledListSlice infos = new ParceledListSlice<>( ImmutableList.of(notThis, none, first, second)); when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS), anyInt())).thenReturn(infos); - Map actual = mPermissionHelper.getAppsRequestingPermission(0); + Set> actual = mPermissionHelper.getAppsRequestingPermission(0); - assertThat(actual).containsExactlyEntriesIn(expected); + assertThat(actual).containsExactlyElementsIn(expected); } @Test @@ -202,10 +209,11 @@ public class PermissionHelperTest extends UiServiceTestCase { eq(new String[] {Manifest.permission.POST_NOTIFICATIONS}), anyInt(), eq(userId))) .thenReturn(infos); - Map expected = ImmutableMap.of(1, "first", 2, "second"); + Set> expected = + ImmutableSet.of(new Pair(1, "first"), new Pair(2, "second")); assertThat(mPermissionHelper.getAppsGrantedPermission(userId)) - .containsExactlyEntriesIn(expected); + .containsExactlyElementsIn(expected); } @Test @@ -268,4 +276,49 @@ public class PermissionHelperTest extends UiServiceTestCase { assertThat(mPermissionHelper.isPermissionFixed("pkg", 0)).isTrue(); } + + @Test + public void testGetNotificationPermissionValues() throws Exception { + int userId = 1; + PackageInfo first = new PackageInfo(); + first.packageName = "first"; + first.requestedPermissions = + new String[] {"something else", Manifest.permission.POST_NOTIFICATIONS}; + ApplicationInfo aiFirst = new ApplicationInfo(); + aiFirst.uid = 1; + first.applicationInfo = aiFirst; + + PackageInfo second = new PackageInfo(); + second.packageName = "second"; + second.requestedPermissions = new String[] {Manifest.permission.POST_NOTIFICATIONS}; + ApplicationInfo aiSecond = new ApplicationInfo(); + aiSecond.uid = 2; + second.applicationInfo = aiSecond; + + PackageInfo third = new PackageInfo(); + third.packageName = "third"; + third.requestedPermissions = new String[] {Manifest.permission.POST_NOTIFICATIONS}; + ApplicationInfo aiThird = new ApplicationInfo(); + aiThird.uid = 3; + third.applicationInfo = aiThird; + + ParceledListSlice infos = new ParceledListSlice<>( + ImmutableList.of(first, second)); + when(mPackageManager.getPackagesHoldingPermissions( + eq(new String[] {Manifest.permission.POST_NOTIFICATIONS}), anyInt(), eq(userId))) + .thenReturn(infos); + ParceledListSlice requesting = new ParceledListSlice<>( + ImmutableList.of(first, second, third)); + when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS), anyInt())) + .thenReturn(requesting); + + Map, Boolean> expected = ImmutableMap.of(new Pair(1, "first"), true, + new Pair(2, "second"), true, + new Pair(3, "third"), false); + + Map, Boolean> actual = + mPermissionHelper.getNotificationPermissionValues(userId); + + assertThat(actual).containsExactlyEntriesIn(expected); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 81e5b9fe24bc0..b89a94a382216 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -29,6 +29,8 @@ import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MAX; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; +import static android.media.AudioAttributes.CONTENT_TYPE_SONIFICATION; +import static android.media.AudioAttributes.USAGE_NOTIFICATION; import static android.util.StatsLog.ANNOTATION_ID_IS_UID; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES; @@ -102,6 +104,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.IntArray; import android.util.Pair; +import android.util.Slog; import android.util.StatsEvent; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; @@ -111,6 +114,9 @@ import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.server.UiServiceTestCase; +import com.android.server.notification.PermissionHelper.PackagePermission; + +import com.google.common.collect.ImmutableMap; import org.json.JSONArray; import org.json.JSONObject; @@ -581,6 +587,394 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertTrue(foundChannel2Group); } + @Test + public void testReadXml_oldXml_migrates() throws Exception { + when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, + mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); + + String xml = "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n"; + NotificationChannel idn = new NotificationChannel("idn", "name", IMPORTANCE_LOW); + idn.setSound(null, new AudioAttributes.Builder() + .setUsage(USAGE_NOTIFICATION) + .setContentType(CONTENT_TYPE_SONIFICATION) + .setFlags(0) + .build()); + idn.setShowBadge(false); + NotificationChannel ido = new NotificationChannel("ido", "name2", IMPORTANCE_LOW); + ido.setShowBadge(true); + ido.setSound(null, new AudioAttributes.Builder() + .setUsage(USAGE_NOTIFICATION) + .setContentType(CONTENT_TYPE_SONIFICATION) + .setFlags(0) + .build()); + NotificationChannel idp = new NotificationChannel("idp", "name3", IMPORTANCE_HIGH); + idp.lockFields(2); + idp.setSound(null, new AudioAttributes.Builder() + .setUsage(USAGE_NOTIFICATION) + .setContentType(CONTENT_TYPE_SONIFICATION) + .setFlags(0) + .build()); + + // Notifications enabled, not user set + PackagePermission nMr1Expected = new PackagePermission(PKG_N_MR1, 0, true, false); + // Notifications not enabled, so user set + PackagePermission oExpected = new PackagePermission(PKG_O, 0, false, true); + // Notifications enabled, user set b/c channel modified + PackagePermission pExpected = new PackagePermission(PKG_P, 0, true, true); + + loadByteArrayXml(xml.getBytes(), true, UserHandle.USER_SYSTEM); + + assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1)); + + assertEquals(idn, mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, idn.getId(), false)); + compareChannels(ido, mHelper.getNotificationChannel(PKG_O, UID_O, ido.getId(), false)); + compareChannels(idp, mHelper.getNotificationChannel(PKG_P, UID_P, idp.getId(), false)); + + verify(mPermissionHelper).setNotificationPermission(nMr1Expected); + verify(mPermissionHelper).setNotificationPermission(oExpected); + verify(mPermissionHelper).setNotificationPermission(pExpected); + } + + @Test + public void testReadXml_newXml_noMigration() throws Exception { + when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, + mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); + + String xml = "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n"; + NotificationChannel idn = new NotificationChannel("idn", "name", IMPORTANCE_LOW); + idn.setSound(null, new AudioAttributes.Builder() + .setUsage(USAGE_NOTIFICATION) + .setContentType(CONTENT_TYPE_SONIFICATION) + .setFlags(0) + .build()); + idn.setShowBadge(false); + NotificationChannel ido = new NotificationChannel("ido", "name2", IMPORTANCE_LOW); + ido.setShowBadge(true); + ido.setSound(null, new AudioAttributes.Builder() + .setUsage(USAGE_NOTIFICATION) + .setContentType(CONTENT_TYPE_SONIFICATION) + .setFlags(0) + .build()); + NotificationChannel idp = new NotificationChannel("idp", "name3", IMPORTANCE_HIGH); + idp.lockFields(2); + idp.setSound(null, new AudioAttributes.Builder() + .setUsage(USAGE_NOTIFICATION) + .setContentType(CONTENT_TYPE_SONIFICATION) + .setFlags(0) + .build()); + + loadByteArrayXml(xml.getBytes(), true, UserHandle.USER_SYSTEM); + + assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1)); + + assertEquals(idn, mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, idn.getId(), false)); + compareChannels(ido, mHelper.getNotificationChannel(PKG_O, UID_O, ido.getId(), false)); + compareChannels(idp, mHelper.getNotificationChannel(PKG_P, UID_P, idp.getId(), false)); + + verify(mPermissionHelper, never()).setNotificationPermission(any()); + } + + @Test + public void testChannelXmlForNonBackup_postMigration() throws Exception { + when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, + mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); + + ArrayMap, Boolean> appPermissions = new ArrayMap<>(); + appPermissions.put(new Pair(1, "first"), true); + appPermissions.put(new Pair(3, "third"), false); + appPermissions.put(new Pair(UID_P, PKG_P), true); + appPermissions.put(new Pair(UID_O, PKG_O), false); + appPermissions.put(new Pair(UID_N_MR1, PKG_N_MR1), true); + + when(mPermissionHelper.getNotificationPermissionValues(UserHandle.USER_SYSTEM)) + .thenReturn(appPermissions); + + NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye"); + NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello"); + NotificationChannel channel1 = + new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH); + channel1.setSound(null, null); + NotificationChannel channel2 = + new NotificationChannel("id2", "name2", IMPORTANCE_LOW); + channel2.setDescription("descriptions for all"); + channel2.setSound(null, null); + channel2.enableLights(true); + channel2.setBypassDnd(true); + channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET); + channel2.enableVibration(false); + channel2.setGroup(ncg.getId()); + channel2.setLightColor(Color.BLUE); + NotificationChannel channel3 = new NotificationChannel("id3", "NAM3", IMPORTANCE_HIGH); + channel3.enableVibration(true); + + mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true); + mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg2, true); + mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false); + mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, false, false); + mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel3, false, false); + mHelper.createNotificationChannel(PKG_O, UID_O, getChannel(), true, false); + + mHelper.setShowBadge(PKG_N_MR1, UID_N_MR1, true); + mHelper.setInvalidMessageSent(PKG_P, UID_P); + mHelper.setValidMessageSent(PKG_P, UID_P); + mHelper.setInvalidMsgAppDemoted(PKG_P, UID_P, true); + + ByteArrayOutputStream baos = writeXmlAndPurge( + PKG_N_MR1, UID_N_MR1, false, UserHandle.USER_SYSTEM); + String expected = "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + ""; + assertThat(baos.toString()).contains(expected); + } + + @Test + public void testChannelXmlForBackup_postMigration() throws Exception { + when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, + mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); + + ArrayMap, Boolean> appPermissions = new ArrayMap<>(); + appPermissions.put(new Pair(1, "first"), true); + appPermissions.put(new Pair(3, "third"), false); + appPermissions.put(new Pair(UID_P, PKG_P), true); + appPermissions.put(new Pair(UID_O, PKG_O), false); + appPermissions.put(new Pair(UID_N_MR1, PKG_N_MR1), true); + + when(mPermissionHelper.getNotificationPermissionValues(UserHandle.USER_SYSTEM)) + .thenReturn(appPermissions); + + NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye"); + NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello"); + NotificationChannel channel1 = + new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH); + channel1.setSound(null, null); + NotificationChannel channel2 = + new NotificationChannel("id2", "name2", IMPORTANCE_LOW); + channel2.setDescription("descriptions for all"); + channel2.setSound(null, null); + channel2.enableLights(true); + channel2.setBypassDnd(true); + channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET); + channel2.enableVibration(false); + channel2.setGroup(ncg.getId()); + channel2.setLightColor(Color.BLUE); + NotificationChannel channel3 = new NotificationChannel("id3", "NAM3", IMPORTANCE_HIGH); + channel3.enableVibration(true); + + mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true); + mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg2, true); + mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false); + mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, false, false); + mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel3, false, false); + mHelper.createNotificationChannel(PKG_O, UID_O, getChannel(), true, false); + + mHelper.setShowBadge(PKG_N_MR1, UID_N_MR1, true); + mHelper.setInvalidMessageSent(PKG_P, UID_P); + mHelper.setValidMessageSent(PKG_P, UID_P); + mHelper.setInvalidMsgAppDemoted(PKG_P, UID_P, true); + + ByteArrayOutputStream baos = writeXmlAndPurge( + PKG_N_MR1, UID_N_MR1, true, UserHandle.USER_SYSTEM); + String expected = "\n" + // Importance 0 because off in permissionhelper + + "\n" + + "\n" + + "\n" + // Importance default because on in permission helper + + "\n" + // Importance default because on in permission helper + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + // Packages that exist solely in permissionhelper + + "\n" + + "\n" + + ""; + assertThat(baos.toString()).contains(expected); + } + + @Test + public void testChannelXmlForBackup_postMigration_noExternal() throws Exception { + when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, + mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); + + ArrayMap, Boolean> appPermissions = new ArrayMap<>(); + appPermissions.put(new Pair(UID_P, PKG_P), true); + appPermissions.put(new Pair(UID_O, PKG_O), false); + when(mPermissionHelper.getNotificationPermissionValues(UserHandle.USER_SYSTEM)) + .thenReturn(appPermissions); + + NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye"); + NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello"); + NotificationChannel channel1 = + new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH); + channel1.setSound(null, null); + NotificationChannel channel2 = + new NotificationChannel("id2", "name2", IMPORTANCE_LOW); + channel2.setDescription("descriptions for all"); + channel2.setSound(null, null); + channel2.enableLights(true); + channel2.setBypassDnd(true); + channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET); + channel2.enableVibration(false); + channel2.setGroup(ncg.getId()); + channel2.setLightColor(Color.BLUE); + NotificationChannel channel3 = new NotificationChannel("id3", "NAM3", IMPORTANCE_HIGH); + channel3.enableVibration(true); + + mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true); + mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg2, true); + mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false); + mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, false, false); + mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel3, false, false); + mHelper.createNotificationChannel(PKG_O, UID_O, getChannel(), true, false); + + mHelper.setShowBadge(PKG_N_MR1, UID_N_MR1, true); + mHelper.setInvalidMessageSent(PKG_P, UID_P); + mHelper.setValidMessageSent(PKG_P, UID_P); + mHelper.setInvalidMsgAppDemoted(PKG_P, UID_P, true); + + ByteArrayOutputStream baos = writeXmlAndPurge( + PKG_N_MR1, UID_N_MR1, true, UserHandle.USER_SYSTEM); + String expected = "\n" + // Importance 0 because off in permissionhelper + + "\n" + + "\n" + + "\n" + // Importance default because on in permission helper + + "\n" + // Importance missing because missing from permission helper + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + ""; + assertThat(baos.toString()).contains(expected); + } + + @Test + public void testChannelXmlForBackup_postMigration_noLocalSettings() throws Exception { + when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, + mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); + + ArrayMap, Boolean> appPermissions = new ArrayMap<>(); + appPermissions.put(new Pair(1, "first"), true); + appPermissions.put(new Pair(3, "third"), false); + appPermissions.put(new Pair(UID_P, PKG_P), true); + appPermissions.put(new Pair(UID_O, PKG_O), false); + appPermissions.put(new Pair(UID_N_MR1, PKG_N_MR1), true); + + when(mPermissionHelper.getNotificationPermissionValues(UserHandle.USER_SYSTEM)) + .thenReturn(appPermissions); + + ByteArrayOutputStream baos = writeXmlAndPurge( + PKG_N_MR1, UID_N_MR1, true, UserHandle.USER_SYSTEM); + String expected = "\n" + // Packages that exist solely in permissionhelper + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + ""; + assertThat(baos.toString()).contains(expected); + } + @Test public void testBackupXml_backupCanonicalizedSoundUri() throws Exception { NotificationChannel channel =