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.NonNull;
20 import android.content.Context;
21 import android.hardware.vibrator.V1_0.EffectStrength;
22 import android.os.ExternalVibrationScale;
23 import android.os.VibrationAttributes;
24 import android.os.VibrationEffect;
25 import android.os.Vibrator;
26 import android.os.vibrator.Flags;
27 import android.os.vibrator.PrebakedSegment;
28 import android.os.vibrator.VibrationEffectSegment;
29 import android.util.IndentingPrintWriter;
30 import android.util.Slog;
31 import android.util.SparseArray;
32 import android.util.proto.ProtoOutputStream;
33 
34 import java.io.PrintWriter;
35 import java.util.ArrayList;
36 import java.util.Locale;
37 
38 /** Controls vibration scaling. */
39 final class VibrationScaler {
40     private static final String TAG = "VibrationScaler";
41 
42     // Scale levels. Each level, except MUTE, is defined as the delta between the current setting
43     // and the default intensity for that type of vibration (i.e. current - default).
44     static final int SCALE_VERY_LOW = ExternalVibrationScale.ScaleLevel.SCALE_VERY_LOW; // -2
45     static final int SCALE_LOW = ExternalVibrationScale.ScaleLevel.SCALE_LOW; // -1
46     static final int SCALE_NONE = ExternalVibrationScale.ScaleLevel.SCALE_NONE; // 0
47     static final int SCALE_HIGH = ExternalVibrationScale.ScaleLevel.SCALE_HIGH; // 1
48     static final int SCALE_VERY_HIGH = ExternalVibrationScale.ScaleLevel.SCALE_VERY_HIGH; // 2
49     static final float ADAPTIVE_SCALE_NONE = 1f;
50 
51     // Scale factors for each level.
52     private static final float SCALE_FACTOR_VERY_LOW = 0.6f;
53     private static final float SCALE_FACTOR_LOW = 0.8f;
54     private static final float SCALE_FACTOR_NONE = 1f;
55     private static final float SCALE_FACTOR_HIGH = 1.2f;
56     private static final float SCALE_FACTOR_VERY_HIGH = 1.4f;
57 
58     private static final ScaleLevel SCALE_LEVEL_NONE = new ScaleLevel(SCALE_FACTOR_NONE);
59 
60     // A mapping from the intensity adjustment to the scaling to apply, where the intensity
61     // adjustment is defined as the delta between the default intensity level and the user selected
62     // intensity level. It's important that we apply the scaling on the delta between the two so
63     // that the default intensity level applies no scaling to application provided effects.
64     private final SparseArray<ScaleLevel> mScaleLevels;
65     private final VibrationSettings mSettingsController;
66     private final int mDefaultVibrationAmplitude;
67     private final SparseArray<Float> mAdaptiveHapticsScales = new SparseArray<>();
68 
VibrationScaler(Context context, VibrationSettings settingsController)69     VibrationScaler(Context context, VibrationSettings settingsController) {
70         mSettingsController = settingsController;
71         mDefaultVibrationAmplitude = context.getResources().getInteger(
72                 com.android.internal.R.integer.config_defaultVibrationAmplitude);
73 
74         mScaleLevels = new SparseArray<>();
75         mScaleLevels.put(SCALE_VERY_LOW, new ScaleLevel(SCALE_FACTOR_VERY_LOW));
76         mScaleLevels.put(SCALE_LOW, new ScaleLevel(SCALE_FACTOR_LOW));
77         mScaleLevels.put(SCALE_NONE, SCALE_LEVEL_NONE);
78         mScaleLevels.put(SCALE_HIGH, new ScaleLevel(SCALE_FACTOR_HIGH));
79         mScaleLevels.put(SCALE_VERY_HIGH, new ScaleLevel(SCALE_FACTOR_VERY_HIGH));
80     }
81 
82     /**
83      * Returns the default vibration amplitude configured for this device, value in [1,255].
84      */
getDefaultVibrationAmplitude()85     public int getDefaultVibrationAmplitude() {
86         return mDefaultVibrationAmplitude;
87     }
88 
89     /**
90      * Calculates the scale to be applied to external vibration with given usage.
91      *
92      * @param usageHint one of VibrationAttributes.USAGE_*
93      * @return one of ExternalVibrationScale.ScaleLevel.SCALE_*
94      */
getScaleLevel(int usageHint)95     public int getScaleLevel(int usageHint) {
96         int defaultIntensity = mSettingsController.getDefaultIntensity(usageHint);
97         int currentIntensity = mSettingsController.getCurrentIntensity(usageHint);
98         if (currentIntensity == Vibrator.VIBRATION_INTENSITY_OFF) {
99             // Bypassing user settings, or it has changed between checking and scaling. Use default.
100             return SCALE_NONE;
101         }
102 
103         int scaleLevel = currentIntensity - defaultIntensity;
104         if (scaleLevel >= SCALE_VERY_LOW && scaleLevel <= SCALE_VERY_HIGH) {
105             return scaleLevel;
106         }
107 
108         // Something about our scaling has gone wrong, so just play with no scaling.
109         Slog.wtf(TAG, "Error in scaling calculations, ended up with invalid scale level "
110                 + scaleLevel + " for vibration with usage " + usageHint);
111 
112         return SCALE_NONE;
113     }
114 
115     /**
116      * Returns the adaptive haptics scale that should be applied to the vibrations with
117      * the given usage. When no adaptive scales are available for the usages, then returns 1
118      * indicating no scaling will be applied
119      *
120      * @param usageHint one of VibrationAttributes.USAGE_*
121      * @return The adaptive haptics scale.
122      */
getAdaptiveHapticsScale(int usageHint)123     public float getAdaptiveHapticsScale(int usageHint) {
124         return Flags.adaptiveHapticsEnabled()
125                 ? mAdaptiveHapticsScales.get(usageHint, ADAPTIVE_SCALE_NONE)
126                 : ADAPTIVE_SCALE_NONE;
127     }
128 
129     /**
130      * Scale a {@link VibrationEffect} based on the given usage hint for this vibration.
131      *
132      * @param effect    the effect to be scaled
133      * @param usageHint one of VibrationAttributes.USAGE_*
134      * @return The same given effect, if no changes were made, or a new {@link VibrationEffect} with
135      * resolved and scaled amplitude
136      */
137     @NonNull
scale(@onNull VibrationEffect effect, int usageHint)138     public VibrationEffect scale(@NonNull VibrationEffect effect, int usageHint) {
139         if (!(effect instanceof VibrationEffect.Composed)) {
140             // This only scales composed vibration effects.
141             Slog.wtf(TAG, "Error scaling unsupported vibration effect: " + effect);
142             return effect;
143         }
144 
145         int newEffectStrength = getEffectStrength(usageHint);
146         ScaleLevel scaleLevel = mScaleLevels.get(getScaleLevel(usageHint));
147         float adaptiveScale = getAdaptiveHapticsScale(usageHint);
148 
149         if (scaleLevel == null) {
150             // Something about our scaling has gone wrong, so just play with no scaling.
151             Slog.e(TAG, "No configured scaling level found! (current="
152                     + mSettingsController.getCurrentIntensity(usageHint) + ", default= "
153                     + mSettingsController.getDefaultIntensity(usageHint) + ")");
154             scaleLevel = SCALE_LEVEL_NONE;
155         }
156 
157         VibrationEffect.Composed composedEffect = (VibrationEffect.Composed) effect;
158         ArrayList<VibrationEffectSegment> segments =
159                 new ArrayList<>(composedEffect.getSegments());
160         int segmentCount = segments.size();
161         for (int i = 0; i < segmentCount; i++) {
162             segments.set(i,
163                     segments.get(i).resolve(mDefaultVibrationAmplitude)
164                             .applyEffectStrength(newEffectStrength)
165                             .scale(scaleLevel.factor)
166                             .scaleLinearly(adaptiveScale));
167         }
168         if (segments.equals(composedEffect.getSegments())) {
169             // No segment was updated, return original effect.
170             return effect;
171         }
172         VibrationEffect.Composed scaled =
173                 new VibrationEffect.Composed(segments, composedEffect.getRepeatIndex());
174         // Make sure we validate what was scaled, since we're using the constructor directly
175         scaled.validate();
176         return scaled;
177     }
178 
179     /**
180      * Scale a {@link PrebakedSegment} based on the given usage hint for this vibration.
181      *
182      * @param prebaked  the prebaked segment to be scaled
183      * @param usageHint one of VibrationAttributes.USAGE_*
184      * @return The same segment if no changes were made, or a new {@link PrebakedSegment} with
185      * updated effect strength
186      */
scale(PrebakedSegment prebaked, int usageHint)187     public PrebakedSegment scale(PrebakedSegment prebaked, int usageHint) {
188         return prebaked.applyEffectStrength(getEffectStrength(usageHint));
189     }
190 
191     /**
192      * Updates the adaptive haptics scales list by adding or modifying the scale for this usage.
193      *
194      * @param usageHint one of VibrationAttributes.USAGE_*.
195      * @param scale The scaling factor that should be applied to vibrations of this usage.
196      */
updateAdaptiveHapticsScale(@ibrationAttributes.Usage int usageHint, float scale)197     public void updateAdaptiveHapticsScale(@VibrationAttributes.Usage int usageHint, float scale) {
198         mAdaptiveHapticsScales.put(usageHint, scale);
199     }
200 
201     /**
202      * Removes the usage from the cached adaptive haptics scales list.
203      *
204      * @param usageHint one of VibrationAttributes.USAGE_*.
205      */
removeAdaptiveHapticsScale(@ibrationAttributes.Usage int usageHint)206     public void removeAdaptiveHapticsScale(@VibrationAttributes.Usage int usageHint) {
207         mAdaptiveHapticsScales.remove(usageHint);
208     }
209 
210     /** Removes all cached adaptive haptics scales. */
clearAdaptiveHapticsScales()211     public void clearAdaptiveHapticsScales() {
212         mAdaptiveHapticsScales.clear();
213     }
214 
215     /** Write current settings into given {@link PrintWriter}. */
dump(IndentingPrintWriter pw)216     void dump(IndentingPrintWriter pw) {
217         pw.println("VibrationScaler:");
218         pw.increaseIndent();
219         pw.println("defaultVibrationAmplitude = " + mDefaultVibrationAmplitude);
220 
221         pw.println("ScaleLevels:");
222         pw.increaseIndent();
223         for (int i = 0; i < mScaleLevels.size(); i++) {
224             int scaleLevelKey = mScaleLevels.keyAt(i);
225             ScaleLevel scaleLevel = mScaleLevels.valueAt(i);
226             pw.println(scaleLevelToString(scaleLevelKey) + " = " + scaleLevel);
227         }
228         pw.decreaseIndent();
229 
230         pw.println("AdaptiveHapticsScales:");
231         pw.increaseIndent();
232         for (int i = 0; i < mAdaptiveHapticsScales.size(); i++) {
233             int usage = mAdaptiveHapticsScales.keyAt(i);
234             float scale = mAdaptiveHapticsScales.valueAt(i);
235             pw.println(VibrationAttributes.usageToString(usage)
236                     + " = " + String.format(Locale.ROOT, "%.2f", scale));
237         }
238         pw.decreaseIndent();
239 
240         pw.decreaseIndent();
241     }
242 
243     /** Write current settings into given {@link ProtoOutputStream}. */
dump(ProtoOutputStream proto)244     void dump(ProtoOutputStream proto) {
245         proto.write(VibratorManagerServiceDumpProto.DEFAULT_VIBRATION_AMPLITUDE,
246                 mDefaultVibrationAmplitude);
247     }
248 
249     @Override
toString()250     public String toString() {
251         return "VibrationScaler{"
252                 + "mScaleLevels=" + mScaleLevels
253                 + ", mDefaultVibrationAmplitude=" + mDefaultVibrationAmplitude
254                 + ", mAdaptiveHapticsScales=" + mAdaptiveHapticsScales
255                 + '}';
256     }
257 
getEffectStrength(int usageHint)258     private int getEffectStrength(int usageHint) {
259         int currentIntensity = mSettingsController.getCurrentIntensity(usageHint);
260 
261         if (currentIntensity == Vibrator.VIBRATION_INTENSITY_OFF) {
262             // Bypassing user settings, or it has changed between checking and scaling. Use default.
263             currentIntensity = mSettingsController.getDefaultIntensity(usageHint);
264         }
265 
266         return intensityToEffectStrength(currentIntensity);
267     }
268 
269     /** Mapping of Vibrator.VIBRATION_INTENSITY_* values to {@link EffectStrength}. */
intensityToEffectStrength(int intensity)270     private static int intensityToEffectStrength(int intensity) {
271         switch (intensity) {
272             case Vibrator.VIBRATION_INTENSITY_LOW:
273                 return EffectStrength.LIGHT;
274             case Vibrator.VIBRATION_INTENSITY_MEDIUM:
275                 return EffectStrength.MEDIUM;
276             case Vibrator.VIBRATION_INTENSITY_HIGH:
277                 return EffectStrength.STRONG;
278             default:
279                 Slog.w(TAG, "Got unexpected vibration intensity: " + intensity);
280                 return EffectStrength.STRONG;
281         }
282     }
283 
scaleLevelToString(int scaleLevel)284     static String scaleLevelToString(int scaleLevel) {
285         return switch (scaleLevel) {
286             case SCALE_VERY_LOW -> "VERY_LOW";
287             case SCALE_LOW -> "LOW";
288             case SCALE_NONE -> "NONE";
289             case SCALE_HIGH -> "HIGH";
290             case SCALE_VERY_HIGH -> "VERY_HIGH";
291             default -> String.valueOf(scaleLevel);
292         };
293     }
294 
295     /** Represents the scale that must be applied to a vibration effect intensity. */
296     private static final class ScaleLevel {
297         public final float factor;
298 
ScaleLevel(float factor)299         ScaleLevel(float factor) {
300             this.factor = factor;
301         }
302 
303         @Override
toString()304         public String toString() {
305             return "ScaleLevel{factor=" + factor + "}";
306         }
307     }
308 }
309