Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a7f9c762 authored by Varun Shah's avatar Varun Shah
Browse files

Delay updating of usage stats package mappings.

Do not update package mappings for the system user when the user is
first unlocked. Instead, schedule a job to be executed after 24 to 48
hours from when the system user is unlocked. This makes the service
initialization phase for the system user a little quicker since their
data is not likely to be stale. Additionally, this also ensures that
restored data is not pruned by mistake if there is a device restart
before restore is completed. The updating of the mappings occurs
normally for other users, on user service initialization.

Bug: 155209652
Test: manually ensure job is skipped for system user
Test: atest android.app.usage.cts.UsageStatsTest
Change-Id: I2c03a1a05246d6b454569c4569813e90bede3693
parent 0728a4fe
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -315,4 +315,15 @@ public abstract class UsageStatsManagerInternal {
     * @return {@code true} if the pruning was successful, {@code false} otherwise
     */
    public abstract boolean pruneUninstalledPackagesData(@UserIdInt int userId);

    /**
     * Called by {@link com.android.server.usage.UsageStatsIdleService} between 24 to 48 hours of
     * 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.
     *
     * @return {@code true} if the updating was successful, {@code false} otherwise
     */
    public abstract boolean updatePackageMappingsData();
}
+45 −8
Original line number Diff line number Diff line
@@ -27,6 +27,8 @@ import android.os.PersistableBundle;

import com.android.server.LocalServices;

import java.util.concurrent.TimeUnit;

/**
 * JobService used to do any work for UsageStats while the device is idle.
 */
