• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.annotation.Nullable;
21 import android.os.CombinedVibration;
22 import android.os.VibrationAttributes;
23 import android.os.VibrationEffect;
24 import android.os.VibratorInfo;
25 import android.os.vibrator.Flags;
26 import android.os.vibrator.PrebakedSegment;
27 import android.os.vibrator.VibrationEffectSegment;
28 import android.util.SparseArray;
29 
30 import java.util.List;
31 import java.util.Objects;
32 import java.util.concurrent.CountDownLatch;
33 import java.util.function.IntFunction;
34 
35 /**
36  * Represents a vibration defined by a {@link CombinedVibration} that will be performed by
37  * the IVibrator HAL.
38  */
39 final class HalVibration extends Vibration {
40 
41     public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>();
42 
43     /** A {@link CountDownLatch} to enable waiting for completion. */
44     private final CountDownLatch mCompletionLatch = new CountDownLatch(1);
45 
46     /** The original effect that was requested, for debugging purposes. */
47     @NonNull
48     private final CombinedVibration mOriginalEffect;
49 
50     /**
51      * The scaled and adapted effect to be played. This should only be updated from a single thread,
52      * but can be read from different ones for debugging purposes.
53      */
54     @NonNull
55     private volatile CombinedVibration mEffectToPlay;
56 
57     /** Reported scale values applied to the vibration effects. */
58     private int mScaleLevel;
59     private float mAdaptiveScale;
60 
HalVibration(@onNull VibrationSession.CallerInfo callerInfo, @NonNull CombinedVibration effect)61     HalVibration(@NonNull VibrationSession.CallerInfo callerInfo,
62             @NonNull CombinedVibration effect) {
63         super(callerInfo);
64         mOriginalEffect = effect;
65         mEffectToPlay = effect;
66         mScaleLevel = VibrationScaler.SCALE_NONE;
67         mAdaptiveScale = VibrationScaler.ADAPTIVE_SCALE_NONE;
68     }
69 
70     @Override
end(EndInfo endInfo)71     public void end(EndInfo endInfo) {
72         super.end(endInfo);
73         mCompletionLatch.countDown();
74     }
75 
76     /** Waits indefinitely until another thread calls {@link #end} on this vibration. */
waitForEnd()77     public void waitForEnd() throws InterruptedException {
78         mCompletionLatch.await();
79     }
80 
81     /**
82      * Return the effect to be played when given prebaked effect id is not supported by the
83      * vibrator.
84      */
85     @Nullable
getFallback(int effectId)86     public VibrationEffect getFallback(int effectId) {
87         return mFallbacks.get(effectId);
88     }
89 
90     /**
91      * Add a fallback {@link VibrationEffect} to be played for each predefined effect id, which
92      * might be necessary for replacement in realtime.
93      */
fillFallbacks(IntFunction<VibrationEffect> fallbackProvider)94     public void fillFallbacks(IntFunction<VibrationEffect> fallbackProvider) {
95         fillFallbacksForEffect(mEffectToPlay, fallbackProvider);
96     }
97 
98     /**
99      * Scales the {@link #getEffectToPlay()} and each fallback effect based on the vibration usage.
100      */
scaleEffects(VibrationScaler scaler)101     public void scaleEffects(VibrationScaler scaler) {
102         int vibrationUsage = callerInfo.attrs.getUsage();
103 
104         // Save scale values for debugging purposes.
105         mScaleLevel = scaler.getScaleLevel(vibrationUsage);
106         mAdaptiveScale = scaler.getAdaptiveHapticsScale(vibrationUsage);
107         stats.reportAdaptiveScale(mAdaptiveScale);
108 
109         // Scale all VibrationEffect instances in given CombinedVibration.
110         CombinedVibration newEffect = mEffectToPlay.transform(scaler::scale, vibrationUsage);
111         if (!Objects.equals(mEffectToPlay, newEffect)) {
112             mEffectToPlay = newEffect;
113         }
114 
115         // Scale all fallback VibrationEffect instances that can be used by VibrationThread.
116         for (int i = 0; i < mFallbacks.size(); i++) {
117             mFallbacks.setValueAt(i, scaler.scale(mFallbacks.valueAt(i), vibrationUsage));
118         }
119     }
120 
121     /**
122      * Adapts the {@link #getEffectToPlay()} to the device using given vibrator adapter.
123      *
124      * @param deviceAdapter A {@link CombinedVibration.VibratorAdapter} that transforms vibration
125      *                      effects to device vibrators based on its capabilities.
126      */
adaptToDevice(CombinedVibration.VibratorAdapter deviceAdapter)127     public boolean adaptToDevice(CombinedVibration.VibratorAdapter deviceAdapter) {
128         CombinedVibration adaptedEffect = mEffectToPlay.adapt(deviceAdapter);
129         if (adaptedEffect == null) {
130             return false;
131         }
132 
133         if (!mEffectToPlay.equals(adaptedEffect)) {
134             mEffectToPlay = adaptedEffect;
135         }
136         // No need to update fallback effects, they are already configured per device.
137 
138         return true;
139     }
140 
141     /** Return the effect that should be played by this vibration. */
getEffectToPlay()142     public CombinedVibration getEffectToPlay() {
143         return mEffectToPlay;
144     }
145 
146     @Override
getDebugInfo()147     public VibrationSession.DebugInfo getDebugInfo() {
148         // Clear the original effect if it's the same as the effect that was played, for simplicity
149         CombinedVibration originalEffect =
150                 Objects.equals(mOriginalEffect, mEffectToPlay) ? null : mOriginalEffect;
151         return new Vibration.DebugInfoImpl(getStatus(), callerInfo,
152                 VibrationStats.StatsInfo.findVibrationType(mEffectToPlay), stats, mEffectToPlay,
153                 originalEffect, mScaleLevel, mAdaptiveScale);
154     }
155 
156     /** Returns true if this vibration can pipeline with the specified one. */
canPipelineWith(HalVibration vib, @Nullable SparseArray<VibratorInfo> vibratorInfos, int durationThresholdMs)157     public boolean canPipelineWith(HalVibration vib,
158             @Nullable SparseArray<VibratorInfo> vibratorInfos, int durationThresholdMs) {
159         long effectDuration = Flags.vibrationPipelineEnabled() && (vibratorInfos != null)
160                 ? mEffectToPlay.getDuration(vibratorInfos)
161                 : mEffectToPlay.getDuration();
162         if (effectDuration == Long.MAX_VALUE) {
163             // Repeating vibrations can't pipeline with following vibrations, because the cancel()
164             // call to stop the repetition will cancel a pending vibration too. This can be changed
165             // if we have a use-case, requiring changes to how pipelined vibrations are cancelled.
166             return false;
167         }
168         if (Flags.vibrationPipelineEnabled()
169                 && (effectDuration > 0) && (effectDuration < durationThresholdMs)) {
170             // Duration is known and it's less than the pipeline threshold, so allow it.
171             // No need to check UID, as we want to avoid cancelling any short effect and let the
172             // vibrator hardware gracefully finish the vibration.
173             return true;
174         }
175         // Check the same app is requesting multiple vibrations with the pipeline flag,
176         // independently of the effect durations.
177         return callerInfo.uid == vib.callerInfo.uid
178                 && callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT)
179                 && vib.callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT);
180     }
181 
fillFallbacksForEffect(CombinedVibration effect, IntFunction<VibrationEffect> fallbackProvider)182     private void fillFallbacksForEffect(CombinedVibration effect,
183             IntFunction<VibrationEffect> fallbackProvider) {
184         if (effect instanceof CombinedVibration.Mono) {
185             fillFallbacksForEffect(((CombinedVibration.Mono) effect).getEffect(), fallbackProvider);
186         } else if (effect instanceof CombinedVibration.Stereo) {
187             SparseArray<VibrationEffect> effects =
188                     ((CombinedVibration.Stereo) effect).getEffects();
189             for (int i = 0; i < effects.size(); i++) {
190                 fillFallbacksForEffect(effects.valueAt(i), fallbackProvider);
191             }
192         } else if (effect instanceof CombinedVibration.Sequential) {
193             List<CombinedVibration> effects =
194                     ((CombinedVibration.Sequential) effect).getEffects();
195             for (int i = 0; i < effects.size(); i++) {
196                 fillFallbacksForEffect(effects.get(i), fallbackProvider);
197             }
198         }
199     }
200 
fillFallbacksForEffect(VibrationEffect effect, IntFunction<VibrationEffect> fallbackProvider)201     private void fillFallbacksForEffect(VibrationEffect effect,
202             IntFunction<VibrationEffect> fallbackProvider) {
203         if (!(effect instanceof VibrationEffect.Composed composed)) {
204             return;
205         }
206         int segmentCount = composed.getSegments().size();
207         for (int i = 0; i < segmentCount; i++) {
208             VibrationEffectSegment segment = composed.getSegments().get(i);
209             if ((segment instanceof PrebakedSegment prebaked) && prebaked.shouldFallback()) {
210                 VibrationEffect fallback = fallbackProvider.apply(prebaked.getEffectId());
211                 if (fallback != null) {
212                     mFallbacks.put(prebaked.getEffectId(), fallback);
213                 }
214             }
215         }
216     }
217 }
218