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