Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6d06826b authored by Dianne Hackborn's avatar Dianne Hackborn
Browse files

Fix issue #38244875: Bring back testChargingConstraintFails

- Add new "get-job-state" shell command that the tests can use
  to find out what is going on with a job.
- Fix an issue in the battery controller where it would not
  immediately stop with a "battery not low" constraint when
  the battery becomes low.
- Also an optimization to rescheduling jobs, for every scheduled
  job that is not ready don't look to see if it is active.
  Instead, go through the active jobs and see if any are no
  longer ready.

Test: bit -t CtsJobSchedulerTestCases:*

Change-Id: I5611886653258ca337eee97c5ee1b9b3dfdb6d85
parent c75bd416
Loading
Loading
Loading
Loading
+99 −23
Original line number Diff line number Diff line
@@ -1270,6 +1270,17 @@ public final class JobSchedulerService extends com.android.server.SystemService
        }
    }

    private void stopNonReadyActiveJobsLocked() {
        for (int i=0; i<mActiveServices.size(); i++) {
            JobServiceContext serviceContext = mActiveServices.get(i);
            final JobStatus running = serviceContext.getRunningJobLocked();
            if (running != null && !running.isReady()) {
                serviceContext.cancelExecutingJobLocked(
                        JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
            }
        }
    }

    /**
     * Run through list of jobs and execute all possible - at least one is expired so we do
     * as many as we can.
@@ -1280,6 +1291,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
        }
        noteJobsNonpending(mPendingJobs);
        mPendingJobs.clear();
        stopNonReadyActiveJobsLocked();
        mJobs.forEachJob(mReadyQueueFunctor);
        mReadyQueueFunctor.postProcess();

@@ -1306,9 +1318,6 @@ public final class JobSchedulerService extends com.android.server.SystemService
                    newReadyJobs = new ArrayList<JobStatus>();
                }
                newReadyJobs.add(job);
            } else if (areJobConstraintsNotSatisfiedLocked(job)) {
                stopJobOnServiceContextLocked(job,
                        JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
            }
        }

@@ -1387,9 +1396,6 @@ public final class JobSchedulerService extends com.android.server.SystemService
                    runnableJobs = new ArrayList<>();
                }
                runnableJobs.add(job);
            } else if (areJobConstraintsNotSatisfiedLocked(job)) {
                stopJobOnServiceContextLocked(job,
                        JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
            }
        }

@@ -1439,6 +1445,7 @@ public final class JobSchedulerService extends com.android.server.SystemService

        noteJobsNonpending(mPendingJobs);
        mPendingJobs.clear();
        stopNonReadyActiveJobsLocked();
        mJobs.forEachJob(mMaybeQueueFunctor);
        mMaybeQueueFunctor.postProcess();
    }
@@ -1515,15 +1522,6 @@ public final class JobSchedulerService extends com.android.server.SystemService
        return componentPresent;
    }

    /**
     * Criteria for cancelling an active job:
     *      - It's not ready
     *      - It's running on a JSC.
     */
    private boolean areJobConstraintsNotSatisfiedLocked(JobStatus job) {
        return !job.isReady() && isCurrentlyActiveLocked(job);
    }

    /**
     * Reconcile jobs in the pending queue against available execution contexts.
     * A controller can force a job into the pending queue even if it's already running, but
@@ -2088,6 +2086,83 @@ public final class JobSchedulerService extends com.android.server.SystemService
        }
    }

    int getJobState(PrintWriter pw, String pkgName, int userId, int jobId) {
        try {
            final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
                    userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM);
            if (uid < 0) {
                pw.print("unknown("); pw.print(pkgName); pw.println(")");
                return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
            }

            synchronized (mLock) {
                final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
                if (DEBUG) Slog.d(TAG, "get-job-state " + uid + "/" + jobId + ": " + js);
                if (js == null) {
                    pw.print("unknown("); UserHandle.formatUid(pw, uid);
                    pw.print("/jid"); pw.print(jobId); pw.println(")");
                    return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
                }

                boolean printed = false;
                if (mPendingJobs.contains(js)) {
                    pw.print("pending");
                    printed = true;
                }
                if (isCurrentlyActiveLocked(js)) {
                    if (printed) {
                        pw.print(" ");
                    }
                    printed = true;
                    pw.println("active");
                }
                if (!ArrayUtils.contains(mStartedUsers, js.getUserId())) {
                    if (printed) {
                        pw.print(" ");
                    }
                    printed = true;
                    pw.println("user-stopped");
                }
                if (mBackingUpUids.indexOfKey(js.getSourceUid()) >= 0) {
                    if (printed) {
                        pw.print(" ");
                    }
                    printed = true;
                    pw.println("backing-up");
                }
                boolean componentPresent = false;
                try {
                    componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
                            js.getServiceComponent(),
                            PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
                            js.getUserId()) != null);
                } catch (RemoteException e) {
                }
                if (!componentPresent) {
                    if (printed) {
                        pw.print(" ");
                    }
                    printed = true;
                    pw.println("no-component");
                }
                if (js.isReady()) {
                    if (printed) {
                        pw.print(" ");
                    }
                    printed = true;
                    pw.println("ready");
                }
                if (!printed) {
                    pw.print("waiting");
                }
                pw.println();
            }
        } catch (RemoteException e) {
            // can't happen
        }
        return 0;
    }

    private String printContextIdToJobMap(JobStatus[] map, String initial) {
        StringBuilder s = new StringBuilder(initial + ": ");
        for (int i=0; i<map.length; i++) {
@@ -2152,7 +2227,8 @@ public final class JobSchedulerService extends com.android.server.SystemService
        }

        final int filterUidFinal = UserHandle.getAppId(filterUid);
        final long now = SystemClock.elapsedRealtime();
        final long nowElapsed = SystemClock.elapsedRealtime();
        final long nowUptime = SystemClock.uptimeMillis();
        synchronized (mLock) {
            mConstants.dump(pw);
            pw.println();
@@ -2184,7 +2260,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
                        continue;
                    }

                    job.dump(pw, "    ", true, now);
                    job.dump(pw, "    ", true, nowElapsed);
                    pw.print("    Ready: ");
                    pw.print(isReadyToBeExecutedLocked(job));
                    pw.print(" (job=");
@@ -2254,14 +2330,14 @@ public final class JobSchedulerService extends com.android.server.SystemService
                JobStatus job = mPendingJobs.get(i);
                pw.print("  Pending #"); pw.print(i); pw.print(": ");
                pw.println(job.toShortString());
                job.dump(pw, "    ", false, now);
                job.dump(pw, "    ", false, nowElapsed);
                int priority = evaluateJobPriorityLocked(job);
                if (priority != JobInfo.PRIORITY_DEFAULT) {
                    pw.print("    Evaluated priority: "); pw.println(priority);
                }
                pw.print("    Tag: "); pw.println(job.getTag());
                pw.print("    Enq: ");
                TimeUtils.formatDuration(job.madePending - now, pw);
                TimeUtils.formatDuration(job.madePending - nowUptime, pw);
                pw.println();
            }
            pw.println();
@@ -2276,17 +2352,17 @@ public final class JobSchedulerService extends com.android.server.SystemService
                } else {
                    pw.println(job.toShortString());
                    pw.print("    Running for: ");
                    TimeUtils.formatDuration(now - jsc.getExecutionStartTimeElapsed(), pw);
                    TimeUtils.formatDuration(nowElapsed - jsc.getExecutionStartTimeElapsed(), pw);
                    pw.print(", timeout at: ");
                    TimeUtils.formatDuration(jsc.getTimeoutElapsed() - now, pw);
                    TimeUtils.formatDuration(jsc.getTimeoutElapsed() - nowElapsed, pw);
                    pw.println();
                    job.dump(pw, "    ", false, now);
                    job.dump(pw, "    ", false, nowElapsed);
                    int priority = evaluateJobPriorityLocked(jsc.getRunningJobLocked());
                    if (priority != JobInfo.PRIORITY_DEFAULT) {
                        pw.print("    Evaluated priority: "); pw.println(priority);
                    }
                    pw.print("    Active at ");
                    TimeUtils.formatDuration(job.madeActive - now, pw);
                    TimeUtils.formatDuration(job.madeActive - nowUptime, pw);
                    pw.print(", pending for ");
                    TimeUtils.formatDuration(job.madeActive - job.madePending, pw);
                    pw.println();
+97 −34
Original line number Diff line number Diff line
@@ -60,6 +60,8 @@ public class JobSchedulerShellCommand extends ShellCommand {
                    return getStorageSeq(pw);
                case "get-storage-not-low":
                    return getStorageNotLow(pw);
                case "get-job-state":
                    return getJobState(pw);
                default:
                    return handleDefaultCommands(cmd);
            }
@@ -83,6 +85,43 @@ public class JobSchedulerShellCommand extends ShellCommand {
        }
    }

    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");

@@ -114,42 +153,17 @@ public class JobSchedulerShellCommand extends ShellCommand {
        final long ident = Binder.clearCallingIdentity();
        try {
            int ret = mInternal.executeRunCommand(pkgName, userId, jobId, force);
            switch (ret) {
                case CMD_ERR_NO_PACKAGE:
                    pw.print("Package not found: ");
                    pw.print(pkgName);
                    pw.print(" / user ");
                    pw.println(userId);
                    break;

                case CMD_ERR_NO_JOB:
                    pw.print("Could not find job ");
                    pw.print(jobId);
                    pw.print(" in package ");
                    pw.print(pkgName);
                    pw.print(" / user ");
                    pw.println(userId);
                    break;

                case CMD_ERR_CONSTRAINTS:
                    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");
                    break;
            if (printError(ret, pkgName, userId, jobId)) {
                return ret;
            }

                default:
            // success!
            pw.print("Running job");
            if (force) {
                pw.print(" [FORCED]");
            }
            pw.println();
                    break;
            }

            return ret;
        } finally {
            Binder.restoreCallingIdentity(ident);
@@ -244,6 +258,43 @@ public class JobSchedulerShellCommand extends ShellCommand {
        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);
        }
    }

    @Override
    public void onHelp() {
        final PrintWriter pw = getOutPrintWriter();
@@ -277,6 +328,18 @@ public class JobSchedulerShellCommand extends ShellCommand {
        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();
    }

+50 −49
Original line number Diff line number Diff line
@@ -93,14 +93,13 @@ public class BatteryController extends StateController {
        }
    }

    private void maybeReportNewChargingState() {
    private void maybeReportNewChargingStateLocked() {
        final boolean stablePower = mChargeTracker.isOnStablePower();
        final boolean batteryNotLow = mChargeTracker.isBatteryNotLow();
        if (DEBUG) {
            Slog.d(TAG, "maybeReportNewChargingState: " + stablePower);
            Slog.d(TAG, "maybeReportNewChargingStateLocked: " + stablePower);
        }
        boolean reportChange = false;
        synchronized (mLock) {
        for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
            final JobStatus ts = mTrackedTasks.valueAt(i);
            boolean previous = ts.setChargingConstraintSatisfied(stablePower);
@@ -112,15 +111,13 @@ public class BatteryController extends StateController {
                reportChange = true;
            }
        }
        }
        // Let the scheduler know that state has changed. This may or may not result in an
        // execution.
        if (reportChange) {
            mStateChangedListener.onControllerStateChanged();
        }
        // Also tell the scheduler that any ready jobs should be flushed.
        if (stablePower || batteryNotLow) {
            // If one of our conditions has been satisfied, always schedule any newly ready jobs.
            mStateChangedListener.onRunJobNow(null);
        } else if (reportChange) {
            // Otherwise, just let the job scheduler know the state has changed and take care of it
            // as it thinks is best.
            mStateChangedListener.onControllerStateChanged();
        }
    }

@@ -201,6 +198,7 @@ public class BatteryController extends StateController {

        @VisibleForTesting
        public void onReceiveInternal(Intent intent) {
            synchronized (mLock) {
                final String action = intent.getAction();
                if (Intent.ACTION_BATTERY_LOW.equals(action)) {
                    if (DEBUG) {
@@ -211,28 +209,31 @@ public class BatteryController extends StateController {
                    // there's no work to cancel. We track this variable for the case where it is
                    // charging, but hasn't been for long enough to be healthy.
                    mBatteryHealthy = false;
                    maybeReportNewChargingStateLocked();
                } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) {
                    if (DEBUG) {
                        Slog.d(TAG, "Battery life healthy enough to do work. @ "
                                + SystemClock.elapsedRealtime());
                    }
                    mBatteryHealthy = true;
                maybeReportNewChargingState();
                    maybeReportNewChargingStateLocked();
                } else if (BatteryManager.ACTION_CHARGING.equals(action)) {
                    if (DEBUG) {
                        Slog.d(TAG, "Received charging intent, fired @ "
                                + SystemClock.elapsedRealtime());
                    }
                    mCharging = true;
                maybeReportNewChargingState();
                    maybeReportNewChargingStateLocked();
                } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
                    if (DEBUG) {
                        Slog.d(TAG, "Disconnected from power.");
                    }
                    mCharging = false;
                maybeReportNewChargingState();
                    maybeReportNewChargingStateLocked();
                }
                mLastBatterySeq = intent.getIntExtra(BatteryManager.EXTRA_SEQUENCE,
                        mLastBatterySeq);
            }
            mLastBatterySeq = intent.getIntExtra(BatteryManager.EXTRA_SEQUENCE, mLastBatterySeq);
        }
    }