/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.server.notification;

import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
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 com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT;

import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.fail;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.content.ContentProvider;
import android.content.Context;
import android.content.IContentProvider;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.content.res.Resources;
import android.graphics.Color;
import android.media.AudioAttributes;
import android.net.Uri;
import android.os.Build;
import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.provider.Settings.Secure;

import android.test.suitebuilder.annotation.SmallTest;
import android.testing.TestableContentResolver;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Pair;
import android.util.Xml;

import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;

import com.android.internal.util.FastXmlSerializer;
import com.android.server.UiServiceTestCase;

import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;

@SmallTest
@RunWith(AndroidJUnit4.class)
public class PreferencesHelperTest extends UiServiceTestCase {
    private static final int UID_N_MR1 = 0;
    private static final UserHandle USER = UserHandle.of(0);
    private static final int UID_O = 1111;
    private static final String SYSTEM_PKG = "android";
    private static final int SYSTEM_UID = 1000;
    private static final UserHandle USER2 = UserHandle.of(10);
    private static final String TEST_CHANNEL_ID = "test_channel_id";
    private static final String TEST_AUTHORITY = "test";
    private static final Uri SOUND_URI =
            Uri.parse("content://" + TEST_AUTHORITY + "/internal/audio/media/10");
    private static final Uri CANONICAL_SOUND_URI =
            Uri.parse("content://" + TEST_AUTHORITY
                    + "/internal/audio/media/10?title=Test&canonical=1");

    @Mock NotificationUsageStats mUsageStats;
    @Mock RankingHandler mHandler;
    @Mock PackageManager mPm;
    @Mock IContentProvider mTestIContentProvider;
    @Mock Context mContext;
    @Mock ZenModeHelper mMockZenModeHelper;

    private NotificationManager.Policy mTestNotificationPolicy;

