Merge "Allow any user's UsageStats to be backed up and restored"
This commit is contained in:
committed by
Android (Google) Code Review
commit
255ca262b7
@@ -123,6 +123,9 @@ public final class UserHandle implements Parcelable {
|
||||
@TestApi
|
||||
public static final int MIN_SECONDARY_USER_ID = 10;
|
||||
|
||||
/** @hide */
|
||||
public static final int MAX_SECONDARY_USER_ID = Integer.MAX_VALUE / UserHandle.PER_USER_RANGE;
|
||||
|
||||
/**
|
||||
* (Arbitrary) user handle cache size.
|
||||
* {@link #CACHED_USER_HANDLES} caches user handles in the range of
|
||||
|
||||
@@ -329,11 +329,11 @@ public abstract class UsageStatsManagerInternal {
|
||||
* when the user is first unlocked to update the usage stats package mappings data that might
|
||||
* be stale or have existed from a restore and belongs to packages that are not installed for
|
||||
* this user anymore.
|
||||
* Note: this is only executed for the system user.
|
||||
*
|
||||
* @param userId The user to update
|
||||
* @return {@code true} if the updating was successful, {@code false} otherwise
|
||||
*/
|
||||
public abstract boolean updatePackageMappingsData();
|
||||
public abstract boolean updatePackageMappingsData(@UserIdInt int userId);
|
||||
|
||||
/**
|
||||
* Listener interface for usage events.
|
||||
|
||||
@@ -86,7 +86,7 @@ public class SystemBackupAgent extends BackupAgentHelper {
|
||||
|
||||
private static final Set<String> sEligibleForMultiUser = Sets.newArraySet(
|
||||
PERMISSION_HELPER, NOTIFICATION_HELPER, SYNC_SETTINGS_HELPER, APP_LOCALES_HELPER,
|
||||
ACCOUNT_MANAGER_HELPER);
|
||||
ACCOUNT_MANAGER_HELPER, USAGE_STATS_HELPER);
|
||||
|
||||
private int mUserId = UserHandle.USER_SYSTEM;
|
||||
|
||||
@@ -100,7 +100,7 @@ public class SystemBackupAgent extends BackupAgentHelper {
|
||||
addHelper(PREFERRED_HELPER, new PreferredActivityBackupHelper(mUserId));
|
||||
addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(mUserId));
|
||||
addHelper(PERMISSION_HELPER, new PermissionBackupHelper(mUserId));
|
||||
addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(this));
|
||||
addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(mUserId));
|
||||
addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper());
|
||||
addHelper(ACCOUNT_MANAGER_HELPER, new AccountManagerBackupHelper(mUserId));
|
||||
addHelper(SLICES_HELPER, new SliceBackupHelper(this));
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.android.server.backup;
|
||||
|
||||
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.backup.BlobBackupHelper;
|
||||
import android.app.usage.UsageStatsManagerInternal;
|
||||
import android.content.Context;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -26,8 +26,16 @@ public class UsageStatsBackupHelper extends BlobBackupHelper {
|
||||
// same as UsageStatsDatabase.KEY_USAGE_STATS
|
||||
static final String KEY_USAGE_STATS = "usage_stats";
|
||||
|
||||
public UsageStatsBackupHelper(Context context) {
|
||||
private final @UserIdInt int mUserId;
|
||||
|
||||
/**
|
||||
* Marshall/unmarshall the usagestats data for the given user
|
||||
*
|
||||
* @param userId The userId to backup/restore
|
||||
*/
|
||||
public UsageStatsBackupHelper(@UserIdInt int userId) {
|
||||
super(BLOB_VERSION, KEY_USAGE_STATS);
|
||||
mUserId = userId;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -38,8 +46,11 @@ public class UsageStatsBackupHelper extends BlobBackupHelper {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
DataOutputStream out = new DataOutputStream(baos);
|
||||
try {
|
||||
// Note: Write 0 here deliberately so that a backup from a secondary user
|
||||
// can still be restored to an older OS where the restore was always to user 0
|
||||
// Writing the actual userId here would result in restores not working on pre-U.
|
||||
out.writeInt(UserHandle.USER_SYSTEM);
|
||||
out.write(localUsageStatsManager.getBackupPayload(UserHandle.USER_SYSTEM, key));
|
||||
out.write(localUsageStatsManager.getBackupPayload(mUserId, key));
|
||||
} catch (IOException ioe) {
|
||||
if (DEBUG) Log.e(TAG, "Failed to backup Usage Stats", ioe);
|
||||
baos.reset();
|
||||
@@ -49,7 +60,6 @@ public class UsageStatsBackupHelper extends BlobBackupHelper {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void applyRestoredPayload(String key, byte[] payload) {
|
||||
if (KEY_USAGE_STATS.equals(key)) {
|
||||
@@ -57,10 +67,10 @@ public class UsageStatsBackupHelper extends BlobBackupHelper {
|
||||
LocalServices.getService(UsageStatsManagerInternal.class);
|
||||
DataInputStream in = new DataInputStream(new ByteArrayInputStream(payload));
|
||||
try {
|
||||
int user = in.readInt();
|
||||
in.readInt(); // Legacy userId parameter, read and ignore
|
||||
byte[] restoreData = new byte[payload.length - 4];
|
||||
in.read(restoreData, 0, restoreData.length);
|
||||
localUsageStatsManager.applyRestoredPayload(user, key, restoreData);
|
||||
localUsageStatsManager.applyRestoredPayload(mUserId, key, restoreData);
|
||||
} catch (IOException ioe) {
|
||||
if (DEBUG) Log.e(TAG, "Failed to restore Usage Stats", ioe);
|
||||
}
|
||||
|
||||
@@ -262,7 +262,7 @@ public class UserManagerService extends IUserManager.Stub {
|
||||
|
||||
// We need to keep process uid within Integer.MAX_VALUE.
|
||||
@VisibleForTesting
|
||||
static final int MAX_USER_ID = Integer.MAX_VALUE / UserHandle.PER_USER_RANGE;
|
||||
static final int MAX_USER_ID = UserHandle.MAX_SECONDARY_USER_ID;
|
||||
|
||||
// Max size of the queue of recently removed users
|
||||
@VisibleForTesting
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.android.server.usage;
|
||||
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobParameters;
|
||||
import android.app.job.JobScheduler;
|
||||
@@ -35,65 +36,71 @@ import java.util.concurrent.TimeUnit;
|
||||
public class UsageStatsIdleService extends JobService {
|
||||
|
||||
/**
|
||||
* Base job ID for the pruning job - must be unique within the system server uid.
|
||||
* Namespace for prune job
|
||||
*/
|
||||
private static final int PRUNE_JOB_ID = 546357475;
|
||||
private static final String PRUNE_JOB_NS = "usagestats_prune";
|
||||
|
||||
/**
|
||||
* Job ID for the update mappings job - must be unique within the system server uid.
|
||||
* Incrementing PRUNE_JOB_ID by 21475 (MAX_USER_ID) to ensure there is no overlap in job ids.
|
||||
* Namespace for update mappings job
|
||||
*/
|
||||
private static final int UPDATE_MAPPINGS_JOB_ID = 546378950;
|
||||
private static final String UPDATE_MAPPINGS_JOB_NS = "usagestats_mapping";
|
||||
|
||||
private static final String USER_ID_KEY = "user_id";
|
||||
|
||||
static void scheduleJob(Context context, int userId) {
|
||||
final int userJobId = PRUNE_JOB_ID + userId; // unique job id per user
|
||||
/** Schedule a prune job */
|
||||
static void schedulePruneJob(Context context, @UserIdInt int userId) {
|
||||
final ComponentName component = new ComponentName(context.getPackageName(),
|
||||
UsageStatsIdleService.class.getName());
|
||||
final PersistableBundle bundle = new PersistableBundle();
|
||||
bundle.putInt(USER_ID_KEY, userId);
|
||||
final JobInfo pruneJob = new JobInfo.Builder(userJobId, component)
|
||||
final JobInfo pruneJob = new JobInfo.Builder(userId, component)
|
||||
.setRequiresDeviceIdle(true)
|
||||
.setExtras(bundle)
|
||||
.setPersisted(true)
|
||||
.build();
|
||||
|
||||
scheduleJobInternal(context, pruneJob, userJobId);
|
||||
scheduleJobInternal(context, pruneJob, PRUNE_JOB_NS, userId);
|
||||
}
|
||||
|
||||
static void scheduleUpdateMappingsJob(Context context) {
|
||||
static void scheduleUpdateMappingsJob(Context context, @UserIdInt int userId) {
|
||||
final ComponentName component = new ComponentName(context.getPackageName(),
|
||||
UsageStatsIdleService.class.getName());
|
||||
final JobInfo updateMappingsJob = new JobInfo.Builder(UPDATE_MAPPINGS_JOB_ID, component)
|
||||
final PersistableBundle bundle = new PersistableBundle();
|
||||
bundle.putInt(USER_ID_KEY, userId);
|
||||
final JobInfo updateMappingsJob = new JobInfo.Builder(userId, component)
|
||||
.setPersisted(true)
|
||||
.setMinimumLatency(TimeUnit.DAYS.toMillis(1))
|
||||
.setOverrideDeadline(TimeUnit.DAYS.toMillis(2))
|
||||
.setExtras(bundle)
|
||||
.build();
|
||||
|
||||
scheduleJobInternal(context, updateMappingsJob, UPDATE_MAPPINGS_JOB_ID);
|
||||
scheduleJobInternal(context, updateMappingsJob, UPDATE_MAPPINGS_JOB_NS, userId);
|
||||
}
|
||||
|
||||
private static void scheduleJobInternal(Context context, JobInfo pruneJob, int jobId) {
|
||||
final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
|
||||
final JobInfo pendingPruneJob = jobScheduler.getPendingJob(jobId);
|
||||
// only schedule a new prune job if one doesn't exist already for this user
|
||||
if (!pruneJob.equals(pendingPruneJob)) {
|
||||
jobScheduler.cancel(jobId); // cancel any previously scheduled prune job
|
||||
jobScheduler.schedule(pruneJob);
|
||||
private static void scheduleJobInternal(Context context, JobInfo jobInfo,
|
||||
String namespace, int jobId) {
|
||||
JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
|
||||
jobScheduler = jobScheduler.forNamespace(namespace);
|
||||
final JobInfo pendingJob = jobScheduler.getPendingJob(jobId);
|
||||
// only schedule a new job if one doesn't exist already for this user
|
||||
if (!jobInfo.equals(pendingJob)) {
|
||||
jobScheduler.cancel(jobId); // cancel any previously scheduled job
|
||||
jobScheduler.schedule(jobInfo);
|
||||
}
|
||||
}
|
||||
|
||||
static void cancelJob(Context context, int userId) {
|
||||
cancelJobInternal(context, PRUNE_JOB_ID + userId);
|
||||
static void cancelPruneJob(Context context, @UserIdInt int userId) {
|
||||
cancelJobInternal(context, PRUNE_JOB_NS, userId);
|
||||
}
|
||||
|
||||
static void cancelUpdateMappingsJob(Context context) {
|
||||
cancelJobInternal(context, UPDATE_MAPPINGS_JOB_ID);
|
||||
static void cancelUpdateMappingsJob(Context context, @UserIdInt int userId) {
|
||||
cancelJobInternal(context, UPDATE_MAPPINGS_JOB_NS, userId);
|
||||
}
|
||||
|
||||
private static void cancelJobInternal(Context context, int jobId) {
|
||||
final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
|
||||
private static void cancelJobInternal(Context context, String namespace, int jobId) {
|
||||
JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
|
||||
if (jobScheduler != null) {
|
||||
jobScheduler = jobScheduler.forNamespace(namespace);
|
||||
jobScheduler.cancel(jobId);
|
||||
}
|
||||
}
|
||||
@@ -102,15 +109,19 @@ public class UsageStatsIdleService extends JobService {
|
||||
public boolean onStartJob(JobParameters params) {
|
||||
final PersistableBundle bundle = params.getExtras();
|
||||
final int userId = bundle.getInt(USER_ID_KEY, -1);
|
||||
if (userId == -1 && params.getJobId() != UPDATE_MAPPINGS_JOB_ID) {
|
||||
|
||||
if (userId == -1) { // legacy job
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do async
|
||||
AsyncTask.execute(() -> {
|
||||
final UsageStatsManagerInternal usageStatsManagerInternal = LocalServices.getService(
|
||||
UsageStatsManagerInternal.class);
|
||||
if (params.getJobId() == UPDATE_MAPPINGS_JOB_ID) {
|
||||
final boolean jobFinished = usageStatsManagerInternal.updatePackageMappingsData();
|
||||
final String jobNs = params.getJobNamespace();
|
||||
if (UPDATE_MAPPINGS_JOB_NS.equals(jobNs)) {
|
||||
final boolean jobFinished =
|
||||
usageStatsManagerInternal.updatePackageMappingsData(userId);
|
||||
jobFinished(params, !jobFinished); // reschedule if data was not updated
|
||||
} else {
|
||||
final boolean jobFinished =
|
||||
@@ -118,6 +129,8 @@ public class UsageStatsIdleService extends JobService {
|
||||
jobFinished(params, !jobFinished); // reschedule if data was not pruned
|
||||
}
|
||||
});
|
||||
|
||||
// Job is running asynchronously
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -433,11 +433,9 @@ public class UsageStatsService extends SystemService implements
|
||||
private void onUserUnlocked(int userId) {
|
||||
// fetch the installed packages outside the lock so it doesn't block package manager.
|
||||
final HashMap<String, Long> installedPackages = getInstalledPackages(userId);
|
||||
// delay updating of package mappings for user 0 since their data is not likely to be stale.
|
||||
// this also makes it less likely for restored data to be erased on unexpected reboots.
|
||||
if (userId == UserHandle.USER_SYSTEM) {
|
||||
UsageStatsIdleService.scheduleUpdateMappingsJob(getContext());
|
||||
}
|
||||
|
||||
UsageStatsIdleService.scheduleUpdateMappingsJob(getContext(), userId);
|
||||
|
||||
final boolean deleteObsoleteData = shouldDeleteObsoleteData(UserHandle.of(userId));
|
||||
synchronized (mLock) {
|
||||
// This should be safe to add this early. Other than reportEventOrAddToQueue and
|
||||
@@ -1261,8 +1259,8 @@ public class UsageStatsService extends SystemService implements
|
||||
}
|
||||
mAppStandby.onUserRemoved(userId);
|
||||
// Cancel any scheduled jobs for this user since the user is being removed.
|
||||
UsageStatsIdleService.cancelJob(getContext(), userId);
|
||||
UsageStatsIdleService.cancelUpdateMappingsJob(getContext());
|
||||
UsageStatsIdleService.cancelPruneJob(getContext(), userId);
|
||||
UsageStatsIdleService.cancelUpdateMappingsJob(getContext(), userId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1300,7 +1298,7 @@ public class UsageStatsService extends SystemService implements
|
||||
|
||||
// Schedule a job to prune any data related to this package.
|
||||
if (tokenRemoved != PackagesTokenData.UNASSIGNED_TOKEN) {
|
||||
UsageStatsIdleService.scheduleJob(getContext(), userId);
|
||||
UsageStatsIdleService.schedulePruneJob(getContext(), userId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1325,19 +1323,19 @@ public class UsageStatsService extends SystemService implements
|
||||
/**
|
||||
* Called by the Binder stub.
|
||||
*/
|
||||
private boolean updatePackageMappingsData() {
|
||||
private boolean updatePackageMappingsData(@UserIdInt int userId) {
|
||||
// don't update the mappings if a profile user is defined
|
||||
if (!shouldDeleteObsoleteData(UserHandle.SYSTEM)) {
|
||||
if (!shouldDeleteObsoleteData(UserHandle.of(userId))) {
|
||||
return true; // return true so job scheduler doesn't reschedule the job
|
||||
}
|
||||
// fetch the installed packages outside the lock so it doesn't block package manager.
|
||||
final HashMap<String, Long> installedPkgs = getInstalledPackages(UserHandle.USER_SYSTEM);
|
||||
final HashMap<String, Long> installedPkgs = getInstalledPackages(userId);
|
||||
synchronized (mLock) {
|
||||
if (!mUserUnlockedStates.contains(UserHandle.USER_SYSTEM)) {
|
||||
if (!mUserUnlockedStates.contains(userId)) {
|
||||
return false; // user is no longer unlocked
|
||||
}
|
||||
|
||||
final UserUsageStatsService userService = mUserState.get(UserHandle.USER_SYSTEM);
|
||||
final UserUsageStatsService userService = mUserState.get(userId);
|
||||
if (userService == null) {
|
||||
return false; // user was stopped or removed
|
||||
}
|
||||
@@ -3055,44 +3053,35 @@ public class UsageStatsService extends SystemService implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getBackupPayload(int user, String key) {
|
||||
if (!mUserUnlockedStates.contains(user)) {
|
||||
Slog.w(TAG, "Failed to get backup payload for locked user " + user);
|
||||
public byte[] getBackupPayload(@UserIdInt int userId, String key) {
|
||||
if (!mUserUnlockedStates.contains(userId)) {
|
||||
Slog.w(TAG, "Failed to get backup payload for locked user " + userId);
|
||||
return null;
|
||||
}
|
||||
synchronized (mLock) {
|
||||
// Check to ensure that only user 0's data is b/r for now
|
||||
// Note: if backup and restore is enabled for users other than the system user, the
|
||||
// #onUserUnlocked logic, specifically when the update mappings job is scheduled via
|
||||
// UsageStatsIdleService.scheduleUpdateMappingsJob, will have to be updated.
|
||||
if (user == UserHandle.USER_SYSTEM) {
|
||||
final UserUsageStatsService userStats = getUserUsageStatsServiceLocked(user);
|
||||
if (userStats == null) {
|
||||
return null; // user was stopped or removed
|
||||
}
|
||||
return userStats.getBackupPayload(key);
|
||||
} else {
|
||||
return null;
|
||||
final UserUsageStatsService userStats = getUserUsageStatsServiceLocked(userId);
|
||||
if (userStats == null) {
|
||||
return null; // user was stopped or removed
|
||||
}
|
||||
Slog.i(TAG, "Returning backup payload for u=" + userId);
|
||||
return userStats.getBackupPayload(key);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyRestoredPayload(int user, String key, byte[] payload) {
|
||||
public void applyRestoredPayload(@UserIdInt int userId, String key, byte[] payload) {
|
||||
synchronized (mLock) {
|
||||
if (!mUserUnlockedStates.contains(user)) {
|
||||
Slog.w(TAG, "Failed to apply restored payload for locked user " + user);
|
||||
if (!mUserUnlockedStates.contains(userId)) {
|
||||
Slog.w(TAG, "Failed to apply restored payload for locked user " + userId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (user == UserHandle.USER_SYSTEM) {
|
||||
final UserUsageStatsService userStats = getUserUsageStatsServiceLocked(user);
|
||||
if (userStats == null) {
|
||||
return; // user was stopped or removed
|
||||
}
|
||||
final Set<String> restoredApps = userStats.applyRestoredPayload(key, payload);
|
||||
mAppStandby.restoreAppsToRare(restoredApps, user);
|
||||
final UserUsageStatsService userStats = getUserUsageStatsServiceLocked(userId);
|
||||
if (userStats == null) {
|
||||
return; // user was stopped or removed
|
||||
}
|
||||
final Set<String> restoredApps = userStats.applyRestoredPayload(key, payload);
|
||||
mAppStandby.restoreAppsToRare(restoredApps, userId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3165,8 +3154,8 @@ public class UsageStatsService extends SystemService implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updatePackageMappingsData() {
|
||||
return UsageStatsService.this.updatePackageMappingsData();
|
||||
public boolean updatePackageMappingsData(@UserIdInt int userId) {
|
||||
return UsageStatsService.this.updatePackageMappingsData(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user