1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.server.usage; 17 18 import android.app.job.JobInfo; 19 import android.app.job.JobParameters; 20 import android.app.job.JobScheduler; 21 import android.app.job.JobService; 22 import android.app.usage.UsageStatsManagerInternal; 23 import android.content.ComponentName; 24 import android.content.Context; 25 import android.os.AsyncTask; 26 import android.os.PersistableBundle; 27 28 import com.android.server.LocalServices; 29 30 import java.util.concurrent.TimeUnit; 31 32 /** 33 * JobService used to do any work for UsageStats while the device is idle. 34 */ 35 public class UsageStatsIdleService extends JobService { 36 37 /** 38 * Base job ID for the pruning job - must be unique within the system server uid. 39 */ 40 private static final int PRUNE_JOB_ID = 546357475; 41 /** 42 * Job ID for the update mappings job - must be unique within the system server uid. 43 * Incrementing PRUNE_JOB_ID by 21475 (MAX_USER_ID) to ensure there is no overlap in job ids. 44 */ 45 private static final int UPDATE_MAPPINGS_JOB_ID = 546378950; 46 47 private static final String USER_ID_KEY = "user_id"; 48 scheduleJob(Context context, int userId)49 static void scheduleJob(Context context, int userId) { 50 final int userJobId = PRUNE_JOB_ID + userId; // unique job id per user 51 final ComponentName component = new ComponentName(context.getPackageName(), 52 UsageStatsIdleService.class.getName()); 53 final PersistableBundle bundle = new PersistableBundle(); 54 bundle.putInt(USER_ID_KEY, userId); 55 final JobInfo pruneJob = new JobInfo.Builder(userJobId, component) 56 .setRequiresDeviceIdle(true) 57 .setExtras(bundle) 58 .setPersisted(true) 59 .build(); 60 61 scheduleJobInternal(context, pruneJob, userJobId); 62 } 63 scheduleUpdateMappingsJob(Context context)64 static void scheduleUpdateMappingsJob(Context context) { 65 final ComponentName component = new ComponentName(context.getPackageName(), 66 UsageStatsIdleService.class.getName()); 67 final JobInfo updateMappingsJob = new JobInfo.Builder(UPDATE_MAPPINGS_JOB_ID, component) 68 .setPersisted(true) 69 .setMinimumLatency(TimeUnit.DAYS.toMillis(1)) 70 .setOverrideDeadline(TimeUnit.DAYS.toMillis(2)) 71 .build(); 72 73 scheduleJobInternal(context, updateMappingsJob, UPDATE_MAPPINGS_JOB_ID); 74 } 75 scheduleJobInternal(Context context, JobInfo pruneJob, int jobId)76 private static void scheduleJobInternal(Context context, JobInfo pruneJob, int jobId) { 77 final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); 78 final JobInfo pendingPruneJob = jobScheduler.getPendingJob(jobId); 79 // only schedule a new prune job if one doesn't exist already for this user 80 if (!pruneJob.equals(pendingPruneJob)) { 81 jobScheduler.cancel(jobId); // cancel any previously scheduled prune job 82 jobScheduler.schedule(pruneJob); 83 } 84 } 85 cancelJob(Context context, int userId)86 static void cancelJob(Context context, int userId) { 87 cancelJobInternal(context, PRUNE_JOB_ID + userId); 88 } 89 cancelUpdateMappingsJob(Context context)90 static void cancelUpdateMappingsJob(Context context) { 91 cancelJobInternal(context, UPDATE_MAPPINGS_JOB_ID); 92 } 93 cancelJobInternal(Context context, int jobId)94 private static void cancelJobInternal(Context context, int jobId) { 95 final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); 96 if (jobScheduler != null) { 97 jobScheduler.cancel(jobId); 98 } 99 } 100 101 @Override onStartJob(JobParameters params)102 public boolean onStartJob(JobParameters params) { 103 final PersistableBundle bundle = params.getExtras(); 104 final int userId = bundle.getInt(USER_ID_KEY, -1); 105 if (userId == -1 && params.getJobId() != UPDATE_MAPPINGS_JOB_ID) { 106 return false; 107 } 108 109 AsyncTask.execute(() -> { 110 final UsageStatsManagerInternal usageStatsManagerInternal = LocalServices.getService( 111 UsageStatsManagerInternal.class); 112 if (params.getJobId() == UPDATE_MAPPINGS_JOB_ID) { 113 final boolean jobFinished = usageStatsManagerInternal.updatePackageMappingsData(); 114 jobFinished(params, !jobFinished); // reschedule if data was not updated 115 } else { 116 final boolean jobFinished = 117 usageStatsManagerInternal.pruneUninstalledPackagesData(userId); 118 jobFinished(params, !jobFinished); // reschedule if data was not pruned 119 } 120 }); 121 return true; 122 } 123 124 @Override onStopJob(JobParameters params)125 public boolean onStopJob(JobParameters params) { 126 // Since the pruning job isn't a heavy job, we don't want to cancel it's execution midway. 127 return false; 128 } 129 } 130