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.UserHandle;
25 
26 import com.android.modules.utils.BasicShellCommandHandler;
27 
28 import java.io.PrintWriter;
29 
30 public final class JobSchedulerShellCommand extends BasicShellCommandHandler {
31     public static final int CMD_ERR_NO_PACKAGE = -1000;
32     public static final int CMD_ERR_NO_JOB = -1001;
33     public static final int CMD_ERR_CONSTRAINTS = -1002;
34 
35     JobSchedulerService mInternal;
36     IPackageManager mPM;
37 
JobSchedulerShellCommand(JobSchedulerService service)38     JobSchedulerShellCommand(JobSchedulerService service) {
39         mInternal = service;
40         mPM = AppGlobals.getPackageManager();
41     }
42 
43     @Override
onCommand(String cmd)44     public int onCommand(String cmd) {
45         final PrintWriter pw = getOutPrintWriter();
46         try {
47             switch (cmd != null ? cmd : "") {
48                 case "run":
49                     return runJob(pw);
50                 case "timeout":
51                     return timeout(pw);
52                 case "cancel":
53                     return cancelJob(pw);
54                 case "monitor-battery":
55                     return monitorBattery(pw);
56                 case "get-battery-seq":
57                     return getBatterySeq(pw);
58                 case "get-battery-charging":
59                     return getBatteryCharging(pw);
60                 case "get-battery-not-low":
61                     return getBatteryNotLow(pw);
62                 case "get-storage-seq":
63                     return getStorageSeq(pw);
64                 case "get-storage-not-low":
65                     return getStorageNotLow(pw);
66                 case "get-job-state":
67                     return getJobState(pw);
68                 case "heartbeat":
69                     return doHeartbeat(pw);
70                 case "reset-execution-quota":
71                     return resetExecutionQuota(pw);
72                 case "reset-schedule-quota":
73                     return resetScheduleQuota(pw);
74                 case "trigger-dock-state":
75                     return triggerDockState(pw);
76                 default:
77                     return handleDefaultCommands(cmd);
78             }
79         } catch (Exception e) {
80             pw.println("Exception: " + e);
81         }
82         return -1;
83     }
84 
checkPermission(String operation)85     private void checkPermission(String operation) throws Exception {
86         final int uid = Binder.getCallingUid();
87         if (uid == 0) {
88             // Root can do anything.
89             return;
90         }
91         final int perm = mPM.checkUidPermission(
92                 "android.permission.CHANGE_APP_IDLE_STATE", uid);
93         if (perm != PackageManager.PERMISSION_GRANTED) {
94             throw new SecurityException("Uid " + uid
95                     + " not permitted to " + operation);
96         }
97     }
98 
printError(int errCode, String pkgName, int userId, int jobId)99     private boolean printError(int errCode, String pkgName, int userId, int jobId) {
100         PrintWriter pw;
101         switch (errCode) {
102             case CMD_ERR_NO_PACKAGE:
103                 pw = getErrPrintWriter();
104                 pw.print("Package not found: ");
105                 pw.print(pkgName);
106                 pw.print(" / user ");
107                 pw.println(userId);
108                 return true;
109 
110             case CMD_ERR_NO_JOB:
111                 pw = getErrPrintWriter();
112                 pw.print("Could not find job ");
113                 pw.print(jobId);
114                 pw.print(" in package ");
115                 pw.print(pkgName);
116                 pw.print(" / user ");
117                 pw.println(userId);
118                 return true;
119 
120             case CMD_ERR_CONSTRAINTS:
121                 pw = getErrPrintWriter();
122                 pw.print("Job ");
123                 pw.print(jobId);
124                 pw.print(" in package ");
125                 pw.print(pkgName);
126                 pw.print(" / user ");
127                 pw.print(userId);
128                 pw.println(" has functional constraints but --force not specified");
129                 return true;
130 
131             default:
132                 return false;
133         }
134     }
135 
runJob(PrintWriter pw)136     private int runJob(PrintWriter pw) throws Exception {
137         checkPermission("force scheduled jobs");
138 
139         boolean force = false;
140         boolean satisfied = false;
141         int userId = UserHandle.USER_SYSTEM;
142 
143         String opt;
144         while ((opt = getNextOption()) != null) {
145             switch (opt) {
146                 case "-f":
147                 case "--force":
148                     force = true;
149                     break;
150 
151                 case "-s":
152                 case "--satisfied":
153                     satisfied = true;
154                     break;
155 
156                 case "-u":
157                 case "--user":
158                     userId = Integer.parseInt(getNextArgRequired());
159                     break;
160 
161                 default:
162                     pw.println("Error: unknown option '" + opt + "'");
163                     return -1;
164             }
165         }
166 
167         if (force && satisfied) {
168             pw.println("Cannot specify both --force and --satisfied");
169             return -1;
170         }
171 
172         final String pkgName = getNextArgRequired();
173         final int jobId = Integer.parseInt(getNextArgRequired());
174 
175         final long ident = Binder.clearCallingIdentity();
176         try {
177             int ret = mInternal.executeRunCommand(pkgName, userId, jobId, satisfied, force);
178             if (printError(ret, pkgName, userId, jobId)) {
179                 return ret;
180             }
181 
182             // success!
183             pw.print("Running job");
184             if (force) {
185                 pw.print(" [FORCED]");
186             }
187             pw.println();
188 
189             return ret;
190         } finally {
191             Binder.restoreCallingIdentity(ident);
192         }
193     }
194 
timeout(PrintWriter pw)195     private int timeout(PrintWriter pw) throws Exception {
196         checkPermission("force timeout jobs");
197 
198         int userId = UserHandle.USER_ALL;
199 
200         String opt;
201         while ((opt = getNextOption()) != null) {
202             switch (opt) {
203                 case "-u":
204                 case "--user":
205                     userId = UserHandle.parseUserArg(getNextArgRequired());
206                     break;
207 
208                 default:
209                     pw.println("Error: unknown option '" + opt + "'");
210                     return -1;
211             }
212         }
213 
214         if (userId == UserHandle.USER_CURRENT) {
215             userId = ActivityManager.getCurrentUser();
216         }
217 
218         final String pkgName = getNextArg();
219         final String jobIdStr = getNextArg();
220         final int jobId = jobIdStr != null ? Integer.parseInt(jobIdStr) : -1;
221 
222         final long ident = Binder.clearCallingIdentity();
223         try {
224             return mInternal.executeTimeoutCommand(pw, pkgName, userId, jobIdStr != null, jobId);
225         } finally {
226             Binder.restoreCallingIdentity(ident);
227         }
228     }
229 
cancelJob(PrintWriter pw)230     private int cancelJob(PrintWriter pw) throws Exception {
231         checkPermission("cancel jobs");
232 
233         int userId = UserHandle.USER_SYSTEM;
234 
235         String opt;
236         while ((opt = getNextOption()) != null) {
237             switch (opt) {
238                 case "-u":
239                 case "--user":
240                     userId = UserHandle.parseUserArg(getNextArgRequired());
241                     break;
242 
243                 default:
244                     pw.println("Error: unknown option '" + opt + "'");
245                     return -1;
246             }
247         }
248 
249         if (userId < 0) {
250             pw.println("Error: must specify a concrete user ID");
251             return -1;
252         }
253 
254         final String pkgName = getNextArg();
255         final String jobIdStr = getNextArg();
256         final int jobId = jobIdStr != null ? Integer.parseInt(jobIdStr) : -1;
257 
258         final long ident = Binder.clearCallingIdentity();
259         try {
260             return mInternal.executeCancelCommand(pw, pkgName, userId, jobIdStr != null, jobId);
261         } finally {
262             Binder.restoreCallingIdentity(ident);
263         }
264     }
265 
monitorBattery(PrintWriter pw)266     private int monitorBattery(PrintWriter pw) throws Exception {
267         checkPermission("change battery monitoring");
268         String opt = getNextArgRequired();
269         boolean enabled;
270         if ("on".equals(opt)) {
271             enabled = true;
272         } else if ("off".equals(opt)) {
273             enabled = false;
274         } else {
275             getErrPrintWriter().println("Error: unknown option " + opt);
276             return 1;
277         }
278         final long ident = Binder.clearCallingIdentity();
279         try {
280             mInternal.setMonitorBattery(enabled);
281             if (enabled) pw.println("Battery monitoring enabled");
282             else pw.println("Battery monitoring disabled");
283         } finally {
284             Binder.restoreCallingIdentity(ident);
285         }
286         return 0;
287     }
288 
getBatterySeq(PrintWriter pw)289     private int getBatterySeq(PrintWriter pw) {
290         int seq = mInternal.getBatterySeq();
291         pw.println(seq);
292         return 0;
293     }
294 
getBatteryCharging(PrintWriter pw)295     private int getBatteryCharging(PrintWriter pw) {
296         boolean val = mInternal.getBatteryCharging();
297         pw.println(val);
298         return 0;
299     }
300 
getBatteryNotLow(PrintWriter pw)301     private int getBatteryNotLow(PrintWriter pw) {
302         boolean val = mInternal.getBatteryNotLow();
303         pw.println(val);
304         return 0;
305     }
306 
getStorageSeq(PrintWriter pw)307     private int getStorageSeq(PrintWriter pw) {
308         int seq = mInternal.getStorageSeq();
309         pw.println(seq);
310         return 0;
311     }
312 
getStorageNotLow(PrintWriter pw)313     private int getStorageNotLow(PrintWriter pw) {
314         boolean val = mInternal.getStorageNotLow();
315         pw.println(val);
316         return 0;
317     }
318 
getJobState(PrintWriter pw)319     private int getJobState(PrintWriter pw) throws Exception {
320         checkPermission("force timeout jobs");
321 
322         int userId = UserHandle.USER_SYSTEM;
323 
324         String opt;
325         while ((opt = getNextOption()) != null) {
326             switch (opt) {
327                 case "-u":
328                 case "--user":
329                     userId = UserHandle.parseUserArg(getNextArgRequired());
330                     break;
331 
332                 default:
333                     pw.println("Error: unknown option '" + opt + "'");
334                     return -1;
335             }
336         }
337 
338         if (userId == UserHandle.USER_CURRENT) {
339             userId = ActivityManager.getCurrentUser();
340         }
341 
342         final String pkgName = getNextArgRequired();
343         final String jobIdStr = getNextArgRequired();
344         final int jobId = Integer.parseInt(jobIdStr);
345 
346         final long ident = Binder.clearCallingIdentity();
347         try {
348             int ret = mInternal.getJobState(pw, pkgName, userId, jobId);
349             printError(ret, pkgName, userId, jobId);
350             return ret;
351         } finally {
352             Binder.restoreCallingIdentity(ident);
353         }
354     }
355 
doHeartbeat(PrintWriter pw)356     private int doHeartbeat(PrintWriter pw) throws Exception {
357         checkPermission("manipulate scheduler heartbeat");
358 
359         pw.println("Heartbeat command is no longer supported");
360         return -1;
361     }
362 
resetExecutionQuota(PrintWriter pw)363     private int resetExecutionQuota(PrintWriter pw) throws Exception {
364         checkPermission("reset execution quota");
365 
366         int userId = UserHandle.USER_SYSTEM;
367 
368         String opt;
369         while ((opt = getNextOption()) != null) {
370             switch (opt) {
371                 case "-u":
372                 case "--user":
373                     userId = UserHandle.parseUserArg(getNextArgRequired());
374                     break;
375 
376                 default:
377                     pw.println("Error: unknown option '" + opt + "'");
378                     return -1;
379             }
380         }
381 
382         if (userId == UserHandle.USER_CURRENT) {
383             userId = ActivityManager.getCurrentUser();
384         }
385 
386         final String pkgName = getNextArgRequired();
387 
388         final long ident = Binder.clearCallingIdentity();
389         try {
390             mInternal.resetExecutionQuota(pkgName, userId);
391         } finally {
392             Binder.restoreCallingIdentity(ident);
393         }
394         return 0;
395     }
396 
resetScheduleQuota(PrintWriter pw)397     private int resetScheduleQuota(PrintWriter pw) throws Exception {
398         checkPermission("reset schedule quota");
399 
400         final long ident = Binder.clearCallingIdentity();
401         try {
402             mInternal.resetScheduleQuota();
403         } finally {
404             Binder.restoreCallingIdentity(ident);
405         }
406         return 0;
407     }
408 
triggerDockState(PrintWriter pw)409     private int triggerDockState(PrintWriter pw) throws Exception {
410         checkPermission("trigger wireless charging dock state");
411 
412         final String opt = getNextArgRequired();
413         boolean idleState;
414         if ("idle".equals(opt)) {
415             idleState = true;
416         } else if ("active".equals(opt)) {
417             idleState = false;
418         } else {
419             getErrPrintWriter().println("Error: unknown option " + opt);
420             return 1;
421         }
422 
423         final long ident = Binder.clearCallingIdentity();
424         try {
425             mInternal.triggerDockState(idleState);
426         } finally {
427             Binder.restoreCallingIdentity(ident);
428         }
429         return 0;
430     }
431 
432     @Override
onHelp()433     public void onHelp() {
434         final PrintWriter pw = getOutPrintWriter();
435 
436         pw.println("Job scheduler (jobscheduler) commands:");
437         pw.println("  help");
438         pw.println("    Print this help text.");
439         pw.println("  run [-f | --force] [-s | --satisfied] [-u | --user USER_ID] PACKAGE JOB_ID");
440         pw.println("    Trigger immediate execution of a specific scheduled job. For historical");
441         pw.println("    reasons, some constraints, such as battery, are ignored when this");
442         pw.println("    command is called. If you don't want any constraints to be ignored,");
443         pw.println("    include the -s flag.");
444         pw.println("    Options:");
445         pw.println("      -f or --force: run the job even if technical constraints such as");
446         pw.println("         connectivity are not currently met. This is incompatible with -f ");
447         pw.println("         and so an error will be reported if both are given.");
448         pw.println("      -s or --satisfied: run the job only if all constraints are met.");
449         pw.println("         This is incompatible with -f and so an error will be reported");
450         pw.println("         if both are given.");
451         pw.println("      -u or --user: specify which user's job is to be run; the default is");
452         pw.println("         the primary or system user");
453         pw.println("  timeout [-u | --user USER_ID] [PACKAGE] [JOB_ID]");
454         pw.println("    Trigger immediate timeout of currently executing jobs, as if their.");
455         pw.println("    execution timeout had expired.");
456         pw.println("    Options:");
457         pw.println("      -u or --user: specify which user's job is to be run; the default is");
458         pw.println("         all users");
459         pw.println("  cancel [-u | --user USER_ID] PACKAGE [JOB_ID]");
460         pw.println("    Cancel a scheduled job.  If a job ID is not supplied, all jobs scheduled");
461         pw.println("    by that package will be canceled.  USE WITH CAUTION.");
462         pw.println("    Options:");
463         pw.println("      -u or --user: specify which user's job is to be run; the default is");
464         pw.println("         the primary or system user");
465         pw.println("  heartbeat [num]");
466         pw.println("    No longer used.");
467         pw.println("  monitor-battery [on|off]");
468         pw.println("    Control monitoring of all battery changes.  Off by default.  Turning");
469         pw.println("    on makes get-battery-seq useful.");
470         pw.println("  get-battery-seq");
471         pw.println("    Return the last battery update sequence number that was received.");
472         pw.println("  get-battery-charging");
473         pw.println("    Return whether the battery is currently considered to be charging.");
474         pw.println("  get-battery-not-low");
475         pw.println("    Return whether the battery is currently considered to not be low.");
476         pw.println("  get-storage-seq");
477         pw.println("    Return the last storage update sequence number that was received.");
478         pw.println("  get-storage-not-low");
479         pw.println("    Return whether storage is currently considered to not be low.");
480         pw.println("  get-job-state [-u | --user USER_ID] PACKAGE JOB_ID");
481         pw.println("    Return the current state of a job, may be any combination of:");
482         pw.println("      pending: currently on the pending list, waiting to be active");
483         pw.println("      active: job is actively running");
484         pw.println("      user-stopped: job can't run because its user is stopped");
485         pw.println("      backing-up: job can't run because app is currently backing up its data");
486         pw.println("      no-component: job can't run because its component is not available");
487         pw.println("      ready: job is ready to run (all constraints satisfied or bypassed)");
488         pw.println("      waiting: if nothing else above is printed, job not ready to run");
489         pw.println("    Options:");
490         pw.println("      -u or --user: specify which user's job is to be run; the default is");
491         pw.println("         the primary or system user");
492         pw.println("  trigger-dock-state [idle|active]");
493         pw.println("    Trigger wireless charging dock state.  Active by default.");
494         pw.println();
495     }
496 
497 }
498