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