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:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user