    private PreferencesHelper mHelper;
    private AudioAttributes mAudioAttributes;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);

        final ApplicationInfo legacy = new ApplicationInfo();
        legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
        final ApplicationInfo upgrade = new ApplicationInfo();
        upgrade.targetSdkVersion = Build.VERSION_CODES.O;
        when(mPm.getApplicationInfoAsUser(eq(PKG_N_MR1), anyInt(), anyInt())).thenReturn(legacy);
        when(mPm.getApplicationInfoAsUser(eq(PKG_O), anyInt(), anyInt())).thenReturn(upgrade);
        when(mPm.getApplicationInfoAsUser(eq(SYSTEM_PKG), anyInt(), anyInt())).thenReturn(upgrade);
        when(mPm.getPackageUidAsUser(eq(PKG_N_MR1), anyInt())).thenReturn(UID_N_MR1);
        when(mPm.getPackageUidAsUser(eq(PKG_O), anyInt())).thenReturn(UID_O);
        when(mPm.getPackageUidAsUser(eq(SYSTEM_PKG), anyInt())).thenReturn(SYSTEM_UID);
        PackageInfo info = mock(PackageInfo.class);
        info.signatures = new Signature[] {mock(Signature.class)};
        when(mPm.getPackageInfoAsUser(eq(SYSTEM_PKG), anyInt(), anyInt())).thenReturn(info);
        when(mPm.getPackageInfoAsUser(eq(PKG_N_MR1), anyInt(), anyInt()))
                .thenReturn(mock(PackageInfo.class));
        when(mContext.getResources()).thenReturn(
                InstrumentationRegistry.getContext().getResources());
        when(mContext.getContentResolver()).thenReturn(
                InstrumentationRegistry.getContext().getContentResolver());
        when(mContext.getPackageManager()).thenReturn(mPm);
        when(mContext.getApplicationInfo()).thenReturn(legacy);
        // most tests assume badging is enabled
        TestableContentResolver contentResolver = getContext().getContentResolver();
        contentResolver.setFallbackToExisting(false);
        Secure.putIntForUser(contentResolver,
                Secure.NOTIFICATION_BADGING, 1, UserHandle.getUserId(UID_N_MR1));
        Global.putInt(contentResolver, Global.NOTIFICATION_BUBBLES, 1);

        ContentProvider testContentProvider = mock(ContentProvider.class);
        when(testContentProvider.getIContentProvider()).thenReturn(mTestIContentProvider);
        contentResolver.addProvider(TEST_AUTHORITY, testContentProvider);

        when(mTestIContentProvider.canonicalize(any(), eq(SOUND_URI)))
                .thenReturn(CANONICAL_SOUND_URI);
        when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
                .thenReturn(CANONICAL_SOUND_URI);
        when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
                .thenReturn(SOUND_URI);

        mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
                NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND);
        when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
        resetZenModeHelper();

        mAudioAttributes = new AudioAttributes.Builder()
                .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
                .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
                .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
                .build();
    }

    private ByteArrayOutputStream writeXmlAndPurge(
            String pkg, int uid, boolean forBackup, int userId, String... channelIds)
            throws Exception {
        XmlSerializer serializer = new FastXmlSerializer();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
        serializer.startDocument(null, true);
        mHelper.writeXml(serializer, forBackup, userId);
        serializer.endDocument();
        serializer.flush();
        for (String channelId : channelIds) {
            mHelper.permanentlyDeleteNotificationChannel(pkg, uid, channelId);
        }
        return baos;
    }

    private void loadStreamXml(ByteArrayOutputStream stream, boolean forRestore, int userId)
            throws Exception {
        loadByteArrayXml(stream.toByteArray(), forRestore, userId);
    }

    private void loadByteArrayXml(byte[] byteArray, boolean forRestore, int userId)
            throws Exception {
        XmlPullParser parser = Xml.newPullParser();
        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(byteArray)), null);
        parser.nextTag();
        mHelper.readXml(parser, forRestore, userId);
    }

    private void compareChannels(NotificationChannel expected, NotificationChannel actual) {
        assertEquals(expected.getId(), actual.getId());
        assertEquals(expected.getName(), actual.getName());
        assertEquals(expected.getDescription(), actual.getDescription());
        assertEquals(expected.shouldVibrate(), actual.shouldVibrate());
        assertEquals(expected.shouldShowLights(), actual.shouldShowLights());
        assertEquals(expected.getImportance(), actual.getImportance());
        assertEquals(expected.getLockscreenVisibility(), actual.getLockscreenVisibility());
        assertEquals(expected.getSound(), actual.getSound());
        assertEquals(expected.canBypassDnd(), actual.canBypassDnd());
        assertTrue(Arrays.equals(expected.getVibrationPattern(), actual.getVibrationPattern()));
        assertEquals(expected.getGroup(), actual.getGroup());
        assertEquals(expected.getAudioAttributes(), actual.getAudioAttributes());
        assertEquals(expected.getLightColor(), actual.getLightColor());
    }

    private void compareGroups(NotificationChannelGroup expected, NotificationChannelGroup actual) {
        assertEquals(expected.getId(), actual.getId());
        assertEquals(expected.getName(), actual.getName());
        assertEquals(expected.getDescription(), actual.getDescription());
        assertEquals(expected.isBlocked(), actual.isBlocked());
    }

    private NotificationChannel getChannel() {
        return new NotificationChannel("id", "name", IMPORTANCE_LOW);
    }

    private NotificationChannel findChannel(List<NotificationChannel> channels, String id) {
        for (NotificationChannel channel : channels) {
            if (channel.getId().equals(id)) {
                return channel;
            }
        }
        return null;
    }

    private void resetZenModeHelper() {
        reset(mMockZenModeHelper);
        when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
    }

    private void setUpPackageWithUid(String packageName, int uid) throws Exception {
        when(mPm.getApplicationInfoAsUser(eq(packageName), anyInt(), anyInt()))
                .thenReturn(new ApplicationInfo());
        when(mPm.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid);
    }

    @Test
    public void testWriteXml_onlyBackupsTargetUser() throws Exception {
        // Setup package notifications.
        String package0 = "test.package.user0";
        int uid0 = 1001;
        setUpPackageWithUid(package0, uid0);
        NotificationChannel channel0 = new NotificationChannel("id0", "name0", IMPORTANCE_HIGH);
        assertTrue(mHelper.createNotificationChannel(package0, uid0, channel0, true, false));

        String package10 = "test.package.user10";
        int uid10 = 1001001;
        setUpPackageWithUid(package10, uid10);
        NotificationChannel channel10 = new NotificationChannel("id10", "name10", IMPORTANCE_HIGH);
        assertTrue(mHelper.createNotificationChannel(package10, uid10, channel10, true, false));

        ByteArrayOutputStream baos = writeXmlAndPurge(package10, uid10, true, 10);

        // Reset state.
        mHelper.onPackagesChanged(true, 0, new String[] {package0}, new int[] {uid0});
        mHelper.onPackagesChanged(true, 10, new String[] {package10}, new int[] {uid10});

        // Parse backup data.
        loadStreamXml(baos, true, 0);
        loadStreamXml(baos, true, 10);

        assertEquals(
                channel10,
                mHelper.getNotificationChannel(package10, uid10, channel10.getId(), false));
        assertNull(mHelper.getNotificationChannel(package0, uid0, channel0.getId(), false));
    }

    @Test
    public void testReadXml_onlyRestoresTargetUser() throws Exception {
        // Setup package in user 0.
        String package0 = "test.package.user0";
        int uid0 = 1001;
        setUpPackageWithUid(package0, uid0);
        NotificationChannel channel0 = new NotificationChannel("id0", "name0", IMPORTANCE_HIGH);
        assertTrue(mHelper.createNotificationChannel(package0, uid0, channel0, true, false));

        ByteArrayOutputStream baos = writeXmlAndPurge(package0, uid0, true, 0);

        // Reset state.
        mHelper.onPackagesChanged(true, 0, new String[] {package0}, new int[] {uid0});

        // Restore should convert the uid according to the target user.
        int expectedUid = 1001001;
        setUpPackageWithUid(package0, expectedUid);
        // Parse backup data.
        loadStreamXml(baos, true, 10);

        assertEquals(
                channel0,
                mHelper.getNotificationChannel(package0, expectedUid, channel0.getId(), false));
        assertNull(mHelper.getNotificationChannel(package0, uid0, channel0.getId(), false));
    }

    @Test
    public void testChannelXml() throws Exception {
        NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
        ncg.setBlocked(true);
        ncg.setDescription("group desc");
        NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
        NotificationChannel channel1 =
                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
        NotificationChannel channel2 =
                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
        channel2.setDescription("descriptions for all");
        channel2.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
        channel2.enableLights(true);
        channel2.setBypassDnd(true);
        channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
        channel2.enableVibration(true);
        channel2.setGroup(ncg.getId());
        channel2.setVibrationPattern(new long[]{100, 67, 145, 156});
        channel2.setLightColor(Color.BLUE);

        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);
        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg2, true);
        assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false));
        assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, false, false));

        mHelper.setShowBadge(PKG_N_MR1, UID_N_MR1, true);
        mHelper.setAppImportanceLocked(PKG_N_MR1, UID_N_MR1);

        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, false,
                UserHandle.USER_ALL, channel1.getId(), channel2.getId(),
                NotificationChannel.DEFAULT_CHANNEL_ID);
        mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG_N_MR1}, new int[]{
                UID_N_MR1});

        loadStreamXml(baos, false, UserHandle.USER_ALL);

        assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
        assertTrue(mHelper.getIsAppImportanceLocked(PKG_N_MR1, UID_N_MR1));
        assertEquals(channel1,
                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1.getId(), false));
        compareChannels(channel2,
                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2.getId(), false));

        List<NotificationChannelGroup> actualGroups = mHelper.getNotificationChannelGroups(
                PKG_N_MR1, UID_N_MR1, false, true, false).getList();
        boolean foundNcg = false;
        for (NotificationChannelGroup actual : actualGroups) {
            if (ncg.getId().equals(actual.getId())) {
                foundNcg = true;
                compareGroups(ncg, actual);
            } else if (ncg2.getId().equals(actual.getId())) {
                compareGroups(ncg2, actual);
            }
        }
        assertTrue(foundNcg);

        boolean foundChannel2Group = false;
        for (NotificationChannelGroup actual : actualGroups) {
            if (channel2.getGroup().equals(actual.getChannels().get(0).getGroup())) {
                foundChannel2Group = true;
                break;
            }
        }
        assertTrue(foundChannel2Group);
    }

    @Test
    public void testChannelXmlForBackup() throws Exception {
        NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
        NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
        NotificationChannel channel1 =
                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
        NotificationChannel channel2 =
                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
        channel2.setDescription("descriptions for all");
        channel2.setSound(SOUND_URI, mAudioAttributes);
        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.setImportance(PKG_O, UID_O, IMPORTANCE_NONE);

        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, true,
                UserHandle.USER_SYSTEM, channel1.getId(), channel2.getId(), channel3.getId(),
                NotificationChannel.DEFAULT_CHANNEL_ID);
        mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG_N_MR1, PKG_O},
                new int[]{UID_N_MR1, UID_O});

        mHelper.setShowBadge(PKG_O, UID_O, true);

        loadStreamXml(baos, true, UserHandle.USER_SYSTEM);

        assertEquals(IMPORTANCE_NONE, mHelper.getImportance(PKG_O, UID_O));
        assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
        assertEquals(channel1,
                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1.getId(), false));
        compareChannels(channel2,
                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2.getId(), false));
        compareChannels(channel3,
                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel3.getId(), false));

        List<NotificationChannelGroup> actualGroups = mHelper.getNotificationChannelGroups(
                PKG_N_MR1, UID_N_MR1, false, true, false).getList();
        boolean foundNcg = false;
        for (NotificationChannelGroup actual : actualGroups) {
            if (ncg.getId().equals(actual.getId())) {
                foundNcg = true;
                compareGroups(ncg, actual);
            } else if (ncg2.getId().equals(actual.getId())) {
                compareGroups(ncg2, actual);
            }
        }
        assertTrue(foundNcg);

        boolean foundChannel2Group = false;
        for (NotificationChannelGroup actual : actualGroups) {
            if (channel2.getGroup().equals(actual.getChannels().get(0).getGroup())) {
                foundChannel2Group = true;
                break;
            }
        }
        assertTrue(foundChannel2Group);
    }

    @Test
    public void testBackupXml_backupCanonicalizedSoundUri() throws Exception {
        NotificationChannel channel =
                new NotificationChannel("id", "name", IMPORTANCE_LOW);
        channel.setSound(SOUND_URI, mAudioAttributes);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false);

        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, true,
                UserHandle.USER_SYSTEM, channel.getId());

        // Testing that in restore we are given the canonical version
        loadStreamXml(baos, true, UserHandle.USER_SYSTEM);
        verify(mTestIContentProvider).uncanonicalize(any(), eq(CANONICAL_SOUND_URI));
    }

    @Test
    public void testRestoreXml_withExistentCanonicalizedSoundUri() throws Exception {
        Uri localUri = Uri.parse("content://" + TEST_AUTHORITY + "/local/url");
        Uri canonicalBasedOnLocal = localUri.buildUpon()
                .appendQueryParameter("title", "Test")
                .appendQueryParameter("canonical", "1")
                .build();
        when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
                .thenReturn(canonicalBasedOnLocal);
        when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
                .thenReturn(localUri);
        when(mTestIContentProvider.uncanonicalize(any(), eq(canonicalBasedOnLocal)))
                .thenReturn(localUri);

        NotificationChannel channel =
                new NotificationChannel("id", "name", IMPORTANCE_LOW);
        channel.setSound(SOUND_URI, mAudioAttributes);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false);
        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, true,
                UserHandle.USER_SYSTEM, channel.getId());

        loadStreamXml(baos, true, UserHandle.USER_SYSTEM);

        NotificationChannel actualChannel = mHelper.getNotificationChannel(
                PKG_N_MR1, UID_N_MR1, channel.getId(), false);
        assertEquals(localUri, actualChannel.getSound());
    }

    @Test
    public void testRestoreXml_withNonExistentCanonicalizedSoundUri() throws Exception {
        Thread.sleep(3000);
        when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
                .thenReturn(null);
        when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
                .thenReturn(null);

        NotificationChannel channel =
                new NotificationChannel("id", "name", IMPORTANCE_LOW);
        channel.setSound(SOUND_URI, mAudioAttributes);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false);
        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, true,
                UserHandle.USER_SYSTEM, channel.getId());

        loadStreamXml(baos, true, UserHandle.USER_SYSTEM);

        NotificationChannel actualChannel = mHelper.getNotificationChannel(
                PKG_N_MR1, UID_N_MR1, channel.getId(), false);
        assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
    }


    /**
     * Although we don't make backups with uncanonicalized uris anymore, we used to, so we have to
     * handle its restore properly.
     */
    @Test
    public void testRestoreXml_withUncanonicalizedNonLocalSoundUri() throws Exception {
        // Not a local uncanonicalized uri, simulating that it fails to exist locally
        when(mTestIContentProvider.canonicalize(any(), eq(SOUND_URI))).thenReturn(null);
        String id = "id";
        String backupWithUncanonicalizedSoundUri = "<ranking version=\"1\">\n"
                + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
                + "<channel id=\"" + id + "\" name=\"name\" importance=\"2\" "
                + "sound=\"" + SOUND_URI + "\" "
                + "usage=\"6\" content_type=\"0\" flags=\"1\" show_badge=\"true\" />\n"
                + "<channel id=\"miscellaneous\" name=\"Uncategorized\" usage=\"5\" "
                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
                + "</package>\n"
                + "</ranking>\n";

        loadByteArrayXml(
                backupWithUncanonicalizedSoundUri.getBytes(), true, UserHandle.USER_SYSTEM);

        NotificationChannel actualChannel = mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, id, false);
        assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
    }

    @Test
    public void testBackupRestoreXml_withNullSoundUri() throws Exception {
        NotificationChannel channel =
                new NotificationChannel("id", "name", IMPORTANCE_LOW);
        channel.setSound(null, mAudioAttributes);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false);
        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, true,
                UserHandle.USER_SYSTEM, channel.getId());

        loadStreamXml(baos, true, UserHandle.USER_SYSTEM);

        NotificationChannel actualChannel = mHelper.getNotificationChannel(
                PKG_N_MR1, UID_N_MR1, channel.getId(), false);
        assertEquals(null, actualChannel.getSound());
    }

    @Test
    public void testChannelXml_backup() throws Exception {
        NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
        NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
        NotificationChannel channel1 =
                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
        NotificationChannel channel2 =
                new NotificationChannel("id2", "name2", IMPORTANCE_HIGH);
        NotificationChannel channel3 =
                new NotificationChannel("id3", "name3", IMPORTANCE_LOW);
        channel3.setGroup(ncg.getId());

        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, true, false);

        mHelper.deleteNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1.getId());
        mHelper.deleteNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg.getId());
        assertEquals(channel2,
                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2.getId(), false));

        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, true,
                UserHandle.USER_SYSTEM, channel1.getId(), channel2.getId(), channel3.getId(),
                NotificationChannel.DEFAULT_CHANNEL_ID);
        mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG_N_MR1}, new int[]{
                UID_N_MR1});

        XmlPullParser parser = Xml.newPullParser();
        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
                null);
        parser.nextTag();
        mHelper.readXml(parser, true, UserHandle.USER_SYSTEM);

        assertNull(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1.getId(), false));
        assertNull(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel3.getId(), false));
        assertNull(mHelper.getNotificationChannelGroup(ncg.getId(), PKG_N_MR1, UID_N_MR1));
        assertEquals(channel2,
                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2.getId(), false));
    }

    @Test
    public void testChannelXml_defaultChannelLegacyApp_noUserSettings() throws Exception {
        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, false,
                UserHandle.USER_ALL, NotificationChannel.DEFAULT_CHANNEL_ID);

        loadStreamXml(baos, false, UserHandle.USER_ALL);

        final NotificationChannel updated = mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1,
                NotificationChannel.DEFAULT_CHANNEL_ID, false);
        assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, updated.getImportance());
        assertFalse(updated.canBypassDnd());
        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE, updated.getLockscreenVisibility());
        assertEquals(0, updated.getUserLockedFields());
    }

    @Test
    public void testChannelXml_defaultChannelUpdatedApp_userSettings() throws Exception {
        final NotificationChannel defaultChannel = mHelper.getNotificationChannel(PKG_N_MR1,
                UID_N_MR1,
                NotificationChannel.DEFAULT_CHANNEL_ID, false);
        defaultChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
        mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, defaultChannel, true);

        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, false,
                UserHandle.USER_ALL, NotificationChannel.DEFAULT_CHANNEL_ID);

        loadStreamXml(baos, false, UserHandle.USER_ALL);

        assertEquals(NotificationManager.IMPORTANCE_LOW, mHelper.getNotificationChannel(
                PKG_N_MR1, UID_N_MR1, NotificationChannel.DEFAULT_CHANNEL_ID, false).getImportance());
    }

    @Test
    public void testChannelXml_upgradeCreateDefaultChannel() throws Exception {
        final String preupgradeXml = "<ranking version=\"1\">\n"
                + "<package name=\"" + PKG_N_MR1
                + "\" importance=\"" + NotificationManager.IMPORTANCE_HIGH
                + "\" priority=\"" + Notification.PRIORITY_MAX + "\" visibility=\""
                + Notification.VISIBILITY_SECRET + "\"" +" uid=\"" + UID_N_MR1 + "\" />\n"
                + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" visibility=\""
                + Notification.VISIBILITY_PRIVATE + "\" />\n"
                + "</ranking>";
        XmlPullParser parser = Xml.newPullParser();
        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(preupgradeXml.getBytes())),
                null);
        parser.nextTag();
        mHelper.readXml(parser, false, UserHandle.USER_ALL);

        final NotificationChannel updated1 =
            mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, NotificationChannel.DEFAULT_CHANNEL_ID, false);
        assertEquals(NotificationManager.IMPORTANCE_HIGH, updated1.getImportance());
        assertTrue(updated1.canBypassDnd());
        assertEquals(Notification.VISIBILITY_SECRET, updated1.getLockscreenVisibility());
        assertEquals(NotificationChannel.USER_LOCKED_IMPORTANCE
                | NotificationChannel.USER_LOCKED_PRIORITY
                | NotificationChannel.USER_LOCKED_VISIBILITY,
                updated1.getUserLockedFields());

        // No Default Channel created for updated packages
        assertEquals(null, mHelper.getNotificationChannel(PKG_O, UID_O,
                NotificationChannel.DEFAULT_CHANNEL_ID, false));
    }

    @Test
    public void testChannelXml_upgradeDeletesDefaultChannel() throws Exception {
        final NotificationChannel defaultChannel = mHelper.getNotificationChannel(
                PKG_N_MR1, UID_N_MR1, NotificationChannel.DEFAULT_CHANNEL_ID, false);
        assertTrue(defaultChannel != null);
        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, false,
                UserHandle.USER_ALL, NotificationChannel.DEFAULT_CHANNEL_ID);
        // Load package at higher sdk.
        final ApplicationInfo upgraded = new ApplicationInfo();
        upgraded.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
        when(mPm.getApplicationInfoAsUser(eq(PKG_N_MR1), anyInt(), anyInt())).thenReturn(upgraded);
        loadStreamXml(baos, false, UserHandle.USER_ALL);

        // Default Channel should be gone.
        assertEquals(null, mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1,
                NotificationChannel.DEFAULT_CHANNEL_ID, false));
    }

    @Test
    public void testDeletesDefaultChannelAfterChannelIsCreated() throws Exception {
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1,
                new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false);
        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, false,
                UserHandle.USER_ALL, NotificationChannel.DEFAULT_CHANNEL_ID, "bananas");

        // Load package at higher sdk.
        final ApplicationInfo upgraded = new ApplicationInfo();
        upgraded.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
        when(mPm.getApplicationInfoAsUser(eq(PKG_N_MR1), anyInt(), anyInt())).thenReturn(upgraded);
        loadStreamXml(baos, false, UserHandle.USER_ALL);

        // Default Channel should be gone.
        assertEquals(null, mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1,
                NotificationChannel.DEFAULT_CHANNEL_ID, false));
    }

    @Test
    public void testLoadingOldChannelsDoesNotDeleteNewlyCreatedChannels() throws Exception {
        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, false,
                UserHandle.USER_ALL, NotificationChannel.DEFAULT_CHANNEL_ID, "bananas");
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1,
                new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false);

        loadStreamXml(baos, false, UserHandle.USER_ALL);

        // Should still have the newly created channel that wasn't in the xml.
        assertTrue(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, "bananas", false) != null);
    }

    @Test
    public void testCreateChannel_blocked() throws Exception {
        mHelper.setImportance(PKG_N_MR1, UID_N_MR1, IMPORTANCE_NONE);

        assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1,
                new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false));
    }

    @Test
    public void testCreateChannel_badImportance() throws Exception {
        try {
            mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1,
                    new NotificationChannel("bananas", "bananas", IMPORTANCE_NONE - 1),
                    true, false);
            fail("Was allowed to create a channel with invalid importance");
        } catch (IllegalArgumentException e) {
            // yay
        }
        try {
            mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1,
                    new NotificationChannel("bananas", "bananas", IMPORTANCE_UNSPECIFIED),
                    true, false);
            fail("Was allowed to create a channel with invalid importance");
        } catch (IllegalArgumentException e) {
            // yay
        }
        try {
            mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1,
                    new NotificationChannel("bananas", "bananas", IMPORTANCE_MAX + 1),
                    true, false);
            fail("Was allowed to create a channel with invalid importance");
        } catch (IllegalArgumentException e) {
            // yay
        }
        assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1,
                new NotificationChannel("bananas", "bananas", IMPORTANCE_NONE), true, false));
        assertFalse(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1,
                new NotificationChannel("bananas", "bananas", IMPORTANCE_MAX), true, false));
    }


    @Test
    public void testUpdate() throws Exception {
        // no fields locked by user
        final NotificationChannel channel =
                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
        channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
        channel.enableLights(true);
        channel.setBypassDnd(true);
        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);

        assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, false, false));

        // same id, try to update all fields
        final NotificationChannel channel2 =
                new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
        channel2.setSound(new Uri.Builder().scheme("test2").build(), mAudioAttributes);
        channel2.enableLights(false);
        channel2.setBypassDnd(false);
        channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);

        mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, true);

        // all fields should be changed
        assertEquals(channel2,
                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), false));

        verify(mHandler, times(1)).requestSort();
    }

    @Test
    public void testUpdate_preUpgrade_updatesAppFields() throws Exception {
        mHelper.setImportance(PKG_N_MR1, UID_N_MR1, IMPORTANCE_UNSPECIFIED);
        assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
        assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG_N_MR1, UID_N_MR1));
        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
                mHelper.getPackageVisibility(PKG_N_MR1, UID_N_MR1));
        assertFalse(mHelper.getIsAppImportanceLocked(PKG_N_MR1, UID_N_MR1));

        NotificationChannel defaultChannel = mHelper.getNotificationChannel(
                PKG_N_MR1, UID_N_MR1, NotificationChannel.DEFAULT_CHANNEL_ID, false);

        defaultChannel.setShowBadge(false);
        defaultChannel.setImportance(IMPORTANCE_NONE);
        defaultChannel.setBypassDnd(true);
        defaultChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);

        mHelper.setAppImportanceLocked(PKG_N_MR1, UID_N_MR1);
        mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, defaultChannel, true);

        // ensure app level fields are changed
        assertFalse(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
        assertEquals(Notification.PRIORITY_MAX, mHelper.getPackagePriority(PKG_N_MR1, UID_N_MR1));
        assertEquals(Notification.VISIBILITY_SECRET, mHelper.getPackageVisibility(PKG_N_MR1,
                UID_N_MR1));
        assertEquals(IMPORTANCE_NONE, mHelper.getImportance(PKG_N_MR1, UID_N_MR1));
        assertTrue(mHelper.getIsAppImportanceLocked(PKG_N_MR1, UID_N_MR1));
    }

    @Test
    public void testUpdate_postUpgrade_noUpdateAppFields() throws Exception {
        final NotificationChannel channel = new NotificationChannel("id2", "name2", IMPORTANCE_LOW);

        mHelper.createNotificationChannel(PKG_O, UID_O, channel, false, false);
        assertTrue(mHelper.canShowBadge(PKG_O, UID_O));
        assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG_O, UID_O));
        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
                mHelper.getPackageVisibility(PKG_O, UID_O));

        channel.setShowBadge(false);
        channel.setImportance(IMPORTANCE_NONE);
        channel.setBypassDnd(true);
        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);

        mHelper.updateNotificationChannel(PKG_O, UID_O, channel, true);

        // ensure app level fields are not changed
        assertTrue(mHelper.canShowBadge(PKG_O, UID_O));
        assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG_O, UID_O));
        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
                mHelper.getPackageVisibility(PKG_O, UID_O));
        assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG_O,
                UID_O));
        assertFalse(mHelper.getIsAppImportanceLocked(PKG_O, UID_O));
    }

    @Test
    public void testUpdate_preUpgrade_noUpdateAppFieldsWithMultipleChannels() throws Exception {
        final NotificationChannel channel = new NotificationChannel("id2", "name2", IMPORTANCE_LOW);

        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, false, false);
        assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
        assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG_N_MR1, UID_N_MR1));
        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
                mHelper.getPackageVisibility(PKG_N_MR1, UID_N_MR1));

        channel.setShowBadge(false);
        channel.setImportance(IMPORTANCE_NONE);
        channel.setBypassDnd(true);
        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);

        mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true);

        NotificationChannel defaultChannel = mHelper.getNotificationChannel(
                PKG_N_MR1, UID_N_MR1, NotificationChannel.DEFAULT_CHANNEL_ID, false);

        defaultChannel.setShowBadge(false);
        defaultChannel.setImportance(IMPORTANCE_NONE);
        defaultChannel.setBypassDnd(true);
        defaultChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);

        mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, defaultChannel, true);

        // ensure app level fields are not changed
        assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
        assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG_N_MR1, UID_N_MR1));
        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
                mHelper.getPackageVisibility(PKG_N_MR1, UID_N_MR1));
        assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG_N_MR1,
                UID_N_MR1));
    }

    @Test
    public void testGetNotificationChannel_ReturnsNullForUnknownChannel() throws Exception {
        assertEquals(null, mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, "garbage", false));
    }

    @Test
    public void testCreateChannel_CannotChangeHiddenFields() {
        final NotificationChannel channel =
                new NotificationChannel("id2", "name2", IMPORTANCE_HIGH);
        channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
        channel.enableLights(true);
        channel.setBypassDnd(true);
        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
        channel.setShowBadge(true);
        channel.setAllowBubbles(false);
        int lockMask = 0;
        for (int i = 0; i < NotificationChannel.LOCKABLE_FIELDS.length; i++) {
            lockMask |= NotificationChannel.LOCKABLE_FIELDS[i];
        }
        channel.lockFields(lockMask);

        assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false));

        NotificationChannel savedChannel =
                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), false);

        assertEquals(channel.getName(), savedChannel.getName());
        assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
        assertFalse(savedChannel.canBypassDnd());
        assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
        assertEquals(channel.canShowBadge(), savedChannel.canShowBadge());
        assertEquals(channel.canBubble(), savedChannel.canBubble());

        verify(mHandler, never()).requestSort();
    }

    @Test
    public void testCreateChannel_CannotChangeHiddenFieldsAssistant() {
        final NotificationChannel channel =
                new NotificationChannel("id2", "name2", IMPORTANCE_HIGH);
        channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
        channel.enableLights(true);
        channel.setBypassDnd(true);
        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
        channel.setShowBadge(true);
        channel.setAllowBubbles(false);
        int lockMask = 0;
        for (int i = 0; i < NotificationChannel.LOCKABLE_FIELDS.length; i++) {
            lockMask |= NotificationChannel.LOCKABLE_FIELDS[i];
        }
        channel.lockFields(lockMask);

        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false);

        NotificationChannel savedChannel =
                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), false);

        assertEquals(channel.getName(), savedChannel.getName());
        assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
        assertFalse(savedChannel.canBypassDnd());
        assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
        assertEquals(channel.canShowBadge(), savedChannel.canShowBadge());
        assertEquals(channel.canBubble(), savedChannel.canBubble());
    }

    @Test
    public void testClearLockedFields() {
        final NotificationChannel channel = getChannel();
        mHelper.clearLockedFieldsLocked(channel);
        assertEquals(0, channel.getUserLockedFields());

        channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY
                | NotificationChannel.USER_LOCKED_IMPORTANCE);
        mHelper.clearLockedFieldsLocked(channel);
        assertEquals(0, channel.getUserLockedFields());
    }

    @Test
    public void testLockFields_soundAndVibration() {
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, getChannel(), true, false);

        final NotificationChannel update1 = getChannel();
        update1.setSound(new Uri.Builder().scheme("test").build(),
                new AudioAttributes.Builder().build());
        update1.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
        mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update1, true);
        assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
                | NotificationChannel.USER_LOCKED_SOUND,
                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update1.getId(), false)
                        .getUserLockedFields());

        NotificationChannel update2 = getChannel();
        update2.enableVibration(true);
        mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update2, true);
        assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
                        | NotificationChannel.USER_LOCKED_SOUND
                        | NotificationChannel.USER_LOCKED_VIBRATION,
                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update2.getId(), false)
                        .getUserLockedFields());
    }

    @Test
    public void testLockFields_vibrationAndLights() {
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, getChannel(), true, false);

        final NotificationChannel update1 = getChannel();
        update1.setVibrationPattern(new long[]{7945, 46 ,246});
        mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update1, true);
        assertEquals(NotificationChannel.USER_LOCKED_VIBRATION,
                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update1.getId(), false)
                        .getUserLockedFields());

        final NotificationChannel update2 = getChannel();
        update2.enableLights(true);
        mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update2, true);
        assertEquals(NotificationChannel.USER_LOCKED_VIBRATION
                        | NotificationChannel.USER_LOCKED_LIGHTS,
                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update2.getId(), false)
                        .getUserLockedFields());
    }

    @Test
    public void testLockFields_lightsAndImportance() {
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, getChannel(), true, false);

        final NotificationChannel update1 = getChannel();
        update1.setLightColor(Color.GREEN);
        mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update1, true);
        assertEquals(NotificationChannel.USER_LOCKED_LIGHTS,
                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update1.getId(), false)
                        .getUserLockedFields());

        final NotificationChannel update2 = getChannel();
        update2.setImportance(IMPORTANCE_DEFAULT);
        mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update2, true);
        assertEquals(NotificationChannel.USER_LOCKED_LIGHTS
                        | NotificationChannel.USER_LOCKED_IMPORTANCE,
                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update2.getId(), false)
                        .getUserLockedFields());
    }

    @Test
    public void testLockFields_visibilityAndDndAndBadge() {
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, getChannel(), true, false);
        assertEquals(0,
                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, getChannel().getId(), false)
                        .getUserLockedFields());

        final NotificationChannel update1 = getChannel();
        update1.setBypassDnd(true);
        mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update1, true);
        assertEquals(NotificationChannel.USER_LOCKED_PRIORITY,
                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update1.getId(), false)
                        .getUserLockedFields());

        final NotificationChannel update2 = getChannel();
        update2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
        mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update2, true);
        assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
                        | NotificationChannel.USER_LOCKED_VISIBILITY,
                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update2.getId(), false)
                        .getUserLockedFields());

        final NotificationChannel update3 = getChannel();
        update3.setShowBadge(false);
        mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update3, true);
        assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
                        | NotificationChannel.USER_LOCKED_VISIBILITY
                        | NotificationChannel.USER_LOCKED_SHOW_BADGE,
                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update3.getId(), false)
                        .getUserLockedFields());
    }

    @Test
    public void testLockFields_allowBubble() {
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, getChannel(), true, false);
        assertEquals(0,
                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, getChannel().getId(), false)
                        .getUserLockedFields());

        final NotificationChannel update = getChannel();
        update.setAllowBubbles(false);
        mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update, true);
        assertEquals(NotificationChannel.USER_LOCKED_ALLOW_BUBBLE,
                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update.getId(), false)
                        .getUserLockedFields());
    }

    @Test
    public void testDeleteNonExistentChannel() throws Exception {
        mHelper.deleteNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, "does not exist");
    }

    @Test
    public void testGetDeletedChannel() throws Exception {
        NotificationChannel channel = getChannel();
        channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
        channel.enableLights(true);
        channel.setBypassDnd(true);
        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
        channel.enableVibration(true);
        channel.setVibrationPattern(new long[]{100, 67, 145, 156});

        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false);
        mHelper.deleteNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId());

        // Does not return deleted channel
        NotificationChannel response =
                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), false);
        assertNull(response);

        // Returns deleted channel
        response = mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), true);
        compareChannels(channel, response);
        assertTrue(response.isDeleted());
    }

    @Test
    public void testGetDeletedChannels() throws Exception {
        Map<String, NotificationChannel> channelMap = new HashMap<>();
        NotificationChannel channel =
                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
        channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
        channel.enableLights(true);
        channel.setBypassDnd(true);
        channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
        channel.enableVibration(true);
        channel.setVibrationPattern(new long[]{100, 67, 145, 156});
        channelMap.put(channel.getId(), channel);
        NotificationChannel channel2 =
                new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
        channelMap.put(channel2.getId(), channel2);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, true, false);

        mHelper.deleteNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId());

        // Returns only non-deleted channels
        List<NotificationChannel> channels =
                mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList();
        assertEquals(2, channels.size());   // Default channel + non-deleted channel
        for (NotificationChannel nc : channels) {
            if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
                compareChannels(channel2, nc);
            }
        }

        // Returns deleted channels too
        channels = mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, true).getList();
        assertEquals(3, channels.size());               // Includes default channel
        for (NotificationChannel nc : channels) {
            if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
                compareChannels(channelMap.get(nc.getId()), nc);
            }
        }
    }

    @Test
    public void testGetDeletedChannelCount() throws Exception {
        NotificationChannel channel =
                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
        NotificationChannel channel2 =
                new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
        NotificationChannel channel3 =
                new NotificationChannel("id5", "a", NotificationManager.IMPORTANCE_HIGH);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, true, false);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel3, true, false);

        mHelper.deleteNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId());
        mHelper.deleteNotificationChannel(PKG_N_MR1, UID_N_MR1, channel3.getId());

        assertEquals(2, mHelper.getDeletedChannelCount(PKG_N_MR1, UID_N_MR1));
        assertEquals(0, mHelper.getDeletedChannelCount("pkg2", UID_O));
    }

    @Test
    public void testGetBlockedChannelCount() throws Exception {
        NotificationChannel channel =
                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
        NotificationChannel channel2 =
                new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_NONE);
        NotificationChannel channel3 =
                new NotificationChannel("id5", "a", NotificationManager.IMPORTANCE_NONE);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, true, false);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel3, true, false);

        mHelper.deleteNotificationChannel(PKG_N_MR1, UID_N_MR1, channel3.getId());

        assertEquals(1, mHelper.getBlockedChannelCount(PKG_N_MR1, UID_N_MR1));
        assertEquals(0, mHelper.getBlockedChannelCount("pkg2", UID_O));
    }

    @Test
    public void testUpdateChannelsBypassingDnd_onUserSwitch_onUserUnlocked() throws Exception {
        int user = USER.getIdentifier();
        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
        NotificationChannel channel1 = new NotificationChannel("id1", "name1",
                NotificationManager.IMPORTANCE_MAX);
        channel1.setBypassDnd(true);
        channel1.setGroup(ncg.getId());

        // channel is associated with a group, then group is deleted
        mHelper.createNotificationChannelGroup(PKG_N_MR1, user, ncg,  /* fromTargetApp */ true);
        mHelper.createNotificationChannel(PKG_N_MR1, user, channel1, true, /*has DND access*/ true);
        mHelper.deleteNotificationChannelGroup(PKG_N_MR1, user, ncg.getId());

        mHelper.onUserSwitched(user);
        mHelper.onUserUnlocked(user);
    }

    @Test
    public void testGetChannelsBypassingDndCount_noChannelsBypassing() throws Exception {
        assertEquals(0, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
                USER.getIdentifier()).getList().size());
    }

    @Test
    public void testGetChannelsBypassingDnd_noChannelsForUserIdBypassing()
            throws Exception {
        int user = 9;
        NotificationChannel channel = new NotificationChannel("id", "name",
                NotificationManager.IMPORTANCE_MAX);
        channel.setBypassDnd(true);
        mHelper.createNotificationChannel(PKG_N_MR1, 111, channel, true, true);

        assertEquals(0, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
                user).getList().size());
    }

    @Test
    public void testGetChannelsBypassingDndCount_oneChannelBypassing_groupBlocked() {
        int user = USER.getIdentifier();
        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
        NotificationChannel channel1 = new NotificationChannel("id1", "name1",
                NotificationManager.IMPORTANCE_MAX);
        channel1.setBypassDnd(true);
        channel1.setGroup(ncg.getId());
        mHelper.createNotificationChannelGroup(PKG_N_MR1, user, ncg,  /* fromTargetApp */ true);
        mHelper.createNotificationChannel(PKG_N_MR1, user, channel1, true, /*has DND access*/ true);

        assertEquals(1, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
                user).getList().size());

        // disable group
        ncg.setBlocked(true);
        mHelper.createNotificationChannelGroup(PKG_N_MR1, user, ncg,  /* fromTargetApp */ false);
        assertEquals(0, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
                user).getList().size());
    }

    @Test
    public void testGetChannelsBypassingDndCount_multipleChannelsBypassing() {
        int user = USER.getIdentifier();
        NotificationChannel channel1 = new NotificationChannel("id1", "name1",
                NotificationManager.IMPORTANCE_MAX);
        NotificationChannel channel2 = new NotificationChannel("id2", "name2",
                NotificationManager.IMPORTANCE_MAX);
        NotificationChannel channel3 = new NotificationChannel("id3", "name3",
                NotificationManager.IMPORTANCE_MAX);
        channel1.setBypassDnd(true);
        channel2.setBypassDnd(true);
        channel3.setBypassDnd(true);
        // has DND access, so can set bypassDnd attribute
        mHelper.createNotificationChannel(PKG_N_MR1, user, channel1, true, /*has DND access*/ true);
        mHelper.createNotificationChannel(PKG_N_MR1, user, channel2, true, true);
        mHelper.createNotificationChannel(PKG_N_MR1, user, channel3, true, true);
        assertEquals(3, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
                user).getList().size());

        // block notifications from this app
        mHelper.setEnabled(PKG_N_MR1, user, false);
        assertEquals(0, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
                user).getList().size());

        // re-enable notifications from this app
        mHelper.setEnabled(PKG_N_MR1, user, true);
        assertEquals(3, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
                user).getList().size());

        // setBypassDnd false for some channels
        channel1.setBypassDnd(false);
        channel2.setBypassDnd(false);
        assertEquals(1, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
                user).getList().size());

        // setBypassDnd false for rest of the channels
        channel3.setBypassDnd(false);
        assertEquals(0, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
                user).getList().size());
    }

    @Test
    public void testGetAppsBypassingDndCount_noAppsBypassing() throws Exception {
        assertEquals(0, mHelper.getAppsBypassingDndCount(USER.getIdentifier()));
    }

    @Test
    public void testGetAppsBypassingDndCount_noAppsForUserIdBypassing() throws Exception {
        int user = 9;
        NotificationChannel channel = new NotificationChannel("id", "name",
                NotificationManager.IMPORTANCE_MAX);
        channel.setBypassDnd(true);
        mHelper.createNotificationChannel(PKG_N_MR1, 111, channel, true, true);

        assertEquals(0, mHelper.getAppsBypassingDndCount(user));
    }

    @Test
    public void testGetAppsBypassingDndCount_oneChannelBypassing_groupBlocked() {
        int user = USER.getIdentifier();
        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
        NotificationChannel channel1 = new NotificationChannel("id1", "name1",
                NotificationManager.IMPORTANCE_MAX);
        channel1.setBypassDnd(true);
        channel1.setGroup(ncg.getId());
        mHelper.createNotificationChannelGroup(PKG_N_MR1, user, ncg,  /* fromTargetApp */ true);
        mHelper.createNotificationChannel(PKG_N_MR1, user, channel1, true, /*has DND access*/ true);

        assertEquals(1, mHelper.getAppsBypassingDndCount(user));

        // disable group
        ncg.setBlocked(true);
        mHelper.createNotificationChannelGroup(PKG_N_MR1, user, ncg,  /* fromTargetApp */ false);
        assertEquals(0, mHelper.getAppsBypassingDndCount(user));
    }

    @Test
    public void testGetAppsBypassingDndCount_oneAppBypassing() {
        int user = USER.getIdentifier();
        NotificationChannel channel1 = new NotificationChannel("id1", "name1",
                NotificationManager.IMPORTANCE_MAX);
        NotificationChannel channel2 = new NotificationChannel("id2", "name2",
                NotificationManager.IMPORTANCE_MAX);
        NotificationChannel channel3 = new NotificationChannel("id3", "name3",
                NotificationManager.IMPORTANCE_MAX);
        channel1.setBypassDnd(true);
        channel2.setBypassDnd(true);
        channel3.setBypassDnd(true);
        // has DND access, so can set bypassDnd attribute
        mHelper.createNotificationChannel(PKG_N_MR1, user, channel1, true, /*has DND access*/ true);
        mHelper.createNotificationChannel(PKG_N_MR1, user, channel2, true, true);
        mHelper.createNotificationChannel(PKG_N_MR1, user, channel3, true, true);
        assertEquals(1, mHelper.getAppsBypassingDndCount(user));

        // block notifications from this app
        mHelper.setEnabled(PKG_N_MR1, user, false);
        assertEquals(0, mHelper.getAppsBypassingDndCount(user)); // no apps can bypass dnd

        // re-enable notifications from this app
        mHelper.setEnabled(PKG_N_MR1, user, true);
        assertEquals(1, mHelper.getAppsBypassingDndCount(user));

        // setBypassDnd false for some channels
        channel1.setBypassDnd(false);
        channel2.setBypassDnd(false);
        assertEquals(1, mHelper.getAppsBypassingDndCount(user));

        // setBypassDnd false for rest of the channels
        channel3.setBypassDnd(false);
        assertEquals(0, mHelper.getAppsBypassingDndCount(user));
    }

    @Test
    public void testCreateAndDeleteCanChannelsBypassDnd() throws Exception {
        // create notification channel that can't bypass dnd
        // expected result: areChannelsBypassingDnd = false
        // setNotificationPolicy isn't called since areChannelsBypassingDnd was already false
        NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false);
        assertFalse(mHelper.areChannelsBypassingDnd());
        verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
        resetZenModeHelper();

        // create notification channel that can bypass dnd
        // expected result: areChannelsBypassingDnd = true
        NotificationChannel channel2 = new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
        channel2.setBypassDnd(true);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, true, true);
        assertTrue(mHelper.areChannelsBypassingDnd());
        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
        resetZenModeHelper();

        // delete channels
        mHelper.deleteNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId());
        assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND
        verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
        resetZenModeHelper();

        mHelper.deleteNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2.getId());
        assertFalse(mHelper.areChannelsBypassingDnd());
        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
        resetZenModeHelper();
    }

    @Test
    public void testUpdateCanChannelsBypassDnd() throws Exception {
        // create notification channel that can't bypass dnd
        // expected result: areChannelsBypassingDnd = false
        // setNotificationPolicy isn't called since areChannelsBypassingDnd was already false
        NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false);
        assertFalse(mHelper.areChannelsBypassingDnd());
        verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
        resetZenModeHelper();

        // update channel so it CAN bypass dnd:
        // expected result: areChannelsBypassingDnd = true
        channel.setBypassDnd(true);
        mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true);
        assertTrue(mHelper.areChannelsBypassingDnd());
        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
        resetZenModeHelper();

        // update channel so it can't bypass dnd:
        // expected result: areChannelsBypassingDnd = false
        channel.setBypassDnd(false);
        mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true);
        assertFalse(mHelper.areChannelsBypassingDnd());
        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
        resetZenModeHelper();
    }

    @Test
    public void testSetupNewZenModeHelper_canBypass() {
        // start notification policy off with mAreChannelsBypassingDnd = true, but
        // RankingHelper should change to false
        mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
                NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND);
        when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
        assertFalse(mHelper.areChannelsBypassingDnd());
        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
        resetZenModeHelper();
    }

    @Test
    public void testSetupNewZenModeHelper_cannotBypass() {
        // start notification policy off with mAreChannelsBypassingDnd = false
        mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0);
        when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
        assertFalse(mHelper.areChannelsBypassingDnd());
        verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
        resetZenModeHelper();
    }

    @Test
    public void testCreateDeletedChannel() throws Exception {
        long[] vibration = new long[]{100, 67, 145, 156};
        NotificationChannel channel =
                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
        channel.setVibrationPattern(vibration);

        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false);
        mHelper.deleteNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId());

        NotificationChannel newChannel = new NotificationChannel(
                channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
        newChannel.setVibrationPattern(new long[]{100});

        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, newChannel, true, false);

        // No long deleted, using old settings
        compareChannels(channel,
                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, newChannel.getId(), false));
    }

    @Test
    public void testOnlyHasDefaultChannel() throws Exception {
        assertTrue(mHelper.onlyHasDefaultChannel(PKG_N_MR1, UID_N_MR1));
        assertFalse(mHelper.onlyHasDefaultChannel(PKG_O, UID_O));

        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, getChannel(), true, false);
        assertFalse(mHelper.onlyHasDefaultChannel(PKG_N_MR1, UID_N_MR1));
    }

    @Test
    public void testCreateChannel_defaultChannelId() throws Exception {
        try {
            mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, new NotificationChannel(
                    NotificationChannel.DEFAULT_CHANNEL_ID, "ha", IMPORTANCE_HIGH), true, false);
            fail("Allowed to create default channel");
        } catch (IllegalArgumentException e) {
            // pass
        }
    }

    @Test
    public void testCreateChannel_alreadyExists() throws Exception {
        long[] vibration = new long[]{100, 67, 145, 156};
        NotificationChannel channel =
                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
        channel.setVibrationPattern(vibration);

        assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false));

        NotificationChannel newChannel = new NotificationChannel(
                channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
        newChannel.setVibrationPattern(new long[]{100});
        newChannel.setAllowBubbles(!channel.canBubble());
        newChannel.setLightColor(Color.BLUE);
        newChannel.setSound(Uri.EMPTY, null);
        newChannel.setShowBadge(!channel.canShowBadge());

        assertFalse(mHelper.createNotificationChannel(
                PKG_N_MR1, UID_N_MR1, newChannel, true, false));

        // Old settings not overridden
        compareChannels(channel,
                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, newChannel.getId(), false));
    }

    @Test
    public void testCreateChannel_noOverrideSound() throws Exception {
        Uri sound = new Uri.Builder().scheme("test").build();
        final NotificationChannel channel = new NotificationChannel("id2", "name2",
                 NotificationManager.IMPORTANCE_DEFAULT);
        channel.setSound(sound, mAudioAttributes);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false);
        assertEquals(sound, mHelper.getNotificationChannel(
                PKG_N_MR1, UID_N_MR1, channel.getId(), false).getSound());
    }

    @Test
    public void testPermanentlyDeleteChannels() throws Exception {
        NotificationChannel channel1 =
                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
        NotificationChannel channel2 =
                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);

        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, false, false);

        mHelper.permanentlyDeleteNotificationChannels(PKG_N_MR1, UID_N_MR1);

        // Only default channel remains
        assertEquals(1, mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, true).getList().size());
    }

    @Test
    public void testDeleteGroup() throws Exception {
        NotificationChannelGroup notDeleted = new NotificationChannelGroup("not", "deleted");
        NotificationChannelGroup deleted = new NotificationChannelGroup("totally", "deleted");
        NotificationChannel nonGroupedNonDeletedChannel =
                new NotificationChannel("no group", "so not deleted", IMPORTANCE_HIGH);
        NotificationChannel groupedButNotDeleted =
                new NotificationChannel("not deleted", "belongs to notDeleted", IMPORTANCE_DEFAULT);
        groupedButNotDeleted.setGroup("not");
        NotificationChannel groupedAndDeleted =
                new NotificationChannel("deleted", "belongs to deleted", IMPORTANCE_DEFAULT);
        groupedAndDeleted.setGroup("totally");

        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, notDeleted, true);
        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, deleted, true);
        mHelper.createNotificationChannel(
                PKG_N_MR1, UID_N_MR1, nonGroupedNonDeletedChannel, true, false);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, groupedAndDeleted, true, false);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, groupedButNotDeleted, true, false);

        mHelper.deleteNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, deleted.getId());

        assertNull(mHelper.getNotificationChannelGroup(deleted.getId(), PKG_N_MR1, UID_N_MR1));
        assertNotNull(
                mHelper.getNotificationChannelGroup(notDeleted.getId(), PKG_N_MR1, UID_N_MR1));

        assertNull(mHelper.getNotificationChannel(
                PKG_N_MR1, UID_N_MR1, groupedAndDeleted.getId(), false));
        compareChannels(groupedAndDeleted, mHelper.getNotificationChannel(
                PKG_N_MR1, UID_N_MR1, groupedAndDeleted.getId(), true));

        compareChannels(groupedButNotDeleted, mHelper.getNotificationChannel(
                PKG_N_MR1, UID_N_MR1, groupedButNotDeleted.getId(), false));
        compareChannels(nonGroupedNonDeletedChannel, mHelper.getNotificationChannel(
                PKG_N_MR1, UID_N_MR1, nonGroupedNonDeletedChannel.getId(), false));

        // notDeleted
        assertEquals(1, mHelper.getNotificationChannelGroups(PKG_N_MR1, UID_N_MR1).size());

        verify(mHandler, never()).requestSort();
    }

    @Test
    public void testOnUserRemoved() throws Exception {
        int[] user0Uids = {98, 235, 16, 3782};
        int[] user1Uids = new int[user0Uids.length];
        for (int i = 0; i < user0Uids.length; i++) {
            user1Uids[i] = UserHandle.PER_USER_RANGE + user0Uids[i];

            final ApplicationInfo legacy = new ApplicationInfo();
            legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
            when(mPm.getApplicationInfoAsUser(eq(PKG_N_MR1), anyInt(), anyInt())).thenReturn(legacy);

            // create records with the default channel for all user 0 and user 1 uids
            mHelper.getImportance(PKG_N_MR1, user0Uids[i]);
            mHelper.getImportance(PKG_N_MR1, user1Uids[i]);
        }

        mHelper.onUserRemoved(1);

        // user 0 records remain
        for (int i = 0; i < user0Uids.length; i++) {
            assertEquals(1,
                    mHelper.getNotificationChannels(PKG_N_MR1, user0Uids[i], false).getList().size());
        }
        // user 1 records are gone
        for (int i = 0; i < user1Uids.length; i++) {
            assertEquals(0,
                    mHelper.getNotificationChannels(PKG_N_MR1, user1Uids[i], false).getList().size());
        }
    }

    @Test
    public void testOnPackageChanged_packageRemoval() throws Exception {
        // Deleted
        NotificationChannel channel1 =
                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false);

        assertTrue(mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG_N_MR1},
                new int[]{UID_N_MR1}));

        assertEquals(0, mHelper.getNotificationChannels(
                PKG_N_MR1, UID_N_MR1, true).getList().size());

        // Not deleted
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false);

        assertFalse(mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM,
                new String[]{PKG_N_MR1}, new int[]{UID_N_MR1}));
        assertEquals(2, mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList().size());
    }

    @Test
    public void testOnPackageChanged_packageRemoval_importance() throws Exception {
        mHelper.setImportance(PKG_N_MR1, UID_N_MR1, NotificationManager.IMPORTANCE_HIGH);

        mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG_N_MR1}, new int[]{
                UID_N_MR1});

        assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG_N_MR1,
                UID_N_MR1));
    }

    @Test
    public void testOnPackageChanged_packageRemoval_groups() throws Exception {
        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);
        NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg2, true);

        mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG_N_MR1}, new int[]{
                UID_N_MR1});

        assertEquals(0, mHelper.getNotificationChannelGroups(
                PKG_N_MR1, UID_N_MR1, true, true, false).getList().size());
    }

    @Test
    public void testOnPackageChange_downgradeTargetSdk() throws Exception {
        // create channel as api 26
        mHelper.createNotificationChannel(PKG_O, UID_O, getChannel(), true, false);

        // install new app version targeting 25
        final ApplicationInfo legacy = new ApplicationInfo();
        legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
        when(mPm.getApplicationInfoAsUser(eq(PKG_O), anyInt(), anyInt())).thenReturn(legacy);
        mHelper.onPackagesChanged(
                false, UserHandle.USER_SYSTEM, new String[]{PKG_O}, new int[]{UID_O});

        // make sure the default channel was readded
        //assertEquals(2, mHelper.getNotificationChannels(PKG_O, UID_O, false).getList().size());
        assertNotNull(mHelper.getNotificationChannel(
                PKG_O, UID_O, NotificationChannel.DEFAULT_CHANNEL_ID, false));
    }

    @Test
    public void testClearData() {
        ArraySet<String> pkg = new ArraySet<>();
        pkg.add(PKG_O);
        ArraySet<Pair<String, Integer>> pkgPair = new ArraySet<>();
        pkgPair.add(new Pair(PKG_O, UID_O));
        mHelper.createNotificationChannel(PKG_O, UID_O, getChannel(), true, false);
        mHelper.createNotificationChannelGroup(
                PKG_O, UID_O, new NotificationChannelGroup("1", "bye"), true);
        mHelper.lockChannelsForOEM(pkg.toArray(new String[]{}));
        mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, pkgPair);
        mHelper.setNotificationDelegate(PKG_O, UID_O, "", 1);
        mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_NONE);
        mHelper.setBubblesAllowed(PKG_O, UID_O, false);
        mHelper.setShowBadge(PKG_O, UID_O, false);
        mHelper.setAppImportanceLocked(PKG_O, UID_O);

        mHelper.clearData(PKG_O, UID_O);

        assertEquals(IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG_O, UID_O));
        assertTrue(mHelper.areBubblesAllowed(PKG_O, UID_O));
        assertTrue(mHelper.canShowBadge(PKG_O, UID_O));
        assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
        assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O));
        assertEquals(0, mHelper.getNotificationChannels(PKG_O, UID_O, true).getList().size());
        assertEquals(0, mHelper.getNotificationChannelGroups(PKG_O, UID_O).size());

        NotificationChannel channel = getChannel();
        mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false);

        assertTrue(channel.isImportanceLockedByCriticalDeviceFunction());
        assertTrue(channel.isImportanceLockedByOEM());
    }

    @Test
    public void testRecordDefaults() throws Exception {
        assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG_N_MR1,
                UID_N_MR1));
        assertEquals(true, mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
        assertEquals(1, mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList().size());
    }

    @Test
    public void testCreateGroup() {
        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);
        assertEquals(ncg,
                mHelper.getNotificationChannelGroups(PKG_N_MR1, UID_N_MR1).iterator().next());
        verify(mHandler, never()).requestSort();
    }

    @Test
    public void testCannotCreateChannel_badGroup() {
        NotificationChannel channel1 =
                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
        channel1.setGroup("garbage");
        try {
            mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false);
            fail("Created a channel with a bad group");
        } catch (IllegalArgumentException e) {
        }
    }

    @Test
    public void testCannotCreateChannel_goodGroup() {
        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);
        NotificationChannel channel1 =
                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
        channel1.setGroup(ncg.getId());
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false);

        assertEquals(ncg.getId(), mHelper.getNotificationChannel(
                PKG_N_MR1, UID_N_MR1, channel1.getId(), false).getGroup());
    }

    @Test
    public void testGetChannelGroups() {
        NotificationChannelGroup unused = new NotificationChannelGroup("unused", "s");
        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, unused, true);
        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);
        NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg2, true);

        NotificationChannel channel1 =
                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
        channel1.setGroup(ncg.getId());
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false);
        NotificationChannel channel1a =
                new NotificationChannel("id1a", "name1", NotificationManager.IMPORTANCE_HIGH);
        channel1a.setGroup(ncg.getId());
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1a, true, false);

        NotificationChannel channel2 =
                new NotificationChannel("id2", "name1", NotificationManager.IMPORTANCE_HIGH);
        channel2.setGroup(ncg2.getId());
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, true, false);

        NotificationChannel channel3 =
                new NotificationChannel("id3", "name1", NotificationManager.IMPORTANCE_HIGH);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel3, true, false);

        List<NotificationChannelGroup> actual = mHelper.getNotificationChannelGroups(
                PKG_N_MR1, UID_N_MR1, true, true, false).getList();
        assertEquals(3, actual.size());
        for (NotificationChannelGroup group : actual) {
            if (group.getId() == null) {
                assertEquals(2, group.getChannels().size()); // misc channel too
                assertTrue(channel3.getId().equals(group.getChannels().get(0).getId())
                        || channel3.getId().equals(group.getChannels().get(1).getId()));
            } else if (group.getId().equals(ncg.getId())) {
                assertEquals(2, group.getChannels().size());
                if (group.getChannels().get(0).getId().equals(channel1.getId())) {
                    assertTrue(group.getChannels().get(1).getId().equals(channel1a.getId()));
                } else if (group.getChannels().get(0).getId().equals(channel1a.getId())) {
                    assertTrue(group.getChannels().get(1).getId().equals(channel1.getId()));
                } else {
                    fail("expected channel not found");
                }
            } else if (group.getId().equals(ncg2.getId())) {
                assertEquals(1, group.getChannels().size());
                assertEquals(channel2.getId(), group.getChannels().get(0).getId());
            }
        }
    }

    @Test
    public void testGetChannelGroups_noSideEffects() {
        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);

        NotificationChannel channel1 =
                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
        channel1.setGroup(ncg.getId());
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false);
        mHelper.getNotificationChannelGroups(PKG_N_MR1, UID_N_MR1, true, true, false).getList();

        channel1.setImportance(IMPORTANCE_LOW);
        mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true);

        List<NotificationChannelGroup> actual = mHelper.getNotificationChannelGroups(
                PKG_N_MR1, UID_N_MR1, true, true, false).getList();

        assertEquals(2, actual.size());
        for (NotificationChannelGroup group : actual) {
            if (Objects.equals(group.getId(), ncg.getId())) {
                assertEquals(1, group.getChannels().size());
            }
        }
    }

    @Test
    public void testGetChannelGroups_includeEmptyGroups() {
        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);
        NotificationChannelGroup ncgEmpty = new NotificationChannelGroup("group2", "name2");
        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncgEmpty, true);

        NotificationChannel channel1 =
                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
        channel1.setGroup(ncg.getId());
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false);

        List<NotificationChannelGroup> actual = mHelper.getNotificationChannelGroups(
                PKG_N_MR1, UID_N_MR1, false, false, true).getList();

        assertEquals(2, actual.size());
        for (NotificationChannelGroup group : actual) {
            if (Objects.equals(group.getId(), ncg.getId())) {
                assertEquals(1, group.getChannels().size());
            }
            if (Objects.equals(group.getId(), ncgEmpty.getId())) {
                assertEquals(0, group.getChannels().size());
            }
        }
    }

    @Test
    public void testCreateChannel_updateName() {
        NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
        assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false));
        NotificationChannel actual =
                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, "id", false);
        assertEquals("hello", actual.getName());

        nc = new NotificationChannel("id", "goodbye", IMPORTANCE_HIGH);
        assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false));

        actual = mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, "id", false);
        assertEquals("goodbye", actual.getName());
        assertEquals(IMPORTANCE_DEFAULT, actual.getImportance());

        verify(mHandler, times(1)).requestSort();
    }

    @Test
    public void testCreateChannel_addToGroup() {
        NotificationChannelGroup group = new NotificationChannelGroup("group", "");
        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true);
        NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
        assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false));
        NotificationChannel actual =
                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, "id", false);
        assertNull(actual.getGroup());

        nc = new NotificationChannel("id", "hello", IMPORTANCE_HIGH);
        nc.setGroup(group.getId());
        assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false));

        actual = mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, "id", false);
        assertNotNull(actual.getGroup());
        assertEquals(IMPORTANCE_DEFAULT, actual.getImportance());

        verify(mHandler, times(1)).requestSort();
    }

    @Test
    public void testDumpChannelsJson() throws Exception {
        final ApplicationInfo upgrade = new ApplicationInfo();
        upgrade.targetSdkVersion = Build.VERSION_CODES.O;
        try {
            when(mPm.getApplicationInfoAsUser(
                    anyString(), anyInt(), anyInt())).thenReturn(upgrade);
        } catch (PackageManager.NameNotFoundException e) {
        }
        ArrayMap<String, Integer> expectedChannels = new ArrayMap<>();
        int numPackages = ThreadLocalRandom.current().nextInt(1, 5);
        for (int i = 0; i < numPackages; i++) {
            String pkgName = "pkg" + i;
            int numChannels = ThreadLocalRandom.current().nextInt(1, 10);
            for (int j = 0; j < numChannels; j++) {
                mHelper.createNotificationChannel(pkgName, UID_N_MR1,
                        new NotificationChannel("" + j, "a", IMPORTANCE_HIGH), true, false);
            }
            expectedChannels.put(pkgName, numChannels);
        }

        // delete the first channel of the first package
        String pkg = expectedChannels.keyAt(0);
        mHelper.deleteNotificationChannel("pkg" + 0, UID_N_MR1, "0");
        // dump should not include deleted channels
        int count = expectedChannels.get(pkg);
        expectedChannels.put(pkg, count - 1);

        JSONArray actual = mHelper.dumpChannelsJson(new NotificationManagerService.DumpFilter());
        assertEquals(numPackages, actual.length());
        for (int i = 0; i < numPackages; i++) {
            JSONObject object = actual.getJSONObject(i);
            assertTrue(expectedChannels.containsKey(object.get("packageName")));
            assertEquals(expectedChannels.get(object.get("packageName")).intValue(),
                    object.getInt("channelCount"));
        }
    }

    @Test
    public void testBadgingOverrideTrue() throws Exception {
        Secure.putIntForUser(getContext().getContentResolver(),
                Secure.NOTIFICATION_BADGING, 1,
                USER.getIdentifier());
        mHelper.updateBadgingEnabled(); // would be called by settings observer
        assertTrue(mHelper.badgingEnabled(USER));
    }

    @Test
    public void testBadgingOverrideFalse() throws Exception {
        Secure.putIntForUser(getContext().getContentResolver(),
                Secure.NOTIFICATION_BADGING, 0,
                USER.getIdentifier());
        mHelper.updateBadgingEnabled(); // would be called by settings observer
        assertFalse(mHelper.badgingEnabled(USER));
    }

    @Test
    public void testBadgingForUserAll() throws Exception {
        try {
            mHelper.badgingEnabled(UserHandle.ALL);
        } catch (Exception e) {
            fail("just don't throw");
        }
    }

    @Test
    public void testBadgingOverrideUserIsolation() throws Exception {
        Secure.putIntForUser(getContext().getContentResolver(),
                Secure.NOTIFICATION_BADGING, 0,
                USER.getIdentifier());
        Secure.putIntForUser(getContext().getContentResolver(),
                Secure.NOTIFICATION_BADGING, 1,
                USER2.getIdentifier());
        mHelper.updateBadgingEnabled(); // would be called by settings observer
        assertFalse(mHelper.badgingEnabled(USER));
        assertTrue(mHelper.badgingEnabled(USER2));
    }

    @Test
    public void testBubblesOverrideTrue() {
        Global.putInt(getContext().getContentResolver(),
                Global.NOTIFICATION_BUBBLES, 1);
        mHelper.updateBubblesEnabled(); // would be called by settings observer
        assertTrue(mHelper.bubblesEnabled());
    }

    @Test
    public void testBubblesOverrideFalse() {
        Global.putInt(getContext().getContentResolver(),
                Global.NOTIFICATION_BUBBLES, 0);
        mHelper.updateBubblesEnabled(); // would be called by settings observer
        assertFalse(mHelper.bubblesEnabled());
    }

    @Test
    public void testOnLocaleChanged_updatesDefaultChannels() throws Exception {
        String newLabel = "bananas!";
        final NotificationChannel defaultChannel = mHelper.getNotificationChannel(PKG_N_MR1,
                UID_N_MR1,
                NotificationChannel.DEFAULT_CHANNEL_ID, false);
        assertFalse(newLabel.equals(defaultChannel.getName()));

        Resources res = mock(Resources.class);
        when(mContext.getResources()).thenReturn(res);
        when(res.getString(com.android.internal.R.string.default_notification_channel_label))
                .thenReturn(newLabel);

        mHelper.onLocaleChanged(mContext, USER.getIdentifier());

        assertEquals(newLabel, mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1,
                NotificationChannel.DEFAULT_CHANNEL_ID, false).getName());
    }

    @Test
    public void testIsGroupBlocked_noGroup() throws Exception {
        assertFalse(mHelper.isGroupBlocked(PKG_N_MR1, UID_N_MR1, null));

        assertFalse(mHelper.isGroupBlocked(PKG_N_MR1, UID_N_MR1, "non existent group"));
    }

    @Test
    public void testIsGroupBlocked_notBlocked() throws Exception {
        NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true);

        assertFalse(mHelper.isGroupBlocked(PKG_N_MR1, UID_N_MR1, group.getId()));
    }

    @Test
    public void testIsGroupBlocked_blocked() throws Exception {
        NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true);
        group.setBlocked(true);
        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, false);

        assertTrue(mHelper.isGroupBlocked(PKG_N_MR1, UID_N_MR1, group.getId()));
    }

    @Test
    public void testIsGroup_appCannotResetBlock() throws Exception {
        NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true);
        NotificationChannelGroup group2 = group.clone();
        group2.setBlocked(true);
        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group2, false);
        assertTrue(mHelper.isGroupBlocked(PKG_N_MR1, UID_N_MR1, group.getId()));

        NotificationChannelGroup group3 = group.clone();
        group3.setBlocked(false);
        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group3, true);
        assertTrue(mHelper.isGroupBlocked(PKG_N_MR1, UID_N_MR1, group.getId()));
    }

    @Test
    public void testGetNotificationChannelGroupWithChannels() throws Exception {
        NotificationChannelGroup group = new NotificationChannelGroup("group", "");
        NotificationChannelGroup other = new NotificationChannelGroup("something else", "");
        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true);
        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, other, true);

        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
        a.setGroup(group.getId());
        NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_DEFAULT);
        b.setGroup(other.getId());
        NotificationChannel c = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT);
        c.setGroup(group.getId());
        NotificationChannel d = new NotificationChannel("d", "d", IMPORTANCE_DEFAULT);

        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, a, true, false);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, b, true, false);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, c, true, false);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, d, true, false);
        mHelper.deleteNotificationChannel(PKG_N_MR1, UID_N_MR1, c.getId());

        NotificationChannelGroup retrieved = mHelper.getNotificationChannelGroupWithChannels(
                PKG_N_MR1, UID_N_MR1, group.getId(), true);
        assertEquals(2, retrieved.getChannels().size());
        compareChannels(a, findChannel(retrieved.getChannels(), a.getId()));
        compareChannels(c, findChannel(retrieved.getChannels(), c.getId()));

        retrieved = mHelper.getNotificationChannelGroupWithChannels(
                PKG_N_MR1, UID_N_MR1, group.getId(), false);
        assertEquals(1, retrieved.getChannels().size());
        compareChannels(a, findChannel(retrieved.getChannels(), a.getId()));
    }

    @Test
    public void testAndroidPkgCannotBypassDnd_creation() {
        NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
        test.setBypassDnd(true);

        mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, test, true, false);

        assertFalse(mHelper.getNotificationChannel(SYSTEM_PKG, SYSTEM_UID, "A", false)
                .canBypassDnd());
    }

    @Test
    public void testDndPkgCanBypassDnd_creation() {
        NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
        test.setBypassDnd(true);

        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, test, true, true);

        assertTrue(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, "A", false).canBypassDnd());
    }

    @Test
    public void testNormalPkgCannotBypassDnd_creation() {
        NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
        test.setBypassDnd(true);

        mHelper.createNotificationChannel(PKG_N_MR1, 1000, test, true, false);

        assertFalse(mHelper.getNotificationChannel(PKG_N_MR1, 1000, "A", false).canBypassDnd());
    }

    @Test
    public void testAndroidPkgCannotBypassDnd_update() throws Exception {
        NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
        mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, test, true, false);

        NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW);
        update.setBypassDnd(true);
        assertFalse(mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, update, true, false));

        assertFalse(mHelper.getNotificationChannel(SYSTEM_PKG, SYSTEM_UID, "A", false)
                .canBypassDnd());
    }

    @Test
    public void testDndPkgCanBypassDnd_update() throws Exception {
        NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, test, true, true);

        NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW);
        update.setBypassDnd(true);
        assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, update, true, true));

        assertTrue(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, "A", false).canBypassDnd());
    }

    @Test
    public void testNormalPkgCannotBypassDnd_update() {
        NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
        mHelper.createNotificationChannel(PKG_N_MR1, 1000, test, true, false);
        NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW);
        update.setBypassDnd(true);
        mHelper.createNotificationChannel(PKG_N_MR1, 1000, update, true, false);
        assertFalse(mHelper.getNotificationChannel(PKG_N_MR1, 1000, "A", false).canBypassDnd());
    }

    @Test
    public void testGetBlockedAppCount_noApps() {
        assertEquals(0, mHelper.getBlockedAppCount(0));
    }

    @Test
    public void testGetBlockedAppCount_noAppsForUserId() {
        mHelper.setEnabled(PKG_N_MR1, 100, false);
        assertEquals(0, mHelper.getBlockedAppCount(9));
    }

    @Test
    public void testGetBlockedAppCount_appsForUserId() {
        mHelper.setEnabled(PKG_N_MR1, 1020, false);
        mHelper.setEnabled(PKG_N_MR1, 1030, false);
        mHelper.setEnabled(PKG_N_MR1, 1060, false);
        mHelper.setEnabled(PKG_N_MR1, 1000, true);
        assertEquals(3, mHelper.getBlockedAppCount(0));
    }

    @Test
    public void testXml_statusBarIcons_default() throws Exception {
        String preQXml = "<ranking version=\"1\">\n"
                + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
                + "<channel id=\"something\" name=\"name\" importance=\"2\" "
                + "show_badge=\"true\" />\n"
                + "<channel id=\"miscellaneous\" name=\"Uncategorized\" usage=\"5\" "
                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
                + "</package>\n"
                + "</ranking>\n";
        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
        loadByteArrayXml(preQXml.getBytes(), true, UserHandle.USER_SYSTEM);

        assertEquals(PreferencesHelper.DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS,
                mHelper.shouldHideSilentStatusIcons());
    }

    @Test
    public void testXml_statusBarIcons() throws Exception {
        mHelper.setHideSilentStatusIcons(!PreferencesHelper.DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS);

        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
        loadStreamXml(baos, false, UserHandle.USER_ALL);

        assertEquals(!PreferencesHelper.DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS,
                mHelper.shouldHideSilentStatusIcons());
    }

    @Test
    public void testSetNotificationDelegate() {
        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
        assertEquals("other", mHelper.getNotificationDelegate(PKG_O, UID_O));
    }

    @Test
    public void testRevokeNotificationDelegate() {
        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
        mHelper.revokeNotificationDelegate(PKG_O, UID_O);

        assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
    }

    @Test
    public void testRevokeNotificationDelegate_noDelegateExistsNoCrash() {
        mHelper.revokeNotificationDelegate(PKG_O, UID_O);

        assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
    }

    @Test
    public void testToggleNotificationDelegate() {
        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
        mHelper.toggleNotificationDelegate(PKG_O, UID_O, false);

        assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));

        mHelper.toggleNotificationDelegate(PKG_O, UID_O, true);
        assertEquals("other", mHelper.getNotificationDelegate(PKG_O, UID_O));
    }

    @Test
    public void testToggleNotificationDelegate_noDelegateExistsNoCrash() {
        mHelper.toggleNotificationDelegate(PKG_O, UID_O, false);
        assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));

        mHelper.toggleNotificationDelegate(PKG_O, UID_O, true);
        assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
    }

    @Test
    public void testIsDelegateAllowed_noSource() {
        assertFalse(mHelper.isDelegateAllowed("does not exist", -1, "whatever", 0));
    }

    @Test
    public void testIsDelegateAllowed_noDelegate() {
        mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_UNSPECIFIED);

        assertFalse(mHelper.isDelegateAllowed(PKG_O, UID_O, "whatever", 0));
    }

    @Test
    public void testIsDelegateAllowed_delegateDisabledByApp() {
        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
        mHelper.revokeNotificationDelegate(PKG_O, UID_O);

        assertFalse(mHelper.isDelegateAllowed(PKG_O, UID_O, "other", 53));
    }

    @Test
    public void testIsDelegateAllowed_wrongDelegate() {
        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
        mHelper.revokeNotificationDelegate(PKG_O, UID_O);

        assertFalse(mHelper.isDelegateAllowed(PKG_O, UID_O, "banana", 27));
    }

    @Test
    public void testIsDelegateAllowed_delegateDisabledByUser() {
        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
        mHelper.toggleNotificationDelegate(PKG_O, UID_O, false);

        assertFalse(mHelper.isDelegateAllowed(PKG_O, UID_O, "other", 53));
    }

    @Test
    public void testIsDelegateAllowed() {
        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);

        assertTrue(mHelper.isDelegateAllowed(PKG_O, UID_O, "other", 53));
    }

    @Test
    public void testDelegateXml_noDelegate() throws Exception {
        mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_UNSPECIFIED);

        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
        loadStreamXml(baos, false, UserHandle.USER_ALL);

        assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
    }

    @Test
    public void testDelegateXml_delegate() throws Exception {
        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);

        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
        loadStreamXml(baos, false, UserHandle.USER_ALL);

        assertEquals("other", mHelper.getNotificationDelegate(PKG_O, UID_O));
    }

    @Test
    public void testDelegateXml_disabledDelegate() throws Exception {
        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
        mHelper.revokeNotificationDelegate(PKG_O, UID_O);

        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
        loadStreamXml(baos, false, UserHandle.USER_ALL);

        assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
    }

    @Test
    public void testDelegateXml_userDisabledDelegate() throws Exception {
        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
        mHelper.toggleNotificationDelegate(PKG_O, UID_O, false);

        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
        loadStreamXml(baos, false, UserHandle.USER_ALL);

        // appears disabled
        assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));

        // but was loaded and can be toggled back on
        mHelper.toggleNotificationDelegate(PKG_O, UID_O, true);
        assertEquals("other", mHelper.getNotificationDelegate(PKG_O, UID_O));
    }

    @Test
    public void testDelegateXml_entirelyDisabledDelegate() throws Exception {
        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
        mHelper.toggleNotificationDelegate(PKG_O, UID_O, false);
        mHelper.revokeNotificationDelegate(PKG_O, UID_O);

        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
        loadStreamXml(baos, false, UserHandle.USER_ALL);

        // appears disabled
        assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));

        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
        assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));

        mHelper.toggleNotificationDelegate(PKG_O, UID_O, true);
        assertEquals("other", mHelper.getNotificationDelegate(PKG_O, UID_O));
    }

    @Test
    public void testAllowBubbles_defaults() throws Exception {
        assertTrue(mHelper.areBubblesAllowed(PKG_O, UID_O));

        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
        loadStreamXml(baos, false, UserHandle.USER_ALL);

        assertTrue(mHelper.areBubblesAllowed(PKG_O, UID_O));
        assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O));
    }

    @Test
    public void testAllowBubbles_xml() throws Exception {
        mHelper.setBubblesAllowed(PKG_O, UID_O, false);
        assertFalse(mHelper.areBubblesAllowed(PKG_O, UID_O));
        assertEquals(PreferencesHelper.LockableAppFields.USER_LOCKED_BUBBLE,
                mHelper.getAppLockedFields(PKG_O, UID_O));

        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
        loadStreamXml(baos, false, UserHandle.USER_ALL);

        assertFalse(mHelper.areBubblesAllowed(PKG_O, UID_O));
        assertEquals(PreferencesHelper.LockableAppFields.USER_LOCKED_BUBBLE,
                mHelper.getAppLockedFields(PKG_O, UID_O));
    }

    @Test
    public void testLockChannelsForOEM_emptyList() {
        mHelper.lockChannelsForOEM(null);
        mHelper.lockChannelsForOEM(new String[0]);
        // no exception
    }

    @Test
    public void testLockChannelsForOEM_appWide() {
        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
        NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
        NotificationChannel c = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT);
        // different uids, same package
        mHelper.createNotificationChannel(PKG_O, 3, a, true, false);
        mHelper.createNotificationChannel(PKG_O, 3, b, false, false);
        mHelper.createNotificationChannel(PKG_O, 30, c, true, true);

        mHelper.lockChannelsForOEM(new String[] {PKG_O});

        assertTrue(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false)
                .isImportanceLockedByOEM());
        assertTrue(mHelper.getNotificationChannel(PKG_O, 3, b.getId(), false)
                .isImportanceLockedByOEM());
        assertTrue(mHelper.getNotificationChannel(PKG_O, 30, c.getId(), false)
                .isImportanceLockedByOEM());
    }

    @Test
    public void testLockChannelsForOEM_onlyGivenPkg() {
        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
        NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
        mHelper.createNotificationChannel(PKG_O, 3, a, true, false);
        mHelper.createNotificationChannel(PKG_N_MR1, 30, b, false, false);

        mHelper.lockChannelsForOEM(new String[] {PKG_O});

        assertTrue(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false)
                .isImportanceLockedByOEM());
        assertFalse(mHelper.getNotificationChannel(PKG_N_MR1, 30, b.getId(), false)
                .isImportanceLockedByOEM());
    }

    @Test
    public void testLockChannelsForOEM_channelSpecific() {
        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
        NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
        NotificationChannel c = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT);
        // different uids, same package
        mHelper.createNotificationChannel(PKG_O, 3, a, true, false);
        mHelper.createNotificationChannel(PKG_O, 3, b, false, false);
        mHelper.createNotificationChannel(PKG_O, 30, c, true, true);

        mHelper.lockChannelsForOEM(new String[] {PKG_O + ":b", PKG_O + ":c"});

        assertFalse(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false)
                .isImportanceLockedByOEM());
        assertTrue(mHelper.getNotificationChannel(PKG_O, 3, b.getId(), false)
                .isImportanceLockedByOEM());
        assertTrue(mHelper.getNotificationChannel(PKG_O, 30, c.getId(), false)
                .isImportanceLockedByOEM());
    }

    @Test
    public void testLockChannelsForOEM_channelDoesNotExistYet_appWide() {
        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
        NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
        mHelper.createNotificationChannel(PKG_O, 3, a, true, false);

        mHelper.lockChannelsForOEM(new String[] {PKG_O});

        assertTrue(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false)
                .isImportanceLockedByOEM());

        mHelper.createNotificationChannel(PKG_O, 3, b, true, false);
        assertTrue(mHelper.getNotificationChannel(PKG_O, 3, b.getId(), false)
                .isImportanceLockedByOEM());
    }

    @Test
    public void testLockChannelsForOEM_channelDoesNotExistYet_channelSpecific() {
        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
        NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
        mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);

        mHelper.lockChannelsForOEM(new String[] {PKG_O + ":a", PKG_O + ":b"});

        assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
                .isImportanceLockedByOEM());

        mHelper.createNotificationChannel(PKG_O, UID_O, b, true, false);
        assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, b.getId(), false)
                .isImportanceLockedByOEM());
    }

    @Test
    public void testUpdateNotificationChannel_oemLockedImportance() {
        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
        mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);

        mHelper.lockChannelsForOEM(new String[] {PKG_O});

        NotificationChannel update = new NotificationChannel("a", "a", IMPORTANCE_NONE);
        update.setAllowBubbles(false);

        mHelper.updateNotificationChannel(PKG_O, UID_O, update, true);

        assertEquals(IMPORTANCE_HIGH,
                mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).getImportance());
        assertEquals(false,
                mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).canBubble());

        mHelper.updateNotificationChannel(PKG_O, UID_O, update, true);

        assertEquals(IMPORTANCE_HIGH,
                mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).getImportance());
    }

    @Test
    public void testUpdateDefaultApps_add_multiUser() {
        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
        NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
        NotificationChannel c = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT);
        // different uids, same package
        mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
        mHelper.createNotificationChannel(PKG_O, UID_O, b, false, false);
        mHelper.createNotificationChannel(PKG_O, UserHandle.PER_USER_RANGE + 1, c, true, true);

        ArraySet<Pair<String, Integer>> toAdd = new ArraySet<>();
        toAdd.add(new Pair(PKG_O, UID_O));
        mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);

        assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
                .isImportanceLockedByCriticalDeviceFunction());
        assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, b.getId(), false)
                .isImportanceLockedByCriticalDeviceFunction());
        assertFalse(mHelper.getNotificationChannel(
                PKG_O, UserHandle.PER_USER_RANGE + 1, c.getId(), false)
                .isImportanceLockedByCriticalDeviceFunction());
    }

    @Test
    public void testUpdateDefaultApps_add_onlyGivenPkg() {
        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
        NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
        mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, b, false, false);

        ArraySet<Pair<String, Integer>> toAdd = new ArraySet<>();
        toAdd.add(new Pair(PKG_O, UID_O));
        mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);

        assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
                .isImportanceLockedByCriticalDeviceFunction());
        assertFalse(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, b.getId(), false)
                .isImportanceLockedByCriticalDeviceFunction());
    }

    @Test
    public void testUpdateDefaultApps_remove() {
        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
        NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
        // different uids, same package
        mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
        mHelper.createNotificationChannel(PKG_O, UID_O, b, false, false);

        ArraySet<Pair<String, Integer>> toAdd = new ArraySet<>();
        toAdd.add(new Pair(PKG_O, UID_O));
        mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);

        assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
                .isImportanceLockedByCriticalDeviceFunction());
        assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, b.getId(), false)
                .isImportanceLockedByCriticalDeviceFunction());

        ArraySet<String> toRemove = new ArraySet<>();
        toRemove.add(PKG_O);
        mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), toRemove, null);

        assertFalse(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
                .isImportanceLockedByCriticalDeviceFunction());
        assertFalse(mHelper.getNotificationChannel(PKG_O, UID_O, b.getId(), false)
                .isImportanceLockedByCriticalDeviceFunction());
    }

    @Test
    public void testUpdateDefaultApps_addAndRemove() {
        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
        NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
        mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, b, false, false);

        ArraySet<Pair<String, Integer>> toAdd = new ArraySet<>();
        toAdd.add(new Pair(PKG_O, UID_O));
        mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);


        assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
                .isImportanceLockedByCriticalDeviceFunction());
        assertFalse(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, b.getId(), false)
                .isImportanceLockedByCriticalDeviceFunction());

        // now the default is PKG_N_MR1
        ArraySet<String> toRemove = new ArraySet<>();
        toRemove.add(PKG_O);
        toAdd = new ArraySet<>();
        toAdd.add(new Pair(PKG_N_MR1, UID_N_MR1));
        mHelper.updateDefaultApps(USER.getIdentifier(), toRemove, toAdd);

        assertFalse(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
                .isImportanceLockedByCriticalDeviceFunction());
        assertTrue(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, b.getId(), false)
                .isImportanceLockedByCriticalDeviceFunction());
    }

    @Test
    public void testUpdateDefaultApps_appDoesNotExist_noCrash() {
        ArraySet<Pair<String, Integer>> toAdd = new ArraySet<>();
        toAdd.add(new Pair(PKG_O, UID_O));
        ArraySet<String> toRemove = new ArraySet<>();
        toRemove.add(PKG_N_MR1);
        mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), toRemove, toAdd);
    }

    @Test
    public void testUpdateDefaultApps_channelDoesNotExistYet() {
        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
        NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
        mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);

        ArraySet<Pair<String, Integer>> toAdd = new ArraySet<>();
        toAdd.add(new Pair(PKG_O, UID_O));
        mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);

        assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
                .isImportanceLockedByCriticalDeviceFunction());

        mHelper.createNotificationChannel(PKG_O, UID_O, b, true, false);
        assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, b.getId(), false)
                .isImportanceLockedByCriticalDeviceFunction());
    }

    @Test
    public void testUpdateNotificationChannel_defaultAppLockedImportance() {
        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
        mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
        ArraySet<Pair<String, Integer>> toAdd = new ArraySet<>();
        toAdd.add(new Pair(PKG_O, UID_O));
        mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);

        NotificationChannel update = new NotificationChannel("a", "a", IMPORTANCE_NONE);
        update.setAllowBubbles(false);

        mHelper.updateNotificationChannel(PKG_O, UID_O, update, true);
        assertEquals(IMPORTANCE_HIGH,
                mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).getImportance());
        assertEquals(false,
                mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).canBubble());

        mHelper.updateNotificationChannel(PKG_O, UID_O, update, false);
        assertEquals(IMPORTANCE_HIGH,
                mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).getImportance());

        NotificationChannel updateImportanceLow = new NotificationChannel("a", "a",
                IMPORTANCE_LOW);
        mHelper.updateNotificationChannel(PKG_O, UID_O, updateImportanceLow, true);
        assertEquals(IMPORTANCE_LOW,
                mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).getImportance());
    }

    @Test
    public void testDefaultApp_appHasNoSettingsYet() {
        ArraySet<Pair<String, Integer>> toAdd = new ArraySet<>();
        toAdd.add(new Pair(PKG_O, UID_O));
        mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);

        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
        mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);

        assertTrue(a.isImportanceLockedByCriticalDeviceFunction());
    }

    @Test
    public void testChannelXml_backupDefaultApp() throws Exception {
        NotificationChannel channel1 =
                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);

        mHelper.createNotificationChannel(PKG_O, UID_O, channel1, true, false);

        // clear data
        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, true,
                UserHandle.USER_SYSTEM, channel1.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
        mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG_O}, new int[]{
                UID_O});

        ArraySet<Pair<String, Integer>> toAdd = new ArraySet<>();
        toAdd.add(new Pair(PKG_O, UID_O));
        mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);

        XmlPullParser parser = Xml.newPullParser();
        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
                null);
        parser.nextTag();
        mHelper.readXml(parser, true, UserHandle.USER_SYSTEM);

        assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, channel1.getId(), false)
                .isImportanceLockedByCriticalDeviceFunction());
    }

    @Test
    public void testSetBubblesAllowed_false() {
        mHelper.setBubblesAllowed(PKG_O, UID_O, false);
        assertFalse(mHelper.areBubblesAllowed(PKG_O, UID_O));
        verify(mHandler, times(1)).requestSort();
    }

    @Test
    public void testTooManyChannels() {
        for (int i = 0; i < NOTIFICATION_CHANNEL_COUNT_LIMIT; i++) {
            NotificationChannel channel = new NotificationChannel(String.valueOf(i),
                    String.valueOf(i), NotificationManager.IMPORTANCE_HIGH);
            mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, true);
        }
        try {
            NotificationChannel channel = new NotificationChannel(
                    String.valueOf(NOTIFICATION_CHANNEL_COUNT_LIMIT),
                    String.valueOf(NOTIFICATION_CHANNEL_COUNT_LIMIT),
                    NotificationManager.IMPORTANCE_HIGH);
            mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, true);
            fail("Allowed to create too many notification channels");
        } catch (IllegalStateException e) {
            // great
        }
    }

    @Test
    public void testTooManyChannels_xml() throws Exception {
        String extraChannel = "EXTRA";
        String extraChannel1 = "EXTRA1";

        // create first... many... directly so we don't need a big xml blob in this test
        for (int i = 0; i < NOTIFICATION_CHANNEL_COUNT_LIMIT; i++) {
            NotificationChannel channel = new NotificationChannel(String.valueOf(i),
                    String.valueOf(i), NotificationManager.IMPORTANCE_HIGH);
            mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, true);
        }

        final String xml = "<ranking version=\"1\">\n"
                + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n"
                + "<channel id=\"" + extraChannel + "\" name=\"hi\" importance=\"3\"/>"
                + "<channel id=\"" + extraChannel1 + "\" name=\"hi\" importance=\"3\"/>"
                + "</package>"
                + "</ranking>";
        XmlPullParser parser = Xml.newPullParser();
        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())),
                null);
        parser.nextTag();
        mHelper.readXml(parser, false, UserHandle.USER_ALL);

        assertNull(mHelper.getNotificationChannel(PKG_O, UID_O, extraChannel, true));
        assertNull(mHelper.getNotificationChannel(PKG_O, UID_O, extraChannel1, true));
    }
}
