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
This commit is contained in:
John Spurlock
2015-05-28 11:24:10 -04:00
parent 21258a3703
commit 35ef0a626c
3 changed files with 166 additions and 51 deletions

View File

@@ -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,6 +335,37 @@ public class NotificationManagerService extends SystemService {
}
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 @@ public class NotificationManagerService extends SystemService {
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 @@ public class NotificationManagerService extends SystemService {
}
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.
@@ -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

View File

@@ -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));
}
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 @@ 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;
}
}

View File

@@ -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);
}
}