Don't freeze bindee process if bound with BIND_ALLOW_OOM_MANAGEMENT
Also fixed app freezer's settings change observer, added a device
config item for the debounce timeout, as well as a lock ordering issue
Bug: 183735766
Test: atest FrameworksServicesTests:ActivityManagerTest
Test: atest CachedAppOptimizerTest
Change-Id: Idfdc90fd55172103eb6bb1ab1f64e5c4f17c89c3
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 1839e2a..e4cb15f 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -23,6 +23,8 @@
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.ApplicationExitInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
import android.os.Debug;
import android.os.Handler;
import android.os.Message;
@@ -80,6 +82,8 @@
"compact_full_delta_rss_throttle_kb";
@VisibleForTesting static final String KEY_COMPACT_PROC_STATE_THROTTLE =
"compact_proc_state_throttle";
+ @VisibleForTesting static final String KEY_FREEZER_DEBOUNCE_TIMEOUT =
+ "freeze_debounce_timeout";
// Phenotype sends int configurations and we map them to the strings we'll use on device,
// preventing a weird string value entering the kernel.
@@ -116,6 +120,10 @@
// Format of this string should be a comma separated list of integers.
@VisibleForTesting static final String DEFAULT_COMPACT_PROC_STATE_THROTTLE =
String.valueOf(ActivityManager.PROCESS_STATE_RECEIVER);
+ @VisibleForTesting static final long DEFAULT_FREEZER_DEBOUNCE_TIMEOUT = 600_000L;
+
+ @VisibleForTesting static final Uri CACHED_APP_FREEZER_ENABLED_URI = Settings.Global.getUriFor(
+ Settings.Global.CACHED_APPS_FREEZER_ENABLED);
@VisibleForTesting
interface PropertyChangedCallbackForTest {
@@ -141,9 +149,6 @@
static final int SET_FROZEN_PROCESS_MSG = 3;
static final int REPORT_UNFREEZE_MSG = 4;
- //TODO:change this static definition into a configurable flag.
- static final long FREEZE_TIMEOUT_MS = 600000;
-
static final int DO_FREEZE = 1;
static final int REPORT_UNFREEZE = 2;
@@ -198,6 +203,8 @@
updateMinOomAdjThrottle();
} else if (KEY_COMPACT_THROTTLE_MAX_OOM_ADJ.equals(name)) {
updateMaxOomAdjThrottle();
+ } else if (KEY_FREEZER_DEBOUNCE_TIMEOUT.equals(name)) {
+ updateFreezerDebounceTimeout();
}
}
}
@@ -207,6 +214,23 @@
}
};
+ private final class SettingsContentObserver extends ContentObserver {
+ SettingsContentObserver() {
+ super(mAm.mHandler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (CACHED_APP_FREEZER_ENABLED_URI.equals(uri)) {
+ synchronized (mPhenotypeFlagLock) {
+ updateUseFreezer();
+ }
+ }
+ }
+ }
+
+ private final SettingsContentObserver mSettingsObserver;
+
private final Object mPhenotypeFlagLock = new Object();
// Configured by phenotype. Updates from the server take effect immediately.
@@ -259,6 +283,8 @@
@GuardedBy("mProcLock")
private boolean mFreezerOverride = false;
+ @VisibleForTesting volatile long mFreezerDebounceTimeout = DEFAULT_FREEZER_DEBOUNCE_TIMEOUT;
+
// Maps process ID to last compaction statistics for processes that we've fully compacted. Used
// when evaluating throttles that we only consider for "full" compaction, so we don't store
// data for "some" compactions. Uses LinkedHashMap to ensure insertion order is kept and
@@ -293,6 +319,7 @@
mProcStateThrottle = new HashSet<>();
mProcessDependencies = processDependencies;
mTestCallback = callback;
+ mSettingsObserver = new SettingsContentObserver();
}
/**
@@ -303,6 +330,8 @@
// TODO: initialize flags to default and only update them if values are set in DeviceConfig
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
ActivityThread.currentApplication().getMainExecutor(), mOnFlagsChangedListener);
+ mAm.mContext.getContentResolver().registerContentObserver(
+ CACHED_APP_FREEZER_ENABLED_URI, false, mSettingsObserver);
synchronized (mPhenotypeFlagLock) {
updateUseCompaction();
updateCompactionActions();
@@ -315,6 +344,7 @@
updateUseFreezer();
updateMinOomAdjThrottle();
updateMaxOomAdjThrottle();
+ updateFreezerDebounceTimeout();
}
}
@@ -367,6 +397,7 @@
+ " processes.");
pw.println(" " + KEY_USE_FREEZER + "=" + mUseFreezer);
pw.println(" " + KEY_FREEZER_STATSD_SAMPLE_RATE + "=" + mFreezerStatsdSampleRate);
+ pw.println(" " + KEY_FREEZER_DEBOUNCE_TIMEOUT + "=" + mFreezerDebounceTimeout);
if (DEBUG_COMPACTION) {
for (Map.Entry<Integer, LastCompactionStats> entry
: mLastCompactionStats.entrySet()) {
@@ -627,21 +658,28 @@
mUseFreezer = isFreezerSupported();
}
- if (mUseFreezer && mFreezeHandler == null) {
- Slog.d(TAG_AM, "Freezer enabled");
- enableFreezer(true);
+ final boolean useFreezer = mUseFreezer;
+ // enableFreezer() would need the global ActivityManagerService lock, post it.
+ mAm.mHandler.post(() -> {
+ if (useFreezer) {
+ Slog.d(TAG_AM, "Freezer enabled");
+ enableFreezer(true);
- if (!mCachedAppOptimizerThread.isAlive()) {
- mCachedAppOptimizerThread.start();
+ if (!mCachedAppOptimizerThread.isAlive()) {
+ mCachedAppOptimizerThread.start();
+ }
+
+ if (mFreezeHandler == null) {
+ mFreezeHandler = new FreezeHandler();
+ }
+
+ Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(),
+ Process.THREAD_GROUP_SYSTEM);
+ } else {
+ Slog.d(TAG_AM, "Freezer disabled");
+ enableFreezer(false);
}
-
- mFreezeHandler = new FreezeHandler();
-
- Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(),
- Process.THREAD_GROUP_SYSTEM);
- } else {
- enableFreezer(false);
- }
+ });
}
@GuardedBy("mPhenotypeFlagLock")
@@ -794,6 +832,16 @@
}
}
+ @GuardedBy("mPhenotypeFlagLock")
+ private void updateFreezerDebounceTimeout() {
+ mFreezerDebounceTimeout = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_FREEZER_DEBOUNCE_TIMEOUT, DEFAULT_FREEZER_DEBOUNCE_TIMEOUT);
+
+ if (mFreezerDebounceTimeout < 0) {
+ mFullDeltaRssThrottleKb = DEFAULT_FREEZER_DEBOUNCE_TIMEOUT;
+ }
+ }
+
private boolean parseProcStateThrottle(String procStateThrottleString) {
String[] procStates = TextUtils.split(procStateThrottleString, ",");
mProcStateThrottle.clear();
@@ -818,7 +866,7 @@
return COMPACT_ACTION_STRING[action];
}
- // This will ensure app will be out of the freezer for at least FREEZE_TIMEOUT_MS
+ // This will ensure app will be out of the freezer for at least mFreezerDebounceTimeout.
@GuardedBy("mAm")
void unfreezeTemporarily(ProcessRecord app) {
if (mUseFreezer) {
@@ -838,7 +886,7 @@
mFreezeHandler.sendMessageDelayed(
mFreezeHandler.obtainMessage(
SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app),
- FREEZE_TIMEOUT_MS);
+ mFreezerDebounceTimeout);
}
@GuardedBy({"mAm", "mProcLock"})
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 5ae65ef..1ffc53c 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2013,6 +2013,10 @@
}
String adjType = null;
if ((cr.flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) {
+ // Similar to BIND_WAIVE_PRIORITY, keep it unfrozen.
+ if (clientAdj < ProcessList.CACHED_APP_MIN_ADJ) {
+ app.mOptRecord.setShouldNotFreeze(true);
+ }
// Not doing bind OOM management, so treat
// this guy more like a started service.
if (state.hasShownUi() && !state.getCachedIsHomeProcess()) {
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index f4ce723..026c1d3 100644
--- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -17,6 +17,7 @@
package com.android.server.am;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
@@ -28,6 +29,9 @@
private final ActivityManagerGlobalLock mProcLock;
+ @VisibleForTesting
+ static final String IS_FROZEN = "isFrozen";
+
/**
* The last time that this process was compacted.
*/
@@ -169,5 +173,6 @@
void dump(PrintWriter pw, String prefix, long nowUptime) {
pw.print(prefix); pw.print("lastCompactTime="); pw.print(mLastCompactTime);
pw.print(" lastCompactAction="); pw.println(mLastCompactAction);
+ pw.print(" " + IS_FROZEN + "="); pw.println(mFrozen);
}
}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 68f5479..d7fbd49 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -42,6 +42,7 @@
"androidx.test.ext.truth",
"androidx.test.runner",
"androidx.test.rules",
+ "cts-wm-util",
"platform-compat-test-rules",
"mockito-target-minus-junit4",
"platform-test-annotations",
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
index e04841b..6bca5e4 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
@@ -46,6 +46,9 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+import android.server.wm.settings.SettingsSession;
import android.support.test.uiautomator.UiDevice;
import android.test.suitebuilder.annotation.LargeTest;
import android.text.TextUtils;
@@ -61,6 +64,8 @@
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* Tests for {@link ActivityManager}.
@@ -316,6 +321,102 @@
}
}
+ @LargeTest
+ @Test
+ public void testAppFreezerWithAllowOomAdj() throws Exception {
+ final long waitFor = 5000;
+ boolean freezerWasEnabled = isFreezerEnabled();
+ SettingsSession<String> freezerEnabled = null;
+ SettingsSession<String> amConstantsSettings = null;
+ DeviceConfigSession<Long> freezerDebounceTimeout = null;
+ MyServiceConnection autoConnection = null;
+ try {
+ if (!freezerWasEnabled) {
+ freezerEnabled = new SettingsSession<>(
+ Settings.Global.getUriFor(Settings.Global.CACHED_APPS_FREEZER_ENABLED),
+ Settings.Global::getString, Settings.Global::putString);
+ freezerEnabled.set("enabled");
+ Thread.sleep(waitFor);
+ if (!isFreezerEnabled()) {
+ // Still not enabled? Probably because the device doesn't support it.
+ return;
+ }
+ }
+ freezerDebounceTimeout = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_FREEZER_DEBOUNCE_TIMEOUT,
+ DeviceConfig::getLong, CachedAppOptimizer.DEFAULT_FREEZER_DEBOUNCE_TIMEOUT);
+ freezerDebounceTimeout.set(waitFor);
+
+ final String activityManagerConstants = Settings.Global.ACTIVITY_MANAGER_CONSTANTS;
+ amConstantsSettings = new SettingsSession<>(
+ Settings.Global.getUriFor(activityManagerConstants),
+ Settings.Global::getString, Settings.Global::putString);
+
+ amConstantsSettings.set(
+ ActivityManagerConstants.KEY_MAX_SERVICE_INACTIVITY + "=" + waitFor);
+
+ final Intent intent = new Intent();
+ intent.setClassName(TEST_APP, TEST_CLASS);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ autoConnection = new MyServiceConnection(latch);
+ mContext.bindService(intent, autoConnection,
+ Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_OOM_MANAGEMENT);
+ try {
+ assertTrue("Timeout to bind to service " + intent.getComponent(),
+ latch.await(AWAIT_TIMEOUT, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ fail("Unable to bind to service " + intent.getComponent());
+ }
+ assertFalse(TEST_APP + " shouldn't be frozen now.", isAppFrozen(TEST_APP));
+
+ // Trigger oomAdjUpdate/
+ toggleScreenOn(false);
+ toggleScreenOn(true);
+
+ // Wait for the freezer kick in if there is any.
+ Thread.sleep(waitFor * 4);
+
+ // It still shouldn't be frozen, although it's been in cached state.
+ assertFalse(TEST_APP + " shouldn't be frozen now.", isAppFrozen(TEST_APP));
+ } finally {
+ toggleScreenOn(true);
+ if (amConstantsSettings != null) {
+ amConstantsSettings.close();
+ }
+ if (freezerEnabled != null) {
+ freezerEnabled.close();
+ }
+ if (freezerDebounceTimeout != null) {
+ freezerDebounceTimeout.close();
+ }
+ if (autoConnection != null) {
+ mContext.unbindService(autoConnection);
+ }
+ }
+ }
+
+ private boolean isFreezerEnabled() throws Exception {
+ final String output = runShellCommand("dumpsys activity settings");
+ final Matcher matcher = Pattern.compile("\\b" + CachedAppOptimizer.KEY_USE_FREEZER
+ + "\\b=\\b(true|false)\\b").matcher(output);
+ if (matcher.find()) {
+ return Boolean.parseBoolean(matcher.group(1));
+ }
+ return false;
+ }
+
+ private boolean isAppFrozen(String packageName) throws Exception {
+ final String output = runShellCommand("dumpsys activity p " + packageName);
+ final Matcher matcher = Pattern.compile("\\b" + ProcessCachedOptimizerRecord.IS_FROZEN
+ + "\\b=\\b(true|false)\\b").matcher(output);
+ if (matcher.find()) {
+ return Boolean.parseBoolean(matcher.group(1));
+ }
+ return false;
+ }
+
/**
* Make sure the screen state.
*/
diff --git a/services/tests/servicestests/src/com/android/server/am/DeviceConfigSession.java b/services/tests/servicestests/src/com/android/server/am/DeviceConfigSession.java
new file mode 100644
index 0000000..03cf17c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/DeviceConfigSession.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 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.am;
+
+import android.annotation.NonNull;
+import android.provider.DeviceConfig;
+
+import com.android.compatibility.common.util.SystemUtil;
+import com.android.internal.util.function.TriFunction;
+
+/**
+ * An utility class to set/restore the given device_config item.
+ */
+public class DeviceConfigSession<T> implements AutoCloseable {
+ private final TriFunction<String, String, T, T> mGetter;
+
+ private final String mNamespace;
+ private final String mKey;
+ private final T mInitialValue;
+ private final T mDefaultValue;
+ private boolean mHasInitalValue;
+
+ DeviceConfigSession(String namespace, String key,
+ TriFunction<String, String, T, T> getter, T defaultValue) {
+ mNamespace = namespace;
+ mKey = key;
+ mGetter = getter;
+ mDefaultValue = defaultValue;
+ // Try {@DeviceConfig#getString} firstly since the DeviceConfig API doesn't
+ // support "not found" exception.
+ final String initialStringValue = DeviceConfig.getString(namespace, key, null);
+ if (initialStringValue == null) {
+ mHasInitalValue = false;
+ mInitialValue = defaultValue;
+ } else {
+ mHasInitalValue = true;
+ mInitialValue = getter.apply(namespace, key, defaultValue);
+ }
+ }
+
+ public void set(final @NonNull T value) {
+ DeviceConfig.setProperty(mNamespace, mKey,
+ value == null ? null : value.toString(), false);
+ }
+
+ public T get() {
+ return mGetter.apply(mNamespace, mKey, mDefaultValue);
+ }
+
+ @Override
+ public void close() throws Exception {
+ if (mHasInitalValue) {
+ set(mInitialValue);
+ } else {
+ SystemUtil.runShellCommand("device_config delete " + mNamespace + " " + mKey);
+ }
+ }
+}