1 /*
2  * Copyright (C) 2016 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 
17 package com.android.server.job;
18 
19 import android.app.ActivityManager;
20 import android.app.AppGlobals;
21 import android.content.pm.IPackageManager;
22 import android.content.pm.PackageManager;
23 import android.os.Binder;
24 import android.os.ShellCommand;
25 import android.os.UserHandle;
26 
27 import java.io.PrintWriter;
28 
29 public final class JobSchedulerShellCommand extends ShellCommand {
30     public static final int CMD_ERR_NO_PACKAGE = -1000;
31     public static final int CMD_ERR_NO_JOB = -1001;
32     public static final int CMD_ERR_CONSTRAINTS = -1002;
33 
34     JobSchedulerService mInternal;
35     IPackageManager mPM;
36 
JobSchedulerShellCommand(JobSchedulerService service)37     JobSchedulerShellCommand(JobSchedulerService service) {
38         mInternal = service;
39         mPM = AppGlobals.getPackageManager();
40     }
41 
42     @Override
onCommand(String cmd)43     public int onCommand(String cmd) {
44         final PrintWriter pw = getOutPrintWriter();
45         try {
46             switch (cmd != null ? cmd : "") {
47                 case "run":
48                     return runJob(pw);
49                 case "timeout":
50                     return timeout(pw);
51                 case "monitor-battery":
52                     return monitorBattery(pw);
53                 case "get-battery-seq":
54                     return getBatterySeq(pw);
55                 case "get-battery-charging":
56                     return getBatteryCharging(pw);
57                 case "get-battery-not-low":
58                     return getBatteryNotLow(pw);
59                 case "get-storage-seq":
60                     return getStorageSeq(pw);
61                 case "get-storage-not-low":
62                     return getStorageNotLow(pw);
63                 case "get-job-state":
64                     return getJobState(pw);
65                 default:
66                     return handleDefaultCommands(cmd);
67             }
68         } catch (Exception e) {
69             pw.println("Exception: " + e);
70         }
71         return -1;
72     }
73 
checkPermission(String operation)74     private void checkPermission(String operation) throws Exception {
75         final int uid = Binder.getCallingUid();
76         if (uid == 0) {
77             // Root can do anything.
78             return;
79         }
80         final int perm = mPM.checkUidPermission(
81                 "android.permission.CHANGE_APP_IDLE_STATE", uid);
82         if (perm != PackageManager.PERMISSION_GRANTED) {
83             throw new SecurityException("Uid " + uid
84                     + " not permitted to " + operation);
85         }
86     }
87 
printError(int errCode, String pkgName, int userId, int jobId)88     private boolean printError(int errCode, String pkgName, int userId, int jobId) {
89         PrintWriter pw;
90         switch (errCode) {
91             case CMD_ERR_NO_PACKAGE:
92                 pw = getErrPrintWriter();
93                 pw.print("Package not found: ");
94                 pw.print(pkgName);
95                 pw.print(" / user ");
96                 pw.println(userId);
97                 return true;
98 
99             case CMD_ERR_NO_JOB:
100                 pw = getErrPrintWriter();
101                 pw.print("Could not find job ");
102                 pw.print(jobId);
103                 pw.print(" in package ");
104                 pw.print(pkgName);
105                 pw.print(" / user ");
106                 pw.println(userId);
107                 return true;
108 
109             case CMD_ERR_CONSTRAINTS:
110                 pw = getErrPrintWriter();
111                 pw.print("Job ");
112                 pw.print(jobId);
113                 pw.print(" in package ");
114                 pw.print(pkgName);
115                 pw.print(" / user ");
116                 pw.print(userId);
117                 pw.println(" has functional constraints but --force not specified");
118                 return true;
119 
120             default:
121                 return false;
122         }
123     }
124 
runJob(PrintWriter pw)125     private int runJob(PrintWriter pw) throws Exception {
126         checkPermission("force scheduled jobs");
127 
128         boolean force = false;
129         int userId = UserHandle.USER_SYSTEM;
130 
131         String opt;
132         while ((opt = getNextOption()) != null) {
133             switch (opt) {
134                 case "-f":
135                 case "--force":
136                     force = true;
137                     break;
138 
139                 case "-u":
140                 case "--user":
141                     userId = Integer.parseInt(getNextArgRequired());
142                     break;
143 
144                 default:
145                     pw.println("Error: unknown option '" + opt + "'");
146                     return -1;
147             }
148         }
149 
150         final String pkgName = getNextArgRequired();
151         final int jobId = Integer.parseInt(getNextArgRequired());
152 
153         final long ident = Binder.clearCallingIdentity();
154         try {
155             int ret = mInternal.executeRunCommand(pkgName, userId, jobId, force);
156             if (printError(ret, pkgName, userId, jobId)) {
157                 return ret;
158             }
159 
160             // success!
161             pw.print("Running job");
162             if (force) {
163                 pw.print(" [FORCED]");
164             }
165             pw.println();
166 
167             return ret;
168         } finally {
169             Binder.restoreCallingIdentity(ident);
170         }
171     }
172 
timeout(PrintWriter pw)173     private int timeout(PrintWriter pw) throws Exception {
174         checkPermission("force timeout jobs");
175 
176         int userId = UserHandle.USER_ALL;
177 
178         String opt;
179         while ((opt = getNextOption()) != null) {
180             switch (opt) {
181                 case "-u":
182                 case "--user":
183                     userId = UserHandle.parseUserArg(getNextArgRequired());
184                     break;
185 
186                 default:
187                     pw.println("Error: unknown option '" + opt + "'");
188                     return -1;
189             }
190         }
191 
192         if (userId == UserHandle.USER_CURRENT) {
193             userId = ActivityManager.getCurrentUser();
194         }
195 
196         final String pkgName = getNextArg();
197         final String jobIdStr = getNextArg();
198         final int jobId = jobIdStr != null ? Integer.parseInt(jobIdStr) : -1;
199 
200         final long ident = Binder.clearCallingIdentity();
201         try {
202             return mInternal.executeTimeoutCommand(pw, pkgName, userId, jobIdStr != null, jobId);
203         } finally {
204             Binder.restoreCallingIdentity(ident);
205         }
206     }
207 
monitorBattery(PrintWriter pw)208     private int monitorBattery(PrintWriter pw) throws Exception {
209         checkPermission("change battery monitoring");
210         String opt = getNextArgRequired();
211         boolean enabled;
212         if ("on".equals(opt)) {
213             enabled = true;
214         } else if ("off".equals(opt)) {
215             enabled = false;
216         } else {
217             getErrPrintWriter().println("Error: unknown option " + opt);
218             return 1;
219         }
220         final long ident = Binder.clearCallingIdentity();
221         try {
222             mInternal.setMonitorBattery(enabled);
223             if (enabled) pw.println("Battery monitoring enabled");
224             else pw.println("Battery monitoring disabled");
225         } finally {
226             Binder.restoreCallingIdentity(ident);
227         }
228         return 0;
229     }
230 
getBatterySeq(PrintWriter pw)231     private int getBatterySeq(PrintWriter pw) {
232         int seq = mInternal.getBatterySeq();
233         pw.println(seq);
234         return 0;
235     }
236 
getBatteryCharging(PrintWriter pw)237     private int getBatteryCharging(PrintWriter pw) {
238         boolean val = mInternal.getBatteryCharging();
239         pw.println(val);
240         return 0;
241     }
242 
getBatteryNotLow(PrintWriter pw)243     private int getBatteryNotLow(PrintWriter pw) {
244         boolean val = mInternal.getBatteryNotLow();
245         pw.println(val);
246         return 0;
247     }
248 
getStorageSeq(PrintWriter pw)249     private int getStorageSeq(PrintWriter pw) {
250         int seq = mInternal.getStorageSeq();
251         pw.println(seq);
252         return 0;
253     }
254 
getStorageNotLow(PrintWriter pw)255     private int getStorageNotLow(PrintWriter pw) {
256         boolean val = mInternal.getStorageNotLow();
257         pw.println(val);
258         return 0;
259     }
260 
getJobState(PrintWriter pw)261     private int getJobState(PrintWriter pw) throws Exception {
262         checkPermission("force timeout jobs");
263 
264         int userId = UserHandle.USER_SYSTEM;
265 
266         String opt;
267         while ((opt = getNextOption()) != null) {
268             switch (opt) {
269                 case "-u":
270                 case "--user":
271                     userId = UserHandle.parseUserArg(getNextArgRequired());
272                     break;
273 
274                 default:
275                     pw.println("Error: unknown option '" + opt + "'");
276                     return -1;
277             }
278         }
279 
280         if (userId == UserHandle.USER_CURRENT) {
281             userId = ActivityManager.getCurrentUser();
282         }
283 
284         final String pkgName = getNextArgRequired();
285         final String jobIdStr = getNextArgRequired();
286         final int jobId = Integer.parseInt(jobIdStr);
287 
288         final long ident = Binder.clearCallingIdentity();
289         try {
290             int ret = mInternal.getJobState(pw, pkgName, userId, jobId);
291             printError(ret, pkgName, userId, jobId);
292             return ret;
293         } finally {
294             Binder.restoreCallingIdentity(ident);
295         }
296     }
297 
298     @Override
onHelp()299     public void onHelp() {
300         final PrintWriter pw = getOutPrintWriter();
301 
302         pw.println("Job scheduler (jobscheduler) commands:");
303         pw.println("  help");
304         pw.println("    Print this help text.");
305         pw.println("  run [-f | --force] [-u | --user USER_ID] PACKAGE JOB_ID");
306         pw.println("    Trigger immediate execution of a specific scheduled job.");
307         pw.println("    Options:");
308         pw.println("      -f or --force: run the job even if technical constraints such as");
309         pw.println("         connectivity are not currently met");
310         pw.println("      -u or --user: specify which user's job is to be run; the default is");
311         pw.println("         the primary or system user");
312         pw.println("  timeout [-u | --user USER_ID] [PACKAGE] [JOB_ID]");
313         pw.println("    Trigger immediate timeout of currently executing jobs, as if their.");
314         pw.println("    execution timeout had expired.");
315         pw.println("    Options:");
316         pw.println("      -u or --user: specify which user's job is to be run; the default is");
317         pw.println("         all users");
318         pw.println("  monitor-battery [on|off]");
319         pw.println("    Control monitoring of all battery changes.  Off by default.  Turning");
320         pw.println("    on makes get-battery-seq useful.");
321         pw.println("  get-battery-seq");
322         pw.println("    Return the last battery update sequence number that was received.");
323         pw.println("  get-battery-charging");
324         pw.println("    Return whether the battery is currently considered to be charging.");
325         pw.println("  get-battery-not-low");
326         pw.println("    Return whether the battery is currently considered to not be low.");
327         pw.println("  get-storage-seq");
328         pw.println("    Return the last storage update sequence number that was received.");
329         pw.println("  get-storage-not-low");
330         pw.println("    Return whether storage is currently considered to not be low.");
331         pw.println("  get-job-state [-u | --user USER_ID] PACKAGE JOB_ID");
332         pw.println("    Return the current state of a job, may be any combination of:");
333         pw.println("      pending: currently on the pending list, waiting to be active");
334         pw.println("      active: job is actively running");
335         pw.println("      user-stopped: job can't run because its user is stopped");
336         pw.println("      backing-up: job can't run because app is currently backing up its data");
337         pw.println("      no-component: job can't run because its component is not available");
338         pw.println("      ready: job is ready to run (all constraints satisfied or bypassed)");
339         pw.println("      waiting: if nothing else above is printed, job not ready to run");
340         pw.println("    Options:");
341         pw.println("      -u or --user: specify which user's job is to be run; the default is");
342         pw.println("         the primary or system user");
343         pw.println();
344     }
345 
346 }
347