diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java index 8e9f4746798f8..17076bc4eea45 100644 --- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java +++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java @@ -97,6 +97,15 @@ public class PowerExemptionManager { */ public static final int TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = 1; + /** + * Delay freezing the app when the broadcast is delivered. This flag is not required if + * TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED or + * TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED are specified, as those will + * already defer freezing during the allowlist duration. + * @hide temporarily until the next release + */ + public static final int TEMPORARY_ALLOW_LIST_TYPE_APP_FREEZING_DELAYED = 1 << 2; + /** * The list of temp allow list types. * @hide @@ -105,6 +114,7 @@ public class PowerExemptionManager { TEMPORARY_ALLOW_LIST_TYPE_NONE, TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED, TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED, + TEMPORARY_ALLOW_LIST_TYPE_APP_FREEZING_DELAYED }) @Retention(RetentionPolicy.SOURCE) public @interface TempAllowListType {} @@ -216,6 +226,11 @@ public class PowerExemptionManager { * Set temp-allow-list for transferring accounts between users. */ public static final int REASON_ACCOUNT_TRANSFER = 104; + /** + * Set temp-allow-list for server push messaging that can be deferred. + * @hide temporarily until the next release + */ + public static final int REASON_PUSH_MESSAGING_DEFERRABLE = 105; /* Reason code range 200-299 are reserved for broadcast actions */ /** @@ -449,6 +464,7 @@ public class PowerExemptionManager { REASON_PUSH_MESSAGING_OVER_QUOTA, REASON_ACTIVITY_RECOGNITION, REASON_ACCOUNT_TRANSFER, + REASON_PUSH_MESSAGING_DEFERRABLE, REASON_BOOT_COMPLETED, REASON_PRE_BOOT_COMPLETED, REASON_LOCKED_BOOT_COMPLETED, @@ -781,6 +797,8 @@ public class PowerExemptionManager { return "ACTIVITY_RECOGNITION"; case REASON_ACCOUNT_TRANSFER: return "REASON_ACCOUNT_TRANSFER"; + case REASON_PUSH_MESSAGING_DEFERRABLE: + return "PUSH_MESSAGING_DEFERRABLE"; case REASON_BOOT_COMPLETED: return "BOOT_COMPLETED"; case REASON_PRE_BOOT_COMPLETED: diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index d9b315794ea3f..d6e692cfdc009 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -62,6 +62,7 @@ import android.os.Bundle; import android.os.BundleMerger; import android.os.Handler; import android.os.Message; +import android.os.PowerExemptionManager; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; @@ -878,12 +879,20 @@ class BroadcastQueueModernImpl extends BroadcastQueue { mLocalHandler.sendMessageDelayed( Message.obtain(mLocalHandler, MSG_BG_ACTIVITY_START_TIMEOUT, args), timeout); } - if (r.options != null && r.options.getTemporaryAppAllowlistDuration() > 0) { - mService.tempAllowlistUidLocked(queue.uid, - r.options.getTemporaryAppAllowlistDuration(), - r.options.getTemporaryAppAllowlistReasonCode(), r.toShortString(), - r.options.getTemporaryAppAllowlistType(), r.callingUid); + if (r.options.getTemporaryAppAllowlistType() + == PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_APP_FREEZING_DELAYED) { + // Only delay freezer, don't add to any temp allowlist + // TODO: Add a unit test + mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(app, + CachedAppOptimizer.UNFREEZE_REASON_START_RECEIVER, + r.options.getTemporaryAppAllowlistDuration()); + } else { + mService.tempAllowlistUidLocked(queue.uid, + r.options.getTemporaryAppAllowlistDuration(), + r.options.getTemporaryAppAllowlistReasonCode(), r.toShortString(), + r.options.getTemporaryAppAllowlistType(), r.callingUid); + } } if (DEBUG_BROADCAST) logv("Scheduling " + r + " to warm " + app); diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index 3e82d557c01ae..7773190d22b6e 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -47,6 +47,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FREEZER; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import android.annotation.IntDef; +import android.annotation.UptimeMillisLong; import android.app.ActivityManager; import android.app.ActivityManagerInternal.OomAdjReason; import android.app.ActivityThread; @@ -1278,14 +1279,35 @@ public final class CachedAppOptimizer { return true; } + /** + * Returns the earliest time (relative) from now that the app can be frozen. + * @param app The app to update + * @param delayMillis How much to delay freezing by + */ + @GuardedBy("mProcLock") + private long updateEarliestFreezableTime(ProcessRecord app, long delayMillis) { + final long now = SystemClock.uptimeMillis(); + app.mOptRecord.setEarliestFreezableTime( + Math.max(app.mOptRecord.getEarliestFreezableTime(), now + delayMillis)); + return app.mOptRecord.getEarliestFreezableTime() - now; + } + // This will ensure app will be out of the freezer for at least mFreezerDebounceTimeout. @GuardedBy("mAm") void unfreezeTemporarily(ProcessRecord app, @UnfreezeReason int reason) { + unfreezeTemporarily(app, reason, mFreezerDebounceTimeout); + } + + // This will ensure app will be out of the freezer for at least mFreezerDebounceTimeout. + @GuardedBy("mAm") + void unfreezeTemporarily(ProcessRecord app, @UnfreezeReason int reason, long delayMillis) { if (mUseFreezer) { synchronized (mProcLock) { + // Move the earliest freezable time further, if necessary + final long delay = updateEarliestFreezableTime(app, delayMillis); if (app.mOptRecord.isFrozen() || app.mOptRecord.isPendingFreeze()) { unfreezeAppLSP(app, reason); - freezeAppAsyncLSP(app); + freezeAppAsyncLSP(app, delay); } } } @@ -1293,11 +1315,17 @@ public final class CachedAppOptimizer { @GuardedBy({"mAm", "mProcLock"}) void freezeAppAsyncLSP(ProcessRecord app) { - freezeAppAsyncInternalLSP(app, mFreezerDebounceTimeout, false); + freezeAppAsyncLSP(app, updateEarliestFreezableTime(app, mFreezerDebounceTimeout)); } @GuardedBy({"mAm", "mProcLock"}) - void freezeAppAsyncInternalLSP(ProcessRecord app, long delayMillis, boolean force) { + private void freezeAppAsyncLSP(ProcessRecord app, @UptimeMillisLong long delayMillis) { + freezeAppAsyncInternalLSP(app, delayMillis, false); + } + + @GuardedBy({"mAm", "mProcLock"}) + void freezeAppAsyncInternalLSP(ProcessRecord app, @UptimeMillisLong long delayMillis, + boolean force) { final ProcessCachedOptimizerRecord opt = app.mOptRecord; if (opt.isPendingFreeze()) { // Skip redundant DO_FREEZE message diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java index e8c8f6dd5462e..7841b699ec982 100644 --- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java +++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java @@ -16,6 +16,7 @@ package com.android.server.am; +import android.annotation.UptimeMillisLong; import android.app.ActivityManagerInternal.OomAdjReason; import com.android.internal.annotations.GuardedBy; @@ -119,6 +120,12 @@ final class ProcessCachedOptimizerRecord { @GuardedBy("mProcLock") private boolean mPendingFreeze; + /** + * This is the soonest the process can be allowed to freeze, in uptime millis + */ + @GuardedBy("mProcLock") + private @UptimeMillisLong long mEarliestFreezableTimeMillis; + @GuardedBy("mProcLock") long getLastCompactTime() { return mLastCompactTime; @@ -263,6 +270,16 @@ final class ProcessCachedOptimizerRecord { mShouldNotFreeze = shouldNotFreeze; } + @GuardedBy("mProcLock") + @UptimeMillisLong long getEarliestFreezableTime() { + return mEarliestFreezableTimeMillis; + } + + @GuardedBy("mProcLock") + void setEarliestFreezableTime(@UptimeMillisLong long earliestFreezableTimeMillis) { + mEarliestFreezableTimeMillis = earliestFreezableTimeMillis; + } + @GuardedBy("mProcLock") boolean isFreezeExempt() { return mFreezeExempt;