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

Commit 35ef0a62 authored by John Spurlock's avatar John Spurlock
Browse files

Backup/restore notification policy.

 - Include zen + ranking config in the backup payload.
 - Owner user only for now.
 - For the most part, reuse existing policy file structure
   as the payload format, but elide items that do not survive
   across devices (info for secondary users, uids).
 - Also elide the manual zen rule, if set.
 - During restore, keep ranking info for uninstalled apps
   in purgatory until the apps exist.

Bug: 17755700
Change-Id: Iadf71a43cd0efd44fe9a0a29874d60666b6d2076
parent 21258a37
Loading
Loading
Loading
Loading
+81 −38
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import android.app.NotificationManager;
import android.app.NotificationManager.Policy;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.app.backup.BackupManager;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
@@ -113,12 +114,16 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
@@ -330,16 +335,10 @@ public class NotificationManagerService extends SystemService {

    }

    private void loadPolicyFile() {
        if (DBG) Slog.d(TAG, "loadPolicyFile");
        synchronized(mPolicyFile) {
            mBlockedPackages.clear();

            FileInputStream infile = null;
            try {
                infile = mPolicyFile.openRead();
    private void readPolicyXml(InputStream stream, boolean forRestore)
            throws XmlPullParserException, NumberFormatException, IOException {
        final XmlPullParser parser = Xml.newPullParser();
                parser.setInput(infile, StandardCharsets.UTF_8.name());
        parser.setInput(stream, StandardCharsets.UTF_8.name());

        int type;
        String tag;
@@ -362,9 +361,20 @@ public class NotificationManagerService extends SystemService {
                    }
                }
            }
                    mZenModeHelper.readXml(parser);
                    mRankingHelper.readXml(parser);
            mZenModeHelper.readXml(parser, forRestore);
            mRankingHelper.readXml(parser, forRestore);
        }
    }

    private void loadPolicyFile() {
        if (DBG) Slog.d(TAG, "loadPolicyFile");
        synchronized(mPolicyFile) {
            mBlockedPackages.clear();

            FileInputStream infile = null;
            try {
                infile = mPolicyFile.openRead();
                readPolicyXml(infile, false /*forRestore*/);
            } catch (FileNotFoundException e) {
                // No data yet
            } catch (IOException e) {
@@ -396,21 +406,26 @@ public class NotificationManagerService extends SystemService {
            }

            try {
                writePolicyXml(stream, false /*forBackup*/);
                mPolicyFile.finishWrite(stream);
            } catch (IOException e) {
                Slog.w(TAG, "Failed to save policy file, restoring backup", e);
                mPolicyFile.failWrite(stream);
            }
        }
        BackupManager.dataChanged(getContext().getPackageName());
    }

    private void writePolicyXml(OutputStream stream, boolean forBackup) throws IOException {
        final XmlSerializer out = new FastXmlSerializer();
        out.setOutput(stream, StandardCharsets.UTF_8.name());
        out.startDocument(null, true);
        out.startTag(null, TAG_NOTIFICATION_POLICY);
        out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION));
                mZenModeHelper.writeXml(out);
                mRankingHelper.writeXml(out);
        mZenModeHelper.writeXml(out, forBackup);
        mRankingHelper.writeXml(out, forBackup);
        out.endTag(null, TAG_NOTIFICATION_POLICY);
        out.endDocument();
                mPolicyFile.finishWrite(stream);
            } catch (IOException e) {
                Slog.w(TAG, "Failed to save policy file, restoring backup", e);
                mPolicyFile.failWrite(stream);
            }
        }
    }

    /** Use this when you actually want to post a notification or toast.
@@ -714,6 +729,7 @@ public class NotificationManagerService extends SystemService {
                }
                mListeners.onPackagesChanged(queryReplace, pkgList);
                mConditionProviders.onPackagesChanged(queryReplace, pkgList);
                mRankingHelper.onPackagesChanged(queryReplace, pkgList);
            }
        }
    };
@@ -1663,13 +1679,40 @@ public class NotificationManagerService extends SystemService {
        // Backup/restore interface
        @Override
        public byte[] getBackupPayload(int user) {
            // TODO: build a payload of whatever is appropriate
            if (DBG) Slog.d(TAG, "getBackupPayload u=" + user);
            if (user != UserHandle.USER_OWNER) {
                Slog.w(TAG, "getBackupPayload: cannot backup policy for user " + user);
                return null;
            }
            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
            try {
                writePolicyXml(baos, true /*forBackup*/);
                return baos.toByteArray();
            } catch (IOException e) {
                Slog.w(TAG, "getBackupPayload: error writing payload for user " + user, e);
            }
            return null;
        }

        @Override
        public void applyRestore(byte[] payload, int user) {
            // TODO: apply the restored payload as new current state
            if (DBG) Slog.d(TAG, "applyRestore u=" + user + " payload="
                    + (payload != null ? new String(payload, StandardCharsets.UTF_8) : null));
            if (payload == null) {
                Slog.w(TAG, "applyRestore: no payload to restore for user " + user);
                return;
            }
            if (user != UserHandle.USER_OWNER) {
                Slog.w(TAG, "applyRestore: cannot restore policy for user " + user);
                return;
            }
            final ByteArrayInputStream bais = new ByteArrayInputStream(payload);
            try {
                readPolicyXml(bais, true /*forRestore*/);
                savePolicyFile();
            } catch (NumberFormatException | XmlPullParserException | IOException e) {
                Slog.w(TAG, "applyRestore: error reading payload", e);
            }
        }

        @Override
