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