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