From f9835510baa0271e14fa220e7b0a5cea1fb85a06 Mon Sep 17 00:00:00 2001 From: charleschen Date: Thu, 14 Dec 2023 02:08:03 +0000 Subject: [PATCH] Fix race condition for VQDS functionalities Lock are missing for thread safety on major VQDS functions. These methods can be invoked consecutively and race condition can happen. We need to add a lock to prevent it from happening. Bug: 295390470 Test: atest CtsVoiceInteractionTestCases Flag: N/A Change-Id: I99c43deb024f2d5fee5c1ac12e549966f949dc1e --- .../service/voice/VisualQueryDetector.java | 90 ++++++++---- .../VisualQueryDetectorSession.java | 134 ++++++++++-------- 2 files changed, 133 insertions(+), 91 deletions(-) diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java index b7d97057a08b..adc54f5b5a8c 100644 --- a/core/java/android/service/voice/VisualQueryDetector.java +++ b/core/java/android/service/voice/VisualQueryDetector.java @@ -94,7 +94,9 @@ public class VisualQueryDetector { */ public void updateState(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) { - mInitializationDelegate.updateState(options, sharedMemory); + synchronized (mInitializationDelegate.getLock()) { + mInitializationDelegate.updateState(options, sharedMemory); + } } @@ -116,18 +118,21 @@ public class VisualQueryDetector { if (DEBUG) { Slog.i(TAG, "#startRecognition"); } - // check if the detector is active with the initialization delegate - mInitializationDelegate.startRecognition(); + synchronized (mInitializationDelegate.getLock()) { + // check if the detector is active with the initialization delegate + mInitializationDelegate.startRecognition(); - try { - mManagerService.startPerceiving(new BinderCallback(mExecutor, mCallback)); - } catch (SecurityException e) { - Slog.e(TAG, "startRecognition failed: " + e); - return false; - } catch (RemoteException e) { - e.rethrowFromSystemServer(); + try { + mManagerService.startPerceiving(new BinderCallback( + mExecutor, mCallback, mInitializationDelegate.getLock())); + } catch (SecurityException e) { + Slog.e(TAG, "startRecognition failed: " + e); + return false; + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return true; } - return true; } /** @@ -140,15 +145,17 @@ public class VisualQueryDetector { if (DEBUG) { Slog.i(TAG, "#stopRecognition"); } - // check if the detector is active with the initialization delegate - mInitializationDelegate.startRecognition(); + synchronized (mInitializationDelegate.getLock()) { + // check if the detector is active with the initialization delegate + mInitializationDelegate.stopRecognition(); - try { - mManagerService.stopPerceiving(); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); + try { + mManagerService.stopPerceiving(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return true; } - return true; } /** @@ -160,12 +167,16 @@ public class VisualQueryDetector { if (DEBUG) { Slog.i(TAG, "#destroy"); } - mInitializationDelegate.destroy(); + synchronized (mInitializationDelegate.getLock()) { + mInitializationDelegate.destroy(); + } } /** @hide */ public void dump(String prefix, PrintWriter pw) { - // TODO: implement this + synchronized (mInitializationDelegate.getLock()) { + mInitializationDelegate.dump(prefix, pw); + } } /** @hide */ @@ -175,7 +186,9 @@ public class VisualQueryDetector { /** @hide */ void registerOnDestroyListener(Consumer onDestroyListener) { - mInitializationDelegate.registerOnDestroyListener(onDestroyListener); + synchronized (mInitializationDelegate.getLock()) { + mInitializationDelegate.registerOnDestroyListener(onDestroyListener); + } } /** @@ -282,6 +295,15 @@ public class VisualQueryDetector { public boolean isUsingSandboxedDetectionService() { return true; } + + @Override + public void dump(String prefix, PrintWriter pw) { + // No-op + } + + private Object getLock() { + return mLock; + } } private static class BinderCallback @@ -289,31 +311,43 @@ public class VisualQueryDetector { private final Executor mExecutor; private final VisualQueryDetector.Callback mCallback; - BinderCallback(Executor executor, VisualQueryDetector.Callback callback) { + private final Object mLock; + + BinderCallback(Executor executor, VisualQueryDetector.Callback callback, Object lock) { this.mExecutor = executor; this.mCallback = callback; + this.mLock = lock; } /** Called when the detected result is valid. */ @Override public void onQueryDetected(@NonNull String partialQuery) { Slog.v(TAG, "BinderCallback#onQueryDetected"); - Binder.withCleanCallingIdentity(() -> mExecutor.execute( - () -> mCallback.onQueryDetected(partialQuery))); + Binder.withCleanCallingIdentity(() -> { + synchronized (mLock) { + mCallback.onQueryDetected(partialQuery); + } + }); } @Override public void onQueryFinished() { Slog.v(TAG, "BinderCallback#onQueryFinished"); - Binder.withCleanCallingIdentity(() -> mExecutor.execute( - () -> mCallback.onQueryFinished())); + Binder.withCleanCallingIdentity(() -> { + synchronized (mLock) { + mCallback.onQueryFinished(); + } + }); } @Override public void onQueryRejected() { Slog.v(TAG, "BinderCallback#onQueryRejected"); - Binder.withCleanCallingIdentity(() -> mExecutor.execute( - () -> mCallback.onQueryRejected())); + Binder.withCleanCallingIdentity(() -> { + synchronized (mLock) { + mCallback.onQueryRejected(); + } + }); } /** Called when the detection fails due to an error. */ diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java index 4720d279a676..f9fa9b7a9524 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java @@ -106,96 +106,104 @@ final class VisualQueryDetectorSession extends DetectorSession { @Override public void onAttentionGained() { Slog.v(TAG, "BinderCallback#onAttentionGained"); - mEgressingData = true; - if (mAttentionListener == null) { - return; - } - try { - mAttentionListener.onAttentionGained(); - } catch (RemoteException e) { - Slog.e(TAG, "Error delivering attention gained event.", e); - try { - callback.onVisualQueryDetectionServiceFailure( - new VisualQueryDetectionServiceFailure( - ERROR_CODE_ILLEGAL_ATTENTION_STATE, - "Attention listener failed to switch to GAINED state.")); - } catch (RemoteException ex) { - Slog.v(TAG, "Fail to call onVisualQueryDetectionServiceFailure"); + synchronized (mLock) { + mEgressingData = true; + if (mAttentionListener == null) { + return; + } + try { + mAttentionListener.onAttentionGained(); + } catch (RemoteException e) { + Slog.e(TAG, "Error delivering attention gained event.", e); + try { + callback.onVisualQueryDetectionServiceFailure( + new VisualQueryDetectionServiceFailure( + ERROR_CODE_ILLEGAL_ATTENTION_STATE, + "Attention listener fails to switch to GAINED state.")); + } catch (RemoteException ex) { + Slog.v(TAG, "Fail to call onVisualQueryDetectionServiceFailure"); + } } - return; } } @Override public void onAttentionLost() { Slog.v(TAG, "BinderCallback#onAttentionLost"); - mEgressingData = false; - if (mAttentionListener == null) { - return; - } - try { - mAttentionListener.onAttentionLost(); - } catch (RemoteException e) { - Slog.e(TAG, "Error delivering attention lost event.", e); - try { - callback.onVisualQueryDetectionServiceFailure( - new VisualQueryDetectionServiceFailure( - ERROR_CODE_ILLEGAL_ATTENTION_STATE, - "Attention listener failed to switch to LOST state.")); - } catch (RemoteException ex) { - Slog.v(TAG, "Fail to call onVisualQueryDetectionServiceFailure"); + synchronized (mLock) { + mEgressingData = false; + if (mAttentionListener == null) { + return; + } + try { + mAttentionListener.onAttentionLost(); + } catch (RemoteException e) { + Slog.e(TAG, "Error delivering attention lost event.", e); + try { + callback.onVisualQueryDetectionServiceFailure( + new VisualQueryDetectionServiceFailure( + ERROR_CODE_ILLEGAL_ATTENTION_STATE, + "Attention listener fails to switch to LOST state.")); + } catch (RemoteException ex) { + Slog.v(TAG, "Fail to call onVisualQueryDetectionServiceFailure"); + } } - return; } } @Override public void onQueryDetected(@NonNull String partialQuery) throws RemoteException { - Objects.requireNonNull(partialQuery); Slog.v(TAG, "BinderCallback#onQueryDetected"); - if (!mEgressingData) { - Slog.v(TAG, "Query should not be egressed within the unattention state."); - callback.onVisualQueryDetectionServiceFailure( - new VisualQueryDetectionServiceFailure( - ERROR_CODE_ILLEGAL_STREAMING_STATE, - "Cannot stream queries without attention signals.")); - return; + synchronized (mLock) { + Objects.requireNonNull(partialQuery); + if (!mEgressingData) { + Slog.v(TAG, "Query should not be egressed within the unattention state."); + callback.onVisualQueryDetectionServiceFailure( + new VisualQueryDetectionServiceFailure( + ERROR_CODE_ILLEGAL_STREAMING_STATE, + "Cannot stream queries without attention signals.")); + return; + } + mQueryStreaming = true; + callback.onQueryDetected(partialQuery); + Slog.i(TAG, "Egressed from visual query detection process."); } - mQueryStreaming = true; - callback.onQueryDetected(partialQuery); - Slog.i(TAG, "Egressed from visual query detection process."); } @Override public void onQueryFinished() throws RemoteException { Slog.v(TAG, "BinderCallback#onQueryFinished"); - if (!mQueryStreaming) { - Slog.v(TAG, "Query streaming state signal FINISHED is block since there is" - + " no active query being streamed."); - callback.onVisualQueryDetectionServiceFailure( - new VisualQueryDetectionServiceFailure( - ERROR_CODE_ILLEGAL_STREAMING_STATE, - "Cannot send FINISHED signal with no query streamed.")); - return; + synchronized (mLock) { + if (!mQueryStreaming) { + Slog.v(TAG, "Query streaming state signal FINISHED is block since there is" + + " no active query being streamed."); + callback.onVisualQueryDetectionServiceFailure( + new VisualQueryDetectionServiceFailure( + ERROR_CODE_ILLEGAL_STREAMING_STATE, + "Cannot send FINISHED signal with no query streamed.")); + return; + } + callback.onQueryFinished(); + mQueryStreaming = false; } - callback.onQueryFinished(); - mQueryStreaming = false; } @Override public void onQueryRejected() throws RemoteException { Slog.v(TAG, "BinderCallback#onQueryRejected"); - if (!mQueryStreaming) { - Slog.v(TAG, "Query streaming state signal REJECTED is block since there is" - + " no active query being streamed."); - callback.onVisualQueryDetectionServiceFailure( - new VisualQueryDetectionServiceFailure( - ERROR_CODE_ILLEGAL_STREAMING_STATE, - "Cannot send REJECTED signal with no query streamed.")); - return; + synchronized (mLock) { + if (!mQueryStreaming) { + Slog.v(TAG, "Query streaming state signal REJECTED is block since there is" + + " no active query being streamed."); + callback.onVisualQueryDetectionServiceFailure( + new VisualQueryDetectionServiceFailure( + ERROR_CODE_ILLEGAL_STREAMING_STATE, + "Cannot send REJECTED signal with no query streamed.")); + return; + } + callback.onQueryRejected(); + mQueryStreaming = false; } - callback.onQueryRejected(); - mQueryStreaming = false; } }; return mRemoteDetectionService.run(