diff options
11 files changed, 113 insertions, 13 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java b/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java index ae86afbd8380..071707059f2d 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java @@ -200,7 +200,10 @@ class JobNotificationCoordinator { // No more jobs using this notification. Apply the final job stop policy. // If the user attempted to stop the job/app, then always remove the notification // so the user doesn't get confused about the app state. + // Similarly, if the user background restricted the app, remove the notification so + // the user doesn't think the app is continuing to run in the background. if (details.jobEndNotificationPolicy == JOB_END_NOTIFICATION_POLICY_REMOVE + || stopReason == JobParameters.STOP_REASON_BACKGROUND_RESTRICTION || stopReason == JobParameters.STOP_REASON_USER) { mNotificationManagerInternal.cancelNotification( packageName, packageName, details.appUid, details.appPid, /* tag */ null, diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java index 5f795b6fedd9..109686d76b2f 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -413,16 +413,22 @@ public final class JobServiceContext implements ServiceConnection { final Intent intent = new Intent().setComponent(job.getServiceComponent()) .setFlags(Intent.FLAG_FROM_BACKGROUND); boolean binding = false; + boolean startedWithForegroundFlag = false; try { final Context.BindServiceFlags bindFlags; - if (job.shouldTreatAsUserInitiatedJob()) { + if (job.shouldTreatAsUserInitiatedJob() && !job.isUserBgRestricted()) { + // If the user has bg restricted the app, don't give the job FG privileges + // such as bypassing data saver or getting the higher foreground proc state. + // If we've gotten to this point, the app is most likely in the foreground, + // so the job will run just fine while the user keeps the app in the foreground. bindFlags = Context.BindServiceFlags.of( Context.BIND_AUTO_CREATE | Context.BIND_ALMOST_PERCEPTIBLE | Context.BIND_BYPASS_POWER_NETWORK_RESTRICTIONS | Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS | Context.BIND_NOT_APP_COMPONENT_USAGE); - } else if (job.shouldTreatAsExpeditedJob()) { + startedWithForegroundFlag = true; + } else if (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiatedJob()) { bindFlags = Context.BindServiceFlags.of( Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND @@ -535,8 +541,11 @@ public final class JobServiceContext implements ServiceConnection { mAvailable = false; mStoppedReason = null; mStoppedTime = 0; + // Wait until after bindService() returns a success value to set these so we don't + // have JobStatus objects that aren't running but have these set to true. job.startedAsExpeditedJob = job.shouldTreatAsExpeditedJob(); job.startedAsUserInitiatedJob = job.shouldTreatAsUserInitiatedJob(); + job.startedWithForegroundFlag = startedWithForegroundFlag; return true; } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java index ecee10a13c1d..25b3421a55f0 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java @@ -19,6 +19,7 @@ package com.android.server.job.controllers; import static com.android.server.job.JobSchedulerService.NEVER_INDEX; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; +import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.os.SystemClock; import android.os.UserHandle; @@ -205,8 +206,32 @@ public final class BackgroundJobsController extends StateController { final int uid = jobStatus.getSourceUid(); final String packageName = jobStatus.getSourcePackageName(); - final boolean canRun = !mAppStateTracker.areJobsRestricted(uid, packageName, - jobStatus.canRunInBatterySaver()); + final boolean isUserBgRestricted = + !mActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled() + && !mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(uid, packageName); + // If a job started with the foreground flag, it'll cause the UID to stay active + // and thus cause areJobsRestricted() to always return false, so if + // areJobsRestricted() returns false and the app is BG restricted and not TOP, + // we need to stop any jobs that started with the foreground flag so they don't + // keep the app in an elevated proc state. If we were to get in this situation, + // then the user restricted the app after the job started, so it's best to stop + // the job as soon as possible, especially since the job would be visible to the + // user (with a notification and in Task Manager). + // There are several other reasons that uidActive can be true for an app even if its + // proc state is less important than BFGS. + // JobScheduler has historically (at least up through UDC) allowed the app's jobs to run + // when its UID was active, even if it's background restricted. This has been fine because + // JobScheduler stops the job as soon as the UID becomes inactive and the jobs themselves + // will not keep the UID active. The logic here is to ensure that special jobs + // (e.g. user-initiated jobs) themselves do not keep the UID active when the app is + // background restricted. + final boolean shouldStopImmediately = jobStatus.startedWithForegroundFlag + && isUserBgRestricted + && mService.getUidProcState(uid) + > ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; + final boolean canRun = !shouldStopImmediately + && !mAppStateTracker.areJobsRestricted( + uid, packageName, jobStatus.canRunInBatterySaver()); final boolean isActive; if (activeState == UNKNOWN) { @@ -219,8 +244,7 @@ public final class BackgroundJobsController extends StateController { } boolean didChange = jobStatus.setBackgroundNotRestrictedConstraintSatisfied(nowElapsed, canRun, - !mActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled() - && !mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(uid, packageName)); + isUserBgRestricted); didChange |= jobStatus.setUidActive(isActive); return didChange; } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java index f6bdb9303a04..6d938debde10 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java @@ -1774,6 +1774,12 @@ public final class ConnectivityController extends RestrictingController implemen } pw.println(); + if (mBackgroundMeteredAllowed.size() > 0) { + pw.print("Background metered allowed: "); + pw.println(mBackgroundMeteredAllowed); + pw.println(); + } + pw.println("Current default network callbacks:"); pw.increaseIndent(); for (int i = 0; i < mCurrentDefaultNetworkCallbacks.size(); i++) { diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index 3baa9e6d3de9..13903acc0439 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -430,6 +430,13 @@ public final class JobStatus { * when it started running. This isn't copied over when a job is rescheduled. */ public boolean startedAsUserInitiatedJob = false; + /** + * Whether this particular JobStatus instance started with the foreground flag + * (or more accurately, did <b>not</b> have the + * {@link android.content.Context#BIND_NOT_FOREGROUND} flag + * included in its binding flags when started). + */ + public boolean startedWithForegroundFlag = false; public boolean startedWithImmediacyPrivilege = false; @@ -1606,6 +1613,10 @@ public final class JobStatus { * for any reason. */ public boolean shouldTreatAsUserInitiatedJob() { + // isUserBgRestricted is intentionally excluded from this method. It should be fine to + // treat the job as a UI job while the app is TOP, but just not in the background. + // Instead of adding a proc state check here, the parts of JS that can make the distinction + // and care about the distinction can do the check. return getJob().isUserInitiated() && (getInternalFlags() & INTERNAL_FLAG_DEMOTED_BY_USER) == 0 && (getInternalFlags() & INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ) == 0; @@ -1653,6 +1664,11 @@ public final class JobStatus { && (mDynamicConstraints & CONSTRAINT_BACKGROUND_NOT_RESTRICTED) == 0); } + /** Returns whether or not the app is background restricted by the user (FAS). */ + public boolean isUserBgRestricted() { + return mIsUserBgRestricted; + } + /** @return true if the constraint was changed, false otherwise. */ boolean setChargingConstraintSatisfied(final long nowElapsed, boolean state) { return setConstraintSatisfied(CONSTRAINT_CHARGING, nowElapsed, state); @@ -2802,6 +2818,12 @@ public final class JobStatus { } pw.decreaseIndent(); + pw.print("Started with foreground flag: "); + pw.println(startedWithForegroundFlag); + if (mIsUserBgRestricted) { + pw.println("User BG restricted"); + } + if (changedAuthorities != null) { pw.println("Changed authorities:"); pw.increaseIndent(); diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 0ec3847d29f4..46260ea5e658 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -607,6 +607,7 @@ interface IActivityManager { void killPackageDependents(in String packageName, int userId); void makePackageIdle(String packageName, int userId); + void setDeterministicUidIdle(boolean deterministic); int getMemoryTrimLevel(); boolean isVrModePackageEnabled(in ComponentName packageName); void notifyLockedProfile(int userId); diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 44e198b53761..8c31209aeeb4 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -1678,9 +1678,9 @@ final class ActivityManagerConstants extends ContentObserver { DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_KILL_BG_RESTRICTED_CACHED_IDLE_SETTLE_TIME, DEFAULT_KILL_BG_RESTRICTED_CACHED_IDLE_SETTLE_TIME_MS); - if (mKillBgRestrictedAndCachedIdleSettleTimeMs != currentSettleTime) { - mService.mHandler.removeMessages( - ActivityManagerService.IDLE_UIDS_MSG); + if (mKillBgRestrictedAndCachedIdleSettleTimeMs < currentSettleTime) { + // Don't remove existing messages in case other IDLE_UIDS_MSG initiators use lower + // delays, but send a new message if the settle time has decreased. mService.mHandler.sendEmptyMessageDelayed( ActivityManagerService.IDLE_UIDS_MSG, mKillBgRestrictedAndCachedIdleSettleTimeMs); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 92e73f585466..e9f38f1b4655 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1522,6 +1522,8 @@ public class ActivityManagerService extends IActivityManager.Stub */ int mBootPhase; + volatile boolean mDeterministicUidIdle = false; + @VisibleForTesting public WindowManagerService mWindowManager; WindowManagerInternal mWmInternal; @@ -16464,6 +16466,11 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @Override + public void setDeterministicUidIdle(boolean deterministic) { + mDeterministicUidIdle = deterministic; + } + /** Make the currently active UIDs idle after a certain grace period. */ final void idleUids() { synchronized (this) { diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index add22bd6009e..fd980727e12b 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -290,6 +290,8 @@ final class ActivityManagerShellCommand extends ShellCommand { return runKillAll(pw); case "make-uid-idle": return runMakeIdle(pw); + case "set-deterministic-uid-idle": + return runSetDeterministicUidIdle(pw); case "monitor": return runMonitor(pw); case "watch-uids": @@ -1520,6 +1522,23 @@ final class ActivityManagerShellCommand extends ShellCommand { return 0; } + int runSetDeterministicUidIdle(PrintWriter pw) throws RemoteException { + int userId = UserHandle.USER_ALL; + + String opt; + while ((opt = getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + } + boolean deterministic = Boolean.parseBoolean(getNextArgRequired()); + mInterface.setDeterministicUidIdle(deterministic); + return 0; + } + static final class MyActivityController extends IActivityController.Stub { final IActivityManager mInterface; final PrintWriter mPw; @@ -4271,6 +4290,11 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" make-uid-idle [--user <USER_ID> | all | current] <PACKAGE>"); pw.println(" If the given application's uid is in the background and waiting to"); pw.println(" become idle (not allowing background services), do that now."); + pw.println( + " set-deterministic-uid-idle [--user <USER_ID> | all | current] <true|false>"); + pw.println(" If true, sets the timing of making UIDs idle consistent and"); + pw.println(" deterministic. If false, the timing will be variable depending on"); + pw.println(" other activity on the device. The default is false."); pw.println(" monitor [--gdb <port>] [-p <TARGET>] [-s] [-c] [-k]"); pw.println(" Start monitoring for crashes or ANRs."); pw.println(" --gdb: start gdbserv on the given port at crash/ANR"); diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index a86c2e362c54..764bbe8bd191 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -1471,7 +1471,8 @@ public class OomAdjuster { if (!ActivityManager.isProcStateBackground(uidRec.getSetProcState()) || uidRec.isSetAllowListed()) { uidRec.setLastBackgroundTime(nowElapsed); - if (!mService.mHandler.hasMessages(IDLE_UIDS_MSG)) { + if (mService.mDeterministicUidIdle + || !mService.mHandler.hasMessages(IDLE_UIDS_MSG)) { // Note: the background settle time is in elapsed realtime, while // the handler time base is uptime. All this means is that we may // stop background uids later than we had intended, but that only @@ -3227,7 +3228,8 @@ public class OomAdjuster { // (for states debouncing to avoid from thrashing). state.setLastCanKillOnBgRestrictedAndIdleTime(nowElapsed); // Kick off the delayed checkup message if needed. - if (!mService.mHandler.hasMessages(IDLE_UIDS_MSG)) { + if (mService.mDeterministicUidIdle + || !mService.mHandler.hasMessages(IDLE_UIDS_MSG)) { mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG, mConstants.mKillBgRestrictedAndCachedIdleSettleTimeMs); } @@ -3346,6 +3348,7 @@ public class OomAdjuster { @GuardedBy("mService") void idleUidsLocked() { final int N = mActiveUids.size(); + mService.mHandler.removeMessages(IDLE_UIDS_MSG); if (N <= 0) { return; } @@ -3391,7 +3394,6 @@ public class OomAdjuster { } } if (nextTime > 0) { - mService.mHandler.removeMessages(IDLE_UIDS_MSG); mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG, nextTime + mConstants.BACKGROUND_SETTLE_TIME - nowElapsed); } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 4342cb994754..86b09ab41ce9 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -5243,7 +5243,9 @@ public final class ProcessList { mAppsInBackgroundRestricted.add(app); final long future = killAppIfBgRestrictedAndCachedIdleLocked( app, nowElapsed); - if (future > 0 && !mService.mHandler.hasMessages(IDLE_UIDS_MSG)) { + if (future > 0 + && (mService.mDeterministicUidIdle + || !mService.mHandler.hasMessages(IDLE_UIDS_MSG))) { mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG, future - nowElapsed); } |