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