Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5fe2eae6 authored by Julia Reynolds's avatar Julia Reynolds
Browse files

Fix backup and restore of package blocks

-Restore block status for apps that already exist
E.g. apps on the system image
- Stop depending on appops, it's duplicative and
not backuped/restored

Fixes: 38340377
Test: runtest systemui-notification, toast and notification
cts tests, backup android, clear data on packages, restore android,
verify that blocked apps stay blocked.
Change-Id: I29a33d7db2701bd64a7b30411dcf77c0a2f321d1
parent afea5e8c
Loading
Loading
Loading
Loading
+11 −75
Original line number Diff line number Diff line
@@ -90,7 +90,6 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.media.AudioManager;
@@ -114,7 +113,6 @@ import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.Vibrator;
import android.os.VibrationEffect;
import android.provider.Settings;
@@ -473,19 +471,6 @@ public class NotificationManagerService extends SystemService {
        out.endDocument();
    }

    /** Use this when you actually want to post a notification or toast.
     *
     * Unchecked. Not exposed via Binder, but can be called in the course of enqueue*().
     */
    private boolean noteNotificationOp(String pkg, int uid) {
        if (mAppOps.noteOpNoThrow(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg)
                != AppOpsManager.MODE_ALLOWED) {
            Slog.v(TAG, "notifications are disabled by AppOps for " + pkg);
            return false;
        }
        return true;
    }

    /** Use this to check if a package can post a notification or toast. */
    private boolean checkNotificationOp(String pkg, int uid) {
        return mAppOps.checkOp(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg)
@@ -1154,7 +1139,7 @@ public class NotificationManagerService extends SystemService {
        final File systemDir = new File(Environment.getDataDirectory(), "system");
        mPolicyFile = new AtomicFile(new File(systemDir, "notification_policy.xml"));

        syncBlockDb();
        loadPolicyFile();

        // This is a ManagedServices object that keeps track of the listeners.
        mListeners = notificationListeners;
@@ -1267,46 +1252,6 @@ public class NotificationManagerService extends SystemService {
                .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), UserHandle.ALL, null);
    }

    /**
     * Make sure the XML config and the the AppOps system agree about blocks.
     */
    private void syncBlockDb() {
        loadPolicyFile();

        // sync bans from ranker into app opps
        Map<Integer, String> packageBans = mRankingHelper.getPackageBans();
        for(Entry<Integer, String> ban : packageBans.entrySet()) {
            final int uid = ban.getKey();
            final String packageName = ban.getValue();
            setNotificationsEnabledForPackageImpl(packageName, uid, false);
        }

        // sync bans from app opps into ranker
        packageBans.clear();
        for (UserInfo user : UserManager.get(getContext()).getUsers()) {
            final int userId = user.getUserHandle().getIdentifier();
            final PackageManager packageManager = getContext().getPackageManager();
            List<PackageInfo> packages = packageManager.getInstalledPackagesAsUser(0, userId);
            final int packageCount = packages.size();
            for (int p = 0; p < packageCount; p++) {
                final String packageName = packages.get(p).packageName;
                try {
                    final int uid = packageManager.getPackageUidAsUser(packageName, userId);
                    if (!checkNotificationOp(packageName, uid)) {
                        packageBans.put(uid, packageName);
                    }
                } catch (NameNotFoundException e) {
                    // forget you
                }
            }
        }
        for (Entry<Integer, String> ban : packageBans.entrySet()) {
            mRankingHelper.setImportance(ban.getValue(), ban.getKey(), IMPORTANCE_NONE);
        }

        savePolicyFile();
    }

    @Override
    public void onBootPhase(int phase) {
        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
@@ -1328,19 +1273,6 @@ public class NotificationManagerService extends SystemService {
        }
    }

    void setNotificationsEnabledForPackageImpl(String pkg, int uid, boolean enabled) {
        Slog.v(TAG, (enabled?"en":"dis") + "abling notifications for " + pkg);

        mAppOps.setMode(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg,
                enabled ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);

        // Now, cancel any outstanding notifications that are part of a just-disabled app
        if (ENABLE_BLOCKED_NOTIFICATIONS && !enabled) {
            cancelAllNotificationsInt(MY_UID, MY_PID, pkg, null, 0, 0, true,
                    UserHandle.getUserId(uid), REASON_PACKAGE_BANNED, null);
        }
    }

    private void updateListenerHintsLocked() {
        final int hints = calculateHints();
        if (hints == mListenerHints) return;
@@ -1522,7 +1454,8 @@ public class NotificationManagerService extends SystemService {
                    isPackageSuspendedForUser(pkg, Binder.getCallingUid());

            if (ENABLE_BLOCKED_TOASTS && !isSystemToast &&
                    (!noteNotificationOp(pkg, Binder.getCallingUid()) || isPackageSuspended)) {
                    (!areNotificationsEnabledForPackage(pkg, Binder.getCallingUid())
                            || isPackageSuspended)) {
                Slog.e(TAG, "Suppressing toast from package " + pkg
                        + (isPackageSuspended
                                ? " due to package suspended by administrator."
@@ -1643,8 +1576,12 @@ public class NotificationManagerService extends SystemService {
        public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) {
            checkCallerIsSystem();

            setNotificationsEnabledForPackageImpl(pkg, uid, enabled);
            mRankingHelper.setEnabled(pkg, uid, enabled);
            // Now, cancel any outstanding notifications that are part of a just-disabled app
            if (ENABLE_BLOCKED_NOTIFICATIONS && !enabled) {
                cancelAllNotificationsInt(MY_UID, MY_PID, pkg, null, 0, 0, true,
                        UserHandle.getUserId(uid), REASON_PACKAGE_BANNED, null);
            }
            savePolicyFile();
        }

@@ -1662,8 +1599,8 @@ public class NotificationManagerService extends SystemService {
        @Override
        public boolean areNotificationsEnabledForPackage(String pkg, int uid) {
            checkCallerIsSystemOrSameApp(pkg);
            return (mAppOps.checkOpNoThrow(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg)
                    == AppOpsManager.MODE_ALLOWED) && !isPackageSuspendedForUser(pkg, uid);

            return mRankingHelper.getImportance(pkg, uid) != IMPORTANCE_NONE;
        }

        @Override
@@ -3400,8 +3337,7 @@ public class NotificationManagerService extends SystemService {
        }

        final boolean isBlocked = r.getImportance() == NotificationManager.IMPORTANCE_NONE
                || r.getChannel().getImportance() == NotificationManager.IMPORTANCE_NONE
                || !noteNotificationOp(pkg, callingUid);
                || r.getChannel().getImportance() == NotificationManager.IMPORTANCE_NONE;
        if (isBlocked) {
            Slog.e(TAG, "Suppressing notification from package by user request.");
            usageStats.registerBlocked(r);
+4 −4
Original line number Diff line number Diff line
@@ -21,8 +21,6 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.util.Preconditions;

import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
@@ -35,8 +33,6 @@ import android.content.pm.ParceledListSlice;
import android.metrics.LogMaker;
import android.os.Build;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.service.notification.NotificationListenerService.Ranking;
import android.text.TextUtils;
@@ -189,6 +185,10 @@ public class RankingHelper implements RankingConfig {
                                safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY),
                                safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
                                safeBool(parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE));
                        r.importance = safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
                        r.priority = safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY);
                        r.visibility = safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
                        r.showBadge = safeBool(parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);

                        final int innerDepth = parser.getDepth();
                        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+76 −19
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import static junit.framework.Assert.fail;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;

@@ -50,15 +51,11 @@ import android.net.Uri;
import android.os.Build;
import android.os.UserHandle;
import android.provider.Settings.Secure;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.TestableContext;
import android.testing.TestableSettingsProvider;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.Xml;

import java.io.BufferedInputStream;
@@ -87,10 +84,10 @@ import static org.mockito.Mockito.when;
public class RankingHelperTest extends NotificationTestCase {
    private static final String PKG = "com.android.server.notification";
    private static final int UID = 0;
    private static final UserHandle USER = UserHandle.getUserHandleForUid(UID);
    private static final UserHandle USER = UserHandle.of(0);
    private static final String UPDATED_PKG = "updatedPkg";
    private static final int UID2 = 1111111;
    private static final UserHandle USER2 = UserHandle.getUserHandleForUid(UID2);
    private static final int UID2 = 1111;
    private static final UserHandle USER2 = UserHandle.of(10);
    private static final String TEST_CHANNEL_ID = "test_channel_id";

    @Mock NotificationUsageStats mUsageStats;
@@ -199,24 +196,21 @@ public class RankingHelperTest extends NotificationTestCase {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
        serializer.startDocument(null, true);
        serializer.startTag(null, "ranking");
        mHelper.writeXml(serializer, forBackup);
        serializer.endTag(null, "ranking");
        serializer.endDocument();
        serializer.flush();

        for (String channelId : channelIds) {
            mHelper.permanentlyDeleteNotificationChannel(pkg, uid, channelId);
        }
        return baos;
    }

    private void loadStreamXml(ByteArrayOutputStream stream) throws Exception {
    private void loadStreamXml(ByteArrayOutputStream stream, boolean forRestore) throws Exception {
        XmlPullParser parser = Xml.newPullParser();
        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(stream.toByteArray())),
                null);
        parser.nextTag();
        mHelper.readXml(parser, false);
        mHelper.readXml(parser, forRestore);
    }

    private void compareChannels(NotificationChannel expected, NotificationChannel actual) {
@@ -323,8 +317,72 @@ public class RankingHelperTest extends NotificationTestCase {
                channel2.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
        mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG}, new int[]{UID});

        loadStreamXml(baos);
        loadStreamXml(baos, false);

        assertTrue(mHelper.canShowBadge(PKG, UID));
        assertEquals(channel1, mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
        compareChannels(channel2,
                mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));

        List<NotificationChannelGroup> actualGroups =
                mHelper.getNotificationChannelGroups(PKG, UID, 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(new Uri.Builder().scheme("test").build(), mAudioAttributes);
        channel2.enableLights(true);
        channel2.setBypassDnd(true);
        channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
        channel2.enableVibration(false);
        channel2.setGroup(ncg.getId());
        channel2.setLightColor(Color.BLUE);

        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
        mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
        mHelper.createNotificationChannel(PKG, UID, channel1, true);
        mHelper.createNotificationChannel(PKG, UID, channel2, false);
        mHelper.createNotificationChannel(UPDATED_PKG, UID2, getChannel(), true);

        mHelper.setShowBadge(PKG, UID, true);

        mHelper.setImportance(UPDATED_PKG, UID2, IMPORTANCE_NONE);

        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel1.getId(),
                channel2.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
        mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG, UPDATED_PKG}, new int[]{UID, UID2});

        mHelper.setShowBadge(UPDATED_PKG, UID2, true);

        loadStreamXml(baos, true);

        assertEquals(IMPORTANCE_NONE, mHelper.getImportance(UPDATED_PKG, UID2));
        assertTrue(mHelper.canShowBadge(PKG, UID));
        assertEquals(channel1, mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
        compareChannels(channel2,
@@ -397,7 +455,7 @@ public class RankingHelperTest extends NotificationTestCase {
        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
                NotificationChannel.DEFAULT_CHANNEL_ID);

        loadStreamXml(baos);
        loadStreamXml(baos, false);

        final NotificationChannel updated = mHelper.getNotificationChannel(PKG, UID,
                NotificationChannel.DEFAULT_CHANNEL_ID, false);
@@ -417,7 +475,7 @@ public class RankingHelperTest extends NotificationTestCase {
        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
                NotificationChannel.DEFAULT_CHANNEL_ID);

        loadStreamXml(baos);
        loadStreamXml(baos, false);

        assertEquals(NotificationManager.IMPORTANCE_LOW, mHelper.getNotificationChannel(
                PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false).getImportance());
@@ -465,7 +523,7 @@ public class RankingHelperTest extends NotificationTestCase {
        final ApplicationInfo upgraded = new ApplicationInfo();
        upgraded.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
        when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(upgraded);
        loadStreamXml(baos);
        loadStreamXml(baos, false);

        // Default Channel should be gone.
        assertEquals(null, mHelper.getNotificationChannel(PKG, UID,
@@ -483,7 +541,7 @@ public class RankingHelperTest extends NotificationTestCase {
        final ApplicationInfo upgraded = new ApplicationInfo();
        upgraded.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
        when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(upgraded);
        loadStreamXml(baos);
        loadStreamXml(baos, false);

        // Default Channel should be gone.
        assertEquals(null, mHelper.getNotificationChannel(PKG, UID,
@@ -497,7 +555,7 @@ public class RankingHelperTest extends NotificationTestCase {
        mHelper.createNotificationChannel(PKG, UID,
                new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true);

        loadStreamXml(baos);
        loadStreamXml(baos, false);

        // Should still have the newly created channel that wasn't in the xml.
        assertTrue(mHelper.getNotificationChannel(PKG, UID, "bananas", false) != null);
@@ -1271,5 +1329,4 @@ public class RankingHelperTest extends NotificationTestCase {
        assertFalse(mHelper.badgingEnabled(USER));
        assertTrue(mHelper.badgingEnabled(USER2));
    }

}