1 /* 2 * Copyright (C) 2024 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 com.android.server.vibrator.VibrationSession.DebugInfo.formatTime; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.media.AudioAttributes; 24 import android.os.CancellationSignal; 25 import android.os.CombinedVibration; 26 import android.os.ExternalVibration; 27 import android.os.Handler; 28 import android.os.IBinder; 29 import android.os.ICancellationSignal; 30 import android.os.RemoteException; 31 import android.os.SystemClock; 32 import android.os.VibrationAttributes; 33 import android.os.vibrator.IVibrationSession; 34 import android.os.vibrator.IVibrationSessionCallback; 35 import android.util.IndentingPrintWriter; 36 import android.util.Slog; 37 import android.util.proto.ProtoOutputStream; 38 39 import com.android.internal.annotations.GuardedBy; 40 import com.android.internal.annotations.VisibleForTesting; 41 42 import java.util.ArrayList; 43 import java.util.Arrays; 44 import java.util.List; 45 import java.util.Locale; 46 import java.util.NoSuchElementException; 47 48 /** 49 * A vibration session started by a vendor request that can trigger {@link CombinedVibration}. 50 */ 51 final class VendorVibrationSession extends IVibrationSession.Stub 52 implements VibrationSession, CancellationSignal.OnCancelListener, IBinder.DeathRecipient { 53 private static final String TAG = "VendorVibrationSession"; 54 // To enable these logs, run: 55 // 'adb shell setprop persist.log.tag.VendorVibrationSession DEBUG && adb reboot' 56 private static final boolean DEBUG = VibratorDebugUtils.isDebuggable(TAG); 57 58 /** Calls into VibratorManager functionality needed for playing an {@link ExternalVibration}. */ 59 interface VibratorManagerHooks { 60 61 /** Tells the manager to end the vibration session. */ endSession(long sessionId, boolean shouldAbort)62 void endSession(long sessionId, boolean shouldAbort); 63 64 /** 65 * Tells the manager that the vibration session is finished and the vibrators can now be 66 * used for another vibration. 67 */ onSessionReleased(long sessionId)68 void onSessionReleased(long sessionId); 69 70 /** Request the manager to trigger a vibration within this session. */ vibrate(long sessionId, CallerInfo callerInfo, CombinedVibration vibration)71 void vibrate(long sessionId, CallerInfo callerInfo, CombinedVibration vibration); 72 } 73 74 private final Object mLock = new Object(); 75 private final long mSessionId = VibrationSession.nextSessionId(); 76 private final ICancellationSignal mCancellationSignal = CancellationSignal.createTransport(); 77 private final int[] mVibratorIds; 78 private final long mCreateUptime; 79 private final long mCreateTime; 80 private final VendorCallbackWrapper mCallback; 81 private final CallerInfo mCallerInfo; 82 private final VibratorManagerHooks mManagerHooks; 83 private final DeviceAdapter mDeviceAdapter; 84 private final Handler mHandler; 85 private final List<DebugInfo> mVibrations = new ArrayList<>(); 86 87 @GuardedBy("mLock") 88 private Status mStatus = Status.RUNNING; 89 @GuardedBy("mLock") 90 private Status mEndStatusRequest; 91 @GuardedBy("mLock") 92 private boolean mEndedByVendor; 93 @GuardedBy("mLock") 94 private long mStartTime; 95 @GuardedBy("mLock") 96 private long mEndUptime; 97 @GuardedBy("mLock") 98 private long mEndTime; 99 @GuardedBy("mLock") 100 private VibrationStepConductor mConductor; 101 VendorVibrationSession(@onNull CallerInfo callerInfo, @NonNull Handler handler, @NonNull VibratorManagerHooks managerHooks, @NonNull DeviceAdapter deviceAdapter, @NonNull IVibrationSessionCallback callback)102 VendorVibrationSession(@NonNull CallerInfo callerInfo, @NonNull Handler handler, 103 @NonNull VibratorManagerHooks managerHooks, @NonNull DeviceAdapter deviceAdapter, 104 @NonNull IVibrationSessionCallback callback) { 105 mCreateUptime = SystemClock.uptimeMillis(); 106 mCreateTime = System.currentTimeMillis(); 107 mVibratorIds = deviceAdapter.getAvailableVibratorIds(); 108 mHandler = handler; 109 mCallback = new VendorCallbackWrapper(callback, handler); 110 mCallerInfo = callerInfo; 111 mManagerHooks = managerHooks; 112 mDeviceAdapter = deviceAdapter; 113 CancellationSignal.fromTransport(mCancellationSignal).setOnCancelListener(this); 114 } 115 116 @Override vibrate(CombinedVibration vibration, String reason)117 public void vibrate(CombinedVibration vibration, String reason) { 118 CallerInfo vibrationCallerInfo = new CallerInfo(mCallerInfo.attrs, mCallerInfo.uid, 119 mCallerInfo.deviceId, mCallerInfo.opPkg, reason); 120 mManagerHooks.vibrate(mSessionId, vibrationCallerInfo, vibration); 121 } 122 123 @Override finishSession()124 public void finishSession() { 125 if (DEBUG) { 126 Slog.d(TAG, "Session finish requested, ending vibration session..."); 127 } 128 // Do not abort session in HAL, wait for ongoing vibration requests to complete. 129 // This might take a while to end the session, but it can be aborted by cancelSession. 130 requestEndSession(Status.FINISHED, /* shouldAbort= */ false, /* isVendorRequest= */ true); 131 } 132 133 @Override cancelSession()134 public void cancelSession() { 135 if (DEBUG) { 136 Slog.d(TAG, "Session cancel requested, aborting vibration session..."); 137 } 138 // Always abort session in HAL while cancelling it. 139 // This might be triggered after finishSession was already called. 140 requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true, 141 /* isVendorRequest= */ true); 142 } 143 144 @Override getSessionId()145 public long getSessionId() { 146 return mSessionId; 147 } 148 149 @Override getCreateUptimeMillis()150 public long getCreateUptimeMillis() { 151 return mCreateUptime; 152 } 153 154 @Override isRepeating()155 public boolean isRepeating() { 156 return false; 157 } 158 159 @Override getCallerInfo()160 public CallerInfo getCallerInfo() { 161 return mCallerInfo; 162 } 163 164 @Override getCallerToken()165 public IBinder getCallerToken() { 166 return mCallback.getBinderToken(); 167 } 168 169 @Override getDebugInfo()170 public DebugInfo getDebugInfo() { 171 synchronized (mLock) { 172 return new DebugInfoImpl(mStatus, mCallerInfo, mCreateUptime, mCreateTime, mStartTime, 173 mEndUptime, mEndTime, mEndedByVendor, mVibrations); 174 } 175 } 176 177 @Override wasEndRequested()178 public boolean wasEndRequested() { 179 synchronized (mLock) { 180 return mEndStatusRequest != null; 181 } 182 } 183 184 @Override onCancel()185 public void onCancel() { 186 if (DEBUG) { 187 Slog.d(TAG, "Session cancellation signal received, aborting vibration session..."); 188 } 189 requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true, 190 /* isVendorRequest= */ true); 191 } 192 193 @Override binderDied()194 public void binderDied() { 195 if (DEBUG) { 196 Slog.d(TAG, "Session binder died, aborting vibration session..."); 197 } 198 requestEndSession(Status.CANCELLED_BINDER_DIED, /* shouldAbort= */ true, 199 /* isVendorRequest= */ false); 200 } 201 202 @Override linkToDeath()203 public boolean linkToDeath() { 204 return mCallback.linkToDeath(this); 205 } 206 207 @Override unlinkToDeath()208 public void unlinkToDeath() { 209 mCallback.unlinkToDeath(this); 210 } 211 212 @Override requestEnd(@onNull Status status, @Nullable CallerInfo endedBy, boolean immediate)213 public void requestEnd(@NonNull Status status, @Nullable CallerInfo endedBy, 214 boolean immediate) { 215 // All requests to end a session should abort it to stop ongoing vibrations, even if 216 // immediate flag is false. Only the #finishSession API will not abort and wait for 217 // session vibrations to complete, which might take a long time. 218 requestEndSession(status, /* shouldAbort= */ true, /* isVendorRequest= */ false); 219 } 220 221 @Override notifyVibratorCallback(int vibratorId, long vibrationId, long stepId)222 public void notifyVibratorCallback(int vibratorId, long vibrationId, long stepId) { 223 if (DEBUG) { 224 Slog.d(TAG, "Vibration callback received for vibration " + vibrationId 225 + " step " + stepId + " on vibrator " + vibratorId + ", ignoring..."); 226 } 227 } 228 229 @Override notifySyncedVibratorsCallback(long vibrationId)230 public void notifySyncedVibratorsCallback(long vibrationId) { 231 if (DEBUG) { 232 Slog.d(TAG, "Synced vibration callback received for vibration " + vibrationId 233 + ", ignoring..."); 234 } 235 } 236 237 @Override notifySessionCallback()238 public void notifySessionCallback() { 239 if (DEBUG) { 240 Slog.d(TAG, "Session callback received, ending vibration session..."); 241 } 242 synchronized (mLock) { 243 // If end was not requested then the HAL has cancelled the session. 244 notifyEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON, 245 /* isVendorRequest= */ false); 246 maybeSetStatusToRequestedLocked(); 247 clearVibrationConductor(); 248 final Status endStatus = mStatus; 249 mHandler.post(() -> { 250 mManagerHooks.onSessionReleased(mSessionId); 251 // Only trigger client callback after session is released in the manager. 252 mCallback.notifyFinished(endStatus); 253 }); 254 } 255 } 256 257 @Override toString()258 public String toString() { 259 synchronized (mLock) { 260 return "createTime: " + formatTime(mCreateTime, /*includeDate=*/ true) 261 + ", startTime: " + (mStartTime == 0 ? null : formatTime(mStartTime, 262 /* includeDate= */ true)) 263 + ", endTime: " + (mEndTime == 0 ? null : formatTime(mEndTime, 264 /* includeDate= */ true)) 265 + ", status: " + mStatus.name().toLowerCase(Locale.ROOT) 266 + ", callerInfo: " + mCallerInfo 267 + ", vibratorIds: " + Arrays.toString(mVibratorIds) 268 + ", vibrations: " + mVibrations; 269 } 270 } 271 getStatus()272 public Status getStatus() { 273 synchronized (mLock) { 274 return mStatus; 275 } 276 } 277 isStarted()278 public boolean isStarted() { 279 synchronized (mLock) { 280 return mStartTime > 0; 281 } 282 } 283 isEnded()284 public boolean isEnded() { 285 synchronized (mLock) { 286 return mEndTime > 0; 287 } 288 } 289 getVibratorIds()290 public int[] getVibratorIds() { 291 return mVibratorIds; 292 } 293 294 @VisibleForTesting getVibrations()295 public List<DebugInfo> getVibrations() { 296 synchronized (mLock) { 297 return new ArrayList<>(mVibrations); 298 } 299 } 300 getCancellationSignal()301 public ICancellationSignal getCancellationSignal() { 302 return mCancellationSignal; 303 } 304 notifyStart()305 public void notifyStart() { 306 boolean isAlreadyEnded = false; 307 synchronized (mLock) { 308 if (isEnded()) { 309 // Session already ended, skip start callbacks. 310 isAlreadyEnded = true; 311 } else { 312 if (DEBUG) { 313 Slog.d(TAG, "Session started at the HAL"); 314 } 315 mStartTime = System.currentTimeMillis(); 316 mCallback.notifyStarted(this); 317 } 318 } 319 if (isAlreadyEnded) { 320 if (DEBUG) { 321 Slog.d(TAG, "Session already ended after starting the HAL, aborting..."); 322 } 323 mHandler.post(() -> mManagerHooks.endSession(mSessionId, /* shouldAbort= */ true)); 324 } 325 } 326 notifyVibrationAttempt(DebugInfo vibrationDebugInfo)327 public void notifyVibrationAttempt(DebugInfo vibrationDebugInfo) { 328 mVibrations.add(vibrationDebugInfo); 329 } 330 331 @Nullable clearVibrationConductor()332 public VibrationStepConductor clearVibrationConductor() { 333 synchronized (mLock) { 334 VibrationStepConductor conductor = mConductor; 335 if (conductor != null) { 336 mVibrations.add(conductor.getVibration().getDebugInfo()); 337 } 338 mConductor = null; 339 return conductor; 340 } 341 } 342 getDeviceAdapter()343 public DeviceAdapter getDeviceAdapter() { 344 return mDeviceAdapter; 345 } 346 maybeSetVibrationConductor(VibrationStepConductor conductor)347 public boolean maybeSetVibrationConductor(VibrationStepConductor conductor) { 348 synchronized (mLock) { 349 if (mConductor != null) { 350 if (DEBUG) { 351 Slog.d(TAG, "Session still dispatching previous vibration, new vibration " 352 + conductor.getVibration().id + " ignored"); 353 } 354 return false; 355 } 356 mConductor = conductor; 357 return true; 358 } 359 } 360 requestEndSession(Status status, boolean shouldAbort, boolean isVendorRequest)361 private void requestEndSession(Status status, boolean shouldAbort, boolean isVendorRequest) { 362 if (DEBUG) { 363 Slog.d(TAG, "Session end request received with status " + status); 364 } 365 synchronized (mLock) { 366 notifyEndRequestLocked(status, isVendorRequest); 367 if (!isEnded() && isStarted()) { 368 // Trigger session hook even if it was already triggered, in case a second request 369 // is aborting the ongoing/ending session. This might cause it to end right away. 370 // Wait for HAL callback before setting the end status. 371 if (DEBUG) { 372 Slog.d(TAG, "Requesting HAL session end with abort=" + shouldAbort); 373 } 374 mHandler.post(() -> mManagerHooks.endSession(mSessionId, shouldAbort)); 375 } else { 376 // Session not active in the HAL, try to set end status right away. 377 maybeSetStatusToRequestedLocked(); 378 // Use status used to end this session, which might be different from requested. 379 mCallback.notifyFinished(mStatus); 380 } 381 } 382 } 383 384 @GuardedBy("mLock") notifyEndRequestLocked(Status status, boolean isVendorRequest)385 private void notifyEndRequestLocked(Status status, boolean isVendorRequest) { 386 if (mEndStatusRequest != null) { 387 // End already requested, keep first requested status. 388 return; 389 } 390 if (DEBUG) { 391 Slog.d(TAG, "Session end request accepted for status " + status); 392 } 393 mEndStatusRequest = status; 394 mEndedByVendor = isVendorRequest; 395 mCallback.notifyFinishing(); 396 if (mConductor != null) { 397 // Vibration is being dispatched when session end was requested, cancel it. 398 mConductor.notifyCancelled(new Vibration.EndInfo(status), 399 /* immediate= */ status != Status.FINISHED); 400 } 401 } 402 403 @GuardedBy("mLock") maybeSetStatusToRequestedLocked()404 private void maybeSetStatusToRequestedLocked() { 405 if (isEnded()) { 406 // End already set, keep first requested status and time. 407 return; 408 } 409 if (mEndStatusRequest == null) { 410 // No end status was requested, nothing to set. 411 return; 412 } 413 if (DEBUG) { 414 Slog.d(TAG, "Session end request applied for status " + mEndStatusRequest); 415 } 416 mStatus = mEndStatusRequest; 417 mEndTime = System.currentTimeMillis(); 418 mEndUptime = SystemClock.uptimeMillis(); 419 } 420 421 /** 422 * Wrapper class to handle client callbacks asynchronously. 423 * 424 * <p>This class is also responsible for link/unlink to the client process binder death, and for 425 * making sure the callbacks are only triggered once. The conversion between session status and 426 * the API status code is also defined here. 427 */ 428 private static final class VendorCallbackWrapper { 429 private final IVibrationSessionCallback mCallback; 430 private final Handler mHandler; 431 432 private boolean mIsStarted; 433 private boolean mIsFinishing; 434 private boolean mIsFinished; 435 VendorCallbackWrapper(@onNull IVibrationSessionCallback callback, @NonNull Handler handler)436 VendorCallbackWrapper(@NonNull IVibrationSessionCallback callback, 437 @NonNull Handler handler) { 438 mCallback = callback; 439 mHandler = handler; 440 } 441 getBinderToken()442 synchronized IBinder getBinderToken() { 443 return mCallback.asBinder(); 444 } 445 linkToDeath(DeathRecipient recipient)446 synchronized boolean linkToDeath(DeathRecipient recipient) { 447 try { 448 mCallback.asBinder().linkToDeath(recipient, 0); 449 } catch (RemoteException e) { 450 Slog.e(TAG, "Error linking session to token death", e); 451 return false; 452 } 453 return true; 454 } 455 unlinkToDeath(DeathRecipient recipient)456 synchronized void unlinkToDeath(DeathRecipient recipient) { 457 try { 458 mCallback.asBinder().unlinkToDeath(recipient, 0); 459 } catch (NoSuchElementException e) { 460 Slog.wtf(TAG, "Failed to unlink session to token death", e); 461 } 462 } 463 notifyStarted(IVibrationSession session)464 synchronized void notifyStarted(IVibrationSession session) { 465 if (mIsStarted) { 466 return; 467 } 468 mIsStarted = true; 469 mHandler.post(() -> { 470 try { 471 mCallback.onStarted(session); 472 } catch (RemoteException e) { 473 Slog.e(TAG, "Error notifying vendor session started", e); 474 } 475 }); 476 } 477 notifyFinishing()478 synchronized void notifyFinishing() { 479 if (!mIsStarted || mIsFinishing || mIsFinished) { 480 // Ignore if never started or if already finishing or finished. 481 return; 482 } 483 mIsFinishing = true; 484 mHandler.post(() -> { 485 try { 486 mCallback.onFinishing(); 487 } catch (RemoteException e) { 488 Slog.e(TAG, "Error notifying vendor session is finishing", e); 489 } 490 }); 491 } 492 notifyFinished(Status status)493 synchronized void notifyFinished(Status status) { 494 if (mIsFinished) { 495 return; 496 } 497 mIsFinished = true; 498 mHandler.post(() -> { 499 try { 500 mCallback.onFinished(toSessionStatus(status)); 501 } catch (RemoteException e) { 502 Slog.e(TAG, "Error notifying vendor session finished", e); 503 } 504 }); 505 } 506 507 @android.os.vibrator.VendorVibrationSession.Status toSessionStatus(Status status)508 private static int toSessionStatus(Status status) { 509 // Exhaustive switch to cover all possible internal status. 510 return switch (status) { 511 case FINISHED 512 -> android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS; 513 case IGNORED_UNSUPPORTED 514 -> STATUS_UNSUPPORTED; 515 case CANCELLED_BINDER_DIED, CANCELLED_BY_APP_OPS, CANCELLED_BY_USER, 516 CANCELLED_SUPERSEDED, CANCELLED_BY_FOREGROUND_USER, CANCELLED_BY_SCREEN_OFF, 517 CANCELLED_BY_SETTINGS_UPDATE, CANCELLED_BY_UNKNOWN_REASON 518 -> android.os.vibrator.VendorVibrationSession.STATUS_CANCELED; 519 case IGNORED_APP_OPS, IGNORED_BACKGROUND, IGNORED_FOR_EXTERNAL, IGNORED_FOR_ONGOING, 520 IGNORED_FOR_POWER, IGNORED_FOR_SETTINGS, IGNORED_FOR_HIGHER_IMPORTANCE, 521 IGNORED_FOR_RINGER_MODE, IGNORED_FROM_VIRTUAL_DEVICE, IGNORED_SUPERSEDED, 522 IGNORED_MISSING_PERMISSION, IGNORED_ON_WIRELESS_CHARGER 523 -> android.os.vibrator.VendorVibrationSession.STATUS_IGNORED; 524 case UNKNOWN, IGNORED_ERROR_APP_OPS, IGNORED_ERROR_CANCELLING, 525 IGNORED_ERROR_SCHEDULING, IGNORED_ERROR_TOKEN, FORWARDED_TO_INPUT_DEVICES, 526 FINISHED_UNEXPECTED, RUNNING 527 -> android.os.vibrator.VendorVibrationSession.STATUS_UNKNOWN_ERROR; 528 }; 529 } 530 } 531 532 /** 533 * Holds lightweight debug information about the session that could potentially be kept in 534 * memory for a long time for bugreport dumpsys operations. 535 * 536 * Since DebugInfo can be kept in memory for a long time, it shouldn't hold any references to 537 * potentially expensive or resource-linked objects, such as {@link IBinder}. 538 */ 539 static final class DebugInfoImpl implements VibrationSession.DebugInfo { 540 private final Status mStatus; 541 private final CallerInfo mCallerInfo; 542 private final List<DebugInfo> mVibrations; 543 544 private final long mCreateUptime; 545 private final long mCreateTime; 546 private final long mStartTime; 547 private final long mEndTime; 548 private final long mDurationMs; 549 private final boolean mEndedByVendor; 550 DebugInfoImpl(Status status, CallerInfo callerInfo, long createUptime, long createTime, long startTime, long endUptime, long endTime, boolean endedByVendor, List<DebugInfo> vibrations)551 DebugInfoImpl(Status status, CallerInfo callerInfo, long createUptime, long createTime, 552 long startTime, long endUptime, long endTime, boolean endedByVendor, 553 List<DebugInfo> vibrations) { 554 mStatus = status; 555 mCallerInfo = callerInfo; 556 mCreateUptime = createUptime; 557 mCreateTime = createTime; 558 mStartTime = startTime; 559 mEndTime = endTime; 560 mEndedByVendor = endedByVendor; 561 mDurationMs = endUptime > 0 ? endUptime - createUptime : -1; 562 mVibrations = vibrations == null ? new ArrayList<>() : new ArrayList<>(vibrations); 563 } 564 565 @Override getStatus()566 public Status getStatus() { 567 return mStatus; 568 } 569 570 @Override getCreateUptimeMillis()571 public long getCreateUptimeMillis() { 572 return mCreateUptime; 573 } 574 575 @Override getCallerInfo()576 public CallerInfo getCallerInfo() { 577 return mCallerInfo; 578 } 579 580 @Nullable 581 @Override getDumpAggregationKey()582 public Object getDumpAggregationKey() { 583 return null; // No aggregation. 584 } 585 586 @Override logMetrics(VibratorFrameworkStatsLogger statsLogger)587 public void logMetrics(VibratorFrameworkStatsLogger statsLogger) { 588 if (mStartTime > 0) { 589 // Only log sessions that have started in the HAL. 590 statsLogger.logVibrationVendorSessionStarted(mCallerInfo.uid); 591 statsLogger.logVibrationVendorSessionVibrations(mCallerInfo.uid, 592 mVibrations.size()); 593 if (!mEndedByVendor) { 594 statsLogger.logVibrationVendorSessionInterrupted(mCallerInfo.uid); 595 } 596 } 597 for (DebugInfo vibration : mVibrations) { 598 vibration.logMetrics(statsLogger); 599 } 600 } 601 602 @Override dump(ProtoOutputStream proto, long fieldId)603 public void dump(ProtoOutputStream proto, long fieldId) { 604 final long token = proto.start(fieldId); 605 proto.write(VibrationProto.END_TIME, mEndTime); 606 proto.write(VibrationProto.DURATION_MS, mDurationMs); 607 proto.write(VibrationProto.STATUS, mStatus.ordinal()); 608 609 final long attrsToken = proto.start(VibrationProto.ATTRIBUTES); 610 final VibrationAttributes attrs = mCallerInfo.attrs; 611 proto.write(VibrationAttributesProto.USAGE, attrs.getUsage()); 612 proto.write(VibrationAttributesProto.AUDIO_USAGE, attrs.getAudioUsage()); 613 proto.write(VibrationAttributesProto.FLAGS, attrs.getFlags()); 614 proto.end(attrsToken); 615 616 proto.end(token); 617 } 618 619 @Override dump(IndentingPrintWriter pw)620 public void dump(IndentingPrintWriter pw) { 621 pw.println("VibrationSession:"); 622 pw.increaseIndent(); 623 pw.println("status = " + mStatus.name().toLowerCase(Locale.ROOT)); 624 pw.println("durationMs = " + mDurationMs); 625 pw.println("createTime = " + formatTime(mCreateTime, /*includeDate=*/ true)); 626 pw.println("startTime = " + formatTime(mStartTime, /*includeDate=*/ true)); 627 pw.println("endTime = " + (mEndTime == 0 ? null 628 : formatTime(mEndTime, /*includeDate=*/ true))); 629 pw.println("callerInfo = " + mCallerInfo); 630 631 pw.println("vibrations:"); 632 pw.increaseIndent(); 633 for (DebugInfo vibration : mVibrations) { 634 vibration.dump(pw); 635 } 636 pw.decreaseIndent(); 637 638 pw.decreaseIndent(); 639 } 640 641 @Override dumpCompact(IndentingPrintWriter pw)642 public void dumpCompact(IndentingPrintWriter pw) { 643 // Follow pattern from Vibration.DebugInfoImpl for better debugging from dumpsys. 644 String timingsStr = String.format(Locale.ROOT, 645 "%s | %8s | %20s | duration: %5dms | start: %12s | end: %12s", 646 formatTime(mCreateTime, /*includeDate=*/ true), 647 "session", 648 mStatus.name().toLowerCase(Locale.ROOT), 649 mDurationMs, 650 mStartTime == 0 ? "" : formatTime(mStartTime, /*includeDate=*/ false), 651 mEndTime == 0 ? "" : formatTime(mEndTime, /*includeDate=*/ false)); 652 String paramStr = String.format(Locale.ROOT, 653 " | flags: %4s | usage: %s", 654 Long.toBinaryString(mCallerInfo.attrs.getFlags()), 655 mCallerInfo.attrs.usageToString()); 656 // Optional, most vibrations should not be defined via AudioAttributes 657 // so skip them to simplify the logs 658 String audioUsageStr = 659 mCallerInfo.attrs.getOriginalAudioUsage() != AudioAttributes.USAGE_UNKNOWN 660 ? " | audioUsage=" + AudioAttributes.usageToString( 661 mCallerInfo.attrs.getOriginalAudioUsage()) 662 : ""; 663 String callerStr = String.format(Locale.ROOT, 664 " | %s (uid=%d, deviceId=%d) | reason: %s", 665 mCallerInfo.opPkg, mCallerInfo.uid, mCallerInfo.deviceId, mCallerInfo.reason); 666 pw.println(timingsStr + paramStr + audioUsageStr + callerStr); 667 668 pw.increaseIndent(); 669 for (DebugInfo vibration : mVibrations) { 670 vibration.dumpCompact(pw); 671 } 672 pw.decreaseIndent(); 673 } 674 675 @Override toString()676 public String toString() { 677 return "createTime: " + formatTime(mCreateTime, /* includeDate= */ true) 678 + ", startTime: " + formatTime(mStartTime, /* includeDate= */ true) 679 + ", endTime: " + (mEndTime == 0 ? null : formatTime(mEndTime, 680 /* includeDate= */ true)) 681 + ", durationMs: " + mDurationMs 682 + ", status: " + mStatus.name().toLowerCase(Locale.ROOT) 683 + ", callerInfo: " + mCallerInfo 684 + ", vibrations: " + mVibrations; 685 } 686 } 687 } 688