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.List;
30 import java.util.concurrent.CountDownLatch;
31 import java.util.function.Function;
32 
33 /**
34  * Represents a vibration defined by a {@link CombinedVibration} that will be performed by
35  * the IVibrator HAL.
36  */
37 final class HalVibration extends Vibration {
38 
39     public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>();
40 
41     /** The actual effect to be played. */
42     @Nullable
43     private CombinedVibration mEffect;
44 
45     /**
46      * The original effect that was requested. Typically these two things differ because the effect
47      * was scaled based on the users vibration intensity settings.
48      */
49     @Nullable
50     private CombinedVibration mOriginalEffect;
51 
52     /** Vibration status. */
53     private Vibration.Status mStatus;
54 
55     /** A {@link CountDownLatch} to enable waiting for completion. */
56     private final CountDownLatch mCompletionLatch = new CountDownLatch(1);
57 
HalVibration(@onNull IBinder token, CombinedVibration effect, @NonNull CallerInfo callerInfo)58     HalVibration(@NonNull IBinder token, CombinedVibration effect, @NonNull CallerInfo callerInfo) {
59         super(token, callerInfo);
60         this.mEffect = effect;
61         mStatus = Vibration.Status.RUNNING;
62     }
63 
64     /**
65      * Set the {@link Status} of this vibration and reports the current system time as this
66      * vibration end time, for debugging purposes.
67      *
68      * <p>This method will only accept given value if the current status is {@link
69      * Status#RUNNING}.
70      */
end(EndInfo info)71     public void end(EndInfo info) {
72         if (hasEnded()) {
73             // Vibration already ended, keep first ending status set and ignore this one.
74             return;
75         }
76         mStatus = info.status;
77         stats.reportEnded(info.endedBy);
78         mCompletionLatch.countDown();
79     }
80 
81     /** Waits indefinitely until another thread calls {@link #end} on this vibration. */
waitForEnd()82     public void waitForEnd() throws InterruptedException {
83         mCompletionLatch.await();
84     }
85 
86     /**
87      * Return the effect to be played when given prebaked effect id is not supported by the
88      * vibrator.
89      */
90     @Nullable
getFallback(int effectId)91     public VibrationEffect getFallback(int effectId) {
92         return mFallbacks.get(effectId);
93     }
94 
95     /**
96      * Add a fallback {@link VibrationEffect} to be played when given effect id is not supported,
97      * which might be necessary for replacement in realtime.
98      */
addFallback(int effectId, VibrationEffect effect)99     public void addFallback(int effectId, VibrationEffect effect) {
100         mFallbacks.put(effectId, effect);
101     }
102 
103     /**
104      * Applied update function to the current effect held by this vibration, and to each fallback
105      * effect added.
106      */
updateEffects(Function<VibrationEffect, VibrationEffect> updateFn)107     public void updateEffects(Function<VibrationEffect, VibrationEffect> updateFn) {
108         CombinedVibration newEffect = transformCombinedEffect(mEffect, updateFn);
109         if (!newEffect.equals(mEffect)) {
110             if (mOriginalEffect == null) {
111                 mOriginalEffect = mEffect;
112             }
113             mEffect = newEffect;
114         }
115         for (int i = 0; i < mFallbacks.size(); i++) {
116             mFallbacks.setValueAt(i, updateFn.apply(mFallbacks.valueAt(i)));
117         }
118     }
119 
120     /**
121      * Creates a new {@link CombinedVibration} by applying the given transformation function
122      * to each {@link VibrationEffect}.
123      */
transformCombinedEffect( CombinedVibration combinedEffect, Function<VibrationEffect, VibrationEffect> fn)124     private static CombinedVibration transformCombinedEffect(
125             CombinedVibration combinedEffect, Function<VibrationEffect, VibrationEffect> fn) {
126         if (combinedEffect instanceof CombinedVibration.Mono) {
127             VibrationEffect effect = ((CombinedVibration.Mono) combinedEffect).getEffect();
128             return CombinedVibration.createParallel(fn.apply(effect));
129         } else if (combinedEffect instanceof CombinedVibration.Stereo) {
130             SparseArray<VibrationEffect> effects =
131                     ((CombinedVibration.Stereo) combinedEffect).getEffects();
132             CombinedVibration.ParallelCombination combination =
133                     CombinedVibration.startParallel();
134             for (int i = 0; i < effects.size(); i++) {
135                 combination.addVibrator(effects.keyAt(i), fn.apply(effects.valueAt(i)));
136             }
137             return combination.combine();
138         } else if (combinedEffect instanceof CombinedVibration.Sequential) {
139             List<CombinedVibration> effects =
140                     ((CombinedVibration.Sequential) combinedEffect).getEffects();
141             CombinedVibration.SequentialCombination combination =
142                     CombinedVibration.startSequential();
143             for (CombinedVibration effect : effects) {
144                 combination.addNext(transformCombinedEffect(effect, fn));
145             }
146             return combination.combine();
147         } else {
148             // Unknown combination, return same effect.
149             return combinedEffect;
150         }
151     }
152 
153     /** Return true is current status is different from {@link Status#RUNNING}. */
hasEnded()154     public boolean hasEnded() {
155         return mStatus != Status.RUNNING;
156     }
157 
158     @Override
isRepeating()159     public boolean isRepeating() {
160         return mEffect.getDuration() == Long.MAX_VALUE;
161     }
162 
163     /** Return the effect that should be played by this vibration. */
164     @Nullable
getEffect()165     public CombinedVibration getEffect() {
166         return mEffect;
167     }
168 
169     /**
170      * Return {@link Vibration.DebugInfo} with read-only debug information about this vibration.
171      */
getDebugInfo()172     public Vibration.DebugInfo getDebugInfo() {
173         return new Vibration.DebugInfo(mStatus, stats, mEffect, mOriginalEffect, /* scale= */ 0,
174                 callerInfo);
175     }
176 
177     /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
getStatsInfo(long completionUptimeMillis)178     public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
179         int vibrationType = isRepeating()
180                 ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED
181                 : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
182         return new VibrationStats.StatsInfo(
183                 callerInfo.uid, vibrationType, callerInfo.attrs.getUsage(), mStatus,
184                 stats, completionUptimeMillis);
185     }
186 
187     /**
188      * Returns true if this vibration can pipeline with the specified one.
189      *
190      * <p>Note that currently, repeating vibrations can't pipeline with following vibrations,
191      * because the cancel() call to stop the repetition will cancel a pending vibration too. This
192      * can be changed if we have a use-case to reason around behavior for. It may also be nice to
193      * pipeline very short vibrations together, regardless of the flag.
194      */
canPipelineWith(HalVibration vib)195     public boolean canPipelineWith(HalVibration vib) {
196         return callerInfo.uid == vib.callerInfo.uid && callerInfo.attrs.isFlagSet(
197                 VibrationAttributes.FLAG_PIPELINED_EFFECT)
198                 && vib.callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT)
199                 && !isRepeating();
200     }
201 }
202