• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 static android.os.VibrationAttributes.USAGE_ACCESSIBILITY;
20 import static android.os.VibrationAttributes.USAGE_ALARM;
21 import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
22 import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK;
23 import static android.os.VibrationAttributes.USAGE_MEDIA;
24 import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
25 import static android.os.VibrationAttributes.USAGE_PHYSICAL_EMULATION;
26 import static android.os.VibrationAttributes.USAGE_RINGTONE;
27 import static android.os.VibrationAttributes.USAGE_TOUCH;
28 import static android.os.VibrationAttributes.USAGE_UNKNOWN;
29 
30 import android.annotation.NonNull;
31 import android.annotation.Nullable;
32 import android.annotation.SuppressLint;
33 import android.content.Context;
34 import android.frameworks.vibrator.IVibratorControlService;
35 import android.frameworks.vibrator.IVibratorController;
36 import android.frameworks.vibrator.ScaleParam;
37 import android.frameworks.vibrator.VibrationParam;
38 import android.os.Binder;
39 import android.os.IBinder;
40 import android.os.RemoteException;
41 import android.os.SystemClock;
42 import android.os.VibrationAttributes;
43 import android.os.VibrationEffect;
44 import android.os.vibrator.Flags;
45 import android.util.IndentingPrintWriter;
46 import android.util.IntArray;
47 import android.util.Slog;
48 import android.util.proto.ProtoOutputStream;
49 
50 import com.android.internal.annotations.GuardedBy;
51 import com.android.internal.annotations.VisibleForTesting;
52 import com.android.internal.util.ArrayUtils;
53 
54 import java.io.PrintWriter;
55 import java.time.Instant;
56 import java.time.ZoneId;
57 import java.time.format.DateTimeFormatter;
58 import java.util.Locale;
59 import java.util.Objects;
60 import java.util.concurrent.CompletableFuture;
61 
62 /**
63  * Implementation of {@link IVibratorControlService} which allows the registration of
64  * {@link IVibratorController} to set and receive vibration params.
65  */
66 final class VibratorControlService extends IVibratorControlService.Stub {
67     private static final String TAG = "VibratorControlService";
68     private static final int UNRECOGNIZED_VIBRATION_TYPE = -1;
69     private static final int NO_SCALE = -1;
70 
71     private static final DateTimeFormatter DEBUG_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(
72             "MM-dd HH:mm:ss.SSS");
73 
74     private final VibrationParamsRecords mVibrationParamsRecords;
75     private final VibratorControllerHolder mVibratorControllerHolder;
76     private final VibrationScaler mVibrationScaler;
77     private final VibratorFrameworkStatsLogger mStatsLogger;
78     private final Object mLock;
79     private final int[] mRequestVibrationParamsForUsages;
80 
81     @GuardedBy("mLock")
82     @Nullable
83     private VibrationParamRequest mVibrationParamRequest = null;
84 
VibratorControlService(Context context, VibratorControllerHolder vibratorControllerHolder, VibrationScaler vibrationScaler, VibrationSettings vibrationSettings, VibratorFrameworkStatsLogger statsLogger, Object lock)85     VibratorControlService(Context context,
86             VibratorControllerHolder vibratorControllerHolder, VibrationScaler vibrationScaler,
87             VibrationSettings vibrationSettings, VibratorFrameworkStatsLogger statsLogger,
88             Object lock) {
89         mVibratorControllerHolder = vibratorControllerHolder;
90         mVibrationScaler = vibrationScaler;
91         mStatsLogger = statsLogger;
92         mLock = lock;
93         mRequestVibrationParamsForUsages = vibrationSettings.getRequestVibrationParamsForUsages();
94 
95         int dumpSizeLimit = context.getResources().getInteger(
96                 com.android.internal.R.integer.config_previousVibrationsDumpSizeLimit);
97         int dumpAggregationTimeLimit = context.getResources().getInteger(
98                 com.android.internal.R.integer
99                         .config_previousVibrationsDumpAggregationTimeMillisLimit);
100         mVibrationParamsRecords =
101                 new VibrationParamsRecords(dumpSizeLimit, dumpAggregationTimeLimit);
102     }
103 
104     @Override
registerVibratorController(@onNull IVibratorController controller)105     public void registerVibratorController(@NonNull IVibratorController controller) {
106         Objects.requireNonNull(controller);
107 
108         synchronized (mLock) {
109             mVibratorControllerHolder.setVibratorController(controller);
110         }
111     }
112 
113     @Override
unregisterVibratorController(@onNull IVibratorController controller)114     public void unregisterVibratorController(@NonNull IVibratorController controller) {
115         Objects.requireNonNull(controller);
116 
117         synchronized (mLock) {
118             if (mVibratorControllerHolder.getVibratorController() == null) {
119                 Slog.w(TAG, "Received request to unregister IVibratorController = "
120                         + controller + ", but no controller was previously registered. Request "
121                         + "Ignored.");
122                 return;
123             }
124             if (!Objects.equals(mVibratorControllerHolder.getVibratorController().asBinder(),
125                     controller.asBinder())) {
126                 Slog.wtf(TAG, "Failed to unregister IVibratorController. The provided "
127                         + "controller doesn't match the registered one. " + this);
128                 return;
129             }
130             mVibrationScaler.clearAdaptiveHapticsScales();
131             mVibratorControllerHolder.setVibratorController(null);
132             endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ true);
133         }
134     }
135 
136     @Override
setVibrationParams(@uppressLint"ArrayReturn") VibrationParam[] params, @NonNull IVibratorController token)137     public void setVibrationParams(@SuppressLint("ArrayReturn") VibrationParam[] params,
138             @NonNull IVibratorController token) {
139         Objects.requireNonNull(token);
140         requireContainsNoNullElement(params);
141 
142         synchronized (mLock) {
143             if (mVibratorControllerHolder.getVibratorController() == null) {
144                 Slog.w(TAG, "Received request to set VibrationParams for IVibratorController = "
145                         + token + ", but no controller was previously registered. Request "
146                         + "Ignored.");
147                 return;
148             }
149             if (!Objects.equals(mVibratorControllerHolder.getVibratorController().asBinder(),
150                     token.asBinder())) {
151                 Slog.wtf(TAG, "Failed to set new VibrationParams. The provided "
152                         + "controller doesn't match the registered one. " + this);
153                 return;
154             }
155             if (params == null) {
156                 // Adaptive haptics scales cannot be set to null. Ignoring request.
157                 Slog.d(TAG,
158                         "New vibration params received but are null. New vibration "
159                                 + "params ignored.");
160                 return;
161             }
162 
163             updateAdaptiveHapticsScales(params);
164             recordUpdateVibrationParams(params, /* fromRequest= */ false);
165         }
166     }
167 
168     @Override
clearVibrationParams(int types, @NonNull IVibratorController token)169     public void clearVibrationParams(int types, @NonNull IVibratorController token) {
170         Objects.requireNonNull(token);
171 
172         synchronized (mLock) {
173             if (mVibratorControllerHolder.getVibratorController() == null) {
174                 Slog.w(TAG, "Received request to clear VibrationParams for IVibratorController = "
175                         + token + ", but no controller was previously registered. Request "
176                         + "Ignored.");
177                 return;
178             }
179             if (!Objects.equals(mVibratorControllerHolder.getVibratorController().asBinder(),
180                     token.asBinder())) {
181                 Slog.wtf(TAG, "Failed to clear VibrationParams. The provided "
182                         + "controller doesn't match the registered one. " + this);
183                 return;
184             }
185 
186             updateAdaptiveHapticsScales(types, NO_SCALE);
187             recordClearVibrationParams(types);
188         }
189     }
190 
191     @Override
onRequestVibrationParamsComplete( @onNull IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result)192     public void onRequestVibrationParamsComplete(
193             @NonNull IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result) {
194         Objects.requireNonNull(requestToken);
195         requireContainsNoNullElement(result);
196 
197         synchronized (mLock) {
198             if (mVibrationParamRequest == null) {
199                 Slog.wtf(TAG,
200                         "New vibration params received but no token was cached in the service. "
201                                 + "New vibration params ignored.");
202                 mStatsLogger.logVibrationParamResponseIgnored();
203                 return;
204             }
205 
206             if (!Objects.equals(requestToken, mVibrationParamRequest.token)) {
207                 Slog.w(TAG,
208                         "New vibration params received but the provided token does not match the "
209                                 + "cached one. New vibration params ignored.");
210                 mStatsLogger.logVibrationParamResponseIgnored();
211                 return;
212             }
213 
214             long latencyMs = SystemClock.uptimeMillis() - mVibrationParamRequest.uptimeMs;
215             mStatsLogger.logVibrationParamRequestLatency(mVibrationParamRequest.uid, latencyMs);
216 
217             if (result == null) {
218                 Slog.d(TAG,
219                         "New vibration params received but are null. New vibration "
220                                 + "params ignored.");
221                 return;
222             }
223 
224             updateAdaptiveHapticsScales(result);
225             endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ false);
226             recordUpdateVibrationParams(result, /* fromRequest= */ true);
227         }
228     }
229 
230     @Override
getInterfaceVersion()231     public int getInterfaceVersion() {
232         return this.VERSION;
233     }
234 
235     @Override
getInterfaceHash()236     public String getInterfaceHash() {
237         return this.HASH;
238     }
239 
240     /**
241      * If an {@link IVibratorController} is registered to the service, it will request the latest
242      * vibration params and return a {@link CompletableFuture} that completes when the request is
243      * fulfilled. Otherwise, ignores the call and returns null.
244      *
245      * @param usage a {@link android.os.VibrationAttributes} usage.
246      * @param timeoutInMillis the request's timeout in millis.
247      * @return a {@link CompletableFuture} to track the completion of the vibration param
248      * request, or null if no {@link IVibratorController} is registered.
249      */
250     @Nullable
triggerVibrationParamsRequest( int uid, @VibrationAttributes.Usage int usage, int timeoutInMillis)251     public CompletableFuture<Void> triggerVibrationParamsRequest(
252             int uid, @VibrationAttributes.Usage int usage, int timeoutInMillis) {
253         synchronized (mLock) {
254             IVibratorController vibratorController =
255                     mVibratorControllerHolder.getVibratorController();
256             if (vibratorController == null) {
257                 Slog.d(TAG, "Unable to request vibration params. There is no registered "
258                         + "IVibrationController.");
259                 return null;
260             }
261 
262             int vibrationType = mapToAdaptiveVibrationType(usage);
263             if (vibrationType == UNRECOGNIZED_VIBRATION_TYPE) {
264                 Slog.d(TAG, "Unable to request vibration params. The provided usage " + usage
265                         + " is unrecognized.");
266                 return null;
267             }
268 
269             if (Flags.throttleVibrationParamsRequests() && mVibrationParamRequest != null
270                     && mVibrationParamRequest.usage == usage) {
271                 // Reuse existing future for ongoing request with same usage.
272                 return mVibrationParamRequest.future;
273             }
274 
275             try {
276                 endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ true);
277                 mVibrationParamRequest = new VibrationParamRequest(uid, usage);
278                 vibratorController.requestVibrationParams(vibrationType, timeoutInMillis,
279                         mVibrationParamRequest.token);
280                 return mVibrationParamRequest.future;
281             } catch (RemoteException e) {
282                 Slog.e(TAG, "Failed to request vibration params.", e);
283                 endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ true);
284             }
285 
286             return null;
287         }
288     }
289 
290     /**
291      * If an {@link IVibratorController} is registered to the service, then it checks whether to
292      * request new vibration params before playing the vibration. Returns true if the
293      * usage is for high latency vibrations, e.g. ringtone and notification, and can be delayed
294      * slightly. Otherwise, returns false.
295      *
296      * @param usage a {@link android.os.VibrationAttributes} usage.
297      * @return true if usage is for high latency vibrations, false otherwise.
298      */
shouldRequestVibrationParams(@ibrationAttributes.Usage int usage)299     public boolean shouldRequestVibrationParams(@VibrationAttributes.Usage int usage) {
300         synchronized (mLock) {
301             IVibratorController vibratorController =
302                     mVibratorControllerHolder.getVibratorController();
303             if (vibratorController == null) {
304                 return false;
305             }
306 
307             return ArrayUtils.contains(mRequestVibrationParamsForUsages, usage);
308         }
309     }
310 
311     /**
312      * Returns the binder token which is used to validate
313      * {@link #onRequestVibrationParamsComplete(IBinder, VibrationParam[])} calls.
314      */
315     @VisibleForTesting
getRequestVibrationParamsToken()316     public IBinder getRequestVibrationParamsToken() {
317         synchronized (mLock) {
318             return mVibrationParamRequest == null ? null : mVibrationParamRequest.token;
319         }
320     }
321 
322     /** Write current settings into given {@link PrintWriter}. */
dump(IndentingPrintWriter pw)323     void dump(IndentingPrintWriter pw) {
324         boolean isVibratorControllerRegistered;
325         boolean hasPendingVibrationParamsRequest;
326         synchronized (mLock) {
327             isVibratorControllerRegistered =
328                     mVibratorControllerHolder.getVibratorController() != null;
329             hasPendingVibrationParamsRequest = mVibrationParamRequest != null;
330         }
331 
332         pw.println("VibratorControlService:");
333         pw.increaseIndent();
334         pw.println("isVibratorControllerRegistered = " + isVibratorControllerRegistered);
335         pw.println("hasPendingVibrationParamsRequest = " + hasPendingVibrationParamsRequest);
336 
337         pw.println();
338         pw.println("Vibration parameters update history:");
339         pw.increaseIndent();
340         mVibrationParamsRecords.dump(pw);
341         pw.decreaseIndent();
342 
343         pw.decreaseIndent();
344     }
345 
346     /** Write current settings into given {@link ProtoOutputStream}. */
dump(ProtoOutputStream proto)347     void dump(ProtoOutputStream proto) {
348         boolean isVibratorControllerRegistered;
349         synchronized (mLock) {
350             isVibratorControllerRegistered =
351                     mVibratorControllerHolder.getVibratorController() != null;
352         }
353         proto.write(VibratorManagerServiceDumpProto.IS_VIBRATOR_CONTROLLER_REGISTERED,
354                 isVibratorControllerRegistered);
355         mVibrationParamsRecords.dump(proto);
356     }
357 
358     /**
359      * Completes or cancels the vibration params request future and resets the future and token
360      * to null.
361      * @param wasCancelled specifies whether the future should be ended by being cancelled or not.
362      */
363     @GuardedBy("mLock")
endOngoingRequestVibrationParamsLocked(boolean wasCancelled)364     private void endOngoingRequestVibrationParamsLocked(boolean wasCancelled) {
365         if (mVibrationParamRequest != null) {
366             mVibrationParamRequest.endRequest(wasCancelled);
367         }
368         mVibrationParamRequest = null;
369     }
370 
mapToAdaptiveVibrationType(@ibrationAttributes.Usage int usage)371     private static int mapToAdaptiveVibrationType(@VibrationAttributes.Usage int usage) {
372         switch (usage) {
373             case USAGE_ALARM -> {
374                 return ScaleParam.TYPE_ALARM;
375             }
376             case USAGE_NOTIFICATION, USAGE_COMMUNICATION_REQUEST -> {
377                 return ScaleParam.TYPE_NOTIFICATION;
378             }
379             case USAGE_RINGTONE -> {
380                 return ScaleParam.TYPE_RINGTONE;
381             }
382             case USAGE_MEDIA, USAGE_UNKNOWN -> {
383                 return ScaleParam.TYPE_MEDIA;
384             }
385             case USAGE_TOUCH, USAGE_HARDWARE_FEEDBACK, USAGE_ACCESSIBILITY,
386                     USAGE_PHYSICAL_EMULATION -> {
387                 return ScaleParam.TYPE_INTERACTIVE;
388             }
389             default -> {
390                 Slog.w(TAG, "Unrecognized vibration usage " + usage);
391                 return UNRECOGNIZED_VIBRATION_TYPE;
392             }
393         }
394     }
395 
mapFromAdaptiveVibrationTypeToVibrationUsages(int types)396     private static int[] mapFromAdaptiveVibrationTypeToVibrationUsages(int types) {
397         IntArray usages = new IntArray(15);
398         if ((ScaleParam.TYPE_ALARM & types) != 0) {
399             usages.add(USAGE_ALARM);
400         }
401 
402         if ((ScaleParam.TYPE_NOTIFICATION & types) != 0) {
403             usages.add(USAGE_NOTIFICATION);
404             usages.add(USAGE_COMMUNICATION_REQUEST);
405         }
406 
407         if ((ScaleParam.TYPE_RINGTONE & types) != 0) {
408             usages.add(USAGE_RINGTONE);
409         }
410 
411         if ((ScaleParam.TYPE_MEDIA & types) != 0) {
412             usages.add(USAGE_MEDIA);
413             usages.add(USAGE_UNKNOWN);
414         }
415 
416         if ((ScaleParam.TYPE_INTERACTIVE & types) != 0) {
417             usages.add(USAGE_TOUCH);
418             usages.add(USAGE_HARDWARE_FEEDBACK);
419         }
420         return usages.toArray();
421     }
422 
423     /**
424      * Updates the adaptive haptics scales cached in {@link VibrationScaler} with the
425      * provided params.
426      *
427      * @param params the new vibration params.
428      */
updateAdaptiveHapticsScales(@onNull VibrationParam[] params)429     private void updateAdaptiveHapticsScales(@NonNull VibrationParam[] params) {
430         Objects.requireNonNull(params);
431 
432         for (VibrationParam param : params) {
433             if (param.getTag() != VibrationParam.scale) {
434                 Slog.e(TAG, "Unsupported vibration param: " + param);
435                 continue;
436             }
437             ScaleParam scaleParam = param.getScale();
438             updateAdaptiveHapticsScales(scaleParam.typesMask, scaleParam.scale);
439         }
440     }
441 
442     /**
443      * Updates the adaptive haptics scales, cached in {@link VibrationScaler}, for the provided
444      * vibration types.
445      *
446      * @param types The type of vibrations.
447      * @param scale The scaling factor that should be applied to the vibrations.
448      */
updateAdaptiveHapticsScales(int types, float scale)449     private void updateAdaptiveHapticsScales(int types, float scale) {
450         mStatsLogger.logVibrationParamScale(scale);
451         for (int usage : mapFromAdaptiveVibrationTypeToVibrationUsages(types)) {
452             updateOrRemoveAdaptiveHapticsScale(usage, scale);
453         }
454     }
455 
456     /**
457      * Updates or removes the adaptive haptics scale for the specified usage. If the scale is set
458      * to {@link #NO_SCALE} then it will be removed from the cached usage scales in
459      * {@link VibrationScaler}. Otherwise, the cached usage scale will be updated by the new value.
460      *
461      * @param usageHint one of VibrationAttributes.USAGE_*.
462      * @param scale     The scaling factor that should be applied to the vibrations. If set to
463      *                  {@link #NO_SCALE} then the scale will be removed.
464      */
updateOrRemoveAdaptiveHapticsScale(@ibrationAttributes.Usage int usageHint, float scale)465     private void updateOrRemoveAdaptiveHapticsScale(@VibrationAttributes.Usage int usageHint,
466             float scale) {
467         if (scale == NO_SCALE) {
468             mVibrationScaler.removeAdaptiveHapticsScale(usageHint);
469             return;
470         }
471 
472         mVibrationScaler.updateAdaptiveHapticsScale(usageHint, scale);
473     }
474 
recordUpdateVibrationParams(@onNull VibrationParam[] params, boolean fromRequest)475     private void recordUpdateVibrationParams(@NonNull VibrationParam[] params,
476             boolean fromRequest) {
477         Objects.requireNonNull(params);
478 
479         VibrationParamsRecords.Operation operation =
480                 fromRequest ? VibrationParamsRecords.Operation.PULL
481                         : VibrationParamsRecords.Operation.PUSH;
482         long createTime = SystemClock.uptimeMillis();
483         for (VibrationParam param : params) {
484             if (param.getTag() != VibrationParam.scale) {
485                 Slog.w(TAG, "Unsupported vibration param ignored from dumpsys records: " + param);
486                 continue;
487             }
488             ScaleParam scaleParam = param.getScale();
489             mVibrationParamsRecords.add(new VibrationScaleParamRecord(operation, createTime,
490                     scaleParam.typesMask, scaleParam.scale));
491         }
492     }
493 
recordClearVibrationParams(int typesMask)494     private void recordClearVibrationParams(int typesMask) {
495         long createTime = SystemClock.uptimeMillis();
496         mVibrationParamsRecords.add(new VibrationScaleParamRecord(
497                 VibrationParamsRecords.Operation.CLEAR, createTime, typesMask, NO_SCALE));
498     }
499 
requireContainsNoNullElement(VibrationParam[] params)500     private void requireContainsNoNullElement(VibrationParam[] params) {
501         if (ArrayUtils.contains(params, null)) {
502             throw new IllegalArgumentException(
503                     "Invalid vibration params received: null values are not permitted.");
504         }
505     }
506 
507     /**
508      * Keep records of {@link VibrationParam} values received by this service from a registered
509      * {@link VibratorController} and provide debug information for this service.
510      */
511     private static final class VibrationParamsRecords
512             extends GroupedAggregatedLogRecords<VibrationScaleParamRecord> {
513 
514         /** The type of operations on vibration parameters that the service is recording. */
515         enum Operation {
516             PULL, PUSH, CLEAR
517         };
518 
VibrationParamsRecords(int sizeLimit, int aggregationTimeLimit)519         VibrationParamsRecords(int sizeLimit, int aggregationTimeLimit) {
520             super(sizeLimit, aggregationTimeLimit);
521         }
522 
523         @Override
dumpGroupHeader(IndentingPrintWriter pw, int paramType)524         synchronized void dumpGroupHeader(IndentingPrintWriter pw, int paramType) {
525             if (paramType == VibrationParam.scale) {
526                 pw.println("SCALE:");
527             } else {
528                 pw.println("UNKNOWN:");
529             }
530         }
531 
532         @Override
findGroupKeyProtoFieldId(int usage)533         synchronized long findGroupKeyProtoFieldId(int usage) {
534             return VibratorManagerServiceDumpProto.PREVIOUS_VIBRATION_PARAMS;
535         }
536     }
537 
538     /** Represents a request for {@link VibrationParam}. */
539     private static final class VibrationParamRequest {
540         public final CompletableFuture<Void> future = new CompletableFuture<>();
541         public final IBinder token = new Binder();
542         public final int uid;
543         public final @VibrationAttributes.Usage int usage;
544         public final long uptimeMs;
545 
VibrationParamRequest(int uid, @VibrationAttributes.Usage int usage)546         VibrationParamRequest(int uid, @VibrationAttributes.Usage int usage) {
547             this.uid = uid;
548             this.usage = usage;
549             uptimeMs = SystemClock.uptimeMillis();
550         }
551 
endRequest(boolean wasCancelled)552         public void endRequest(boolean wasCancelled) {
553             if (wasCancelled) {
554                 future.cancel(/* mayInterruptIfRunning= */ true);
555             } else {
556                 future.complete(null);
557             }
558         }
559     }
560 
561     /**
562      * Record for a single {@link VibrationSession.DebugInfo}, that can be grouped by usage and
563      * aggregated by UID, {@link VibrationAttributes} and {@link VibrationEffect}.
564      */
565     private static final class VibrationScaleParamRecord
566             implements GroupedAggregatedLogRecords.SingleLogRecord {
567 
568         private final VibrationParamsRecords.Operation mOperation;
569         private final long mCreateTime;
570         private final int mTypesMask;
571         private final float mScale;
572 
VibrationScaleParamRecord(VibrationParamsRecords.Operation operation, long createTime, int typesMask, float scale)573         VibrationScaleParamRecord(VibrationParamsRecords.Operation operation, long createTime,
574                 int typesMask, float scale) {
575             mOperation = operation;
576             mCreateTime = createTime;
577             mTypesMask = typesMask;
578             mScale = scale;
579         }
580 
581         @Override
getGroupKey()582         public int getGroupKey() {
583             return VibrationParam.scale;
584         }
585 
586         @Override
getCreateUptimeMs()587         public long getCreateUptimeMs() {
588             return mCreateTime;
589         }
590 
591         @Override
mayAggregate(GroupedAggregatedLogRecords.SingleLogRecord record)592         public boolean mayAggregate(GroupedAggregatedLogRecords.SingleLogRecord record) {
593             if (!(record instanceof VibrationScaleParamRecord param)) {
594                 return false;
595             }
596             return mTypesMask == param.mTypesMask && mOperation == param.mOperation;
597         }
598 
599         @Override
dump(IndentingPrintWriter pw)600         public void dump(IndentingPrintWriter pw) {
601             String line = String.format(Locale.ROOT,
602                     "%s | %6s | scale: %5s | typesMask: %6s | usages: %s",
603                     DEBUG_DATE_TIME_FORMATTER.withZone(ZoneId.systemDefault()).format(
604                             Instant.ofEpochMilli(mCreateTime)),
605                     mOperation.name().toLowerCase(Locale.ROOT),
606                     (mScale == NO_SCALE) ? "" : String.format(Locale.ROOT, "%.2f", mScale),
607                     Long.toBinaryString(mTypesMask), createVibrationUsagesString());
608             pw.println(line);
609         }
610 
611         @Override
dump(ProtoOutputStream proto, long fieldId)612         public void dump(ProtoOutputStream proto, long fieldId) {
613             final long token = proto.start(fieldId);
614             proto.write(VibrationParamProto.CREATE_TIME, mCreateTime);
615             proto.write(VibrationParamProto.IS_FROM_REQUEST,
616                     mOperation == VibrationParamsRecords.Operation.PULL);
617 
618             final long scaleToken = proto.start(VibrationParamProto.SCALE);
619             proto.write(VibrationScaleParamProto.TYPES_MASK, mTypesMask);
620             proto.write(VibrationScaleParamProto.SCALE, mScale);
621             proto.end(scaleToken);
622 
623             proto.end(token);
624         }
625 
createVibrationUsagesString()626         private String createVibrationUsagesString() {
627             StringBuilder sb = new StringBuilder();
628             int[] usages = mapFromAdaptiveVibrationTypeToVibrationUsages(mTypesMask);
629             for (int i = 0; i < usages.length; i++) {
630                 if (i > 0) sb.append(", ");
631                 sb.append(VibrationAttributes.usageToString(usages[i]));
632             }
633             return sb.toString();
634         }
635     }
636 }
637