Enabled Backup/Restore of Package UsageStatistics

Backing up UsageStatistics using a new API in
UsageStatsService like the Notifications backup.
The backup logic is in the same package as UsageStatsDatabase
while the BackupAgentHelper resides in the android package
alongside SystemBackupAgent.

Bug: 26179323
Change-Id: I022d85fbcd4abb763230bec6eea50a7e723b5152
This commit is contained in:
Ritesh Reddy
2015-12-17 17:03:54 +00:00
parent 6cfdf6b67a
commit 8a6ce2cb64
7 changed files with 310 additions and 5 deletions

View File

@@ -19,6 +19,8 @@ package android.app.usage;
import android.content.ComponentName;
import android.content.res.Configuration;
import java.io.IOException;
/**
* UsageStatsManager local system service interface.
*
@@ -109,4 +111,9 @@ public abstract class UsageStatsManagerInternal {
public abstract void onParoleStateChanged(boolean isParoleOn);
}
/* Backup/Restore API */
public abstract byte[] getBackupPayload(int user, String key);
public abstract void applyRestoredPayload(int user, String key, byte[] payload);
}

View File

@@ -47,6 +47,7 @@ public class SystemBackupAgent extends BackupAgentHelper {
private static final String PREFERRED_HELPER = "preferred_activities";
private static final String NOTIFICATION_HELPER = "notifications";
private static final String PERMISSION_HELPER = "permissions";
private static final String USAGE_STATS_HELPER = "usage_stats";
// These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME
// are also used in the full-backup file format, so must not change unless steps are
@@ -96,7 +97,7 @@ public class SystemBackupAgent extends BackupAgentHelper {
addHelper(PREFERRED_HELPER, new PreferredActivityBackupHelper());
addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(this));
addHelper(PERMISSION_HELPER, new PermissionBackupHelper());
addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(this));
super.onBackup(oldState, data, newState);
}
@@ -131,6 +132,7 @@ public class SystemBackupAgent extends BackupAgentHelper {
addHelper(PREFERRED_HELPER, new PreferredActivityBackupHelper());
addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(this));
addHelper(PERMISSION_HELPER, new PermissionBackupHelper());
addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(this));
try {
super.onRestore(data, appVersionCode, newState);
@@ -183,7 +185,7 @@ public class SystemBackupAgent extends BackupAgentHelper {
if (restoredWallpaper) {
IWallpaperManager wallpaper =
(IWallpaperManager)ServiceManager.getService(
Context.WALLPAPER_SERVICE);
Context.WALLPAPER_SERVICE);
if (wallpaper != null) {
try {
wallpaper.settingsRestored();

View File

@@ -0,0 +1,70 @@
package com.android.server.backup;
import android.app.backup.BlobBackupHelper;
import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.Log;
import com.android.server.LocalServices;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
public class UsageStatsBackupHelper extends BlobBackupHelper {
static final String TAG = "UsgStatsBackupHelper"; // must be < 23 chars
static final boolean DEBUG = false;
// Current version of the blob schema
static final int BLOB_VERSION = 1;
// Key under which the payload blob is stored
// same as UsageStatsBackupHelperAssistant.KEY_USAGE_STATS
static final String KEY_USAGE_STATS = "usage_stats";
public UsageStatsBackupHelper(Context context) {
super(BLOB_VERSION, KEY_USAGE_STATS);
}
@Override
protected byte[] getBackupPayload(String key) {
if(KEY_USAGE_STATS.equals(key)) {
UsageStatsManagerInternal localUsageStatsManager = LocalServices.getService(UsageStatsManagerInternal.class);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(baos);
try{
out.writeInt(UserHandle.USER_SYSTEM);
out.write(localUsageStatsManager.getBackupPayload(UserHandle.USER_SYSTEM, key));
} catch (IOException ioe){
if (DEBUG) Log.e(TAG, "Failed to backup Usage Stats", ioe);
baos.reset();
}
return baos.toByteArray();
}
return null;
}
@Override
protected void applyRestoredPayload(String key, byte[] payload) {
if (KEY_USAGE_STATS.equals(key)) {
UsageStatsManagerInternal localUsageStatsManager = LocalServices.getService(UsageStatsManagerInternal.class);
DataInputStream in = new DataInputStream(new ByteArrayInputStream(payload));
try{
int user = in.readInt();
byte[] restoreData = new byte[payload.length - 4];
in.read(restoreData, 0, payload.length-4);
localUsageStatsManager.applyRestoredPayload(user, key, restoreData);
} catch (IOException ioe){
if (DEBUG) Log.e(TAG, "Failed to restore Usage Stats", ioe);
}
}
}
}

View File

@@ -17,6 +17,7 @@
package com.android.server.usage;
import android.app.usage.TimeSparseArray;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.os.Build;
import android.util.AtomicFile;
@@ -25,6 +26,10 @@ import android.util.TimeUtils;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
@@ -39,6 +44,14 @@ import java.util.List;
class UsageStatsDatabase {
private static final int CURRENT_VERSION = 3;
// Current version of the backup schema
static final int BACKUP_STATE_VERSION = 1;
// Key under which the payload blob is stored
// same as UsageStatsBackupHelper.KEY_USAGE_STATS
static final String KEY_USAGE_STATS = "usage_stats";
private static final String TAG = "UsageStatsDatabase";
private static final boolean DEBUG = UsageStatsService.DEBUG;
private static final String BAK_SUFFIX = ".bak";
@@ -539,4 +552,186 @@ class UsageStatsDatabase {
stats.lastTimeSaved = f.getLastModifiedTime();
}
}
/* Backup/Restore Code */
protected byte[] getBackupPayload(String key){
synchronized (mLock) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
if (KEY_USAGE_STATS.equals(key)) {
prune(System.currentTimeMillis());
DataOutputStream out = new DataOutputStream(baos);
try {
out.writeInt(BACKUP_STATE_VERSION);
out.writeInt(mSortedStatFiles[UsageStatsManager.INTERVAL_DAILY].size());
for(int i = 0; i<mSortedStatFiles[UsageStatsManager.INTERVAL_DAILY].size(); i++){
writeIntervalStatsToStream(out, mSortedStatFiles[UsageStatsManager.INTERVAL_DAILY].valueAt(i));
}
out.writeInt(mSortedStatFiles[UsageStatsManager.INTERVAL_WEEKLY].size());
for(int i = 0; i<mSortedStatFiles[UsageStatsManager.INTERVAL_WEEKLY].size(); i++){
writeIntervalStatsToStream(out, mSortedStatFiles[UsageStatsManager.INTERVAL_WEEKLY].valueAt(i));
}
out.writeInt(mSortedStatFiles[UsageStatsManager.INTERVAL_MONTHLY].size());
for(int i = 0; i<mSortedStatFiles[UsageStatsManager.INTERVAL_MONTHLY].size(); i++){
writeIntervalStatsToStream(out, mSortedStatFiles[UsageStatsManager.INTERVAL_MONTHLY].valueAt(i));
}
out.writeInt(mSortedStatFiles[UsageStatsManager.INTERVAL_YEARLY].size());
for(int i = 0; i<mSortedStatFiles[UsageStatsManager.INTERVAL_YEARLY].size(); i++){
writeIntervalStatsToStream(out, mSortedStatFiles[UsageStatsManager.INTERVAL_YEARLY].valueAt(i));
}
if (DEBUG) Slog.i(TAG, "Written " + baos.size() + " bytes of data");
} catch (IOException ioe){
Slog.d(TAG, "Failed to write data to output stream", ioe);
baos.reset();
}
}
return baos.toByteArray();
}
}
protected void applyRestoredPayload(String key, byte[] payload){
synchronized (mLock) {
if (KEY_USAGE_STATS.equals(key)) {
// Read stats files for the current device configs
IntervalStats dailyConfigSource = getLatestUsageStats(UsageStatsManager.INTERVAL_DAILY);
IntervalStats weeklyConfigSource = getLatestUsageStats(UsageStatsManager.INTERVAL_WEEKLY);
IntervalStats monthlyConfigSource = getLatestUsageStats(UsageStatsManager.INTERVAL_MONTHLY);
IntervalStats yearlyConfigSource = getLatestUsageStats(UsageStatsManager.INTERVAL_YEARLY);
// Delete all stats files
for(int i = 0; i<mIntervalDirs.length; i++){
deleteDirectoryContents(mIntervalDirs[i]);
}
try {
DataInputStream in = new DataInputStream(new ByteArrayInputStream(payload));
int stateVersion = in.readInt();
int fileCount = in.readInt();
for(int i = 0; i<fileCount; i++){
IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in));
stats = mergeStats(stats, dailyConfigSource);
putUsageStats(UsageStatsManager.INTERVAL_DAILY, stats);
}
fileCount = in.readInt();
for(int i = 0; i<fileCount; i++){
IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in));
stats = mergeStats(stats, weeklyConfigSource);
putUsageStats(UsageStatsManager.INTERVAL_WEEKLY, stats);
}
fileCount = in.readInt();
for(int i = 0; i<fileCount; i++){
IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in));
stats = mergeStats(stats, monthlyConfigSource);
putUsageStats(UsageStatsManager.INTERVAL_MONTHLY, stats);
}
fileCount = in.readInt();
for(int i = 0; i<fileCount; i++){
IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in));
stats = mergeStats(stats, yearlyConfigSource);
putUsageStats(UsageStatsManager.INTERVAL_YEARLY, stats);
}
if (DEBUG) Slog.i(TAG, "Completed Restoring UsageStats");
} catch (IOException ioe){
Slog.d(TAG, "Failed to read data from input stream", ioe);
}
finally {
indexFilesLocked();
}
}
}
}
/**
* Get the Configuration Statistics from the current device statistics and merge them
* with the backed up usage statistics.
*/
private IntervalStats mergeStats(IntervalStats beingRestored, IntervalStats onDevice) {
beingRestored.activeConfiguration = onDevice.activeConfiguration;
beingRestored.configurations.putAll(onDevice.configurations);
beingRestored.events = onDevice.events;
return beingRestored;
}
private void writeIntervalStatsToStream(DataOutputStream out, AtomicFile statsFile) throws IOException{
IntervalStats stats = new IntervalStats();
try {
UsageStatsXml.read(statsFile, stats);
} catch (IOException e) {
Slog.e(TAG, "Failed to read usage stats file", e);
out.writeInt(0);
return;
}
sanitizeIntervalStatsForBackup(stats);
byte[] data = serializeIntervalStats(stats);
out.writeInt(data.length);
out.write(data);
}
private static byte[] getIntervalStatsBytes(DataInputStream in) throws IOException {
int length = in.readInt();
byte[] buffer = new byte[length];
in.read(buffer, 0, length);
return buffer;
}
private static void sanitizeIntervalStatsForBackup(IntervalStats stats) {
if (stats == null) return;
stats.activeConfiguration = null;
stats.configurations.clear();
if (stats.events != null) stats.events.clear();
}
private static byte[] serializeIntervalStats(IntervalStats stats) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(baos);
try {
out.writeLong(stats.beginTime);
UsageStatsXml.write(out, stats);
} catch (IOException ioe) {
Slog.d(TAG, "Serializing IntervalStats Failed", ioe);
baos.reset();
}
return baos.toByteArray();
}
private static IntervalStats deserializeIntervalStats(byte[] data) {
ByteArrayInputStream bais = new ByteArrayInputStream(data);
DataInputStream in = new DataInputStream(bais);
IntervalStats stats = new IntervalStats();
try {
stats.beginTime = in.readLong();
UsageStatsXml.read(in, stats);
} catch (IOException ioe) {
Slog.d(TAG, "DeSerializing IntervalStats Failed", ioe);
stats = null;
}
return stats;
}
private static void deleteDirectoryContents(File directory){
File[] files = directory.listFiles();
for (File file : files) {
deleteDirectory(file);
}
}
private static void deleteDirectory(File directory) {
File[] files = directory.listFiles();
for (File file : files) {
if (!file.isDirectory()) {
file.delete();
} else {
deleteDirectory(file);
}
}
directory.delete();
}
}

