| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License |
| */ |
| |
| package com.android.server.job; |
| |
| import android.app.ActivityManager; |
| import android.app.AppGlobals; |
| import android.content.pm.IPackageManager; |
| import android.content.pm.PackageManager; |
| import android.os.BasicShellCommandHandler; |
| import android.os.Binder; |
| import android.os.UserHandle; |
| |
| import java.io.PrintWriter; |
| |
| public final class JobSchedulerShellCommand extends BasicShellCommandHandler { |
| public static final int CMD_ERR_NO_PACKAGE = -1000; |
| public static final int CMD_ERR_NO_JOB = -1001; |
| public static final int CMD_ERR_CONSTRAINTS = -1002; |
| |
| JobSchedulerService mInternal; |
| IPackageManager mPM; |
| |
| JobSchedulerShellCommand(JobSchedulerService service) { |
| mInternal = service; |
| mPM = AppGlobals.getPackageManager(); |
| } |
| |
| @Override |
| public int onCommand(String cmd) { |
| final PrintWriter pw = getOutPrintWriter(); |
| try { |
| switch (cmd != null ? cmd : "") { |
| case "run": |
| return runJob(pw); |
| case "timeout": |
| return timeout(pw); |
| case "cancel": |
| return cancelJob(pw); |
| case "monitor-battery": |
| return monitorBattery(pw); |
| case "get-battery-seq": |
| return getBatterySeq(pw); |
| case "get-battery-charging": |
| return getBatteryCharging(pw); |
| case "get-battery-not-low": |
| return getBatteryNotLow(pw); |
| case "get-storage-seq": |
| return getStorageSeq(pw); |
| case "get-storage-not-low": |
| return getStorageNotLow(pw); |
| case "get-job-state": |
| return getJobState(pw); |
| case "heartbeat": |
| return doHeartbeat(pw); |
| case "reset-execution-quota": |
| return resetExecutionQuota(pw); |
| case "reset-schedule-quota": |
| return resetScheduleQuota(pw); |
| case "trigger-dock-state": |
| return triggerDockState(pw); |
| default: |
| return handleDefaultCommands(cmd); |
| } |
| } catch (Exception e) { |
| pw.println("Exception: " + e); |
| } |
| return -1; |
| } |
| |
| private void checkPermission(String operation) throws Exception { |
| final int uid = Binder.getCallingUid(); |
| if (uid == 0) { |
| // Root can do anything. |
| return; |
| } |
| final int perm = mPM.checkUidPermission( |
| "android.permission.CHANGE_APP_IDLE_STATE", uid); |
| if (perm != PackageManager.PERMISSION_GRANTED) { |
| throw new SecurityException("Uid " + uid |
| + " not permitted to " + operation); |
| } |
| } |
| |
| private boolean printError(int errCode, String pkgName, int userId, int jobId) { |
| PrintWriter pw; |
| switch (errCode) { |
| case CMD_ERR_NO_PACKAGE: |
| pw = getErrPrintWriter(); |
| pw.print("Package not found: "); |
| pw.print(pkgName); |
| pw.print(" / user "); |
| pw.println(userId); |
| return true; |
| |
| case CMD_ERR_NO_JOB: |
| pw = getErrPrintWriter(); |
| pw.print("Could not find job "); |
| pw.print(jobId); |
| pw.print(" in package "); |
| pw.print(pkgName); |
| pw.print(" / user "); |
| pw.println(userId); |
| return true; |
| |
| case CMD_ERR_CONSTRAINTS: |
| pw = getErrPrintWriter(); |
| pw.print("Job "); |
| pw.print(jobId); |
| pw.print(" in package "); |
| pw.print(pkgName); |
| pw.print(" / user "); |
| pw.print(userId); |
| pw.println(" has functional constraints but --force not specified"); |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| private int runJob(PrintWriter pw) throws Exception { |
| checkPermission("force scheduled jobs"); |
| |
| boolean force = false; |
| boolean satisfied = false; |
| int userId = UserHandle.USER_SYSTEM; |
| |
| String opt; |
| while ((opt = getNextOption()) != null) { |
| switch (opt) { |
| case "-f": |
| case "--force": |
| force = true; |
| break; |
| |
| case "-s": |
| case "--satisfied": |
| satisfied = true; |
| break; |
| |
| case "-u": |
| case "--user": |
| userId = Integer.parseInt(getNextArgRequired()); |
| break; |
| |
| default: |
| pw.println("Error: unknown option '" + opt + "'"); |
| return -1; |
| } |
| } |
| |
| if (force && satisfied) { |
| pw.println("Cannot specify both --force and --satisfied"); |
| return -1; |
| } |
| |
| final String pkgName = getNextArgRequired(); |
| final int jobId = Integer.parseInt(getNextArgRequired()); |
| |
| final long ident = Binder.clearCallingIdentity(); |
| try { |
| int ret = mInternal.executeRunCommand(pkgName, userId, jobId, satisfied, force); |
| if (printError(ret, pkgName, userId, jobId)) { |
| return ret; |
| } |
| |
| // success! |
| pw.print("Running job"); |
| if (force) { |
| pw.print(" [FORCED]"); |
| } |
| pw.println(); |
| |
| return ret; |
| } finally { |
| Binder.restoreCallingIdentity(ident); |
| } |
| } |
| |
| private int timeout(PrintWriter pw) throws Exception { |
| checkPermission("force timeout jobs"); |
| |
| int userId = UserHandle.USER_ALL; |
| |
| String opt; |
| while ((opt = getNextOption()) != null) { |
| switch (opt) { |
| case "-u": |
| case "--user": |
| userId = UserHandle.parseUserArg(getNextArgRequired()); |
| break; |
| |
| default: |
| pw.println("Error: unknown option '" + opt + "'"); |
| return -1; |
| } |
| } |
| |
| if (userId == UserHandle.USER_CURRENT) { |
| userId = ActivityManager.getCurrentUser(); |
| } |
| |
| final String pkgName = getNextArg(); |
| final String jobIdStr = getNextArg(); |
| final int jobId = jobIdStr != null ? Integer.parseInt(jobIdStr) : -1; |
| |
| final long ident = Binder.clearCallingIdentity(); |
| try { |
| return mInternal.executeTimeoutCommand(pw, pkgName, userId, jobIdStr != null, jobId); |
| } finally { |
| Binder.restoreCallingIdentity(ident); |
| } |
| } |
| |
| private int cancelJob(PrintWriter pw) throws Exception { |
| checkPermission("cancel jobs"); |
| |
| int userId = UserHandle.USER_SYSTEM; |
| |
| String opt; |
| while ((opt = getNextOption()) != null) { |
| switch (opt) { |
| case "-u": |
| case "--user": |
| userId = UserHandle.parseUserArg(getNextArgRequired()); |
| break; |
| |
| default: |
| pw.println("Error: unknown option '" + opt + "'"); |
| return -1; |
| } |
| } |
| |
| if (userId < 0) { |
| pw.println("Error: must specify a concrete user ID"); |
| return -1; |
| } |
| |
| final String pkgName = getNextArg(); |
| final String jobIdStr = getNextArg(); |
| final int jobId = jobIdStr != null ? Integer.parseInt(jobIdStr) : -1; |
| |
| final long ident = Binder.clearCallingIdentity(); |
| try { |
| return mInternal.executeCancelCommand(pw, pkgName, userId, jobIdStr != null, jobId); |
| } finally { |
| Binder.restoreCallingIdentity(ident); |
| } |
| } |
| |
| private int monitorBattery(PrintWriter pw) throws Exception { |
| checkPermission("change battery monitoring"); |
| String opt = getNextArgRequired(); |
| boolean enabled; |
| if ("on".equals(opt)) { |
| enabled = true; |
| } else if ("off".equals(opt)) { |
| enabled = false; |
| } else { |
| getErrPrintWriter().println("Error: unknown option " + opt); |
| return 1; |
| } |
| final long ident = Binder.clearCallingIdentity(); |
| try { |
| mInternal.setMonitorBattery(enabled); |
| if (enabled) pw.println("Battery monitoring enabled"); |
| else pw.println("Battery monitoring disabled"); |
| } finally { |
| Binder.restoreCallingIdentity(ident); |
| } |
| return 0; |
| } |
| |
| private int getBatterySeq(PrintWriter pw) { |
| int seq = mInternal.getBatterySeq(); |
| pw.println(seq); |
| return 0; |
| } |
| |
| private int getBatteryCharging(PrintWriter pw) { |
| boolean val = mInternal.getBatteryCharging(); |
| pw.println(val); |
| return 0; |
| } |
| |
| private int getBatteryNotLow(PrintWriter pw) { |
| boolean val = mInternal.getBatteryNotLow(); |
| pw.println(val); |
| return 0; |
| } |
| |
| private int getStorageSeq(PrintWriter pw) { |
| int seq = mInternal.getStorageSeq(); |
| pw.println(seq); |
| return 0; |
| } |
| |
| private int getStorageNotLow(PrintWriter pw) { |
| boolean val = mInternal.getStorageNotLow(); |
| pw.println(val); |
| return 0; |
| } |
| |
| private int getJobState(PrintWriter pw) throws Exception { |
| checkPermission("force timeout jobs"); |
| |
| int userId = UserHandle.USER_SYSTEM; |
| |
| String opt; |
| while ((opt = getNextOption()) != null) { |
| switch (opt) { |
| case "-u": |
| case "--user": |
| userId = UserHandle.parseUserArg(getNextArgRequired()); |
| break; |
| |
| default: |
| pw.println("Error: unknown option '" + opt + "'"); |
| return -1; |
| } |
| } |
| |
| if (userId == UserHandle.USER_CURRENT) { |
| userId = ActivityManager.getCurrentUser(); |
| } |
| |
| final String pkgName = getNextArgRequired(); |
| final String jobIdStr = getNextArgRequired(); |
| final int jobId = Integer.parseInt(jobIdStr); |
| |
| final long ident = Binder.clearCallingIdentity(); |
| try { |
| int ret = mInternal.getJobState(pw, pkgName, userId, jobId); |
| printError(ret, pkgName, userId, jobId); |
| return ret; |
| } finally { |
| Binder.restoreCallingIdentity(ident); |
| } |
| } |
| |
| private int doHeartbeat(PrintWriter pw) throws Exception { |
| checkPermission("manipulate scheduler heartbeat"); |
| |
| pw.println("Heartbeat command is no longer supported"); |
| return -1; |
| } |
| |
| private int resetExecutionQuota(PrintWriter pw) throws Exception { |
| checkPermission("reset execution quota"); |
| |
| int userId = UserHandle.USER_SYSTEM; |
| |
| String opt; |
| while ((opt = getNextOption()) != null) { |
| switch (opt) { |
| case "-u": |
| case "--user": |
| userId = UserHandle.parseUserArg(getNextArgRequired()); |
| break; |
| |
| default: |
| pw.println("Error: unknown option '" + opt + "'"); |
| return -1; |
| } |
| } |
| |
| if (userId == UserHandle.USER_CURRENT) { |
| userId = ActivityManager.getCurrentUser(); |
| } |
| |
| final String pkgName = getNextArgRequired(); |
| |
| final long ident = Binder.clearCallingIdentity(); |
| try { |
| mInternal.resetExecutionQuota(pkgName, userId); |
| } finally { |
| Binder.restoreCallingIdentity(ident); |
| } |
| return 0; |
| } |
| |
| private int resetScheduleQuota(PrintWriter pw) throws Exception { |
| checkPermission("reset schedule quota"); |
| |
| final long ident = Binder.clearCallingIdentity(); |
| try { |
| mInternal.resetScheduleQuota(); |
| } finally { |
| Binder.restoreCallingIdentity(ident); |
| } |
| return 0; |
| } |
| |
| private int triggerDockState(PrintWriter pw) throws Exception { |
| checkPermission("trigger wireless charging dock state"); |
| |
| final String opt = getNextArgRequired(); |
| boolean idleState; |
| if ("idle".equals(opt)) { |
| idleState = true; |
| } else if ("active".equals(opt)) { |
| idleState = false; |
| } else { |
| getErrPrintWriter().println("Error: unknown option " + opt); |
| return 1; |
| } |
| |
| final long ident = Binder.clearCallingIdentity(); |
| try { |
| mInternal.triggerDockState(idleState); |
| } finally { |
| Binder.restoreCallingIdentity(ident); |
| } |
| return 0; |
| } |
| |
| @Override |
| public void onHelp() { |
| final PrintWriter pw = getOutPrintWriter(); |
| |
| pw.println("Job scheduler (jobscheduler) commands:"); |
| pw.println(" help"); |
| pw.println(" Print this help text."); |
| pw.println(" run [-f | --force] [-s | --satisfied] [-u | --user USER_ID] PACKAGE JOB_ID"); |
| pw.println(" Trigger immediate execution of a specific scheduled job. For historical"); |
| pw.println(" reasons, some constraints, such as battery, are ignored when this"); |
| pw.println(" command is called. If you don't want any constraints to be ignored,"); |
| pw.println(" include the -s flag."); |
| pw.println(" Options:"); |
| pw.println(" -f or --force: run the job even if technical constraints such as"); |
| pw.println(" connectivity are not currently met. This is incompatible with -f "); |
| pw.println(" and so an error will be reported if both are given."); |
| pw.println(" -s or --satisfied: run the job only if all constraints are met."); |
| pw.println(" This is incompatible with -f and so an error will be reported"); |
| pw.println(" if both are given."); |
| pw.println(" -u or --user: specify which user's job is to be run; the default is"); |
| pw.println(" the primary or system user"); |
| pw.println(" timeout [-u | --user USER_ID] [PACKAGE] [JOB_ID]"); |
| pw.println(" Trigger immediate timeout of currently executing jobs, as if their."); |
| pw.println(" execution timeout had expired."); |
| pw.println(" Options:"); |
| pw.println(" -u or --user: specify which user's job is to be run; the default is"); |
| pw.println(" all users"); |
| pw.println(" cancel [-u | --user USER_ID] PACKAGE [JOB_ID]"); |
| pw.println(" Cancel a scheduled job. If a job ID is not supplied, all jobs scheduled"); |
| pw.println(" by that package will be canceled. USE WITH CAUTION."); |
| pw.println(" Options:"); |
| pw.println(" -u or --user: specify which user's job is to be run; the default is"); |
| pw.println(" the primary or system user"); |
| pw.println(" heartbeat [num]"); |
| pw.println(" No longer used."); |
| pw.println(" monitor-battery [on|off]"); |
| pw.println(" Control monitoring of all battery changes. Off by default. Turning"); |
| pw.println(" on makes get-battery-seq useful."); |
| pw.println(" get-battery-seq"); |
| pw.println(" Return the last battery update sequence number that was received."); |
| pw.println(" get-battery-charging"); |
| pw.println(" Return whether the battery is currently considered to be charging."); |
| pw.println(" get-battery-not-low"); |
| pw.println(" Return whether the battery is currently considered to not be low."); |
| pw.println(" get-storage-seq"); |
| pw.println(" Return the last storage update sequence number that was received."); |
| pw.println(" get-storage-not-low"); |
| pw.println(" Return whether storage is currently considered to not be low."); |
| pw.println(" get-job-state [-u | --user USER_ID] PACKAGE JOB_ID"); |
| pw.println(" Return the current state of a job, may be any combination of:"); |
| pw.println(" pending: currently on the pending list, waiting to be active"); |
| pw.println(" active: job is actively running"); |
| pw.println(" user-stopped: job can't run because its user is stopped"); |
| pw.println(" backing-up: job can't run because app is currently backing up its data"); |
| pw.println(" no-component: job can't run because its component is not available"); |
| pw.println(" ready: job is ready to run (all constraints satisfied or bypassed)"); |
| pw.println(" waiting: if nothing else above is printed, job not ready to run"); |
| pw.println(" Options:"); |
| pw.println(" -u or --user: specify which user's job is to be run; the default is"); |
| pw.println(" the primary or system user"); |
| pw.println(" trigger-dock-state [idle|active]"); |
| pw.println(" Trigger wireless charging dock state. Active by default."); |
| pw.println(); |
| } |
| |
| } |