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