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

Commit 5ed32664 authored by Christopher Tate's avatar Christopher Tate Committed by android-build-merger
Browse files

Merge "Jobs in 'active' apps are not subject to standby" into pi-dev

am: 2d157b1b

Change-Id: I33a12f57ea8a262230807dfc9f9e858dd0e26368
parents 03828b0b 2d157b1b
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -614,7 +614,6 @@ message JobStatusDumpProto {
        CONSTRAINT_DEADLINE = 5;
        CONSTRAINT_IDLE = 6;
        CONSTRAINT_CONNECTIVITY = 7;
        CONSTRAINT_APP_NOT_IDLE = 8;
        CONSTRAINT_CONTENT_TRIGGER = 9;
        CONSTRAINT_DEVICE_NOT_DOZING = 10;
    }
+7 −5
Original line number Diff line number Diff line
@@ -258,7 +258,7 @@ public class AppStateTracker {
         */
        private void onRunAnyAppOpsChanged(AppStateTracker sender,
                int uid, @NonNull String packageName) {
            updateJobsForUidPackage(uid, packageName);
            updateJobsForUidPackage(uid, packageName, sender.isUidActive(uid));

            if (!sender.areAlarmsRestricted(uid, packageName, /*allowWhileIdle=*/ false)) {
                unblockAlarmsForUidPackage(uid, packageName);
@@ -279,9 +279,11 @@ public class AppStateTracker {
         * This is called when the active/idle state changed for a UID.
         */
        private void onUidActiveStateChanged(AppStateTracker sender, int uid) {
            updateJobsForUid(uid);
            final boolean isActive = sender.isUidActive(uid);

            if (sender.isUidActive(uid)) {
            updateJobsForUid(uid, isActive);

            if (isActive) {
                unblockAlarmsForUid(uid);
            }
        }
@@ -346,14 +348,14 @@ public class AppStateTracker {
         * Called when the job restrictions for a UID might have changed, so the job
         * scheduler should re-evaluate all restrictions for all jobs.
         */
        public void updateJobsForUid(int uid) {
        public void updateJobsForUid(int uid, boolean isNowActive) {
        }

        /**
         * Called when the job restrictions for a UID - package might have changed, so the job
         * scheduler should re-evaluate all restrictions for all jobs.
         */
        public void updateJobsForUidPackage(int uid, String packageName) {
        public void updateJobsForUidPackage(int uid, String packageName, boolean isNowActive) {
        }

        /**
+19 −5
Original line number Diff line number Diff line
@@ -88,7 +88,6 @@ import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob;
import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob;
import com.android.server.job.JobSchedulerServiceDumpProto.RegisteredJob;
import com.android.server.job.controllers.AppIdleController;
import com.android.server.job.controllers.BackgroundJobsController;
import com.android.server.job.controllers.BatteryController;
import com.android.server.job.controllers.ConnectivityController;
@@ -1051,7 +1050,8 @@ public final class JobSchedulerService extends com.android.server.SystemService
                final JobStatus job = jsc.getRunningJobLocked();
                if (job != null
                        && (job.getJob().getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) == 0
                        && !job.dozeWhitelisted) {
                        && !job.dozeWhitelisted
                        && !job.uidActive) {
                    // We will report active if we have a job running and it is not an exception
                    // due to being in the foreground or whitelisted.
                    active = true;
@@ -1115,7 +1115,6 @@ public final class JobSchedulerService extends com.android.server.SystemService
        mStorageController = new StorageController(this);
        mControllers.add(mStorageController);
        mControllers.add(new BackgroundJobsController(this));
        mControllers.add(new AppIdleController(this));
        mControllers.add(new ContentObserverController(this));
        mDeviceIdleJobsController = new DeviceIdleJobsController(this);
        mControllers.add(mDeviceIdleJobsController);
@@ -1867,6 +1866,9 @@ public final class JobSchedulerService extends com.android.server.SystemService
        // scheduled are sitting there, not ready yet) and very cheap to check (just
        // a few conditions on data in JobStatus).
        if (!jobReady) {
            if (job.getSourcePackageName().equals("android.jobscheduler.cts.jobtestapp")) {
                Slog.v(TAG, "    NOT READY: " + job);
            }
            return false;
        }

@@ -1905,9 +1907,21 @@ public final class JobSchedulerService extends com.android.server.SystemService
        // an appropriate amount of time since the last invocation.  During device-
        // wide parole, standby bucketing is ignored.
        //
        // But if a job has FLAG_EXEMPT_FROM_APP_STANDBY, don't check it.
        if (!mInParole && !job.getJob().isExemptedFromAppStandby()) {
        // Jobs in 'active' apps are not subject to standby, nor are jobs that are
        // specifically marked as exempt.
        if (DEBUG_STANDBY) {
            Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
                    + " parole=" + mInParole + " active=" + job.uidActive
                    + " exempt=" + job.getJob().isExemptedFromAppStandby());
        }
        if (!mInParole
                && !job.uidActive
                && !job.getJob().isExemptedFromAppStandby()) {
            final int bucket = job.getStandbyBucket();
            if (DEBUG_STANDBY) {
                Slog.v(TAG, "  bucket=" + bucket + " heartbeat=" + mHeartbeat
                        + " next=" + mNextBucketHeartbeat[bucket]);
            }
            if (mHeartbeat < mNextBucketHeartbeat[bucket]) {
                // Only skip this job if the app is still waiting for the end of its nominal
                // bucket interval.  Once it's waited that long, we let it go ahead and clear.
+0 −216
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 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 android.app.usage.UsageStatsManagerInternal;
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;

import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateControllerProto;

import java.util.function.Consumer;
import java.util.function.Predicate;

/**
 * Controls when apps are considered idle and if jobs pertaining to those apps should
 * be executed. Apps that haven't been actively launched or accessed from a foreground app
 * for a certain amount of time (maybe hours or days) are considered idle. When the app comes
 * out of idle state, it will be allowed to run scheduled jobs.
 */
public final class AppIdleController extends StateController {
    private static final String TAG = "JobScheduler.AppIdle";
    private static final boolean DEBUG = JobSchedulerService.DEBUG
            || Log.isLoggable(TAG, Log.DEBUG);

    private final UsageStatsManagerInternal mUsageStatsInternal;
    private boolean mInitializedParoleOn;
    boolean mAppIdleParoleOn;

    final class GlobalUpdateFunc implements Consumer<JobStatus> {
        boolean mChanged;

        @Override
        public void accept(JobStatus jobStatus) {
            String packageName = jobStatus.getSourcePackageName();
            final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName,
                    jobStatus.getSourceUid(), jobStatus.getSourceUserId());
            if (DEBUG) {
                Slog.d(TAG, "Setting idle state of " + packageName + " to " + appIdle);
            }
            if (jobStatus.setAppNotIdleConstraintSatisfied(!appIdle)) {
                mChanged = true;
            }
        }
    }

    final static class PackageUpdateFunc implements Consumer<JobStatus> {
        final int mUserId;
        final String mPackage;
        final boolean mIdle;
        boolean mChanged;

        PackageUpdateFunc(int userId, String pkg, boolean idle) {
            mUserId = userId;
            mPackage = pkg;
            mIdle = idle;
        }

        @Override
        public void accept(JobStatus jobStatus) {
            if (jobStatus.getSourcePackageName().equals(mPackage)
                    && jobStatus.getSourceUserId() == mUserId) {
                if (jobStatus.setAppNotIdleConstraintSatisfied(!mIdle)) {
                    if (DEBUG) {
                        Slog.d(TAG, "App Idle state changed, setting idle state of "
                                + mPackage + " to " + mIdle);
                    }
                    mChanged = true;
                }
            }
        }
    }

    public AppIdleController(JobSchedulerService service) {
        super(service);
        mUsageStatsInternal = LocalServices.getService(UsageStatsManagerInternal.class);
        mAppIdleParoleOn = true;
        mUsageStatsInternal.addAppIdleStateChangeListener(new AppIdleStateChangeListener());
    }

    @Override
    public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
        if (!mInitializedParoleOn) {
            mInitializedParoleOn = true;
            mAppIdleParoleOn = mUsageStatsInternal.isAppIdleParoleOn();
        }
        String packageName = jobStatus.getSourcePackageName();
        final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName,
                jobStatus.getSourceUid(), jobStatus.getSourceUserId());
        if (DEBUG) {
            Slog.d(TAG, "Start tracking, setting idle state of "
                    + packageName + " to " + appIdle);
        }
        jobStatus.setAppNotIdleConstraintSatisfied(!appIdle);
    }

    @Override
    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
            boolean forUpdate) {
    }

    @Override
    public void dumpControllerStateLocked(final IndentingPrintWriter pw,
            final Predicate<JobStatus> predicate) {
        pw.println("Parole on: " + mAppIdleParoleOn);
        pw.println();

        mService.getJobStore().forEachJob(predicate, (jobStatus) -> {
            pw.print("#");
            jobStatus.printUniqueId(pw);
            pw.print(" from ");
            UserHandle.formatUid(pw, jobStatus.getSourceUid());
            pw.print(": ");
            pw.print(jobStatus.getSourcePackageName());
            if ((jobStatus.satisfiedConstraints&JobStatus.CONSTRAINT_APP_NOT_IDLE) != 0) {
                pw.println(" RUNNABLE");
            } else {
                pw.println(" WAITING");
            }
        });
    }

    @Override
    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
            Predicate<JobStatus> predicate) {
        final long token = proto.start(fieldId);
        final long mToken = proto.start(StateControllerProto.APP_IDLE);

        proto.write(StateControllerProto.AppIdleController.IS_PAROLE_ON, mAppIdleParoleOn);

        mService.getJobStore().forEachJob(predicate, (js) -> {
            final long jsToken =
                    proto.start(StateControllerProto.AppIdleController.TRACKED_JOBS);
            js.writeToShortProto(proto, StateControllerProto.AppIdleController.TrackedJob.INFO);
            proto.write(StateControllerProto.AppIdleController.TrackedJob.SOURCE_UID,
                    js.getSourceUid());
            proto.write(StateControllerProto.AppIdleController.TrackedJob.SOURCE_PACKAGE_NAME,
                    js.getSourcePackageName());
            proto.write(
                    StateControllerProto.AppIdleController.TrackedJob.ARE_CONSTRAINTS_SATISFIED,
                    (js.satisfiedConstraints & JobStatus.CONSTRAINT_APP_NOT_IDLE) != 0);
            proto.end(jsToken);
        });

        proto.end(mToken);
        proto.end(token);
    }

    void setAppIdleParoleOn(boolean isAppIdleParoleOn) {
        // Flag if any app's idle state has changed
        boolean changed = false;
        synchronized (mLock) {
            if (mAppIdleParoleOn == isAppIdleParoleOn) {
                return;
            }
            mAppIdleParoleOn = isAppIdleParoleOn;
            GlobalUpdateFunc update = new GlobalUpdateFunc();
            mService.getJobStore().forEachJob(update);
            if (update.mChanged) {
                changed = true;
            }
        }
        if (changed) {
            mStateChangedListener.onControllerStateChanged();
        }
    }

    private final class AppIdleStateChangeListener
            extends UsageStatsManagerInternal.AppIdleStateChangeListener {
        @Override
        public void onAppIdleStateChanged(String packageName, int userId, boolean idle, int bucket,
                int reason) {
            boolean changed = false;
            synchronized (mLock) {
                if (mAppIdleParoleOn) {
                    return;
                }

                PackageUpdateFunc update = new PackageUpdateFunc(userId, packageName, idle);
                mService.getJobStore().forEachJob(update);
                if (update.mChanged) {
                    changed = true;
                }
            }
            if (changed) {
                mStateChangedListener.onControllerStateChanged();
            }
        }

        @Override
        public void onParoleStateChanged(boolean isParoleOn) {
            if (DEBUG) {
                Slog.d(TAG, "Parole on: " + isParoleOn);
            }
            setAppIdleParoleOn(isParoleOn);
        }
    }
}
+45 −25
Original line number Diff line number Diff line
@@ -28,17 +28,32 @@ import com.android.server.AppStateTracker;
import com.android.server.AppStateTracker.Listener;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobStore;
import com.android.server.job.StateControllerProto;
import com.android.server.job.StateControllerProto.BackgroundJobsController.TrackedJob;

import java.util.function.Consumer;
import java.util.function.Predicate;

/**
 * Tracks the following pieces of JobStatus state:
 *
 * - the CONSTRAINT_BACKGROUND_NOT_RESTRICTED general constraint bit, which
 *    is used to selectively permit battery-saver exempted jobs to run; and
 *
 * - the uid-active boolean state expressed by the AppStateTracker.  Jobs in 'active'
 *    uids are inherently eligible to run jobs regardless of the uid's standby bucket.
 */
public final class BackgroundJobsController extends StateController {
    private static final String TAG = "JobScheduler.Background";
    private static final boolean DEBUG = JobSchedulerService.DEBUG
            || Log.isLoggable(TAG, Log.DEBUG);

    // Tri-state about possible "is this uid 'active'?" knowledge
    static final int UNKNOWN = 0;
    static final int KNOWN_ACTIVE = 1;
    static final int KNOWN_INACTIVE = 2;

    private final AppStateTracker mAppStateTracker;

    public BackgroundJobsController(JobSchedulerService service) {
@@ -51,7 +66,7 @@ public final class BackgroundJobsController extends StateController {

    @Override
    public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
        updateSingleJobRestrictionLocked(jobStatus);
        updateSingleJobRestrictionLocked(jobStatus, UNKNOWN);
    }

    @Override
@@ -137,23 +152,24 @@ public final class BackgroundJobsController extends StateController {
    }

    private void updateAllJobRestrictionsLocked() {
        updateJobRestrictionsLocked(/*filterUid=*/ -1);
        updateJobRestrictionsLocked(/*filterUid=*/ -1, UNKNOWN);
    }

    private void updateJobRestrictionsForUidLocked(int uid) {

        // TODO Use forEachJobForSourceUid() once we have it.

        updateJobRestrictionsLocked(/*filterUid=*/ uid);
    private void updateJobRestrictionsForUidLocked(int uid, boolean isActive) {
        updateJobRestrictionsLocked(uid, (isActive) ? KNOWN_ACTIVE : KNOWN_INACTIVE);
    }

    private void updateJobRestrictionsLocked(int filterUid) {
        final UpdateJobFunctor updateTrackedJobs =
                new UpdateJobFunctor(filterUid);
    private void updateJobRestrictionsLocked(int filterUid, int newActiveState) {
        final UpdateJobFunctor updateTrackedJobs = new UpdateJobFunctor(newActiveState);

        final long start = DEBUG ? SystemClock.elapsedRealtimeNanos() : 0;

        mService.getJobStore().forEachJob(updateTrackedJobs);
        final JobStore store = mService.getJobStore();
        if (filterUid > 0) {
            store.forEachJobForSourceUid(filterUid, updateTrackedJobs);
        } else {
            store.forEachJob(updateTrackedJobs);
        }

        final long time = DEBUG ? (SystemClock.elapsedRealtimeNanos() - start) : 0;
        if (DEBUG) {
@@ -170,7 +186,7 @@ public final class BackgroundJobsController extends StateController {
        }
    }

    boolean updateSingleJobRestrictionLocked(JobStatus jobStatus) {
    boolean updateSingleJobRestrictionLocked(JobStatus jobStatus, int activeState) {

        final int uid = jobStatus.getSourceUid();
        final String packageName = jobStatus.getSourcePackageName();
@@ -179,28 +195,32 @@ public final class BackgroundJobsController extends StateController {
                (jobStatus.getInternalFlags() & JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION)
                        != 0);

        return jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRun);
        final boolean isActive;
        if (activeState == UNKNOWN) {
            isActive = mAppStateTracker.isUidActive(uid);
        } else {
            isActive = (activeState == KNOWN_ACTIVE);
        }
        boolean didChange = jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRun);
        didChange |= jobStatus.setUidActive(isActive);
        return didChange;
    }

    private final class UpdateJobFunctor implements Consumer<JobStatus> {
        private final int mFilterUid;

        final int activeState;
        boolean mChanged = false;
        int mTotalCount = 0;
        int mCheckedCount = 0;

        UpdateJobFunctor(int filterUid) {
            mFilterUid = filterUid;
        public UpdateJobFunctor(int newActiveState) {
            activeState = newActiveState;
        }

        @Override
        public void accept(JobStatus jobStatus) {
            mTotalCount++;
            if ((mFilterUid > 0) && (mFilterUid != jobStatus.getSourceUid())) {
                return;
            }
            mCheckedCount++;
            if (updateSingleJobRestrictionLocked(jobStatus)) {
            if (updateSingleJobRestrictionLocked(jobStatus, activeState)) {
                mChanged = true;
            }
        }
@@ -215,16 +235,16 @@ public final class BackgroundJobsController extends StateController {
        }

        @Override
        public void updateJobsForUid(int uid) {
        public void updateJobsForUid(int uid, boolean isActive) {
            synchronized (mLock) {
                updateJobRestrictionsForUidLocked(uid);
                updateJobRestrictionsForUidLocked(uid, isActive);
            }
        }

        @Override
        public void updateJobsForUidPackage(int uid, String packageName) {
        public void updateJobsForUidPackage(int uid, String packageName, boolean isActive) {
            synchronized (mLock) {
                updateJobRestrictionsForUidLocked(uid);
                updateJobRestrictionsForUidLocked(uid, isActive);
            }
        }
    };
Loading