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.Nullable;
20 import android.os.CombinedVibration;
21 import android.os.IBinder;
22 import android.os.SystemClock;
23 import android.os.VibrationAttributes;
24 import android.os.VibrationEffect;
25 import android.os.vibrator.PrebakedSegment;
26 import android.os.vibrator.PrimitiveSegment;
27 import android.os.vibrator.RampSegment;
28 import android.os.vibrator.StepSegment;
29 import android.os.vibrator.VibrationEffectSegment;
30 import android.util.SparseArray;
31 import android.util.proto.ProtoOutputStream;
32 
33 import java.text.SimpleDateFormat;
34 import java.util.Date;
35 import java.util.List;
36 import java.util.function.Function;
37 
38 /** Represents a vibration request to the vibrator service. */
39 final class Vibration {
40     private static final String TAG = "Vibration";
41     private static final SimpleDateFormat DEBUG_DATE_FORMAT =
42             new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
43 
44     enum Status {
45         RUNNING,
46         FINISHED,
47         FINISHED_UNEXPECTED,  // Didn't terminate in the usual way.
48         FORWARDED_TO_INPUT_DEVICES,
49         CANCELLED,
50         IGNORED_ERROR_APP_OPS,
51         IGNORED_ERROR_TOKEN,
52         IGNORED,
53         IGNORED_APP_OPS,
54         IGNORED_BACKGROUND,
55         IGNORED_RINGTONE,
56         IGNORED_UNKNOWN_VIBRATION,
57         IGNORED_UNSUPPORTED,
58         IGNORED_FOR_ALARM,
59         IGNORED_FOR_EXTERNAL,
60         IGNORED_FOR_ONGOING,
61         IGNORED_FOR_POWER,
62         IGNORED_FOR_SETTINGS,
63     }
64 
65     /** Start time in CLOCK_BOOTTIME base. */
66     public final long startTime;
67     public final VibrationAttributes attrs;
68     public final long id;
69     public final int uid;
70     public final String opPkg;
71     public final String reason;
72     public final IBinder token;
73     public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>();
74 
75     /** The actual effect to be played. */
76     @Nullable
77     private CombinedVibration mEffect;
78 
79     /**
80      * The original effect that was requested. Typically these two things differ because the effect
81      * was scaled based on the users vibration intensity settings.
82      */
83     @Nullable
84     private CombinedVibration mOriginalEffect;
85 
86     /**
87      * Start/end times in unix epoch time. Only to be used for debugging purposes and to correlate
88      * with other system events, any duration calculations should be done use {@link #startTime} so
89      * as not to be affected by discontinuities created by RTC adjustments.
90      */
91     private final long mStartTimeDebug;
92     private long mEndTimeDebug;
93     private Status mStatus;
94 
Vibration(IBinder token, int id, CombinedVibration effect, VibrationAttributes attrs, int uid, String opPkg, String reason)95     Vibration(IBinder token, int id, CombinedVibration effect,
96             VibrationAttributes attrs, int uid, String opPkg, String reason) {
97         this.token = token;
98         this.mEffect = effect;
99         this.id = id;
100         this.startTime = SystemClock.elapsedRealtime();
101         this.attrs = attrs;
102         this.uid = uid;
103         this.opPkg = opPkg;
104         this.reason = reason;
105         mStartTimeDebug = System.currentTimeMillis();
106         mStatus = Status.RUNNING;
107     }
108 
109     /**
110      * Set the {@link Status} of this vibration and the current system time as this
111      * vibration end time, for debugging purposes.
112      *
113      * <p>This method will only accept given value if the current status is {@link
114      * Status#RUNNING}.
115      */
end(Status status)116     public void end(Status status) {
117         if (hasEnded()) {
118             // Vibration already ended, keep first ending status set and ignore this one.
119             return;
120         }
121         mStatus = status;
122         mEndTimeDebug = System.currentTimeMillis();
123     }
124 
125     /**
126      * Return the effect to be played when given prebaked effect id is not supported by the
127      * vibrator.
128      */
129     @Nullable
getFallback(int effectId)130     public VibrationEffect getFallback(int effectId) {
131         return mFallbacks.get(effectId);
132     }
133 
134     /**
135      * Add a fallback {@link VibrationEffect} to be played when given effect id is not supported,
136      * which might be necessary for replacement in realtime.
137      */
addFallback(int effectId, VibrationEffect effect)138     public void addFallback(int effectId, VibrationEffect effect) {
139         mFallbacks.put(effectId, effect);
140     }
141 
142     /**
143      * Applied update function to the current effect held by this vibration, and to each fallback
144      * effect added.
145      */
updateEffects(Function<VibrationEffect, VibrationEffect> updateFn)146     public void updateEffects(Function<VibrationEffect, VibrationEffect> updateFn) {
147         CombinedVibration newEffect = transformCombinedEffect(mEffect, updateFn);
148         if (!newEffect.equals(mEffect)) {
149             if (mOriginalEffect == null) {
150                 mOriginalEffect = mEffect;
151             }
152             mEffect = newEffect;
153         }
154         for (int i = 0; i < mFallbacks.size(); i++) {
155             mFallbacks.setValueAt(i, updateFn.apply(mFallbacks.valueAt(i)));
156         }
157     }
158 
159     /**
160      * Creates a new {@link CombinedVibration} by applying the given transformation function
161      * to each {@link VibrationEffect}.
162      */
transformCombinedEffect( CombinedVibration combinedEffect, Function<VibrationEffect, VibrationEffect> fn)163     private static CombinedVibration transformCombinedEffect(
164             CombinedVibration combinedEffect, Function<VibrationEffect, VibrationEffect> fn) {
165         if (combinedEffect instanceof CombinedVibration.Mono) {
166             VibrationEffect effect = ((CombinedVibration.Mono) combinedEffect).getEffect();
167             return CombinedVibration.createParallel(fn.apply(effect));
168         } else if (combinedEffect instanceof CombinedVibration.Stereo) {
169             SparseArray<VibrationEffect> effects =
170                     ((CombinedVibration.Stereo) combinedEffect).getEffects();
171             CombinedVibration.ParallelCombination combination =
172                     CombinedVibration.startParallel();
173             for (int i = 0; i < effects.size(); i++) {
174                 combination.addVibrator(effects.keyAt(i), fn.apply(effects.valueAt(i)));
175             }
176             return combination.combine();
177         } else if (combinedEffect instanceof CombinedVibration.Sequential) {
178             List<CombinedVibration> effects =
179                     ((CombinedVibration.Sequential) combinedEffect).getEffects();
180             CombinedVibration.SequentialCombination combination =
181                     CombinedVibration.startSequential();
182             for (CombinedVibration effect : effects) {
183                 combination.addNext(transformCombinedEffect(effect, fn));
184             }
185             return combination.combine();
186         } else {
187             // Unknown combination, return same effect.
188             return combinedEffect;
189         }
190     }
191 
192     /** Return true is current status is different from {@link Status#RUNNING}. */
hasEnded()193     public boolean hasEnded() {
194         return mStatus != Status.RUNNING;
195     }
196 
197     /** Return true is effect is a repeating vibration. */
isRepeating()198     public boolean isRepeating() {
199         return mEffect.getDuration() == Long.MAX_VALUE;
200     }
201 
202     /** Return the effect that should be played by this vibration. */
203     @Nullable
getEffect()204     public CombinedVibration getEffect() {
205         return mEffect;
206     }
207 
208     /** Return {@link Vibration.DebugInfo} with read-only debug information about this vibration. */
getDebugInfo()209     public Vibration.DebugInfo getDebugInfo() {
210         return new Vibration.DebugInfo(
211                 mStartTimeDebug, mEndTimeDebug, mEffect, mOriginalEffect, /* scale= */ 0, attrs,
212                 uid, opPkg, reason, mStatus);
213     }
214 
215     /** Debug information about vibrations. */
216     static final class DebugInfo {
217         private final long mStartTimeDebug;
218         private final long mEndTimeDebug;
219         private final CombinedVibration mEffect;
220         private final CombinedVibration mOriginalEffect;
221         private final float mScale;
222         private final VibrationAttributes mAttrs;
223         private final int mUid;
224         private final String mOpPkg;
225         private final String mReason;
226         private final Status mStatus;
227 
DebugInfo(long startTimeDebug, long endTimeDebug, CombinedVibration effect, CombinedVibration originalEffect, float scale, VibrationAttributes attrs, int uid, String opPkg, String reason, Status status)228         DebugInfo(long startTimeDebug, long endTimeDebug, CombinedVibration effect,
229                 CombinedVibration originalEffect, float scale, VibrationAttributes attrs,
230                 int uid, String opPkg, String reason, Status status) {
231             mStartTimeDebug = startTimeDebug;
232             mEndTimeDebug = endTimeDebug;
233             mEffect = effect;
234             mOriginalEffect = originalEffect;
235             mScale = scale;
236             mAttrs = attrs;
237             mUid = uid;
238             mOpPkg = opPkg;
239             mReason = reason;
240             mStatus = status;
241         }
242 
243         @Override
toString()244         public String toString() {
245             return new StringBuilder()
246                     .append("startTime: ")
247                     .append(DEBUG_DATE_FORMAT.format(new Date(mStartTimeDebug)))
248                     .append(", endTime: ")
249                     .append(mEndTimeDebug == 0 ? null
250                             : DEBUG_DATE_FORMAT.format(new Date(mEndTimeDebug)))
251                     .append(", status: ")
252                     .append(mStatus.name().toLowerCase())
253                     .append(", effect: ")
254                     .append(mEffect)
255                     .append(", originalEffect: ")
256                     .append(mOriginalEffect)
257                     .append(", scale: ")
258                     .append(String.format("%.2f", mScale))
259                     .append(", attrs: ")
260                     .append(mAttrs)
261                     .append(", uid: ")
262                     .append(mUid)
263                     .append(", opPkg: ")
264                     .append(mOpPkg)
265                     .append(", reason: ")
266                     .append(mReason)
267                     .toString();
268         }
269 
270         /** Write this info into given {@code fieldId} on {@link ProtoOutputStream}. */
dumpProto(ProtoOutputStream proto, long fieldId)271         public void dumpProto(ProtoOutputStream proto, long fieldId) {
272             final long token = proto.start(fieldId);
273             proto.write(VibrationProto.START_TIME, mStartTimeDebug);
274             proto.write(VibrationProto.END_TIME, mEndTimeDebug);
275             proto.write(VibrationProto.STATUS, mStatus.ordinal());
276 
277             final long attrsToken = proto.start(VibrationProto.ATTRIBUTES);
278             proto.write(VibrationAttributesProto.USAGE, mAttrs.getUsage());
279             proto.write(VibrationAttributesProto.AUDIO_USAGE, mAttrs.getAudioUsage());
280             proto.write(VibrationAttributesProto.FLAGS, mAttrs.getFlags());
281             proto.end(attrsToken);
282 
283             if (mEffect != null) {
284                 dumpEffect(proto, VibrationProto.EFFECT, mEffect);
285             }
286             if (mOriginalEffect != null) {
287                 dumpEffect(proto, VibrationProto.ORIGINAL_EFFECT, mOriginalEffect);
288             }
289 
290             proto.end(token);
291         }
292 
dumpEffect( ProtoOutputStream proto, long fieldId, CombinedVibration effect)293         private void dumpEffect(
294                 ProtoOutputStream proto, long fieldId, CombinedVibration effect) {
295             dumpEffect(proto, fieldId,
296                     (CombinedVibration.Sequential) CombinedVibration.startSequential()
297                             .addNext(effect)
298                             .combine());
299         }
300 
dumpEffect( ProtoOutputStream proto, long fieldId, CombinedVibration.Sequential effect)301         private void dumpEffect(
302                 ProtoOutputStream proto, long fieldId, CombinedVibration.Sequential effect) {
303             final long token = proto.start(fieldId);
304             for (int i = 0; i < effect.getEffects().size(); i++) {
305                 CombinedVibration nestedEffect = effect.getEffects().get(i);
306                 if (nestedEffect instanceof CombinedVibration.Mono) {
307                     dumpEffect(proto, CombinedVibrationEffectProto.EFFECTS,
308                             (CombinedVibration.Mono) nestedEffect);
309                 } else if (nestedEffect instanceof CombinedVibration.Stereo) {
310                     dumpEffect(proto, CombinedVibrationEffectProto.EFFECTS,
311                             (CombinedVibration.Stereo) nestedEffect);
312                 }
313                 proto.write(CombinedVibrationEffectProto.DELAYS, effect.getDelays().get(i));
314             }
315             proto.end(token);
316         }
317 
dumpEffect( ProtoOutputStream proto, long fieldId, CombinedVibration.Mono effect)318         private void dumpEffect(
319                 ProtoOutputStream proto, long fieldId, CombinedVibration.Mono effect) {
320             final long token = proto.start(fieldId);
321             dumpEffect(proto, SyncVibrationEffectProto.EFFECTS, effect.getEffect());
322             proto.end(token);
323         }
324 
dumpEffect( ProtoOutputStream proto, long fieldId, CombinedVibration.Stereo effect)325         private void dumpEffect(
326                 ProtoOutputStream proto, long fieldId, CombinedVibration.Stereo effect) {
327             final long token = proto.start(fieldId);
328             for (int i = 0; i < effect.getEffects().size(); i++) {
329                 proto.write(SyncVibrationEffectProto.VIBRATOR_IDS, effect.getEffects().keyAt(i));
330                 dumpEffect(proto, SyncVibrationEffectProto.EFFECTS, effect.getEffects().valueAt(i));
331             }
332             proto.end(token);
333         }
334 
dumpEffect( ProtoOutputStream proto, long fieldId, VibrationEffect effect)335         private void dumpEffect(
336                 ProtoOutputStream proto, long fieldId, VibrationEffect effect) {
337             final long token = proto.start(fieldId);
338             VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
339             for (VibrationEffectSegment segment : composed.getSegments()) {
340                 dumpEffect(proto, VibrationEffectProto.SEGMENTS, segment);
341             }
342             proto.write(VibrationEffectProto.REPEAT, composed.getRepeatIndex());
343             proto.end(token);
344         }
345 
dumpEffect(ProtoOutputStream proto, long fieldId, VibrationEffectSegment segment)346         private void dumpEffect(ProtoOutputStream proto, long fieldId,
347                 VibrationEffectSegment segment) {
348             final long token = proto.start(fieldId);
349             if (segment instanceof StepSegment) {
350                 dumpEffect(proto, SegmentProto.STEP, (StepSegment) segment);
351             } else if (segment instanceof RampSegment) {
352                 dumpEffect(proto, SegmentProto.RAMP, (RampSegment) segment);
353             } else if (segment instanceof PrebakedSegment) {
354                 dumpEffect(proto, SegmentProto.PREBAKED, (PrebakedSegment) segment);
355             } else if (segment instanceof PrimitiveSegment) {
356                 dumpEffect(proto, SegmentProto.PRIMITIVE, (PrimitiveSegment) segment);
357             }
358             proto.end(token);
359         }
360 
dumpEffect(ProtoOutputStream proto, long fieldId, StepSegment segment)361         private void dumpEffect(ProtoOutputStream proto, long fieldId, StepSegment segment) {
362             final long token = proto.start(fieldId);
363             proto.write(StepSegmentProto.DURATION, segment.getDuration());
364             proto.write(StepSegmentProto.AMPLITUDE, segment.getAmplitude());
365             proto.write(StepSegmentProto.FREQUENCY, segment.getFrequency());
366             proto.end(token);
367         }
368 
dumpEffect(ProtoOutputStream proto, long fieldId, RampSegment segment)369         private void dumpEffect(ProtoOutputStream proto, long fieldId, RampSegment segment) {
370             final long token = proto.start(fieldId);
371             proto.write(RampSegmentProto.DURATION, segment.getDuration());
372             proto.write(RampSegmentProto.START_AMPLITUDE, segment.getStartAmplitude());
373             proto.write(RampSegmentProto.END_AMPLITUDE, segment.getEndAmplitude());
374             proto.write(RampSegmentProto.START_FREQUENCY, segment.getStartFrequency());
375             proto.write(RampSegmentProto.END_FREQUENCY, segment.getEndFrequency());
376             proto.end(token);
377         }
378 
dumpEffect(ProtoOutputStream proto, long fieldId, PrebakedSegment segment)379         private void dumpEffect(ProtoOutputStream proto, long fieldId,
380                 PrebakedSegment segment) {
381             final long token = proto.start(fieldId);
382             proto.write(PrebakedSegmentProto.EFFECT_ID, segment.getEffectId());
383             proto.write(PrebakedSegmentProto.EFFECT_STRENGTH, segment.getEffectStrength());
384             proto.write(PrebakedSegmentProto.FALLBACK, segment.shouldFallback());
385             proto.end(token);
386         }
387 
dumpEffect(ProtoOutputStream proto, long fieldId, PrimitiveSegment segment)388         private void dumpEffect(ProtoOutputStream proto, long fieldId,
389                 PrimitiveSegment segment) {
390             final long token = proto.start(fieldId);
391             proto.write(PrimitiveSegmentProto.PRIMITIVE_ID, segment.getPrimitiveId());
392             proto.write(PrimitiveSegmentProto.SCALE, segment.getScale());
393             proto.write(PrimitiveSegmentProto.DELAY, segment.getDelay());
394             proto.end(token);
395         }
396     }
397 }
398