Merge "Improve error handling for Trusted Hotword" into udc-dev

This commit is contained in:
Peter Li
2023-02-18 08:33:46 +00:00
committed by Android (Google) Code Review
14 changed files with 153 additions and 37 deletions

View File

@@ -13140,7 +13140,8 @@ package android.service.voice {
public static interface HotwordDetector.Callback {
method public void onDetected(@NonNull android.service.voice.AlwaysOnHotwordDetector.EventPayload);
method public void onError();
method @Deprecated public void onError();
method public default void onFailure(@NonNull android.service.voice.DetectorFailure);
method public void onHotwordDetectionServiceInitialized(int);
method public void onHotwordDetectionServiceRestarted();
method public void onRecognitionPaused();
@@ -13201,7 +13202,7 @@ package android.service.voice {
}
public static interface VisualQueryDetector.Callback {
method public void onError();
method public void onFailure(@NonNull android.service.voice.DetectorFailure);
method public void onQueryDetected(@NonNull String);
method public void onQueryFinished();
method public void onQueryRejected();

View File

@@ -231,9 +231,12 @@ abstract class AbstractDetector implements HotwordDetector {
/** Called when the detection fails due to an error. */
@Override
public void onError() {
Slog.v(TAG, "BinderCallback#onError");
Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> mCallback.onError()));
public void onError(DetectorFailure detectorFailure) {
Slog.v(TAG, "BinderCallback#onError detectorFailure: " + detectorFailure);
Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> {
mCallback.onFailure(detectorFailure != null ? detectorFailure
: new UnknownFailure("Error data is null"));
}));
}
@Override

View File

@@ -732,7 +732,13 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
*/
public abstract void onDetected(@NonNull EventPayload eventPayload);
/** {@inheritDoc} */
/**
* {@inheritDoc}
*
* @deprecated Use {@link HotwordDetector.Callback#onError(DetectorFailure)} instead.
*/
@Deprecated
@Override
public abstract void onError();
/** {@inheritDoc} */
@@ -1658,9 +1664,18 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
@Override
public void onError(int status) {
Slog.i(TAG, "onError: " + status);
mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
// This is a workaround before the sound trigger uses the onDetectionFailure method.
Message.obtain(mHandler, MSG_DETECTION_ERROR,
new SoundTriggerFailure(status, "Sound trigger error")).sendToTarget();
}
@Override
public void onDetectionFailure(DetectorFailure detectorFailure) {
Slog.v(TAG, "onDetectionFailure detectorFailure: " + detectorFailure);
Message.obtain(mHandler, MSG_DETECTION_ERROR,
detectorFailure != null ? detectorFailure
: new UnknownFailure("Error data is null")).sendToTarget();
}
@Override
public void onRecognitionPaused() {
Slog.i(TAG, "onRecognitionPaused");
@@ -1716,7 +1731,7 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
mExternalCallback.onDetected((EventPayload) message.obj);
break;
case MSG_DETECTION_ERROR:
mExternalCallback.onError();
mExternalCallback.onFailure((DetectorFailure) msg.obj);
break;
case MSG_DETECTION_PAUSE:
mExternalCallback.onRecognitionPaused();

View File

@@ -231,9 +231,28 @@ public interface HotwordDetector {
/**
* Called when the detection fails due to an error.
*
* @deprecated On Android 14 and above, implement {@link #onFailure(DetectorFailure)}
* instead.
*/
@Deprecated
void onError();
/**
* Called when the detection fails due to an error, the subclasses of
* {@link DetectorFailure} will be reported to the detector.
*
* @see android.service.voice.HotwordDetectionServiceFailure
* @see android.service.voice.SoundTriggerFailure
* @see android.service.voice.UnknownFailure
* @see android.service.voice.VisualQueryDetectionServiceFailure
*
* @param detectorFailure It provides the error code, error message and suggested action.
*/
default void onFailure(@NonNull DetectorFailure detectorFailure) {
onError();
}
/**
* Called when the recognition is paused temporarily for some reason.
* This is an informational callback, and the clients shouldn't be doing anything here

View File

@@ -17,6 +17,7 @@
package android.service.voice;
import android.media.AudioFormat;
import android.service.voice.DetectorFailure;
import android.service.voice.HotwordDetectedResult;
import android.service.voice.HotwordRejectedResult;
@@ -38,7 +39,7 @@ oneway interface IMicrophoneHotwordDetectionVoiceInteractionCallback {
/**
* Called when the detection fails due to an error.
*/
void onError();
void onError(in DetectorFailure detectorFailure);
/**
* Called when the detected result was not detected.

View File

@@ -16,7 +16,7 @@
package android.service.voice;
import android.media.AudioFormat;
import android.service.voice.DetectorFailure;
/**
* Callback for returning the detected result from the VisualQueryDetectionService.
@@ -43,6 +43,5 @@ oneway interface IVisualQueryDetectionVoiceInteractionCallback {
/**
* Called when the detection fails due to an error.
*/
void onError();
void onDetectionFailure(in DetectorFailure detectorFailure);
}

View File

@@ -169,9 +169,12 @@ class SoftwareHotwordDetector extends AbstractDetector {
/** Called when the detection fails due to an error. */
@Override
public void onError() {
Slog.v(TAG, "BinderCallback#onError");
Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> mCallback.onError()));
public void onError(DetectorFailure detectorFailure) {
Slog.v(TAG, "BinderCallback#onError detectorFailure: " + detectorFailure);
Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> {
mCallback.onFailure(detectorFailure != null ? detectorFailure
: new UnknownFailure("Error data is null"));
}));
}
@Override
@@ -222,6 +225,16 @@ class SoftwareHotwordDetector extends AbstractDetector {
if (DEBUG) {
Slog.i(TAG, "Ignored #onError (" + status + ") event");
}
// TODO: Check if we still need to implement this method with DetectorFailure mechanism.
}
@Override
public void onDetectionFailure(DetectorFailure detectorFailure) throws RemoteException {
Slog.v(TAG, "onDetectionFailure detectorFailure: " + detectorFailure);
Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> {
mCallback.onFailure(detectorFailure != null ? detectorFailure
: new UnknownFailure("Error data is null"));
}));
}
@Override

