Merge "Handle upgrade and B&R compatibilty"

This commit is contained in:
Julia Reynolds
2021-11-04 12:27:23 +00:00
committed by Android (Google) Code Review
4 changed files with 637 additions and 99 deletions

View File

@@ -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<Integer, String> getAppsRequestingPermission(int userId) {
Set<Pair<Integer, String>> getAppsRequestingPermission(int userId) {
assertFlag();
Map<Integer, String> requested = new HashMap<>();
Set<Pair<Integer, String>> requested = new HashSet<>();
List<PackageInfo> 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<Integer, String> getAppsGrantedPermission(int userId) {
Set<Pair<Integer, String>> getAppsGrantedPermission(int userId) {
assertFlag();
Map<Integer, String> granted = new HashMap<>();
Set<Pair<Integer, String>> granted = new HashSet<>();
ParceledListSlice<PackageInfo> 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<Pair<Integer, String>, Boolean> getNotificationPermissionValues(
int userId) {
assertFlag();
ArrayMap<Pair<Integer, String>, Boolean> notifPermissions = new ArrayMap<>();
Set<Pair<Integer, String>> allRequestingUids = getAppsRequestingPermission(userId);
Set<Pair<Integer, String>> allApprovedUids = getAppsGrantedPermission(userId);
for (Pair<Integer, String> 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 +
'}';
}
}
}

View File

@@ -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<PermissionHelper.PackagePermission> 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<Pair<Integer, String>, 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<Integer, String> 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<Integer, String> 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<String, PackagePreferences> 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));
}

View File

@@ -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<Integer, String> expected = ImmutableMap.of(1, "first", 2, "second");
Set<Pair<Integer, String>> expected =
ImmutableSet.of(new Pair(1, "first"), new Pair(2, "second"));
ParceledListSlice<PackageInfo> infos = new ParceledListSlice<>(
ImmutableList.of(notThis, none, first, second));
when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS), anyInt())).thenReturn(infos);
Map<Integer, String> actual = mPermissionHelper.getAppsRequestingPermission(0);
Set<Pair<Integer, String>> 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<Integer, String> expected = ImmutableMap.of(1, "first", 2, "second");
Set<Pair<Integer, String>> 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<PackageInfo> infos = new ParceledListSlice<>(
ImmutableList.of(first, second));
when(mPackageManager.getPackagesHoldingPermissions(
eq(new String[] {Manifest.permission.POST_NOTIFICATIONS}), anyInt(), eq(userId)))
.thenReturn(infos);
ParceledListSlice<PackageInfo> requesting = new ParceledListSlice<>(
ImmutableList.of(first, second, third));
when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS), anyInt()))
.thenReturn(requesting);
Map<Pair<Integer, String>, Boolean> expected = ImmutableMap.of(new Pair(1, "first"), true,
new Pair(2, "second"), true,
new Pair(3, "third"), false);
Map<Pair<Integer, String>, Boolean> actual =
mPermissionHelper.getNotificationPermissionValues(userId);
assertThat(actual).containsExactlyEntriesIn(expected);
}
}

View File

