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.IBinder;
23 import android.os.VibrationAttributes;
24 import android.os.VibrationEffect;
25 import android.util.SparseArray;
26 
27 import com.android.internal.util.FrameworkStatsLog;
28 
29 import java.util.Objects;
30 import java.util.concurrent.CountDownLatch;
31 
32 /**
33  * Represents a vibration defined by a {@link CombinedVibration} that will be performed by
34  * the IVibrator HAL.
35  */
36 final class HalVibration extends Vibration {
37 
38     public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>();
39 
40     /** A {@link CountDownLatch} to enable waiting for completion. */
41     private final CountDownLatch mCompletionLatch = new CountDownLatch(1);
42 
43     /** The original effect that was requested, for debugging purposes. */
44     @NonNull
45     private final CombinedVibration mOriginalEffect;
46 
47     /**
48      * The scaled and adapted effect to be played. This should only be updated from a single thread,
49      * but can be read from different ones for debugging purposes.
50      */
51     @NonNull
52     private volatile CombinedVibration mEffectToPlay;
53 
54     /** Vibration status. */
55     private Vibration.Status mStatus;
56 
57     /** Reported scale values applied to the vibration effects. */
58     private int mScaleLevel;
59     private float mAdaptiveScale;
60 
HalVibration(@onNull IBinder token, @NonNull CombinedVibration effect, @NonNull CallerInfo callerInfo)61     HalVibration(@NonNull IBinder token, @NonNull CombinedVibration effect,
62             @NonNull CallerInfo callerInfo) {
63         super(token, callerInfo);
64         mOriginalEffect = effect;
65         mEffectToPlay = effect;
66         mStatus = Vibration.Status.RUNNING;
67         mScaleLevel = VibrationScaler.SCALE_NONE;
68         mAdaptiveScale = VibrationScaler.ADAPTIVE_SCALE_NONE;
69     }
70 
71     /**
72      * Set the {@link Status} of this vibration and reports the current system time as this
73      * vibration end time, for debugging purposes.
74      *
75      * <p>This method will only accept given value if the current status is {@link
76      * Status#RUNNING}.
77      */
end(EndInfo info)78     public void end(EndInfo info) {
79         if (hasEnded()) {
80             // Vibration already ended, keep first ending status set and ignore this one.
81             return;
82         }
83         mStatus = info.status;
84         stats.reportEnded(info.endedBy);
85         mCompletionLatch.countDown();
86     }
87 
88     /** Waits indefinitely until another thread calls {@link #end} on this vibration. */
waitForEnd()89     public void waitForEnd() throws InterruptedException {
90         mCompletionLatch.await();
91     }
92 
93     /**
94      * Return the effect to be played when given prebaked effect id is not supported by the
95      * vibrator.
96      */
97     @Nullable
getFallback(int effectId)98     public VibrationEffect getFallback(int effectId) {
99         return mFallbacks.get(effectId);
100     }
101 
102     /**
103      * Add a fallback {@link VibrationEffect} to be played when given effect id is not supported,
104      * which might be necessary for replacement in realtime.
105      */
addFallback(int effectId, VibrationEffect effect)106     public void addFallback(int effectId, VibrationEffect effect) {
107         mFallbacks.put(effectId, effect);
108     }
109 
110     /**
111      * Resolves the default vibration amplitude of {@link #getEffectToPlay()} and each fallback.
112      *
113      * @param defaultAmplitude An integer in [1,255] representing the device default amplitude to
114      *                        replace the {@link VibrationEffect#DEFAULT_AMPLITUDE}.
115      */
resolveEffects(int defaultAmplitude)116     public void resolveEffects(int defaultAmplitude) {
117         CombinedVibration newEffect =
118                 mEffectToPlay.transform(VibrationEffect::resolve, defaultAmplitude);
119         if (!Objects.equals(mEffectToPlay, newEffect)) {
120             mEffectToPlay = newEffect;
121         }
122         for (int i = 0; i < mFallbacks.size(); i++) {
123             mFallbacks.setValueAt(i, mFallbacks.valueAt(i).resolve(defaultAmplitude));
124         }
125     }
126 
127     /**
128      * Scales the {@link #getEffectToPlay()} and each fallback effect based on the vibration usage.
129      */
scaleEffects(VibrationScaler scaler)130     public void scaleEffects(VibrationScaler scaler) {
131         int vibrationUsage = callerInfo.attrs.getUsage();
132 
133         // Save scale values for debugging purposes.
134         mScaleLevel = scaler.getScaleLevel(vibrationUsage);
135         mAdaptiveScale = scaler.getAdaptiveHapticsScale(vibrationUsage);
136         stats.reportAdaptiveScale(mAdaptiveScale);
137 
138         // Scale all VibrationEffect instances in given CombinedVibration.
139         CombinedVibration newEffect = mEffectToPlay.transform(scaler::scale, vibrationUsage);
140         if (!Objects.equals(mEffectToPlay, newEffect)) {
141             mEffectToPlay = newEffect;
142         }
143 
144         // Scale all fallback VibrationEffect instances that can be used by VibrationThread.
145         for (int i = 0; i < mFallbacks.size(); i++) {
146             mFallbacks.setValueAt(i, scaler.scale(mFallbacks.valueAt(i), vibrationUsage));
147         }
148     }
149 
150     /**
151      * Adapts the {@link #getEffectToPlay()} to the device using given vibrator adapter.
152      *
153      * @param deviceAdapter A {@link CombinedVibration.VibratorAdapter} that transforms vibration
154      *                      effects to device vibrators based on its capabilities.
155      */
adaptToDevice(CombinedVibration.VibratorAdapter deviceAdapter)156     public void adaptToDevice(CombinedVibration.VibratorAdapter deviceAdapter) {
157         CombinedVibration newEffect = mEffectToPlay.adapt(deviceAdapter);
158         if (!Objects.equals(mEffectToPlay, newEffect)) {
159             mEffectToPlay = newEffect;
160         }
161         // No need to update fallback effects, they are already configured per device.
162     }
163 
164     /** Return true is current status is different from {@link Status#RUNNING}. */
hasEnded()165     public boolean hasEnded() {
166         return mStatus != Status.RUNNING;
167     }
168 
169     @Override
isRepeating()170     public boolean isRepeating() {
171         return mOriginalEffect.getDuration() == Long.MAX_VALUE;
172     }
173 
174     /** Return the effect that should be played by this vibration. */
getEffectToPlay()175     public CombinedVibration getEffectToPlay() {
176         return mEffectToPlay;
177     }
178 
179     /** Return {@link Vibration.DebugInfo} with read-only debug information about this vibration. */
getDebugInfo()180     public Vibration.DebugInfo getDebugInfo() {
181         // Clear the original effect if it's the same as the effect that was played, for simplicity
182         CombinedVibration originalEffect =
183                 Objects.equals(mOriginalEffect, mEffectToPlay) ? null : mOriginalEffect;
184         return new Vibration.DebugInfo(mStatus, stats, mEffectToPlay, originalEffect,
185                 mScaleLevel, mAdaptiveScale, callerInfo);
186     }
187 
188     /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
getStatsInfo(long completionUptimeMillis)189     public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
190         int vibrationType = isRepeating()
191                 ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED
192                 : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
193         return new VibrationStats.StatsInfo(
194                 callerInfo.uid, vibrationType, callerInfo.attrs.getUsage(), mStatus,
195                 stats, completionUptimeMillis);
196     }
197 
198     /**
199      * Returns true if this vibration can pipeline with the specified one.
200      *
201      * <p>Note that currently, repeating vibrations can't pipeline with following vibrations,
202      * because the cancel() call to stop the repetition will cancel a pending vibration too. This
203      * can be changed if we have a use-case to reason around behavior for. It may also be nice to
204      * pipeline very short vibrations together, regardless of the flag.
205      */
canPipelineWith(HalVibration vib)206     public boolean canPipelineWith(HalVibration vib) {
207         return callerInfo.uid == vib.callerInfo.uid && callerInfo.attrs.isFlagSet(
208                 VibrationAttributes.FLAG_PIPELINED_EFFECT)
209                 && vib.callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT)
210                 && !isRepeating();
211     }
212 }
213