View File

@@ -216,8 +216,7 @@ public class VisualQueryDetector {
/**
* Called when the detection fails due to an error.
*/
//TODO(b/265390855): Replace this callback with the new onError(DetectorError) design.
void onError();
void onFailure(@NonNull DetectorFailure detectorFailure);
}
private class VisualQueryDetectorInitializationDelegate extends AbstractDetector {
@@ -294,12 +293,11 @@ public class VisualQueryDetector {
/** Called when the detection fails due to an error. */
@Override
public void onError() {
Slog.v(TAG, "BinderCallback#onError");
public void onDetectionFailure(DetectorFailure detectorFailure) {
Slog.v(TAG, "BinderCallback#onDetectionFailure");
Binder.withCleanCallingIdentity(() -> mExecutor.execute(
() -> mCallback.onError()));
() -> mCallback.onFailure(detectorFailure)));
}
}
@@ -373,5 +371,9 @@ public class VisualQueryDetector {
Slog.v(TAG, "Initialization Error: (" + status + ")");
// Do nothing
}
@Override
public void onDetectionFailure(DetectorFailure detectorFailure) throws RemoteException {
}
}
}

View File

@@ -17,6 +17,7 @@
package com.android.internal.app;
import android.hardware.soundtrigger.SoundTrigger;
import android.service.voice.DetectorFailure;
import android.service.voice.HotwordDetectedResult;
import android.service.voice.HotwordRejectedResult;
@@ -61,6 +62,13 @@ oneway interface IHotwordRecognitionStatusCallback {
*/
void onError(int status);
/**
* Called when the detection fails due to an error.
*
* @param detectorFailure It provides the error code, error message and suggested action.
*/
void onDetectionFailure(in DetectorFailure detectorFailure);
/**
* Called when the recognition is paused temporarily for some reason.
*/

View File

@@ -70,6 +70,7 @@ import android.os.RemoteException;
import android.os.SharedMemory;
import android.service.voice.HotwordDetectedResult;
import android.service.voice.HotwordDetectionService;
import android.service.voice.HotwordDetectionServiceFailure;
import android.service.voice.HotwordDetector;
import android.service.voice.HotwordRejectedResult;
import android.service.voice.IDspHotwordDetectionCallback;
@@ -122,10 +123,16 @@ abstract class DetectorSession {
"Providing hotword detection result to VoiceInteractionService";
// The error codes are used for onError callback
static final int HOTWORD_DETECTION_SERVICE_DIED = -1;
static final int CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION = -2;
static final int CALLBACK_DETECT_TIMEOUT = -3;
static final int CALLBACK_ONDETECTED_STREAM_COPY_ERROR = -4;
static final int HOTWORD_DETECTION_SERVICE_DIED =
HotwordDetectionServiceFailure.ERROR_CODE_BINDING_DIED;
static final int CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION =
HotwordDetectionServiceFailure.ERROR_CODE_ON_DETECTED_SECURITY_EXCEPTION;
static final int CALLBACK_DETECT_TIMEOUT =
HotwordDetectionServiceFailure.ERROR_CODE_DETECT_TIMEOUT;
static final int CALLBACK_ONDETECTED_STREAM_COPY_ERROR =
HotwordDetectionServiceFailure.ERROR_CODE_ON_DETECTED_STREAM_COPY_FAILURE;
static final int CALLBACK_COPY_AUDIO_DATA_FAILURE =
HotwordDetectionServiceFailure.ERROR_CODE_COPY_AUDIO_DATA_FAILURE;
// TODO: These constants need to be refined.
private static final long MAX_UPDATE_TIMEOUT_MILLIS = 30000;
@@ -426,7 +433,9 @@ abstract class DetectorSession {
Slog.w(TAG, "Failed supplying audio data to validator", e);
try {
callback.onError();
callback.onError(
new HotwordDetectionServiceFailure(CALLBACK_COPY_AUDIO_DATA_FAILURE,
"Copy audio data failure for external source detection."));
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to report onError status: " + ex);
if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
@@ -505,7 +514,10 @@ abstract class DetectorSession {
getDetectorType(),
EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION,
mVoiceInteractionServiceUid);
callback.onError();
callback.onError(new HotwordDetectionServiceFailure(
CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION,
"Security exception occurs in #onDetected"
+ " method."));
return;
}
HotwordDetectedResult newResult;
@@ -514,7 +526,9 @@ abstract class DetectorSession {
.startCopyingAudioStreams(triggerResult);
} catch (IOException e) {
// TODO: Write event
callback.onError();
callback.onError(new HotwordDetectionServiceFailure(
CALLBACK_ONDETECTED_STREAM_COPY_ERROR,
"Copy audio stream failure."));
return;
}
callback.onDetected(newResult, /* audioFormat= */ null,
@@ -569,9 +583,11 @@ abstract class DetectorSession {
mRemoteDetectionService = remoteDetectionService;
}
void reportErrorLocked(int status) {
void reportErrorLocked(int errorCode, @NonNull String errorMessage) {
try {
mCallback.onError(status);
// TODO: Use instanceof(this) to get different detector to set the right error source.
mCallback.onDetectionFailure(
new HotwordDetectionServiceFailure(errorCode, errorMessage));
} catch (RemoteException e) {
Slog.w(TAG, "Failed to report onError status: " + e);
if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {

View File

@@ -34,6 +34,7 @@ import android.os.RemoteException;
import android.os.SharedMemory;
import android.service.voice.HotwordDetectedResult;
import android.service.voice.HotwordDetectionService;
import android.service.voice.HotwordDetectionServiceFailure;
import android.service.voice.HotwordDetector;
import android.service.voice.HotwordRejectedResult;
import android.service.voice.IDspHotwordDetectionCallback;
@@ -130,7 +131,9 @@ final class DspTrustedHotwordDetectorSession extends DetectorSession {
HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION,
mVoiceInteractionServiceUid);
externalCallback.onError(CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION);
externalCallback.onDetectionFailure(new HotwordDetectionServiceFailure(
CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION,
"Security exception occurs in #onDetected method."));
return;
}
saveProximityValueToBundle(result);
@@ -138,7 +141,9 @@ final class DspTrustedHotwordDetectorSession extends DetectorSession {
try {
newResult = mHotwordAudioStreamCopier.startCopyingAudioStreams(result);
} catch (IOException e) {
externalCallback.onError(CALLBACK_ONDETECTED_STREAM_COPY_ERROR);
externalCallback.onDetectionFailure(new HotwordDetectionServiceFailure(
CALLBACK_ONDETECTED_STREAM_COPY_ERROR,
"Copy audio stream failure."));
return;
}
externalCallback.onKeyphraseDetected(recognitionEvent, newResult);
@@ -201,7 +206,9 @@ final class DspTrustedHotwordDetectorSession extends DetectorSession {
HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_TIMEOUT,
mVoiceInteractionServiceUid);
try {
externalCallback.onError(CALLBACK_DETECT_TIMEOUT);
externalCallback.onDetectionFailure(
new HotwordDetectionServiceFailure(CALLBACK_DETECT_TIMEOUT,
"Timeout to response to the detection result."));
} catch (RemoteException e) {
Slog.w(TAG, "Failed to report onError status: ", e);
HotwordMetricsLogger.writeDetectorEvent(

View File

@@ -690,8 +690,8 @@ final class HotwordDetectionConnection {
//TODO(b265535257): report error to either service only.
synchronized (HotwordDetectionConnection.this.mLock) {
runForEachDetectorSessionLocked((session) -> {
session.reportErrorLocked(
DetectorSession.HOTWORD_DETECTION_SERVICE_DIED);
session.reportErrorLocked(DetectorSession.HOTWORD_DETECTION_SERVICE_DIED,
"Detection service is dead.");
});
}
// Can improve to log exit reason if needed

View File

@@ -33,6 +33,7 @@ import android.os.RemoteException;
import android.os.SharedMemory;
import android.service.voice.HotwordDetectedResult;
import android.service.voice.HotwordDetectionService;
import android.service.voice.HotwordDetectionServiceFailure;
import android.service.voice.HotwordDetector;
import android.service.voice.HotwordRejectedResult;
import android.service.voice.IDspHotwordDetectionCallback;
@@ -121,7 +122,9 @@ final class SoftwareTrustedHotwordDetectorSession extends DetectorSession {
HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION,
mVoiceInteractionServiceUid);
mSoftwareCallback.onError();
mSoftwareCallback.onError(new HotwordDetectionServiceFailure(
CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION,
"Security exception occurs in #onDetected method."));
return;
}
saveProximityValueToBundle(result);
@@ -130,7 +133,9 @@ final class SoftwareTrustedHotwordDetectorSession extends DetectorSession {
newResult = mHotwordAudioStreamCopier.startCopyingAudioStreams(result);
} catch (IOException e) {
// TODO: Write event
mSoftwareCallback.onError();
mSoftwareCallback.onError(new HotwordDetectionServiceFailure(
CALLBACK_ONDETECTED_STREAM_COPY_ERROR,
"Copy audio stream failure."));
return;
}
mSoftwareCallback.onDetected(newResult, null, null);

View File

@@ -16,6 +16,9 @@
package com.android.server.voiceinteraction;
import static android.service.voice.VisualQueryDetectionServiceFailure.ERROR_CODE_ILLEGAL_ATTENTION_STATE;
import static android.service.voice.VisualQueryDetectionServiceFailure.ERROR_CODE_ILLEGAL_STREAMING_STATE;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -30,6 +33,7 @@ import android.service.voice.IDetectorSessionVisualQueryDetectionCallback;
import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
import android.service.voice.ISandboxedDetectionService;
import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
import android.service.voice.VisualQueryDetectionServiceFailure;
import android.util.Slog;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
@@ -102,6 +106,13 @@ final class VisualQueryDetectorSession extends DetectorSession {
mAttentionListener.onAttentionGained();
} catch (RemoteException e) {
Slog.e(TAG, "Error delivering attention gained event.", e);
try {
callback.onDetectionFailure(new VisualQueryDetectionServiceFailure(
ERROR_CODE_ILLEGAL_ATTENTION_STATE,
"Attention listener failed to switch to GAINED state."));
} catch (RemoteException ex) {
Slog.v(TAG, "Fail to call onDetectionFailure");
}
return;
}
}
@@ -117,6 +128,13 @@ final class VisualQueryDetectorSession extends DetectorSession {
mAttentionListener.onAttentionLost();
} catch (RemoteException e) {
Slog.e(TAG, "Error delivering attention lost event.", e);
try {
callback.onDetectionFailure(new VisualQueryDetectionServiceFailure(
ERROR_CODE_ILLEGAL_ATTENTION_STATE,
"Attention listener failed to switch to LOST state."));
} catch (RemoteException ex) {
Slog.v(TAG, "Fail to call onDetectionFailure");
}
return;
}
}
@@ -127,6 +145,9 @@ final class VisualQueryDetectorSession extends DetectorSession {
Slog.v(TAG, "BinderCallback#onQueryDetected");
if (!mEgressingData) {
Slog.v(TAG, "Query should not be egressed within the unattention state.");
callback.onDetectionFailure(new VisualQueryDetectionServiceFailure(
ERROR_CODE_ILLEGAL_STREAMING_STATE,
"Cannot stream queries without attention signals."));
return;
}
mQueryStreaming = true;
@@ -140,6 +161,9 @@ final class VisualQueryDetectorSession extends DetectorSession {
if (!mQueryStreaming) {
Slog.v(TAG, "Query streaming state signal FINISHED is block since there is"
+ " no active query being streamed.");
callback.onDetectionFailure(new VisualQueryDetectionServiceFailure(
ERROR_CODE_ILLEGAL_STREAMING_STATE,
"Cannot send FINISHED signal with no query streamed."));
return;
}
callback.onQueryFinished();
@@ -152,6 +176,9 @@ final class VisualQueryDetectorSession extends DetectorSession {
if (!mQueryStreaming) {
Slog.v(TAG, "Query streaming state signal REJECTED is block since there is"
+ " no active query being streamed.");
callback.onDetectionFailure(new VisualQueryDetectionServiceFailure(
ERROR_CODE_ILLEGAL_STREAMING_STATE,
"Cannot send REJECTED signal with no query streamed."));
return;
}
callback.onQueryRejected();