/*
 * Copyright (C) 2020 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.vibrator;

import static android.os.VibrationAttributes.USAGE_ACCESSIBILITY;
import static android.os.VibrationAttributes.USAGE_ALARM;
import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK;
import static android.os.VibrationAttributes.USAGE_MEDIA;
import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
import static android.os.VibrationAttributes.USAGE_PHYSICAL_EMULATION;
import static android.os.VibrationAttributes.USAGE_RINGTONE;
import static android.os.VibrationAttributes.USAGE_TOUCH;
import static android.os.VibrationAttributes.USAGE_UNKNOWN;

import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.IUidObserver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManagerInternal;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.Vibrator.VibrationIntensity;
import android.os.vibrator.VibrationConfig;
import android.provider.Settings;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
import android.view.Display;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/** Controls all the system settings related to vibration. */
final class VibrationSettings {
    private static final String TAG = "VibrationSettings";

    /**
     * Set of usages allowed for vibrations from background processes.
     *
     * <p>Some examples are notification, ringtone or alarm vibrations, that are allowed to vibrate
     * unexpectedly as they are meant to grab the user's attention. Hardware feedback and physical
     * emulation are also supported, as the trigger process might still be in the background when
     * the user interaction wakes the device.
     */
    private static final Set<Integer> BACKGROUND_PROCESS_USAGE_ALLOWLIST = new HashSet<>(
            Arrays.asList(
                    USAGE_RINGTONE,
                    USAGE_ALARM,
                    USAGE_NOTIFICATION,
                    USAGE_COMMUNICATION_REQUEST,
                    USAGE_HARDWARE_FEEDBACK,
                    USAGE_PHYSICAL_EMULATION));

    /**
     * Set of usages allowed for vibrations in battery saver mode (low power).
     *
     * <p>Some examples are ringtone or alarm vibrations, that have high priority and should vibrate
     * even when the device is saving battery.
     */
    private static final Set<Integer> BATTERY_SAVER_USAGE_ALLOWLIST = new HashSet<>(
            Arrays.asList(
                    USAGE_RINGTONE,
                    USAGE_ALARM,
                    USAGE_COMMUNICATION_REQUEST,
                    USAGE_PHYSICAL_EMULATION,
                    USAGE_HARDWARE_FEEDBACK));

    /**
     * Usage allowed for vibrations when {@link Settings.System#VIBRATE_ON} is disabled.
     *
     * <p>The only allowed usage is accessibility, which is applied when the user enables talkback.
     * Other usages that must ignore this setting should use
     * {@link VibrationAttributes#FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF}.
     */
    private static final int VIBRATE_ON_DISABLED_USAGE_ALLOWED = USAGE_ACCESSIBILITY;

    /**
     * Set of usages allowed for vibrations from system packages when the screen goes off.
     *
     * <p>Some examples are touch and hardware feedback, and physical emulation. When the system is
     * playing one of these usages during the screen off event then the vibration will not be
     * cancelled by the service.
     */
    private static final Set<Integer> SYSTEM_VIBRATION_SCREEN_OFF_USAGE_ALLOWLIST = new HashSet<>(
            Arrays.asList(
                    USAGE_TOUCH,
                    USAGE_PHYSICAL_EMULATION,
                    USAGE_HARDWARE_FEEDBACK));

    /**
     * Set of reasons for {@link PowerManager} going to sleep events that allows vibrations to
     * continue running.
     *
     * <p>Some examples are timeout and inattentive, which indicates automatic screen off events.
     * When a vibration is playing during one of these screen off events then it will not be
     * cancelled by the service.
     */
    private static final Set<Integer> POWER_MANAGER_SLEEP_REASON_ALLOWLIST = new HashSet<>(
            Arrays.asList(
                    PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE,
                    PowerManager.GO_TO_SLEEP_REASON_TIMEOUT));

