/*
 * Copyright (C) 2018 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.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ALLOWLIST;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BATCH_UPDATE_REQUEST;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FOLLOW_UP;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RECONFIGURATION;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SERVICE_BINDER_CALL;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE;
import static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;

import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_COMPACTION;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FREEZER;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;

import android.annotation.IntDef;
import android.annotation.UptimeMillisLong;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal.FrozenProcessListener;
import android.app.ActivityManagerInternal.OomAdjReason;
import android.app.ActivityThread;
import android.app.ApplicationExitInfo;
import android.app.ApplicationExitInfo.Reason;
import android.app.ApplicationExitInfo.SubReason;
import android.app.IApplicationThread;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManagerInternal;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.OnPropertiesChangedListener;
import android.provider.DeviceConfig.Properties;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.EventLog;
import android.util.IntArray;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BinderfsStatsReader;
import com.android.internal.os.ProcLocksReader;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.ServiceThread;
import com.android.server.am.compaction.CompactionStatsManager;
import com.android.server.am.compaction.SingleCompactionStats;

import dalvik.annotation.optimization.NeverCompile;

import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.Executor;

public class CachedAppOptimizer {

    // Flags stored in the DeviceConfig API.
    @VisibleForTesting static final String KEY_USE_COMPACTION = "use_compaction";
    @VisibleForTesting static final String KEY_USE_FREEZER = "use_freezer";
    @VisibleForTesting static final String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1";
    @VisibleForTesting static final String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2";
    @VisibleForTesting static final String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3";
    @VisibleForTesting static final String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4";
    @VisibleForTesting static final String KEY_COMPACT_THROTTLE_5 = "compact_throttle_5";
    @VisibleForTesting static final String KEY_COMPACT_THROTTLE_6 = "compact_throttle_6";
    @VisibleForTesting static final String KEY_COMPACT_THROTTLE_MIN_OOM_ADJ =
            "compact_throttle_min_oom_adj";
    @VisibleForTesting static final String KEY_COMPACT_THROTTLE_MAX_OOM_ADJ =
            "compact_throttle_max_oom_adj";
    @VisibleForTesting static final String KEY_COMPACT_STATSD_SAMPLE_RATE =
            "compact_statsd_sample_rate";
    @VisibleForTesting static final String KEY_FREEZER_STATSD_SAMPLE_RATE =
            "freeze_statsd_sample_rate";
    @VisibleForTesting static final String KEY_COMPACT_FULL_RSS_THROTTLE_KB =
            "compact_full_rss_throttle_kb";
    @VisibleForTesting static final String KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB =
            "compact_full_delta_rss_throttle_kb";
    @VisibleForTesting static final String KEY_COMPACT_PROC_STATE_THROTTLE =
            "compact_proc_state_throttle";
    @VisibleForTesting static final String KEY_FREEZER_DEBOUNCE_TIMEOUT =
            "freeze_debounce_timeout";
    @VisibleForTesting static final String KEY_FREEZER_EXEMPT_INST_PKG =
            "freeze_exempt_inst_pkg";
    @VisibleForTesting static final String KEY_FREEZER_BINDER_ENABLED =
            "freeze_binder_enabled";
    @VisibleForTesting static final String KEY_FREEZER_BINDER_DIVISOR =
            "freeze_binder_divisor";
    @VisibleForTesting static final String KEY_FREEZER_BINDER_OFFSET =
            "freeze_binder_offset";
    @VisibleForTesting static final String KEY_FREEZER_BINDER_THRESHOLD =
            "freeze_binder_threshold";
    @VisibleForTesting static final String KEY_FREEZER_BINDER_CALLBACK_ENABLED =
            "freeze_binder_callback_enabled";
    @VisibleForTesting static final String KEY_FREEZER_BINDER_CALLBACK_THROTTLE =
            "freeze_binder_callback_throttle";
    @VisibleForTesting static final String KEY_FREEZER_BINDER_ASYNC_THRESHOLD =
            "freeze_binder_async_threshold";

    static final int UNFREEZE_REASON_NONE =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_NONE;
    static final int UNFREEZE_REASON_ACTIVITY =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_ACTIVITY;
    static final int UNFREEZE_REASON_FINISH_RECEIVER =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_FINISH_RECEIVER;
    static final int UNFREEZE_REASON_START_RECEIVER =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_START_RECEIVER;
    static final int UNFREEZE_REASON_BIND_SERVICE =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_BIND_SERVICE;
    static final int UNFREEZE_REASON_UNBIND_SERVICE =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_UNBIND_SERVICE;
    static final int UNFREEZE_REASON_START_SERVICE =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_START_SERVICE;
    static final int UNFREEZE_REASON_GET_PROVIDER =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_GET_PROVIDER;
    static final int UNFREEZE_REASON_REMOVE_PROVIDER =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_REMOVE_PROVIDER;
    static final int UNFREEZE_REASON_UI_VISIBILITY =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_UI_VISIBILITY;
    static final int UNFREEZE_REASON_ALLOWLIST =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_ALLOWLIST;
    static final int UNFREEZE_REASON_PROCESS_BEGIN =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_PROCESS_BEGIN;
    static final int UNFREEZE_REASON_PROCESS_END =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_PROCESS_END;
    static final int UNFREEZE_REASON_TRIM_MEMORY =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_TRIM_MEMORY;
    static final int UNFREEZE_REASON_PING =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_PING;
    static final int UNFREEZE_REASON_FILE_LOCKS =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_FILE_LOCKS;
    static final int UNFREEZE_REASON_FILE_LOCK_CHECK_FAILURE =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_FILE_LOCK_CHECK_FAILURE;
    static final int UNFREEZE_REASON_BINDER_TXNS =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_BINDER_TXNS;
    static final int UNFREEZE_REASON_FEATURE_FLAGS =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_FEATURE_FLAGS;
    static final int UNFREEZE_REASON_SHORT_FGS_TIMEOUT =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SHORT_FGS_TIMEOUT;
    static final int UNFREEZE_REASON_SYSTEM_INIT =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SYSTEM_INIT;
    static final int UNFREEZE_REASON_BACKUP =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_BACKUP;
    static final int UNFREEZE_REASON_SHELL =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SHELL;
    static final int UNFREEZE_REASON_REMOVE_TASK =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_REMOVE_TASK;
    static final int UNFREEZE_REASON_UID_IDLE =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_UID_IDLE;
    static final int UNFREEZE_REASON_STOP_SERVICE =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_STOP_SERVICE;
    static final int UNFREEZE_REASON_EXECUTING_SERVICE =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_EXECUTING_SERVICE;
    static final int UNFREEZE_REASON_RESTRICTION_CHANGE =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_RESTRICTION_CHANGE;
    static final int UNFREEZE_REASON_COMPONENT_DISABLED =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_COMPONENT_DISABLED;
    static final int UNFREEZE_REASON_OOM_ADJ_FOLLOW_UP =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_OOM_ADJ_FOLLOW_UP;
    static final int UNFREEZE_REASON_OOM_ADJ_RECONFIGURATION =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_OOM_ADJ_RECONFIGURATION;
    static final int UNFREEZE_REASON_OOM_ADJ_SERVICE_BINDER_CALL = FrameworkStatsLog
            .APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_OOM_ADJ_REASON_SERVICE_BINDER_CALL;
    static final int UNFREEZE_REASON_OOM_ADJ_BATCH_UPDATE_REQUEST = FrameworkStatsLog
            .APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_OOM_ADJ_REASON_BATCH_UPDATE_REQUEST;

    @IntDef(prefix = {"UNFREEZE_REASON_"}, value = {
        UNFREEZE_REASON_NONE,
        UNFREEZE_REASON_ACTIVITY,
        UNFREEZE_REASON_FINISH_RECEIVER,
        UNFREEZE_REASON_START_RECEIVER,
        UNFREEZE_REASON_BIND_SERVICE,
        UNFREEZE_REASON_UNBIND_SERVICE,
        UNFREEZE_REASON_START_SERVICE,
        UNFREEZE_REASON_GET_PROVIDER,
        UNFREEZE_REASON_REMOVE_PROVIDER,
        UNFREEZE_REASON_UI_VISIBILITY,
        UNFREEZE_REASON_ALLOWLIST,
        UNFREEZE_REASON_PROCESS_BEGIN,
        UNFREEZE_REASON_PROCESS_END,
        UNFREEZE_REASON_TRIM_MEMORY,
        UNFREEZE_REASON_PING,
        UNFREEZE_REASON_FILE_LOCKS,
        UNFREEZE_REASON_FILE_LOCK_CHECK_FAILURE,
        UNFREEZE_REASON_BINDER_TXNS,
        UNFREEZE_REASON_FEATURE_FLAGS,
        UNFREEZE_REASON_SHORT_FGS_TIMEOUT,
        UNFREEZE_REASON_SYSTEM_INIT,
        UNFREEZE_REASON_BACKUP,
        UNFREEZE_REASON_SHELL,
        UNFREEZE_REASON_REMOVE_TASK,
        UNFREEZE_REASON_UID_IDLE,
        UNFREEZE_REASON_STOP_SERVICE,
        UNFREEZE_REASON_EXECUTING_SERVICE,
        UNFREEZE_REASON_RESTRICTION_CHANGE,
        UNFREEZE_REASON_COMPONENT_DISABLED,
        UNFREEZE_REASON_OOM_ADJ_FOLLOW_UP,
        UNFREEZE_REASON_OOM_ADJ_RECONFIGURATION,
        UNFREEZE_REASON_OOM_ADJ_SERVICE_BINDER_CALL,
        UNFREEZE_REASON_OOM_ADJ_BATCH_UPDATE_REQUEST,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface UnfreezeReason {}

    // RSS Indices
    private static final int RSS_TOTAL_INDEX = 0;
    private static final int RSS_FILE_INDEX = 1;
    private static final int RSS_ANON_INDEX = 2;
    private static final int RSS_SWAP_INDEX = 3;

    // Keeps these flags in sync with services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
    private static final int COMPACT_ACTION_FILE_FLAG = 1;
    private static final int COMPACT_ACTION_ANON_FLAG = 2;

    private static final String ATRACE_COMPACTION_TRACK = "Compaction";
    public static final String ATRACE_FREEZER_TRACK = "Freezer";

    private static final int FREEZE_BINDER_TIMEOUT_MS = 0;
    private static final int FREEZE_DEADLOCK_TIMEOUT_MS = 1000;

    // If enabled, any compaction issued will apply to code mappings and share memory mappings.
    @VisibleForTesting static final boolean ENABLE_SHARED_AND_CODE_COMPACT = false;

    // Defaults for phenotype flags.
    @VisibleForTesting static final boolean DEFAULT_USE_COMPACTION = true;
    @VisibleForTesting static final boolean DEFAULT_USE_FREEZER = true;
    @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 static final long DEFAULT_COMPACT_THROTTLE_5 = 10 * 60 * 1000;
    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_6 = 10 * 60 * 1000;
    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ =
            ProcessList.CACHED_APP_MIN_ADJ;
    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_MAX_OOM_ADJ =
            ProcessList.CACHED_APP_MAX_ADJ;
    // The sampling rate to push app compaction events into statsd for upload.
    @VisibleForTesting static final float DEFAULT_STATSD_SAMPLE_RATE = 0.1f;
    @VisibleForTesting static final long DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB = 12_000L;
    @VisibleForTesting static final long DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB = 8_000L;
    // Format of this string should be a comma separated list of integers.
    @VisibleForTesting static final String DEFAULT_COMPACT_PROC_STATE_THROTTLE =
            String.valueOf(ActivityManager.PROCESS_STATE_RECEIVER);
    // The debounce timeout is a grace period for apps that run background cleanup operations
    // outside their valid lifecycles.  A too-aggressive value (that is, a too-low value) may
    // freeze these apps before the operations complete.  The default has been found to work on
    // phones.  (A lower default has been found to work on Wear).  However, once these apps have
    // been corrected to honor their valid lifecycles, this debounce default may be lowerered or
    // set to zero.
    @VisibleForTesting static final long DEFAULT_FREEZER_DEBOUNCE_TIMEOUT = 10_000L;
    @VisibleForTesting static final boolean DEFAULT_FREEZER_EXEMPT_INST_PKG = false;
    @VisibleForTesting static final boolean DEFAULT_FREEZER_BINDER_ENABLED = true;
    @VisibleForTesting static final long DEFAULT_FREEZER_BINDER_DIVISOR = 4;
    @VisibleForTesting static final int DEFAULT_FREEZER_BINDER_OFFSET = 500;
    @VisibleForTesting static final long DEFAULT_FREEZER_BINDER_THRESHOLD = 1_000;
    @VisibleForTesting static final boolean DEFAULT_FREEZER_BINDER_CALLBACK_ENABLED = true;
    @VisibleForTesting static final long DEFAULT_FREEZER_BINDER_CALLBACK_THROTTLE = 10_000L;
    @VisibleForTesting static final int DEFAULT_FREEZER_BINDER_ASYNC_THRESHOLD = 1_024;

    @VisibleForTesting static final Uri CACHED_APP_FREEZER_ENABLED_URI = Settings.Global.getUriFor(
                Settings.Global.CACHED_APPS_FREEZER_ENABLED);

    @VisibleForTesting
    interface PropertyChangedCallbackForTest {
        void onPropertyChanged();
    }
    private PropertyChangedCallbackForTest mTestCallback;

    // This interface is for functions related to the Process object that need a different
    // implementation in the tests as we are not creating real processes when testing compaction.
    @VisibleForTesting
    interface ProcessDependencies {
        long[] getRss(int pid);
        void performCompaction(CompactProfile action, int pid) throws IOException; // deprecated
        void performMemcgCompaction(CompactProfile action, int uid, int pid) throws IOException;
        void performNativeCompaction(CompactProfile action, int pid) throws IOException;
    }

    // This indicates the compaction we want to perform
    public enum CompactProfile {
        NONE, // No compaction
        SOME, // File compaction
        ANON, // Anon compaction
        FULL // File+anon compaction
    }

    // This indicates who initiated the compaction request
    public enum CompactSource { APP, SHELL }

    public enum CancelCompactReason {
        SCREEN_ON, // screen was turned on which cancels all compactions.
        OOM_IMPROVEMENT // process moved out of cached state and into a more perceptible state.
    }

    // Handler constants.
    static final int COMPACT_PROCESS_MSG = 1;
    static final int COMPACT_SYSTEM_MSG = 2;
    static final int SET_FROZEN_PROCESS_MSG = 3;
    static final int REPORT_UNFREEZE_MSG = 4;
    static final int COMPACT_NATIVE_MSG = 5;
    static final int UID_FROZEN_STATE_CHANGED_MSG = 6;
    static final int DEADLOCK_WATCHDOG_MSG = 7;
    static final int BINDER_ERROR_MSG = 8;

    // When free swap falls below this percentage threshold any full (file + anon)
    // compactions will be downgraded to file only compactions to reduce pressure
    // on swap resources as file.
    static final double COMPACT_DOWNGRADE_FREE_SWAP_THRESHOLD = 0.2;

    static final int DO_FREEZE = 1;
    static final int REPORT_UNFREEZE = 2;

    // Bitfield values for sync/async transactions reveived by frozen processes
    static final int SYNC_RECEIVED_WHILE_FROZEN = 1;
    static final int ASYNC_RECEIVED_WHILE_FROZEN = 2;

    // Bitfield values for sync transactions received by frozen binder threads
    static final int TXNS_PENDING_WHILE_FROZEN = 4;

    /**
     * This thread must be moved to the system background cpuset.
     * If that doesn't happen, it's probably going to draw a lot of power.
     * However, this has to happen after the first updateOomAdjLocked, because
     * that will wipe out the cpuset assignment for system_server threads.
     * Accordingly, this is in the AMS constructor.
     */
    final ServiceThread mCachedAppOptimizerThread;

    @GuardedBy("mProcLock")
    private final ArrayList<ProcessRecord> mPendingCompactionProcesses =
            new ArrayList<ProcessRecord>();

    @GuardedBy("mProcLock")
    private final SparseArray<ProcessRecord> mFrozenProcesses =
            new SparseArray<>();

    private final ActivityManagerService mAm;

    private final ActivityManagerGlobalLock mProcLock;

    public final Object mFreezerLock = new Object();

    private final OnPropertiesChangedListener mOnFlagsChangedListener =
            new OnPropertiesChangedListener() {
                @Override
                public void onPropertiesChanged(Properties properties) {
                    synchronized (mPhenotypeFlagLock) {
                        for (String name : properties.getKeyset()) {
                            if (KEY_USE_COMPACTION.equals(name)) {
                                updateUseCompaction();
                            } 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)
                                    || KEY_COMPACT_THROTTLE_5.equals(name)
                                    || KEY_COMPACT_THROTTLE_6.equals(name)) {
                                updateCompactionThrottles();
                            } else if (KEY_COMPACT_STATSD_SAMPLE_RATE.equals(name)) {
                                updateCompactStatsdSampleRate();
                            } else if (KEY_FREEZER_STATSD_SAMPLE_RATE.equals(name)) {
                                updateFreezerStatsdSampleRate();
                            } else if (KEY_COMPACT_FULL_RSS_THROTTLE_KB.equals(name)) {
                                updateFullRssThrottle();
                            } else if (KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB.equals(name)) {
                                updateFullDeltaRssThrottle();
                            } else if (KEY_COMPACT_PROC_STATE_THROTTLE.equals(name)) {
                                updateProcStateThrottle();
                            } else if (KEY_COMPACT_THROTTLE_MIN_OOM_ADJ.equals(name)) {
                                updateMinOomAdjThrottle();
                            } else if (KEY_COMPACT_THROTTLE_MAX_OOM_ADJ.equals(name)) {
                                updateMaxOomAdjThrottle();
                            }
                        }
                    }
                    if (mTestCallback != null) {
                        mTestCallback.onPropertyChanged();
                    }
                }
            };

    private final OnPropertiesChangedListener mOnNativeBootFlagsChangedListener =
            new OnPropertiesChangedListener() {
                @Override
                public void onPropertiesChanged(Properties properties) {
                    synchronized (mPhenotypeFlagLock) {
                        for (String name : properties.getKeyset()) {
                            if (KEY_FREEZER_DEBOUNCE_TIMEOUT.equals(name)) {
                                updateFreezerDebounceTimeout();
                            } else if (KEY_FREEZER_EXEMPT_INST_PKG.equals(name)) {
                                updateFreezerExemptInstPkg();
                            } else if (KEY_FREEZER_BINDER_ENABLED.equals(name)
                                    || KEY_FREEZER_BINDER_DIVISOR.equals(name)
                                    || KEY_FREEZER_BINDER_THRESHOLD.equals(name)
                                    || KEY_FREEZER_BINDER_OFFSET.equals(name)
                                    || KEY_FREEZER_BINDER_CALLBACK_ENABLED.equals(name)
                                    || KEY_FREEZER_BINDER_CALLBACK_THROTTLE.equals(name)
                                    || KEY_FREEZER_BINDER_ASYNC_THRESHOLD.equals(name)) {
                                updateFreezerBinderState();
                            }
                        }
                    }
                    if (mTestCallback != null) {
                        mTestCallback.onPropertyChanged();
                    }
                }
            };

    private final class SettingsContentObserver extends ContentObserver {
        SettingsContentObserver() {
            super(mAm.mHandler);
        }

        @Override
        public void onChange(boolean selfChange, Uri uri) {
            if (CACHED_APP_FREEZER_ENABLED_URI.equals(uri)) {
                synchronized (mPhenotypeFlagLock) {
                    updateUseFreezer();
                }
            }
        }
    }

    private final SettingsContentObserver mSettingsObserver;

    @VisibleForTesting
    final Object mPhenotypeFlagLock = new Object();

    // Configured by phenotype. Updates from the server take effect immediately.
    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting volatile long mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1;
    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting volatile long mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2;
    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting volatile long mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3;
    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting volatile long mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4;
    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting volatile long mCompactThrottleMinOomAdj =
            DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ;
    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting volatile long mCompactThrottleMaxOomAdj =
            DEFAULT_COMPACT_THROTTLE_MAX_OOM_ADJ;
    @GuardedBy("mPhenotypeFlagLock")
    private volatile boolean mUseCompaction = DEFAULT_USE_COMPACTION;
    private volatile boolean mUseFreezer = false; // set to DEFAULT in init()
    @GuardedBy("this")
    private int mFreezerDisableCount = 1; // Freezer is initially disabled, until enabled
    private final Random mRandom = new Random();
    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting volatile float mCompactStatsdSampleRate = DEFAULT_STATSD_SAMPLE_RATE;
    @VisibleForTesting volatile float mFreezerStatsdSampleRate = DEFAULT_STATSD_SAMPLE_RATE;
    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting volatile long mFullAnonRssThrottleKb =
            DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB;
    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting volatile long mFullDeltaRssThrottleKb =
            DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB;
    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting final Set<Integer> mProcStateThrottle;

    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting volatile boolean mFreezerBinderEnabled = DEFAULT_FREEZER_BINDER_ENABLED;
    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting volatile long mFreezerBinderDivisor = DEFAULT_FREEZER_BINDER_DIVISOR;
    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting volatile int mFreezerBinderOffset = DEFAULT_FREEZER_BINDER_OFFSET;
    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting volatile long mFreezerBinderThreshold = DEFAULT_FREEZER_BINDER_THRESHOLD;
    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting volatile boolean mFreezerBinderCallbackEnabled =
            DEFAULT_FREEZER_BINDER_CALLBACK_ENABLED;
    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting volatile long mFreezerBinderCallbackThrottle =
            DEFAULT_FREEZER_BINDER_CALLBACK_THROTTLE;
    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting volatile int mFreezerBinderAsyncThreshold =
            DEFAULT_FREEZER_BINDER_ASYNC_THRESHOLD;

    // Handler on which compaction runs.
    @VisibleForTesting
    Handler mCompactionHandler;
    @VisibleForTesting
    CompactionStatsManager mCompactStatsManager;

    private Handler mFreezeHandler;
    @GuardedBy("mProcLock")
    private boolean mFreezerOverride = false;
    private long mFreezerBinderCallbackLast = -1;

    @VisibleForTesting volatile long mFreezerDebounceTimeout = DEFAULT_FREEZER_DEBOUNCE_TIMEOUT;
    @VisibleForTesting volatile boolean mFreezerExemptInstPkg = DEFAULT_FREEZER_EXEMPT_INST_PKG;

    private final ProcessDependencies mProcessDependencies;
    private final ProcLocksReader mProcLocksReader;

    private final Freezer mFreezer;

    public CachedAppOptimizer(ActivityManagerService am) {
        this(am, null, new DefaultProcessDependencies());
    }

    @VisibleForTesting
    CachedAppOptimizer(ActivityManagerService am, PropertyChangedCallbackForTest callback,
            ProcessDependencies processDependencies) {
        mAm = am;
        mProcLock = am.mProcLock;
        mCachedAppOptimizerThread = new ServiceThread("CachedAppOptimizerThread",
            Process.THREAD_GROUP_SYSTEM, true);
        mProcStateThrottle = new HashSet<>();
        mProcessDependencies = processDependencies;
        mTestCallback = callback;
        mSettingsObserver = new SettingsContentObserver();
        mProcLocksReader = new ProcLocksReader();
        mFreezer = mAm.getFreezer();
    }

    /**
     * Reads phenotype config to determine whether app compaction is enabled or not and
     * starts the background thread if necessary.
     */
    public void init() {
        // TODO: initialize flags to default and only update them if values are set in DeviceConfig
        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                ActivityThread.currentApplication().getMainExecutor(), mOnFlagsChangedListener);
        DeviceConfig.addOnPropertiesChangedListener(
                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
                ActivityThread.currentApplication().getMainExecutor(),
                mOnNativeBootFlagsChangedListener);
        mAm.mContext.getContentResolver().registerContentObserver(
                CACHED_APP_FREEZER_ENABLED_URI, false, mSettingsObserver);
        synchronized (mPhenotypeFlagLock) {
            updateUseCompaction();
            updateCompactionThrottles();
            updateCompactStatsdSampleRate();
            updateFreezerStatsdSampleRate();
            updateFullRssThrottle();
            updateFullDeltaRssThrottle();
            updateProcStateThrottle();
            updateUseFreezer();
            updateMinOomAdjThrottle();
            updateMaxOomAdjThrottle();
        }
    }

    /**
     * Returns whether compaction is enabled.
     */
    public boolean useCompaction() {
        synchronized (mPhenotypeFlagLock) {
            return mUseCompaction;
        }
    }

    /**
     * Returns whether freezer is enabled.
     */
    public boolean useFreezer() {
        synchronized (mPhenotypeFlagLock) {
            return mUseFreezer;
        }
    }

    @NeverCompile
    void dump(PrintWriter pw) {
        dumpCompact(pw);
        dumpFreezer(pw);
    }

    @NeverCompile
    void dumpCompact(PrintWriter pw) {
        pw.println("Compaction settings");
        synchronized (mPhenotypeFlagLock) {
            pw.println("  " + KEY_USE_COMPACTION + "=" + mUseCompaction);
            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);
            pw.println("  " + KEY_COMPACT_THROTTLE_MIN_OOM_ADJ + "=" + mCompactThrottleMinOomAdj);
            pw.println("  " + KEY_COMPACT_THROTTLE_MAX_OOM_ADJ + "=" + mCompactThrottleMaxOomAdj);
            pw.println("  " + KEY_COMPACT_STATSD_SAMPLE_RATE + "=" + mCompactStatsdSampleRate);
            pw.println("  " + KEY_COMPACT_FULL_RSS_THROTTLE_KB + "="
                    + mFullAnonRssThrottleKb);
            pw.println("  " + KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + "="
                    + mFullDeltaRssThrottleKb);
            pw.println("  " + KEY_COMPACT_PROC_STATE_THROTTLE + "="
                    + Arrays.toString(mProcStateThrottle.toArray(new Integer[0])));
        }

        if (mCompactStatsManager != null) {
            // Compaction stats manager only exists when compaction is enabled.
            mCompactStatsManager.dump(pw);
        }

        synchronized (mProcLock) {
            if (!mPendingCompactionProcesses.isEmpty()) {
                pw.println("  Pending compactions:");
                int size = mPendingCompactionProcesses.size();
                for (int i = 0; i < size; i++) {
                    ProcessRecord app = mPendingCompactionProcesses.get(i);
                    pw.println("    pid: " + app.getPid() + ". name: " + app.processName
                            + ". hasPendingCompact: " + app.mOptRecord.hasPendingCompact());
                }
            }
        }
        pw.println();
    }

    @NeverCompile
    void dumpFreezer(PrintWriter pw) {
        pw.println("Freezer settings");
        synchronized (mPhenotypeFlagLock) {
            pw.println("  " + KEY_USE_FREEZER + "=" + mUseFreezer);
            pw.println("  " + KEY_FREEZER_STATSD_SAMPLE_RATE + "=" + mFreezerStatsdSampleRate);
            pw.println("  " + KEY_FREEZER_DEBOUNCE_TIMEOUT + "=" + mFreezerDebounceTimeout);
            pw.println("  " + KEY_FREEZER_EXEMPT_INST_PKG + "=" + mFreezerExemptInstPkg);
            pw.println("  " + KEY_FREEZER_BINDER_ENABLED + "=" + mFreezerBinderEnabled);
            pw.println("  " + KEY_FREEZER_BINDER_THRESHOLD + "=" + mFreezerBinderThreshold);
            pw.println("  " + KEY_FREEZER_BINDER_DIVISOR + "=" + mFreezerBinderDivisor);
            pw.println("  " + KEY_FREEZER_BINDER_OFFSET + "=" + mFreezerBinderOffset);
            pw.println("  " + KEY_FREEZER_BINDER_CALLBACK_ENABLED + "="
                    + mFreezerBinderCallbackEnabled);
            pw.println("  " + KEY_FREEZER_BINDER_CALLBACK_THROTTLE + "="
                    + mFreezerBinderCallbackThrottle);
            pw.println("  " + KEY_FREEZER_BINDER_ASYNC_THRESHOLD + "="
                    + mFreezerBinderAsyncThreshold);
            synchronized (mProcLock) {
                int size = mFrozenProcesses.size();
                pw.println("  Apps frozen: " + size);
                for (int i = 0; i < size; i++) {
                    ProcessRecord app = mFrozenProcesses.valueAt(i);
                    pw.println("    " + app.mOptRecord.getFreezeUnfreezeTime() + ": " + app.getPid()
                            + " " + app.processName
                            + (app.mOptRecord.isFreezeSticky() ? " (sticky)" : ""));
                }
            }
        }
    }

    @GuardedBy("mProcLock")
    boolean compactApp(
            ProcessRecord app, CompactProfile compactProfile, CompactSource source, boolean force) {
        app.mOptRecord.setReqCompactSource(source);
        app.mOptRecord.setReqCompactProfile(compactProfile);

        if(compactProfile == null || compactProfile.equals(CompactProfile.NONE)) {
            return false;
        }
        if (mCompactionHandler == null) {
            return false;
        }
        final String processName = (app.processName != null ? app.processName : "");
        mCompactStatsManager.logCompactionRequested(source, compactProfile, processName);

        if (!app.mOptRecord.hasPendingCompact()) {
            if (DEBUG_COMPACTION) {
                Slog.d(TAG_AM,
                        "compactApp " + app.mOptRecord.getReqCompactSource().name() + " "
                                + app.mOptRecord.getReqCompactProfile().name() + " " + processName);
            }
            app.mOptRecord.setHasPendingCompact(true);
            app.mOptRecord.setForceCompact(force);
            mPendingCompactionProcesses.add(app);
            mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
                    COMPACT_PROCESS_MSG, app.getCurAdj(), app.getSetProcState()));
            return true;
        }

        if (DEBUG_COMPACTION) {
            Slog.d(TAG_AM,
                    " compactApp Skipped for " + app.processName + " pendingCompact= "
                            + app.mOptRecord.hasPendingCompact() + ". Requested compact profile: "
                            + app.mOptRecord.getReqCompactProfile().name() + ". Compact source "
                            + app.mOptRecord.getReqCompactSource().name());
        }
        return false;
    }

    void compactNative(CompactProfile compactProfile, int pid) {
        if (useCompaction()) {
            mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
                    COMPACT_NATIVE_MSG, pid, compactProfile.ordinal()));
        }
    }

    void compactAllSystem() {
        if (useCompaction()) {
            if (DEBUG_COMPACTION) {
                Slog.d(TAG_AM, "compactAllSystem");
            }
            Trace.instantForTrack(
                    Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK, "compactAllSystem");
            mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
                                              COMPACT_SYSTEM_MSG));
        }
    }

    private native void compactSystem();

    /**
     * Compacts a process or app
     * @param pid pid of process to compact
     * @param compactionFlags selects the compaction type as defined by COMPACT_ACTION_{TYPE}_FLAG
     *         constants
     */
    private static native void compactProcess(int pid, int compactionFlags);
    private static native void performNativeMemcgCompaction(int uid, int pid, int compactionFlags);
    private static native void compactNativeProcess(int pid, int compactionFlags);
    private static native boolean compactionFlagsValidForMemcg(int compactionFlags);

    static private native void cancelCompaction();

    /**
     * Returns the cpu time for the current thread
     */
    static private native long threadCpuTimeNs();

    /**
     * Retrieves the free swap percentage.
     */
    static native double getFreeSwapPercent();

    /**
     * Retrieves the total used physical ZRAM
     */
    static private native long getUsedZramMemory();

    /**
     * Amount of memory that has been made free due to compaction.
     * It represents uncompressed memory size - compressed memory size.
     */
    static private native long getMemoryFreedCompaction();

    /**
     * Reads the flag value from DeviceConfig to determine whether app compaction
     * should be enabled, and starts the freeze/compaction thread if needed.
     */
    @GuardedBy("mPhenotypeFlagLock")
    private void updateUseCompaction() {
        mUseCompaction = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                    KEY_USE_COMPACTION, DEFAULT_USE_COMPACTION);

        if (mUseCompaction && mCompactionHandler == null) {
            if (!mCachedAppOptimizerThread.isAlive()) {
                mCachedAppOptimizerThread.start();
            }

            mCompactionHandler = new MemCompactionHandler();
            mCompactStatsManager = CompactionStatsManager.getInstance();

            Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(),
                    Process.THREAD_GROUP_SYSTEM);
        }
    }

    /**
     * Enables or disabled the app freezer.
     * @param enable Enables the freezer if true, disables it if false.
     * @return true if the operation completed successfully, false otherwise.
     */
    public synchronized boolean enableFreezer(boolean enable) {
        if (!mUseFreezer) {
            return false;
        }

        if (enable) {
            mFreezerDisableCount--;

            if (mFreezerDisableCount > 0) {
                return true;
            } else if (mFreezerDisableCount < 0) {
                Slog.e(TAG_AM, "unbalanced call to enableFreezer, ignoring");
                mFreezerDisableCount = 0;
                return false;
            }
        } else {
            mFreezerDisableCount++;

            if (mFreezerDisableCount > 1) {
                return true;
            }
        }

        // Override is applied immediately, restore is delayed
        synchronized (mAm) {
            synchronized (mProcLock) {
                mFreezerOverride = !enable;
                Slog.d(TAG_AM, "freezer override set to " + mFreezerOverride);

                mAm.mProcessList.forEachLruProcessesLOSP(true, process -> {
                    if (process == null) {
                        return;
                    }

                    final ProcessCachedOptimizerRecord opt = process.mOptRecord;
                    if (enable && opt.hasFreezerOverride()) {
                        if (OomAdjuster.getFreezePolicy(process)) {
                            freezeAppAsyncLSP(process);
                        }
                        opt.setFreezerOverride(false);
                    }

                    if (!enable && opt.isFrozen()) {
                        unfreezeAppLSP(process, UNFREEZE_REASON_FEATURE_FLAGS);

                        // Set freezerOverride *after* calling unfreezeAppLSP (it resets the flag)
                        opt.setFreezerOverride(true);
                    }
                });
            }
        }

        return true;
    }

    /**
     * Reads the flag value from DeviceConfig to determine whether app freezer
     * should be enabled, and starts the freeze/compaction thread if needed.
     */
    @GuardedBy("mPhenotypeFlagLock")
    private void updateUseFreezer() {
        final String configOverride = Settings.Global.getString(mAm.mContext.getContentResolver(),
                Settings.Global.CACHED_APPS_FREEZER_ENABLED);

        if ("disabled".equals(configOverride)) {
            mUseFreezer = false;
        } else if ("enabled".equals(configOverride)
                || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
                    KEY_USE_FREEZER, DEFAULT_USE_FREEZER)) {
            mUseFreezer = mFreezer.isFreezerSupported();
            updateFreezerDebounceTimeout();
            updateFreezerExemptInstPkg();
        } else {
            mUseFreezer = false;
        }

        final boolean useFreezer = mUseFreezer;
        // enableFreezer() would need the global ActivityManagerService lock, post it.
        mAm.mHandler.post(() -> {
            if (useFreezer) {
                Slog.d(TAG_AM, "Freezer enabled");
                enableFreezer(true);

                if (!mCachedAppOptimizerThread.isAlive()) {
                    mCachedAppOptimizerThread.start();
                }

                if (mFreezeHandler == null) {
                    mFreezeHandler = new FreezeHandler();
                }

                Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(),
                        Process.THREAD_GROUP_SYSTEM);
            } else {
                Slog.d(TAG_AM, "Freezer disabled");
                enableFreezer(false);
            }
        });
    }

    @GuardedBy("mPhenotypeFlagLock")
    private void updateCompactionThrottles() {
        boolean useThrottleDefaults = false;
        // TODO: improve efficiency by calling DeviceConfig only once for all flags.
        String throttleSomeSomeFlag =
                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                    KEY_COMPACT_THROTTLE_1);
        String throttleSomeFullFlag =
                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                    KEY_COMPACT_THROTTLE_2);
        String throttleFullSomeFlag =
                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                    KEY_COMPACT_THROTTLE_3);
        String throttleFullFullFlag =
                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                    KEY_COMPACT_THROTTLE_4);
        String throttleBFGSFlag =
                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                    KEY_COMPACT_THROTTLE_5);
        String throttlePersistentFlag =
                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                    KEY_COMPACT_THROTTLE_6);
        String throttleMinOomAdjFlag =
                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                    KEY_COMPACT_THROTTLE_MIN_OOM_ADJ);
        String throttleMaxOomAdjFlag =
                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                    KEY_COMPACT_THROTTLE_MAX_OOM_ADJ);

        if (TextUtils.isEmpty(throttleSomeSomeFlag) || TextUtils.isEmpty(throttleSomeFullFlag)
                || TextUtils.isEmpty(throttleFullSomeFlag)
                || TextUtils.isEmpty(throttleFullFullFlag)
                || TextUtils.isEmpty(throttleBFGSFlag)
                || TextUtils.isEmpty(throttlePersistentFlag)
                || TextUtils.isEmpty(throttleMinOomAdjFlag)
                || TextUtils.isEmpty(throttleMaxOomAdjFlag)) {
            // 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);
                mCompactThrottleMinOomAdj = Long.parseLong(throttleMinOomAdjFlag);
                mCompactThrottleMaxOomAdj = Long.parseLong(throttleMaxOomAdjFlag);
            } 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;
            mCompactThrottleMinOomAdj = DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ;
            mCompactThrottleMaxOomAdj = DEFAULT_COMPACT_THROTTLE_MAX_OOM_ADJ;
        }
    }

    @GuardedBy("mPhenotypeFlagLock")
    private void updateCompactStatsdSampleRate() {
        mCompactStatsdSampleRate = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                KEY_COMPACT_STATSD_SAMPLE_RATE, DEFAULT_STATSD_SAMPLE_RATE);
        mCompactStatsdSampleRate = Math.min(1.0f, Math.max(0.0f, mCompactStatsdSampleRate));
    }

    @GuardedBy("mPhenotypeFlagLock")
    private void updateFreezerStatsdSampleRate() {
        mFreezerStatsdSampleRate = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                KEY_FREEZER_STATSD_SAMPLE_RATE, DEFAULT_STATSD_SAMPLE_RATE);
        mFreezerStatsdSampleRate = Math.min(1.0f, Math.max(0.0f, mFreezerStatsdSampleRate));
    }

    @GuardedBy("mPhenotypeFlagLock")
    private void updateFullRssThrottle() {
        mFullAnonRssThrottleKb = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                KEY_COMPACT_FULL_RSS_THROTTLE_KB, DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);

        // Don't allow negative values. 0 means don't apply the throttle.
        if (mFullAnonRssThrottleKb < 0) {
            mFullAnonRssThrottleKb = DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB;
        }
    }

    @GuardedBy("mPhenotypeFlagLock")
    private void updateFullDeltaRssThrottle() {
        mFullDeltaRssThrottleKb = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);

        if (mFullDeltaRssThrottleKb < 0) {
            mFullDeltaRssThrottleKb = DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB;
        }
    }

    @GuardedBy("mPhenotypeFlagLock")
    private void updateProcStateThrottle() {
        String procStateThrottleString = DeviceConfig.getString(
                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_COMPACT_PROC_STATE_THROTTLE,
                DEFAULT_COMPACT_PROC_STATE_THROTTLE);
        if (!parseProcStateThrottle(procStateThrottleString)) {
            Slog.w(TAG_AM, "Unable to parse app compact proc state throttle \""
                    + procStateThrottleString + "\" falling back to default.");
            if (!parseProcStateThrottle(DEFAULT_COMPACT_PROC_STATE_THROTTLE)) {
                Slog.wtf(TAG_AM,
                        "Unable to parse default app compact proc state throttle "
                                + DEFAULT_COMPACT_PROC_STATE_THROTTLE);
            }
        }
    }

    @GuardedBy("mPhenotypeFlagLock")
    private void updateMinOomAdjThrottle() {
        mCompactThrottleMinOomAdj = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
            KEY_COMPACT_THROTTLE_MIN_OOM_ADJ, DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ);

        // Should only compact cached processes.
        if (mCompactThrottleMinOomAdj < ProcessList.CACHED_APP_MIN_ADJ) {
            mCompactThrottleMinOomAdj = DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ;
        }
    }

    @GuardedBy("mPhenotypeFlagLock")
    private void updateMaxOomAdjThrottle() {
        mCompactThrottleMaxOomAdj = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
            KEY_COMPACT_THROTTLE_MAX_OOM_ADJ, DEFAULT_COMPACT_THROTTLE_MAX_OOM_ADJ);

        // Should only compact cached processes.
        if (mCompactThrottleMaxOomAdj > ProcessList.CACHED_APP_MAX_ADJ) {
            mCompactThrottleMaxOomAdj = DEFAULT_COMPACT_THROTTLE_MAX_OOM_ADJ;
        }
    }

    @GuardedBy("mPhenotypeFlagLock")
    private void updateFreezerDebounceTimeout() {
        mFreezerDebounceTimeout = DeviceConfig.getLong(
                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
                KEY_FREEZER_DEBOUNCE_TIMEOUT, DEFAULT_FREEZER_DEBOUNCE_TIMEOUT);

        if (mFreezerDebounceTimeout < 0) {
            mFreezerDebounceTimeout = DEFAULT_FREEZER_DEBOUNCE_TIMEOUT;
        }
        Slog.d(TAG_AM, "Freezer timeout set to " + mFreezerDebounceTimeout);
    }

    @GuardedBy("mPhenotypeFlagLock")
    private void updateFreezerExemptInstPkg() {
        mFreezerExemptInstPkg = DeviceConfig.getBoolean(
                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
                KEY_FREEZER_EXEMPT_INST_PKG, DEFAULT_FREEZER_EXEMPT_INST_PKG);
        Slog.d(TAG_AM, "Freezer exemption set to " + mFreezerExemptInstPkg);
    }

    @GuardedBy("mPhenotypeFlagLock")
    private void updateFreezerBinderState() {
        mFreezerBinderEnabled = DeviceConfig.getBoolean(
                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
                KEY_FREEZER_BINDER_ENABLED, DEFAULT_FREEZER_BINDER_ENABLED);
        mFreezerBinderDivisor = DeviceConfig.getLong(
                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
                KEY_FREEZER_BINDER_DIVISOR, DEFAULT_FREEZER_BINDER_DIVISOR);
        mFreezerBinderOffset = DeviceConfig.getInt(
                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
                KEY_FREEZER_BINDER_OFFSET, DEFAULT_FREEZER_BINDER_OFFSET);
        mFreezerBinderThreshold = DeviceConfig.getLong(
                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
                KEY_FREEZER_BINDER_THRESHOLD, DEFAULT_FREEZER_BINDER_THRESHOLD);
        mFreezerBinderCallbackEnabled = DeviceConfig.getBoolean(
                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
                KEY_FREEZER_BINDER_CALLBACK_ENABLED, DEFAULT_FREEZER_BINDER_CALLBACK_ENABLED);
        mFreezerBinderCallbackThrottle = DeviceConfig.getLong(
                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
                KEY_FREEZER_BINDER_CALLBACK_THROTTLE, DEFAULT_FREEZER_BINDER_CALLBACK_THROTTLE);
        mFreezerBinderAsyncThreshold = DeviceConfig.getInt(
                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
                KEY_FREEZER_BINDER_ASYNC_THRESHOLD, DEFAULT_FREEZER_BINDER_ASYNC_THRESHOLD);
        Slog.d(TAG_AM, "Freezer binder state set to enabled=" + mFreezerBinderEnabled
                + ", divisor=" + mFreezerBinderDivisor
                + ", offset=" + mFreezerBinderOffset
                + ", threshold=" + mFreezerBinderThreshold
                + ", callback enabled=" + mFreezerBinderCallbackEnabled
                + ", callback throttle=" + mFreezerBinderCallbackThrottle
                + ", async threshold=" + mFreezerBinderAsyncThreshold);
    }

    private boolean parseProcStateThrottle(String procStateThrottleString) {
        String[] procStates = TextUtils.split(procStateThrottleString, ",");
        mProcStateThrottle.clear();
        for (String procState : procStates) {
            try {
                mProcStateThrottle.add(Integer.parseInt(procState));
            } catch (NumberFormatException e) {
                Slog.e(TAG_AM, "Failed to parse default app compaction proc state: "
                        + procState);
                return false;
            }
        }
        return true;
    }

    /**
     * Returns the earliest time (relative) from now that the app can be frozen.
     * @param app The app to update
     * @param delayMillis How much to delay freezing by
     */
    @GuardedBy("mProcLock")
    private long updateEarliestFreezableTime(ProcessRecord app, long delayMillis) {
        final long now = SystemClock.uptimeMillis();
        app.mOptRecord.setEarliestFreezableTime(
                Math.max(app.mOptRecord.getEarliestFreezableTime(), now + delayMillis));
        return app.mOptRecord.getEarliestFreezableTime() - now;
    }

    // This will ensure app will be out of the freezer for at least mFreezerDebounceTimeout.
    @GuardedBy("mAm")
    void unfreezeTemporarily(ProcessRecord app, @UnfreezeReason int reason) {
        unfreezeTemporarily(app, reason, mFreezerDebounceTimeout);
    }

    // This will ensure app will be out of the freezer for at least mFreezerDebounceTimeout.
    @GuardedBy("mAm")
    void unfreezeTemporarily(ProcessRecord app, @UnfreezeReason int reason, long delayMillis) {
        if (mUseFreezer) {
            synchronized (mProcLock) {
                // Move the earliest freezable time further, if necessary
                final long delay = updateEarliestFreezableTime(app, delayMillis);
                if (app.mOptRecord.isFrozen() || app.mOptRecord.isPendingFreeze()) {
                    unfreezeAppLSP(app, reason);
                    freezeAppAsyncLSP(app, delay);
                }
            }
        }
    }

    @GuardedBy({"mAm", "mProcLock"})
    void freezeAppAsyncLSP(ProcessRecord app) {
        freezeAppAsyncLSP(app, updateEarliestFreezableTime(app, mFreezerDebounceTimeout));
    }

    @GuardedBy({"mAm", "mProcLock"})
    void forceFreezeAppAsyncLSP(ProcessRecord app) {
        freezeAppAsyncInternalLSP(app, 0 /* delayMillis */, true /* force */);
    }

    @GuardedBy({"mAm", "mProcLock"})
    private void freezeAppAsyncLSP(ProcessRecord app, @UptimeMillisLong long delayMillis) {
        freezeAppAsyncInternalLSP(app, delayMillis, false /* force */);
    }

    @GuardedBy({"mAm", "mProcLock"})
    void freezeAppAsyncAtEarliestLSP(ProcessRecord app) {
        freezeAppAsyncLSP(app, updateEarliestFreezableTime(app, 0));
    }

    // TODO: Update freezeAppAsyncAtEarliestLSP to actually freeze the app at the earliest
    // and remove this method.
    @GuardedBy({"mAm", "mProcLock"})
    void freezeAppAsyncImmediateLSP(ProcessRecord app) {
        freezeAppAsyncInternalLSP(app, 0 /* delayMillis */, false /* force */);
    }

    @GuardedBy({"mAm", "mProcLock"})
    private void freezeAppAsyncInternalLSP(ProcessRecord app, @UptimeMillisLong long delayMillis,
            boolean force) {
        final ProcessCachedOptimizerRecord opt = app.mOptRecord;
        if (opt.isPendingFreeze()) {
            if (delayMillis == 0) {
                // Caller is requesting to freeze the process without delay, so remove
                // any already posted messages which would have been handled with a delay and
                // post a new message without a delay.
                mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);
                mFreezeHandler.sendMessage(mFreezeHandler.obtainMessage(
                        SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app));
            }
            // Skip redundant DO_FREEZE message
            return;
        }

        if (opt.isFreezeSticky() && !force) {
            if (DEBUG_FREEZER) {
                Slog.d(TAG_AM,
                        "Skip freezing because unfrozen state is sticky pid=" + app.getPid() + " "
                                + app.processName);
            }
            return;
        }

        if (app.getSetAdj() >= ProcessList.CACHED_APP_MIN_ADJ) {
            final IApplicationThread thread = app.getThread();
            if (thread != null) {
                try {
                    thread.scheduleTrimMemory(TRIM_MEMORY_BACKGROUND);
                } catch (RemoteException e) {
                    // do nothing
                }
            }
        }
        reportProcessFreezableChangedLocked(app);
        app.mOptRecord.setLastUsedTimeout(delayMillis);
        mFreezeHandler.sendMessageDelayed(
                mFreezeHandler.obtainMessage(SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app),
                delayMillis);
        opt.setPendingFreeze(true);
        if (DEBUG_FREEZER) {
            Slog.d(TAG_AM, "Async freezing " + app.getPid() + " " + app.processName);
        }
    }

    /**
     * Returns true if the app was frozen and became unfrozen, otherwise false.
     *
     * Do not call this directly.  It will unfreeze a process but it will not send out any
     * notifications.  Instead call unfreezeAppLSP().
     */
    @GuardedBy({"mAm", "mProcLock", "mFreezerLock"})
    private boolean unfreezeAppInternalLSP(ProcessRecord app, @UnfreezeReason int reason,
            boolean force) {
        final int pid = app.getPid();
        final ProcessCachedOptimizerRecord opt = app.mOptRecord;
        boolean sticky = opt.isFreezeSticky();
        if (sticky && !force) {
            // Sticky freezes will not change their state unless forced out of it.
            if (DEBUG_FREEZER) {
                Slog.d(TAG_AM,
                        "Skip unfreezing because frozen state is sticky pid=" + pid + " "
                                + app.processName);
            }
            return false;
        }
        boolean processFreezableChangeReported = false;
        if (opt.isPendingFreeze()) {
            // Remove pending DO_FREEZE message
            mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);
            opt.setPendingFreeze(false);
            reportProcessFreezableChangedLocked(app);
            processFreezableChangeReported = true;
            if (DEBUG_FREEZER) {
                Slog.d(TAG_AM, "Cancel freezing " + pid + " " + app.processName);
            }
        }

        UidRecord uidRec = app.getUidRecord();
        if (uidRec != null && uidRec.isFrozen()) {
            uidRec.setFrozen(false);
            postUidFrozenMessage(uidRec.getUid(), false);
        }

        opt.setFreezerOverride(false);
        if (pid == 0 || !opt.isFrozen()) {
            return false;
        }

        // Unfreeze the binder interface first, to avoid transactions triggered by timers fired
        // right after unfreezing the process to fail
        boolean processKilled = false;

        try {
            int freezeInfo = mFreezer.getBinderFreezeInfo(pid);

            if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) {
                Slog.d(TAG_AM, "pid " + pid + " " + app.processName
                        + " received sync transactions while frozen, killing");
                app.killLocked("Sync transaction while in frozen state",
                        ApplicationExitInfo.REASON_FREEZER,
                        ApplicationExitInfo.SUBREASON_FREEZER_BINDER_TRANSACTION, true);
                processKilled = true;
            }

            if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0 && DEBUG_FREEZER) {
                Slog.d(TAG_AM, "pid " + pid + " " + app.processName
                        + " received async transactions while frozen");
            }
        } catch (Exception e) {
            Slog.d(TAG_AM, "Unable to query binder frozen info for pid " + pid + " "
                    + app.processName + ". Killing it. Exception: " + e);
            app.killLocked("Unable to query binder frozen stats",
                    ApplicationExitInfo.REASON_FREEZER,
                    ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
            processKilled = true;
        }

        if (processKilled) {
            return false;
        }
        if (!processFreezableChangeReported) {
            reportProcessFreezableChangedLocked(app);
        }

        long freezeTime = opt.getFreezeUnfreezeTime();

        try {
            mFreezer.freezeBinder(pid, false, FREEZE_BINDER_TIMEOUT_MS);
        } catch (RuntimeException e) {
            Slog.e(TAG_AM, "Unable to unfreeze binder for " + pid + " " + app.processName
                    + ". Killing it");
            app.killLocked("Unable to unfreeze",
                    ApplicationExitInfo.REASON_FREEZER,
                    ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
            return false;
        }

        try {
            traceAppFreeze(app.processName, pid, reason);
            mFreezer.setProcessFrozen(pid, app.uid, false);

            opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
            opt.setFrozen(false);
            mFrozenProcesses.delete(pid);
        } catch (Exception e) {
            Slog.e(TAG_AM, "Unable to unfreeze " + pid + " " + app.processName
                    + ". This might cause inconsistency or UI hangs.");
        }

        if (!opt.isFrozen()) {
            Slog.d(TAG_AM, "sync unfroze " + pid + " " + app.processName + " for " + reason);

            mFreezeHandler.sendMessage(
                    mFreezeHandler.obtainMessage(REPORT_UNFREEZE_MSG,
                        pid,
                        (int) Math.min(opt.getFreezeUnfreezeTime() - freezeTime, Integer.MAX_VALUE),
                        new Pair<ProcessRecord, Integer>(app, reason)));
            return true;
        }
        return false;
    }

    @GuardedBy({"mAm", "mProcLock"})
    void unfreezeAppLSP(ProcessRecord app, @UnfreezeReason int reason, boolean force) {
        final boolean shouldDispatch;
        synchronized (mFreezerLock) {
            shouldDispatch = unfreezeAppInternalLSP(app, reason, force);
        }
        if (shouldDispatch) {
            app.mOptRecord.dispatchUnfrozenEvent();
        }
    }

    @GuardedBy({"mAm", "mProcLock"})
    void unfreezeAppLSP(ProcessRecord app, @UnfreezeReason int reason) {
        unfreezeAppLSP(app, reason, false);
    }

    /**
     * This quick function works around the race condition between WM/ATMS and AMS, allowing
     * the former to directly unfreeze a frozen process before the latter runs updateOomAdj.
     * After the race issue is solved, this workaround can be removed. (b/213288355)
     * The caller of this function should still trigger updateOomAdj for AMS to unfreeze the app.
     * @param pid pid of the process to be unfrozen
     */
    void unfreezeProcess(int pid, @OomAdjReason int reason) {
        final ProcessRecord app;
        synchronized (mFreezerLock) {
            app = mFrozenProcesses.get(pid);
            if (app == null) {
                return;
            }
            Slog.d(TAG_AM, "quick sync unfreeze " + pid + " for " +  reason);
            try {
                mFreezer.freezeBinder(pid, false, FREEZE_BINDER_TIMEOUT_MS);
            } catch (RuntimeException e) {
                Slog.e(TAG_AM, "Unable to quick unfreeze binder for " + pid);
                return;
            }

            try {
                traceAppFreeze(app.processName, pid, reason);
                mFreezer.setProcessFrozen(pid, app.uid, false);
            } catch (Exception e) {
                Slog.e(TAG_AM, "Unable to quick unfreeze " + pid);
            }
        }
        app.mOptRecord.dispatchUnfrozenEvent();
    }

    /**
     * Trace app freeze status
     * @param processName The name of the target process
     * @param pid The pid of the target process
     * @param reason UNFREEZE_REASON_XXX (>=0) for unfreezing and -1 for freezing
     */
    private static void traceAppFreeze(String processName, int pid, int reason) {
        Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_FREEZER_TRACK,
                (reason < 0 ? "Freeze " : "Unfreeze ") + processName + ":" + pid + " " + reason);
    }

    /**
     * To be called when the given app is killed.
     */
    @GuardedBy({"mAm", "mProcLock"})
    void onCleanupApplicationRecordLocked(ProcessRecord app) {
        if (mUseFreezer) {
            final ProcessCachedOptimizerRecord opt = app.mOptRecord;
            if (opt.isPendingFreeze()) {
                // Remove pending DO_FREEZE message
                mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);
                opt.setPendingFreeze(false);
            }

            final UidRecord uidRec = app.getUidRecord();
            if (uidRec != null) {
                final boolean isFrozen = uidRec.getNumOfProcs() > 1
                        && uidRec.areAllProcessesFrozen(app);
                if (isFrozen != uidRec.isFrozen()) {
                    uidRec.setFrozen(isFrozen);
                    postUidFrozenMessage(uidRec.getUid(), isFrozen);
                }
            }

            mFrozenProcesses.delete(app.getPid());
        }
    }

    void onWakefulnessChanged(int wakefulness) {
        if(wakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE) {
            if (useCompaction()) {
                // Remove any pending compaction we may have scheduled to happen while screen was
                // off
                cancelAllCompactions(CancelCompactReason.SCREEN_ON);
            }
        }
    }

    void cancelAllCompactions(CancelCompactReason reason) {
        synchronized (mProcLock) {
            while(!mPendingCompactionProcesses.isEmpty()) {
                if (DEBUG_COMPACTION) {
                    Slog.e(TAG_AM,
                            "Cancel pending compaction as system is awake for process="
                                    + mPendingCompactionProcesses.get(0).processName);
                }
                cancelCompactionForProcess(mPendingCompactionProcesses.get(0), reason);
            }
            mPendingCompactionProcesses.clear();
        }
    }

    @GuardedBy("mProcLock")
    void cancelCompactionForProcess(ProcessRecord app, CancelCompactReason cancelReason) {
        boolean cancelled = false;
        if (mPendingCompactionProcesses.contains(app)) {
            app.mOptRecord.setHasPendingCompact(false);
            mPendingCompactionProcesses.remove(app);
            cancelled = true;
        }
        if (DefaultProcessDependencies.mPidCompacting == app.mPid) {
            cancelCompaction();
            cancelled = true;
        }
        if (cancelled) {
            mCompactStatsManager.logCompactionCancelled(cancelReason);
            if (DEBUG_COMPACTION) {
                Slog.d(TAG_AM,
                        "Cancelled pending or running compactions for process: " +
                                app.processName != null ? app.processName : "" +
                                " reason: " + cancelReason.name());
            }
        }
    }

    @GuardedBy({"mService", "mProcLock"})
    void onOomAdjustChanged(int oldAdj, int newAdj, ProcessRecord app) {
        if (useCompaction()) {
            // Cancel any currently executing compactions
            // if the process moved out of cached state
            if (newAdj < oldAdj && newAdj < ProcessList.CACHED_APP_MIN_ADJ) {
                cancelCompactionForProcess(app, CancelCompactReason.OOM_IMPROVEMENT);
            }
        }
    }

    /**
     * Callback received after a process has been frozen.
     */
    void onProcessFrozen(ProcessRecord frozenProc) {
        if (useCompaction()) {
            synchronized (mProcLock) {
                // only full-compact if process is cached
                if (frozenProc.getSetAdj() >= mCompactThrottleMinOomAdj) {
                    compactApp(frozenProc, CompactProfile.FULL, CompactSource.APP, false);
                }
            }
        }
        frozenProc.onProcessFrozen();
    }

    /**
     * Callback received when an attempt to freeze a process is cancelled (failed).
     */
    void onProcessFrozenCancelled(ProcessRecord app) {
        app.onProcessFrozenCancelled();
    }

    /**
     * Computes the final compaction profile to be used which depends on compaction
     * features enabled and swap usage.
     */
    CompactProfile resolveCompactionProfile(CompactProfile profile) {
        if (profile == CompactProfile.FULL) {
            double swapFreePercent = getFreeSwapPercent();
            // Downgrade compaction under swap memory pressure
            if (swapFreePercent < COMPACT_DOWNGRADE_FREE_SWAP_THRESHOLD) {
                profile = CompactProfile.SOME;
                mCompactStatsManager.logCompactionDowngrade();
                if (DEBUG_COMPACTION) {
                    Slog.d(TAG_AM,
                            "Downgraded compaction to "+ profile +" due to low swap."
                                    + " Swap Free% " + swapFreePercent);
                }
            }
        }

        if (!ENABLE_SHARED_AND_CODE_COMPACT) {
            if (profile == CompactProfile.SOME) {
                profile = CompactProfile.NONE;
            } else if (profile == CompactProfile.FULL) {
                profile = CompactProfile.ANON;
            }
            if (DEBUG_COMPACTION) {
                Slog.d(TAG_AM,
                        "Final compaction profile "+ profile +" due to file compact disabled");
            }
        }

        return profile;
    }

    boolean isProcessFrozen(int pid) {
        synchronized (mProcLock) {
            return mFrozenProcesses.contains(pid);
        }
    }

    private static int getCompactionFlags(CompactProfile profile) {
        if (profile == CompactProfile.FULL) {
            return COMPACT_ACTION_FILE_FLAG | COMPACT_ACTION_ANON_FLAG;
        } else if (profile == CompactProfile.SOME) {
            return COMPACT_ACTION_FILE_FLAG;
        } else if (profile == CompactProfile.ANON) {
            return COMPACT_ACTION_ANON_FLAG;
        }
        return 0;
    }

    private final class MemCompactionHandler extends Handler {
        private MemCompactionHandler() {
            super(mCachedAppOptimizerThread.getLooper());
        }

        private boolean shouldOomAdjThrottleCompaction(ProcessRecord proc) {
            final String name = proc.processName;

            // don't compact if the process has returned to perceptible
            // and this is only a cached/home/prev compaction
            if (proc.getSetAdj() <= ProcessList.PERCEPTIBLE_APP_ADJ) {
                if (DEBUG_COMPACTION) {
                    Slog.d(TAG_AM,
                            "Skipping compaction as process " + name + " is "
                                    + "now perceptible.");
                }
                return true;
            }

            return false;
        }

        private boolean shouldTimeThrottleCompaction(ProcessRecord proc, long start,
                CompactProfile pendingProfile, CompactSource source) {
            final ProcessCachedOptimizerRecord opt = proc.mOptRecord;
            final String name = proc.processName;

            CompactProfile lastCompactProfile = opt.getLastCompactProfile();
            long lastCompactTime = opt.getLastCompactTime();

            // basic throttling
            // use the Phenotype flag knobs to determine whether current/previous
            // compaction combo should be throttled 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 (lastCompactTime != 0) {
                if (source == CompactSource.APP) {
                    if (pendingProfile == CompactProfile.SOME) {
                        if ((lastCompactProfile == CompactProfile.SOME
                                    && (start - lastCompactTime < mCompactThrottleSomeSome))
                                || (lastCompactProfile == CompactProfile.FULL
                                        && (start - lastCompactTime < mCompactThrottleSomeFull))) {
                            if (DEBUG_COMPACTION) {
                                Slog.d(TAG_AM,
                                        "Skipping some compaction for " + name
                                                + ": too soon. throttle=" + mCompactThrottleSomeSome
                                                + "/" + mCompactThrottleSomeFull
                                                + " last=" + (start - lastCompactTime) + "ms ago");
                            }
                            return true;
                        }
                    } else if (pendingProfile == CompactProfile.FULL) {
                        if ((lastCompactProfile == CompactProfile.SOME
                                    && (start - lastCompactTime < mCompactThrottleFullSome))
                                || (lastCompactProfile == CompactProfile.FULL
                                        && (start - lastCompactTime < mCompactThrottleFullFull))) {
                            if (DEBUG_COMPACTION) {
                                Slog.d(TAG_AM,
                                        "Skipping full compaction for " + name
                                                + ": too soon. throttle=" + mCompactThrottleFullSome
                                                + "/" + mCompactThrottleFullFull
                                                + " last=" + (start - lastCompactTime) + "ms ago");
                            }
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        private boolean shouldThrottleMiscCompaction(ProcessRecord proc, int procState) {
            if (mProcStateThrottle.contains(procState)) {
                if (DEBUG_COMPACTION) {
                    final String name = proc.processName;
                    Slog.d(TAG_AM,
                            "Skipping full compaction for process " + name + "; proc state is "
                                    + procState);
                }
                return true;
            }

            return false;
        }

        private boolean shouldRssThrottleCompaction(
                CompactProfile profile, int pid, String name, long[] rssBefore) {
            long anonRssBefore = rssBefore[RSS_ANON_INDEX];
            SingleCompactionStats lastCompactionStats =
                    mCompactStatsManager.getLastCompactionStats(pid);

            if (rssBefore[RSS_TOTAL_INDEX] == 0 && rssBefore[RSS_FILE_INDEX] == 0
                    && rssBefore[RSS_ANON_INDEX] == 0 && rssBefore[RSS_SWAP_INDEX] == 0) {
                if (DEBUG_COMPACTION) {
                    Slog.d(TAG_AM,
                            "Skipping compaction for"
                                    + "process " + pid + " with no memory usage. Dead?");
                }
                return true;
            }

            if (profile == CompactProfile.FULL) {
                if (mFullAnonRssThrottleKb > 0L && anonRssBefore < mFullAnonRssThrottleKb) {
                    if (DEBUG_COMPACTION) {
                        Slog.d(TAG_AM,
                                "Skipping full compaction for process " + name
                                        + "; anon RSS is too small: " + anonRssBefore + "KB.");
                    }
                    return true;
                }

                if (lastCompactionStats != null && mFullDeltaRssThrottleKb > 0L) {
                    long[] lastRss = lastCompactionStats.getRssAfterCompaction();
                    long absDelta = Math.abs(rssBefore[RSS_FILE_INDEX] - lastRss[RSS_FILE_INDEX])
                            + Math.abs(rssBefore[RSS_ANON_INDEX] - lastRss[RSS_ANON_INDEX])
                            + Math.abs(rssBefore[RSS_SWAP_INDEX] - lastRss[RSS_SWAP_INDEX]);
                    if (absDelta <= mFullDeltaRssThrottleKb) {
                        if (DEBUG_COMPACTION) {
                            Slog.d(TAG_AM,
                                    "Skipping full compaction for process " + name
                                            + "; abs delta is too small: " + absDelta + "KB.");
                        }
                        return true;
                    }
                }
            }

            return false;
        }

        private EnumMap<CompactProfile, Boolean> mProfileValidForMemcgMap =
                new EnumMap<>(CompactProfile.class);

        private boolean profileValidForMemcg(CompactProfile profile) {
            Boolean valid = mProfileValidForMemcgMap.get(profile);

            if (valid == null) {
                // Use JNI only once
                valid = new Boolean(compactionFlagsValidForMemcg(getCompactionFlags(profile)));
                mProfileValidForMemcgMap.put(profile, valid);
            }

            return valid.booleanValue();
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case COMPACT_PROCESS_MSG: {
                    long start = SystemClock.uptimeMillis();
                    ProcessRecord proc;
                    final ProcessCachedOptimizerRecord opt;
                    int uid;
                    int pid;
                    final String name;
                    CompactProfile lastCompactProfile;
                    long lastCompactTime;
                    int newOomAdj = msg.arg1;
                    int procState = msg.arg2;
                    boolean forceCompaction;
                    CompactSource compactSource;
                    CompactProfile requestedProfile;
                    int oomAdjReason;
                    synchronized (mProcLock) {
                        if (mPendingCompactionProcesses.isEmpty()) {
                            if (DEBUG_COMPACTION) {
                                Slog.d(TAG_AM, "No processes pending compaction, bail out");
                            }
                            return;
                        }
                        proc = mPendingCompactionProcesses.remove(0);
                        opt = proc.mOptRecord;
                        forceCompaction = opt.isForceCompact();
                        opt.setForceCompact(false); // since this is a one-shot operation
                        uid = proc.uid;
                        pid = proc.getPid();
                        name = proc.processName;
                        opt.setHasPendingCompact(false);
                        compactSource = opt.getReqCompactSource();
                        requestedProfile = opt.getReqCompactProfile();
                        lastCompactProfile = opt.getLastCompactProfile();
                        lastCompactTime = opt.getLastCompactTime();
                        oomAdjReason = opt.getLastOomAdjChangeReason();
                    }

                    long[] rssBefore;
                    if (pid == 0) {
                        // not a real process, either one being launched or one being killed
                        if (DEBUG_COMPACTION) {
                            Slog.d(TAG_AM, "Compaction failed, pid is 0");
                        }
                        mCompactStatsManager.logCompactionThrottled(
                                CompactionStatsManager.COMPACT_THROTTLE_REASON_NO_PID,
                                compactSource, name);
                        return;
                    }

                    if (!forceCompaction) {
                        if (shouldOomAdjThrottleCompaction(proc)) {
                            mCompactStatsManager.logCompactionThrottled(
                                    CompactionStatsManager.COMPACT_THROTTLE_REASON_OOM_ADJ,
                                    compactSource, name);
                            return;
                        }
                        if (shouldTimeThrottleCompaction(
                                    proc, start, requestedProfile, compactSource)) {
                            mCompactStatsManager.logCompactionThrottled(
                                    CompactionStatsManager.COMPACT_THROTTLE_REASON_TIME_TOO_SOON,
                                    compactSource, name);
                            return;
                        }
                        if (shouldThrottleMiscCompaction(proc, procState)) {
                            mCompactStatsManager.logCompactionThrottled(
                                    CompactionStatsManager.COMPACT_THROTTLE_REASON_PROC_STATE,
                                    compactSource, name);
                            return;
                        }
                        rssBefore = mProcessDependencies.getRss(pid);
                        if (shouldRssThrottleCompaction(requestedProfile, pid, name, rssBefore)) {
                            mCompactStatsManager.logCompactionThrottled(
                                    CompactionStatsManager.COMPACT_THROTTLE_REASON_DELTA_RSS,
                                    compactSource, name);
                            return;
                        }
                    } else {
                        rssBefore = mProcessDependencies.getRss(pid);
                        if (DEBUG_COMPACTION) {
                            Slog.d(TAG_AM, "Forcing compaction for " + name);
                        }
                    }

                    CompactProfile resolvedProfile =
                            resolveCompactionProfile(requestedProfile);
                    if (resolvedProfile == CompactProfile.NONE) {
                        // No point on issuing compaction call as we don't want to compact.
                        if (DEBUG_COMPACTION) {
                            Slog.d(TAG_AM, "Resolved no compaction for "+ name +
                                    " requested profile="+requestedProfile);
                        }
                        return;
                    }

                    try {
                        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                                "Compact " + resolvedProfile.name() + ": " + name
                                        + " lastOomAdjReason: " + oomAdjReason
                                        + " source: " + compactSource.name());
                        long zramUsedKbBefore = getUsedZramMemory();
                        long startCpuTime = threadCpuTimeNs();

                        if (Flags.useMemcgForCompaction() &&
                                profileValidForMemcg(resolvedProfile)) {
                            mProcessDependencies.performMemcgCompaction(resolvedProfile, uid, pid);
                        } else {
                            mProcessDependencies.performCompaction(resolvedProfile, pid);
                        }

                        long endCpuTime = threadCpuTimeNs();
                        long[] rssAfter = mProcessDependencies.getRss(pid);
                        long end = SystemClock.uptimeMillis();
                        long time = end - start;
                        long deltaCpuTimeNanos = endCpuTime - startCpuTime;
                        long zramUsedKbAfter = getUsedZramMemory();
                        long deltaTotalRss = rssAfter[RSS_TOTAL_INDEX] - rssBefore[RSS_TOTAL_INDEX];
                        long deltaFileRss = rssAfter[RSS_FILE_INDEX] - rssBefore[RSS_FILE_INDEX];
                        long deltaAnonRss = rssAfter[RSS_ANON_INDEX] - rssBefore[RSS_ANON_INDEX];
                        long deltaSwapRss = rssAfter[RSS_SWAP_INDEX] - rssBefore[RSS_SWAP_INDEX];
                        switch (opt.getReqCompactProfile()) {
                            case SOME:
                                mCompactStatsManager.logSomeCompactionPerformed(compactSource,
                                    name);
                                break;
                            case FULL:
                                long anonRssSavings = -deltaAnonRss;
                                long zramConsumed = zramUsedKbAfter - zramUsedKbBefore;
                                long memFreed = anonRssSavings - zramConsumed;
                                long totalCpuTimeMillis = deltaCpuTimeNanos / 1000000;
                                long origAnonRss = rssBefore[RSS_ANON_INDEX];
                                mCompactStatsManager.logFullCompactionPerformed(compactSource, name,
                                        anonRssSavings, zramConsumed, memFreed, origAnonRss,
                                        totalCpuTimeMillis, rssAfter, procState, newOomAdj,
                                        oomAdjReason, proc.uid, pid, !forceCompaction);
                                break;
                            default:
                                // We likely missed adding this category, it needs to be added
                                // if we end up here. In the meantime, gracefully fallback to
                                // attribute to app.
                                Slog.wtf(TAG_AM, "Compaction: Unknown requested action");
                                return;
                        }
                        EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name,
                                resolvedProfile.name(), rssBefore[RSS_TOTAL_INDEX],
                                rssBefore[RSS_FILE_INDEX], rssBefore[RSS_ANON_INDEX],
                                rssBefore[RSS_SWAP_INDEX], deltaTotalRss, deltaFileRss,
                                deltaAnonRss, deltaSwapRss, time, lastCompactProfile.name(),
                                lastCompactTime, newOomAdj, procState, zramUsedKbBefore,
                                zramUsedKbBefore - zramUsedKbAfter);
                        synchronized (mProcLock) {
                            opt.setLastCompactTime(end);
                            opt.setLastCompactProfile(requestedProfile);
                        }
                    } catch (Exception e) {
                        // nothing to do, presumably the process died
                        Slog.d(TAG_AM,
                                "Exception occurred while compacting pid: " + name
                                        + ". Exception:" + e.getMessage());
                    } finally {
                        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    }
                    break;
                }
                case COMPACT_SYSTEM_MSG: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "compactSystem");
                    long memFreedBefore = getMemoryFreedCompaction();
                    compactSystem();
                    long memFreedAfter = getMemoryFreedCompaction();
                    long memFreed = memFreedAfter - memFreedBefore;
                    mCompactStatsManager.logSystemCompactionPerformed(memFreed);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                }
                case COMPACT_NATIVE_MSG: {
                    int pid = msg.arg1;
                    CompactProfile compactProfile = CompactProfile.values()[msg.arg2];
                    Slog.d(TAG_AM,
                            "Performing native compaction for pid=" + pid
                                    + " type=" + compactProfile.name());
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "compactNative");
                    try {
                        if (Flags.useMemcgForCompaction()) {
                            mProcessDependencies.performNativeCompaction(compactProfile, pid);
                        } else {
                            mProcessDependencies.performCompaction(compactProfile, pid);
                        }
                    } catch (Exception e) {
                        Slog.d(TAG_AM, "Failed compacting native pid= " + pid);
                    }
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                }
            }
        }
    }

    private void reportOneUidFrozenStateChanged(int uid, boolean frozen) {
        final int[] uids = new int[1];
        final int[] frozenStates = new int[1];

        uids[0] = uid;
        frozenStates[0] = frozen ? UID_FROZEN_STATE_FROZEN : UID_FROZEN_STATE_UNFROZEN;

        if (DEBUG_FREEZER) {
            Slog.d(TAG_AM, "reportOneUidFrozenStateChanged uid " + uid + " frozen = " + frozen);
        }

        mAm.reportUidFrozenStateChanged(uids, frozenStates);
    }

    private void postUidFrozenMessage(int uid, boolean frozen) {
        final Integer uidObj = Integer.valueOf(uid);
        mFreezeHandler.removeEqualMessages(UID_FROZEN_STATE_CHANGED_MSG, uidObj);

        final int op = frozen ? 1 : 0;
        mFreezeHandler.sendMessage(mFreezeHandler.obtainMessage(UID_FROZEN_STATE_CHANGED_MSG, op,
                0, uidObj));
    }

    @GuardedBy("mAm")
    private void reportProcessFreezableChangedLocked(ProcessRecord app) {
        mAm.onProcessFreezableChangedLocked(app);
    }

    private final class FreezeHandler extends Handler implements
            ProcLocksReader.ProcLocksReaderCallback {
        private FreezeHandler() {
            super(mCachedAppOptimizerThread.getLooper());
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case SET_FROZEN_PROCESS_MSG: {
                    ProcessRecord proc = (ProcessRecord) msg.obj;
                    synchronized (mAm) {
                        if (!proc.mOptRecord.isPendingFreeze()) {
                            return;
                        }
                        freezeProcess(proc);
                    }
                    if (proc.mOptRecord.isFrozen()) {
                        onProcessFrozen(proc);
                        removeMessages(DEADLOCK_WATCHDOG_MSG);
                        sendEmptyMessageDelayed(DEADLOCK_WATCHDOG_MSG, FREEZE_DEADLOCK_TIMEOUT_MS);
                    } else {
                        onProcessFrozenCancelled(proc);
                    }
                } break;
                case REPORT_UNFREEZE_MSG: {
                    int pid = msg.arg1;
                    int frozenDuration = msg.arg2;
                    Pair<ProcessRecord, Integer> obj = (Pair<ProcessRecord, Integer>) msg.obj;
                    ProcessRecord app = obj.first;
                    String processName = app.processName;
                    int reason = obj.second;

                    reportUnfreeze(app, pid, frozenDuration, processName, reason);
                } break;
                case UID_FROZEN_STATE_CHANGED_MSG: {
                    final boolean frozen = (msg.arg1 == 1);
                    final int uid = (int) msg.obj;
                    reportOneUidFrozenStateChanged(uid, frozen);
                } break;
                case DEADLOCK_WATCHDOG_MSG: {
                    try {
                        // post-check to prevent deadlock
                        if (DEBUG_FREEZER) {
                            Slog.d(TAG_AM, "Freezer deadlock watchdog");
                        }
                        mProcLocksReader.handleBlockingFileLocks(this);
                    } catch (IOException e) {
                        Slog.w(TAG_AM, "Unable to check file locks");
                    }
                } break;
                case BINDER_ERROR_MSG: {
                    IntArray pids = new IntArray();
                    // Copy the frozen pids to a local array to release mProcLock ASAP
                    synchronized (mProcLock) {
                        int size = mFrozenProcesses.size();
                        for (int i = 0; i < size; i++) {
                            pids.add(mFrozenProcesses.keyAt(i));
                        }
                    }

                    // Check binder errors to frozen processes
                    // Freezer lock is not required as we don't perform (un)freeze operations here
                    binderErrorInternal(pids);
                } break;
                default:
                    return;
            }
        }

        @GuardedBy({"mAm", "mProcLock"})
        private void handleBinderFreezerFailure(final ProcessRecord proc, final String reason) {
            if (!mFreezerBinderEnabled) {
                // Just reschedule indefinitely.
                unfreezeAppLSP(proc, UNFREEZE_REASON_BINDER_TXNS);
                freezeAppAsyncLSP(proc);
                return;
            }
            /*
             * This handles the case where a process couldn't be frozen due to pending binder
             * transactions. In order to prevent apps from avoiding the freezer by spamming binder
             * transactions, there is an exponential decrease in freezer retry times plus a random
             * offset per attempt to avoid phase issues. Once the last-attempted timeout is below a
             * threshold, we assume that the app is spamming binder calls and can never be frozen,
             * and we will then crash the app.
             */
            if (proc.mOptRecord.getLastUsedTimeout() <= mFreezerBinderThreshold) {
                // We've given the app plenty of chances, assume broken. Time to die.
                Slog.d(TAG_AM, "Kill app due to repeated failure to freeze binder: "
                        + proc.getPid() + " " + proc.processName);
                mAm.mHandler.post(() -> {
                    synchronized (mAm) {
                        // Crash regardless of procstate in case the app has found another way
                        // to abuse oom_adj
                        if (proc.getThread() == null) {
                            return;
                        }
                        proc.killLocked("excessive binder traffic during cached",
                                ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
                                ApplicationExitInfo.SUBREASON_EXCESSIVE_CPU,
                                true);
                    }
                });
                return;
            }

            long timeout = proc.mOptRecord.getLastUsedTimeout() / mFreezerBinderDivisor;
            // range is [-mFreezerBinderOffset, +mFreezerBinderOffset]
            int offset = mRandom.nextInt(mFreezerBinderOffset * 2) - mFreezerBinderOffset;
            timeout = Math.max(timeout + offset, mFreezerBinderThreshold);

            Slog.d(TAG_AM, "Reschedule freeze for process " + proc.getPid()
                    + " " + proc.processName + " (" + reason  + "), timeout=" + timeout);
            Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_FREEZER_TRACK,
                    "Reschedule freeze " + proc.processName + ":" + proc.getPid()
                    + " timeout=" + timeout + ", reason=" + reason);

            unfreezeAppLSP(proc, UNFREEZE_REASON_BINDER_TXNS);
            freezeAppAsyncLSP(proc, timeout);
        }

        /**
         * Freeze a process.
         * @param proc process to be frozen
         */
        @GuardedBy({"mAm"})
        private void freezeProcess(final ProcessRecord proc) {
            final int pid;
            final String name = proc.processName;
            final long unfrozenDuration;
            final boolean frozen;
            final ProcessCachedOptimizerRecord opt = proc.mOptRecord;

            synchronized (mProcLock) {
                // someone has canceled this freeze
                if (!opt.isPendingFreeze()) {
                    return;
                }
                opt.setPendingFreeze(false);
                pid = proc.getPid();

                if (mFreezerOverride) {
                    opt.setFreezerOverride(true);
                    Slog.d(TAG_AM, "Skipping freeze for process " + pid
                            + " " + name + " curAdj = " + proc.getCurAdj()
                            + "(override)");
                    return;
                }

                if (opt.shouldNotFreeze() && !Flags.cpuTimeCapabilityBasedFreezePolicy()) {
                    if (DEBUG_FREEZER) {
                        Slog.d(TAG_AM, "Skipping freeze because process is marked "
                                + "should not be frozen");
                    }
                    reportProcessFreezableChangedLocked(proc);
                    return;
                }

                if (pid == 0 || opt.isFrozen()) {
                    // Already frozen or not a real process, either one being
                    // launched or one being killed
                    if (DEBUG_FREEZER) {
                        Slog.d(TAG_AM, "Skipping freeze for process " + pid
                                + " " + name + ". Already frozen or not a real process");
                    }
                    return;
                }

                Slog.d(TAG_AM, "freezing " + pid + " " + name);

                // Freeze binder interface before the process, to flush any
                // transactions that might be pending.
                try {
                    if (mFreezer.freezeBinder(pid, true, FREEZE_BINDER_TIMEOUT_MS) != 0) {
                        handleBinderFreezerFailure(proc, "outstanding txns");
                        return;
                    }
                } catch (RuntimeException e) {
                    Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
                    mFreezeHandler.post(() -> {
                        synchronized (mAm) {
                            proc.killLocked("Unable to freeze binder interface",
                                    ApplicationExitInfo.REASON_FREEZER,
                                    ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
                        }
                    });
                }

                long unfreezeTime = opt.getFreezeUnfreezeTime();

                try {
                    traceAppFreeze(proc.processName, pid, -1);
                    mFreezer.setProcessFrozen(pid, proc.uid, true);
                    opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
                    opt.setFrozen(true);
                    opt.setHasCollectedFrozenPSS(false);
                    mFrozenProcesses.put(pid, proc);
                } catch (Exception e) {
                    Slog.w(TAG_AM, "Unable to freeze " + pid + " " + name);
                }

                unfrozenDuration = opt.getFreezeUnfreezeTime() - unfreezeTime;
                frozen = opt.isFrozen();

                final UidRecord uidRec = proc.getUidRecord();
                if (frozen && uidRec != null && uidRec.areAllProcessesFrozen()) {
                    uidRec.setFrozen(true);

                    postUidFrozenMessage(uidRec.getUid(), true);
                }
            }

            if (!frozen) {
                return;
            }

            EventLog.writeEvent(EventLogTags.AM_FREEZE, pid, name);

            // See above for why we're not taking mPhenotypeFlagLock here
            if (mRandom.nextFloat() < mFreezerStatsdSampleRate) {
                FrameworkStatsLog.write(FrameworkStatsLog.APP_FREEZE_CHANGED,
                        FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__FREEZE_APP,
                        pid,
                        name,
                        unfrozenDuration,
                        FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__NONE,
                        UNFREEZE_REASON_NONE);
            }

            try {
                // post-check to prevent races
                int freezeInfo = mFreezer.getBinderFreezeInfo(pid);

                if ((freezeInfo & TXNS_PENDING_WHILE_FROZEN) != 0) {
                    synchronized (mProcLock) {
                        handleBinderFreezerFailure(proc, "new pending txns");
                    }
                    return;
                }
            } catch (RuntimeException e) {
                Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
                mFreezeHandler.post(() -> {
                    synchronized (mAm) {
                        proc.killLocked("Unable to freeze binder interface",
                                ApplicationExitInfo.REASON_FREEZER,
                                ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
                    }
                });
            }
            opt.dispatchFrozenEvent();
        }

        private void reportUnfreeze(ProcessRecord app, int pid, int frozenDuration,
                String processName, @UnfreezeReason int reason) {

            EventLog.writeEvent(EventLogTags.AM_UNFREEZE, pid, processName, reason);
            app.onProcessUnfrozen();

            // See above for why we're not taking mPhenotypeFlagLock here
            if (mRandom.nextFloat() < mFreezerStatsdSampleRate) {
                FrameworkStatsLog.write(
                        FrameworkStatsLog.APP_FREEZE_CHANGED,
                        FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__UNFREEZE_APP,
                        pid,
                        processName,
                        frozenDuration,
                        FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__NONE, // deprecated
                        reason);
            }
        }

        @GuardedBy({"mAm"})
        @Override
        public void onBlockingFileLock(IntArray pids) {
            if (DEBUG_FREEZER) {
                Slog.d(TAG_AM, "Blocking file lock found: " + pids);
            }
            synchronized (mAm) {
                synchronized (mProcLock) {
                    int pid = pids.get(0);
                    ProcessRecord app = mFrozenProcesses.get(pid);
                    ProcessRecord pr;
                    if (app != null) {
                        for (int i = 1; i < pids.size(); i++) {
                            int blocked = pids.get(i);
                            synchronized (mAm.mPidsSelfLocked) {
                                pr = mAm.mPidsSelfLocked.get(blocked);
                            }
                            if (pr != null && !pr.isFreezable()) {
                                Slog.d(TAG_AM, app.processName + " (" + pid + ") blocks "
                                        + pr.processName + " (" + blocked + ")");
                                // Found at least one blocked unfrozen process
                                unfreezeAppLSP(app, UNFREEZE_REASON_FILE_LOCKS);
                                break;
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * Default implementation for ProcessDependencies, public for visibility to OomAdjuster class.
     */
    private static final class DefaultProcessDependencies implements ProcessDependencies {
        public static volatile int mPidCompacting = -1;

        // Get memory RSS from process.
        @Override
        public long[] getRss(int pid) {
            return Process.getRss(pid);
        }

        // Compact process.
        @Override
        public void performCompaction(CompactProfile profile, int pid) throws IOException {
            mPidCompacting = pid;
            if (profile == CompactProfile.FULL) {
                compactProcess(pid, COMPACT_ACTION_FILE_FLAG | COMPACT_ACTION_ANON_FLAG);
            } else if (profile == CompactProfile.SOME) {
                compactProcess(pid, COMPACT_ACTION_FILE_FLAG);
            } else if (profile == CompactProfile.ANON) {
                compactProcess(pid, COMPACT_ACTION_ANON_FLAG);
            }
            mPidCompacting = -1;
        }

        @Override
        public void performMemcgCompaction(CompactProfile profile, int uid, int pid)
                throws IOException {
            int compactionFlags = getCompactionFlags(profile);
            mPidCompacting = pid;
            performNativeMemcgCompaction(uid, pid, compactionFlags);
            mPidCompacting = -1;
        }

        // Compaction normally requires the UID to identify the correct cgroup path for the process
        // so that we can use memory cgroup reclaim without looking up its cgroup placement. Most
        // of the time the (real/effective/saved) UID of the process matches its UID cgroup
        // placement, but that is not true for native processes launched under adb shells which have
        // UIDs of 2000 but live under the adbd cgroup in uid_0 as a child or grandchild of adbd.
        // Both the UID and the PID would cause the generated cgroup path to be incorrect for shell
        // processes in this case. So here we do not attempt to use cgroup reclaim, and always scan
        // all VMAs and madvise the individual process.
        // An alternative would be to reclaim the adbd cgroup, but this would compact *all* shells
        // (and any running commands under them) which could be more than just the specified pid.
        @Override
        public void performNativeCompaction(CompactProfile profile, int pid)
                throws IOException {
            int compactionFlags = getCompactionFlags(profile);
            mPidCompacting = pid;
            compactNativeProcess(pid, compactionFlags);
            mPidCompacting = -1;
        }
    }

    static int getUnfreezeReasonCodeFromOomAdjReason(@OomAdjReason int oomAdjReason) {
        switch (oomAdjReason) {
            case OOM_ADJ_REASON_ACTIVITY:
                return UNFREEZE_REASON_ACTIVITY;
            case OOM_ADJ_REASON_FINISH_RECEIVER:
                return UNFREEZE_REASON_FINISH_RECEIVER;
            case OOM_ADJ_REASON_START_RECEIVER:
                return UNFREEZE_REASON_START_RECEIVER;
            case OOM_ADJ_REASON_BIND_SERVICE:
                return UNFREEZE_REASON_BIND_SERVICE;
            case OOM_ADJ_REASON_UNBIND_SERVICE:
                return UNFREEZE_REASON_UNBIND_SERVICE;
            case OOM_ADJ_REASON_START_SERVICE:
                return UNFREEZE_REASON_START_SERVICE;
            case OOM_ADJ_REASON_GET_PROVIDER:
                return UNFREEZE_REASON_GET_PROVIDER;
            case OOM_ADJ_REASON_REMOVE_PROVIDER:
                return UNFREEZE_REASON_REMOVE_PROVIDER;
            case OOM_ADJ_REASON_UI_VISIBILITY:
                return UNFREEZE_REASON_UI_VISIBILITY;
            case OOM_ADJ_REASON_ALLOWLIST:
                return UNFREEZE_REASON_ALLOWLIST;
            case OOM_ADJ_REASON_PROCESS_BEGIN:
                return UNFREEZE_REASON_PROCESS_BEGIN;
            case OOM_ADJ_REASON_PROCESS_END:
                return UNFREEZE_REASON_PROCESS_END;
            case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
                return UNFREEZE_REASON_SHORT_FGS_TIMEOUT;
            case OOM_ADJ_REASON_SYSTEM_INIT:
                return UNFREEZE_REASON_SYSTEM_INIT;
            case OOM_ADJ_REASON_BACKUP:
                return UNFREEZE_REASON_BACKUP;
            case OOM_ADJ_REASON_SHELL:
                return UNFREEZE_REASON_SHELL;
            case OOM_ADJ_REASON_REMOVE_TASK:
                return UNFREEZE_REASON_REMOVE_TASK;
            case OOM_ADJ_REASON_UID_IDLE:
                return UNFREEZE_REASON_UID_IDLE;
            case OOM_ADJ_REASON_STOP_SERVICE:
                return UNFREEZE_REASON_STOP_SERVICE;
            case OOM_ADJ_REASON_EXECUTING_SERVICE:
                return UNFREEZE_REASON_EXECUTING_SERVICE;
            case OOM_ADJ_REASON_RESTRICTION_CHANGE:
                return UNFREEZE_REASON_RESTRICTION_CHANGE;
            case OOM_ADJ_REASON_COMPONENT_DISABLED:
                return UNFREEZE_REASON_COMPONENT_DISABLED;
            case OOM_ADJ_REASON_FOLLOW_UP:
                return UNFREEZE_REASON_OOM_ADJ_FOLLOW_UP;
            case OOM_ADJ_REASON_RECONFIGURATION:
                return UNFREEZE_REASON_OOM_ADJ_RECONFIGURATION;
            case OOM_ADJ_REASON_SERVICE_BINDER_CALL:
                return UNFREEZE_REASON_OOM_ADJ_SERVICE_BINDER_CALL;
            case OOM_ADJ_REASON_BATCH_UPDATE_REQUEST:
                return UNFREEZE_REASON_OOM_ADJ_BATCH_UPDATE_REQUEST;
            default:
                return UNFREEZE_REASON_NONE;
        }
    }

    /**
     * Kill a frozen process with a specified reason
     */
    public void killProcess(int pid, String reason, @Reason int reasonCode,
            @SubReason int subReason) {
        mAm.mHandler.post(() -> {
            synchronized (mAm) {
                synchronized (mProcLock) {
                    ProcessRecord proc = mFrozenProcesses.get(pid);
                    // The process might have been killed or unfrozen by others
                    if (proc != null && proc.getThread() != null && !proc.isKilledByAm()) {
                        proc.killLocked(reason, reasonCode, subReason, true);
                    }
                }
            }
        });
    }

    /**
     * Freeze or unfreeze a process.  This should only be used for testing.
     */
    @VisibleForTesting
    void forceFreezeForTest(ProcessRecord proc, boolean freeze) {
        synchronized (mAm) {
            synchronized (mProcLock) {
                if (freeze) {
                    forceFreezeAppAsyncLSP(proc);
                } else {
                    unfreezeAppLSP(proc, UNFREEZE_REASON_NONE, true);
                }
            }
        }
    }

    /**
     * Sending binder transactions to frozen apps most likely indicates there's a bug. Log it and
     * kill the frozen apps if they 1) receive sync binder transactions while frozen, or 2) miss
     * async binder transactions due to kernel binder buffer running out.
     *
     * @param debugPid The binder transaction sender
     * @param app The ProcessRecord of the sender
     * @param code The binder transaction code
     * @param flags The binder transaction flags
     * @param err The binder transaction error
     */
    public void binderError(int debugPid, ProcessRecord app, int code, int flags, int err) {
        Slog.w(TAG_AM, "pid " + debugPid + " " + (app == null ? "null" : app.processName)
                + " sent binder code " + code + " with flags " + flags + " and got error " + err);

        // Do nothing if the binder error callback is not enabled.
        // That means the frozen apps in a wrong state will be killed when they are unfrozen later.
        if (!mUseFreezer || !mFreezerBinderCallbackEnabled) {
            return;
        }

        final long now = SystemClock.uptimeMillis();
        if (now < mFreezerBinderCallbackLast + mFreezerBinderCallbackThrottle) {
            Slog.d(TAG_AM, "Too many transaction errors, throttling freezer binder callback.");
            return;
        }
        mFreezerBinderCallbackLast = now;

        // Check all frozen processes in Freezer handler
        mFreezeHandler.sendEmptyMessage(BINDER_ERROR_MSG);
    }

    private void binderErrorInternal(IntArray pids) {
        // PIDs that run out of async binder buffer when being frozen
        ArraySet<Integer> pidsAsync = (mFreezerBinderAsyncThreshold < 0) ? null : new ArraySet<>();

        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "binderErrorSync");
        for (int i = 0; i < pids.size(); i++) {
            int current = pids.get(i);
            try {
                int freezeInfo = mFreezer.getBinderFreezeInfo(current);

                if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) {
                    killProcess(current, "Sync transaction while frozen",
                            ApplicationExitInfo.REASON_FREEZER,
                            ApplicationExitInfo.SUBREASON_FREEZER_BINDER_TRANSACTION);

                    // No need to check async transactions in this case
                    continue;
                }

                if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0) {
                    if (pidsAsync != null) {
                        pidsAsync.add(current);
                    }
                    if (DEBUG_FREEZER) {
                        Slog.w(TAG_AM, "pid " + current
                                + " received async transactions while frozen");
                    }
                }
            } catch (Exception e) {
                // The process has died. No need to kill it again.
                Slog.w(TAG_AM, "Unable to query binder frozen stats for pid " + current);
            }
        }
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

        // TODO: when kernel binder driver supports, poll the binder status directly.
        // Binderfs stats, like other debugfs files, is not a reliable interface. But it's the
        // only true source for now. The following code checks all frozen PIDs. If any of them
        // is running out of async binder buffer, kill it. Otherwise it will be killed at a
        // later time when AMS unfreezes it, which causes race issues.
        if (pidsAsync == null || pidsAsync.size() == 0) {
            return;
        }

        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "binderErrorAsync");
        new BinderfsStatsReader().handleFreeAsyncSpace(
                // Check if the frozen process has pending async calls
                pidsAsync::contains,

                // Kill the current process if it's running out of async binder space
                (current, free) -> {
                    if (free < mFreezerBinderAsyncThreshold) {
                        Slog.w(TAG_AM, "pid " + current
                                + " has " + free + " free async space, killing");
                        killProcess(current, "Async binder space running out while frozen",
                                ApplicationExitInfo.REASON_FREEZER,
                                ApplicationExitInfo.SUBREASON_FREEZER_BINDER_ASYNC_FULL);
                    }
                },

                // Log the error if binderfs stats can't be accesses or correctly parsed
                exception -> Slog.e(TAG_AM, "Unable to parse binderfs stats"));
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    }

    /**
     * Register a callback to notify when a process's frozen state changes.
     */
    public void addFrozenProcessListener(ProcessRecord app, Executor executor,
            FrozenProcessListener listener) {
        app.mOptRecord.addFrozenProcessListener(executor, listener);
    }
}
