blob: 6012993db9161da09d86e6580fa3ea2ccce29ac4 [file] [log] [blame]
/*
* 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);
}
}
}
}
}