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

Commit 9de3073d authored by Kweku Adams's avatar Kweku Adams
Browse files

Add basic prefetch job handling.

1. Add a PrefetController that will mark prefetch jobs as ineligible
   to run if the system doesn't think the app will be launched in the
   next few hours.
2. Force batch all prefetch jobs unless the system thinks the app will
   be launched within the next hour.

Upcoming:
* Adding functionality in UsageStats to determine the next time the app
  will be launched.

Bug: 194532703
Test: atest FrameworksMockingServicesTests:PrefetchControllerTest
Change-Id: Ic4e911eb53b4cfb5a904eec0aa59fdc80f029cb1
parent 1ea287ba
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -235,6 +235,14 @@ public class JobParameters implements Parcelable {
    public static final int STOP_REASON_USER = 13;
    /** The system is doing some processing that requires stopping this job. */
    public static final int STOP_REASON_SYSTEM_PROCESSING = 14;
    /**
     * The system's estimate of when the app will be launched changed significantly enough to
     * decide this job shouldn't be running right now. This will mostly apply to prefetch jobs.
     *
     * @see JobInfo#isPrefetch()
     * @see JobInfo.Builder#setPrefetch(boolean)
     */
    public static final int STOP_REASON_ESTIMATED_APP_LAUNCH_TIME_CHANGED = 15;

    /** @hide */
    @IntDef(prefix = {"STOP_REASON_"}, value = {
@@ -253,6 +261,7 @@ public class JobParameters implements Parcelable {
            STOP_REASON_APP_STANDBY,
            STOP_REASON_USER,
            STOP_REASON_SYSTEM_PROCESSING,
            STOP_REASON_ESTIMATED_APP_LAUNCH_TIME_CHANGED,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface StopReason {
+38 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.server.job;

import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;

import android.annotation.NonNull;
@@ -105,6 +106,7 @@ import com.android.server.job.controllers.ContentObserverController;
import com.android.server.job.controllers.DeviceIdleJobsController;
import com.android.server.job.controllers.IdleController;
import com.android.server.job.controllers.JobStatus;
import com.android.server.job.controllers.PrefetchController;
import com.android.server.job.controllers.QuotaController;
import com.android.server.job.controllers.RestrictingController;
import com.android.server.job.controllers.StateController;
@@ -252,6 +254,8 @@ public class JobSchedulerService extends com.android.server.SystemService
    private final StorageController mStorageController;
    /** Need directly for sending uid state changes */
    private final DeviceIdleJobsController mDeviceIdleJobsController;
    /** Needed to get next estimated launch time. */
    private final PrefetchController mPrefetchController;
    /** Needed to get remaining quota time. */
    private final QuotaController mQuotaController;
    /** Needed to get max execution time and expedited-job allowance. */
@@ -427,6 +431,9 @@ public class JobSchedulerService extends com.android.server.SystemService
                        case Constants.KEY_CONN_PREFETCH_RELAX_FRAC:
                            mConstants.updateConnectivityConstantsLocked();
                            break;
                        case Constants.KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS:
                            mConstants.updatePrefetchConstantsLocked();
                            break;
                        case Constants.KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS:
                        case Constants.KEY_RUNTIME_MIN_GUARANTEE_MS:
                        case Constants.KEY_RUNTIME_MIN_EJ_GUARANTEE_MS:
@@ -482,6 +489,8 @@ public class JobSchedulerService extends com.android.server.SystemService
        private static final String KEY_MIN_EXP_BACKOFF_TIME_MS = "min_exp_backoff_time_ms";
        private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac";
        private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac";
        private static final String KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS =
                "prefetch_force_batch_relax_threshold_ms";
        private static final String KEY_ENABLE_API_QUOTAS = "enable_api_quotas";
        private static final String KEY_API_QUOTA_SCHEDULE_COUNT = "aq_schedule_count";
        private static final String KEY_API_QUOTA_SCHEDULE_WINDOW_MS = "aq_schedule_window_ms";
@@ -503,6 +512,7 @@ public class JobSchedulerService extends com.android.server.SystemService
        private static final long DEFAULT_MIN_EXP_BACKOFF_TIME_MS = JobInfo.MIN_BACKOFF_MILLIS;
        private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
        private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f;
        private static final long DEFAULT_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS = HOUR_IN_MILLIS;
        private static final boolean DEFAULT_ENABLE_API_QUOTAS = true;
        private static final int DEFAULT_API_QUOTA_SCHEDULE_COUNT = 250;
        private static final long DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS = MINUTE_IN_MILLIS;
@@ -556,6 +566,14 @@ public class JobSchedulerService extends com.android.server.SystemService
         */
        public float CONN_PREFETCH_RELAX_FRAC = DEFAULT_CONN_PREFETCH_RELAX_FRAC;

        /**
         * The amount of time within which we would consider the app to be launching relatively soon
         * and will relax the force batching policy on prefetch jobs. If the app is not going to be
         * launched within this amount of time from now, then we will force batch the prefetch job.
         */
        public long PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS =
                DEFAULT_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS;

        /**
         * Whether to enable quota limits on APIs.
         */
@@ -635,6 +653,13 @@ public class JobSchedulerService extends com.android.server.SystemService
                    DEFAULT_CONN_PREFETCH_RELAX_FRAC);
        }

        private void updatePrefetchConstantsLocked() {
            PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS = DeviceConfig.getLong(
                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                    KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS,
                    DEFAULT_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS);
        }

        private void updateApiQuotaConstantsLocked() {
            ENABLE_API_QUOTAS = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                    KEY_ENABLE_API_QUOTAS, DEFAULT_ENABLE_API_QUOTAS);
@@ -700,6 +725,8 @@ public class JobSchedulerService extends com.android.server.SystemService
            pw.print(KEY_MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS).println();
            pw.print(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
            pw.print(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println();
            pw.print(KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS,
                    PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS).println();

            pw.print(KEY_ENABLE_API_QUOTAS, ENABLE_API_QUOTAS).println();
            pw.print(KEY_API_QUOTA_SCHEDULE_COUNT, API_QUOTA_SCHEDULE_COUNT).println();
@@ -1569,6 +1596,8 @@ public class JobSchedulerService extends com.android.server.SystemService
        mControllers.add(new ContentObserverController(this));
        mDeviceIdleJobsController = new DeviceIdleJobsController(this);
        mControllers.add(mDeviceIdleJobsController);
        mPrefetchController = new PrefetchController(this);
        mControllers.add(mPrefetchController);
        mQuotaController =
                new QuotaController(this, backgroundJobsController, connectivityController);
        mControllers.add(mQuotaController);
@@ -2325,6 +2354,15 @@ public class JobSchedulerService extends com.android.server.SystemService
                } else if (job.getEffectiveStandbyBucket() == RESTRICTED_INDEX) {
                    // Restricted jobs must always be batched
                    shouldForceBatchJob = true;
                } else if (job.getJob().isPrefetch()) {
                    // Only relax batching on prefetch jobs if we expect the app to be launched
                    // relatively soon. PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS defines what
                    // "relatively soon" means.
                    final long relativelySoonCutoffTime = sSystemClock.millis()
                            + mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS;
                    shouldForceBatchJob =
                            mPrefetchController.getNextEstimatedLaunchTimeLocked(job)
                                    > relativelySoonCutoffTime;
                } else if (job.getNumFailures() > 0) {
                    shouldForceBatchJob = false;
                } else {
+15 −2
Original line number Diff line number Diff line
@@ -93,6 +93,7 @@ public final class JobStatus {
    static final int CONSTRAINT_CONTENT_TRIGGER = 1<<26;
    static final int CONSTRAINT_DEVICE_NOT_DOZING = 1 << 25; // Implicit constraint
    static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24;      // Implicit constraint
    static final int CONSTRAINT_PREFETCH = 1 << 23;
    static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint

    // The following set of dynamic constraints are for specific use cases (as explained in their
@@ -147,6 +148,7 @@ public final class JobStatus {
    private static final int STATSD_CONSTRAINTS_TO_LOG = CONSTRAINT_CONTENT_TRIGGER
            | CONSTRAINT_DEADLINE
            | CONSTRAINT_IDLE
            | CONSTRAINT_PREFETCH
            | CONSTRAINT_TARE_WEALTH
            | CONSTRAINT_TIMING_DELAY
            | CONSTRAINT_WITHIN_QUOTA;
@@ -1175,6 +1177,11 @@ public final class JobStatus {
        return setConstraintSatisfied(CONSTRAINT_STORAGE_NOT_LOW, nowElapsed, state);
    }

    /** @return true if the constraint was changed, false otherwise. */
    boolean setPrefetchConstraintSatisfied(final long nowElapsed, boolean state) {
        return setConstraintSatisfied(CONSTRAINT_PREFETCH, nowElapsed, state);
    }

    /** @return true if the constraint was changed, false otherwise. */
    boolean setTimingDelayConstraintSatisfied(final long nowElapsed, boolean state) {
        return setConstraintSatisfied(CONSTRAINT_TIMING_DELAY, nowElapsed, state);
@@ -1387,6 +1394,9 @@ public final class JobStatus {
            case CONSTRAINT_DEVICE_NOT_DOZING:
                return JobParameters.STOP_REASON_DEVICE_STATE;

            case CONSTRAINT_PREFETCH:
                return JobParameters.STOP_REASON_ESTIMATED_APP_LAUNCH_TIME_CHANGED;

            case CONSTRAINT_TARE_WEALTH:
            case CONSTRAINT_WITHIN_QUOTA:
                return JobParameters.STOP_REASON_QUOTA;
@@ -1581,12 +1591,12 @@ public final class JobStatus {
    /** All constraints besides implicit and deadline. */
    static final int CONSTRAINTS_OF_INTEREST = CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW
            | CONSTRAINT_STORAGE_NOT_LOW | CONSTRAINT_TIMING_DELAY | CONSTRAINT_CONNECTIVITY
            | CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER;
            | CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER | CONSTRAINT_PREFETCH;

    // Soft override covers all non-"functional" constraints
    static final int SOFT_OVERRIDE_CONSTRAINTS =
            CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_STORAGE_NOT_LOW
                    | CONSTRAINT_TIMING_DELAY | CONSTRAINT_IDLE;
                    | CONSTRAINT_TIMING_DELAY | CONSTRAINT_IDLE | CONSTRAINT_PREFETCH;

    /** Returns true whenever all dynamically set constraints are satisfied. */
    public boolean areDynamicConstraintsSatisfied() {
@@ -1775,6 +1785,9 @@ public final class JobStatus {
        if ((constraints&CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) {
            pw.print(" BACKGROUND_NOT_RESTRICTED");
        }
        if ((constraints & CONSTRAINT_PREFETCH) != 0) {
            pw.print(" PREFETCH");
        }
        if ((constraints & CONSTRAINT_TARE_WEALTH) != 0) {
            pw.print(" TARE_WEALTH");
        }
+303 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.controllers;

import static android.text.format.DateUtils.HOUR_IN_MILLIS;

import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.JobSchedulerService.sSystemClock;

import android.annotation.CurrentTimeMillisLong;
import android.annotation.NonNull;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArrayMap;
import android.util.TimeUtils;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.JobSchedulerBackgroundThread;
import com.android.server.job.JobSchedulerService;

import java.util.function.Predicate;

/**
 * Controller to delay prefetch jobs until we get close to an expected app launch.
 */
public class PrefetchController extends StateController {
    private static final String TAG = "JobScheduler.Prefetch";
    private static final boolean DEBUG = JobSchedulerService.DEBUG
            || Log.isLoggable(TAG, Log.DEBUG);

    private final PcConstants mPcConstants;

    @GuardedBy("mLock")
    private final SparseArrayMap<String, ArraySet<JobStatus>> mTrackedJobs = new SparseArrayMap<>();
    /**
     * Cached set of the estimated next launch times of each app. Time are in the current time
     * millis ({@link CurrentTimeMillisLong}) timebase.
     */
    @GuardedBy("mLock")
    private final SparseArrayMap<String, Long> mEstimatedLaunchTimes = new SparseArrayMap<>();

    /**
     * The cutoff point to decide if a prefetch job is worth running or not. If the app is expected
     * to launch within this amount of time into the future, then we will let a prefetch job run.
     */
    @GuardedBy("mLock")
    @CurrentTimeMillisLong
    private long mLaunchTimeThresholdMs = PcConstants.DEFAULT_LAUNCH_TIME_THRESHOLD_MS;

    public PrefetchController(JobSchedulerService service) {
        super(service);
        mPcConstants = new PcConstants();
    }

    @Override
    @GuardedBy("mLock")
    public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
        if (jobStatus.getJob().isPrefetch()) {
            final int userId = jobStatus.getSourceUserId();
            final String pkgName = jobStatus.getSourcePackageName();
            ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
            if (jobs == null) {
                jobs = new ArraySet<>();
                mTrackedJobs.add(userId, pkgName, jobs);
            }
            jobs.add(jobStatus);
            updateConstraintLocked(jobStatus,
                    sSystemClock.millis(), sElapsedRealtimeClock.millis());
        }
    }

    @Override
    @GuardedBy("mLock")
    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
            boolean forUpdate) {
        final ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUserId(),
                jobStatus.getSourcePackageName());
        if (jobs != null) {
            jobs.remove(jobStatus);
        }
    }

    @Override
    @GuardedBy("mLock")
    public void onAppRemovedLocked(String packageName, int uid) {
        if (packageName == null) {
            Slog.wtf(TAG, "Told app removed but given null package name.");
            return;
        }
        final int userId = UserHandle.getUserId(uid);
        mTrackedJobs.delete(userId, packageName);
        mEstimatedLaunchTimes.delete(userId, packageName);
    }

    @Override
    @GuardedBy("mLock")
    public void onUserRemovedLocked(int userId) {
        mTrackedJobs.delete(userId);
        mEstimatedLaunchTimes.delete(userId);
    }

    /** Return the app's next estimated launch time. */
    @GuardedBy("mLock")
    @CurrentTimeMillisLong
    public long getNextEstimatedLaunchTimeLocked(@NonNull JobStatus jobStatus) {
        final int userId = jobStatus.getSourceUserId();
        final String pkgName = jobStatus.getSourcePackageName();
        Long nextEstimatedLaunchTime = mEstimatedLaunchTimes.get(userId, pkgName);
        final long now = sSystemClock.millis();
        if (nextEstimatedLaunchTime == null || nextEstimatedLaunchTime < now) {
            // TODO(194532703): get estimated time from UsageStats
            nextEstimatedLaunchTime = now + 2 * HOUR_IN_MILLIS;
            mEstimatedLaunchTimes.add(userId, pkgName, nextEstimatedLaunchTime);
        }
        return nextEstimatedLaunchTime;
    }

    @GuardedBy("mLock")
    private boolean maybeUpdateConstraintForPkgLocked(long now, long nowElapsed, int userId,
            String pkgName) {
        final ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
        if (jobs == null) {
            return false;
        }
        boolean changed = false;
        for (int i = 0; i < jobs.size(); i++) {
            final JobStatus js = jobs.valueAt(i);
            changed |= updateConstraintLocked(js, now, nowElapsed);
        }
        return changed;
    }

    @GuardedBy("mLock")
    private boolean updateConstraintLocked(@NonNull JobStatus jobStatus, long now,
            long nowElapsed) {
        return jobStatus.setPrefetchConstraintSatisfied(nowElapsed,
                getNextEstimatedLaunchTimeLocked(jobStatus) <= now + mLaunchTimeThresholdMs);
    }

    @Override
    @GuardedBy("mLock")
    public void prepareForUpdatedConstantsLocked() {
        mPcConstants.mShouldReevaluateConstraints = false;
    }

    @Override
    @GuardedBy("mLock")
    public void processConstantLocked(DeviceConfig.Properties properties, String key) {
        mPcConstants.processConstantLocked(properties, key);
    }

    @Override
    @GuardedBy("mLock")
    public void onConstantsUpdatedLocked() {
        if (mPcConstants.mShouldReevaluateConstraints) {
            // Update job bookkeeping out of band.
            JobSchedulerBackgroundThread.getHandler().post(() -> {
                final ArraySet<JobStatus> changedJobs = new ArraySet<>();
                synchronized (mLock) {
                    final long nowElapsed = sElapsedRealtimeClock.millis();
                    final long now = sSystemClock.millis();
                    for (int u = 0; u < mTrackedJobs.numMaps(); ++u) {
                        final int userId = mTrackedJobs.keyAt(u);
                        for (int p = 0; p < mTrackedJobs.numElementsForKey(userId); ++p) {
                            final String packageName = mTrackedJobs.keyAt(u, p);
                            if (maybeUpdateConstraintForPkgLocked(
                                    now, nowElapsed, userId, packageName)) {
                                changedJobs.addAll(mTrackedJobs.valueAt(u, p));
                            }
                        }
                    }
                }
                if (changedJobs.size() > 0) {
                    mStateChangedListener.onControllerStateChanged(changedJobs);
                }
            });
        }
    }

    @VisibleForTesting
    class PcConstants {
        private boolean mShouldReevaluateConstraints = false;

        /** Prefix to use with all constant keys in order to "sub-namespace" the keys. */
        private static final String PC_CONSTANT_PREFIX = "pc_";

        @VisibleForTesting
        static final String KEY_LAUNCH_TIME_THRESHOLD_MS =
                PC_CONSTANT_PREFIX + "launch_time_threshold_ms";

        private static final long DEFAULT_LAUNCH_TIME_THRESHOLD_MS = 7 * HOUR_IN_MILLIS;

        /** How much time each app will have to run jobs within their standby bucket window. */
        public long LAUNCH_TIME_THRESHOLD_MS = DEFAULT_LAUNCH_TIME_THRESHOLD_MS;

        @GuardedBy("mLock")
        public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
                @NonNull String key) {
            switch (key) {
                case KEY_LAUNCH_TIME_THRESHOLD_MS:
                    LAUNCH_TIME_THRESHOLD_MS =
                            properties.getLong(key, DEFAULT_LAUNCH_TIME_THRESHOLD_MS);
                    // Limit the threshold to the range [1, 24] hours.
                    long newLaunchTimeThresholdMs = Math.min(24 * HOUR_IN_MILLIS,
                            Math.max(HOUR_IN_MILLIS, LAUNCH_TIME_THRESHOLD_MS));
                    if (mLaunchTimeThresholdMs != newLaunchTimeThresholdMs) {
                        mLaunchTimeThresholdMs = newLaunchTimeThresholdMs;
                        mShouldReevaluateConstraints = true;
                    }
                    break;
            }
        }

        private void dump(IndentingPrintWriter pw) {
            pw.println();
            pw.print(PrefetchController.class.getSimpleName());
            pw.println(":");
            pw.increaseIndent();

            pw.print(KEY_LAUNCH_TIME_THRESHOLD_MS, LAUNCH_TIME_THRESHOLD_MS).println();

            pw.decreaseIndent();
        }
    }

    //////////////////////// TESTING HELPERS /////////////////////////////

    @VisibleForTesting
    long getLaunchTimeThresholdMs() {
        return mLaunchTimeThresholdMs;
    }

    @VisibleForTesting
    @NonNull
    PcConstants getPcConstants() {
        return mPcConstants;
    }

    //////////////////////////// DATA DUMP //////////////////////////////

    @Override
    @GuardedBy("mLock")
    public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
        final long now = sSystemClock.millis();

        pw.println("Cached launch times:");
        pw.increaseIndent();
        for (int u = 0; u < mEstimatedLaunchTimes.numMaps(); ++u) {
            final int userId = mEstimatedLaunchTimes.keyAt(u);
            for (int p = 0; p < mEstimatedLaunchTimes.numElementsForKey(userId); ++p) {
                final String pkgName = mEstimatedLaunchTimes.keyAt(u, p);
                final long estimatedLaunchTime = mEstimatedLaunchTimes.valueAt(u, p);

                pw.print("<" + userId + ">" + pkgName + ": ");
                pw.print(estimatedLaunchTime);
                pw.print(" (");
                TimeUtils.formatDuration(estimatedLaunchTime - now, pw,
                        TimeUtils.HUNDRED_DAY_FIELD_LEN);
                pw.println(" from now)");
            }
        }
        pw.decreaseIndent();

        pw.println();
        mTrackedJobs.forEach((jobs) -> {
            for (int j = 0; j < jobs.size(); j++) {
                final JobStatus js = jobs.valueAt(j);
                if (!predicate.test(js)) {
                    continue;
                }
                pw.print("#");
                js.printUniqueId(pw);
                pw.print(" from ");
                UserHandle.formatUid(pw, js.getSourceUid());
                pw.println();
            }
        });
    }

    @Override
    public void dumpConstants(IndentingPrintWriter pw) {
        mPcConstants.dump(pw);
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -8053,6 +8053,7 @@ package android.app.job {
    field public static final int STOP_REASON_CONSTRAINT_DEVICE_IDLE = 8; // 0x8
    field public static final int STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW = 9; // 0x9
    field public static final int STOP_REASON_DEVICE_STATE = 4; // 0x4
    field public static final int STOP_REASON_ESTIMATED_APP_LAUNCH_TIME_CHANGED = 15; // 0xf
    field public static final int STOP_REASON_PREEMPT = 2; // 0x2
    field public static final int STOP_REASON_QUOTA = 10; // 0xa
    field public static final int STOP_REASON_SYSTEM_PROCESSING = 14; // 0xe
Loading