/*
 * Copyright (C) 2017 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.power.batterysaver;

import android.annotation.IntDef;
import android.app.UiModeManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.BatterySaverPolicyConfig;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManager.ServiceType;
import android.os.PowerSaveState;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.KeyValueListParser;
import android.util.Slog;
import android.view.accessibility.AccessibilityManager;

import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ConcurrentUtils;
import com.android.server.power.PowerManagerService;

import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * Class to decide whether to turn on battery saver mode for specific services.
 *
 * IMPORTANT: This class shares the power manager lock, which is very low in the lock hierarchy.
 * Do not call out with the lock held, such as AccessibilityManager. (Settings provider is okay.)
 *
 * Test: atest com.android.server.power.batterysaver.BatterySaverPolicyTest
 */
public class BatterySaverPolicy extends ContentObserver {
    private static final String TAG = "BatterySaverPolicy";

    static final boolean DEBUG = false; // DO NOT SUBMIT WITH TRUE.

    private static final String KEY_GPS_MODE = "gps_mode";
    private static final String KEY_VIBRATION_DISABLED = "vibration_disabled";
    private static final String KEY_ANIMATION_DISABLED = "animation_disabled";
    private static final String KEY_SOUNDTRIGGER_DISABLED = "soundtrigger_disabled";

    /**
     * Disable turning on the network firewall when Battery Saver is turned on.
     * If set to false, the firewall WILL be turned on when Battery Saver is turned on.
     * If set to true, the firewall WILL NOT be turned on when Battery Saver is turned on.
     */
    private static final String KEY_ACTIVATE_FIREWALL_DISABLED = "firewall_disabled";

    /**
     * Disable turning on the special low power screen brightness dimming when Battery Saver is
     * turned on.
     * If set to false, the screen brightness dimming WILL be turned on by Battery Saver.
     * If set to true, the screen brightness WILL NOT be turned on by Battery Saver.
     */
    private static final String KEY_ADJUST_BRIGHTNESS_DISABLED = "adjust_brightness_disabled";

    /**
     * Disable turning on Data Saver when Battery Saver is turned on.
     * If set to false, Data Saver WILL be turned on when Battery Saver is turned on.
     * If set to true, Data Saver WILL NOT be turned on when Battery Saver is turned on.
     */
    private static final String KEY_ACTIVATE_DATASAVER_DISABLED = "datasaver_disabled";

    /**
     * {@code true} if the Policy should advertise to the rest of the system that battery saver
     * is enabled. This advertising could cause other system components to change their
     * behavior. This will not affect other policy flags and what they change.
     */
    private static final String KEY_ADVERTISE_IS_ENABLED = "advertise_is_enabled";

    private static final String KEY_LAUNCH_BOOST_DISABLED = "launch_boost_disabled";
    private static final String KEY_ADJUST_BRIGHTNESS_FACTOR = "adjust_brightness_factor";
    private static final String KEY_FULLBACKUP_DEFERRED = "fullbackup_deferred";
    private static final String KEY_KEYVALUE_DEFERRED = "keyvaluebackup_deferred";
    private static final String KEY_FORCE_ALL_APPS_STANDBY = "force_all_apps_standby";
    private static final String KEY_FORCE_BACKGROUND_CHECK = "force_background_check";
    private static final String KEY_OPTIONAL_SENSORS_DISABLED = "optional_sensors_disabled";
    private static final String KEY_AOD_DISABLED = "aod_disabled";
    // Go into deep Doze as soon as the screen turns off.
    private static final String KEY_QUICK_DOZE_ENABLED = "quick_doze_enabled";
    private static final String KEY_ENABLE_NIGHT_MODE = "enable_night_mode";

    private static final String KEY_CPU_FREQ_INTERACTIVE = "cpufreq-i";
    private static final String KEY_CPU_FREQ_NONINTERACTIVE = "cpufreq-n";

    @VisibleForTesting
    static final Policy OFF_POLICY = new Policy(
            1f,    /* adjustBrightnessFactor */
            false, /* advertiseIsEnabled */
            false, /* deferFullBackup */
            false, /* deferKeyValueBackup */
            false, /* disableAnimation */
            false, /* disableAod */
            false, /* disableLaunchBoost */
            false, /* disableOptionalSensors */
            false, /* disableSoundTrigger */
            false, /* disableVibration */
            false, /* enableAdjustBrightness */
            false, /* enableDataSaver */
            false, /* enableFireWall */
            false, /* enableNightMode */
            false, /* enableQuickDoze */
            new ArrayMap<>(), /* filesForInteractive */
            new ArrayMap<>(), /* filesForNoninteractive */
            false, /* forceAllAppsStandby */
            false, /* forceBackgroundCheck */
            PowerManager.LOCATION_MODE_NO_CHANGE /* locationMode */
    );

    private static final Policy DEFAULT_ADAPTIVE_POLICY = OFF_POLICY;

    private static final Policy DEFAULT_FULL_POLICY = new Policy(
            0.5f,  /* adjustBrightnessFactor */
            true,  /* advertiseIsEnabled */
            true,  /* deferFullBackup */
            true,  /* deferKeyValueBackup */
            false, /* disableAnimation */
            true,  /* disableAod */
            true,  /* disableLaunchBoost */
            true,  /* disableOptionalSensors */
            true,  /* disableSoundTrigger */
            true,  /* disableVibration */
            false, /* enableAdjustBrightness */
            false, /* enableDataSaver */
            true,  /* enableFirewall */
            true, /* enableNightMode */
            true, /* enableQuickDoze */
            new ArrayMap<>(), /* filesForInteractive */
            new ArrayMap<>(), /* filesForNoninteractive */
            true, /* forceAllAppsStandby */
            true, /* forceBackgroundCheck */
            PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF /* locationMode */
    );

