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.content.Context;
20 import android.hardware.vibrator.V1_0.EffectStrength;
21 import android.os.IExternalVibratorService;
22 import android.os.VibrationEffect;
23 import android.os.Vibrator;
24 import android.os.vibrator.PrebakedSegment;
25 import android.util.Slog;
26 import android.util.SparseArray;
27 
28 /** Controls vibration scaling. */
29 final class VibrationScaler {
30     private static final String TAG = "VibrationScaler";
31 
32     // Scale levels. Each level, except MUTE, is defined as the delta between the current setting
33     // and the default intensity for that type of vibration (i.e. current - default).
34     private static final int SCALE_VERY_LOW = IExternalVibratorService.SCALE_VERY_LOW; // -2
35     private static final int SCALE_LOW = IExternalVibratorService.SCALE_LOW; // -1
36     private static final int SCALE_NONE = IExternalVibratorService.SCALE_NONE; // 0
37     private static final int SCALE_HIGH = IExternalVibratorService.SCALE_HIGH; // 1
38     private static final int SCALE_VERY_HIGH = IExternalVibratorService.SCALE_VERY_HIGH; // 2
39 
40     // Scale factors for each level.
41     private static final float SCALE_FACTOR_VERY_LOW = 0.6f;
42     private static final float SCALE_FACTOR_LOW = 0.8f;
43     private static final float SCALE_FACTOR_NONE = 1f;
44     private static final float SCALE_FACTOR_HIGH = 1.2f;
45     private static final float SCALE_FACTOR_VERY_HIGH = 1.4f;
46 
47     // A mapping from the intensity adjustment to the scaling to apply, where the intensity
48     // adjustment is defined as the delta between the default intensity level and the user selected
49     // intensity level. It's important that we apply the scaling on the delta between the two so
50     // that the default intensity level applies no scaling to application provided effects.
51     private final SparseArray<ScaleLevel> mScaleLevels;
52     private final VibrationSettings mSettingsController;
53     private final int mDefaultVibrationAmplitude;
54 
VibrationScaler(Context context, VibrationSettings settingsController)55     VibrationScaler(Context context, VibrationSettings settingsController) {
56         mSettingsController = settingsController;
57         mDefaultVibrationAmplitude = context.getResources().getInteger(
58                 com.android.internal.R.integer.config_defaultVibrationAmplitude);
59 
60         mScaleLevels = new SparseArray<>();
61         mScaleLevels.put(SCALE_VERY_LOW, new ScaleLevel(SCALE_FACTOR_VERY_LOW));
62         mScaleLevels.put(SCALE_LOW, new ScaleLevel(SCALE_FACTOR_LOW));
63         mScaleLevels.put(SCALE_NONE, new ScaleLevel(SCALE_FACTOR_NONE));
64         mScaleLevels.put(SCALE_HIGH, new ScaleLevel(SCALE_FACTOR_HIGH));
65         mScaleLevels.put(SCALE_VERY_HIGH, new ScaleLevel(SCALE_FACTOR_VERY_HIGH));
66     }
67 
68     /**
69      * Calculates the scale to be applied to external vibration with given usage.
70      *
71      * @param usageHint one of VibrationAttributes.USAGE_*
72      * @return one of IExternalVibratorService.SCALE_*
73      */
getExternalVibrationScale(int usageHint)74     public int getExternalVibrationScale(int usageHint) {
75         int defaultIntensity = mSettingsController.getDefaultIntensity(usageHint);
76         int currentIntensity = mSettingsController.getCurrentIntensity(usageHint);
77 
78         if (currentIntensity == Vibrator.VIBRATION_INTENSITY_OFF) {
79             // Bypassing user settings, or it has changed between checking and scaling. Use default.
80             return SCALE_NONE;
81         }
82 
83         int scaleLevel = currentIntensity - defaultIntensity;
84 
85         if (scaleLevel >= SCALE_VERY_LOW && scaleLevel <= SCALE_VERY_HIGH) {
86             return scaleLevel;
87         } else {
88             // Something about our scaling has gone wrong, so just play with no scaling.
89             Slog.w(TAG, "Error in scaling calculations, ended up with invalid scale level "
90                     + scaleLevel + " for vibration with usage " + usageHint);
91             return SCALE_NONE;
92         }
93     }
94 
95     /**
96      * Scale a {@link VibrationEffect} based on the given usage hint for this vibration.
97      *
98      * @param effect    the effect to be scaled
99      * @param usageHint one of VibrationAttributes.USAGE_*
100      * @return The same given effect, if no changes were made, or a new {@link VibrationEffect} with
101      * resolved and scaled amplitude
102      */
scale(VibrationEffect effect, int usageHint)103     public <T extends VibrationEffect> T scale(VibrationEffect effect, int usageHint) {
104         int defaultIntensity = mSettingsController.getDefaultIntensity(usageHint);
105         int currentIntensity = mSettingsController.getCurrentIntensity(usageHint);
106 
107         if (currentIntensity == Vibrator.VIBRATION_INTENSITY_OFF) {
108             // Bypassing user settings, or it has changed between checking and scaling. Use default.
109             currentIntensity = defaultIntensity;
110         }
111 
112         int newEffectStrength = intensityToEffectStrength(currentIntensity);
113         effect = effect.applyEffectStrength(newEffectStrength).resolve(mDefaultVibrationAmplitude);
114         ScaleLevel scale = mScaleLevels.get(currentIntensity - defaultIntensity);
115 
116         if (scale == null) {
117             // Something about our scaling has gone wrong, so just play with no scaling.
118             Slog.e(TAG, "No configured scaling level!"
119                     + " (current=" + currentIntensity + ", default= " + defaultIntensity + ")");
120             return (T) effect;
121         }
122 
123         return (T) effect.scale(scale.factor);
124     }
125 
126     /**
127      * Scale a {@link PrebakedSegment} based on the given usage hint for this vibration.
128      *
129      * @param prebaked  the prebaked segment to be scaled
130      * @param usageHint one of VibrationAttributes.USAGE_*
131      * @return The same segment if no changes were made, or a new {@link PrebakedSegment} with
132      * updated effect strength
133      */
scale(PrebakedSegment prebaked, int usageHint)134     public PrebakedSegment scale(PrebakedSegment prebaked, int usageHint) {
135         int currentIntensity = mSettingsController.getCurrentIntensity(usageHint);
136 
137         if (currentIntensity == Vibrator.VIBRATION_INTENSITY_OFF) {
138             // Bypassing user settings, or it has changed between checking and scaling. Use default.
139             currentIntensity = mSettingsController.getDefaultIntensity(usageHint);
140         }
141 
142         int newEffectStrength = intensityToEffectStrength(currentIntensity);
143         return prebaked.applyEffectStrength(newEffectStrength);
144     }
145 
146     /** Mapping of Vibrator.VIBRATION_INTENSITY_* values to {@link EffectStrength}. */
intensityToEffectStrength(int intensity)147     private static int intensityToEffectStrength(int intensity) {
148         switch (intensity) {
149             case Vibrator.VIBRATION_INTENSITY_LOW:
150                 return EffectStrength.LIGHT;
151             case Vibrator.VIBRATION_INTENSITY_MEDIUM:
152                 return EffectStrength.MEDIUM;
153             case Vibrator.VIBRATION_INTENSITY_HIGH:
154                 return EffectStrength.STRONG;
155             default:
156                 Slog.w(TAG, "Got unexpected vibration intensity: " + intensity);
157                 return EffectStrength.STRONG;
158         }
159     }
160 
161     /** Represents the scale that must be applied to a vibration effect intensity. */
162     private static final class ScaleLevel {
163         public final float factor;
164 
ScaleLevel(float factor)165         ScaleLevel(float factor) {
166             this.factor = factor;
167         }
168 
169         @Override
toString()170         public String toString() {
171             return "ScaleLevel{factor=" + factor + "}";
172         }
173     }
174 }
175