Loading core/proto/android/server/jobscheduler.proto +0 −1 Original line number Diff line number Diff line Loading @@ -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; } Loading services/core/java/com/android/server/AppStateTracker.java +7 −5 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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); } } Loading Loading @@ -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) { } /** Loading services/core/java/com/android/server/job/JobSchedulerService.java +19 −5 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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; } Loading Loading @@ -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. Loading services/core/java/com/android/server/job/controllers/AppIdleController.javadeleted 100644 → 0 +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); } } } services/core/java/com/android/server/job/controllers/BackgroundJobsController.java +45 −25 Original line number Diff line number Diff line Loading @@ -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) { Loading @@ -51,7 +66,7 @@ public final class BackgroundJobsController extends StateController { @Override public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { updateSingleJobRestrictionLocked(jobStatus); updateSingleJobRestrictionLocked(jobStatus, UNKNOWN); } @Override Loading Loading @@ -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) { Loading @@ -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(); Loading @@ -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; } } Loading @@ -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 Loading
core/proto/android/server/jobscheduler.proto +0 −1 Original line number Diff line number Diff line Loading @@ -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; } Loading
services/core/java/com/android/server/AppStateTracker.java +7 −5 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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); } } Loading Loading @@ -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) { } /** Loading
services/core/java/com/android/server/job/JobSchedulerService.java +19 −5 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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; } Loading Loading @@ -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. Loading
services/core/java/com/android/server/job/controllers/AppIdleController.javadeleted 100644 → 0 +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); } } }
services/core/java/com/android/server/job/controllers/BackgroundJobsController.java +45 −25 Original line number Diff line number Diff line Loading @@ -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) { Loading @@ -51,7 +66,7 @@ public final class BackgroundJobsController extends StateController { @Override public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { updateSingleJobRestrictionLocked(jobStatus); updateSingleJobRestrictionLocked(jobStatus, UNKNOWN); } @Override Loading Loading @@ -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) { Loading @@ -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(); Loading @@ -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; } } Loading @@ -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