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.os.Handler;
20 import android.os.SystemClock;
21 import android.util.Slog;
22 import android.view.HapticFeedbackConstants;
23 
24 import com.android.internal.annotations.GuardedBy;
25 import com.android.internal.annotations.VisibleForTesting;
26 import com.android.internal.util.FrameworkStatsLog;
27 import com.android.modules.expresslog.Counter;
28 import com.android.modules.expresslog.Histogram;
29 
30 import java.util.ArrayDeque;
31 import java.util.Queue;
32 
33 /** Helper class for async write of atoms to {@link FrameworkStatsLog} using a given Handler. */
34 public class VibratorFrameworkStatsLogger {
35     private static final String TAG = "VibratorFrameworkStatsLogger";
36 
37     // VibrationReported pushed atom needs to be throttled to at most one every 10ms.
38     private static final int VIBRATION_REPORTED_MIN_INTERVAL_MILLIS = 10;
39     // We accumulate events that should take 3s to write and drop excessive metrics.
40     private static final int VIBRATION_REPORTED_MAX_QUEUE_SIZE = 300;
41     // Warning about dropping entries after this amount of atoms were dropped by the throttle.
42     private static final int VIBRATION_REPORTED_WARNING_QUEUE_SIZE = 200;
43 
44     // Latency between 0ms and 99ms, with 100 representing overflow latencies >= 100ms.
45     // Underflow not expected.
46     private static final Histogram sVibrationParamRequestLatencyHistogram = new Histogram(
47             "vibrator.value_vibration_param_request_latency",
48             new Histogram.UniformOptions(20, 0, 100));
49 
50     // Scales in [0, 2), with 2 representing overflow scales >= 2.
51     // Underflow expected to represent how many times scales were cleared (set to -1).
52     private static final Histogram sVibrationParamScaleHistogram = new Histogram(
53             "vibrator.value_vibration_param_scale", new Histogram.UniformOptions(20, 0, 2));
54 
55     // Scales in [0, 2), with 2 representing overflow scales >= 2.
56     // Underflow not expected.
57     private static final Histogram sAdaptiveHapticScaleHistogram = new Histogram(
58             "vibrator.value_vibration_adaptive_haptic_scale",
59             new Histogram.UniformOptions(20, 0, 2));
60 
61     private final Object mLock = new Object();
62     private final Handler mHandler;
63     private final long mVibrationReportedLogIntervalMillis;
64     private final long mVibrationReportedQueueMaxSize;
65     private final Runnable mConsumeVibrationStatsQueueRunnable =
66             () -> writeVibrationReportedFromQueue();
67 
68     @GuardedBy("mLock")
69     private long mLastVibrationReportedLogUptime;
70     @GuardedBy("mLock")
71     private Queue<VibrationStats.StatsInfo> mVibrationStatsQueue = new ArrayDeque<>();
72 
VibratorFrameworkStatsLogger(Handler handler)73     VibratorFrameworkStatsLogger(Handler handler) {
74         this(handler, VIBRATION_REPORTED_MIN_INTERVAL_MILLIS, VIBRATION_REPORTED_MAX_QUEUE_SIZE);
75     }
76 
77     @VisibleForTesting
VibratorFrameworkStatsLogger(Handler handler, int vibrationReportedLogIntervalMillis, int vibrationReportedQueueMaxSize)78     VibratorFrameworkStatsLogger(Handler handler, int vibrationReportedLogIntervalMillis,
79             int vibrationReportedQueueMaxSize) {
80         mHandler = handler;
81         mVibrationReportedLogIntervalMillis = vibrationReportedLogIntervalMillis;
82         mVibrationReportedQueueMaxSize = vibrationReportedQueueMaxSize;
83     }
84 
85     /** Writes {@link FrameworkStatsLog#VIBRATOR_STATE_CHANGED} for state ON. */
writeVibratorStateOnAsync(int uid, long duration)86     public void writeVibratorStateOnAsync(int uid, long duration) {
87         mHandler.post(
88                 () -> FrameworkStatsLog.write_non_chained(
89                         FrameworkStatsLog.VIBRATOR_STATE_CHANGED, uid, null,
90                         FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON, duration));
91     }
92 
93     /** Writes {@link FrameworkStatsLog#VIBRATOR_STATE_CHANGED} for state OFF. */
writeVibratorStateOffAsync(int uid)94     public void writeVibratorStateOffAsync(int uid) {
95         mHandler.post(
96                 () -> FrameworkStatsLog.write_non_chained(
97                         FrameworkStatsLog.VIBRATOR_STATE_CHANGED, uid, null,
98                         FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF,
99                         /* duration= */ 0));
100     }
101 
102     /**
103      *  Writes {@link FrameworkStatsLog#VIBRATION_REPORTED} for given vibration.
104      *
105      *  <p>This atom is throttled to be pushed once every 10ms, so this logger can keep a queue of
106      *  {@link VibrationStats.StatsInfo} entries to slowly write to statsd.
107      */
writeVibrationReportedAsync(VibrationStats.StatsInfo metrics)108     public void writeVibrationReportedAsync(VibrationStats.StatsInfo metrics) {
109         boolean needsScheduling;
110         long scheduleDelayMs;
111         int queueSize;
112 
113         synchronized (mLock) {
114             queueSize = mVibrationStatsQueue.size();
115             needsScheduling = (queueSize == 0);
116 
117             if (queueSize < mVibrationReportedQueueMaxSize) {
118                 mVibrationStatsQueue.offer(metrics);
119             }
120 
121             long nextLogUptime =
122                     mLastVibrationReportedLogUptime + mVibrationReportedLogIntervalMillis;
123             scheduleDelayMs = Math.max(0, nextLogUptime - SystemClock.uptimeMillis());
124         }
125 
126         if ((queueSize + 1) == VIBRATION_REPORTED_WARNING_QUEUE_SIZE) {
127             Slog.w(TAG, " Approaching vibration metrics queue limit, events might be dropped.");
128         }
129 
130         if (needsScheduling) {
131             mHandler.postDelayed(mConsumeVibrationStatsQueueRunnable, scheduleDelayMs);
132         }
133     }
134 
135     /** Writes next {@link FrameworkStatsLog#VIBRATION_REPORTED} from the queue. */
writeVibrationReportedFromQueue()136     private void writeVibrationReportedFromQueue() {
137         boolean needsScheduling;
138         VibrationStats.StatsInfo stats;
139 
140         synchronized (mLock) {
141             stats = mVibrationStatsQueue.poll();
142             needsScheduling = !mVibrationStatsQueue.isEmpty();
143 
144             if (stats != null) {
145                 mLastVibrationReportedLogUptime = SystemClock.uptimeMillis();
146             }
147         }
148 
149         if (stats == null) {
150             Slog.w(TAG, "Unexpected vibration metric flush with empty queue. Ignoring.");
151         } else {
152             stats.writeVibrationReported();
153         }
154 
155         if (needsScheduling) {
156             mHandler.postDelayed(mConsumeVibrationStatsQueueRunnable,
157                     mVibrationReportedLogIntervalMillis);
158         }
159     }
160 
161     /** Logs adaptive haptic scale value applied to a vibration, only if it's not 1.0. */
logVibrationAdaptiveHapticScale(int uid, float scale)162     public void logVibrationAdaptiveHapticScale(int uid, float scale) {
163         if (Float.compare(scale, 1f) != 0) {
164             sAdaptiveHapticScaleHistogram.logSampleWithUid(uid, scale);
165         }
166     }
167 
168     /** Logs a vibration param scale value received by the vibrator control service. */
logVibrationParamScale(float scale)169     public void logVibrationParamScale(float scale) {
170         sVibrationParamScaleHistogram.logSample(scale);
171     }
172 
173     /** Logs the latency of a successful vibration params request completed before a vibration. */
logVibrationParamRequestLatency(int uid, long latencyMs)174     public void logVibrationParamRequestLatency(int uid, long latencyMs) {
175         sVibrationParamRequestLatencyHistogram.logSampleWithUid(uid, (float) latencyMs);
176     }
177 
178     /** Logs a vibration params request timed out before a vibration. */
logVibrationParamRequestTimeout(int uid)179     public void logVibrationParamRequestTimeout(int uid) {
180         Counter.logIncrementWithUid("vibrator.value_vibration_param_request_timeout", uid);
181     }
182 
183     /** Logs when a response received for a vibration params request is ignored by the service. */
logVibrationParamResponseIgnored()184     public void logVibrationParamResponseIgnored() {
185         Counter.logIncrement("vibrator.value_vibration_param_response_ignored");
186     }
187 
188     /** Logs only if the haptics feedback effect is one of the KEYBOARD_ constants. */
logPerformHapticsFeedbackIfKeyboard(int uid, int hapticsFeedbackEffect)189     public static void logPerformHapticsFeedbackIfKeyboard(int uid, int hapticsFeedbackEffect) {
190         boolean isKeyboard;
191         switch (hapticsFeedbackEffect) {
192             case HapticFeedbackConstants.KEYBOARD_TAP:
193             case HapticFeedbackConstants.KEYBOARD_RELEASE:
194                 isKeyboard = true;
195                 break;
196             default:
197                 isKeyboard = false;
198                 break;
199         }
200         if (isKeyboard) {
201             Counter.logIncrementWithUid("vibrator.value_perform_haptic_feedback_keyboard", uid);
202         }
203     }
204 }
205