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