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