Merge "Backup/restore notification policy." into mnc-dev
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 3300cdb..2d15d13 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -36,6 +36,7 @@
 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.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,6 +335,37 @@
 
     }
 
+    private void readPolicyXml(InputStream stream, boolean forRestore)
+            throws XmlPullParserException, NumberFormatException, IOException {
+        final XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(stream, StandardCharsets.UTF_8.name());
+
+        int type;
+        String tag;
+        int version = DB_VERSION;
+        while ((type = parser.next()) != END_DOCUMENT) {
+            tag = parser.getName();
+            if (type == START_TAG) {
+                if (TAG_NOTIFICATION_POLICY.equals(tag)) {
+                    version = Integer.parseInt(
+                            parser.getAttributeValue(null, ATTR_VERSION));
+                } else if (TAG_BLOCKED_PKGS.equals(tag)) {
+                    while ((type = parser.next()) != END_DOCUMENT) {
+                        tag = parser.getName();
+                        if (TAG_PACKAGE.equals(tag)) {
+                            mBlockedPackages.add(
+                                    parser.getAttributeValue(null, ATTR_NAME));
+                        } else if (TAG_BLOCKED_PKGS.equals(tag) && type == END_TAG) {
+                            break;
+                        }
+                    }
+                }
+            }
+            mZenModeHelper.readXml(parser, forRestore);
+            mRankingHelper.readXml(parser, forRestore);
+        }
+    }
+
     private void loadPolicyFile() {
         if (DBG) Slog.d(TAG, "loadPolicyFile");
         synchronized(mPolicyFile) {
@@ -338,33 +374,7 @@
             FileInputStream infile = null;
             try {
                 infile = mPolicyFile.openRead();
-                final XmlPullParser parser = Xml.newPullParser();
-                parser.setInput(infile, StandardCharsets.UTF_8.name());
-
-                int type;
-                String tag;
-                int version = DB_VERSION;
-                while ((type = parser.next()) != END_DOCUMENT) {
-                    tag = parser.getName();
-                    if (type == START_TAG) {
-                        if (TAG_NOTIFICATION_POLICY.equals(tag)) {
-                            version = Integer.parseInt(
-                                    parser.getAttributeValue(null, ATTR_VERSION));
-                        } else if (TAG_BLOCKED_PKGS.equals(tag)) {
-                            while ((type = parser.next()) != END_DOCUMENT) {
-                                tag = parser.getName();
-                                if (TAG_PACKAGE.equals(tag)) {
-                                    mBlockedPackages.add(
-                                            parser.getAttributeValue(null, ATTR_NAME));
-                                } else if (TAG_BLOCKED_PKGS.equals(tag) && type == END_TAG) {
-                                    break;
-                                }
-                            }
-                        }
-                    }
-                    mZenModeHelper.readXml(parser);
-                    mRankingHelper.readXml(parser);
-                }
+                readPolicyXml(infile, false /*forRestore*/);
             } catch (FileNotFoundException e) {
                 // No data yet
             } catch (IOException e) {
@@ -396,21 +406,26 @@
             }
 
             try {
-                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);
-                out.endTag(null, TAG_NOTIFICATION_POLICY);
-                out.endDocument();
+                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, forBackup);
+        mRankingHelper.writeXml(out, forBackup);
+        out.endTag(null, TAG_NOTIFICATION_POLICY);
+        out.endDocument();
     }
 
     /** Use this when you actually want to post a notification or toast.
@@ -722,6 +737,7 @@
                 }
                 mListeners.onPackagesChanged(queryReplace, pkgList);
                 mConditionProviders.onPackagesChanged(queryReplace, pkgList);
+                mRankingHelper.onPackagesChanged(queryReplace, pkgList);
             }
         }
     };
@@ -1671,13 +1687,40 @@
         // 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
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 88055ba..e503ac8 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -17,6 +17,8 @@
 
 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 @@
 
     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 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 @@
             }
             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 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 @@
             if (r.visibility != DEFAULT_VISIBILITY) {
                 out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
             }
-            out.attribute(null, ATT_UID, Integer.toString(r.uid));
+            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 @@
             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 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;
     }
+
 }
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 702c194..755b2ea 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -256,17 +256,27 @@
         }
     }
 
-    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);
         }
     }