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