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";
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<OnPropertyChangedListener, Pair<String, Executor>> sListeners =
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;
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";
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;
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;
// 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;
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) {
updateConstants();
updateActivityStartsLoggingEnabled();
updateBackgroundActivityStartsEnabled();
+ DeviceConfig.addOnPropertyChangedListener(DeviceConfig.ActivityManager.NAMESPACE,
+ ActivityThread.currentApplication().getMainExecutor(),
+ mOnDeviceConfigChangedListener);
+ updateMaxCachedProcesses();
+
}
public void setOverrideMaxCachedProcesses(int value) {
// 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,
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();
}
}
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
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) {
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.
synchronized(this) {
mConstants.dump(pw);
+ mOomAdjuster.dumpAppCompactorSettings(pw);
pw.println();
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
} else if ("settings".equals(cmd)) {
synchronized (this) {
mConstants.dump(pw);
+ mOomAdjuster.dumpAppCompactorSettings(pw);
}
} else if ("services".equals(cmd) || "s".equals(cmd)) {
if (dumpClient) {
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<ProcessRecord> mPendingCompactionProcesses = new ArrayList<ProcessRecord>();
+ // 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;
*/
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<ProcessRecord> mPendingCompactionProcesses =
+ new ArrayList<ProcessRecord>();
+ 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(
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(
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);
}
}
- }
}
}
-
-
}
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();
}
/**
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
+ " mNewNumServiceProcs=" + mNewNumServiceProcs);
}
+ @GuardedBy("mService")
+ void dumpAppCompactorSettings(PrintWriter pw) {
+ mAppCompact.dump(pw);
+ }
}
--- /dev/null
+/*
+ * 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;
+ }
+ }
+}