Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6cd3ed3c authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Fix session start race condition" into main

parents f0d9ba87 c01811df
Loading
Loading
Loading
Loading
+182 −97
Original line number Diff line number Diff line
@@ -51,6 +51,7 @@ import java.util.NoSuchElementException;
final class VendorVibrationSession extends IVibrationSession.Stub
        implements VibrationSession, CancellationSignal.OnCancelListener, IBinder.DeathRecipient {
    private static final String TAG = "VendorVibrationSession";
    private static final boolean DEBUG = false;

    /** Calls into VibratorManager functionality needed for playing an {@link ExternalVibration}. */
    interface VibratorManagerHooks {
@@ -73,8 +74,8 @@ final class VendorVibrationSession extends IVibrationSession.Stub
    private final ICancellationSignal mCancellationSignal = CancellationSignal.createTransport();
    private final int[] mVibratorIds;
    private final long mCreateUptime;
    private final long mCreateTime; // for debugging
    private final IVibrationSessionCallback mCallback;
    private final long mCreateTime;
    private final VendorCallbackWrapper mCallback;
    private final CallerInfo mCallerInfo;
    private final VibratorManagerHooks mManagerHooks;
    private final DeviceAdapter mDeviceAdapter;
@@ -88,11 +89,11 @@ final class VendorVibrationSession extends IVibrationSession.Stub
    @GuardedBy("mLock")
    private boolean mEndedByVendor;
    @GuardedBy("mLock")
    private long mStartTime; // for debugging
    private long mStartTime;
    @GuardedBy("mLock")
    private long mEndUptime;
    @GuardedBy("mLock")
    private long mEndTime; // for debugging
    private long mEndTime;
    @GuardedBy("mLock")
    private VibrationStepConductor mConductor;

@@ -103,7 +104,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub
        mCreateTime = System.currentTimeMillis();
        mVibratorIds = deviceAdapter.getAvailableVibratorIds();
        mHandler = handler;
        mCallback = callback;
        mCallback = new VendorCallbackWrapper(callback, handler);
        mCallerInfo = callerInfo;
        mManagerHooks = managerHooks;
        mDeviceAdapter = deviceAdapter;
@@ -119,7 +120,9 @@ final class VendorVibrationSession extends IVibrationSession.Stub

    @Override
    public void finishSession() {
        if (DEBUG) {
            Slog.d(TAG, "Session finish requested, ending vibration session...");
        }
        // Do not abort session in HAL, wait for ongoing vibration requests to complete.
        // This might take a while to end the session, but it can be aborted by cancelSession.
        requestEndSession(Status.FINISHED, /* shouldAbort= */ false, /* isVendorRequest= */ true);
@@ -127,7 +130,9 @@ final class VendorVibrationSession extends IVibrationSession.Stub

    @Override
    public void cancelSession() {
        if (DEBUG) {
            Slog.d(TAG, "Session cancel requested, aborting vibration session...");
        }
        // Always abort session in HAL while cancelling it.
        // This might be triggered after finishSession was already called.
        requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true,
@@ -156,7 +161,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub

    @Override
    public IBinder getCallerToken() {
        return mCallback.asBinder();
        return mCallback.getBinderToken();
    }

    @Override
@@ -176,36 +181,30 @@ final class VendorVibrationSession extends IVibrationSession.Stub

    @Override
    public void onCancel() {
        if (DEBUG) {
            Slog.d(TAG, "Session cancellation signal received, aborting vibration session...");
        }
        requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true,
                /* isVendorRequest= */ true);
    }

    @Override
    public void binderDied() {
        if (DEBUG) {
            Slog.d(TAG, "Session binder died, aborting vibration session...");
        }
        requestEndSession(Status.CANCELLED_BINDER_DIED, /* shouldAbort= */ true,
                /* isVendorRequest= */ false);
    }

    @Override
    public boolean linkToDeath() {
        try {
            mCallback.asBinder().linkToDeath(this, 0);
        } catch (RemoteException e) {
            Slog.e(TAG, "Error linking session to token death", e);
            return false;
        }
        return true;
        return mCallback.linkToDeath(this);
    }

    @Override
    public void unlinkToDeath() {
        try {
            mCallback.asBinder().unlinkToDeath(this, 0);
        } catch (NoSuchElementException e) {
            Slog.wtf(TAG, "Failed to unlink session to token death", e);
        }
        mCallback.unlinkToDeath(this);
    }

    @Override
@@ -219,26 +218,37 @@ final class VendorVibrationSession extends IVibrationSession.Stub

    @Override
    public void notifyVibratorCallback(int vibratorId, long vibrationId, long stepId) {
        Slog.d(TAG, "Vibration callback received for vibration " + vibrationId + " step " + stepId
                + " on vibrator " + vibratorId + ", ignoring...");
        if (DEBUG) {
            Slog.d(TAG, "Vibration callback received for vibration " + vibrationId
                    + " step " + stepId + " on vibrator " + vibratorId + ", ignoring...");
        }
    }

    @Override
    public void notifySyncedVibratorsCallback(long vibrationId) {
        if (DEBUG) {
            Slog.d(TAG, "Synced vibration callback received for vibration " + vibrationId
                    + ", ignoring...");
        }
    }

    @Override
    public void notifySessionCallback() {
        if (DEBUG) {
            Slog.d(TAG, "Session callback received, ending vibration session...");
        }
        synchronized (mLock) {
            // If end was not requested then the HAL has cancelled the session.
            maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON,
            notifyEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON,
                    /* isVendorRequest= */ false);
            maybeSetStatusToRequestedLocked();
            clearVibrationConductor();
            mHandler.post(() -> mManagerHooks.onSessionReleased(mSessionId));
            final Status endStatus = mStatus;
            mHandler.post(() -> {
                mManagerHooks.onSessionReleased(mSessionId);
                // Only trigger client callback after session is released in the manager.
                mCallback.notifyFinished(endStatus);
            });
        }
    }

@@ -271,7 +281,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub

    public boolean isEnded() {
        synchronized (mLock) {
            return mStatus != Status.RUNNING;
            return mEndTime > 0;
        }
    }

@@ -297,19 +307,17 @@ final class VendorVibrationSession extends IVibrationSession.Stub
                // Session already ended, skip start callbacks.
                isAlreadyEnded = true;
            } else {
                mStartTime = System.currentTimeMillis();
                // Run client callback in separate thread.
                mHandler.post(() -> {
                    try {
                        mCallback.onStarted(this);
                    } catch (RemoteException e) {
                        Slog.e(TAG, "Error notifying vendor session started", e);
                if (DEBUG) {
                    Slog.d(TAG, "Session started at the HAL");
                }
                });
                mStartTime = System.currentTimeMillis();
                mCallback.notifyStarted(this);
            }
        }
        if (isAlreadyEnded) {
            if (DEBUG) {
                Slog.d(TAG, "Session already ended after starting the HAL, aborting...");
            }
            mHandler.post(() -> mManagerHooks.endSession(mSessionId, /* shouldAbort= */ true));
        }
    }