    private static final IntentFilter USER_SWITCHED_INTENT_FILTER =
            new IntentFilter(Intent.ACTION_USER_SWITCHED);
    private static final IntentFilter INTERNAL_RINGER_MODE_CHANGED_INTENT_FILTER =
            new IntentFilter(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);

    /** Listener for changes on vibration settings. */
    interface OnVibratorSettingsChanged {
        /** Callback triggered when any of the vibrator settings change. */
        void onChange();
    }

    private final Object mLock = new Object();
    private final Context mContext;
    private final String mSystemUiPackage;
    @VisibleForTesting
    final SettingsContentObserver mSettingObserver;
    @VisibleForTesting
    final UidObserver mUidObserver;
    @VisibleForTesting
    final SettingsBroadcastReceiver mSettingChangeReceiver;
    final VirtualDeviceListener mVirtualDeviceListener;

    @GuardedBy("mLock")
    private final List<OnVibratorSettingsChanged> mListeners = new ArrayList<>();
    private final SparseArray<VibrationEffect> mFallbackEffects;

    private final VibrationConfig mVibrationConfig;

    @GuardedBy("mLock")
    @Nullable
    private AudioManager mAudioManager;
    @GuardedBy("mLock")
    @Nullable
    private PowerManagerInternal mPowerManagerInternal;

    @GuardedBy("mLock")
    private boolean mVibrateInputDevices;
    @GuardedBy("mLock")
    private SparseIntArray mCurrentVibrationIntensities = new SparseIntArray();
    @GuardedBy("mLock")
    private boolean mBatterySaverMode;
    @GuardedBy("mLock")
    private boolean mVibrateOn;
    @GuardedBy("mLock")
    private int mRingerMode;

    VibrationSettings(Context context, Handler handler) {
        this(context, handler, new VibrationConfig(context.getResources()));
    }

    @VisibleForTesting
    VibrationSettings(Context context, Handler handler, VibrationConfig config) {
        mContext = context;
        mVibrationConfig = config;
        mSettingObserver = new SettingsContentObserver(handler);
        mUidObserver = new UidObserver();
        mSettingChangeReceiver = new SettingsBroadcastReceiver();
        mVirtualDeviceListener = new VirtualDeviceListener();

        mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class)
                .getSystemUiServiceComponent().getPackageName();

        VibrationEffect clickEffect = createEffectFromResource(
                com.android.internal.R.array.config_virtualKeyVibePattern);
        VibrationEffect doubleClickEffect = createEffectFromResource(
                com.android.internal.R.array.config_doubleClickVibePattern);
        VibrationEffect heavyClickEffect = createEffectFromResource(
                com.android.internal.R.array.config_longPressVibePattern);
        VibrationEffect tickEffect = createEffectFromResource(
                com.android.internal.R.array.config_clockTickVibePattern);

        mFallbackEffects = new SparseArray<>();
        mFallbackEffects.put(VibrationEffect.EFFECT_CLICK, clickEffect);
        mFallbackEffects.put(VibrationEffect.EFFECT_DOUBLE_CLICK, doubleClickEffect);
        mFallbackEffects.put(VibrationEffect.EFFECT_TICK, tickEffect);
        mFallbackEffects.put(VibrationEffect.EFFECT_HEAVY_CLICK, heavyClickEffect);
        mFallbackEffects.put(VibrationEffect.EFFECT_TEXTURE_TICK,
                VibrationEffect.get(VibrationEffect.EFFECT_TICK, false));

