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/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);
+ }
+ }
+}