@@ -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 = "<ranking version=\"2\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
+ "<channel id=\"idn\" name=\"name\" importance=\"2\" />\n"
+ "<channel id=\"miscellaneous\" name=\"Uncategorized\" />\n"
+ "</package>\n"
+ "<package name=\"" + PKG_O + "\" importance=\"0\">\n"
+ "<channel id=\"ido\" name=\"name2\" importance=\"2\" show_badge=\"true\"/>\n"
+ "</package>\n"
+ "<package name=\"" + PKG_P + "\" importance=\"2\">\n"
+ "<channel id=\"idp\" name=\"name3\" importance=\"4\" locked=\"2\" />\n"
+ "</package>\n"
+ "</ranking>\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 = "<ranking version=\"3\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
+ "<channel id=\"idn\" name=\"name\" importance=\"2\"/>\n"
+ "<channel id=\"miscellaneous\" name=\"Uncategorized\" />\n"
+ "</package>\n"
+ "<package name=\"" + PKG_O + "\" >\n"
+ "<channel id=\"ido\" name=\"name2\" importance=\"2\" show_badge=\"true\"/>\n"
+ "</package>\n"
+ "<package name=\"" + PKG_P + "\" >\n"
+ "<channel id=\"idp\" name=\"name3\" importance=\"4\" locked=\"2\" />\n"
+ "</package>\n"
+ "</ranking>\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<Pair<Integer, String>, 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 = "<ranking version=\"3\">\n"
+ "<package name=\"com.example.o\" show_badge=\"true\" "
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
+ "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" uid=\"1111\">\n"
+ "<channel id=\"id\" name=\"name\" importance=\"2\" "
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" show_badge=\"true\" orig_imp=\"2\" />\n"
+ "</package>\n"
+ "<package name=\"com.example.p\" show_badge=\"true\" "
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"true\" sent_valid_msg=\"true\""
+ " user_demote_msg_app=\"true\" uid=\"2222\" />\n"
+ "<package name=\"com.example.n_mr1\" show_badge=\"true\" "
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
+ "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" uid=\"0\">\n"
+ "<channelGroup id=\"1\" name=\"bye\" blocked=\"false\" locked=\"0\" />\n"
+ "<channelGroup id=\"2\" name=\"hello\" blocked=\"false\" locked=\"0\" />\n"
+ "<channel id=\"id1\" name=\"name1\" importance=\"4\" show_badge=\"true\" "
+ "orig_imp=\"4\" />\n"
+ "<channel id=\"id2\" name=\"name2\" desc=\"descriptions for all\" "
+ "importance=\"2\" priority=\"2\" visibility=\"-1\" lights=\"true\" "
+ "light_color=\"-16776961\" show_badge=\"true\" group=\"1\" orig_imp=\"2\" />\n"
+ "<channel id=\"id3\" name=\"NAM3\" importance=\"4\" "
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" vibration_enabled=\"true\" show_badge=\"true\" "
+ "orig_imp=\"4\" />\n"
+ "<channel id=\"miscellaneous\" name=\"Uncategorized\" "
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+ "</package>\n"
+ "</ranking>";
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<Pair<Integer, String>, 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 = "<ranking version=\"3\">\n"
// Importance 0 because off in permissionhelper
+ "<package name=\"com.example.o\" importance=\"0\" show_badge=\"true\" "
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
+ "sent_valid_msg=\"false\" user_demote_msg_app=\"false\">\n"
+ "<channel id=\"id\" name=\"name\" importance=\"2\" "
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" show_badge=\"true\" orig_imp=\"2\" />\n"
+ "</package>\n"
// Importance default because on in permission helper
+ "<package name=\"com.example.p\" importance=\"3\" show_badge=\"true\" "
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"true\" sent_valid_msg=\"true\""
+ " user_demote_msg_app=\"true\" />\n"
// Importance default because on in permission helper
+ "<package name=\"com.example.n_mr1\" importance=\"3\" show_badge=\"true\" "
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
+ "sent_valid_msg=\"false\" user_demote_msg_app=\"false\">\n"
+ "<channelGroup id=\"1\" name=\"bye\" blocked=\"false\" locked=\"0\" />\n"
+ "<channelGroup id=\"2\" name=\"hello\" blocked=\"false\" locked=\"0\" />\n"
+ "<channel id=\"id1\" name=\"name1\" importance=\"4\" show_badge=\"true\" "
+ "orig_imp=\"4\" />\n"
+ "<channel id=\"id2\" name=\"name2\" desc=\"descriptions for all\" "
+ "importance=\"2\" priority=\"2\" visibility=\"-1\" lights=\"true\" "
+ "light_color=\"-16776961\" show_badge=\"true\" group=\"1\" orig_imp=\"2\" />\n"
+ "<channel id=\"id3\" name=\"NAM3\" importance=\"4\" "
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" vibration_enabled=\"true\" show_badge=\"true\" "
+ "orig_imp=\"4\" />\n"
+ "<channel id=\"miscellaneous\" name=\"Uncategorized\" "
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+ "</package>\n"
// Packages that exist solely in permissionhelper
+ "<package name=\"first\" importance=\"3\" />\n"
+ "<package name=\"third\" importance=\"0\" />\n"
+ "</ranking>";
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<Pair<Integer, String>, 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 = "<ranking version=\"3\">\n"
// Importance 0 because off in permissionhelper
+ "<package name=\"com.example.o\" importance=\"0\" show_badge=\"true\" "
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
+ "sent_valid_msg=\"false\" user_demote_msg_app=\"false\">\n"
+ "<channel id=\"id\" name=\"name\" importance=\"2\" "
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" show_badge=\"true\" orig_imp=\"2\" />\n"
+ "</package>\n"
// Importance default because on in permission helper
+ "<package name=\"com.example.p\" importance=\"3\" show_badge=\"true\" "
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"true\" sent_valid_msg=\"true\""
+ " user_demote_msg_app=\"true\" />\n"
// Importance missing because missing from permission helper
+ "<package name=\"com.example.n_mr1\" show_badge=\"true\" "
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
+ "sent_valid_msg=\"false\" user_demote_msg_app=\"false\">\n"
+ "<channelGroup id=\"1\" name=\"bye\" blocked=\"false\" locked=\"0\" />\n"
+ "<channelGroup id=\"2\" name=\"hello\" blocked=\"false\" locked=\"0\" />\n"
+ "<channel id=\"id1\" name=\"name1\" importance=\"4\" show_badge=\"true\" "
+ "orig_imp=\"4\" />\n"
+ "<channel id=\"id2\" name=\"name2\" desc=\"descriptions for all\" "
+ "importance=\"2\" priority=\"2\" visibility=\"-1\" lights=\"true\" "
+ "light_color=\"-16776961\" show_badge=\"true\" group=\"1\" orig_imp=\"2\" />\n"
+ "<channel id=\"id3\" name=\"NAM3\" importance=\"4\" "
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" vibration_enabled=\"true\" show_badge=\"true\" "
+ "orig_imp=\"4\" />\n"
+ "<channel id=\"miscellaneous\" name=\"Uncategorized\" "
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+ "</package>\n"
+ "</ranking>";
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<Pair<Integer, String>, 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 = "<ranking version=\"3\">\n"
// Packages that exist solely in permissionhelper
+ "<package name=\"" + PKG_P + "\" importance=\"3\" />\n"
+ "<package name=\"" + PKG_O + "\" importance=\"0\" />\n"
+ "<package name=\"" + PKG_N_MR1 + "\" importance=\"3\" />\n"
+ "<package name=\"first\" importance=\"3\" />\n"
+ "<package name=\"third\" importance=\"0\" />\n"
+ "</ranking>";
assertThat(baos.toString()).contains(expected);
}
@Test
public void testBackupXml_backupCanonicalizedSoundUri() throws Exception {
NotificationChannel channel =