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