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