@@ -337,8 +345,10 @@ final class VendorVibrationSession extends IVibrationSession.Stub
    public boolean maybeSetVibrationConductor(VibrationStepConductor conductor) {
        synchronized (mLock) {
            if (mConductor != null) {
                if (DEBUG) {
                    Slog.d(TAG, "Session still dispatching previous vibration, new vibration "
                            + conductor.getVibration().id + " ignored");
                }
                return false;
            }
            mConductor = conductor;
@@ -347,53 +357,45 @@ final class VendorVibrationSession extends IVibrationSession.Stub
    }

    private void requestEndSession(Status status, boolean shouldAbort, boolean isVendorRequest) {
        if (DEBUG) {
            Slog.d(TAG, "Session end request received with status " + status);
        boolean shouldTriggerSessionHook = false;
        }
        synchronized (mLock) {
            maybeSetEndRequestLocked(status, isVendorRequest);
            notifyEndRequestLocked(status, isVendorRequest);
            if (!isEnded() && isStarted()) {
                // Trigger session hook even if it was already triggered, in case a second request
                // is aborting the ongoing/ending session. This might cause it to end right away.
                // Wait for HAL callback before setting the end status.
                shouldTriggerSessionHook = true;
                if (DEBUG) {
                    Slog.d(TAG, "Requesting HAL session end with abort=" + shouldAbort);
                }
                mHandler.post(() ->  mManagerHooks.endSession(mSessionId, shouldAbort));
            } else {
                // Session not active in the HAL, set end status right away.
                // Session not active in the HAL, try to set end status right away.
                maybeSetStatusToRequestedLocked();
                // Use status used to end this session, which might be different from requested.
                mCallback.notifyFinished(mStatus);
            }
        }
        if (shouldTriggerSessionHook) {
            Slog.d(TAG, "Requesting HAL session end with abort=" + shouldAbort);
            mHandler.post(() ->  mManagerHooks.endSession(mSessionId, shouldAbort));
        }
    }

    @GuardedBy("mLock")
    private void maybeSetEndRequestLocked(Status status, boolean isVendorRequest) {
    private void notifyEndRequestLocked(Status status, boolean isVendorRequest) {
        if (mEndStatusRequest != null) {
            // End already requested, keep first requested status and time.
            // End already requested, keep first requested status.
            return;
        }
        if (DEBUG) {
            Slog.d(TAG, "Session end request accepted for status " + status);
        }
        mEndStatusRequest = status;
        mEndedByVendor = isVendorRequest;
        mEndTime = System.currentTimeMillis();
        mEndUptime = SystemClock.uptimeMillis();
        mCallback.notifyFinishing();
        if (mConductor != null) {
            // Vibration is being dispatched when session end was requested, cancel it.
            mConductor.notifyCancelled(new Vibration.EndInfo(status),
                    /* immediate= */ status != Status.FINISHED);
        }
        if (isStarted()) {
            // Only trigger "finishing" callback if session started.
            // Run client callback in separate thread.
            mHandler.post(() -> {
                try {
                    mCallback.onFinishing();
                } catch (RemoteException e) {
                    Slog.e(TAG, "Error notifying vendor session is finishing", e);
                }
            });
        }
    }

    @GuardedBy("mLock")
@@ -406,13 +408,94 @@ final class VendorVibrationSession extends IVibrationSession.Stub
            // No end status was requested, nothing to set.
            return;
        }
        if (DEBUG) {
            Slog.d(TAG, "Session end request applied for status " + mEndStatusRequest);
        }
        mStatus = mEndStatusRequest;
        // Run client callback in separate thread.
        final Status endStatus = mStatus;
        mEndTime = System.currentTimeMillis();
        mEndUptime = SystemClock.uptimeMillis();
    }

    /**
     * Wrapper class to handle client callbacks asynchronously.
     *
     * <p>This class is also responsible for link/unlink to the client process binder death, and for
     * making sure the callbacks are only triggered once. The conversion between session status and
     * the API status code is also defined here.
     */
    private static final class VendorCallbackWrapper {
        private final IVibrationSessionCallback mCallback;
        private final Handler mHandler;

        private boolean mIsStarted;
        private boolean mIsFinishing;
        private boolean mIsFinished;

        VendorCallbackWrapper(@NonNull IVibrationSessionCallback callback,
                @NonNull Handler handler) {
            mCallback = callback;
            mHandler = handler;
        }

        synchronized IBinder getBinderToken() {
            return mCallback.asBinder();
        }

        synchronized boolean linkToDeath(DeathRecipient recipient) {
            try {
                mCallback.asBinder().linkToDeath(recipient, 0);
            } catch (RemoteException e) {
                Slog.e(TAG, "Error linking session to token death", e);
                return false;
            }
            return true;
        }

        synchronized void unlinkToDeath(DeathRecipient recipient) {
            try {
                mCallback.asBinder().unlinkToDeath(recipient, 0);
            } catch (NoSuchElementException e) {
                Slog.wtf(TAG, "Failed to unlink session to token death", e);
            }
        }

        synchronized void notifyStarted(IVibrationSession session) {
            if (mIsStarted) {
                return;
            }
            mIsStarted = true;
            mHandler.post(() -> {
                try {
                mCallback.onFinished(toSessionStatus(endStatus));
                    mCallback.onStarted(session);
                } catch (RemoteException e) {
                    Slog.e(TAG, "Error notifying vendor session started", e);
                }
            });
        }

        synchronized void notifyFinishing() {
            if (!mIsStarted || mIsFinishing || mIsFinished) {
                // Ignore if never started or if already finishing or finished.
                return;
            }
            mIsFinishing = true;
            mHandler.post(() -> {
                try {
                    mCallback.onFinishing();
                } catch (RemoteException e) {
                    Slog.e(TAG, "Error notifying vendor session is finishing", e);
                }
            });
        }

        synchronized void notifyFinished(Status status) {
            if (mIsFinished) {
                return;
            }
            mIsFinished = true;
            mHandler.post(() -> {
                try {
                    mCallback.onFinished(toSessionStatus(status));
                } catch (RemoteException e) {
                    Slog.e(TAG, "Error notifying vendor session finished", e);
                }
@@ -436,11 +519,13 @@ final class VendorVibrationSession extends IVibrationSession.Stub
                     IGNORED_FOR_RINGER_MODE, IGNORED_FROM_VIRTUAL_DEVICE, IGNORED_SUPERSEDED,
                     IGNORED_MISSING_PERMISSION, IGNORED_ON_WIRELESS_CHARGER
                        -> android.os.vibrator.VendorVibrationSession.STATUS_IGNORED;
            case UNKNOWN, IGNORED_ERROR_APP_OPS, IGNORED_ERROR_CANCELLING, IGNORED_ERROR_SCHEDULING,
                 IGNORED_ERROR_TOKEN, FORWARDED_TO_INPUT_DEVICES, FINISHED_UNEXPECTED, RUNNING
                case UNKNOWN, IGNORED_ERROR_APP_OPS, IGNORED_ERROR_CANCELLING,
                     IGNORED_ERROR_SCHEDULING, IGNORED_ERROR_TOKEN, FORWARDED_TO_INPUT_DEVICES,
                     FINISHED_UNEXPECTED, RUNNING
                        -> android.os.vibrator.VendorVibrationSession.STATUS_UNKNOWN_ERROR;
            };
        }
    }

    /**
     * Holds lightweight debug information about the session that could potentially be kept in
@@ -499,7 +584,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub
        @Override
        public void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
            if (mStartTime > 0) {
                // Only log sessions that have started.
                // Only log sessions that have started in the HAL.
                statsLogger.logVibrationVendorSessionStarted(mCallerInfo.uid);
                statsLogger.logVibrationVendorSessionVibrations(mCallerInfo.uid,
                        mVibrations.size());