        // Update with current values from settings.
        update();
    }

    public void onSystemReady() {
        PowerManagerInternal pm = LocalServices.getService(PowerManagerInternal.class);
        AudioManager am = mContext.getSystemService(AudioManager.class);
        int ringerMode = am.getRingerModeInternal();

        synchronized (mLock) {
            mPowerManagerInternal = pm;
            mAudioManager = am;
            mRingerMode = ringerMode;
        }

        try {
            ActivityManager.getService().registerUidObserver(mUidObserver,
                    ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE,
                    ActivityManager.PROCESS_STATE_UNKNOWN, null);
        } catch (RemoteException e) {
            // ignored; both services live in system_server
        }

        pm.registerLowPowerModeObserver(
                new PowerManagerInternal.LowPowerModeListener() {
                    @Override
                    public int getServiceType() {
                        return PowerManager.ServiceType.VIBRATION;
                    }

                    @Override
                    public void onLowPowerModeChanged(PowerSaveState result) {
                        boolean shouldNotifyListeners;
                        synchronized (mLock) {
                            shouldNotifyListeners = result.batterySaverEnabled != mBatterySaverMode;
                            mBatterySaverMode = result.batterySaverEnabled;
                        }
                        if (shouldNotifyListeners) {
                            notifyListeners();
                        }
                    }
                });

        VirtualDeviceManagerInternal vdm = LocalServices.getService(
                VirtualDeviceManagerInternal.class);
        if (vdm != null) {
            vdm.registerVirtualDisplayListener(mVirtualDeviceListener);
            vdm.registerAppsOnVirtualDeviceListener(mVirtualDeviceListener);
        }

        registerSettingsChangeReceiver(USER_SWITCHED_INTENT_FILTER);
        registerSettingsChangeReceiver(INTERNAL_RINGER_MODE_CHANGED_INTENT_FILTER);

        // Listen to all settings that might affect the result of Vibrator.getVibrationIntensity.
        registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES));
        registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_ON));
        registerSettingsObserver(Settings.System.getUriFor(
                Settings.System.HAPTIC_FEEDBACK_ENABLED));
        registerSettingsObserver(
                Settings.System.getUriFor(Settings.System.ALARM_VIBRATION_INTENSITY));
        registerSettingsObserver(
                Settings.System.getUriFor(Settings.System.HAPTIC_FEEDBACK_INTENSITY));
        registerSettingsObserver(
                Settings.System.getUriFor(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY));
        registerSettingsObserver(
                Settings.System.getUriFor(Settings.System.MEDIA_VIBRATION_INTENSITY));
        registerSettingsObserver(
                Settings.System.getUriFor(Settings.System.NOTIFICATION_VIBRATION_INTENSITY));
        registerSettingsObserver(
                Settings.System.getUriFor(Settings.System.RING_VIBRATION_INTENSITY));

        // Update with newly loaded services.
        update();
    }

    /**
     * Add listener to vibrator settings changes. This will trigger the listener with current state
     * immediately and every time one of the settings change.
     */
    public void addListener(OnVibratorSettingsChanged listener) {
        synchronized (mLock) {
            if (!mListeners.contains(listener)) {
                mListeners.add(listener);
            }
        }
    }

    /** Remove listener to vibrator settings. */
    public void removeListener(OnVibratorSettingsChanged listener) {
        synchronized (mLock) {
            mListeners.remove(listener);
        }
    }

    /**
     * The duration, in milliseconds, that should be applied to convert vibration effect's
     * {@link android.os.vibrator.RampSegment} to a {@link android.os.vibrator.StepSegment} on
     * devices without PWLE support.
     */
    public int getRampStepDuration() {
        return mVibrationConfig.getRampStepDurationMs();
    }

    /**
     * The duration, in milliseconds, that should be applied to the ramp to turn off the vibrator
     * when a vibration is cancelled or finished at non-zero amplitude.
     */
    public int getRampDownDuration() {
        return mVibrationConfig.getRampDownDurationMs();
    }

    /**
     * Return default vibration intensity for given usage.
     *
     * @param usageHint one of VibrationAttributes.USAGE_*
     * @return The vibration intensity, one of Vibrator.VIBRATION_INTENSITY_*
     */
    public int getDefaultIntensity(@VibrationAttributes.Usage int usageHint) {
        return mVibrationConfig.getDefaultVibrationIntensity(usageHint);
    }

    /**
     * Return the current vibration intensity set for given usage at the user settings.
     *
     * @param usageHint one of VibrationAttributes.USAGE_*
     * @return The vibration intensity, one of Vibrator.VIBRATION_INTENSITY_*
     */
    public int getCurrentIntensity(@VibrationAttributes.Usage int usageHint) {
        int defaultIntensity = getDefaultIntensity(usageHint);
        synchronized (mLock) {
            return mCurrentVibrationIntensities.get(usageHint, defaultIntensity);
        }
    }

    /**
     * Return a {@link VibrationEffect} that should be played if the device do not support given
     * {@code effectId}.
     *
     * @param effectId one of VibrationEffect.EFFECT_*
     * @return The effect to be played as a fallback
     */
    public VibrationEffect getFallbackEffect(int effectId) {
        return mFallbackEffects.get(effectId);
    }

    /** Return {@code true} if input devices should vibrate instead of this device. */
    public boolean shouldVibrateInputDevices() {
        return mVibrateInputDevices;
    }

    /**
     * Check if given vibration should be ignored by the service.
     *
     * @return One of Vibration.Status.IGNORED_* values if the vibration should be ignored,
     * null otherwise.
     */
    @Nullable
    public Vibration.Status shouldIgnoreVibration(int uid, int displayId,
            VibrationAttributes attrs) {
        final int usage = attrs.getUsage();
        synchronized (mLock) {
            if (!mUidObserver.isUidForeground(uid)
                    && !BACKGROUND_PROCESS_USAGE_ALLOWLIST.contains(usage)) {
                return Vibration.Status.IGNORED_BACKGROUND;
            }
            if (mVirtualDeviceListener.isAppOrDisplayOnAnyVirtualDevice(uid, displayId)) {
                return Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE;
            }

            if (mBatterySaverMode && !BATTERY_SAVER_USAGE_ALLOWLIST.contains(usage)) {
                return Vibration.Status.IGNORED_FOR_POWER;
            }

            if (!attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)) {
                if (!mVibrateOn && (VIBRATE_ON_DISABLED_USAGE_ALLOWED != usage)) {
                    return Vibration.Status.IGNORED_FOR_SETTINGS;
                }

                if (getCurrentIntensity(usage) == Vibrator.VIBRATION_INTENSITY_OFF) {
                    return Vibration.Status.IGNORED_FOR_SETTINGS;
                }
            }

            if (!attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) {
                if (!shouldVibrateForRingerModeLocked(usage)) {
                    return Vibration.Status.IGNORED_FOR_RINGER_MODE;
                }
            }
        }
        return null;
    }

    /**
     * Check if given vibration should be cancelled by the service when the screen goes off.
     *
     * <p>When the system is entering a non-interactive state, we want to cancel vibrations in case
     * a misbehaving app has abandoned them. However, it may happen that the system is currently
     * playing haptic feedback as part of the transition. So we don't cancel system vibrations of
     * usages like touch and hardware feedback, and physical emulation.
     *
     * @return true if the vibration should be cancelled when the screen goes off, false otherwise.
     */
    public boolean shouldCancelVibrationOnScreenOff(int uid, String opPkg,
            @VibrationAttributes.Usage int usage, long vibrationStartUptimeMillis) {
        PowerManagerInternal pm;
        synchronized (mLock) {
            pm = mPowerManagerInternal;
        }
        if (pm != null) {
            // The SleepData from PowerManager may refer to a more recent sleep than the broadcast
            // that triggered this method call. That's ok because only automatic sleeps would be
            // ignored here and not cancel a vibration, and those are usually triggered by timeout
            // or inactivity, so it's unlikely that it will override a more active goToSleep reason.
            PowerManager.SleepData sleepData = pm.getLastGoToSleep();
            if ((sleepData.goToSleepUptimeMillis < vibrationStartUptimeMillis)
                    || POWER_MANAGER_SLEEP_REASON_ALLOWLIST.contains(sleepData.goToSleepReason)) {
                // Ignore screen off events triggered before the vibration started, and all
                // automatic "go to sleep" events from allowlist.
                Slog.d(TAG, "Ignoring screen off event triggered at uptime "
                        + sleepData.goToSleepUptimeMillis + " for reason "
                        + PowerManager.sleepReasonToString(sleepData.goToSleepReason));
                return false;
            }
        }
        if (!SYSTEM_VIBRATION_SCREEN_OFF_USAGE_ALLOWLIST.contains(usage)) {
            // Usages not allowed even for system vibrations should always be cancelled.
            return true;
        }
        // Only allow vibrations from System packages to continue vibrating when the screen goes off
        return uid != Process.SYSTEM_UID && uid != 0 && !mSystemUiPackage.equals(opPkg);
    }

    /**
     * Return {@code true} if the device should vibrate for current ringer mode.
     *
     * <p>This checks the current {@link AudioManager#getRingerModeInternal()} against user settings
     * for ringtone and notification usages. All other usages are allowed by this method.
     */
    @GuardedBy("mLock")
    private boolean shouldVibrateForRingerModeLocked(@VibrationAttributes.Usage int usageHint) {
        if ((usageHint != USAGE_RINGTONE) && (usageHint != USAGE_NOTIFICATION)) {
            // Only ringtone and notification vibrations are disabled when phone is on silent mode.
            return true;
        }
        return mRingerMode != AudioManager.RINGER_MODE_SILENT;
    }

    /** Update all cached settings and triggers registered listeners. */
    void update() {
        updateSettings();
        updateRingerMode();
        notifyListeners();
    }

    private void updateSettings() {
        synchronized (mLock) {
            mVibrateInputDevices = loadSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0) > 0;
            mVibrateOn = loadSystemSetting(Settings.System.VIBRATE_ON, 1) > 0;

            int alarmIntensity = toIntensity(
                    loadSystemSetting(Settings.System.ALARM_VIBRATION_INTENSITY, -1),
                    getDefaultIntensity(USAGE_ALARM));
            int defaultHapticFeedbackIntensity = getDefaultIntensity(USAGE_TOUCH);
            int hapticFeedbackIntensity = toIntensity(
                    loadSystemSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, -1),
                    defaultHapticFeedbackIntensity);
            int positiveHapticFeedbackIntensity = toPositiveIntensity(
                    hapticFeedbackIntensity, defaultHapticFeedbackIntensity);
            int hardwareFeedbackIntensity = toIntensity(
                    loadSystemSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, -1),
                    positiveHapticFeedbackIntensity);
            int mediaIntensity = toIntensity(
                    loadSystemSetting(Settings.System.MEDIA_VIBRATION_INTENSITY, -1),
                    getDefaultIntensity(USAGE_MEDIA));
            int defaultNotificationIntensity = getDefaultIntensity(USAGE_NOTIFICATION);
            int notificationIntensity = toIntensity(
                    loadSystemSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, -1),
                    defaultNotificationIntensity);
            int positiveNotificationIntensity = toPositiveIntensity(
                    notificationIntensity, defaultNotificationIntensity);
            int ringIntensity = toIntensity(
                    loadSystemSetting(Settings.System.RING_VIBRATION_INTENSITY, -1),
                    getDefaultIntensity(USAGE_RINGTONE));

            mCurrentVibrationIntensities.clear();
            mCurrentVibrationIntensities.put(USAGE_ALARM, alarmIntensity);
            mCurrentVibrationIntensities.put(USAGE_NOTIFICATION, notificationIntensity);
            mCurrentVibrationIntensities.put(USAGE_MEDIA, mediaIntensity);
            mCurrentVibrationIntensities.put(USAGE_UNKNOWN, mediaIntensity);
            mCurrentVibrationIntensities.put(USAGE_RINGTONE, ringIntensity);

            // Communication request is not disabled by the notification setting.
            mCurrentVibrationIntensities.put(USAGE_COMMUNICATION_REQUEST,
                    positiveNotificationIntensity);

            // This should adapt the behavior preceding the introduction of this new setting
            // key, which is to apply HAPTIC_FEEDBACK_INTENSITY, unless it's disabled.
            mCurrentVibrationIntensities.put(USAGE_HARDWARE_FEEDBACK, hardwareFeedbackIntensity);
            mCurrentVibrationIntensities.put(USAGE_PHYSICAL_EMULATION, hardwareFeedbackIntensity);

            if (!loadBooleanSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED)) {
                // Make sure deprecated boolean setting still disables touch vibrations.
                mCurrentVibrationIntensities.put(USAGE_TOUCH, Vibrator.VIBRATION_INTENSITY_OFF);
            } else {
                mCurrentVibrationIntensities.put(USAGE_TOUCH, hapticFeedbackIntensity);
            }

            // A11y is not disabled by any haptic feedback setting.
            mCurrentVibrationIntensities.put(USAGE_ACCESSIBILITY, positiveHapticFeedbackIntensity);
        }
    }

    private void updateRingerMode() {
        synchronized (mLock) {
            // If audio manager was not loaded yet then assume most restrictive mode.
            // This will be loaded again as soon as the audio manager is loaded in onSystemReady.
            mRingerMode = (mAudioManager == null)
                    ? AudioManager.RINGER_MODE_SILENT
                    : mAudioManager.getRingerModeInternal();
        }
    }

    @Override
    public String toString() {
        synchronized (mLock) {
            StringBuilder vibrationIntensitiesString = new StringBuilder("{");
            for (int i = 0; i < mCurrentVibrationIntensities.size(); i++) {
                int usage = mCurrentVibrationIntensities.keyAt(i);
                int intensity = mCurrentVibrationIntensities.valueAt(i);
                vibrationIntensitiesString.append(VibrationAttributes.usageToString(usage))
                        .append("=(").append(intensityToString(intensity))
                        .append(",default:").append(intensityToString(getDefaultIntensity(usage)))
                        .append("), ");
            }
            vibrationIntensitiesString.append('}');
            return "VibrationSettings{"
                    + "mVibratorConfig=" + mVibrationConfig
                    + ", mVibrateInputDevices=" + mVibrateInputDevices
                    + ", mBatterySaverMode=" + mBatterySaverMode
                    + ", mVibrateOn=" + mVibrateOn
                    + ", mVibrationIntensities=" + vibrationIntensitiesString
                    + ", mProcStatesCache=" + mUidObserver.mProcStatesCache
                    + '}';
        }
    }

    /** Write current settings into given {@link ProtoOutputStream}. */
    public void dumpProto(ProtoOutputStream proto) {
        synchronized (mLock) {
            proto.write(VibratorManagerServiceDumpProto.VIBRATE_ON, mVibrateOn);
            proto.write(VibratorManagerServiceDumpProto.LOW_POWER_MODE, mBatterySaverMode);
            proto.write(VibratorManagerServiceDumpProto.ALARM_INTENSITY,
                    getCurrentIntensity(USAGE_ALARM));
            proto.write(VibratorManagerServiceDumpProto.ALARM_DEFAULT_INTENSITY,
                    getDefaultIntensity(USAGE_ALARM));
            proto.write(VibratorManagerServiceDumpProto.HARDWARE_FEEDBACK_INTENSITY,
                    getCurrentIntensity(USAGE_HARDWARE_FEEDBACK));
            proto.write(VibratorManagerServiceDumpProto.HARDWARE_FEEDBACK_DEFAULT_INTENSITY,
                    getDefaultIntensity(USAGE_HARDWARE_FEEDBACK));
            proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY,
                    getCurrentIntensity(USAGE_TOUCH));
            proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY,
                    getDefaultIntensity(USAGE_TOUCH));
            proto.write(VibratorManagerServiceDumpProto.MEDIA_INTENSITY,
                    getCurrentIntensity(USAGE_MEDIA));
            proto.write(VibratorManagerServiceDumpProto.MEDIA_DEFAULT_INTENSITY,
                    getDefaultIntensity(USAGE_MEDIA));
            proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_INTENSITY,
                    getCurrentIntensity(USAGE_NOTIFICATION));
            proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY,
                    getDefaultIntensity(USAGE_NOTIFICATION));
            proto.write(VibratorManagerServiceDumpProto.RING_INTENSITY,
                    getCurrentIntensity(USAGE_RINGTONE));
            proto.write(VibratorManagerServiceDumpProto.RING_DEFAULT_INTENSITY,
                    getDefaultIntensity(USAGE_RINGTONE));
        }
    }

    private void notifyListeners() {
        List<OnVibratorSettingsChanged> currentListeners;
        synchronized (mLock) {
            currentListeners = new ArrayList<>(mListeners);
        }
        for (OnVibratorSettingsChanged listener : currentListeners) {
            listener.onChange();
        }
    }

    private static String intensityToString(int intensity) {
        switch (intensity) {
            case Vibrator.VIBRATION_INTENSITY_OFF:
                return "OFF";
            case Vibrator.VIBRATION_INTENSITY_LOW:
                return "LOW";
            case Vibrator.VIBRATION_INTENSITY_MEDIUM:
                return "MEDIUM";
            case Vibrator.VIBRATION_INTENSITY_HIGH:
                return "HIGH";
            default:
                return "UNKNOWN INTENSITY " + intensity;
        }
    }

    @VibrationIntensity
    private int toPositiveIntensity(int value, @VibrationIntensity int defaultValue) {
        if (value == Vibrator.VIBRATION_INTENSITY_OFF) {
            return defaultValue;
        }
        return toIntensity(value, defaultValue);
    }

    @VibrationIntensity
    private int toIntensity(int value, @VibrationIntensity int defaultValue) {
        if ((value < Vibrator.VIBRATION_INTENSITY_OFF)
                || (value > Vibrator.VIBRATION_INTENSITY_HIGH)) {
            return defaultValue;
        }
        return value;
    }

    private boolean loadBooleanSetting(String settingKey) {
        return Settings.System.getIntForUser(mContext.getContentResolver(),
                settingKey, 0, UserHandle.USER_CURRENT) != 0;
    }

    private int loadSystemSetting(String settingName, int defaultValue) {
        return Settings.System.getIntForUser(mContext.getContentResolver(),
                settingName, defaultValue, UserHandle.USER_CURRENT);
    }

    private void registerSettingsObserver(Uri settingUri) {
        mContext.getContentResolver().registerContentObserver(
                settingUri, /* notifyForDescendants= */ true, mSettingObserver,
                UserHandle.USER_ALL);
    }

    private void registerSettingsChangeReceiver(IntentFilter intentFilter) {
        mContext.registerReceiver(mSettingChangeReceiver, intentFilter);
    }

    @Nullable
    private VibrationEffect createEffectFromResource(int resId) {
        long[] timings = getLongIntArray(mContext.getResources(), resId);
        return createEffectFromTimings(timings);
    }

    @Nullable
    private static VibrationEffect createEffectFromTimings(@Nullable long[] timings) {
        if (timings == null || timings.length == 0) {
            return null;
        } else if (timings.length == 1) {
            return VibrationEffect.createOneShot(timings[0], VibrationEffect.DEFAULT_AMPLITUDE);
        } else {
            return VibrationEffect.createWaveform(timings, -1);
        }
    }

    private static long[] getLongIntArray(Resources r, int resid) {
        int[] ar = r.getIntArray(resid);
        if (ar == null) {
            return null;
        }
        long[] out = new long[ar.length];
        for (int i = 0; i < ar.length; i++) {
            out[i] = ar[i];
        }
        return out;
    }

    /** Implementation of {@link ContentObserver} to be registered to a setting {@link Uri}. */
    @VisibleForTesting
    final class SettingsContentObserver extends ContentObserver {
        SettingsContentObserver(Handler handler) {
            super(handler);
        }

        @Override
        public void onChange(boolean selfChange) {
            updateSettings();
            notifyListeners();
        }
    }

    /**
     * Implementation of {@link BroadcastReceiver} to update settings on current user or ringer
     * mode change.
     */
    @VisibleForTesting
    final class SettingsBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (Intent.ACTION_USER_SWITCHED.equals(action)) {
                // Reload all settings, as they are user-based.
                update();
            } else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) {
                updateRingerMode();
                notifyListeners();
            }
        }
    }

    /** Implementation of {@link ContentObserver} to be registered to a setting {@link Uri}. */
    @VisibleForTesting
    final class UidObserver extends IUidObserver.Stub {
        private final SparseArray<Integer> mProcStatesCache = new SparseArray<>();

        public boolean isUidForeground(int uid) {
            return mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
                    <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
        }

        @Override
        public void onUidGone(int uid, boolean disabled) {
            mProcStatesCache.delete(uid);
        }

        @Override
        public void onUidActive(int uid) {
        }

        @Override
        public void onUidIdle(int uid, boolean disabled) {
        }

        @Override
        public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
            mProcStatesCache.put(uid, procState);
        }

        @Override
        public void onUidCachedChanged(int uid, boolean cached) {
        }

        @Override
        public void onUidProcAdjChanged(int uid) {
        }
    }

    /**
     * Implementation of Virtual Device listeners for the changes of virtual displays and of apps
     * running on any virtual device.
     */
    final class VirtualDeviceListener implements
            VirtualDeviceManagerInternal.VirtualDisplayListener,
            VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener {
        @GuardedBy("mLock")
        private final Set<Integer> mVirtualDisplays = new HashSet<>();
        @GuardedBy("mLock")
        private final Set<Integer> mAppsOnVirtualDevice = new HashSet<>();


        @Override
        public void onVirtualDisplayCreated(int displayId) {
            synchronized (mLock) {
                mVirtualDisplays.add(displayId);
            }
        }

        @Override
        public void onVirtualDisplayRemoved(int displayId) {
            synchronized (mLock) {
                mVirtualDisplays.remove(displayId);
            }
        }


        @Override
        public void onAppsOnAnyVirtualDeviceChanged(Set<Integer> allRunningUids) {
            synchronized (mLock) {
                mAppsOnVirtualDevice.clear();
                mAppsOnVirtualDevice.addAll(allRunningUids);
            }
        }

        /**
         * @param uid:       uid of the calling app.
         * @param displayId: the id of a Display.
         * @return Returns true if:
         * <ul>
         *   <li> the displayId is valid, and it's owned by a virtual device.</li>
         *   <li> the displayId is invalid, and the calling app (uid) is running on a virtual
         *        device.</li>
         * </ul>
         */
        public boolean isAppOrDisplayOnAnyVirtualDevice(int uid, int displayId) {
            if (displayId == Display.DEFAULT_DISPLAY) {
                // The default display is the primary physical display on the phone.
                return false;
            }

            synchronized (mLock) {
                if (displayId == Display.INVALID_DISPLAY) {
                    // There is no Display object associated with the Context of calling
                    // {@link SystemVibratorManager}, checking the calling UID instead.
                    return mAppsOnVirtualDevice.contains(uid);
                } else {
                    // Other valid display IDs representing valid logical displays will be
                    // checked
                    // against the active virtual displays set built with the registered
                    // {@link VirtualDisplayListener}.
                    return mVirtualDisplays.contains(displayId);
                }
            }
        }

    }
}
