From 369f7153e7d0c6c9084d5fab06770d92bde615b2 Mon Sep 17 00:00:00 2001 From: Amith Yamasani Date: Thu, 11 May 2023 23:33:55 -0700 Subject: [PATCH] Delay freezing for given duration through broadcast options This allows a privileged app to set an allowlist duration to only delay freezing an app. It doesn't put the app into the temp allowlist nor put it into active state. It only kicks the can down the road on when to freeze the process. Fixes: 278940896 Test: atest BroadcastFreezerTest Change-Id: I2498c29401d1ec2514450e034498de0403f334da --- .../android/os/PowerExemptionManager.java | 18 ++++++++++ .../server/am/BroadcastQueueModernImpl.java | 19 ++++++++--- .../android/server/am/CachedAppOptimizer.java | 34 +++++++++++++++++-- .../am/ProcessCachedOptimizerRecord.java | 17 ++++++++++ 4 files changed, 80 insertions(+), 8 deletions(-) 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 059239df3a7f1..e5cbf9120177d 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; @@ -867,12 +868,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;