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.annotation.UserIdInt; 19 import android.app.job.JobInfo; 20 import android.app.job.JobParameters; 21 import android.app.job.JobScheduler; 22 import android.app.job.JobService; 23 import android.app.usage.UsageStatsManagerInternal; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.os.AsyncTask; 27 import android.os.PersistableBundle; 28 29 import com.android.server.LocalServices; 30 31 import java.util.concurrent.TimeUnit; 32 33 /** 34 * JobService used to do any work for UsageStats while the device is idle. 35 */ 36 public class UsageStatsIdleService extends JobService { 37 38 /** 39 * Namespace for prune job 40 */ 41 private static final String PRUNE_JOB_NS = "usagestats_prune"; 42 43 /** 44 * Namespace for update mappings job 45 */ 46 private static final String UPDATE_MAPPINGS_JOB_NS = "usagestats_mapping"; 47 48 private static final String USER_ID_KEY = "user_id"; 49 50 /** Schedule a prune job */ schedulePruneJob(Context context, @UserIdInt int userId)51 static void schedulePruneJob(Context context, @UserIdInt int userId) { 52 final ComponentName component = new ComponentName(context.getPackageName(), 53 UsageStatsIdleService.class.getName()); 54 final PersistableBundle bundle = new PersistableBundle(); 55 bundle.putInt(USER_ID_KEY, userId); 56 final JobInfo pruneJob = new JobInfo.Builder(userId, component) 57 .setRequiresDeviceIdle(true) 58 .setExtras(bundle) 59 .setPersisted(true) 60 .build(); 61 62 scheduleJobInternal(context, pruneJob, PRUNE_JOB_NS, userId); 63 } 64 scheduleUpdateMappingsJob(Context context, @UserIdInt int userId)65 static void scheduleUpdateMappingsJob(Context context, @UserIdInt int userId) { 66 final ComponentName component = new ComponentName(context.getPackageName(), 67 UsageStatsIdleService.class.getName()); 68 final PersistableBundle bundle = new PersistableBundle(); 69 bundle.putInt(USER_ID_KEY, userId); 70 final JobInfo updateMappingsJob = new JobInfo.Builder(userId, component) 71 .setPersisted(true) 72 .setMinimumLatency(TimeUnit.DAYS.toMillis(1)) 73 .setOverrideDeadline(TimeUnit.DAYS.toMillis(2)) 74 .setExtras(bundle) 75 .build(); 76 77 scheduleJobInternal(context, updateMappingsJob, UPDATE_MAPPINGS_JOB_NS, userId); 78 } 79 scheduleJobInternal(Context context, JobInfo jobInfo, String namespace, int jobId)80 private static void scheduleJobInternal(Context context, JobInfo jobInfo, 81 String namespace, int jobId) { 82 JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); 83 jobScheduler = jobScheduler.forNamespace(namespace); 84 final JobInfo pendingJob = jobScheduler.getPendingJob(jobId); 85 // only schedule a new job if one doesn't exist already for this user 86 if (!jobInfo.equals(pendingJob)) { 87 jobScheduler.cancel(jobId); // cancel any previously scheduled job 88 jobScheduler.schedule(jobInfo); 89 } 90 } 91 cancelPruneJob(Context context, @UserIdInt int userId)92 static void cancelPruneJob(Context context, @UserIdInt int userId) { 93 cancelJobInternal(context, PRUNE_JOB_NS, userId); 94 } 95 cancelUpdateMappingsJob(Context context, @UserIdInt int userId)96 static void cancelUpdateMappingsJob(Context context, @UserIdInt int userId) { 97 cancelJobInternal(context, UPDATE_MAPPINGS_JOB_NS, userId); 98 } 99 cancelJobInternal(Context context, String namespace, int jobId)100 private static void cancelJobInternal(Context context, String namespace, int jobId) { 101 JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); 102 if (jobScheduler != null) { 103 jobScheduler = jobScheduler.forNamespace(namespace); 104 jobScheduler.cancel(jobId); 105 } 106 } 107 108 @Override onStartJob(JobParameters params)109 public boolean onStartJob(JobParameters params) { 110 final PersistableBundle bundle = params.getExtras(); 111 final int userId = bundle.getInt(USER_ID_KEY, -1); 112 113 if (userId == -1) { // legacy job 114 return false; 115 } 116 117 // Do async 118 AsyncTask.execute(() -> { 119 final UsageStatsManagerInternal usageStatsManagerInternal = LocalServices.getService( 120 UsageStatsManagerInternal.class); 121 final String jobNs = params.getJobNamespace(); 122 if (UPDATE_MAPPINGS_JOB_NS.equals(jobNs)) { 123 final boolean jobFinished = 124 usageStatsManagerInternal.updatePackageMappingsData(userId); 125 jobFinished(params, !jobFinished); // reschedule if data was not updated 126 } else { 127 final boolean jobFinished = 128 usageStatsManagerInternal.pruneUninstalledPackagesData(userId); 129 jobFinished(params, !jobFinished); // reschedule if data was not pruned 130 } 131 }); 132 133 // Job is running asynchronously 134 return true; 135 } 136 137 @Override onStopJob(JobParameters params)138 public boolean onStopJob(JobParameters params) { 139 // Since the pruning job isn't a heavy job, we don't want to cancel it's execution midway. 140 return false; 141 } 142 } 143