1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.vibrator;
18 
19 import android.annotation.Nullable;
20 import android.app.ActivityManager;
21 import android.app.IUidObserver;
22 import android.content.BroadcastReceiver;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.content.res.Resources;
27 import android.database.ContentObserver;
28 import android.media.AudioManager;
29 import android.net.Uri;
30 import android.os.Handler;
31 import android.os.PowerManager;
32 import android.os.PowerManagerInternal;
33 import android.os.PowerSaveState;
34 import android.os.RemoteException;
35 import android.os.UserHandle;
36 import android.os.VibrationAttributes;
37 import android.os.VibrationEffect;
38 import android.os.Vibrator;
39 import android.provider.Settings;
40 import android.util.SparseArray;
41 import android.util.proto.ProtoOutputStream;
42 
43 import com.android.internal.annotations.GuardedBy;
44 import com.android.internal.annotations.VisibleForTesting;
45 import com.android.server.LocalServices;
46 
47 import java.util.ArrayList;
48 import java.util.List;
49 
50 /** Controls all the system settings related to vibration. */
51 final class VibrationSettings {
52     private static final String TAG = "VibrationSettings";
53 
54     private static final long[] DOUBLE_CLICK_EFFECT_FALLBACK_TIMINGS = {0, 30, 100, 30};
55 
56     /** Listener for changes on vibration settings. */
57     interface OnVibratorSettingsChanged {
58         /** Callback triggered when any of the vibrator settings change. */
onChange()59         void onChange();
60     }
61 
62     private final Object mLock = new Object();
63     private final Context mContext;
64     private final SettingsObserver mSettingObserver;
65     @VisibleForTesting
66     final UidObserver mUidObserver;
67     @VisibleForTesting
68     final UserObserver mUserReceiver;
69 
70     @GuardedBy("mLock")
71     private final List<OnVibratorSettingsChanged> mListeners = new ArrayList<>();
72     private final SparseArray<VibrationEffect> mFallbackEffects;
73 
74     private final int mRampStepDuration;
75     private final int mRampDownDuration;
76 
77     @GuardedBy("mLock")
78     @Nullable
79     private Vibrator mVibrator;
80     @GuardedBy("mLock")
81     @Nullable
82     private AudioManager mAudioManager;
83 
84     @GuardedBy("mLock")
85     private boolean mVibrateInputDevices;
86     @GuardedBy("mLock")
87     private boolean mVibrateWhenRinging;
88     @GuardedBy("mLock")
89     private boolean mApplyRampingRinger;
90     @GuardedBy("mLock")
91     private int mZenMode;
92     @GuardedBy("mLock")
93     private int mHapticFeedbackIntensity;
94     @GuardedBy("mLock")
95     private int mNotificationIntensity;
96     @GuardedBy("mLock")
97     private int mRingIntensity;
98     @GuardedBy("mLock")
99     private boolean mLowPowerMode;
100 
VibrationSettings(Context context, Handler handler)101     VibrationSettings(Context context, Handler handler) {
102         this(context, handler,
103                 context.getResources().getInteger(
104                         com.android.internal.R.integer.config_vibrationWaveformRampDownDuration),
105                 context.getResources().getInteger(
106                         com.android.internal.R.integer.config_vibrationWaveformRampStepDuration));
107     }
108 
109     @VisibleForTesting
VibrationSettings(Context context, Handler handler, int rampDownDuration, int rampStepDuration)110     VibrationSettings(Context context, Handler handler, int rampDownDuration,
111             int rampStepDuration) {
112         mContext = context;
113         mSettingObserver = new SettingsObserver(handler);
114         mUidObserver = new UidObserver();
115         mUserReceiver = new UserObserver();
116 
117         // TODO(b/191150049): move these to vibrator static config file
118         mRampDownDuration = rampDownDuration;
119         mRampStepDuration = rampStepDuration;
120 
121         VibrationEffect clickEffect = createEffectFromResource(
122                 com.android.internal.R.array.config_virtualKeyVibePattern);
123         VibrationEffect doubleClickEffect = VibrationEffect.createWaveform(
124                 DOUBLE_CLICK_EFFECT_FALLBACK_TIMINGS, -1 /*repeatIndex*/);
125         VibrationEffect heavyClickEffect = createEffectFromResource(
126                 com.android.internal.R.array.config_longPressVibePattern);
127         VibrationEffect tickEffect = createEffectFromResource(
128                 com.android.internal.R.array.config_clockTickVibePattern);
129 
130         mFallbackEffects = new SparseArray<>();
131         mFallbackEffects.put(VibrationEffect.EFFECT_CLICK, clickEffect);
132         mFallbackEffects.put(VibrationEffect.EFFECT_DOUBLE_CLICK, doubleClickEffect);
133         mFallbackEffects.put(VibrationEffect.EFFECT_TICK, tickEffect);
134         mFallbackEffects.put(VibrationEffect.EFFECT_HEAVY_CLICK, heavyClickEffect);
135         mFallbackEffects.put(VibrationEffect.EFFECT_TEXTURE_TICK,
136                 VibrationEffect.get(VibrationEffect.EFFECT_TICK, false));
137 
138         // Update with current values from settings.
139         updateSettings();
140     }
141 
onSystemReady()142     public void onSystemReady() {
143         synchronized (mLock) {
144             mVibrator = mContext.getSystemService(Vibrator.class);
145             mAudioManager = mContext.getSystemService(AudioManager.class);
146         }
147         try {
148             ActivityManager.getService().registerUidObserver(mUidObserver,
149                     ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE,
150                     ActivityManager.PROCESS_STATE_UNKNOWN, null);
151         } catch (RemoteException e) {
152             // ignored; both services live in system_server
153         }
154 
155         PowerManagerInternal pm = LocalServices.getService(PowerManagerInternal.class);
156         pm.registerLowPowerModeObserver(
157                 new PowerManagerInternal.LowPowerModeListener() {
158                     @Override
159                     public int getServiceType() {
160                         return PowerManager.ServiceType.VIBRATION;
161                     }
162 
163                     @Override
164                     public void onLowPowerModeChanged(PowerSaveState result) {
165                         boolean shouldNotifyListeners;
166                         synchronized (mLock) {
167                             shouldNotifyListeners = result.batterySaverEnabled != mLowPowerMode;
168                             mLowPowerMode = result.batterySaverEnabled;
169                         }
170                         if (shouldNotifyListeners) {
171                             notifyListeners();
172                         }
173                     }
174                 });
175 
176         mContext.registerReceiver(mUserReceiver, new IntentFilter(Intent.ACTION_USER_SWITCHED));
177         registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES));
178         registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_WHEN_RINGING));
179         registerSettingsObserver(Settings.Global.getUriFor(Settings.Global.APPLY_RAMPING_RINGER));
180         registerSettingsObserver(Settings.Global.getUriFor(Settings.Global.ZEN_MODE));
181         registerSettingsObserver(
182                 Settings.System.getUriFor(Settings.System.HAPTIC_FEEDBACK_INTENSITY));
183         registerSettingsObserver(
184                 Settings.System.getUriFor(Settings.System.NOTIFICATION_VIBRATION_INTENSITY));
185         registerSettingsObserver(
186                 Settings.System.getUriFor(Settings.System.RING_VIBRATION_INTENSITY));
187 
188         // Update with newly loaded services.
189         updateSettings();
190     }
191 
192     /**
193      * Add listener to vibrator settings changes. This will trigger the listener with current state
194      * immediately and every time one of the settings change.
195      */
addListener(OnVibratorSettingsChanged listener)196     public void addListener(OnVibratorSettingsChanged listener) {
197         synchronized (mLock) {
198             if (!mListeners.contains(listener)) {
199                 mListeners.add(listener);
200             }
201         }
202     }
203 
204     /** Remove listener to vibrator settings. */
removeListener(OnVibratorSettingsChanged listener)205     public void removeListener(OnVibratorSettingsChanged listener) {
206         synchronized (mLock) {
207             mListeners.remove(listener);
208         }
209     }
210 
211     /**
212      * The duration, in milliseconds, that should be applied to convert vibration effect's
213      * {@link android.os.vibrator.RampSegment} to a {@link android.os.vibrator.StepSegment} on
214      * devices without PWLE support.
215      */
getRampStepDuration()216     public int getRampStepDuration() {
217         return mRampStepDuration;
218     }
219 
220     /**
221      * The duration, in milliseconds, that should be applied to the ramp to turn off the vibrator
222      * when a vibration is cancelled or finished at non-zero amplitude.
223      */
getRampDownDuration()224     public int getRampDownDuration() {
225         return mRampDownDuration;
226     }
227 
228     /**
229      * Return default vibration intensity for given usage.
230      *
231      * @param usageHint one of VibrationAttributes.USAGE_*
232      * @return The vibration intensity, one of Vibrator.VIBRATION_INTENSITY_*
233      */
getDefaultIntensity(int usageHint)234     public int getDefaultIntensity(int usageHint) {
235         if (isAlarm(usageHint)) {
236             return Vibrator.VIBRATION_INTENSITY_HIGH;
237         }
238         synchronized (mLock) {
239             if (mVibrator != null) {
240                 if (isRingtone(usageHint)) {
241                     return mVibrator.getDefaultRingVibrationIntensity();
242                 } else if (isNotification(usageHint)) {
243                     return mVibrator.getDefaultNotificationVibrationIntensity();
244                 } else if (isHapticFeedback(usageHint)) {
245                     return mVibrator.getDefaultHapticFeedbackIntensity();
246                 }
247             }
248         }
249         return Vibrator.VIBRATION_INTENSITY_MEDIUM;
250     }
251 
252     /**
253      * Return the current vibration intensity set for given usage at the user settings.
254      *
255      * @param usageHint one of VibrationAttributes.USAGE_*
256      * @return The vibration intensity, one of Vibrator.VIBRATION_INTENSITY_*
257      */
getCurrentIntensity(int usageHint)258     public int getCurrentIntensity(int usageHint) {
259         synchronized (mLock) {
260             if (isRingtone(usageHint)) {
261                 return mRingIntensity;
262             } else if (isNotification(usageHint)) {
263                 return mNotificationIntensity;
264             } else if (isHapticFeedback(usageHint)) {
265                 return mHapticFeedbackIntensity;
266             } else if (isAlarm(usageHint)) {
267                 return Vibrator.VIBRATION_INTENSITY_HIGH;
268             } else {
269                 return Vibrator.VIBRATION_INTENSITY_MEDIUM;
270             }
271         }
272     }
273 
274     /**
275      * Return a {@link VibrationEffect} that should be played if the device do not support given
276      * {@code effectId}.
277      *
278      * @param effectId one of VibrationEffect.EFFECT_*
279      * @return The effect to be played as a fallback
280      */
getFallbackEffect(int effectId)281     public VibrationEffect getFallbackEffect(int effectId) {
282         return mFallbackEffects.get(effectId);
283     }
284 
285     /**
286      * Return {@code true} if the device should vibrate for current ringer mode.
287      *
288      * <p>This checks the current {@link AudioManager#getRingerModeInternal()} against user settings
289      * for ringtone usage only. All other usages are allowed independently of ringer mode.
290      */
shouldVibrateForRingerMode(int usageHint)291     public boolean shouldVibrateForRingerMode(int usageHint) {
292         if (!isRingtone(usageHint)) {
293             return true;
294         }
295         synchronized (mLock) {
296             if (mAudioManager == null) {
297                 return false;
298             }
299             int ringerMode = mAudioManager.getRingerModeInternal();
300             if (mVibrateWhenRinging) {
301                 return ringerMode != AudioManager.RINGER_MODE_SILENT;
302             } else if (mApplyRampingRinger) {
303                 return ringerMode != AudioManager.RINGER_MODE_SILENT;
304             } else {
305                 return ringerMode == AudioManager.RINGER_MODE_VIBRATE;
306             }
307         }
308     }
309 
310     /**
311      * Returns {@code true} if this vibration is allowed for given {@code uid}.
312      *
313      * <p>This checks if the user is aware of this foreground process, or if the vibration usage is
314      * allowed to play in the background (i.e. it's a notification, ringtone or alarm vibration).
315      */
shouldVibrateForUid(int uid, int usageHint)316     public boolean shouldVibrateForUid(int uid, int usageHint) {
317         return mUidObserver.isUidForeground(uid) || isClassAlarm(usageHint);
318     }
319 
320     /**
321      * Returns {@code true} if this vibration is allowed for current power mode state.
322      *
323      * <p>This checks if the device is in battery saver mode, in which case only alarm, ringtone and
324      * {@link VibrationAttributes#USAGE_COMMUNICATION_REQUEST} usages are allowed to vibrate.
325      */
shouldVibrateForPowerMode(int usageHint)326     public boolean shouldVibrateForPowerMode(int usageHint) {
327         return !mLowPowerMode || isRingtone(usageHint) || isAlarm(usageHint)
328                 || usageHint == VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
329     }
330 
331     /** Return {@code true} if input devices should vibrate instead of this device. */
shouldVibrateInputDevices()332     public boolean shouldVibrateInputDevices() {
333         return mVibrateInputDevices;
334     }
335 
336     /** Return {@code true} if setting for {@link Settings.Global#ZEN_MODE} is not OFF. */
isInZenMode()337     public boolean isInZenMode() {
338         return mZenMode != Settings.Global.ZEN_MODE_OFF;
339     }
340 
isNotification(int usageHint)341     private static boolean isNotification(int usageHint) {
342         return usageHint == VibrationAttributes.USAGE_NOTIFICATION;
343     }
344 
isRingtone(int usageHint)345     private static boolean isRingtone(int usageHint) {
346         return usageHint == VibrationAttributes.USAGE_RINGTONE;
347     }
348 
isHapticFeedback(int usageHint)349     private static boolean isHapticFeedback(int usageHint) {
350         return usageHint == VibrationAttributes.USAGE_TOUCH;
351     }
352 
isAlarm(int usageHint)353     private static boolean isAlarm(int usageHint) {
354         return usageHint == VibrationAttributes.USAGE_ALARM;
355     }
356 
isClassAlarm(int usageHint)357     private static boolean isClassAlarm(int usageHint) {
358         return (usageHint & VibrationAttributes.USAGE_CLASS_MASK)
359                 == VibrationAttributes.USAGE_CLASS_ALARM;
360     }
361 
362     /** Updates all vibration settings and triggers registered listeners. */
updateSettings()363     public void updateSettings() {
364         synchronized (mLock) {
365             mVibrateWhenRinging = getSystemSetting(Settings.System.VIBRATE_WHEN_RINGING, 0) != 0;
366             mApplyRampingRinger = getGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0) != 0;
367             mHapticFeedbackIntensity = getSystemSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
368                     getDefaultIntensity(VibrationAttributes.USAGE_TOUCH));
369             mNotificationIntensity = getSystemSetting(
370                     Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
371                     getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION));
372             mRingIntensity = getSystemSetting(Settings.System.RING_VIBRATION_INTENSITY,
373                     getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE));
374             mVibrateInputDevices = getSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0) > 0;
375             mZenMode = getGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
376         }
377         notifyListeners();
378     }
379 
380     @Override
toString()381     public String toString() {
382         return "VibrationSettings{"
383                 + "mVibrateInputDevices=" + mVibrateInputDevices
384                 + ", mVibrateWhenRinging=" + mVibrateWhenRinging
385                 + ", mApplyRampingRinger=" + mApplyRampingRinger
386                 + ", mLowPowerMode=" + mLowPowerMode
387                 + ", mZenMode=" + Settings.Global.zenModeToString(mZenMode)
388                 + ", mProcStatesCache=" + mUidObserver.mProcStatesCache
389                 + ", mHapticChannelMaxVibrationAmplitude=" + getHapticChannelMaxVibrationAmplitude()
390                 + ", mRampStepDuration=" + mRampStepDuration
391                 + ", mRampDownDuration=" + mRampDownDuration
392                 + ", mHapticFeedbackIntensity="
393                 + intensityToString(getCurrentIntensity(VibrationAttributes.USAGE_TOUCH))
394                 + ", mHapticFeedbackDefaultIntensity="
395                 + intensityToString(getDefaultIntensity(VibrationAttributes.USAGE_TOUCH))
396                 + ", mNotificationIntensity="
397                 + intensityToString(getCurrentIntensity(VibrationAttributes.USAGE_NOTIFICATION))
398                 + ", mNotificationDefaultIntensity="
399                 + intensityToString(getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION))
400                 + ", mRingIntensity="
401                 + intensityToString(getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE))
402                 + ", mRingDefaultIntensity="
403                 + intensityToString(getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE))
404                 + '}';
405     }
406 
407     /** Write current settings into given {@link ProtoOutputStream}. */
dumpProto(ProtoOutputStream proto)408     public void dumpProto(ProtoOutputStream proto) {
409         synchronized (mLock) {
410             proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY,
411                     mHapticFeedbackIntensity);
412             proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY,
413                     getDefaultIntensity(VibrationAttributes.USAGE_TOUCH));
414             proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_INTENSITY,
415                     mNotificationIntensity);
416             proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY,
417                     getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION));
418             proto.write(VibratorManagerServiceDumpProto.RING_INTENSITY,
419                     mRingIntensity);
420             proto.write(VibratorManagerServiceDumpProto.RING_DEFAULT_INTENSITY,
421                     getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE));
422         }
423     }
424 
notifyListeners()425     private void notifyListeners() {
426         List<OnVibratorSettingsChanged> currentListeners;
427         synchronized (mLock) {
428             currentListeners = new ArrayList<>(mListeners);
429         }
430         for (OnVibratorSettingsChanged listener : currentListeners) {
431             listener.onChange();
432         }
433     }
434 
intensityToString(int intensity)435     private static String intensityToString(int intensity) {
436         switch (intensity) {
437             case Vibrator.VIBRATION_INTENSITY_OFF:
438                 return "OFF";
439             case Vibrator.VIBRATION_INTENSITY_LOW:
440                 return "LOW";
441             case Vibrator.VIBRATION_INTENSITY_MEDIUM:
442                 return "MEDIUM";
443             case Vibrator.VIBRATION_INTENSITY_HIGH:
444                 return "HIGH";
445             default:
446                 return "UNKNOWN INTENSITY " + intensity;
447         }
448     }
449 
getHapticChannelMaxVibrationAmplitude()450     private float getHapticChannelMaxVibrationAmplitude() {
451         synchronized (mLock) {
452             return mVibrator == null ? Float.NaN : mVibrator.getHapticChannelMaximumAmplitude();
453         }
454     }
455 
getSystemSetting(String settingName, int defaultValue)456     private int getSystemSetting(String settingName, int defaultValue) {
457         return Settings.System.getIntForUser(mContext.getContentResolver(),
458                 settingName, defaultValue, UserHandle.USER_CURRENT);
459     }
460 
getGlobalSetting(String settingName, int defaultValue)461     private int getGlobalSetting(String settingName, int defaultValue) {
462         return Settings.Global.getInt(mContext.getContentResolver(), settingName, defaultValue);
463     }
464 
registerSettingsObserver(Uri settingUri)465     private void registerSettingsObserver(Uri settingUri) {
466         mContext.getContentResolver().registerContentObserver(
467                 settingUri, /* notifyForDescendants= */ true, mSettingObserver,
468                 UserHandle.USER_ALL);
469     }
470 
createEffectFromResource(int resId)471     private VibrationEffect createEffectFromResource(int resId) {
472         long[] timings = getLongIntArray(mContext.getResources(), resId);
473         return createEffectFromTimings(timings);
474     }
475 
createEffectFromTimings(long[] timings)476     private static VibrationEffect createEffectFromTimings(long[] timings) {
477         if (timings == null || timings.length == 0) {
478             return null;
479         } else if (timings.length == 1) {
480             return VibrationEffect.createOneShot(timings[0], VibrationEffect.DEFAULT_AMPLITUDE);
481         } else {
482             return VibrationEffect.createWaveform(timings, -1);
483         }
484     }
485 
getLongIntArray(Resources r, int resid)486     private static long[] getLongIntArray(Resources r, int resid) {
487         int[] ar = r.getIntArray(resid);
488         if (ar == null) {
489             return null;
490         }
491         long[] out = new long[ar.length];
492         for (int i = 0; i < ar.length; i++) {
493             out[i] = ar[i];
494         }
495         return out;
496     }
497 
498     /** Implementation of {@link ContentObserver} to be registered to a setting {@link Uri}. */
499     private final class SettingsObserver extends ContentObserver {
SettingsObserver(Handler handler)500         SettingsObserver(Handler handler) {
501             super(handler);
502         }
503 
504         @Override
onChange(boolean selfChange)505         public void onChange(boolean selfChange) {
506             updateSettings();
507         }
508     }
509 
510     /** Implementation of {@link BroadcastReceiver} to update settings on current user change. */
511     @VisibleForTesting
512     final class UserObserver extends BroadcastReceiver {
513         @Override
onReceive(Context context, Intent intent)514         public void onReceive(Context context, Intent intent) {
515             if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
516                 updateSettings();
517             }
518         }
519     }
520 
521     /** Implementation of {@link ContentObserver} to be registered to a setting {@link Uri}. */
522     @VisibleForTesting
523     final class UidObserver extends IUidObserver.Stub {
524         private final SparseArray<Integer> mProcStatesCache = new SparseArray<>();
525 
isUidForeground(int uid)526         public boolean isUidForeground(int uid) {
527             return mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
528                     <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
529         }
530 
531         @Override
onUidGone(int uid, boolean disabled)532         public void onUidGone(int uid, boolean disabled) {
533             mProcStatesCache.delete(uid);
534         }
535 
536         @Override
onUidActive(int uid)537         public void onUidActive(int uid) {
538         }
539 
540         @Override
onUidIdle(int uid, boolean disabled)541         public void onUidIdle(int uid, boolean disabled) {
542         }
543 
544         @Override
onUidStateChanged(int uid, int procState, long procStateSeq, int capability)545         public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
546             mProcStatesCache.put(uid, procState);
547         }
548 
549         @Override
onUidCachedChanged(int uid, boolean cached)550         public void onUidCachedChanged(int uid, boolean cached) {
551         }
552     }
553 }
554