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