Home | History | Annotate | Download | only in job
      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 
     37     JobSchedulerShellCommand(JobSchedulerService service) {
     38         mInternal = service;
     39         mPM = AppGlobals.getPackageManager();
     40     }
     41 
     42     @Override
     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 
     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 
     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 
    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 
    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 
    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 
    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 
    273     private int getBatterySeq(PrintWriter pw) {
    274         int seq = mInternal.getBatterySeq();
    275         pw.println(seq);
    276         return 0;
    277     }
    278 
    279     private int getBatteryCharging(PrintWriter pw) {
    280         boolean val = mInternal.getBatteryCharging();
    281         pw.println(val);
    282         return 0;
    283     }
    284 
    285     private int getBatteryNotLow(PrintWriter pw) {
    286         boolean val = mInternal.getBatteryNotLow();
    287         pw.println(val);
    288         return 0;
    289     }
    290 
    291     private int getStorageSeq(PrintWriter pw) {
    292         int seq = mInternal.getStorageSeq();
    293         pw.println(seq);
    294         return 0;
    295     }
    296 
    297     private int getStorageNotLow(PrintWriter pw) {
    298         boolean val = mInternal.getStorageNotLow();
    299         pw.println(val);
    300         return 0;
    301     }
    302 
    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 
    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 
    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
    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