• 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.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.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 com.android.internal.util.FrameworkStatsLog;
34 
35 import java.text.SimpleDateFormat;
36 import java.util.Date;
37 import java.util.List;
38 import java.util.Objects;
39 import java.util.concurrent.CountDownLatch;
40 import java.util.function.Function;
41 
42 /** Represents a vibration request to the vibrator service. */
43 final class Vibration {
44     private static final SimpleDateFormat DEBUG_DATE_FORMAT =
45             new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
46 
47     /** Vibration status with reference to values from vibratormanagerservice.proto for logging. */
48     enum Status {
49         UNKNOWN(VibrationProto.UNKNOWN),
50         RUNNING(VibrationProto.RUNNING),
51         FINISHED(VibrationProto.FINISHED),
52         FINISHED_UNEXPECTED(VibrationProto.FINISHED_UNEXPECTED),
53         FORWARDED_TO_INPUT_DEVICES(VibrationProto.FORWARDED_TO_INPUT_DEVICES),
54         CANCELLED_BINDER_DIED(VibrationProto.CANCELLED_BINDER_DIED),
55         CANCELLED_BY_SCREEN_OFF(VibrationProto.CANCELLED_BY_SCREEN_OFF),
56         CANCELLED_BY_SETTINGS_UPDATE(VibrationProto.CANCELLED_BY_SETTINGS_UPDATE),
57         CANCELLED_BY_USER(VibrationProto.CANCELLED_BY_USER),
58         CANCELLED_BY_UNKNOWN_REASON(VibrationProto.CANCELLED_BY_UNKNOWN_REASON),
59         CANCELLED_SUPERSEDED(VibrationProto.CANCELLED_SUPERSEDED),
60         IGNORED_ERROR_APP_OPS(VibrationProto.IGNORED_ERROR_APP_OPS),
61         IGNORED_ERROR_CANCELLING(VibrationProto.IGNORED_ERROR_CANCELLING),
62         IGNORED_ERROR_SCHEDULING(VibrationProto.IGNORED_ERROR_SCHEDULING),
63         IGNORED_ERROR_TOKEN(VibrationProto.IGNORED_ERROR_TOKEN),
64         IGNORED_APP_OPS(VibrationProto.IGNORED_APP_OPS),
65         IGNORED_BACKGROUND(VibrationProto.IGNORED_BACKGROUND),
66         IGNORED_UNKNOWN_VIBRATION(VibrationProto.IGNORED_UNKNOWN_VIBRATION),
67         IGNORED_UNSUPPORTED(VibrationProto.IGNORED_UNSUPPORTED),
68         IGNORED_FOR_EXTERNAL(VibrationProto.IGNORED_FOR_EXTERNAL),
69         IGNORED_FOR_HIGHER_IMPORTANCE(VibrationProto.IGNORED_FOR_HIGHER_IMPORTANCE),
70         IGNORED_FOR_ONGOING(VibrationProto.IGNORED_FOR_ONGOING),
71         IGNORED_FOR_POWER(VibrationProto.IGNORED_FOR_POWER),
72         IGNORED_FOR_RINGER_MODE(VibrationProto.IGNORED_FOR_RINGER_MODE),
73         IGNORED_FOR_SETTINGS(VibrationProto.IGNORED_FOR_SETTINGS),
74         IGNORED_SUPERSEDED(VibrationProto.IGNORED_SUPERSEDED),
75         IGNORED_FROM_VIRTUAL_DEVICE(VibrationProto.IGNORED_FROM_VIRTUAL_DEVICE);
76 
77         private final int mProtoEnumValue;
78 
Status(int value)79         Status(int value) {
80             mProtoEnumValue = value;
81         }
82 
getProtoEnumValue()83         public int getProtoEnumValue() {
84             return mProtoEnumValue;
85         }
86     }
87 
88     public final VibrationAttributes attrs;
89     public final long id;
90     public final int uid;
91     public final int displayId;
92     public final String opPkg;
93     public final String reason;
94     public final IBinder token;
95     public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>();
96 
97     /** The actual effect to be played. */
98     @Nullable
99     private CombinedVibration mEffect;
100 
101     /**
102      * The original effect that was requested. Typically these two things differ because the effect
103      * was scaled based on the users vibration intensity settings.
104      */
105     @Nullable
106     private CombinedVibration mOriginalEffect;
107 
108     /** Vibration status. */
109     private Vibration.Status mStatus;
110 
111     /** Vibration runtime stats. */
112     private final VibrationStats mStats = new VibrationStats();
113 
114     /** A {@link CountDownLatch} to enable waiting for completion. */
115     private final CountDownLatch mCompletionLatch = new CountDownLatch(1);
116 
Vibration(IBinder token, int id, CombinedVibration effect, VibrationAttributes attrs, int uid, int displayId, String opPkg, String reason)117     Vibration(IBinder token, int id, CombinedVibration effect,
118             VibrationAttributes attrs, int uid, int displayId, String opPkg, String reason) {
119         this.token = token;
120         this.mEffect = effect;
121         this.id = id;
122         this.attrs = attrs;
123         this.uid = uid;
124         this.displayId = displayId;
125         this.opPkg = opPkg;
126         this.reason = reason;
127         mStatus = Vibration.Status.RUNNING;
128     }
129 
stats()130     VibrationStats stats() {
131         return mStats;
132     }
133 
134     /**
135      * Set the {@link Status} of this vibration and reports the current system time as this
136      * vibration end time, for debugging purposes.
137      *
138      * <p>This method will only accept given value if the current status is {@link
139      * Status#RUNNING}.
140      */
end(EndInfo info)141     public void end(EndInfo info) {
142         if (hasEnded()) {
143             // Vibration already ended, keep first ending status set and ignore this one.
144             return;
145         }
146         mStatus = info.status;
147         mStats.reportEnded(info.endedByUid, info.endedByUsage);
148         mCompletionLatch.countDown();
149     }
150 
151     /** Waits indefinitely until another thread calls {@link #end} on this vibration. */
waitForEnd()152     public void waitForEnd() throws InterruptedException {
153         mCompletionLatch.await();
154     }
155 
156     /**
157      * Return the effect to be played when given prebaked effect id is not supported by the
158      * vibrator.
159      */
160     @Nullable
getFallback(int effectId)161     public VibrationEffect getFallback(int effectId) {
162         return mFallbacks.get(effectId);
163     }
164 
165     /**
166      * Add a fallback {@link VibrationEffect} to be played when given effect id is not supported,
167      * which might be necessary for replacement in realtime.
168      */
addFallback(int effectId, VibrationEffect effect)169     public void addFallback(int effectId, VibrationEffect effect) {
170         mFallbacks.put(effectId, effect);
171     }
172 
173     /**
174      * Applied update function to the current effect held by this vibration, and to each fallback
175      * effect added.
176      */
updateEffects(Function<VibrationEffect, VibrationEffect> updateFn)177     public void updateEffects(Function<VibrationEffect, VibrationEffect> updateFn) {
178         CombinedVibration newEffect = transformCombinedEffect(mEffect, updateFn);
179         if (!newEffect.equals(mEffect)) {
180             if (mOriginalEffect == null) {
181                 mOriginalEffect = mEffect;
182             }
183             mEffect = newEffect;
184         }
185         for (int i = 0; i < mFallbacks.size(); i++) {
186             mFallbacks.setValueAt(i, updateFn.apply(mFallbacks.valueAt(i)));
187         }
188     }
189 
190     /**
191      * Creates a new {@link CombinedVibration} by applying the given transformation function
192      * to each {@link VibrationEffect}.
193      */
transformCombinedEffect( CombinedVibration combinedEffect, Function<VibrationEffect, VibrationEffect> fn)194     private static CombinedVibration transformCombinedEffect(
195             CombinedVibration combinedEffect, Function<VibrationEffect, VibrationEffect> fn) {
196         if (combinedEffect instanceof CombinedVibration.Mono) {
197             VibrationEffect effect = ((CombinedVibration.Mono) combinedEffect).getEffect();
198             return CombinedVibration.createParallel(fn.apply(effect));
199         } else if (combinedEffect instanceof CombinedVibration.Stereo) {
200             SparseArray<VibrationEffect> effects =
201                     ((CombinedVibration.Stereo) combinedEffect).getEffects();
202             CombinedVibration.ParallelCombination combination =
203                     CombinedVibration.startParallel();
204             for (int i = 0; i < effects.size(); i++) {
205                 combination.addVibrator(effects.keyAt(i), fn.apply(effects.valueAt(i)));
206             }
207             return combination.combine();
208         } else if (combinedEffect instanceof CombinedVibration.Sequential) {
209             List<CombinedVibration> effects =
210                     ((CombinedVibration.Sequential) combinedEffect).getEffects();
211             CombinedVibration.SequentialCombination combination =
212                     CombinedVibration.startSequential();
213             for (CombinedVibration effect : effects) {
214                 combination.addNext(transformCombinedEffect(effect, fn));
215             }
216             return combination.combine();
217         } else {
218             // Unknown combination, return same effect.
219             return combinedEffect;
220         }
221     }
222 
223     /** Return true is current status is different from {@link Status#RUNNING}. */
hasEnded()224     public boolean hasEnded() {
225         return mStatus != Status.RUNNING;
226     }
227 
228     /** Return true is effect is a repeating vibration. */
isRepeating()229     public boolean isRepeating() {
230         return mEffect.getDuration() == Long.MAX_VALUE;
231     }
232 
233     /** Return the effect that should be played by this vibration. */
234     @Nullable
getEffect()235     public CombinedVibration getEffect() {
236         return mEffect;
237     }
238 
239     /** Return {@link Vibration.DebugInfo} with read-only debug information about this vibration. */
getDebugInfo()240     public Vibration.DebugInfo getDebugInfo() {
241         return new Vibration.DebugInfo(mStatus, mStats, mEffect, mOriginalEffect, /* scale= */ 0,
242                 attrs, uid, displayId, opPkg, reason);
243     }
244 
245     /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
getStatsInfo(long completionUptimeMillis)246     public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
247         int vibrationType = isRepeating()
248                 ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED
249                 : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
250         return new VibrationStats.StatsInfo(
251                 uid, vibrationType, attrs.getUsage(), mStatus, mStats, completionUptimeMillis);
252     }
253 
254     /** Immutable info passed as a signal to end a vibration. */
255     static final class EndInfo {
256         /** The {@link Status} to be set to the vibration when it ends with this info. */
257         @NonNull
258         public final Status status;
259         /** The UID that triggered the vibration that ended this, or -1 if undefined. */
260         public final int endedByUid;
261         /** The VibrationAttributes.USAGE_* of the vibration that ended this, or -1 if undefined. */
262         public final int endedByUsage;
263 
EndInfo(@onNull Vibration.Status status)264         EndInfo(@NonNull Vibration.Status status) {
265             this(status, /* endedByUid= */ -1, /* endedByUsage= */ -1);
266         }
267 
EndInfo(@onNull Vibration.Status status, int endedByUid, int endedByUsage)268         EndInfo(@NonNull Vibration.Status status, int endedByUid, int endedByUsage) {
269             this.status = status;
270             this.endedByUid = endedByUid;
271             this.endedByUsage = endedByUsage;
272         }
273 
274         @Override
equals(Object o)275         public boolean equals(Object o) {
276             if (this == o) return true;
277             if (!(o instanceof EndInfo)) return false;
278             EndInfo that = (EndInfo) o;
279             return endedByUid == that.endedByUid
280                     && endedByUsage == that.endedByUsage
281                     && status == that.status;
282         }
283 
284         @Override
hashCode()285         public int hashCode() {
286             return Objects.hash(status, endedByUid, endedByUsage);
287         }
288 
289         @Override
toString()290         public String toString() {
291             return "EndInfo{"
292                     + "status=" + status
293                     + ", endedByUid=" + endedByUid
294                     + ", endedByUsage=" + endedByUsage
295                     + '}';
296         }
297     }
298 
299     /** Debug information about vibrations. */
300     static final class DebugInfo {
301         private final long mCreateTime;
302         private final long mStartTime;
303         private final long mEndTime;
304         private final long mDurationMs;
305         private final CombinedVibration mEffect;
306         private final CombinedVibration mOriginalEffect;
307         private final float mScale;
308         private final VibrationAttributes mAttrs;
309         private final int mUid;
310         private final int mDisplayId;
311         private final String mOpPkg;
312         private final String mReason;
313         private final Status mStatus;
314 
DebugInfo(Status status, VibrationStats stats, @Nullable CombinedVibration effect, @Nullable CombinedVibration originalEffect, float scale, VibrationAttributes attrs, int uid, int displayId, String opPkg, String reason)315         DebugInfo(Status status, VibrationStats stats, @Nullable CombinedVibration effect,
316                 @Nullable CombinedVibration originalEffect, float scale, VibrationAttributes attrs,
317                 int uid, int displayId, String opPkg, String reason) {
318             mCreateTime = stats.getCreateTimeDebug();
319             mStartTime = stats.getStartTimeDebug();
320             mEndTime = stats.getEndTimeDebug();
321             mDurationMs = stats.getDurationDebug();
322             mEffect = effect;
323             mOriginalEffect = originalEffect;
324             mScale = scale;
325             mAttrs = attrs;
326             mUid = uid;
327             mDisplayId = displayId;
328             mOpPkg = opPkg;
329             mReason = reason;
330             mStatus = status;
331         }
332 
333         @Override
toString()334         public String toString() {
335             return new StringBuilder()
336                     .append("createTime: ")
337                     .append(DEBUG_DATE_FORMAT.format(new Date(mCreateTime)))
338                     .append(", startTime: ")
339                     .append(DEBUG_DATE_FORMAT.format(new Date(mStartTime)))
340                     .append(", endTime: ")
341                     .append(mEndTime == 0 ? null
342                             : DEBUG_DATE_FORMAT.format(new Date(mEndTime)))
343                     .append(", durationMs: ")
344                     .append(mDurationMs)
345                     .append(", status: ")
346                     .append(mStatus.name().toLowerCase())
347                     .append(", effect: ")
348                     .append(mEffect)
349                     .append(", originalEffect: ")
350                     .append(mOriginalEffect)
351                     .append(", scale: ")
352                     .append(String.format("%.2f", mScale))
353                     .append(", attrs: ")
354                     .append(mAttrs)
355                     .append(", uid: ")
356                     .append(mUid)
357                     .append(", displayId: ")
358                     .append(mDisplayId)
359                     .append(", opPkg: ")
360                     .append(mOpPkg)
361                     .append(", reason: ")
362                     .append(mReason)
363                     .toString();
364         }
365 
366         /** Write this info into given {@code fieldId} on {@link ProtoOutputStream}. */
dumpProto(ProtoOutputStream proto, long fieldId)367         public void dumpProto(ProtoOutputStream proto, long fieldId) {
368             final long token = proto.start(fieldId);
369             proto.write(VibrationProto.START_TIME, mStartTime);
370             proto.write(VibrationProto.END_TIME, mEndTime);
371             proto.write(VibrationProto.DURATION_MS, mDurationMs);
372             proto.write(VibrationProto.STATUS, mStatus.ordinal());
373 
374             final long attrsToken = proto.start(VibrationProto.ATTRIBUTES);
375             proto.write(VibrationAttributesProto.USAGE, mAttrs.getUsage());
376             proto.write(VibrationAttributesProto.AUDIO_USAGE, mAttrs.getAudioUsage());
377             proto.write(VibrationAttributesProto.FLAGS, mAttrs.getFlags());
378             proto.end(attrsToken);
379 
380             if (mEffect != null) {
381                 dumpEffect(proto, VibrationProto.EFFECT, mEffect);
382             }
383             if (mOriginalEffect != null) {
384                 dumpEffect(proto, VibrationProto.ORIGINAL_EFFECT, mOriginalEffect);
385             }
386 
387             proto.end(token);
388         }
389 
dumpEffect( ProtoOutputStream proto, long fieldId, CombinedVibration effect)390         private void dumpEffect(
391                 ProtoOutputStream proto, long fieldId, CombinedVibration effect) {
392             dumpEffect(proto, fieldId,
393                     (CombinedVibration.Sequential) CombinedVibration.startSequential()
394                             .addNext(effect)
395                             .combine());
396         }
397 
dumpEffect( ProtoOutputStream proto, long fieldId, CombinedVibration.Sequential effect)398         private void dumpEffect(
399                 ProtoOutputStream proto, long fieldId, CombinedVibration.Sequential effect) {
400             final long token = proto.start(fieldId);
401             for (int i = 0; i < effect.getEffects().size(); i++) {
402                 CombinedVibration nestedEffect = effect.getEffects().get(i);
403                 if (nestedEffect instanceof CombinedVibration.Mono) {
404                     dumpEffect(proto, CombinedVibrationEffectProto.EFFECTS,
405                             (CombinedVibration.Mono) nestedEffect);
406                 } else if (nestedEffect instanceof CombinedVibration.Stereo) {
407                     dumpEffect(proto, CombinedVibrationEffectProto.EFFECTS,
408                             (CombinedVibration.Stereo) nestedEffect);
409                 }
410                 proto.write(CombinedVibrationEffectProto.DELAYS, effect.getDelays().get(i));
411             }
412             proto.end(token);
413         }
414 
dumpEffect( ProtoOutputStream proto, long fieldId, CombinedVibration.Mono effect)415         private void dumpEffect(
416                 ProtoOutputStream proto, long fieldId, CombinedVibration.Mono effect) {
417             final long token = proto.start(fieldId);
418             dumpEffect(proto, SyncVibrationEffectProto.EFFECTS, effect.getEffect());
419             proto.end(token);
420         }
421 
dumpEffect( ProtoOutputStream proto, long fieldId, CombinedVibration.Stereo effect)422         private void dumpEffect(
423                 ProtoOutputStream proto, long fieldId, CombinedVibration.Stereo effect) {
424             final long token = proto.start(fieldId);
425             for (int i = 0; i < effect.getEffects().size(); i++) {
426                 proto.write(SyncVibrationEffectProto.VIBRATOR_IDS, effect.getEffects().keyAt(i));
427                 dumpEffect(proto, SyncVibrationEffectProto.EFFECTS, effect.getEffects().valueAt(i));
428             }
429             proto.end(token);
430         }
431 
dumpEffect( ProtoOutputStream proto, long fieldId, VibrationEffect effect)432         private void dumpEffect(
433                 ProtoOutputStream proto, long fieldId, VibrationEffect effect) {
434             final long token = proto.start(fieldId);
435             VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
436             for (VibrationEffectSegment segment : composed.getSegments()) {
437                 dumpEffect(proto, VibrationEffectProto.SEGMENTS, segment);
438             }
439             proto.write(VibrationEffectProto.REPEAT, composed.getRepeatIndex());
440             proto.end(token);
441         }
442 
dumpEffect(ProtoOutputStream proto, long fieldId, VibrationEffectSegment segment)443         private void dumpEffect(ProtoOutputStream proto, long fieldId,
444                 VibrationEffectSegment segment) {
445             final long token = proto.start(fieldId);
446             if (segment instanceof StepSegment) {
447                 dumpEffect(proto, SegmentProto.STEP, (StepSegment) segment);
448             } else if (segment instanceof RampSegment) {
449                 dumpEffect(proto, SegmentProto.RAMP, (RampSegment) segment);
450             } else if (segment instanceof PrebakedSegment) {
451                 dumpEffect(proto, SegmentProto.PREBAKED, (PrebakedSegment) segment);
452             } else if (segment instanceof PrimitiveSegment) {
453                 dumpEffect(proto, SegmentProto.PRIMITIVE, (PrimitiveSegment) segment);
454             }
455             proto.end(token);
456         }
457 
dumpEffect(ProtoOutputStream proto, long fieldId, StepSegment segment)458         private void dumpEffect(ProtoOutputStream proto, long fieldId, StepSegment segment) {
459             final long token = proto.start(fieldId);
460             proto.write(StepSegmentProto.DURATION, segment.getDuration());
461             proto.write(StepSegmentProto.AMPLITUDE, segment.getAmplitude());
462             proto.write(StepSegmentProto.FREQUENCY, segment.getFrequencyHz());
463             proto.end(token);
464         }
465 
dumpEffect(ProtoOutputStream proto, long fieldId, RampSegment segment)466         private void dumpEffect(ProtoOutputStream proto, long fieldId, RampSegment segment) {
467             final long token = proto.start(fieldId);
468             proto.write(RampSegmentProto.DURATION, segment.getDuration());
469             proto.write(RampSegmentProto.START_AMPLITUDE, segment.getStartAmplitude());
470             proto.write(RampSegmentProto.END_AMPLITUDE, segment.getEndAmplitude());
471             proto.write(RampSegmentProto.START_FREQUENCY, segment.getStartFrequencyHz());
472             proto.write(RampSegmentProto.END_FREQUENCY, segment.getEndFrequencyHz());
473             proto.end(token);
474         }
475 
dumpEffect(ProtoOutputStream proto, long fieldId, PrebakedSegment segment)476         private void dumpEffect(ProtoOutputStream proto, long fieldId,
477                 PrebakedSegment segment) {
478             final long token = proto.start(fieldId);
479             proto.write(PrebakedSegmentProto.EFFECT_ID, segment.getEffectId());
480             proto.write(PrebakedSegmentProto.EFFECT_STRENGTH, segment.getEffectStrength());
481             proto.write(PrebakedSegmentProto.FALLBACK, segment.shouldFallback());
482             proto.end(token);
483         }
484 
dumpEffect(ProtoOutputStream proto, long fieldId, PrimitiveSegment segment)485         private void dumpEffect(ProtoOutputStream proto, long fieldId,
486                 PrimitiveSegment segment) {
487             final long token = proto.start(fieldId);
488             proto.write(PrimitiveSegmentProto.PRIMITIVE_ID, segment.getPrimitiveId());
489             proto.write(PrimitiveSegmentProto.SCALE, segment.getScale());
490             proto.write(PrimitiveSegmentProto.DELAY, segment.getDelay());
491             proto.end(token);
492         }
493     }
494 
495 }
496