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.annotation.Nullable;
20 import android.app.ActivityManager;
21 import android.app.AppGlobals;
22 import android.app.job.JobParameters;
23 import android.content.pm.IPackageManager;
24 import android.content.pm.PackageManager;
25 import android.os.Binder;
26 import android.os.UserHandle;
27 
28 import com.android.modules.utils.BasicShellCommandHandler;
29 
30 import java.io.PrintWriter;
31 
32 public final class JobSchedulerShellCommand extends BasicShellCommandHandler {
33     public static final int CMD_ERR_NO_PACKAGE = -1000;
34     public static final int CMD_ERR_NO_JOB = -1001;
35     public static final int CMD_ERR_CONSTRAINTS = -1002;
36 
37     static final int BYTE_OPTION_DOWNLOAD = 0;
38     static final int BYTE_OPTION_UPLOAD = 1;
39 
40     JobSchedulerService mInternal;
41     IPackageManager mPM;
42 
JobSchedulerShellCommand(JobSchedulerService service)43     JobSchedulerShellCommand(JobSchedulerService service) {
44         mInternal = service;
45         mPM = AppGlobals.getPackageManager();
46     }
47 
48     @Override
onCommand(String cmd)49     public int onCommand(String cmd) {
50         final PrintWriter pw = getOutPrintWriter();
51         try {
52             switch (cmd != null ? cmd : "") {
53                 case "run":
54                     return runJob(pw);
55                 case "timeout":
56                     return timeout(pw);
57                 case "cancel":
58                     return cancelJob(pw);
59                 case "monitor-battery":
60                     return monitorBattery(pw);
61                 case "get-battery-seq":
62                     return getBatterySeq(pw);
63                 case "get-battery-charging":
64                     return getBatteryCharging(pw);
65                 case "get-battery-not-low":
66                     return getBatteryNotLow(pw);
67                 case "get-estimated-download-bytes":
68                     return getEstimatedNetworkBytes(pw, BYTE_OPTION_DOWNLOAD);
69                 case "get-estimated-upload-bytes":
70                     return getEstimatedNetworkBytes(pw, BYTE_OPTION_UPLOAD);
71                 case "get-storage-seq":
72                     return getStorageSeq(pw);
73                 case "get-storage-not-low":
74                     return getStorageNotLow(pw);
75                 case "get-transferred-download-bytes":
76                     return getTransferredNetworkBytes(pw, BYTE_OPTION_DOWNLOAD);
77                 case "get-transferred-upload-bytes":
78                     return getTransferredNetworkBytes(pw, BYTE_OPTION_UPLOAD);
79                 case "get-job-state":
80                     return getJobState(pw);
81                 case "heartbeat":
82                     return doHeartbeat(pw);
83                 case "reset-execution-quota":
84                     return resetExecutionQuota(pw);
85                 case "reset-schedule-quota":
86                     return resetScheduleQuota(pw);
87                 case "stop":
88                     return stop(pw);
89                 case "trigger-dock-state":
90                     return triggerDockState(pw);
91                 default:
92                     return handleDefaultCommands(cmd);
93             }
94         } catch (Exception e) {
95             pw.println("Exception: " + e);
96         }
97         return -1;
98     }
99 
checkPermission(String operation)100     private void checkPermission(String operation) throws Exception {
101         final int uid = Binder.getCallingUid();
102         if (uid == 0) {
103             // Root can do anything.
104             return;
105         }
106         final int perm = mPM.checkUidPermission(
107                 "android.permission.CHANGE_APP_IDLE_STATE", uid);
108         if (perm != PackageManager.PERMISSION_GRANTED) {
109             throw new SecurityException("Uid " + uid
110                     + " not permitted to " + operation);
111         }
112     }
113 
printError(int errCode, String pkgName, int userId, @Nullable String namespace, int jobId)114     private boolean printError(int errCode, String pkgName, int userId, @Nullable String namespace,
115             int jobId) {
116         PrintWriter pw;
117         switch (errCode) {
118             case CMD_ERR_NO_PACKAGE:
119                 pw = getErrPrintWriter();
120                 pw.print("Package not found: ");
121                 pw.print(pkgName);
122                 pw.print(" / user ");
123                 pw.println(userId);
124                 return true;
125 
126             case CMD_ERR_NO_JOB:
127                 pw = getErrPrintWriter();
128                 pw.print("Could not find job ");
129                 pw.print(jobId);
130                 pw.print(" in package ");
131                 pw.print(pkgName);
132                 if (namespace != null) {
133                     pw.print(" / namespace ");
134                     pw.print(namespace);
135                 }
136                 pw.print(" / user ");
137                 pw.println(userId);
138                 return true;
139 
140             case CMD_ERR_CONSTRAINTS:
141                 pw = getErrPrintWriter();
142                 pw.print("Job ");
143                 pw.print(jobId);
144                 pw.print(" in package ");
145                 pw.print(pkgName);
146                 if (namespace != null) {
147                     pw.print(" / namespace ");
148                     pw.print(namespace);
149                 }
150                 pw.print(" / user ");
151                 pw.print(userId);
152                 pw.println(" has functional constraints but --force not specified");
153                 return true;
154 
155             default:
156                 return false;
157         }
158     }
159 
runJob(PrintWriter pw)160     private int runJob(PrintWriter pw) throws Exception {
161         checkPermission("force scheduled jobs");
162 
163         boolean force = false;
164         boolean satisfied = false;
165         int userId = UserHandle.USER_SYSTEM;
166         String namespace = null;
167 
168         String opt;
169         while ((opt = getNextOption()) != null) {
170             switch (opt) {
171                 case "-f":
172                 case "--force":
173                     force = true;
174                     break;
175 
176                 case "-s":
177                 case "--satisfied":
178                     satisfied = true;
179                     break;
180 
181                 case "-u":
182                 case "--user":
183                     userId = Integer.parseInt(getNextArgRequired());
184                     break;
185 
186                 case "-n":
187                 case "--namespace":
188                     namespace = getNextArgRequired();
189                     break;
190 
191                 default:
192                     pw.println("Error: unknown option '" + opt + "'");
193                     return -1;
194             }
195         }
196 
197         if (force && satisfied) {
198             pw.println("Cannot specify both --force and --satisfied");
199             return -1;
200         }
201 
202         final String pkgName = getNextArgRequired();
203         final int jobId = Integer.parseInt(getNextArgRequired());
204 
205         final long ident = Binder.clearCallingIdentity();
206         try {
207             int ret = mInternal.executeRunCommand(pkgName, userId, namespace,
208                     jobId, satisfied, force);
209             if (printError(ret, pkgName, userId, namespace, jobId)) {
210                 return ret;
211             }
212 
213             // success!
214             pw.print("Running job");
215             if (force) {
216                 pw.print(" [FORCED]");
217             }
218             pw.println();
219 
220             return ret;
221         } finally {
222             Binder.restoreCallingIdentity(ident);
223         }
224     }
225 
timeout(PrintWriter pw)226     private int timeout(PrintWriter pw) throws Exception {
227         checkPermission("force timeout jobs");
228 
229         int userId = UserHandle.USER_ALL;
230         String namespace = null;
231 
232         String opt;
233         while ((opt = getNextOption()) != null) {
234             switch (opt) {
235                 case "-u":
236                 case "--user":
237                     userId = UserHandle.parseUserArg(getNextArgRequired());
238                     break;
239 
240                 case "-n":
241                 case "--namespace":
242                     namespace = getNextArgRequired();
243                     break;
244 
245                 default:
246                     pw.println("Error: unknown option '" + opt + "'");
247                     return -1;
248             }
249         }
250 
251         if (userId == UserHandle.USER_CURRENT) {
252             userId = ActivityManager.getCurrentUser();
253         }
254 
255         final String pkgName = getNextArg();
256         final String jobIdStr = getNextArg();
257         final int jobId = jobIdStr != null ? Integer.parseInt(jobIdStr) : -1;
258 
259         final long ident = Binder.clearCallingIdentity();
260         try {
261             return mInternal.executeStopCommand(pw, pkgName, userId, namespace,
262                     jobIdStr != null, jobId,
263                     JobParameters.STOP_REASON_TIMEOUT, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
264         } finally {
265             Binder.restoreCallingIdentity(ident);
266         }
267     }
268 
cancelJob(PrintWriter pw)269     private int cancelJob(PrintWriter pw) throws Exception {
270         checkPermission("cancel jobs");
271 
272         int userId = UserHandle.USER_SYSTEM;
273         String namespace = null;
274 
275         String opt;
276         while ((opt = getNextOption()) != null) {
277             switch (opt) {
278                 case "-u":
279                 case "--user":
280                     userId = UserHandle.parseUserArg(getNextArgRequired());
281                     break;
282 
283                 case "-n":
284                 case "--namespace":
285                     namespace = getNextArgRequired();
286                     break;
287 
288                 default:
289                     pw.println("Error: unknown option '" + opt + "'");
290                     return -1;
291             }
292         }
293 
294         if (userId < 0) {
295             pw.println("Error: must specify a concrete user ID");
296             return -1;
297         }
298 
299         final String pkgName = getNextArg();
300         final String jobIdStr = getNextArg();
301         final int jobId = jobIdStr != null ? Integer.parseInt(jobIdStr) : -1;
302 
303         final long ident = Binder.clearCallingIdentity();
304         try {
305             return mInternal.executeCancelCommand(pw, pkgName, userId, namespace,
306                     jobIdStr != null, jobId);
307         } finally {
308             Binder.restoreCallingIdentity(ident);
309         }
310     }
311 
monitorBattery(PrintWriter pw)312     private int monitorBattery(PrintWriter pw) throws Exception {
313         checkPermission("change battery monitoring");
314         String opt = getNextArgRequired();
315         boolean enabled;
316         if ("on".equals(opt)) {
317             enabled = true;
318         } else if ("off".equals(opt)) {
319             enabled = false;
320         } else {
321             getErrPrintWriter().println("Error: unknown option " + opt);
322             return 1;
323         }
324         final long ident = Binder.clearCallingIdentity();
325         try {
326             mInternal.setMonitorBattery(enabled);
327             if (enabled) pw.println("Battery monitoring enabled");
328             else pw.println("Battery monitoring disabled");
329         } finally {
330             Binder.restoreCallingIdentity(ident);
331         }
332         return 0;
333     }
334 
getBatterySeq(PrintWriter pw)335     private int getBatterySeq(PrintWriter pw) {
336         int seq = mInternal.getBatterySeq();
337         pw.println(seq);
338         return 0;
339     }
340 
getBatteryCharging(PrintWriter pw)341     private int getBatteryCharging(PrintWriter pw) {
342         boolean val = mInternal.isBatteryCharging();
343         pw.println(val);
344         return 0;
345     }
346 
getBatteryNotLow(PrintWriter pw)347     private int getBatteryNotLow(PrintWriter pw) {
348         boolean val = mInternal.isBatteryNotLow();
349         pw.println(val);
350         return 0;
351     }
352 
getEstimatedNetworkBytes(PrintWriter pw, int byteOption)353     private int getEstimatedNetworkBytes(PrintWriter pw, int byteOption) throws Exception {
354         checkPermission("get estimated bytes");
355 
356         int userId = UserHandle.USER_SYSTEM;
357         String namespace = null;
358 
359         String opt;
360         while ((opt = getNextOption()) != null) {
361             switch (opt) {
362                 case "-u":
363                 case "--user":
364                     userId = UserHandle.parseUserArg(getNextArgRequired());
365                     break;
366 
367                 case "-n":
368                 case "--namespace":
369                     namespace = getNextArgRequired();
370                     break;
371 
372                 default:
373                     pw.println("Error: unknown option '" + opt + "'");
374                     return -1;
375             }
376         }
377 
378         if (userId == UserHandle.USER_CURRENT) {
379             userId = ActivityManager.getCurrentUser();
380         }
381 
382         final String pkgName = getNextArgRequired();
383         final String jobIdStr = getNextArgRequired();
384         final int jobId = Integer.parseInt(jobIdStr);
385 
386         final long ident = Binder.clearCallingIdentity();
387         try {
388             int ret = mInternal.getEstimatedNetworkBytes(pw, pkgName, userId, namespace,
389                     jobId, byteOption);
390             printError(ret, pkgName, userId, namespace, jobId);
391             return ret;
392         } finally {
393             Binder.restoreCallingIdentity(ident);
394         }
395     }
396 
getStorageSeq(PrintWriter pw)397     private int getStorageSeq(PrintWriter pw) {
398         int seq = mInternal.getStorageSeq();
399         pw.println(seq);
400         return 0;
401     }
402 
getStorageNotLow(PrintWriter pw)403     private int getStorageNotLow(PrintWriter pw) {
404         boolean val = mInternal.getStorageNotLow();
405         pw.println(val);
406         return 0;
407     }
408 
getTransferredNetworkBytes(PrintWriter pw, int byteOption)409     private int getTransferredNetworkBytes(PrintWriter pw, int byteOption) throws Exception {
410         checkPermission("get transferred bytes");
411 
412         int userId = UserHandle.USER_SYSTEM;
413         String namespace = null;
414 
415         String opt;
416         while ((opt = getNextOption()) != null) {
417             switch (opt) {
418                 case "-u":
419                 case "--user":
420                     userId = UserHandle.parseUserArg(getNextArgRequired());
421                     break;
422 
423                 case "-n":
424                 case "--namespace":
425                     namespace = getNextArgRequired();
426                     break;
427 
428                 default:
429                     pw.println("Error: unknown option '" + opt + "'");
430                     return -1;
431             }
432         }
433 
434         if (userId == UserHandle.USER_CURRENT) {
435             userId = ActivityManager.getCurrentUser();
436         }
437 
438         final String pkgName = getNextArgRequired();
439         final String jobIdStr = getNextArgRequired();
440         final int jobId = Integer.parseInt(jobIdStr);
441 
442         final long ident = Binder.clearCallingIdentity();
443         try {
444             int ret = mInternal.getTransferredNetworkBytes(pw, pkgName, userId, namespace,
445                     jobId, byteOption);
446             printError(ret, pkgName, userId, namespace, jobId);
447             return ret;
448         } finally {
449             Binder.restoreCallingIdentity(ident);
450         }
451     }
452 
getJobState(PrintWriter pw)453     private int getJobState(PrintWriter pw) throws Exception {
454         checkPermission("get job state");
455 
456         int userId = UserHandle.USER_SYSTEM;
457         String namespace = null;
458 
459         String opt;
460         while ((opt = getNextOption()) != null) {
461             switch (opt) {
462                 case "-u":
463                 case "--user":
464                     userId = UserHandle.parseUserArg(getNextArgRequired());
465                     break;
466 
467                 case "-n":
468                 case "--namespace":
469                     namespace = getNextArgRequired();
470                     break;
471 
472                 default:
473                     pw.println("Error: unknown option '" + opt + "'");
474                     return -1;
475             }
476         }
477 
478         if (userId == UserHandle.USER_CURRENT) {
479             userId = ActivityManager.getCurrentUser();
480         }
481 
482         final String pkgName = getNextArgRequired();
483         final String jobIdStr = getNextArgRequired();
484         final int jobId = Integer.parseInt(jobIdStr);
485 
486         final long ident = Binder.clearCallingIdentity();
487         try {
488             int ret = mInternal.getJobState(pw, pkgName, userId, namespace, jobId);
489             printError(ret, pkgName, userId, namespace, jobId);
490             return ret;
491         } finally {
492             Binder.restoreCallingIdentity(ident);
493         }
494     }
495 
doHeartbeat(PrintWriter pw)496     private int doHeartbeat(PrintWriter pw) throws Exception {
497         checkPermission("manipulate scheduler heartbeat");
498 
499         pw.println("Heartbeat command is no longer supported");
500         return -1;
501     }
502 
resetExecutionQuota(PrintWriter pw)503     private int resetExecutionQuota(PrintWriter pw) throws Exception {
504         checkPermission("reset execution quota");
505 
506         int userId = UserHandle.USER_SYSTEM;
507 
508         String opt;
509         while ((opt = getNextOption()) != null) {
510             switch (opt) {
511                 case "-u":
512                 case "--user":
513                     userId = UserHandle.parseUserArg(getNextArgRequired());
514                     break;
515 
516                 default:
517                     pw.println("Error: unknown option '" + opt + "'");
518                     return -1;
519             }
520         }
521 
522         if (userId == UserHandle.USER_CURRENT) {
523             userId = ActivityManager.getCurrentUser();
524         }
525 
526         final String pkgName = getNextArgRequired();
527 
528         final long ident = Binder.clearCallingIdentity();
529         try {
530             mInternal.resetExecutionQuota(pkgName, userId);
531         } finally {
532             Binder.restoreCallingIdentity(ident);
533         }
534         return 0;
535     }
536 
resetScheduleQuota(PrintWriter pw)537     private int resetScheduleQuota(PrintWriter pw) throws Exception {
538         checkPermission("reset schedule quota");
539 
540         final long ident = Binder.clearCallingIdentity();
541         try {
542             mInternal.resetScheduleQuota();
543         } finally {
544             Binder.restoreCallingIdentity(ident);
545         }
546         return 0;
547     }
548 
stop(PrintWriter pw)549     private int stop(PrintWriter pw) throws Exception {
550         checkPermission("stop jobs");
551 
552         int userId = UserHandle.USER_ALL;
553         String namespace = null;
554         int stopReason = JobParameters.STOP_REASON_USER;
555         int internalStopReason = JobParameters.INTERNAL_STOP_REASON_UNKNOWN;
556 
557         String opt;
558         while ((opt = getNextOption()) != null) {
559             switch (opt) {
560                 case "-u":
561                 case "--user":
562                     userId = UserHandle.parseUserArg(getNextArgRequired());
563                     break;
564 
565                 case "-n":
566                 case "--namespace":
567                     namespace = getNextArgRequired();
568                     break;
569 
570                 case "-s":
571                 case "--stop-reason":
572                     stopReason = Integer.parseInt(getNextArgRequired());
573                     break;
574 
575                 case "-i":
576                 case "--internal-stop-reason":
577                     internalStopReason = Integer.parseInt(getNextArgRequired());
578                     break;
579 
580                 default:
581                     pw.println("Error: unknown option '" + opt + "'");
582                     return -1;
583             }
584         }
585 
586         if (userId == UserHandle.USER_CURRENT) {
587             userId = ActivityManager.getCurrentUser();
588         }
589 
590         final String pkgName = getNextArg();
591         final String jobIdStr = getNextArg();
592         final int jobId = jobIdStr != null ? Integer.parseInt(jobIdStr) : -1;
593 
594         final long ident = Binder.clearCallingIdentity();
595         try {
596             return mInternal.executeStopCommand(pw, pkgName, userId, namespace,
597                     jobIdStr != null, jobId, stopReason, internalStopReason);
598         } finally {
599             Binder.restoreCallingIdentity(ident);
600         }
601     }
602 
triggerDockState(PrintWriter pw)603     private int triggerDockState(PrintWriter pw) throws Exception {
604         checkPermission("trigger wireless charging dock state");
605 
606         final String opt = getNextArgRequired();
607         boolean idleState;
608         if ("idle".equals(opt)) {
609             idleState = true;
610         } else if ("active".equals(opt)) {
611             idleState = false;
612         } else {
613             getErrPrintWriter().println("Error: unknown option " + opt);
614             return 1;
615         }
616 
617         final long ident = Binder.clearCallingIdentity();
618         try {
619             mInternal.triggerDockState(idleState);
620         } finally {
621             Binder.restoreCallingIdentity(ident);
622         }
623         return 0;
624     }
625 
626     @Override
onHelp()627     public void onHelp() {
628         final PrintWriter pw = getOutPrintWriter();
629 
630         pw.println("Job scheduler (jobscheduler) commands:");
631         pw.println("  help");
632         pw.println("    Print this help text.");
633         pw.println("  run [-f | --force] [-s | --satisfied] [-u | --user USER_ID]"
634                 + " [-n | --namespace NAMESPACE] PACKAGE JOB_ID");
635         pw.println("    Trigger immediate execution of a specific scheduled job. For historical");
636         pw.println("    reasons, some constraints, such as battery, are ignored when this");
637         pw.println("    command is called. If you don't want any constraints to be ignored,");
638         pw.println("    include the -s flag.");
639         pw.println("    Options:");
640         pw.println("      -f or --force: run the job even if technical constraints such as");
641         pw.println("         connectivity are not currently met. This is incompatible with -f ");
642         pw.println("         and so an error will be reported if both are given.");
643         pw.println("      -n or --namespace: specify the namespace this job sits in; the default");
644         pw.println("         is null (no namespace).");
645         pw.println("      -s or --satisfied: run the job only if all constraints are met.");
646         pw.println("         This is incompatible with -f and so an error will be reported");
647         pw.println("         if both are given.");
648         pw.println("      -u or --user: specify which user's job is to be run; the default is");
649         pw.println("         the primary or system user");
650         pw.println("  stop [-u | --user USER_ID] [-n | --namespace NAMESPACE]"
651                 + " [-s | --stop-reason STOP_REASON] [-i | --internal-stop-reason STOP_REASON]"
652                 + " [PACKAGE] [JOB_ID]");
653         pw.println("    Trigger immediate stop of currently executing jobs using the specified");
654         pw.println("    stop reasons.");
655         pw.println("    Options:");
656         pw.println("      -u or --user: specify which user's job is to be run; the default is");
657         pw.println("         all users");
658         pw.println("      -n or --namespace: specify the namespace this job sits in; the default");
659         pw.println("         is null (no namespace).");
660         pw.println("      -s or --stop-reason: specify the stop reason given to the job.");
661         pw.println("         Valid values are those that can be returned from");
662         pw.println("         JobParameters.getStopReason().");
663         pw.println("          The default value is STOP_REASON_USER.");
664         pw.println("      -i or --internal-stop-reason: specify the internal stop reason.");
665         pw.println("         JobScheduler will use for internal processing.");
666         pw.println("         Valid values are those that can be returned from");
667         pw.println("         JobParameters.getInternalStopReason().");
668         pw.println("          The default value is INTERNAL_STOP_REASON_UNDEFINED.");
669         pw.println("  timeout [-u | --user USER_ID] [-n | --namespace NAMESPACE]"
670                 + " [PACKAGE] [JOB_ID]");
671         pw.println("    Trigger immediate timeout of currently executing jobs, as if their");
672         pw.println("    execution timeout had expired.");
673         pw.println("    This is the equivalent of calling `stop -s 3 -i 3`.");
674         pw.println("    Options:");
675         pw.println("      -u or --user: specify which user's job is to be run; the default is");
676         pw.println("         all users");
677         pw.println("      -n or --namespace: specify the namespace this job sits in; the default");
678         pw.println("         is null (no namespace).");
679         pw.println("  cancel [-u | --user USER_ID] [-n | --namespace NAMESPACE] PACKAGE [JOB_ID]");
680         pw.println("    Cancel a scheduled job.  If a job ID is not supplied, all jobs scheduled");
681         pw.println("    by that package will be canceled.  USE WITH CAUTION.");
682         pw.println("    Options:");
683         pw.println("      -u or --user: specify which user's job is to be run; the default is");
684         pw.println("         the primary or system user");
685         pw.println("      -n or --namespace: specify the namespace this job sits in; the default");
686         pw.println("         is null (no namespace).");
687         pw.println("  heartbeat [num]");
688         pw.println("    No longer used.");
689         pw.println("  monitor-battery [on|off]");
690         pw.println("    Control monitoring of all battery changes.  Off by default.  Turning");
691         pw.println("    on makes get-battery-seq useful.");
692         pw.println("  get-battery-seq");
693         pw.println("    Return the last battery update sequence number that was received.");
694         pw.println("  get-battery-charging");
695         pw.println("    Return whether the battery is currently considered to be charging.");
696         pw.println("  get-battery-not-low");
697         pw.println("    Return whether the battery is currently considered to not be low.");
698         pw.println("  get-estimated-download-bytes [-u | --user USER_ID]"
699                 + " [-n | --namespace NAMESPACE] PACKAGE JOB_ID");
700         pw.println("    Return the most recent estimated download bytes for the job.");
701         pw.println("    Options:");
702         pw.println("      -u or --user: specify which user's job is to be run; the default is");
703         pw.println("         the primary or system user");
704         pw.println("  get-estimated-upload-bytes [-u | --user USER_ID]"
705                 + " [-n | --namespace NAMESPACE] PACKAGE JOB_ID");
706         pw.println("    Return the most recent estimated upload bytes for the job.");
707         pw.println("    Options:");
708         pw.println("      -u or --user: specify which user's job is to be run; the default is");
709         pw.println("         the primary or system user");
710         pw.println("  get-storage-seq");
711         pw.println("    Return the last storage update sequence number that was received.");
712         pw.println("  get-storage-not-low");
713         pw.println("    Return whether storage is currently considered to not be low.");
714         pw.println("  get-transferred-download-bytes [-u | --user USER_ID]"
715                 + " [-n | --namespace NAMESPACE] PACKAGE JOB_ID");
716         pw.println("    Return the most recent transferred download bytes for the job.");
717         pw.println("    Options:");
718         pw.println("      -u or --user: specify which user's job is to be run; the default is");
719         pw.println("         the primary or system user");
720         pw.println("  get-transferred-upload-bytes [-u | --user USER_ID]"
721                 + " [-n | --namespace NAMESPACE] PACKAGE JOB_ID");
722         pw.println("    Return the most recent transferred upload bytes for the job.");
723         pw.println("    Options:");
724         pw.println("      -u or --user: specify which user's job is to be run; the default is");
725         pw.println("         the primary or system user");
726         pw.println("  get-job-state [-u | --user USER_ID] [-n | --namespace NAMESPACE]"
727                 + " PACKAGE JOB_ID");
728         pw.println("    Return the current state of a job, may be any combination of:");
729         pw.println("      pending: currently on the pending list, waiting to be active");
730         pw.println("      active: job is actively running");
731         pw.println("      user-stopped: job can't run because its user is stopped");
732         pw.println("      backing-up: job can't run because app is currently backing up its data");
733         pw.println("      no-component: job can't run because its component is not available");
734         pw.println("      ready: job is ready to run (all constraints satisfied or bypassed)");
735         pw.println("      waiting: if nothing else above is printed, job not ready to run");
736         pw.println("    Options:");
737         pw.println("      -u or --user: specify which user's job is to be run; the default is");
738         pw.println("         the primary or system user");
739         pw.println("      -n or --namespace: specify the namespace this job sits in; the default");
740         pw.println("         is null (no namespace).");
741         pw.println("  trigger-dock-state [idle|active]");
742         pw.println("    Trigger wireless charging dock state.  Active by default.");
743         pw.println();
744     }
745 }
746