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

Commit 78c84e15 authored by lpeter's avatar lpeter
Browse files

Follow HotwordDetector / DetectorFailure API feedback

By API Council feedback, there are two suggestions instead of
using instanceof.
(1) Use multiple methods in the callback.
(2) Use one big failure class which has a field for the failure type.

For suggestion 1, the assistant application can easy know the failure
comes from which module and take its next action in the different
callback method. Currently there are three kinds of detectors, I list
what callbacks will be used by different detectors.
(1)AlwaysOnHotwordDetector:
    onFailure(HotwordDetectionServiceFailure)
    onFailure(SoundTriggerFailure)
    onUnknownFailure(String)
(2)SoftwareHotwordDetector
    onFailure(HotwordDetectionServiceFailure)
    onUnknownFailure(String)
(3)VisualQueryDetector
    onFailure(VisualQueryDetectionServiceFailure)
    onUnknownFailure(String)

For suggestion 2, it seems that the assistant application still needs
to take the action first to know the failure comes from which module
in this one callback method.

We think that the suggestion 1 is better than suggestion 2.

Bug: 271107031
Test: atest CtsVoiceInteractionTestCases
Change-Id: I57fa93d5a0c2919ce762688f1d97766e531ccfb3
Merged-In: I57fa93d5a0c2919ce762688f1d97766e531ccfb3
parent 52cef828
Loading
Loading
Loading
Loading
+23 −21
Original line number Diff line number Diff line
@@ -13033,6 +13033,7 @@ package android.service.voice {
  public abstract static class AlwaysOnHotwordDetector.Callback implements android.service.voice.HotwordDetector.Callback {
    ctor public AlwaysOnHotwordDetector.Callback();
    method public abstract void onAvailabilityChanged(int);
    method public void onFailure(@NonNull android.service.voice.SoundTriggerFailure);
    method public void onHotwordDetectionServiceInitialized(int);
    method public void onHotwordDetectionServiceRestarted();
    method public void onRejected(@NonNull android.service.voice.HotwordRejectedResult);
@@ -13056,17 +13057,12 @@ package android.service.voice {
    method public int getStart();
  }
  public abstract class DetectorFailure implements android.os.Parcelable {
    method public int describeContents();
    method @NonNull public String getErrorMessage();
    method public abstract int getSuggestedAction();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.DetectorFailure> CREATOR;
    field public static final int SUGGESTED_ACTION_DISABLE_DETECTION = 2; // 0x2
    field public static final int SUGGESTED_ACTION_NONE = 1; // 0x1
    field public static final int SUGGESTED_ACTION_RECREATE_DETECTOR = 3; // 0x3
    field public static final int SUGGESTED_ACTION_RESTART_RECOGNITION = 4; // 0x4
    field public static final int SUGGESTED_ACTION_UNKNOWN = 0; // 0x0
  public final class FailureSuggestedAction {
    field public static final int DISABLE_DETECTION = 2; // 0x2
    field public static final int NONE = 1; // 0x1
    field public static final int RECREATE_DETECTOR = 3; // 0x3
    field public static final int RESTART_RECOGNITION = 4; // 0x4
    field public static final int UNKNOWN = 0; // 0x0
  }
  public final class HotwordAudioStream implements android.os.Parcelable {
@@ -13163,9 +13159,12 @@ package android.service.voice {
    method public void onRejected(@NonNull android.service.voice.HotwordRejectedResult);
  }
  public final class HotwordDetectionServiceFailure extends android.service.voice.DetectorFailure {
  public final class HotwordDetectionServiceFailure implements android.os.Parcelable {
    method public int describeContents();
    method public int getErrorCode();
    method @NonNull public String getErrorMessage();
    method public int getSuggestedAction();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.HotwordDetectionServiceFailure> CREATOR;
    field public static final int ERROR_CODE_BINDING_DIED = 2; // 0x2
    field public static final int ERROR_CODE_BIND_FAILURE = 1; // 0x1
@@ -13188,12 +13187,13 @@ package android.service.voice {
  public static interface HotwordDetector.Callback {
    method public void onDetected(@NonNull android.service.voice.AlwaysOnHotwordDetector.EventPayload);
    method @Deprecated public void onError();
    method public default void onFailure(@NonNull android.service.voice.DetectorFailure);
    method public default void onFailure(@NonNull android.service.voice.HotwordDetectionServiceFailure);
    method public void onHotwordDetectionServiceInitialized(int);
    method public void onHotwordDetectionServiceRestarted();
    method public void onRecognitionPaused();
    method public void onRecognitionResumed();
    method public void onRejected(@NonNull android.service.voice.HotwordRejectedResult);
    method public default void onUnknownFailure(@NonNull String);
  }
  public final class HotwordRejectedResult implements android.os.Parcelable {
@@ -13220,9 +13220,12 @@ package android.service.voice {
    field public static final int INITIALIZATION_STATUS_UNKNOWN = 100; // 0x64
  }
  public final class SoundTriggerFailure extends android.service.voice.DetectorFailure {
  public final class SoundTriggerFailure implements android.os.Parcelable {
    method public int describeContents();
    method public int getErrorCode();
    method @NonNull public String getErrorMessage();
    method public int getSuggestedAction();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.SoundTriggerFailure> CREATOR;
    field public static final int ERROR_CODE_MODULE_DIED = 1; // 0x1
    field public static final int ERROR_CODE_RECOGNITION_RESUME_FAILED = 2; // 0x2
@@ -13230,11 +13233,6 @@ package android.service.voice {
    field public static final int ERROR_CODE_UNKNOWN = 0; // 0x0
  }
  public final class UnknownFailure extends android.service.voice.DetectorFailure {
    method public int getSuggestedAction();
    field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.UnknownFailure> CREATOR;
  }
  public abstract class VisualQueryDetectionService extends android.app.Service implements android.service.voice.SandboxedDetectionInitializer {
    ctor public VisualQueryDetectionService();
    method public final void finishQuery() throws java.lang.IllegalStateException;
@@ -13249,9 +13247,12 @@ package android.service.voice {
    field public static final String SERVICE_INTERFACE = "android.service.voice.VisualQueryDetectionService";
  }
  public final class VisualQueryDetectionServiceFailure extends android.service.voice.DetectorFailure {
  public final class VisualQueryDetectionServiceFailure implements android.os.Parcelable {
    method public int describeContents();
    method public int getErrorCode();
    method @NonNull public String getErrorMessage();
    method public int getSuggestedAction();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.VisualQueryDetectionServiceFailure> CREATOR;
    field public static final int ERROR_CODE_BINDING_DIED = 2; // 0x2
    field public static final int ERROR_CODE_BIND_FAILURE = 1; // 0x1
@@ -13269,10 +13270,11 @@ package android.service.voice {
  }
  public static interface VisualQueryDetector.Callback {
    method public void onFailure(@NonNull android.service.voice.DetectorFailure);
    method public void onFailure(@NonNull android.service.voice.VisualQueryDetectionServiceFailure);
    method public void onQueryDetected(@NonNull String);
    method public void onQueryFinished();
    method public void onQueryRejected();
    method public void onUnknownFailure(@NonNull String);
    method public void onVisualQueryDetectionServiceInitialized(int);
    method public void onVisualQueryDetectionServiceRestarted();
  }
+3 −7
Original line number Diff line number Diff line
@@ -2897,23 +2897,19 @@ package android.service.voice {
    field public static final boolean ENABLE_PROXIMITY_RESULT = true;
  }

  public final class HotwordDetectionServiceFailure extends android.service.voice.DetectorFailure {
  public final class HotwordDetectionServiceFailure implements android.os.Parcelable {
    ctor public HotwordDetectionServiceFailure(int, @NonNull String);
  }

  public final class SoundTriggerFailure extends android.service.voice.DetectorFailure {
  public final class SoundTriggerFailure implements android.os.Parcelable {
    ctor public SoundTriggerFailure(int, @NonNull String);
  }

  public final class UnknownFailure extends android.service.voice.DetectorFailure {
    ctor public UnknownFailure(@NonNull String);
  }

  public final class VisibleActivityInfo implements android.os.Parcelable {
    ctor public VisibleActivityInfo(int, @NonNull android.os.IBinder);
  }

  public final class VisualQueryDetectionServiceFailure extends android.service.voice.DetectorFailure {
  public final class VisualQueryDetectionServiceFailure implements android.os.Parcelable {
    ctor public VisualQueryDetectionServiceFailure(int, @NonNull String);
  }

+9 −4
Original line number Diff line number Diff line
@@ -228,11 +228,16 @@ abstract class AbstractDetector implements HotwordDetector {

        /** Called when the detection fails due to an error. */
        @Override
        public void onError(DetectorFailure detectorFailure) {
            Slog.v(TAG, "BinderCallback#onError detectorFailure: " + detectorFailure);
        public void onHotwordDetectionServiceFailure(
                HotwordDetectionServiceFailure hotwordDetectionServiceFailure) {
            Slog.v(TAG, "BinderCallback#onHotwordDetectionServiceFailure: "
                    + hotwordDetectionServiceFailure);
            Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> {
                mCallback.onFailure(detectorFailure != null ? detectorFailure
                        : new UnknownFailure("Error data is null"));
                if (hotwordDetectionServiceFailure != null) {
                    mCallback.onFailure(hotwordDetectionServiceFailure);
                } else {
                    mCallback.onUnknownFailure("Error data is null");
                }
            }));
        }

+66 −11
Original line number Diff line number Diff line
@@ -280,6 +280,9 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
    private static final int MSG_HOTWORD_REJECTED = 6;
    private static final int MSG_HOTWORD_STATUS_REPORTED = 7;
    private static final int MSG_PROCESS_RESTARTED = 8;
    private static final int MSG_DETECTION_HOTWORD_DETECTION_SERVICE_FAILURE = 9;
    private static final int MSG_DETECTION_SOUND_TRIGGER_FAILURE = 10;
    private static final int MSG_DETECTION_UNKNOWN_FAILURE = 11;

    private final String mText;
    private final Locale mLocale;
@@ -774,12 +777,28 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
        /**
         * {@inheritDoc}
         *
         * @deprecated Use {@link HotwordDetector.Callback#onError(DetectorFailure)} instead.
         * @deprecated On {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above,
         * implement {@link HotwordDetector.Callback#onFailure(HotwordDetectionServiceFailure)},
         * {@link AlwaysOnHotwordDetector.Callback#onFailure(SoundTriggerFailure)},
         * {@link HotwordDetector.Callback#onUnknownFailure(String)} instead.
         */
        @Deprecated
        @Override
        public abstract void onError();

        /**
         * Called when the detection fails due to an error occurs in the
         * {@link com.android.server.soundtrigger.SoundTriggerService} and
         * {@link com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareService},
         * {@link SoundTriggerFailure} will be reported to the detector.
         *
         * @param soundTriggerFailure It provides the error code, error message and suggested
         *                            action.
         */
        public void onFailure(@NonNull SoundTriggerFailure soundTriggerFailure) {
            onError();
        }

        /** {@inheritDoc} */
        public abstract void onRecognitionPaused();

@@ -1556,18 +1575,43 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
        @Override
        public void onError(int status) {
            Slog.i(TAG, "onError: " + status);
            // 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();
            // TODO(b/271534248): This is a workaround before the sound trigger uses the new error
            // method.
            Message.obtain(mHandler, MSG_DETECTION_SOUND_TRIGGER_FAILURE,
                    new SoundTriggerFailure(SoundTriggerFailure.ERROR_CODE_UNKNOWN,
                            "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();
        public void onHotwordDetectionServiceFailure(
                HotwordDetectionServiceFailure hotwordDetectionServiceFailure) {
            Slog.v(TAG, "onHotwordDetectionServiceFailure: " + hotwordDetectionServiceFailure);
            if (hotwordDetectionServiceFailure != null) {
                Message.obtain(mHandler, MSG_DETECTION_HOTWORD_DETECTION_SERVICE_FAILURE,
                        hotwordDetectionServiceFailure).sendToTarget();
            } else {
                Message.obtain(mHandler, MSG_DETECTION_UNKNOWN_FAILURE,
                        "Error data is null").sendToTarget();
            }
        }

        @Override
        public void onVisualQueryDetectionServiceFailure(
                VisualQueryDetectionServiceFailure visualQueryDetectionServiceFailure)
                throws RemoteException {
            // It should never be called here.
            Slog.w(TAG,
                    "onVisualQueryDetectionServiceFailure: " + visualQueryDetectionServiceFailure);
        }

        @Override
        public void onUnknownFailure(String errorMessage) throws RemoteException {
            Slog.v(TAG, "onUnknownFailure: " + errorMessage);
            Message.obtain(mHandler, MSG_DETECTION_UNKNOWN_FAILURE,
                    !TextUtils.isEmpty(errorMessage) ? errorMessage
                            : "Error data is null").sendToTarget();
        }

        @Override
        public void onRecognitionPaused() {
            Slog.i(TAG, "onRecognitionPaused");
@@ -1600,7 +1644,7 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
    }

    void onDetectorRemoteException() {
        Message.obtain(mHandler, MSG_DETECTION_ERROR,
        Message.obtain(mHandler, MSG_DETECTION_HOTWORD_DETECTION_SERVICE_FAILURE,
                new HotwordDetectionServiceFailure(
                        HotwordDetectionServiceFailure.ERROR_CODE_REMOTE_EXCEPTION,
                        "Detector remote exception occurs")).sendToTarget();
@@ -1630,7 +1674,9 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
                        mExternalCallback.onDetected((EventPayload) message.obj);
                        break;
                    case MSG_DETECTION_ERROR:
                        mExternalCallback.onFailure((DetectorFailure) message.obj);
                        // TODO(b/271534248): After reverting the workaround, this logic is still
                        // necessary.
                        mExternalCallback.onError();
                        break;
                    case MSG_DETECTION_PAUSE:
                        mExternalCallback.onRecognitionPaused();
@@ -1647,6 +1693,15 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
                    case MSG_PROCESS_RESTARTED:
                        mExternalCallback.onHotwordDetectionServiceRestarted();
                        break;
                    case MSG_DETECTION_HOTWORD_DETECTION_SERVICE_FAILURE:
                        mExternalCallback.onFailure((HotwordDetectionServiceFailure) message.obj);
                        break;
                    case MSG_DETECTION_SOUND_TRIGGER_FAILURE:
                        mExternalCallback.onFailure((SoundTriggerFailure) message.obj);
                        break;
                    case MSG_DETECTION_UNKNOWN_FAILURE:
                        mExternalCallback.onUnknownFailure((String) message.obj);
                        break;
                    default:
                        super.handleMessage(message);
                }
+0 −19
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.service.voice;

parcelable DetectorFailure;
Loading