1 /*
2  * Copyright (C) 2022 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.SystemClock;
22 import android.os.vibrator.PrebakedSegment;
23 import android.os.vibrator.PrimitiveSegment;
24 import android.os.vibrator.RampSegment;
25 import android.util.Slog;
26 import android.util.SparseBooleanArray;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.internal.util.FrameworkStatsLog;
30 
31 /** Holds basic stats about the vibration playback and interaction with the vibrator HAL. */
32 final class VibrationStats {
33     static final String TAG = "VibrationStats";
34 
35     // Milestone timestamps, using SystemClock.uptimeMillis(), for calculations.
36     // - Create: time a vibration object was created, which is closer to when the service receives a
37     //           vibrate request.
38     // - Start: time a vibration started to play, which is closer to the time that the
39     //          VibrationEffect started playing the very first segment.
40     // - End: time a vibration ended, even if it never started to play. This can be as soon as the
41     //        vibrator HAL reports it has finished the last command, or before it has even started
42     //        when the vibration is ignored or cancelled.
43     // Create and end times set by VibratorManagerService only, guarded by its lock.
44     // Start times set by VibrationThread only (single-threaded).
45     private long mCreateUptimeMillis;
46     private long mStartUptimeMillis;
47     private long mEndUptimeMillis;
48 
49     // Milestone timestamps, using unix epoch time, only to be used for debugging purposes and
50     // to correlate with other system events. Any duration calculations should be done with the
51     // {create/start/end}UptimeMillis counterparts so as not to be affected by discontinuities
52     // created by RTC adjustments.
53     // Set together with the *UptimeMillis counterparts.
54     private long mCreateTimeDebug;
55     private long mStartTimeDebug;
56     private long mEndTimeDebug;
57 
58     // Vibration interruption tracking.
59     // Set by VibratorManagerService only, guarded by its lock.
60     private int mEndedByUid;
61     private int mEndedByUsage;
62     private int mInterruptedUsage;
63 
64     // All following counters are set by VibrationThread only (single-threaded):
65     // Counts how many times the VibrationEffect was repeated.
66     private int mRepeatCount;
67     // Total duration, in milliseconds, the vibrator was active with non-zero amplitude.
68     private int mVibratorOnTotalDurationMillis;
69     // Total number of primitives used in compositions.
70     private int mVibrationCompositionTotalSize;
71     private int mVibrationPwleTotalSize;
72     // Counts how many times each IVibrator method was triggered by this vibration.
73     private int mVibratorOnCount;
74     private int mVibratorOffCount;
75     private int mVibratorSetAmplitudeCount;
76     private int mVibratorSetExternalControlCount;
77     private int mVibratorPerformCount;
78     private int mVibratorComposeCount;
79     private int mVibratorComposePwleCount;
80 
81     // Ids of vibration effects and primitives used by this vibration, with support flag.
82     // Set by VibrationThread only (single-threaded).
83     private SparseBooleanArray mVibratorEffectsUsed = new SparseBooleanArray();
84     private SparseBooleanArray mVibratorPrimitivesUsed = new SparseBooleanArray();
85 
VibrationStats()86     VibrationStats() {
87         mCreateUptimeMillis = SystemClock.uptimeMillis();
88         mCreateTimeDebug = System.currentTimeMillis();
89         // Set invalid UID and VibrationAttributes.USAGE values to indicate fields are unset.
90         mEndedByUid = -1;
91         mEndedByUsage = -1;
92         mInterruptedUsage = -1;
93     }
94 
getCreateUptimeMillis()95     long getCreateUptimeMillis() {
96         return mCreateUptimeMillis;
97     }
98 
getStartUptimeMillis()99     long getStartUptimeMillis() {
100         return mStartUptimeMillis;
101     }
102 
getEndUptimeMillis()103     long getEndUptimeMillis() {
104         return mEndUptimeMillis;
105     }
106 
getCreateTimeDebug()107     long getCreateTimeDebug() {
108         return mCreateTimeDebug;
109     }
110 
getStartTimeDebug()111     long getStartTimeDebug() {
112         return mStartTimeDebug;
113     }
114 
getEndTimeDebug()115     long getEndTimeDebug() {
116         return mEndTimeDebug;
117     }
118 
119     /**
120      * Duration calculated for debugging purposes, between the creation of a vibration and the
121      * end time being reported, or -1 if the vibration has not ended.
122      */
getDurationDebug()123     long getDurationDebug() {
124         return hasEnded() ? (mEndUptimeMillis - mCreateUptimeMillis) : -1;
125     }
126 
127     /** Return true if vibration reported it has ended. */
hasEnded()128     boolean hasEnded() {
129         return mEndUptimeMillis > 0;
130     }
131 
132     /** Return true if vibration reported it has started triggering the vibrator. */
hasStarted()133     boolean hasStarted() {
134         return mStartUptimeMillis > 0;
135     }
136 
137     /**
138      * Set the current system time as this vibration start time, for debugging purposes.
139      *
140      * <p>This indicates the vibration has started to interact with the vibrator HAL and the
141      * device may start vibrating after this point.
142      *
143      * <p>This method will only accept given value if the start timestamp was never set.
144      */
reportStarted()145     void reportStarted() {
146         if (hasEnded() || (mStartUptimeMillis != 0)) {
147             // Vibration already started or ended, keep first time set and ignore this one.
148             return;
149         }
150         mStartUptimeMillis = SystemClock.uptimeMillis();
151         mStartTimeDebug = System.currentTimeMillis();
152     }
153 
154     /**
155      * Set status and end cause for this vibration to end, and the current system time as this
156      * vibration end time, for debugging purposes.
157      *
158      * <p>This might be triggered before {@link #reportStarted()}, which indicates this
159      * vibration was cancelled or ignored before it started triggering the vibrator.
160      *
161      * @return true if the status was accepted. This method will only accept given values if
162      * the end timestamp was never set.
163      */
reportEnded(@ullable Vibration.CallerInfo endedBy)164     boolean reportEnded(@Nullable Vibration.CallerInfo endedBy) {
165         if (hasEnded()) {
166             // Vibration already ended, keep first ending stats set and ignore this one.
167             return false;
168         }
169         if (endedBy != null) {
170             mEndedByUid = endedBy.uid;
171             mEndedByUsage = endedBy.attrs.getUsage();
172         }
173         mEndUptimeMillis = SystemClock.uptimeMillis();
174         mEndTimeDebug = System.currentTimeMillis();
175 
176         return true;
177     }
178 
179     /**
180      * Report this vibration has interrupted another vibration.
181      *
182      * <p>This method will only accept the first value as the one that was interrupted by this
183      * vibration, and will ignore all successive calls.
184      */
reportInterruptedAnotherVibration(@onNull Vibration.CallerInfo callerInfo)185     void reportInterruptedAnotherVibration(@NonNull Vibration.CallerInfo callerInfo) {
186         if (mInterruptedUsage < 0) {
187             mInterruptedUsage = callerInfo.attrs.getUsage();
188         }
189     }
190 
191     /** Report the vibration has looped a few more times. */
reportRepetition(int loops)192     void reportRepetition(int loops) {
193         mRepeatCount += loops;
194     }
195 
196     /** Report a call to vibrator method to turn on for given duration. */
reportVibratorOn(long halResult)197     void reportVibratorOn(long halResult) {
198         mVibratorOnCount++;
199 
200         if (halResult > 0) {
201             // If HAL result is positive then it represents the actual duration it will be ON.
202             mVibratorOnTotalDurationMillis += (int) halResult;
203         }
204     }
205 
206     /** Report a call to vibrator method to turn off. */
reportVibratorOff()207     void reportVibratorOff() {
208         mVibratorOffCount++;
209     }
210 
211     /** Report a call to vibrator method to change the vibration amplitude. */
reportSetAmplitude()212     void reportSetAmplitude() {
213         mVibratorSetAmplitudeCount++;
214     }
215 
216     /** Report a call to vibrator method to trigger a vibration effect. */
reportPerformEffect(long halResult, PrebakedSegment prebaked)217     void reportPerformEffect(long halResult, PrebakedSegment prebaked) {
218         mVibratorPerformCount++;
219 
220         if (halResult > 0) {
221             // If HAL result is positive then it represents the actual duration of the vibration.
222             mVibratorEffectsUsed.put(prebaked.getEffectId(), true);
223             mVibratorOnTotalDurationMillis += (int) halResult;
224         } else {
225             // Effect unsupported or request failed.
226             mVibratorEffectsUsed.put(prebaked.getEffectId(), false);
227         }
228     }
229 
230     /** Report a call to vibrator method to trigger a vibration as a composition of primitives. */
reportComposePrimitives(long halResult, PrimitiveSegment[] primitives)231     void reportComposePrimitives(long halResult, PrimitiveSegment[] primitives) {
232         mVibratorComposeCount++;
233         mVibrationCompositionTotalSize += primitives.length;
234 
235         if (halResult > 0) {
236             // If HAL result is positive then it represents the actual duration of the vibration.
237             // Remove the requested delays to update the total time the vibrator was ON.
238             for (PrimitiveSegment primitive : primitives) {
239                 halResult -= primitive.getDelay();
240                 mVibratorPrimitivesUsed.put(primitive.getPrimitiveId(), true);
241             }
242             if (halResult > 0) {
243                 mVibratorOnTotalDurationMillis += (int) halResult;
244             }
245         } else {
246             // One or more primitives were unsupported, or request failed.
247             for (PrimitiveSegment primitive : primitives) {
248                 mVibratorPrimitivesUsed.put(primitive.getPrimitiveId(), false);
249             }
250         }
251     }
252 
253     /** Report a call to vibrator method to trigger a vibration as a PWLE. */
reportComposePwle(long halResult, RampSegment[] segments)254     void reportComposePwle(long halResult, RampSegment[] segments) {
255         mVibratorComposePwleCount++;
256         mVibrationPwleTotalSize += segments.length;
257 
258         if (halResult > 0) {
259             // If HAL result is positive then it represents the actual duration of the vibration.
260             // Remove the zero-amplitude segments to update the total time the vibrator was ON.
261             for (RampSegment ramp : segments) {
262                 if ((ramp.getStartAmplitude() == 0) && (ramp.getEndAmplitude() == 0)) {
263                     halResult -= ramp.getDuration();
264                 }
265             }
266             if (halResult > 0) {
267                 mVibratorOnTotalDurationMillis += (int) halResult;
268             }
269         }
270     }
271 
272     /**
273      * Increment the stats for total number of times the {@code setExternalControl} method was
274      * triggered in the vibrator HAL.
275      */
reportSetExternalControl()276     void reportSetExternalControl() {
277         mVibratorSetExternalControlCount++;
278     }
279 
280     /**
281      * Immutable metrics about this vibration, to be kept in memory until it can be pushed through
282      * {@link com.android.internal.util.FrameworkStatsLog} as a
283      * {@link com.android.internal.util.FrameworkStatsLog#VIBRATION_REPORTED}.
284      */
285     static final class StatsInfo {
286         public final int uid;
287         public final int vibrationType;
288         public final int usage;
289         public final int status;
290         public final boolean endedBySameUid;
291         public final int endedByUsage;
292         public final int interruptedUsage;
293         public final int repeatCount;
294         public final int totalDurationMillis;
295         public final int vibratorOnMillis;
296         public final int startLatencyMillis;
297         public final int endLatencyMillis;
298         public final int halComposeCount;
299         public final int halComposePwleCount;
300         public final int halOnCount;
301         public final int halOffCount;
302         public final int halPerformCount;
303         public final int halSetAmplitudeCount;
304         public final int halSetExternalControlCount;
305         public final int halCompositionSize;
306         public final int halPwleSize;
307         public final int[] halSupportedCompositionPrimitivesUsed;
308         public final int[] halSupportedEffectsUsed;
309         public final int[] halUnsupportedCompositionPrimitivesUsed;
310         public final int[] halUnsupportedEffectsUsed;
311         private boolean mIsWritten;
312 
StatsInfo(int uid, int vibrationType, int usage, Vibration.Status status, VibrationStats stats, long completionUptimeMillis)313         StatsInfo(int uid, int vibrationType, int usage, Vibration.Status status,
314                 VibrationStats stats, long completionUptimeMillis) {
315             this.uid = uid;
316             this.vibrationType = vibrationType;
317             this.usage = usage;
318             this.status = status.getProtoEnumValue();
319             endedBySameUid = (uid == stats.mEndedByUid);
320             endedByUsage = stats.mEndedByUsage;
321             interruptedUsage = stats.mInterruptedUsage;
322             repeatCount = stats.mRepeatCount;
323 
324             // This duration goes from the time this object was created until the time it was
325             // completed. We can use latencies to detect the times between first and last
326             // interaction with vibrator.
327             totalDurationMillis =
328                     (int) Math.max(0,  completionUptimeMillis - stats.mCreateUptimeMillis);
329             vibratorOnMillis = stats.mVibratorOnTotalDurationMillis;
330 
331             if (stats.hasStarted()) {
332                 // We only measure latencies for vibrations that actually triggered the vibrator.
333                 startLatencyMillis =
334                         (int) Math.max(0, stats.mStartUptimeMillis - stats.mCreateUptimeMillis);
335                 endLatencyMillis =
336                         (int) Math.max(0, completionUptimeMillis - stats.mEndUptimeMillis);
337             } else {
338                 startLatencyMillis = endLatencyMillis = 0;
339             }
340 
341             halComposeCount = stats.mVibratorComposeCount;
342             halComposePwleCount = stats.mVibratorComposePwleCount;
343             halOnCount = stats.mVibratorOnCount;
344             halOffCount = stats.mVibratorOffCount;
345             halPerformCount = stats.mVibratorPerformCount;
346             halSetAmplitudeCount = stats.mVibratorSetAmplitudeCount;
347             halSetExternalControlCount = stats.mVibratorSetExternalControlCount;
348             halCompositionSize = stats.mVibrationCompositionTotalSize;
349             halPwleSize = stats.mVibrationPwleTotalSize;
350             halSupportedCompositionPrimitivesUsed =
351                     filteredKeys(stats.mVibratorPrimitivesUsed, /* supported= */ true);
352             halSupportedEffectsUsed =
353                     filteredKeys(stats.mVibratorEffectsUsed, /* supported= */ true);
354             halUnsupportedCompositionPrimitivesUsed =
355                     filteredKeys(stats.mVibratorPrimitivesUsed, /* supported= */ false);
356             halUnsupportedEffectsUsed =
357                     filteredKeys(stats.mVibratorEffectsUsed, /* supported= */ false);
358         }
359 
360         @VisibleForTesting
isWritten()361         boolean isWritten() {
362             return mIsWritten;
363         }
364 
writeVibrationReported()365         void writeVibrationReported() {
366             if (mIsWritten) {
367                 Slog.wtf(TAG, "Writing same vibration stats multiple times for uid=" + uid);
368             }
369             mIsWritten = true;
370             // Mapping from this MetricInfo representation and the atom proto VibrationReported.
371             FrameworkStatsLog.write_non_chained(
372                     FrameworkStatsLog.VIBRATION_REPORTED,
373                     uid, null, vibrationType, usage, status, endedBySameUid, endedByUsage,
374                     interruptedUsage, repeatCount, totalDurationMillis, vibratorOnMillis,
375                     startLatencyMillis, endLatencyMillis, halComposeCount, halComposePwleCount,
376                     halOnCount, halOffCount, halPerformCount, halSetAmplitudeCount,
377                     halSetExternalControlCount, halSupportedCompositionPrimitivesUsed,
378                     halSupportedEffectsUsed, halUnsupportedCompositionPrimitivesUsed,
379                     halUnsupportedEffectsUsed, halCompositionSize, halPwleSize);
380         }
381 
filteredKeys(SparseBooleanArray supportArray, boolean supported)382         private static int[] filteredKeys(SparseBooleanArray supportArray, boolean supported) {
383             int count = 0;
384             for (int i = 0; i < supportArray.size(); i++) {
385                 if (supportArray.valueAt(i) == supported) count++;
386             }
387             if (count == 0) {
388                 return null;
389             }
390             int pos = 0;
391             int[] res = new int[count];
392             for (int i = 0; i < supportArray.size(); i++) {
393                 if (supportArray.valueAt(i) == supported) {
394                     res[pos++] = supportArray.keyAt(i);
395                 }
396             }
397             return res;
398         }
399     }
400 }
401