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.media.AudioAttributes;
22 import android.os.CombinedVibration;
23 import android.os.IBinder;
24 import android.os.VibrationAttributes;
25 import android.os.VibrationEffect;
26 import android.os.vibrator.PrebakedSegment;
27 import android.os.vibrator.PrimitiveSegment;
28 import android.os.vibrator.RampSegment;
29 import android.os.vibrator.StepSegment;
30 import android.os.vibrator.VibrationEffectSegment;
31 import android.util.IndentingPrintWriter;
32 import android.util.proto.ProtoOutputStream;
33 
34 import java.io.PrintWriter;
35 import java.time.Instant;
36 import java.time.ZoneId;
37 import java.time.format.DateTimeFormatter;
38 import java.util.Locale;
39 import java.util.Objects;
40 import java.util.concurrent.atomic.AtomicInteger;
41 
42 /**
43  * The base class for all vibrations.
44  */
45 abstract class Vibration {
46     private static final DateTimeFormatter DEBUG_TIME_FORMATTER = DateTimeFormatter.ofPattern(
47             "HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
48     private static final DateTimeFormatter DEBUG_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(
49             "MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
50 
51     // Used to generate globally unique vibration ids.
52     private static final AtomicInteger sNextVibrationId = new AtomicInteger(1); // 0 = no callback
53 
54     public final long id;
55     public final CallerInfo callerInfo;
56     public final VibrationStats stats = new VibrationStats();
57     public final IBinder callerToken;
58 
59     /** Vibration status with reference to values from vibratormanagerservice.proto for logging. */
60     enum Status {
61         UNKNOWN(VibrationProto.UNKNOWN),
62         RUNNING(VibrationProto.RUNNING),
63         FINISHED(VibrationProto.FINISHED),
64         FINISHED_UNEXPECTED(VibrationProto.FINISHED_UNEXPECTED),
65         FORWARDED_TO_INPUT_DEVICES(VibrationProto.FORWARDED_TO_INPUT_DEVICES),
66         CANCELLED_BINDER_DIED(VibrationProto.CANCELLED_BINDER_DIED),
67         CANCELLED_BY_SCREEN_OFF(VibrationProto.CANCELLED_BY_SCREEN_OFF),
68         CANCELLED_BY_SETTINGS_UPDATE(VibrationProto.CANCELLED_BY_SETTINGS_UPDATE),
69         CANCELLED_BY_USER(VibrationProto.CANCELLED_BY_USER),
70         CANCELLED_BY_UNKNOWN_REASON(VibrationProto.CANCELLED_BY_UNKNOWN_REASON),
71         CANCELLED_SUPERSEDED(VibrationProto.CANCELLED_SUPERSEDED),
72         IGNORED_ERROR_APP_OPS(VibrationProto.IGNORED_ERROR_APP_OPS),
73         IGNORED_ERROR_CANCELLING(VibrationProto.IGNORED_ERROR_CANCELLING),
74         IGNORED_ERROR_SCHEDULING(VibrationProto.IGNORED_ERROR_SCHEDULING),
75         IGNORED_ERROR_TOKEN(VibrationProto.IGNORED_ERROR_TOKEN),
76         IGNORED_APP_OPS(VibrationProto.IGNORED_APP_OPS),
77         IGNORED_BACKGROUND(VibrationProto.IGNORED_BACKGROUND),
78         IGNORED_MISSING_PERMISSION(VibrationProto.IGNORED_MISSING_PERMISSION),
79         IGNORED_UNSUPPORTED(VibrationProto.IGNORED_UNSUPPORTED),
80         IGNORED_FOR_EXTERNAL(VibrationProto.IGNORED_FOR_EXTERNAL),
81         IGNORED_FOR_HIGHER_IMPORTANCE(VibrationProto.IGNORED_FOR_HIGHER_IMPORTANCE),
82         IGNORED_FOR_ONGOING(VibrationProto.IGNORED_FOR_ONGOING),
83         IGNORED_FOR_POWER(VibrationProto.IGNORED_FOR_POWER),
84         IGNORED_FOR_RINGER_MODE(VibrationProto.IGNORED_FOR_RINGER_MODE),
85         IGNORED_FOR_SETTINGS(VibrationProto.IGNORED_FOR_SETTINGS),
86         IGNORED_SUPERSEDED(VibrationProto.IGNORED_SUPERSEDED),
87         IGNORED_FROM_VIRTUAL_DEVICE(VibrationProto.IGNORED_FROM_VIRTUAL_DEVICE),
88         IGNORED_ON_WIRELESS_CHARGER(VibrationProto.IGNORED_ON_WIRELESS_CHARGER);
89 
90         private final int mProtoEnumValue;
91 
Status(int value)92         Status(int value) {
93             mProtoEnumValue = value;
94         }
95 
getProtoEnumValue()96         public int getProtoEnumValue() {
97             return mProtoEnumValue;
98         }
99     }
100 
Vibration(@onNull IBinder token, @NonNull CallerInfo callerInfo)101     Vibration(@NonNull IBinder token, @NonNull CallerInfo callerInfo) {
102         Objects.requireNonNull(token);
103         Objects.requireNonNull(callerInfo);
104         this.id = sNextVibrationId.getAndIncrement();
105         this.callerToken = token;
106         this.callerInfo = callerInfo;
107     }
108 
109     /** Return true if vibration is a repeating vibration. */
isRepeating()110     abstract boolean isRepeating();
111 
112     /**
113      * Holds lightweight immutable info on the process that triggered the vibration. This data
114      * could potentially be kept in memory for a long time for bugreport dumpsys operations.
115      *
116      * Since CallerInfo can be kept in memory for a long time, it shouldn't hold any references to
117      * potentially expensive or resource-linked objects, such as {@link IBinder}.
118      */
119     static final class CallerInfo {
120         public final VibrationAttributes attrs;
121         public final int uid;
122         public final int deviceId;
123         public final String opPkg;
124         public final String reason;
125 
CallerInfo(@onNull VibrationAttributes attrs, int uid, int deviceId, String opPkg, String reason)126         CallerInfo(@NonNull VibrationAttributes attrs, int uid, int deviceId, String opPkg,
127                 String reason) {
128             Objects.requireNonNull(attrs);
129             this.attrs = attrs;
130             this.uid = uid;
131             this.deviceId = deviceId;
132             this.opPkg = opPkg;
133             this.reason = reason;
134         }
135 
136         @Override
equals(Object o)137         public boolean equals(Object o) {
138             if (this == o) return true;
139             if (!(o instanceof CallerInfo)) return false;
140             CallerInfo that = (CallerInfo) o;
141             return Objects.equals(attrs, that.attrs)
142                     && uid == that.uid
143                     && deviceId == that.deviceId
144                     && Objects.equals(opPkg, that.opPkg)
145                     && Objects.equals(reason, that.reason);
146         }
147 
148         @Override
hashCode()149         public int hashCode() {
150             return Objects.hash(attrs, uid, deviceId, opPkg, reason);
151         }
152 
153         @Override
toString()154         public String toString() {
155             return "CallerInfo{"
156                     + " uid=" + uid
157                     + ", opPkg=" + opPkg
158                     + ", deviceId=" + deviceId
159                     + ", attrs=" + attrs
160                     + ", reason=" + reason
161                     + '}';
162         }
163     }
164 
165     /** Immutable info passed as a signal to end a vibration. */
166     static final class EndInfo {
167         /** The {@link Status} to be set to the vibration when it ends with this info. */
168         @NonNull
169         public final Status status;
170         /** Info about the process that ended the vibration. */
171         public final CallerInfo endedBy;
172 
EndInfo(@onNull Vibration.Status status)173         EndInfo(@NonNull Vibration.Status status) {
174             this(status, null);
175         }
176 
EndInfo(@onNull Vibration.Status status, @Nullable CallerInfo endedBy)177         EndInfo(@NonNull Vibration.Status status, @Nullable CallerInfo endedBy) {
178             this.status = status;
179             this.endedBy = endedBy;
180         }
181 
182         @Override
equals(Object o)183         public boolean equals(Object o) {
184             if (this == o) return true;
185             if (!(o instanceof EndInfo)) return false;
186             EndInfo that = (EndInfo) o;
187             return Objects.equals(endedBy, that.endedBy)
188                     && status == that.status;
189         }
190 
191         @Override
hashCode()192         public int hashCode() {
193             return Objects.hash(status, endedBy);
194         }
195 
196         @Override
toString()197         public String toString() {
198             return "EndInfo{"
199                     + "status=" + status
200                     + ", endedBy=" + endedBy
201                     + '}';
202         }
203     }
204 
205     /**
206      * Holds lightweight debug information about the vibration that could potentially be kept in
207      * memory for a long time for bugreport dumpsys operations.
208      *
209      * Since DebugInfo can be kept in memory for a long time, it shouldn't hold any references to
210      * potentially expensive or resource-linked objects, such as {@link IBinder}.
211      */
212     static final class DebugInfo {
213         final Status mStatus;
214         final long mCreateTime;
215         final CallerInfo mCallerInfo;
216         @Nullable
217         final CombinedVibration mPlayedEffect;
218 
219         private final long mStartTime;
220         private final long mEndTime;
221         private final long mDurationMs;
222         @Nullable
223         private final CombinedVibration mOriginalEffect;
224         private final int mScaleLevel;
225         private final float mAdaptiveScale;
226 
DebugInfo(Status status, VibrationStats stats, @Nullable CombinedVibration playedEffect, @Nullable CombinedVibration originalEffect, int scaleLevel, float adaptiveScale, @NonNull CallerInfo callerInfo)227         DebugInfo(Status status, VibrationStats stats, @Nullable CombinedVibration playedEffect,
228                 @Nullable CombinedVibration originalEffect, int scaleLevel,
229                 float adaptiveScale, @NonNull CallerInfo callerInfo) {
230             Objects.requireNonNull(callerInfo);
231             mCreateTime = stats.getCreateTimeDebug();
232             mStartTime = stats.getStartTimeDebug();
233             mEndTime = stats.getEndTimeDebug();
234             mDurationMs = stats.getDurationDebug();
235             mPlayedEffect = playedEffect;
236             mOriginalEffect = originalEffect;
237             mScaleLevel = scaleLevel;
238             mAdaptiveScale = adaptiveScale;
239             mCallerInfo = callerInfo;
240             mStatus = status;
241         }
242 
243         @Override
toString()244         public String toString() {
245             return "createTime: " + DEBUG_DATE_TIME_FORMATTER.format(
246                     Instant.ofEpochMilli(mCreateTime))
247                     + ", startTime: " + DEBUG_DATE_TIME_FORMATTER.format(
248                     Instant.ofEpochMilli(mStartTime))
249                     + ", endTime: " + (mEndTime == 0 ? null : DEBUG_DATE_TIME_FORMATTER.format(
250                     Instant.ofEpochMilli(mEndTime)))
251                     + ", durationMs: " + mDurationMs
252                     + ", status: " + mStatus.name().toLowerCase(Locale.ROOT)
253                     + ", playedEffect: " + mPlayedEffect
254                     + ", originalEffect: " + mOriginalEffect
255                     + ", scaleLevel: " + VibrationScaler.scaleLevelToString(mScaleLevel)
256                     + ", adaptiveScale: " + String.format(Locale.ROOT, "%.2f", mAdaptiveScale)
257                     + ", callerInfo: " + mCallerInfo;
258         }
259 
logMetrics(VibratorFrameworkStatsLogger statsLogger)260         void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
261             statsLogger.logVibrationAdaptiveHapticScale(mCallerInfo.uid, mAdaptiveScale);
262         }
263 
264         /**
265          * Write this info in a compact way into given {@link PrintWriter}.
266          *
267          * <p>This is used by dumpsys to log multiple vibration records in single lines that are
268          * easy to skim through by the sorted created time.
269          */
dumpCompact(IndentingPrintWriter pw)270         void dumpCompact(IndentingPrintWriter pw) {
271             boolean isExternalVibration = mPlayedEffect == null;
272             String timingsStr = String.format(Locale.ROOT,
273                     "%s | %8s | %20s | duration: %5dms | start: %12s | end: %12s",
274                     DEBUG_DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(mCreateTime)),
275                     isExternalVibration ? "external" : "effect",
276                     mStatus.name().toLowerCase(Locale.ROOT),
277                     mDurationMs,
278                     mStartTime == 0 ? ""
279                             : DEBUG_TIME_FORMATTER.format(Instant.ofEpochMilli(mStartTime)),
280                     mEndTime == 0 ? ""
281                             : DEBUG_TIME_FORMATTER.format(Instant.ofEpochMilli(mEndTime)));
282             String paramStr = String.format(Locale.ROOT,
283                     " | scale: %8s (adaptive=%.2f) | flags: %4s | usage: %s",
284                     VibrationScaler.scaleLevelToString(mScaleLevel), mAdaptiveScale,
285                     Long.toBinaryString(mCallerInfo.attrs.getFlags()),
286                     mCallerInfo.attrs.usageToString());
287             // Optional, most vibrations have category unknown so skip them to simplify the logs
288             String categoryStr =
289                     mCallerInfo.attrs.getCategory() != VibrationAttributes.CATEGORY_UNKNOWN
290                             ? " | category=" + VibrationAttributes.categoryToString(
291                             mCallerInfo.attrs.getCategory())
292                             : "";
293             // Optional, most vibrations should not be defined via AudioAttributes
294             // so skip them to simplify the logs
295             String audioUsageStr =
296                     mCallerInfo.attrs.getOriginalAudioUsage() != AudioAttributes.USAGE_UNKNOWN
297                             ? " | audioUsage=" + AudioAttributes.usageToString(
298                             mCallerInfo.attrs.getOriginalAudioUsage())
299                             : "";
300             String callerStr = String.format(Locale.ROOT,
301                     " | %s (uid=%d, deviceId=%d) | reason: %s",
302                     mCallerInfo.opPkg, mCallerInfo.uid, mCallerInfo.deviceId, mCallerInfo.reason);
303             String effectStr = String.format(Locale.ROOT,
304                     " | played: %s | original: %s",
305                     mPlayedEffect == null ? null : mPlayedEffect.toDebugString(),
306                     mOriginalEffect == null ? null : mOriginalEffect.toDebugString());
307             pw.println(timingsStr + paramStr + categoryStr + audioUsageStr + callerStr + effectStr);
308         }
309 
310         /** Write this info into given {@link PrintWriter}. */
dump(IndentingPrintWriter pw)311         void dump(IndentingPrintWriter pw) {
312             pw.println("Vibration:");
313             pw.increaseIndent();
314             pw.println("status = " + mStatus.name().toLowerCase(Locale.ROOT));
315             pw.println("durationMs = " + mDurationMs);
316             pw.println("createTime = " + DEBUG_DATE_TIME_FORMATTER.format(
317                     Instant.ofEpochMilli(mCreateTime)));
318             pw.println("startTime = " + DEBUG_DATE_TIME_FORMATTER.format(
319                     Instant.ofEpochMilli(mStartTime)));
320             pw.println("endTime = " + (mEndTime == 0 ? null
321                     : DEBUG_DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(mEndTime))));
322             pw.println("playedEffect = " + mPlayedEffect);
323             pw.println("originalEffect = " + mOriginalEffect);
324             pw.println("scale = " + VibrationScaler.scaleLevelToString(mScaleLevel));
325             pw.println("adaptiveScale = " + String.format(Locale.ROOT, "%.2f", mAdaptiveScale));
326             pw.println("callerInfo = " + mCallerInfo);
327             pw.decreaseIndent();
328         }
329 
330         /** Write this info into given {@code fieldId} on {@link ProtoOutputStream}. */
dump(ProtoOutputStream proto, long fieldId)331         void dump(ProtoOutputStream proto, long fieldId) {
332             final long token = proto.start(fieldId);
333             proto.write(VibrationProto.START_TIME, mStartTime);
334             proto.write(VibrationProto.END_TIME, mEndTime);
335             proto.write(VibrationProto.DURATION_MS, mDurationMs);
336             proto.write(VibrationProto.STATUS, mStatus.ordinal());
337 
338             final long attrsToken = proto.start(VibrationProto.ATTRIBUTES);
339             final VibrationAttributes attrs = mCallerInfo.attrs;
340             proto.write(VibrationAttributesProto.USAGE, attrs.getUsage());
341             proto.write(VibrationAttributesProto.AUDIO_USAGE, attrs.getAudioUsage());
342             proto.write(VibrationAttributesProto.CATEGORY, attrs.getCategory());
343             proto.write(VibrationAttributesProto.FLAGS, attrs.getFlags());
344             proto.end(attrsToken);
345 
346             if (mPlayedEffect != null) {
347                 dumpEffect(proto, VibrationProto.PLAYED_EFFECT, mPlayedEffect);
348             }
349             if (mOriginalEffect != null) {
350                 dumpEffect(proto, VibrationProto.ORIGINAL_EFFECT, mOriginalEffect);
351             }
352 
353             proto.end(token);
354         }
355 
dumpEffect( ProtoOutputStream proto, long fieldId, CombinedVibration effect)356         private void dumpEffect(
357                 ProtoOutputStream proto, long fieldId, CombinedVibration effect) {
358             dumpEffect(proto, fieldId,
359                     (CombinedVibration.Sequential) CombinedVibration.startSequential()
360                             .addNext(effect)
361                             .combine());
362         }
363 
dumpEffect( ProtoOutputStream proto, long fieldId, CombinedVibration.Sequential effect)364         private void dumpEffect(
365                 ProtoOutputStream proto, long fieldId, CombinedVibration.Sequential effect) {
366             final long token = proto.start(fieldId);
367             for (int i = 0; i < effect.getEffects().size(); i++) {
368                 CombinedVibration nestedEffect = effect.getEffects().get(i);
369                 if (nestedEffect instanceof CombinedVibration.Mono) {
370                     dumpEffect(proto, CombinedVibrationEffectProto.EFFECTS,
371                             (CombinedVibration.Mono) nestedEffect);
372                 } else if (nestedEffect instanceof CombinedVibration.Stereo) {
373                     dumpEffect(proto, CombinedVibrationEffectProto.EFFECTS,
374                             (CombinedVibration.Stereo) nestedEffect);
375                 }
376                 proto.write(CombinedVibrationEffectProto.DELAYS, effect.getDelays().get(i));
377             }
378             proto.end(token);
379         }
380 
dumpEffect( ProtoOutputStream proto, long fieldId, CombinedVibration.Mono effect)381         private void dumpEffect(
382                 ProtoOutputStream proto, long fieldId, CombinedVibration.Mono effect) {
383             final long token = proto.start(fieldId);
384             dumpEffect(proto, SyncVibrationEffectProto.EFFECTS, effect.getEffect());
385             proto.end(token);
386         }
387 
dumpEffect( ProtoOutputStream proto, long fieldId, CombinedVibration.Stereo effect)388         private void dumpEffect(
389                 ProtoOutputStream proto, long fieldId, CombinedVibration.Stereo effect) {
390             final long token = proto.start(fieldId);
391             for (int i = 0; i < effect.getEffects().size(); i++) {
392                 proto.write(SyncVibrationEffectProto.VIBRATOR_IDS, effect.getEffects().keyAt(i));
393                 dumpEffect(proto, SyncVibrationEffectProto.EFFECTS, effect.getEffects().valueAt(i));
394             }
395             proto.end(token);
396         }
397 
dumpEffect( ProtoOutputStream proto, long fieldId, VibrationEffect effect)398         private void dumpEffect(
399                 ProtoOutputStream proto, long fieldId, VibrationEffect effect) {
400             final long token = proto.start(fieldId);
401             VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
402             for (VibrationEffectSegment segment : composed.getSegments()) {
403                 dumpEffect(proto, VibrationEffectProto.SEGMENTS, segment);
404             }
405             proto.write(VibrationEffectProto.REPEAT, composed.getRepeatIndex());
406             proto.end(token);
407         }
408 
dumpEffect(ProtoOutputStream proto, long fieldId, VibrationEffectSegment segment)409         private void dumpEffect(ProtoOutputStream proto, long fieldId,
410                 VibrationEffectSegment segment) {
411             final long token = proto.start(fieldId);
412             if (segment instanceof StepSegment) {
413                 dumpEffect(proto, SegmentProto.STEP, (StepSegment) segment);
414             } else if (segment instanceof RampSegment) {
415                 dumpEffect(proto, SegmentProto.RAMP, (RampSegment) segment);
416             } else if (segment instanceof PrebakedSegment) {
417                 dumpEffect(proto, SegmentProto.PREBAKED, (PrebakedSegment) segment);
418             } else if (segment instanceof PrimitiveSegment) {
419                 dumpEffect(proto, SegmentProto.PRIMITIVE, (PrimitiveSegment) segment);
420             }
421             proto.end(token);
422         }
423 
dumpEffect(ProtoOutputStream proto, long fieldId, StepSegment segment)424         private void dumpEffect(ProtoOutputStream proto, long fieldId, StepSegment segment) {
425             final long token = proto.start(fieldId);
426             proto.write(StepSegmentProto.DURATION, segment.getDuration());
427             proto.write(StepSegmentProto.AMPLITUDE, segment.getAmplitude());
428             proto.write(StepSegmentProto.FREQUENCY, segment.getFrequencyHz());
429             proto.end(token);
430         }
431 
dumpEffect(ProtoOutputStream proto, long fieldId, RampSegment segment)432         private void dumpEffect(ProtoOutputStream proto, long fieldId, RampSegment segment) {
433             final long token = proto.start(fieldId);
434             proto.write(RampSegmentProto.DURATION, segment.getDuration());
435             proto.write(RampSegmentProto.START_AMPLITUDE, segment.getStartAmplitude());
436             proto.write(RampSegmentProto.END_AMPLITUDE, segment.getEndAmplitude());
437             proto.write(RampSegmentProto.START_FREQUENCY, segment.getStartFrequencyHz());
438             proto.write(RampSegmentProto.END_FREQUENCY, segment.getEndFrequencyHz());
439             proto.end(token);
440         }
441 
dumpEffect(ProtoOutputStream proto, long fieldId, PrebakedSegment segment)442         private void dumpEffect(ProtoOutputStream proto, long fieldId,
443                 PrebakedSegment segment) {
444             final long token = proto.start(fieldId);
445             proto.write(PrebakedSegmentProto.EFFECT_ID, segment.getEffectId());
446             proto.write(PrebakedSegmentProto.EFFECT_STRENGTH, segment.getEffectStrength());
447             proto.write(PrebakedSegmentProto.FALLBACK, segment.shouldFallback());
448             proto.end(token);
449         }
450 
dumpEffect(ProtoOutputStream proto, long fieldId, PrimitiveSegment segment)451         private void dumpEffect(ProtoOutputStream proto, long fieldId,
452                 PrimitiveSegment segment) {
453             final long token = proto.start(fieldId);
454             proto.write(PrimitiveSegmentProto.PRIMITIVE_ID, segment.getPrimitiveId());
455             proto.write(PrimitiveSegmentProto.SCALE, segment.getScale());
456             proto.write(PrimitiveSegmentProto.DELAY, segment.getDelay());
457             proto.end(token);
458         }
459     }
460 }
461