• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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