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