View File

@@ -1310,6 +1310,8 @@ public class UsageStatsService extends SystemService implements
}
UsageStatsService.this.dump(args, pw);
}
}
/**
@@ -1416,5 +1418,26 @@ public class UsageStatsService extends SystemService implements
AppIdleStateChangeListener listener) {
UsageStatsService.this.removeListener(listener);
}
@Override
public byte[] getBackupPayload(int user, String key) {
// Check to ensure that only user 0's data is b/r for now
if (user == UserHandle.USER_SYSTEM) {
final UserUsageStatsService userStats =
getUserDataAndInitializeIfNeededLocked(user, checkAndGetTimeLocked());
return userStats.getBackupPayload(key);
} else {
return null;
}
}
@Override
public void applyRestoredPayload(int user, String key, byte[] payload) {
if (user == UserHandle.USER_SYSTEM) {
final UserUsageStatsService userStats =
getUserDataAndInitializeIfNeededLocked(user, checkAndGetTimeLocked());
userStats.applyRestoredPayload(key, payload);
}
}
}
}

View File

@@ -87,7 +87,7 @@ public class UsageStatsXml {
}
}
private static void read(InputStream in, IntervalStats statsOut) throws IOException {
static void read(InputStream in, IntervalStats statsOut) throws IOException {
XmlPullParser parser = Xml.newPullParser();
try {
parser.setInput(in, "utf-8");
@@ -113,7 +113,7 @@ public class UsageStatsXml {
}
}
private static void write(OutputStream out, IntervalStats stats) throws IOException {
static void write(OutputStream out, IntervalStats stats) throws IOException {
FastXmlSerializer xml = new FastXmlSerializer();
xml.setOutput(out, "utf-8");
xml.startDocument("utf-8", true);
@@ -126,4 +126,4 @@ public class UsageStatsXml {
xml.endTag(null, USAGESTATS_TAG);
xml.endDocument();
}
}
}

View File

@@ -741,4 +741,12 @@ class UserUsageStatsService {
return "UNKNOWN";
}
}
byte[] getBackupPayload(String key){
return mDatabase.getBackupPayload(key);
}
void applyRestoredPayload(String key, byte[] payload){
mDatabase.applyRestoredPayload(key, payload);
}
}