    private final Object mLock;
    private final Handler mHandler;

    @GuardedBy("mLock")
    private String mSettings;

    @GuardedBy("mLock")
    private String mDeviceSpecificSettings;

    @GuardedBy("mLock")
    private String mDeviceSpecificSettingsSource; // For dump() only.

    @GuardedBy("mLock")
    private String mAdaptiveSettings;

    @GuardedBy("mLock")
    private String mAdaptiveDeviceSpecificSettings;

    /**
     * A short string describing which battery saver is now enabled, which we dump in the eventlog.
     */
    @GuardedBy("mLock")
    private String mEventLogKeys;

    /**
     * Whether accessibility is currently enabled or not.
     */
    @GuardedBy("mLock")
    private boolean mAccessibilityEnabled;

    /** Whether the phone is projecting in car mode or not. */
    @GuardedBy("mLock")
    private boolean mCarModeEnabled;

    /** The current default adaptive policy. */
    @GuardedBy("mLock")
    private Policy mDefaultAdaptivePolicy = DEFAULT_ADAPTIVE_POLICY;

    /** The policy that will be used for adaptive battery saver. */
    @GuardedBy("mLock")
    private Policy mAdaptivePolicy = DEFAULT_ADAPTIVE_POLICY;

    /** The policy to be used for full battery saver. */
    @GuardedBy("mLock")
    private Policy mFullPolicy = DEFAULT_FULL_POLICY;

    /**
     * The current effective policy. This is based on the current policy level's policy, with any
     * required adjustments.
     */
    @GuardedBy("mLock")
    private Policy mEffectivePolicyRaw = OFF_POLICY;

    @IntDef(prefix = {"POLICY_LEVEL_"}, value = {
            POLICY_LEVEL_OFF,
            POLICY_LEVEL_ADAPTIVE,
            POLICY_LEVEL_FULL,
    })
    @Retention(RetentionPolicy.SOURCE)
    @interface PolicyLevel {}

    static final int POLICY_LEVEL_OFF = 0;
    static final int POLICY_LEVEL_ADAPTIVE = 1;
    static final int POLICY_LEVEL_FULL = 2;

    @GuardedBy("mLock")
    private int mPolicyLevel = POLICY_LEVEL_OFF;