@@ -36,6 +38,11 @@ public class UsageStatsIdleService extends JobService {
     * Base job ID for the pruning job - must be unique within the system server uid.
     */
    private static final int PRUNE_JOB_ID = 546357475;
    /**
     * 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.
     */
    private static final int UPDATE_MAPPINGS_JOB_ID = 546378950;

    private static final String USER_ID_KEY = "user_id";

@@ -51,35 +58,65 @@ public class UsageStatsIdleService extends JobService {
                .setPersisted(true)
                .build();

        scheduleJobInternal(context, pruneJob, userJobId);
    }

    static void scheduleUpdateMappingsJob(Context context) {
        final ComponentName component = new ComponentName(context.getPackageName(),
                UsageStatsIdleService.class.getName());
        final JobInfo updateMappingsJob = new JobInfo.Builder(UPDATE_MAPPINGS_JOB_ID, component)
                .setPersisted(true)
                .setMinimumLatency(TimeUnit.DAYS.toMillis(1))
                .setOverrideDeadline(TimeUnit.DAYS.toMillis(2))
                .build();

        scheduleJobInternal(context, updateMappingsJob, UPDATE_MAPPINGS_JOB_ID);
    }

    private static void scheduleJobInternal(Context context, JobInfo pruneJob, int jobId) {
        final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
        final JobInfo pendingPruneJob = jobScheduler.getPendingJob(userJobId);
        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(userJobId); // cancel any previously scheduled prune job
            jobScheduler.cancel(jobId); // cancel any previously scheduled prune job
            jobScheduler.schedule(pruneJob);
        }

    }

    static void cancelJob(Context context, int userId) {
        final int userJobId = PRUNE_JOB_ID + userId; // unique job id per user
        cancelJobInternal(context, PRUNE_JOB_ID + userId);
    }

    static void cancelUpdateMappingsJob(Context context) {
        cancelJobInternal(context, UPDATE_MAPPINGS_JOB_ID);
    }

    private static void cancelJobInternal(Context context, int jobId) {
        final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
        jobScheduler.cancel(userJobId);
        if (jobScheduler != null) {
            jobScheduler.cancel(jobId);
        }
    }

    @Override
    public boolean onStartJob(JobParameters params) {
        final PersistableBundle bundle = params.getExtras();
        final int userId = bundle.getInt(USER_ID_KEY, -1);
        if (userId == -1) {
        if (userId == -1 && params.getJobId() != UPDATE_MAPPINGS_JOB_ID) {
            return false;
        }

        AsyncTask.execute(() -> {
            final UsageStatsManagerInternal usageStatsManagerInternal = LocalServices.getService(
                    UsageStatsManagerInternal.class);
            final boolean pruned = usageStatsManagerInternal.pruneUninstalledPackagesData(userId);
            jobFinished(params, !pruned); // reschedule if data was not pruned
            if (params.getJobId() == UPDATE_MAPPINGS_JOB_ID) {
                final boolean jobFinished = usageStatsManagerInternal.updatePackageMappingsData();
                jobFinished(params, !jobFinished); // reschedule if data was not updated
            } else {
                final boolean jobFinished =
                        usageStatsManagerInternal.pruneUninstalledPackagesData(userId);
                jobFinished(params, !jobFinished); // reschedule if data was not pruned
            }
        });
        return true;
    }
+36 −2
Original line number Diff line number Diff line
@@ -332,6 +332,11 @@ 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());
        }
        synchronized (mLock) {
            // Create a user unlocked event to report
            final Event unlockEvent = new Event(USER_UNLOCKED, SystemClock.elapsedRealtime());
@@ -543,8 +548,8 @@ public class UsageStatsService extends SystemService implements
     * Initializes the given user's usage stats service - this should ideally only be called once,
     * when the user is initially unlocked.
     */
    private void initializeUserUsageStatsServiceLocked(int userId,
            long currentTimeMillis, HashMap<String, Long> installedPackages) {
    private void initializeUserUsageStatsServiceLocked(int userId, long currentTimeMillis,
            HashMap<String, Long> installedPackages) {
        final File usageStatsDir = new File(Environment.getDataSystemCeDirectory(userId),
                "usagestats");
        final UserUsageStatsService service = new UserUsageStatsService(getContext(), userId,
@@ -931,6 +936,7 @@ public class UsageStatsService extends SystemService implements
        }
        // Cancel any scheduled jobs for this user since the user is being removed.
        UsageStatsIdleService.cancelJob(getContext(), userId);
        UsageStatsIdleService.cancelUpdateMappingsJob(getContext());
    }

    /**
@@ -977,6 +983,26 @@ public class UsageStatsService extends SystemService implements
        }
    }

    /**
     * Called by the Binder stub.
     */
    private boolean updatePackageMappingsData() {
        // fetch the installed packages outside the lock so it doesn't block package manager.
        final HashMap<String, Long> installedPkgs = getInstalledPackages(UserHandle.USER_SYSTEM);
        synchronized (mLock) {
            if (!mUserUnlockedStates.get(UserHandle.USER_SYSTEM)) {
                return false; // user is no longer unlocked
            }

            final UserUsageStatsService userService = mUserState.get(UserHandle.USER_SYSTEM);
            if (userService == null) {
                return false; // user was stopped or removed
            }

            return userService.updatePackageMappingsLocked(installedPkgs);
        }
    }

    /**
     * Called by the Binder stub.
     */
@@ -2137,6 +2163,9 @@ public class UsageStatsService extends SystemService implements
                }

                // 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) {
@@ -2229,6 +2258,11 @@ public class UsageStatsService extends SystemService implements
        public boolean pruneUninstalledPackagesData(int userId) {
            return UsageStatsService.this.pruneUninstalledPackagesData(userId);
        }

        @Override
        public boolean updatePackageMappingsData() {
            return UsageStatsService.this.updatePackageMappingsData();
        }
    }

    private class MyPackageMonitor extends PackageMonitor {
+17 −6
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ import android.app.usage.UsageStatsManager;
import android.content.Context;
import android.content.res.Configuration;
import android.os.SystemClock;
import android.os.UserHandle;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -181,19 +182,27 @@ class UserUsageStatsService {

    private void readPackageMappingsLocked(HashMap<String, Long> installedPackages) {
        mDatabase.readMappingsLocked();
        // Package mappings for the system user are updated after 24 hours via a job scheduled by
        // UsageStatsIdleService to ensure restored data is not lost on first boot. Additionally,
        // this makes user service initialization a little quicker on subsequent boots.
        if (mUserId != UserHandle.USER_SYSTEM) {
            updatePackageMappingsLocked(installedPackages);
        }
    }

    /**
     * Queries Job Scheduler for any pending data prune jobs and if any exist, it updates the
     * package mappings in memory by removing those tokens.
     * Compares the package mappings on disk with the ones currently installed and removes the
     * mappings for those packages that have been uninstalled.
     * This will only happen once per device boot, when the user is unlocked for the first time.
     * If the user is the system user (user 0), this is delayed to ensure data for packages
     * that were restored isn't removed before the restore is complete.
     *
     * @param installedPackages map of installed packages (package_name:package_install_time)
     * @return {@code true} on a successful mappings update, {@code false} otherwise.
     */
    private void updatePackageMappingsLocked(HashMap<String, Long> installedPackages) {
    boolean updatePackageMappingsLocked(HashMap<String, Long> installedPackages) {
        if (ArrayUtils.isEmpty(installedPackages)) {
            return;
            return true;
        }

        final long timeNow = System.currentTimeMillis();
@@ -206,7 +215,7 @@ class UserUsageStatsService {
            }
        }
        if (removedPackages.isEmpty()) {
            return;
            return true;
        }

        // remove packages in the mappings that are no longer installed and persist to disk
@@ -217,7 +226,9 @@ class UserUsageStatsService {
            mDatabase.writeMappingsLocked();
        } catch (Exception e) {
            Slog.w(TAG, "Unable to write updated package mappings file on service initialization.");
            return false;
        }
        return true;
    }

    boolean pruneUninstalledPackagesData() {