From c26a5a81c4d3f446946ab29d0c6b5a94cc28770d Mon Sep 17 00:00:00 2001 From: Ben Murdoch Date: Wed, 16 Jan 2019 10:05:58 +0000 Subject: [PATCH] Migrate AppCompaction settings to DeviceConfig APIs. To support app compaction P/H experiments, migrate it's settings away from using the key/value pair string the rest of ActivityManagerConstants uses, and use the DeviceConfig APIs instead. Test: boots, works, atest FrameworksServicesTests:AppCompactorTest Bug: 122879579 Change-Id: I9d1ae17d9671d0443b728ecdeca7102ba34d9c2f --- api/system-current.txt | 12 + core/java/android/provider/DeviceConfig.java | 28 ++ .../server/am/ActivityManagerConstants.java | 83 ++-- .../android/server/am/ActivityManagerService.java | 3 + .../java/com/android/server/am/AppCompactor.java | 451 ++++++++++++++------- .../java/com/android/server/am/OomAdjuster.java | 15 +- .../com/android/server/am/AppCompactorTest.java | 379 +++++++++++++++++ 7 files changed, 781 insertions(+), 190 deletions(-) create mode 100644 services/tests/servicestests/src/com/android/server/am/AppCompactorTest.java diff --git a/api/system-current.txt b/api/system-current.txt index 315a941d3cbb..7e78ee999d00 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5626,6 +5626,18 @@ package android.provider { field public static final String NAMESPACE_NOTIFICATION_ASSISTANT = "notification_assistant"; } + public static interface DeviceConfig.ActivityManager { + field public static final String KEY_COMPACT_ACTION_1 = "compact_action_1"; + field public static final String KEY_COMPACT_ACTION_2 = "compact_action_2"; + field public static final String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1"; + field public static final String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2"; + field public static final String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3"; + field public static final String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4"; + field public static final String KEY_MAX_CACHED_PROCESSES = "max_cached_processes"; + field public static final String KEY_USE_COMPACTION = "use_compaction"; + field public static final String NAMESPACE = "activity_manager"; + } + public static interface DeviceConfig.FsiBoot { field public static final String NAMESPACE = "fsi_boot"; field public static final String OOB_ENABLED = "oob_enabled"; diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 6ccd2968824e..cd823a9c8997 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -153,6 +153,34 @@ public final class DeviceConfig { String OOB_WHITELIST = "oob_whitelist"; } + /** + * Namespace for activity manager related features. These features will be applied + * immediately upon change. + * + * @hide + */ + @SystemApi + public interface ActivityManager { + String NAMESPACE = "activity_manager"; + + /** + * App compaction flags. See {@link com.android.server.am.AppCompactor}. + */ + String KEY_USE_COMPACTION = "use_compaction"; + String KEY_COMPACT_ACTION_1 = "compact_action_1"; + String KEY_COMPACT_ACTION_2 = "compact_action_2"; + String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1"; + String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2"; + String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3"; + String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4"; + + /** + * Maximum number of cached processes. See + * {@link com.android.server.am.ActivityManagerConstants}. + */ + String KEY_MAX_CACHED_PROCESSES = "max_cached_processes"; + } + private static final Object sLock = new Object(); @GuardedBy("sLock") private static Map> sListeners = diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index dd2b33ab1179..d025d739055b 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -16,13 +16,19 @@ package com.android.server.am; +import static android.provider.DeviceConfig.ActivityManager.KEY_MAX_CACHED_PROCESSES; + import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER_QUICK; +import android.app.ActivityThread; import android.content.ContentResolver; import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; +import android.provider.DeviceConfig; +import android.provider.DeviceConfig.OnPropertyChangedListener; import android.provider.Settings; +import android.text.TextUtils; import android.util.KeyValueListParser; import android.util.Slog; @@ -34,7 +40,6 @@ import java.io.PrintWriter; final class ActivityManagerConstants extends ContentObserver { // Key names stored in the settings value. - private static final String KEY_MAX_CACHED_PROCESSES = "max_cached_processes"; private static final String KEY_BACKGROUND_SETTLE_TIME = "background_settle_time"; private static final String KEY_FGSERVICE_MIN_SHOWN_TIME = "fgservice_min_shown_time"; @@ -69,13 +74,6 @@ final class ActivityManagerConstants extends ContentObserver { static final String KEY_PROCESS_START_ASYNC = "process_start_async"; static final String KEY_MEMORY_INFO_THROTTLE_TIME = "memory_info_throttle_time"; static final String KEY_TOP_TO_FGS_GRACE_DURATION = "top_to_fgs_grace_duration"; - static final String KEY_USE_COMPACTION = "use_compaction"; - static final String KEY_COMPACT_ACTION_1 = "compact_action_1"; - static final String KEY_COMPACT_ACTION_2 = "compact_action_2"; - static final String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1"; - static final String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2"; - static final String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3"; - static final String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4"; private static final int DEFAULT_MAX_CACHED_PROCESSES = 32; private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000; @@ -106,13 +104,6 @@ final class ActivityManagerConstants extends ContentObserver { private static final boolean DEFAULT_PROCESS_START_ASYNC = true; private static final long DEFAULT_MEMORY_INFO_THROTTLE_TIME = 5*60*1000; private static final long DEFAULT_TOP_TO_FGS_GRACE_DURATION = 15 * 1000; - private static final boolean DEFAULT_USE_COMPACTION = false; - public static final int DEFAULT_COMPACT_ACTION_1 = 1; - public static final int DEFAULT_COMPACT_ACTION_2 = 3; - public static final long DEFAULT_COMPACT_THROTTLE_1 = 5000; - public static final long DEFAULT_COMPACT_THROTTLE_2 = 10000; - public static final long DEFAULT_COMPACT_THROTTLE_3 = 500; - public static final long DEFAULT_COMPACT_THROTTLE_4 = 10000; // Maximum number of cached processes we will allow. public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES; @@ -232,23 +223,6 @@ final class ActivityManagerConstants extends ContentObserver { // this long. public long TOP_TO_FGS_GRACE_DURATION = DEFAULT_TOP_TO_FGS_GRACE_DURATION; - // Use compaction for background apps. - public boolean USE_COMPACTION = DEFAULT_USE_COMPACTION; - - // Action for compactAppSome. - public int COMPACT_ACTION_1 = DEFAULT_COMPACT_ACTION_1; - // Action for compactAppFull; - public int COMPACT_ACTION_2 = DEFAULT_COMPACT_ACTION_2; - - // How long we'll skip second compactAppSome after first compactAppSome - public long COMPACT_THROTTLE_1 = DEFAULT_COMPACT_THROTTLE_1; - // How long we'll skip compactAppSome after compactAppFull - public long COMPACT_THROTTLE_2 = DEFAULT_COMPACT_THROTTLE_2; - // How long we'll skip compactAppFull after compactAppSome - public long COMPACT_THROTTLE_3 = DEFAULT_COMPACT_THROTTLE_3; - // How long we'll skip second compactAppFull after first compactAppFull - public long COMPACT_THROTTLE_4 = DEFAULT_COMPACT_THROTTLE_4; - // Indicates whether the activity starts logging is enabled. // Controlled by Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED volatile boolean mFlagActivityStartsLoggingEnabled; @@ -295,10 +269,19 @@ final class ActivityManagerConstants extends ContentObserver { Settings.Global.getUriFor( Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED); + private final OnPropertyChangedListener mOnDeviceConfigChangedListener = + new OnPropertyChangedListener() { + @Override + public void onPropertyChanged(String namespace, String name, String value) { + if (KEY_MAX_CACHED_PROCESSES.equals(name)) { + updateMaxCachedProcesses(); + } + } + }; + public ActivityManagerConstants(ActivityManagerService service, Handler handler) { super(handler); mService = service; - updateMaxCachedProcesses(); } public void start(ContentResolver resolver) { @@ -309,6 +292,11 @@ final class ActivityManagerConstants extends ContentObserver { updateConstants(); updateActivityStartsLoggingEnabled(); updateBackgroundActivityStartsEnabled(); + DeviceConfig.addOnPropertyChangedListener(DeviceConfig.ActivityManager.NAMESPACE, + ActivityThread.currentApplication().getMainExecutor(), + mOnDeviceConfigChangedListener); + updateMaxCachedProcesses(); + } public void setOverrideMaxCachedProcesses(int value) { @@ -347,8 +335,6 @@ final class ActivityManagerConstants extends ContentObserver { // with defaults. Slog.e("ActivityManagerConstants", "Bad activity manager config settings", e); } - MAX_CACHED_PROCESSES = mParser.getInt(KEY_MAX_CACHED_PROCESSES, - DEFAULT_MAX_CACHED_PROCESSES); BACKGROUND_SETTLE_TIME = mParser.getLong(KEY_BACKGROUND_SETTLE_TIME, DEFAULT_BACKGROUND_SETTLE_TIME); FGSERVICE_MIN_SHOWN_TIME = mParser.getLong(KEY_FGSERVICE_MIN_SHOWN_TIME, @@ -406,13 +392,9 @@ final class ActivityManagerConstants extends ContentObserver { DEFAULT_MEMORY_INFO_THROTTLE_TIME); TOP_TO_FGS_GRACE_DURATION = mParser.getDurationMillis(KEY_TOP_TO_FGS_GRACE_DURATION, DEFAULT_TOP_TO_FGS_GRACE_DURATION); - USE_COMPACTION = mParser.getBoolean(KEY_USE_COMPACTION, DEFAULT_USE_COMPACTION); - COMPACT_ACTION_1 = mParser.getInt(KEY_COMPACT_ACTION_1, DEFAULT_COMPACT_ACTION_1); - COMPACT_ACTION_2 = mParser.getInt(KEY_COMPACT_ACTION_2, DEFAULT_COMPACT_ACTION_2); - COMPACT_THROTTLE_1 = mParser.getLong(KEY_COMPACT_THROTTLE_1, DEFAULT_COMPACT_THROTTLE_1); - COMPACT_THROTTLE_2 = mParser.getLong(KEY_COMPACT_THROTTLE_2, DEFAULT_COMPACT_THROTTLE_2); - COMPACT_THROTTLE_3 = mParser.getLong(KEY_COMPACT_THROTTLE_3, DEFAULT_COMPACT_THROTTLE_3); - COMPACT_THROTTLE_4 = mParser.getLong(KEY_COMPACT_THROTTLE_4, DEFAULT_COMPACT_THROTTLE_4); + + // For new flags that are intended for server-side experiments, please use the new + // DeviceConfig package. updateMaxCachedProcesses(); } @@ -429,8 +411,19 @@ final class ActivityManagerConstants extends ContentObserver { } private void updateMaxCachedProcesses() { - CUR_MAX_CACHED_PROCESSES = mOverrideMaxCachedProcesses < 0 - ? MAX_CACHED_PROCESSES : mOverrideMaxCachedProcesses; + String maxCachedProcessesFlag = DeviceConfig.getProperty( + DeviceConfig.ActivityManager.NAMESPACE, KEY_MAX_CACHED_PROCESSES); + try { + CUR_MAX_CACHED_PROCESSES = mOverrideMaxCachedProcesses < 0 + ? (TextUtils.isEmpty(maxCachedProcessesFlag) + ? DEFAULT_MAX_CACHED_PROCESSES : Integer.parseInt(maxCachedProcessesFlag)) + : mOverrideMaxCachedProcesses; + } catch (NumberFormatException e) { + // Bad flag value from Phenotype, revert to default. + Slog.e("ActivityManagerConstants", + "Unable to parse flag for max_cached_processes: " + maxCachedProcessesFlag, e); + CUR_MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES; + } CUR_MAX_EMPTY_PROCESSES = computeEmptyProcessLimit(CUR_MAX_CACHED_PROCESSES); // Note the trim levels do NOT depend on the override process limit, we want @@ -503,8 +496,6 @@ final class ActivityManagerConstants extends ContentObserver { pw.println(MEMORY_INFO_THROTTLE_TIME); pw.print(" "); pw.print(KEY_TOP_TO_FGS_GRACE_DURATION); pw.print("="); pw.println(TOP_TO_FGS_GRACE_DURATION); - pw.print(" "); pw.print(KEY_USE_COMPACTION); pw.print("="); - pw.println(USE_COMPACTION); pw.println(); if (mOverrideMaxCachedProcesses >= 0) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index b24290f62385..c203c79d68c1 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -7228,6 +7228,7 @@ public class ActivityManagerService extends IActivityManager.Stub mActivityTaskManager.installSystemProviders(); mDevelopmentSettingsObserver = new DevelopmentSettingsObserver(); SettingsToPropertiesMapper.start(mContext.getContentResolver()); + mOomAdjuster.initSettings(); // Now that the settings provider is published we can consider sending // in a rescue party. @@ -9340,6 +9341,7 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized(this) { mConstants.dump(pw); + mOomAdjuster.dumpAppCompactorSettings(pw); pw.println(); if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); @@ -9739,6 +9741,7 @@ public class ActivityManagerService extends IActivityManager.Stub } else if ("settings".equals(cmd)) { synchronized (this) { mConstants.dump(pw); + mOomAdjuster.dumpAppCompactorSettings(pw); } } else if ("services".equals(cmd) || "s".equals(cmd)) { if (dumpClient) { diff --git a/services/core/java/com/android/server/am/AppCompactor.java b/services/core/java/com/android/server/am/AppCompactor.java index fd402cc08c0c..bb55ec3100c0 100644 --- a/services/core/java/com/android/server/am/AppCompactor.java +++ b/services/core/java/com/android/server/am/AppCompactor.java @@ -16,34 +16,63 @@ package com.android.server.am; -import com.android.internal.annotations.GuardedBy; +import static android.os.Process.THREAD_PRIORITY_FOREGROUND; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_ACTION_1; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_ACTION_2; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_1; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_2; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_3; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_4; +import static android.provider.DeviceConfig.ActivityManager.KEY_USE_COMPACTION; import android.app.ActivityManager; - +import android.app.ActivityThread; import android.os.Handler; -import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.SystemClock; import android.os.Trace; - +import android.provider.DeviceConfig; +import android.provider.DeviceConfig.OnPropertyChangedListener; +import android.text.TextUtils; import android.util.EventLog; import android.util.StatsLog; -import static android.os.Process.THREAD_PRIORITY_FOREGROUND; - +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.ServiceThread; import java.io.FileOutputStream; -import java.io.IOException; +import java.io.PrintWriter; import java.util.ArrayList; public final class AppCompactor { - /** - * Processes to compact. - */ - final ArrayList mPendingCompactionProcesses = new ArrayList(); + // Phenotype sends int configurations and we map them to the strings we'll use on device, + // preventing a weird string value entering the kernel. + private static final int COMPACT_ACTION_FILE_FLAG = 1; + private static final int COMPACT_ACTION_ANON_FLAG = 2; + private static final int COMPACT_ACTION_FULL_FLAG = 3; + private static final String COMPACT_ACTION_FILE = "file"; + private static final String COMPACT_ACTION_ANON = "anon"; + private static final String COMPACT_ACTION_FULL = "all"; + + // Defaults for phenotype flags. + @VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = false; + @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_1 = COMPACT_ACTION_FILE_FLAG; + @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_2 = COMPACT_ACTION_FULL_FLAG; + @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_1 = 5_000; + @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_2 = 10_000; + @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_3 = 500; + @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_4 = 10_000; + + @VisibleForTesting + interface PropertyChangedCallbackForTest { + void onPropertyChanged(); + } + private PropertyChangedCallbackForTest mTestCallback; + + // Handler constants. static final int COMPACT_PROCESS_SOME = 1; static final int COMPACT_PROCESS_FULL = 2; static final int COMPACT_PROCESS_MSG = 1; @@ -57,70 +86,106 @@ public final class AppCompactor { */ final ServiceThread mCompactionThread; - final private Handler mCompactionHandler; - - final private ActivityManagerService mAm; - final private ActivityManagerConstants mConstants; - - final private String COMPACT_ACTION_FILE = "file"; - final private String COMPACT_ACTION_ANON = "anon"; - final private String COMPACT_ACTION_FULL = "all"; - - final private String compactActionSome; - final private String compactActionFull; - - final private long throttleSomeSome; - final private long throttleSomeFull; - final private long throttleFullSome; - final private long throttleFullFull; + private final ArrayList mPendingCompactionProcesses = + new ArrayList(); + private final ActivityManagerService mAm; + private final OnPropertyChangedListener mOnFlagsChangedListener = + new OnPropertyChangedListener() { + @Override + public void onPropertyChanged(String namespace, String name, String value) { + synchronized (mPhenotypeFlagLock) { + if (KEY_USE_COMPACTION.equals(name)) { + updateUseCompaction(); + } else if (KEY_COMPACT_ACTION_1.equals(name) + || KEY_COMPACT_ACTION_2.equals(name)) { + updateCompactionActions(); + } else if (KEY_COMPACT_THROTTLE_1.equals(name) + || KEY_COMPACT_THROTTLE_2.equals(name) + || KEY_COMPACT_THROTTLE_3.equals(name) + || KEY_COMPACT_THROTTLE_4.equals(name)) { + updateCompactionThrottles(); + } + } + if (mTestCallback != null) { + mTestCallback.onPropertyChanged(); + } + } + }; + + private final Object mPhenotypeFlagLock = new Object(); + + // Configured by phenotype. Updates from the server take effect immediately. + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting String mCompactActionSome = + compactActionIntToString(DEFAULT_COMPACT_ACTION_1); + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting String mCompactActionFull = + compactActionIntToString(DEFAULT_COMPACT_ACTION_2); + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting long mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1; + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting long mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2; + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting long mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3; + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting long mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4; + @GuardedBy("mPhenotypeFlagLock") + private boolean mUseCompaction = DEFAULT_USE_COMPACTION; + + // Handler on which compaction runs. + private Handler mCompactionHandler; public AppCompactor(ActivityManagerService am) { mAm = am; - mConstants = am.mConstants; - mCompactionThread = new ServiceThread("CompactionThread", THREAD_PRIORITY_FOREGROUND, true); - mCompactionThread.start(); - mCompactionHandler = new MemCompactionHandler(this); - - switch(mConstants.COMPACT_ACTION_1) { - case 1: - compactActionSome = COMPACT_ACTION_FILE; - break; - case 2: - compactActionSome = COMPACT_ACTION_ANON; - break; - case 3: - compactActionSome = COMPACT_ACTION_FULL; - break; - default: - compactActionSome = COMPACT_ACTION_FILE; - break; + } + + @VisibleForTesting + AppCompactor(ActivityManagerService am, PropertyChangedCallbackForTest callback) { + this(am); + mTestCallback = callback; + } + + /** + * Reads phenotype config to determine whether app compaction is enabled or not and + * starts the background thread if necessary. + */ + public void init() { + DeviceConfig.addOnPropertyChangedListener(DeviceConfig.ActivityManager.NAMESPACE, + ActivityThread.currentApplication().getMainExecutor(), mOnFlagsChangedListener); + synchronized (mPhenotypeFlagLock) { + updateUseCompaction(); + updateCompactionActions(); + updateCompactionThrottles(); } + } - switch(mConstants.COMPACT_ACTION_2) { - case 1: - compactActionFull = COMPACT_ACTION_FILE; - break; - case 2: - compactActionFull = COMPACT_ACTION_ANON; - break; - case 3: - compactActionFull = COMPACT_ACTION_FULL; - break; - default: - compactActionFull = COMPACT_ACTION_FULL; - break; + /** + * Returns whether compaction is enabled. + */ + public boolean useCompaction() { + synchronized (mPhenotypeFlagLock) { + return mUseCompaction; } + } - throttleSomeSome = mConstants.COMPACT_THROTTLE_1; - throttleSomeFull = mConstants.COMPACT_THROTTLE_2; - throttleFullSome = mConstants.COMPACT_THROTTLE_3; - throttleFullFull = mConstants.COMPACT_THROTTLE_4; + @GuardedBy("mAm") + void dump(PrintWriter pw) { + pw.println("AppCompactor settings"); + synchronized (mPhenotypeFlagLock) { + pw.println(" " + KEY_USE_COMPACTION + "=" + mUseCompaction); + pw.println(" " + KEY_COMPACT_ACTION_1 + "=" + mCompactActionSome); + pw.println(" " + KEY_COMPACT_ACTION_2 + "=" + mCompactActionFull); + pw.println(" " + KEY_COMPACT_THROTTLE_1 + "=" + mCompactThrottleSomeSome); + pw.println(" " + KEY_COMPACT_THROTTLE_2 + "=" + mCompactThrottleSomeFull); + pw.println(" " + KEY_COMPACT_THROTTLE_3 + "=" + mCompactThrottleFullSome); + pw.println(" " + KEY_COMPACT_THROTTLE_4 + "=" + mCompactThrottleFullFull); + } } - // Must be called while holding AMS lock. - final void compactAppSome(ProcessRecord app) { + @GuardedBy("mAm") + void compactAppSome(ProcessRecord app) { app.reqCompactAction = COMPACT_PROCESS_SOME; mPendingCompactionProcesses.add(app); mCompactionHandler.sendMessage( @@ -128,8 +193,8 @@ public final class AppCompactor { COMPACT_PROCESS_MSG, app.curAdj, app.setProcState)); } - // Must be called while holding AMS lock. - final void compactAppFull(ProcessRecord app) { + @GuardedBy("mAm") + void compactAppFull(ProcessRecord app) { app.reqCompactAction = COMPACT_PROCESS_FULL; mPendingCompactionProcesses.add(app); mCompactionHandler.sendMessage( @@ -137,97 +202,205 @@ public final class AppCompactor { COMPACT_PROCESS_MSG, app.curAdj, app.setProcState)); } - final class MemCompactionHandler extends Handler { - AppCompactor mAc; - private MemCompactionHandler(AppCompactor ac) { - super(ac.mCompactionThread.getLooper()); - mAc = ac; + /** + * Reads the flag value from DeviceConfig to determine whether app compaction + * should be enabled, and starts/stops the compaction thread as needed. + */ + @GuardedBy("mPhenotypeFlagLock") + private void updateUseCompaction() { + String useCompactionFlag = + DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_USE_COMPACTION); + mUseCompaction = TextUtils.isEmpty(useCompactionFlag) + ? DEFAULT_USE_COMPACTION : Boolean.parseBoolean(useCompactionFlag); + if (mUseCompaction && !mCompactionThread.isAlive()) { + mCompactionThread.start(); + mCompactionHandler = new MemCompactionHandler(); + } + } + + @GuardedBy("mPhenotypeFlagLock") + private void updateCompactionActions() { + String compactAction1Flag = + DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_1); + String compactAction2Flag = + DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_2); + + int compactAction1 = DEFAULT_COMPACT_ACTION_1; + try { + compactAction1 = TextUtils.isEmpty(compactAction1Flag) + ? DEFAULT_COMPACT_ACTION_1 : Integer.parseInt(compactAction1Flag); + } catch (NumberFormatException e) { + // Do nothing, leave default. + } + + int compactAction2 = DEFAULT_COMPACT_ACTION_2; + try { + compactAction2 = TextUtils.isEmpty(compactAction2Flag) + ? DEFAULT_COMPACT_ACTION_2 : Integer.parseInt(compactAction2Flag); + } catch (NumberFormatException e) { + // Do nothing, leave default. + } + + mCompactActionSome = compactActionIntToString(compactAction1); + mCompactActionFull = compactActionIntToString(compactAction2); + } + + @GuardedBy("mPhenotypeFlagLock") + private void updateCompactionThrottles() { + boolean useThrottleDefaults = false; + String throttleSomeSomeFlag = + DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_1); + String throttleSomeFullFlag = + DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_2); + String throttleFullSomeFlag = + DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_3); + String throttleFullFullFlag = + DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_4); + + if (TextUtils.isEmpty(throttleSomeSomeFlag) || TextUtils.isEmpty(throttleSomeFullFlag) + || TextUtils.isEmpty(throttleFullSomeFlag) + || TextUtils.isEmpty(throttleFullFullFlag)) { + // Set defaults for all if any are not set. + useThrottleDefaults = true; + } else { + try { + mCompactThrottleSomeSome = Integer.parseInt(throttleSomeSomeFlag); + mCompactThrottleSomeFull = Integer.parseInt(throttleSomeFullFlag); + mCompactThrottleFullSome = Integer.parseInt(throttleFullSomeFlag); + mCompactThrottleFullFull = Integer.parseInt(throttleFullFullFlag); + } catch (NumberFormatException e) { + useThrottleDefaults = true; + } + } + + if (useThrottleDefaults) { + mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1; + mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2; + mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3; + mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4; + } + } + + @VisibleForTesting + static String compactActionIntToString(int action) { + switch(action) { + case COMPACT_ACTION_FILE_FLAG: + return COMPACT_ACTION_FILE; + case COMPACT_ACTION_ANON_FLAG: + return COMPACT_ACTION_ANON; + case COMPACT_ACTION_FULL_FLAG: + return COMPACT_ACTION_FULL; + default: + return COMPACT_ACTION_FILE; + } + } + + private final class MemCompactionHandler extends Handler { + private MemCompactionHandler() { + super(mCompactionThread.getLooper()); } @Override public void handleMessage(Message msg) { switch (msg.what) { - case COMPACT_PROCESS_MSG: { - long start = SystemClock.uptimeMillis(); - ProcessRecord proc; - int pid; - String action; - final String name; - int pendingAction, lastCompactAction; - long lastCompactTime; - synchronized(mAc.mAm) { - proc = mAc.mPendingCompactionProcesses.remove(0); - - // don't compact if the process has returned to perceptible - if (proc.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) { - return; + case COMPACT_PROCESS_MSG: { + long start = SystemClock.uptimeMillis(); + ProcessRecord proc; + int pid; + String action; + final String name; + int pendingAction, lastCompactAction; + long lastCompactTime; + synchronized (mAm) { + proc = mPendingCompactionProcesses.remove(0); + + // don't compact if the process has returned to perceptible + if (proc.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) { + return; + } + + pid = proc.pid; + name = proc.processName; + pendingAction = proc.reqCompactAction; + lastCompactAction = proc.lastCompactAction; + lastCompactTime = proc.lastCompactTime; } - pid = proc.pid; - name = proc.processName; - pendingAction = proc.reqCompactAction; - lastCompactAction = proc.lastCompactAction; - lastCompactTime = proc.lastCompactTime; - } - if (pid == 0) { - // not a real process, either one being launched or one being killed - return; - } - - // basic throttling - // use the ActivityManagerConstants knobs to determine whether current/prevous - // compaction combo should be throtted or not - if (pendingAction == COMPACT_PROCESS_SOME) { - if ((lastCompactAction == COMPACT_PROCESS_SOME && (start - lastCompactTime < throttleSomeSome)) || - (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < throttleSomeFull))) { + if (pid == 0) { + // not a real process, either one being launched or one being killed return; } - } else { - if ((lastCompactAction == COMPACT_PROCESS_SOME && (start - lastCompactTime < throttleFullSome)) || - (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < throttleFullFull))) { - return; + + // basic throttling + // use the Phenotype flag knobs to determine whether current/prevous + // compaction combo should be throtted or not + + // Note that we explicitly don't take mPhenotypeFlagLock here as the flags + // should very seldom change, and taking the risk of using the wrong action is + // preferable to taking the lock for every single compaction action. + if (pendingAction == COMPACT_PROCESS_SOME) { + if ((lastCompactAction == COMPACT_PROCESS_SOME + && (start - lastCompactTime < mCompactThrottleSomeSome)) + || (lastCompactAction == COMPACT_PROCESS_FULL + && (start - lastCompactTime + < mCompactThrottleSomeFull))) { + return; + } + } else { + if ((lastCompactAction == COMPACT_PROCESS_SOME + && (start - lastCompactTime < mCompactThrottleFullSome)) + || (lastCompactAction == COMPACT_PROCESS_FULL + && (start - lastCompactTime + < mCompactThrottleFullFull))) { + return; + } } - } - try { - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact " + - ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full") + - ": " + name); - long[] rssBefore = Process.getRss(pid); - FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim"); if (pendingAction == COMPACT_PROCESS_SOME) { - action = compactActionSome; + action = mCompactActionSome; } else { - action = compactActionFull; + action = mCompactActionFull; } - fos.write(action.getBytes()); - fos.close(); - long[] rssAfter = Process.getRss(pid); - long end = SystemClock.uptimeMillis(); - long time = end - start; - EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action, - rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3], - rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time, - lastCompactAction, lastCompactTime, msg.arg1, msg.arg2); - StatsLog.write(StatsLog.APP_COMPACTED, pid, name, pendingAction, - rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3], - rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time, - lastCompactAction, lastCompactTime, msg.arg1, - ActivityManager.processStateAmToProto(msg.arg2)); - synchronized(mAc.mAm) { - proc.lastCompactTime = end; - proc.lastCompactAction = pendingAction; + + try { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact " + + ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full") + + ": " + name); + long[] rssBefore = Process.getRss(pid); + FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim"); + fos.write(action.getBytes()); + fos.close(); + long[] rssAfter = Process.getRss(pid); + long end = SystemClock.uptimeMillis(); + long time = end - start; + EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action, + rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3], + rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time, + lastCompactAction, lastCompactTime, msg.arg1, msg.arg2); + StatsLog.write(StatsLog.APP_COMPACTED, pid, name, pendingAction, + rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3], + rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time, + lastCompactAction, lastCompactTime, msg.arg1, + ActivityManager.processStateAmToProto(msg.arg2)); + synchronized (mAm) { + proc.lastCompactTime = end; + proc.lastCompactAction = pendingAction; + } + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } catch (Exception e) { + // nothing to do, presumably the process died + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - } catch (Exception e) { - // nothing to do, presumably the process died - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } } - } } } - - } diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index be910d46a8ad..1e03f6c3ba5f 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -134,10 +134,11 @@ public final class OomAdjuster { mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class); mConstants = mService.mConstants; - // mConstants can be null under test, which causes AppCompactor to crash - if (mConstants != null) { - mAppCompact = new AppCompactor(mService); - } + mAppCompact = new AppCompactor(mService); + } + + void initSettings() { + mAppCompact.init(); } /** @@ -1679,7 +1680,7 @@ public final class OomAdjuster { if (app.curAdj != app.setAdj) { // don't compact during bootup - if (mConstants.USE_COMPACTION && mService.mBooted) { + if (mAppCompact.useCompaction() && mService.mBooted) { // Perform a minor compaction when a perceptible app becomes the prev/home app // Perform a major compaction when any app enters cached // reminder: here, setAdj is previous state, curAdj is upcoming state @@ -2104,4 +2105,8 @@ public final class OomAdjuster { + " mNewNumServiceProcs=" + mNewNumServiceProcs); } + @GuardedBy("mService") + void dumpAppCompactorSettings(PrintWriter pw) { + mAppCompact.dump(pw); + } } diff --git a/services/tests/servicestests/src/com/android/server/am/AppCompactorTest.java b/services/tests/servicestests/src/com/android/server/am/AppCompactorTest.java new file mode 100644 index 000000000000..1a231cf56921 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/am/AppCompactorTest.java @@ -0,0 +1,379 @@ +/* + * Copyright (C) 2019 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 static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_ACTION_1; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_ACTION_2; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_1; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_2; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_3; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_4; +import static android.provider.DeviceConfig.ActivityManager.KEY_USE_COMPACTION; + +import static com.android.server.am.ActivityManagerService.Injector; +import static com.android.server.am.AppCompactor.compactActionIntToString; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import android.os.Handler; +import android.os.HandlerThread; +import android.provider.DeviceConfig; +import android.support.test.uiautomator.UiDevice; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.appop.AppOpsService; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Tests for {@link AppCompactor}. + * + * Build/Install/Run: + * atest FrameworksServicesTests:AppCompactorTest + */ +@RunWith(AndroidJUnit4.class) +public final class AppCompactorTest { + + @Mock private AppOpsService mAppOpsService; + private AppCompactor mCompactorUnderTest; + private HandlerThread mHandlerThread; + private Handler mHandler; + private CountDownLatch mCountDown; + + private static void clearDeviceConfig() throws IOException { + UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + uiDevice.executeShellCommand( + "device_config delete activity_manager " + KEY_USE_COMPACTION); + uiDevice.executeShellCommand( + "device_config delete activity_manager " + KEY_COMPACT_ACTION_1); + uiDevice.executeShellCommand( + "device_config delete activity_manager " + KEY_COMPACT_ACTION_2); + uiDevice.executeShellCommand( + "device_config delete activity_manager " + KEY_COMPACT_THROTTLE_1); + uiDevice.executeShellCommand( + "device_config delete activity_manager " + KEY_COMPACT_THROTTLE_2); + uiDevice.executeShellCommand( + "device_config delete activity_manager " + KEY_COMPACT_THROTTLE_3); + uiDevice.executeShellCommand( + "device_config delete activity_manager " + KEY_COMPACT_THROTTLE_4); + } + + @Before + public void setUp() throws IOException { + MockitoAnnotations.initMocks(this); + clearDeviceConfig(); + mHandlerThread = new HandlerThread(""); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + ActivityManagerService ams = new ActivityManagerService(new TestInjector()); + mCompactorUnderTest = new AppCompactor(ams, + new AppCompactor.PropertyChangedCallbackForTest() { + @Override + public void onPropertyChanged() { + if (mCountDown != null) { + mCountDown.countDown(); + } + } + }); + } + + @After + public void tearDown() throws IOException { + mHandlerThread.quit(); + mCountDown = null; + clearDeviceConfig(); + } + + @Test + public void init_setsDefaults() { + mCompactorUnderTest.init(); + assertThat(mCompactorUnderTest.useCompaction(), + is(mCompactorUnderTest.DEFAULT_USE_COMPACTION)); + assertThat(mCompactorUnderTest.mCompactActionSome, is( + compactActionIntToString(mCompactorUnderTest.DEFAULT_COMPACT_ACTION_1))); + assertThat(mCompactorUnderTest.mCompactActionFull, is( + compactActionIntToString(mCompactorUnderTest.DEFAULT_COMPACT_ACTION_2))); + assertThat(mCompactorUnderTest.mCompactThrottleSomeSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2)); + assertThat(mCompactorUnderTest.mCompactThrottleFullSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3)); + assertThat(mCompactorUnderTest.mCompactThrottleFullFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4)); + } + + @Test + public void init_withDeviceConfigSetsParameters() { + // When the DeviceConfig already has a flag value stored (note this test will need to + // change if the default value changes from false). + assertThat(mCompactorUnderTest.DEFAULT_USE_COMPACTION, is(false)); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_USE_COMPACTION, "true", false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_1, + Integer.toString((AppCompactor.DEFAULT_COMPACT_ACTION_1 + 1 % 3) + 1), false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_2, + Integer.toString((AppCompactor.DEFAULT_COMPACT_ACTION_2 + 1 % 3) + 1), false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_1, + Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1), false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_2, + Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1), false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_3, + Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1), false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_4, + Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1), false); + + // Then calling init will read and set that flag. + mCompactorUnderTest.init(); + assertThat(mCompactorUnderTest.useCompaction(), is(true)); + assertThat(mCompactorUnderTest.mCompactionThread.isAlive(), is(true)); + + assertThat(mCompactorUnderTest.mCompactActionSome, + is(compactActionIntToString((AppCompactor.DEFAULT_COMPACT_ACTION_1 + 1 % 3) + 1))); + assertThat(mCompactorUnderTest.mCompactActionFull, + is(compactActionIntToString((AppCompactor.DEFAULT_COMPACT_ACTION_2 + 1 % 3) + 1))); + assertThat(mCompactorUnderTest.mCompactThrottleSomeSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1)); + assertThat(mCompactorUnderTest.mCompactThrottleFullSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1)); + assertThat(mCompactorUnderTest.mCompactThrottleFullFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1)); + } + + @Test + public void useCompaction_listensToDeviceConfigChanges() throws InterruptedException { + assertThat(mCompactorUnderTest.useCompaction(), + is(mCompactorUnderTest.DEFAULT_USE_COMPACTION)); + // When we call init and change some the flag value... + mCompactorUnderTest.init(); + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_USE_COMPACTION, "true", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + + // Then that new flag value is updated in the implementation. + assertThat(mCompactorUnderTest.useCompaction(), is(true)); + assertThat(mCompactorUnderTest.mCompactionThread.isAlive(), is(true)); + + // And again, setting the flag the other way. + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_USE_COMPACTION, "false", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + assertThat(mCompactorUnderTest.useCompaction(), is(false)); + } + + @Test + public void useCompaction_listensToDeviceConfigChangesBadValues() throws InterruptedException { + assertThat(mCompactorUnderTest.useCompaction(), + is(mCompactorUnderTest.DEFAULT_USE_COMPACTION)); + mCompactorUnderTest.init(); + + // When we push an invalid flag value... + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_USE_COMPACTION, "foobar", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + + // Then we set the default. + assertThat(mCompactorUnderTest.useCompaction(), is(AppCompactor.DEFAULT_USE_COMPACTION)); + } + + @Test + public void compactAction_listensToDeviceConfigChanges() throws InterruptedException { + mCompactorUnderTest.init(); + + // When we override new values for the compaction action with reasonable values... + + // There are three possible values for compactAction[Some|Full]. + for (int i = 1; i < 4; i++) { + mCountDown = new CountDownLatch(2); + int expectedSome = (mCompactorUnderTest.DEFAULT_COMPACT_ACTION_1 + i) % 3 + 1; + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_1, Integer.toString(expectedSome), false); + int expectedFull = (mCompactorUnderTest.DEFAULT_COMPACT_ACTION_2 + i) % 3 + 1; + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_2, Integer.toString(expectedFull), false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + + // Then the updates are reflected in the flags. + assertThat(mCompactorUnderTest.mCompactActionSome, + is(compactActionIntToString(expectedSome))); + assertThat(mCompactorUnderTest.mCompactActionFull, + is(compactActionIntToString(expectedFull))); + } + } + + @Test + public void compactAction_listensToDeviceConfigChangesBadValues() throws InterruptedException { + mCompactorUnderTest.init(); + + // When we override new values for the compaction action with bad values ... + mCountDown = new CountDownLatch(2); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_1, "foo", false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_2, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + + // Then the default values are reflected in the flag + assertThat(mCompactorUnderTest.mCompactActionSome, + is(compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1))); + assertThat(mCompactorUnderTest.mCompactActionFull, + is(compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2))); + + mCountDown = new CountDownLatch(2); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_1, "", false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_2, "", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + + assertThat(mCompactorUnderTest.mCompactActionSome, + is(compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1))); + assertThat(mCompactorUnderTest.mCompactActionFull, + is(compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2))); + } + + @Test + public void compactThrottle_listensToDeviceConfigChanges() throws InterruptedException { + mCompactorUnderTest.init(); + + // When we override new reasonable throttle values after init... + mCountDown = new CountDownLatch(4); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_1, + Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1), false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_2, + Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1), false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_3, + Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1), false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_4, + Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1), false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + + // Then those flags values are reflected in the compactor. + assertThat(mCompactorUnderTest.mCompactThrottleSomeSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1)); + assertThat(mCompactorUnderTest.mCompactThrottleFullSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1)); + assertThat(mCompactorUnderTest.mCompactThrottleFullFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1)); + } + + @Test + public void compactThrottle_listensToDeviceConfigChangesBadValues() + throws IOException, InterruptedException { + mCompactorUnderTest.init(); + + // When one of the throttles is overridden with a bad value... + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_1, "foo", false); + // Then all the throttles have the defaults set. + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2)); + assertThat(mCompactorUnderTest.mCompactThrottleFullSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3)); + assertThat(mCompactorUnderTest.mCompactThrottleFullFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4)); + clearDeviceConfig(); + + // Repeat for each of the throttle keys. + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_2, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2)); + assertThat(mCompactorUnderTest.mCompactThrottleFullSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3)); + assertThat(mCompactorUnderTest.mCompactThrottleFullFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4)); + clearDeviceConfig(); + + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_3, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2)); + assertThat(mCompactorUnderTest.mCompactThrottleFullSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3)); + assertThat(mCompactorUnderTest.mCompactThrottleFullFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4)); + clearDeviceConfig(); + + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_4, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2)); + assertThat(mCompactorUnderTest.mCompactThrottleFullSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3)); + assertThat(mCompactorUnderTest.mCompactThrottleFullFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4)); + } + + private class TestInjector extends Injector { + @Override + public AppOpsService getAppOpsService(File file, Handler handler) { + return mAppOpsService; + } + + @Override + public Handler getUiHandler(ActivityManagerService service) { + return mHandler; + } + } +} -- 2.11.0