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