+73 −11
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@ package com.android.server.notification;

import android.app.Notification;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Handler;
import android.os.Message;
import android.os.UserHandle;
@@ -61,6 +63,7 @@ public class RankingHelper implements RankingConfig {

    private final ArrayMap<String, Record> mRecords = new ArrayMap<>(); // pkg|uid => Record
    private final ArrayMap<String, NotificationRecord> mProxyByGroupTmp = new ArrayMap<>();
    private final ArrayMap<String, Record> mRestoredWithoutUids = new ArrayMap<>(); // pkg => Record

    private final Context mContext;
    private final Handler mRankingHandler;
@@ -119,12 +122,15 @@ public class RankingHelper implements RankingConfig {
        }
    }

    public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException {
    public void readXml(XmlPullParser parser, boolean forRestore)
            throws XmlPullParserException, IOException {
        final PackageManager pm = mContext.getPackageManager();
        int type = parser.getEventType();
        if (type != XmlPullParser.START_TAG) return;
        String tag = parser.getName();
        if (!TAG_RANKING.equals(tag)) return;
        mRecords.clear();
        mRestoredWithoutUids.clear();
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
            tag = parser.getName();
            if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
@@ -132,21 +138,38 @@ public class RankingHelper implements RankingConfig {
            }
            if (type == XmlPullParser.START_TAG) {
                if (TAG_PACKAGE.equals(tag)) {
                    int uid = safeInt(parser, ATT_UID, UserHandle.USER_ALL);
                    int uid = safeInt(parser, ATT_UID, Record.UNKNOWN_UID);
                    int priority = safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY);
                    boolean peekable = safeBool(parser, ATT_PEEKABLE, DEFAULT_PEEKABLE);
                    int vis = safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
                    String name = parser.getAttributeValue(null, ATT_NAME);

                    if (!TextUtils.isEmpty(name)) {
                        if (forRestore) {
                            try {
                                uid = pm.getPackageUid(name, UserHandle.USER_OWNER);
                            } catch (NameNotFoundException e) {
                                // noop
                            }
                        }
                        Record r = null;
                        if (uid == Record.UNKNOWN_UID) {
                            r = mRestoredWithoutUids.get(name);
                            if (r == null) {
                                r = new Record();
                                mRestoredWithoutUids.put(name, r);
                            }
                        } else {
                            r = getOrCreateRecord(name, uid);
                        }
                        if (priority != DEFAULT_PRIORITY) {
                            getOrCreateRecord(name, uid).priority = priority;
                            r.priority = priority;
                        }
                        if (peekable != DEFAULT_PEEKABLE) {
                            getOrCreateRecord(name, uid).peekable = peekable;
                            r.peekable = peekable;
                        }
                        if (vis != DEFAULT_VISIBILITY) {
                            getOrCreateRecord(name, uid).visibility = vis;
                            r.visibility = vis;
                        }
                    }
                }
@@ -182,13 +205,16 @@ public class RankingHelper implements RankingConfig {
        }
    }

    public void writeXml(XmlSerializer out) throws IOException {
    public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
        out.startTag(null, TAG_RANKING);
        out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));

        final int N = mRecords.size();
        for (int i = 0; i < N; i++) {
            final Record r = mRecords.valueAt(i);
            if (forBackup && UserHandle.getUserId(r.uid) != UserHandle.USER_OWNER) {
                continue;
            }
            out.startTag(null, TAG_PACKAGE);
            out.attribute(null, ATT_NAME, r.pkg);
            if (r.priority != DEFAULT_PRIORITY) {
@@ -200,7 +226,9 @@ public class RankingHelper implements RankingConfig {
            if (r.visibility != DEFAULT_VISIBILITY) {
                out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
            }
            if (!forBackup) {
                out.attribute(null, ATT_UID, Integer.toString(r.uid));
            }
            out.endTag(null, TAG_PACKAGE);
        }
        out.endTag(null, TAG_RANKING);
@@ -364,15 +392,21 @@ public class RankingHelper implements RankingConfig {
            pw.print(prefix);
            pw.println("per-package config:");
        }
        final int N = mRecords.size();
        dumpRecords(pw, prefix, filter, mRecords);
        dumpRecords(pw, prefix, filter, mRestoredWithoutUids);
    }

    private static void dumpRecords(PrintWriter pw, String prefix,
            NotificationManagerService.DumpFilter filter, ArrayMap<String, Record> records) {
        final int N = records.size();
        for (int i = 0; i < N; i++) {
            final Record r = mRecords.valueAt(i);
            final Record r = records.valueAt(i);
            if (filter == null || filter.matches(r.pkg)) {
                pw.print(prefix);
                pw.print("  ");
                pw.print(r.pkg);
                pw.print(" (");
                pw.print(r.uid);
                pw.print(r.uid == Record.UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
                pw.print(')');
                if (r.priority != DEFAULT_PRIORITY) {
                    pw.print(" priority=");
@@ -391,11 +425,39 @@ public class RankingHelper implements RankingConfig {
        }
    }

    public void onPackagesChanged(boolean queryReplace, String[] pkgList) {
        if (queryReplace || pkgList == null || pkgList.length == 0
                || mRestoredWithoutUids.isEmpty()) {
            return; // nothing to do
        }
        final PackageManager pm = mContext.getPackageManager();
        boolean updated = false;
        for (String pkg : pkgList) {
            final Record r = mRestoredWithoutUids.get(pkg);
            if (r != null) {
                try {
                    r.uid = pm.getPackageUid(r.pkg, UserHandle.USER_OWNER);
                    mRestoredWithoutUids.remove(pkg);
                    mRecords.put(recordKey(r.pkg, r.uid), r);
                    updated = true;
                } catch (NameNotFoundException e) {
                    // noop
                }
            }
        }
        if (updated) {
            updateConfig();
        }
    }

    private static class Record {
        static int UNKNOWN_UID = UserHandle.USER_NULL;

        String pkg;
        int uid;
        int uid = UNKNOWN_UID;
        int priority = DEFAULT_PRIORITY;
        boolean peekable = DEFAULT_PEEKABLE;
        int visibility = DEFAULT_VISIBILITY;
    }

}
+12 −2
Original line number Diff line number Diff line
@@ -256,17 +256,27 @@ public class ZenModeHelper {
        }
    }

    public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException {
    public void readXml(XmlPullParser parser, boolean forRestore)
            throws XmlPullParserException, IOException {
        final ZenModeConfig config = ZenModeConfig.readXml(parser, mConfigMigration);
        if (config != null) {
            if (forRestore) {
                if (config.user != UserHandle.USER_OWNER) {
                    return;
                }
                config.manualRule = null;  // don't restore the manual rule
            }
            if (DEBUG) Log.d(TAG, "readXml");
            setConfig(config, "readXml");
        }
    }

    public void writeXml(XmlSerializer out) throws IOException {
    public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
        final int N = mConfigs.size();
        for (int i = 0; i < N; i++) {
            if (forBackup && mConfigs.keyAt(i) != UserHandle.USER_OWNER) {
                continue;
            }
            mConfigs.valueAt(i).writeXml(out);
        }
    }