    private final Context mContext;
    private final ContentResolver mContentResolver;
    private final BatterySavingStats mBatterySavingStats;

    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            switch (intent.getAction()) {
                case UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED:
                    setCarModeEnabled(true);
                    break;
                case UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED:
                    setCarModeEnabled(false);
                    break;
            }
        }
    };

    @GuardedBy("mLock")
    private final List<BatterySaverPolicyListener> mListeners = new ArrayList<>();

    public interface BatterySaverPolicyListener {
        void onBatterySaverPolicyChanged(BatterySaverPolicy policy);
    }

    public BatterySaverPolicy(Object lock, Context context, BatterySavingStats batterySavingStats) {
        super(BackgroundThread.getHandler());
        mLock = lock;
        mHandler = BackgroundThread.getHandler();
        mContext = context;
        mContentResolver = context.getContentResolver();
        mBatterySavingStats = batterySavingStats;
    }

    /**
     * Called by {@link PowerManagerService#systemReady}, *with no lock held.*
     */
    public void systemReady() {
        ConcurrentUtils.wtfIfLockHeld(TAG, mLock);

        mContentResolver.registerContentObserver(Settings.Global.getUriFor(
                Settings.Global.BATTERY_SAVER_CONSTANTS), false, this);
        mContentResolver.registerContentObserver(Settings.Global.getUriFor(
                Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS), false, this);
        mContentResolver.registerContentObserver(Settings.Global.getUriFor(
                Settings.Global.BATTERY_SAVER_ADAPTIVE_CONSTANTS), false, this);
        mContentResolver.registerContentObserver(Settings.Global.getUriFor(
                Settings.Global.BATTERY_SAVER_ADAPTIVE_DEVICE_SPECIFIC_CONSTANTS), false, this);

        final AccessibilityManager acm = mContext.getSystemService(AccessibilityManager.class);

        acm.addAccessibilityStateChangeListener((enabled) -> setAccessibilityEnabled(enabled));
        final boolean accessibilityEnabled = acm.isEnabled();
        synchronized (mLock) {
            mAccessibilityEnabled = accessibilityEnabled;
        }

        final IntentFilter filter = new IntentFilter(
                UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED);
        filter.addAction(UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED);
        // The ENTER/EXIT_CAR_MODE_PRIORITIZED intents are sent to UserHandle.ALL, so no need to
        // register as all users here.
        mContext.registerReceiver(mBroadcastReceiver, filter);
        final boolean carModeEnabled =
                mContext.getSystemService(UiModeManager.class).getCurrentModeType()
                        == Configuration.UI_MODE_TYPE_CAR;
        synchronized (mLock) {
            mCarModeEnabled = carModeEnabled;
        }

        onChange(true, null);
    }

    @VisibleForTesting
    public void addListener(BatterySaverPolicyListener listener) {
        synchronized (mLock) {
            // TODO: set this in the constructor instead
            mListeners.add(listener);
        }
    }

    @VisibleForTesting
    String getGlobalSetting(String key) {
        return Settings.Global.getString(mContentResolver, key);
    }

    @VisibleForTesting
    int getDeviceSpecificConfigResId() {
        return R.string.config_batterySaverDeviceSpecificConfig;
    }

    @VisibleForTesting
    void invalidatePowerSaveModeCaches() {
        PowerManager.invalidatePowerSaveModeCaches();
    }

    /**
     * Notifies listeners of a policy change on the handler thread only if the current policy level
     * is not {@link POLICY_LEVEL_OFF}.
     */
    private void maybeNotifyListenersOfPolicyChange() {
        final BatterySaverPolicyListener[] listeners;
        synchronized (mLock) {
            if (mPolicyLevel == POLICY_LEVEL_OFF) {
                // Current policy is OFF, so there's no change to notify listeners of.
                return;
            }
            // Don't call out to listeners with the lock held.
            listeners = mListeners.toArray(new BatterySaverPolicyListener[mListeners.size()]);
        }

        mHandler.post(() -> {
            for (BatterySaverPolicyListener listener : listeners) {
                listener.onBatterySaverPolicyChanged(this);
            }
        });
    }

    @Override
    public void onChange(boolean selfChange, Uri uri) {
        refreshSettings();
    }

    private void refreshSettings() {
        synchronized (mLock) {
            // Load the non-device-specific setting.
            final String setting = getGlobalSetting(Settings.Global.BATTERY_SAVER_CONSTANTS);

            // Load the device specific setting.
            // We first check the global setting, and if it's empty or the string "null" is set,
            // use the default value from config.xml.
            String deviceSpecificSetting = getGlobalSetting(
                    Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS);
            mDeviceSpecificSettingsSource =
                    Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS;

            if (TextUtils.isEmpty(deviceSpecificSetting) || "null".equals(deviceSpecificSetting)) {
                deviceSpecificSetting =
                        mContext.getString(getDeviceSpecificConfigResId());
                mDeviceSpecificSettingsSource = "(overlay)";
            }

            final String adaptiveSetting =
                    getGlobalSetting(Settings.Global.BATTERY_SAVER_ADAPTIVE_CONSTANTS);
            final String adaptiveDeviceSpecificSetting = getGlobalSetting(
                    Settings.Global.BATTERY_SAVER_ADAPTIVE_DEVICE_SPECIFIC_CONSTANTS);

            if (!updateConstantsLocked(setting, deviceSpecificSetting,
                    adaptiveSetting, adaptiveDeviceSpecificSetting)) {
                // Nothing of note changed.
                return;
            }
        }

        maybeNotifyListenersOfPolicyChange();
    }

    @GuardedBy("mLock")
    @VisibleForTesting
    void updateConstantsLocked(final String setting, final String deviceSpecificSetting) {
        updateConstantsLocked(setting, deviceSpecificSetting, "", "");
    }

    /** @return true if the currently active policy changed. */
    private boolean updateConstantsLocked(String setting, String deviceSpecificSetting,
            String adaptiveSetting, String adaptiveDeviceSpecificSetting) {
        setting = TextUtils.emptyIfNull(setting);
        deviceSpecificSetting = TextUtils.emptyIfNull(deviceSpecificSetting);
        adaptiveSetting = TextUtils.emptyIfNull(adaptiveSetting);
        adaptiveDeviceSpecificSetting = TextUtils.emptyIfNull(adaptiveDeviceSpecificSetting);

        if (setting.equals(mSettings)
                && deviceSpecificSetting.equals(mDeviceSpecificSettings)
                && adaptiveSetting.equals(mAdaptiveSettings)
                && adaptiveDeviceSpecificSetting.equals(mAdaptiveDeviceSpecificSettings)) {
            return false;
        }

        mSettings = setting;
        mDeviceSpecificSettings = deviceSpecificSetting;
        mAdaptiveSettings = adaptiveSetting;
        mAdaptiveDeviceSpecificSettings = adaptiveDeviceSpecificSetting;

        if (DEBUG) {
            Slog.i(TAG, "mSettings=" + mSettings);
            Slog.i(TAG, "mDeviceSpecificSettings=" + mDeviceSpecificSettings);
            Slog.i(TAG, "mAdaptiveSettings=" + mAdaptiveSettings);
            Slog.i(TAG, "mAdaptiveDeviceSpecificSettings=" + mAdaptiveDeviceSpecificSettings);
        }

        boolean changed = false;
        Policy newFullPolicy = Policy.fromSettings(setting, deviceSpecificSetting,
                DEFAULT_FULL_POLICY);
        if (mPolicyLevel == POLICY_LEVEL_FULL && !mFullPolicy.equals(newFullPolicy)) {
            changed = true;
        }
        mFullPolicy = newFullPolicy;

        mDefaultAdaptivePolicy = Policy.fromSettings(adaptiveSetting, adaptiveDeviceSpecificSetting,
                DEFAULT_ADAPTIVE_POLICY);
        if (mPolicyLevel == POLICY_LEVEL_ADAPTIVE
                && !mAdaptivePolicy.equals(mDefaultAdaptivePolicy)) {
            changed = true;
        }
        // This will override any config set by an external source. This should be fine for now.
        // TODO: make sure it doesn't override what's set externally
        mAdaptivePolicy = mDefaultAdaptivePolicy;

        updatePolicyDependenciesLocked();

        return changed;
    }

    @GuardedBy("mLock")
    private void updatePolicyDependenciesLocked() {
        final Policy rawPolicy = getCurrentRawPolicyLocked();
        final int locationMode;

        invalidatePowerSaveModeCaches();
        if (mCarModeEnabled
                && rawPolicy.locationMode != PowerManager.LOCATION_MODE_NO_CHANGE
                && rawPolicy.locationMode != PowerManager.LOCATION_MODE_FOREGROUND_ONLY) {
            // If car projection is enabled, ensure that navigation works.
            locationMode = PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
        } else {
            locationMode = rawPolicy.locationMode;
        }

        mEffectivePolicyRaw = new Policy(
                rawPolicy.adjustBrightnessFactor,
                rawPolicy.advertiseIsEnabled,
                rawPolicy.deferFullBackup,
                rawPolicy.deferKeyValueBackup,
                rawPolicy.disableAnimation,
                rawPolicy.disableAod,
                rawPolicy.disableLaunchBoost,
                rawPolicy.disableOptionalSensors,
                rawPolicy.disableSoundTrigger,
                // Don't disable vibration when accessibility is on.
                rawPolicy.disableVibration && !mAccessibilityEnabled,
                rawPolicy.enableAdjustBrightness,
                rawPolicy.enableDataSaver,
                rawPolicy.enableFirewall,
                // Don't force night mode when car projection is enabled.
                rawPolicy.enableNightMode && !mCarModeEnabled,
                rawPolicy.enableQuickDoze,
                rawPolicy.filesForInteractive,
                rawPolicy.filesForNoninteractive,
                rawPolicy.forceAllAppsStandby,
                rawPolicy.forceBackgroundCheck,
                locationMode
        );


        final StringBuilder sb = new StringBuilder();

        if (mEffectivePolicyRaw.forceAllAppsStandby) sb.append("A");
        if (mEffectivePolicyRaw.forceBackgroundCheck) sb.append("B");

        if (mEffectivePolicyRaw.disableVibration) sb.append("v");
        if (mEffectivePolicyRaw.disableAnimation) sb.append("a");
        if (mEffectivePolicyRaw.disableSoundTrigger) sb.append("s");
        if (mEffectivePolicyRaw.deferFullBackup) sb.append("F");
        if (mEffectivePolicyRaw.deferKeyValueBackup) sb.append("K");
        if (mEffectivePolicyRaw.enableFirewall) sb.append("f");
        if (mEffectivePolicyRaw.enableDataSaver) sb.append("d");
        if (mEffectivePolicyRaw.enableAdjustBrightness) sb.append("b");

        if (mEffectivePolicyRaw.disableLaunchBoost) sb.append("l");
        if (mEffectivePolicyRaw.disableOptionalSensors) sb.append("S");
        if (mEffectivePolicyRaw.disableAod) sb.append("o");
        if (mEffectivePolicyRaw.enableQuickDoze) sb.append("q");

        sb.append(mEffectivePolicyRaw.locationMode);

        mEventLogKeys = sb.toString();
    }

    static class Policy {
        /**
         * This is the flag to decide the how much to adjust the screen brightness. This is
         * the float value from 0 to 1 where 1 means don't change brightness.
         *
         * @see Settings.Global#BATTERY_SAVER_CONSTANTS
         * @see #KEY_ADJUST_BRIGHTNESS_FACTOR
         */
        public final float adjustBrightnessFactor;

        /**
         * {@code true} if the Policy should advertise to the rest of the system that battery saver
         * is enabled. This advertising could cause other system components to change their
         * behavior. This will not affect other policy flags and what they change.
         *
         * @see Settings.Global#BATTERY_SAVER_CONSTANTS
         * @see #KEY_ADVERTISE_IS_ENABLED
         */
        public final boolean advertiseIsEnabled;

        /**
         * {@code true} if full backup is deferred in battery saver mode.
         *
         * @see Settings.Global#BATTERY_SAVER_CONSTANTS
         * @see #KEY_FULLBACKUP_DEFERRED
         */
        public final boolean deferFullBackup;

        /**
         * {@code true} if key value backup is deferred in battery saver mode.
         *
         * @see Settings.Global#BATTERY_SAVER_CONSTANTS
         * @see #KEY_KEYVALUE_DEFERRED
         */
        public final boolean deferKeyValueBackup;

        /**
         * {@code true} if animation is disabled in battery saver mode.
         *
         * @see Settings.Global#BATTERY_SAVER_CONSTANTS
         * @see #KEY_ANIMATION_DISABLED
         */
        public final boolean disableAnimation;

        /**
         * {@code true} if AOD is disabled in battery saver mode.
         */
        public final boolean disableAod;

        /**
         * {@code true} if launch boost should be disabled on battery saver.
         */
        public final boolean disableLaunchBoost;

        /**
         * Whether to show non-essential sensors (e.g. edge sensors) or not.
         */
        public final boolean disableOptionalSensors;

        /**
         * {@code true} if sound trigger is disabled in battery saver mode
         * in battery saver mode.
         *
         * @see Settings.Global#BATTERY_SAVER_CONSTANTS
         * @see #KEY_SOUNDTRIGGER_DISABLED
         */
        public final boolean disableSoundTrigger;

        /**
         * {@code true} if vibration is disabled in battery saver mode.
         *
         * @see Settings.Global#BATTERY_SAVER_CONSTANTS
         * @see #KEY_VIBRATION_DISABLED
         */
        public final boolean disableVibration;

        /**
         * {@code true} if low power mode brightness adjustment should be turned on in battery saver
         * mode.
         *
         * @see Settings.Global#BATTERY_SAVER_CONSTANTS
         * @see #KEY_ADJUST_BRIGHTNESS_DISABLED
         */
        public final boolean enableAdjustBrightness;

        /**
         * {@code true} if data saver should be turned on in battery saver mode.
         *
         * @see Settings.Global#BATTERY_SAVER_CONSTANTS
         * @see #KEY_ACTIVATE_DATASAVER_DISABLED
         */
        public final boolean enableDataSaver;

        /**
         * {@code true} if network policy firewall should be turned on in battery saver mode.
         *
         * @see Settings.Global#BATTERY_SAVER_CONSTANTS
         * @see #KEY_ACTIVATE_FIREWALL_DISABLED
         */
        public final boolean enableFirewall;

        /**
         * Whether to enable night mode or not.
         */
        public final boolean enableNightMode;

        /**
         * Whether Quick Doze is enabled or not.
         */
        public final boolean enableQuickDoze;

        /**
         * List of [Filename -> content] that should be written when battery saver is activated
         * and the device is interactive.
         *
         * We use this to change the max CPU frequencies.
         */
        public final ArrayMap<String, String> filesForInteractive;

        /**
         * List of [Filename -> content] that should be written when battery saver is activated
         * and the device is non-interactive.
         *
         * We use this to change the max CPU frequencies.
         */
        public final ArrayMap<String, String> filesForNoninteractive;

        /**
         * Whether to put all apps in the stand-by mode.
         */
        public final boolean forceAllAppsStandby;

        /**
         * Whether to force background check.
         */
        public final boolean forceBackgroundCheck;

        /**
         * This is the flag to decide the location mode in battery saver mode. This was
         * previously called gpsMode.
         *
         * @see Settings.Global#BATTERY_SAVER_CONSTANTS
         * @see #KEY_GPS_MODE
         */
        public final int locationMode;

        private final int mHashCode;

        Policy(
                float adjustBrightnessFactor,
                boolean advertiseIsEnabled,
                boolean deferFullBackup,
                boolean deferKeyValueBackup,
                boolean disableAnimation,
                boolean disableAod,
                boolean disableLaunchBoost,
                boolean disableOptionalSensors,
                boolean disableSoundTrigger,
                boolean disableVibration,
                boolean enableAdjustBrightness,
                boolean enableDataSaver,
                boolean enableFirewall,
                boolean enableNightMode,
                boolean enableQuickDoze,
                ArrayMap<String, String> filesForInteractive,
                ArrayMap<String, String> filesForNoninteractive,
                boolean forceAllAppsStandby,
                boolean forceBackgroundCheck,
                int locationMode) {

            this.adjustBrightnessFactor = Math.min(1, Math.max(0, adjustBrightnessFactor));
            this.advertiseIsEnabled = advertiseIsEnabled;
            this.deferFullBackup = deferFullBackup;
            this.deferKeyValueBackup = deferKeyValueBackup;
            this.disableAnimation = disableAnimation;
            this.disableAod = disableAod;
            this.disableLaunchBoost = disableLaunchBoost;
            this.disableOptionalSensors = disableOptionalSensors;
            this.disableSoundTrigger = disableSoundTrigger;
            this.disableVibration = disableVibration;
            this.enableAdjustBrightness = enableAdjustBrightness;
            this.enableDataSaver = enableDataSaver;
            this.enableFirewall = enableFirewall;
            this.enableNightMode = enableNightMode;
            this.enableQuickDoze = enableQuickDoze;
            this.filesForInteractive = filesForInteractive;
            this.filesForNoninteractive = filesForNoninteractive;
            this.forceAllAppsStandby = forceAllAppsStandby;
            this.forceBackgroundCheck = forceBackgroundCheck;

            if (locationMode < PowerManager.MIN_LOCATION_MODE
                    || PowerManager.MAX_LOCATION_MODE < locationMode) {
                Slog.e(TAG, "Invalid location mode: " + locationMode);
                this.locationMode = PowerManager.LOCATION_MODE_NO_CHANGE;
            } else {
                this.locationMode = locationMode;
            }

            mHashCode = Objects.hash(
                    adjustBrightnessFactor,
                    advertiseIsEnabled,
                    deferFullBackup,
                    deferKeyValueBackup,
                    disableAnimation,
                    disableAod,
                    disableLaunchBoost,
                    disableOptionalSensors,
                    disableSoundTrigger,
                    disableVibration,
                    enableAdjustBrightness,
                    enableDataSaver,
                    enableFirewall,
                    enableNightMode,
                    enableQuickDoze,
                    filesForInteractive,
                    filesForNoninteractive,
                    forceAllAppsStandby,
                    forceBackgroundCheck,
                    locationMode);
        }

        static Policy fromConfig(BatterySaverPolicyConfig config) {
            if (config == null) {
                Slog.e(TAG, "Null config passed down to BatterySaverPolicy");
                return OFF_POLICY;
            }

            // Device-specific parameters.
            Map<String, String> deviceSpecificSettings = config.getDeviceSpecificSettings();
            final String cpuFreqInteractive =
                    deviceSpecificSettings.getOrDefault(KEY_CPU_FREQ_INTERACTIVE, "");
            final String cpuFreqNoninteractive =
                    deviceSpecificSettings.getOrDefault(KEY_CPU_FREQ_NONINTERACTIVE, "");

            return new Policy(
                    config.getAdjustBrightnessFactor(),
                    config.getAdvertiseIsEnabled(),
                    config.getDeferFullBackup(),
                    config.getDeferKeyValueBackup(),
                    config.getDisableAnimation(),
                    config.getDisableAod(),
                    config.getDisableLaunchBoost(),
                    config.getDisableOptionalSensors(),
                    config.getDisableSoundTrigger(),
                    config.getDisableVibration(),
                    config.getEnableAdjustBrightness(),
                    config.getEnableDataSaver(),
                    config.getEnableFirewall(),
                    config.getEnableNightMode(),
                    config.getEnableQuickDoze(),
                    /* filesForInteractive */
                    (new CpuFrequencies()).parseString(cpuFreqInteractive).toSysFileMap(),
                    /* filesForNoninteractive */
                    (new CpuFrequencies()).parseString(cpuFreqNoninteractive).toSysFileMap(),
                    config.getForceAllAppsStandby(),
                    config.getForceBackgroundCheck(),
                    config.getLocationMode()
            );
        }

        static Policy fromSettings(String settings, String deviceSpecificSettings) {
            return fromSettings(settings, deviceSpecificSettings, OFF_POLICY);
        }

        static Policy fromSettings(String settings, String deviceSpecificSettings,
                Policy defaultPolicy) {
            final KeyValueListParser parser = new KeyValueListParser(',');

            // Device-specific parameters.
            try {
                parser.setString(deviceSpecificSettings == null ? "" : deviceSpecificSettings);
            } catch (IllegalArgumentException e) {
                Slog.wtf(TAG, "Bad device specific battery saver constants: "
                        + deviceSpecificSettings);
            }

            final String cpuFreqInteractive = parser.getString(KEY_CPU_FREQ_INTERACTIVE, "");
            final String cpuFreqNoninteractive = parser.getString(KEY_CPU_FREQ_NONINTERACTIVE, "");

            // Non-device-specific parameters.
            try {
                parser.setString(settings == null ? "" : settings);
            } catch (IllegalArgumentException e) {
                Slog.wtf(TAG, "Bad battery saver constants: " + settings);
            }

            float adjustBrightnessFactor = parser.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR,
                    defaultPolicy.adjustBrightnessFactor);
            boolean advertiseIsEnabled = parser.getBoolean(KEY_ADVERTISE_IS_ENABLED,
                    defaultPolicy.advertiseIsEnabled);
            boolean deferFullBackup = parser.getBoolean(KEY_FULLBACKUP_DEFERRED,
                    defaultPolicy.deferFullBackup);
            boolean deferKeyValueBackup = parser.getBoolean(KEY_KEYVALUE_DEFERRED,
                    defaultPolicy.deferKeyValueBackup);
            boolean disableAnimation = parser.getBoolean(KEY_ANIMATION_DISABLED,
                    defaultPolicy.disableAnimation);
            boolean disableAod = parser.getBoolean(KEY_AOD_DISABLED, defaultPolicy.disableAod);
            boolean disableLaunchBoost = parser.getBoolean(KEY_LAUNCH_BOOST_DISABLED,
                    defaultPolicy.disableLaunchBoost);
            boolean disableOptionalSensors = parser.getBoolean(KEY_OPTIONAL_SENSORS_DISABLED,
                    defaultPolicy.disableOptionalSensors);
            boolean disableSoundTrigger = parser.getBoolean(KEY_SOUNDTRIGGER_DISABLED,
                    defaultPolicy.disableSoundTrigger);
            boolean disableVibrationConfig = parser.getBoolean(KEY_VIBRATION_DISABLED,
                    defaultPolicy.disableVibration);
            boolean enableAdjustBrightness = !parser.getBoolean(KEY_ADJUST_BRIGHTNESS_DISABLED,
                    !defaultPolicy.enableAdjustBrightness);
            boolean enableDataSaver = !parser.getBoolean(KEY_ACTIVATE_DATASAVER_DISABLED,
                    !defaultPolicy.enableDataSaver);
            boolean enableFirewall = !parser.getBoolean(KEY_ACTIVATE_FIREWALL_DISABLED,
                    !defaultPolicy.enableFirewall);
            boolean enableNightMode = parser.getBoolean(KEY_ENABLE_NIGHT_MODE,
                    defaultPolicy.enableNightMode);
            boolean enableQuickDoze = parser.getBoolean(KEY_QUICK_DOZE_ENABLED,
                    defaultPolicy.enableQuickDoze);
            boolean forceAllAppsStandby = parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY,
                    defaultPolicy.forceAllAppsStandby);
            boolean forceBackgroundCheck = parser.getBoolean(KEY_FORCE_BACKGROUND_CHECK,
                    defaultPolicy.forceBackgroundCheck);
            int locationMode = parser.getInt(KEY_GPS_MODE, defaultPolicy.locationMode);

            return new Policy(
                    adjustBrightnessFactor,
                    advertiseIsEnabled,
                    deferFullBackup,
                    deferKeyValueBackup,
                    disableAnimation,
                    disableAod,
                    disableLaunchBoost,
                    disableOptionalSensors,
                    disableSoundTrigger,
                    /* disableVibration */
                    disableVibrationConfig,
                    enableAdjustBrightness,
                    enableDataSaver,
                    enableFirewall,
                    enableNightMode,
                    enableQuickDoze,
                    /* filesForInteractive */
                    (new CpuFrequencies()).parseString(cpuFreqInteractive).toSysFileMap(),
                    /* filesForNoninteractive */
                    (new CpuFrequencies()).parseString(cpuFreqNoninteractive).toSysFileMap(),
                    forceAllAppsStandby,
                    forceBackgroundCheck,
                    locationMode
            );
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (!(obj instanceof Policy)) return false;
            Policy other = (Policy) obj;
            return Float.compare(other.adjustBrightnessFactor, adjustBrightnessFactor) == 0
                    && advertiseIsEnabled == other.advertiseIsEnabled
                    && deferFullBackup == other.deferFullBackup
                    && deferKeyValueBackup == other.deferKeyValueBackup
                    && disableAnimation == other.disableAnimation
                    && disableAod == other.disableAod
                    && disableLaunchBoost == other.disableLaunchBoost
                    && disableOptionalSensors == other.disableOptionalSensors
                    && disableSoundTrigger == other.disableSoundTrigger
                    && disableVibration == other.disableVibration
                    && enableAdjustBrightness == other.enableAdjustBrightness
                    && enableDataSaver == other.enableDataSaver
                    && enableFirewall == other.enableFirewall
                    && enableNightMode == other.enableNightMode
                    && enableQuickDoze == other.enableQuickDoze
                    && forceAllAppsStandby == other.forceAllAppsStandby
                    && forceBackgroundCheck == other.forceBackgroundCheck
                    && locationMode == other.locationMode
                    && filesForInteractive.equals(other.filesForInteractive)
                    && filesForNoninteractive.equals(other.filesForNoninteractive);
        }

        @Override
        public int hashCode() {
            return mHashCode;
        }
    }

    /**
     * Get the {@link PowerSaveState} based on the current policy level.
     * The result will have {@link PowerSaveState#batterySaverEnabled} and some other
     * parameters when necessary.
     *
     * @param type   type of the service, one of {@link ServiceType}
     * @return State data that contains battery saver data
     */
    public PowerSaveState getBatterySaverPolicy(@ServiceType int type) {
        synchronized (mLock) {
            final Policy currPolicy = getCurrentPolicyLocked();
            final PowerSaveState.Builder builder = new PowerSaveState.Builder()
                    .setGlobalBatterySaverEnabled(currPolicy.advertiseIsEnabled);
            switch (type) {
                case ServiceType.LOCATION:
                    boolean isEnabled = currPolicy.advertiseIsEnabled
                            || currPolicy.locationMode != PowerManager.LOCATION_MODE_NO_CHANGE;
                    return builder.setBatterySaverEnabled(isEnabled)
                            .setLocationMode(currPolicy.locationMode)
                            .build();
                case ServiceType.ANIMATION:
                    return builder.setBatterySaverEnabled(currPolicy.disableAnimation)
                            .build();
                case ServiceType.FULL_BACKUP:
                    return builder.setBatterySaverEnabled(currPolicy.deferFullBackup)
                            .build();
                case ServiceType.KEYVALUE_BACKUP:
                    return builder.setBatterySaverEnabled(currPolicy.deferKeyValueBackup)
                            .build();
                case ServiceType.NETWORK_FIREWALL:
                    return builder.setBatterySaverEnabled(currPolicy.enableFirewall)
                            .build();
                case ServiceType.SCREEN_BRIGHTNESS:
                    return builder.setBatterySaverEnabled(currPolicy.enableAdjustBrightness)
                            .setBrightnessFactor(currPolicy.adjustBrightnessFactor)
                            .build();
                case ServiceType.DATA_SAVER:
                    return builder.setBatterySaverEnabled(currPolicy.enableDataSaver)
                            .build();
                case ServiceType.SOUND:
                    return builder.setBatterySaverEnabled(currPolicy.disableSoundTrigger)
                            .build();
                case ServiceType.VIBRATION:
                    return builder.setBatterySaverEnabled(currPolicy.disableVibration)
                            .build();
                case ServiceType.FORCE_ALL_APPS_STANDBY:
                    return builder.setBatterySaverEnabled(currPolicy.forceAllAppsStandby)
                            .build();
                case ServiceType.FORCE_BACKGROUND_CHECK:
                    return builder.setBatterySaverEnabled(currPolicy.forceBackgroundCheck)
                            .build();
                case ServiceType.NIGHT_MODE:
                    return builder.setBatterySaverEnabled(currPolicy.enableNightMode)
                            .build();
                case ServiceType.OPTIONAL_SENSORS:
                    return builder.setBatterySaverEnabled(currPolicy.disableOptionalSensors)
                            .build();
                case ServiceType.AOD:
                    return builder.setBatterySaverEnabled(currPolicy.disableAod)
                            .build();
                case ServiceType.QUICK_DOZE:
                    return builder.setBatterySaverEnabled(currPolicy.enableQuickDoze)
                            .build();
                default:
                    return builder.setBatterySaverEnabled(currPolicy.advertiseIsEnabled)
                            .build();
            }
        }
    }

    /**
     * Sets the current policy.
     *
     * @return true if the policy level was changed.
     */
    boolean setPolicyLevel(@PolicyLevel int level) {
        synchronized (mLock) {
            if (mPolicyLevel == level) {
                return false;
            }
            switch (level) {
                case POLICY_LEVEL_FULL:
                case POLICY_LEVEL_ADAPTIVE:
                case POLICY_LEVEL_OFF:
                    mPolicyLevel = level;
                    break;
                default:
                    Slog.wtf(TAG, "setPolicyLevel invalid level given: " + level);
                    return false;
            }
            updatePolicyDependenciesLocked();
            return true;
        }
    }

    /** @return true if the current policy changed and the policy level is ADAPTIVE. */
    boolean setAdaptivePolicyLocked(Policy p) {
        if (p == null) {
            Slog.wtf(TAG, "setAdaptivePolicy given null policy");
            return false;
        }
        if (mAdaptivePolicy.equals(p)) {
            return false;
        }

        mAdaptivePolicy = p;
        if (mPolicyLevel == POLICY_LEVEL_ADAPTIVE) {
            updatePolicyDependenciesLocked();
            return true;
        }
        return false;
    }

    /** @return true if the current policy changed and the policy level is ADAPTIVE. */
    boolean resetAdaptivePolicyLocked() {
        return setAdaptivePolicyLocked(mDefaultAdaptivePolicy);
    }

    private Policy getCurrentPolicyLocked() {
        return mEffectivePolicyRaw;
    }

    private Policy getCurrentRawPolicyLocked() {
        switch (mPolicyLevel) {
            case POLICY_LEVEL_FULL:
                return mFullPolicy;
            case POLICY_LEVEL_ADAPTIVE:
                return mAdaptivePolicy;
            case POLICY_LEVEL_OFF:
            default:
                return OFF_POLICY;
        }
    }

    public int getGpsMode() {
        synchronized (mLock) {
            return getCurrentPolicyLocked().locationMode;
        }
    }

    public ArrayMap<String, String> getFileValues(boolean interactive) {
        synchronized (mLock) {
            return interactive ? getCurrentPolicyLocked().filesForInteractive
                    : getCurrentPolicyLocked().filesForNoninteractive;
        }
    }

    public boolean isLaunchBoostDisabled() {
        synchronized (mLock) {
            return getCurrentPolicyLocked().disableLaunchBoost;
        }
    }

    boolean shouldAdvertiseIsEnabled() {
        synchronized (mLock) {
            return getCurrentPolicyLocked().advertiseIsEnabled;
        }
    }

    public String toEventLogString() {
        synchronized (mLock) {
            return mEventLogKeys;
        }
    }

    public void dump(PrintWriter pw) {
        synchronized (mLock) {
            pw.println();
            mBatterySavingStats.dump(pw, "");

            pw.println();
            pw.println("Battery saver policy (*NOTE* they only apply when battery saver is ON):");
            pw.println("  Settings: " + Settings.Global.BATTERY_SAVER_CONSTANTS);
            pw.println("    value: " + mSettings);
            pw.println("  Settings: " + mDeviceSpecificSettingsSource);
            pw.println("    value: " + mDeviceSpecificSettings);

            pw.println("  Adaptive Settings: " + Settings.Global.BATTERY_SAVER_ADAPTIVE_CONSTANTS);
            pw.println("    value: " + mAdaptiveSettings);
            pw.println("  Adaptive Device Specific Settings: "
                    + Settings.Global.BATTERY_SAVER_ADAPTIVE_DEVICE_SPECIFIC_CONSTANTS);
            pw.println("    value: " + mAdaptiveDeviceSpecificSettings);

            pw.println("  mAccessibilityEnabled=" + mAccessibilityEnabled);
            pw.println("  mCarModeEnabled=" + mCarModeEnabled);
            pw.println("  mPolicyLevel=" + mPolicyLevel);

            dumpPolicyLocked(pw, "  ", "full", mFullPolicy);
            dumpPolicyLocked(pw, "  ", "default adaptive", mDefaultAdaptivePolicy);
            dumpPolicyLocked(pw, "  ", "current adaptive", mAdaptivePolicy);
            dumpPolicyLocked(pw, "  ", "effective", mEffectivePolicyRaw);
        }
    }

    private void dumpPolicyLocked(PrintWriter pw, String indent, String label, Policy p) {
        pw.println();
        pw.print(indent);
        pw.println("Policy '" + label + "'");
        pw.print(indent);
        pw.println("  " + KEY_ADVERTISE_IS_ENABLED + "=" + p.advertiseIsEnabled);
        pw.print(indent);
        pw.println("  " + KEY_VIBRATION_DISABLED + "=" + p.disableVibration);
        pw.print(indent);
        pw.println("  " + KEY_ANIMATION_DISABLED + "=" + p.disableAnimation);
        pw.print(indent);
        pw.println("  " + KEY_FULLBACKUP_DEFERRED + "=" + p.deferFullBackup);
        pw.print(indent);
        pw.println("  " + KEY_KEYVALUE_DEFERRED + "=" + p.deferKeyValueBackup);
        pw.print(indent);
        pw.println("  " + KEY_ACTIVATE_FIREWALL_DISABLED + "=" + !p.enableFirewall);
        pw.print(indent);
        pw.println("  " + KEY_ACTIVATE_DATASAVER_DISABLED + "=" + !p.enableDataSaver);
        pw.print(indent);
        pw.println("  " + KEY_LAUNCH_BOOST_DISABLED + "=" + p.disableLaunchBoost);
        pw.println(
                "    " + KEY_ADJUST_BRIGHTNESS_DISABLED + "=" + !p.enableAdjustBrightness);
        pw.print(indent);
        pw.println("  " + KEY_ADJUST_BRIGHTNESS_FACTOR + "=" + p.adjustBrightnessFactor);
        pw.print(indent);
        pw.println("  " + KEY_GPS_MODE + "=" + p.locationMode);
        pw.print(indent);
        pw.println("  " + KEY_FORCE_ALL_APPS_STANDBY + "=" + p.forceAllAppsStandby);
        pw.print(indent);
        pw.println("  " + KEY_FORCE_BACKGROUND_CHECK + "=" + p.forceBackgroundCheck);
        pw.println(
                "    " + KEY_OPTIONAL_SENSORS_DISABLED + "=" + p.disableOptionalSensors);
        pw.print(indent);
        pw.println("  " + KEY_AOD_DISABLED + "=" + p.disableAod);
        pw.print(indent);
        pw.println("  " + KEY_SOUNDTRIGGER_DISABLED + "=" + p.disableSoundTrigger);
        pw.print(indent);
        pw.println("  " + KEY_QUICK_DOZE_ENABLED + "=" + p.enableQuickDoze);
        pw.print(indent);
        pw.println("  " + KEY_ENABLE_NIGHT_MODE + "=" + p.enableNightMode);

        pw.print("    Interactive File values:\n");
        dumpMap(pw, "      ", p.filesForInteractive);
        pw.println();

        pw.print("    Noninteractive File values:\n");
        dumpMap(pw, "      ", p.filesForNoninteractive);
    }

    private void dumpMap(PrintWriter pw, String prefix, ArrayMap<String, String> map) {
        if (map == null) {
            return;
        }
        final int size = map.size();
        for (int i = 0; i < size; i++) {
            pw.print(prefix);
            pw.print(map.keyAt(i));
            pw.print(": '");
            pw.print(map.valueAt(i));
            pw.println("'");
        }
    }

    @VisibleForTesting
    void setAccessibilityEnabled(boolean enabled) {
        synchronized (mLock) {
            if (mAccessibilityEnabled != enabled) {
                mAccessibilityEnabled = enabled;
                updatePolicyDependenciesLocked();
                maybeNotifyListenersOfPolicyChange();
            }
        }
    }

    @VisibleForTesting
    void setCarModeEnabled(boolean enabled) {
        synchronized (mLock) {
            if (mCarModeEnabled != enabled) {
                mCarModeEnabled = enabled;
                updatePolicyDependenciesLocked();
                maybeNotifyListenersOfPolicyChange();
            }
        }
    }
}
