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

Commit f71e9ef4 authored by Nicholas Ambur's avatar Nicholas Ambur Committed by Android (Google) Code Review
Browse files

Merge changes from topic "startrecognition-data"

* changes:
  add AlwaysOnHotwordDetector availabilty test API
  expand AlwaysOnHotwordDetection.startRecognition
parents ed42c2be 63f3e1a2
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -11905,6 +11905,7 @@ package android.service.voice {
    method public int getSupportedRecognitionModes();
    method @Nullable @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int);
    method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public int setParameter(int, int);
    method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition(int, @NonNull byte[]);
    method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition(int);
    method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition();
    method public boolean startRecognition(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @Nullable android.os.PersistableBundle);
+2 −0
Original line number Diff line number Diff line
@@ -2429,6 +2429,8 @@ package android.service.quicksettings {
package android.service.voice {

  public class AlwaysOnHotwordDetector implements android.service.voice.HotwordDetector {
    method public void overrideAvailability(int);
    method public void resetAvailability();
    method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public void triggerHardwareRecognitionEventForTest(int, int, boolean, int, int, int, boolean, @NonNull android.media.AudioFormat, @Nullable byte[], @NonNull java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>);
  }

+125 −20
Original line number Diff line number Diff line
@@ -53,6 +53,7 @@ import android.os.SharedMemory;
import android.util.Log;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.app.IVoiceInteractionManagerService;
import com.android.internal.app.IVoiceInteractionSoundTriggerSession;
@@ -62,8 +63,10 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;

/**
 * A class that lets a VoiceInteractionService implementation interact with
@@ -275,6 +278,7 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
     * The metadata of the Keyphrase, derived from the enrollment application.
     * This may be null if this keyphrase isn't supported by the enrollment application.
     */
    @GuardedBy("mLock")
    @Nullable
    private KeyphraseMetadata mKeyphraseMetadata;
    private final KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo;
@@ -287,6 +291,9 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
    private final int mTargetSdkVersion;
    private final boolean mSupportHotwordDetectionService;

    @GuardedBy("mLock")
    private boolean mIsAvailabilityOverriddenByTestApi = false;
    @GuardedBy("mLock")
    private int mAvailability = STATE_NOT_READY;

    /**
@@ -815,12 +822,13 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
        try {
            Identity identity = new Identity();
            identity.packageName = ActivityThread.currentOpPackageName();
            mSoundTriggerSession = mModelManagementService.createSoundTriggerSessionAsOriginator(
            mSoundTriggerSession =
                    mModelManagementService.createSoundTriggerSessionAsOriginator(
                            identity, mBinder);
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }
        new RefreshAvailabiltyTask().execute();
        new RefreshAvailabilityTask().execute();
    }

    /**
@@ -847,6 +855,47 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
        super.updateState(options, sharedMemory);
    }

    /**
     * Test API for manipulating the voice engine and sound model availability.
     *
     * After overriding the availability status, the client's
     * {@link Callback#onAvailabilityChanged(int)} will be called to reflect the updated state.
     *
     * When this override is set, all system updates to availability will be ignored.
     * @hide
     */
    @TestApi
    public void overrideAvailability(int availability) {
        synchronized (mLock) {
            mAvailability = availability;
            mIsAvailabilityOverriddenByTestApi = true;
            // ENROLLED state requires there to be metadata about the sound model so a fake one
            // is created.
            if (mKeyphraseMetadata == null && mAvailability == STATE_KEYPHRASE_ENROLLED) {
                Set<Locale> fakeSupportedLocales = new HashSet<>();
                fakeSupportedLocales.add(mLocale);
                mKeyphraseMetadata = new KeyphraseMetadata(1, mText, fakeSupportedLocales,
                        AlwaysOnHotwordDetector.RECOGNITION_MODE_VOICE_TRIGGER);
            }
            notifyStateChangedLocked();
        }
    }

    /**
     * Test API for clearing an availability override set by {@link #overrideAvailability(int)}
     *
     * This method will restore the availability to the current system state.
     * @hide
     */
    @TestApi
    public void resetAvailability() {
        synchronized (mLock) {
            mIsAvailabilityOverriddenByTestApi = false;
        }
        // Execute a refresh availability task - which should then notify of a change.
        new RefreshAvailabilityTask().execute();
    }

    /**
     * Test API to simulate to trigger hardware recognition event for test.
     *
@@ -897,6 +946,7 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
        }
    }

    @GuardedBy("mLock")
    private int getSupportedRecognitionModesLocked() {
        if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
            throw new IllegalStateException(
@@ -931,6 +981,7 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
        }
    }

    @GuardedBy("mLock")
    private int getSupportedAudioCapabilitiesLocked() {
        try {
            ModuleProperties properties =
@@ -954,6 +1005,9 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
     * @see #RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS
     *
     * @param recognitionFlags The flags to control the recognition properties.
     * @param data Additional pass-through data to the system voice engine along with the
     *             startRecognition request. This data is intended to provide additional parameters
     *             when starting the opaque sound model.
     * @return Indicates whether the call succeeded or not.
     * @throws UnsupportedOperationException if the recognition isn't supported.
     *         Callers should only call this method after a supported state callback on
@@ -963,21 +1017,35 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
     *         {@link VoiceInteractionService} hosting this detector has been shut down.
     */
    @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
    public boolean startRecognition(@RecognitionFlags int recognitionFlags) {
        if (DBG) Slog.d(TAG, "startRecognition(" + recognitionFlags + ")");
    public boolean startRecognition(@RecognitionFlags int recognitionFlags, @NonNull byte[] data) {
        synchronized (mLock) {
            if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
                throw new IllegalStateException(
                        "startRecognition called on an invalid detector or error state");
            return startRecognitionLocked(recognitionFlags, data)
                    == STATUS_OK;
        }

            // Check if we can start/stop a recognition.
            if (mAvailability != STATE_KEYPHRASE_ENROLLED) {
                throw new UnsupportedOperationException(
                        "Recognition for the given keyphrase is not supported");
    }

            return startRecognitionLocked(recognitionFlags) == STATUS_OK;
    /**
     * Starts recognition for the associated keyphrase.
     * Caller must be the active voice interaction service via
     * Settings.Secure.VOICE_INTERACTION_SERVICE.
     *
     * @see #RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
     * @see #RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS
     *
     * @param recognitionFlags The flags to control the recognition properties.
     * @return Indicates whether the call succeeded or not.
     * @throws UnsupportedOperationException if the recognition isn't supported.
     *         Callers should only call this method after a supported state callback on
     *         {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
     * @throws IllegalStateException if the detector is in an invalid or error state.
     *         This may happen if another detector has been instantiated or the
     *         {@link VoiceInteractionService} hosting this detector has been shut down.
     */
    @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
    public boolean startRecognition(@RecognitionFlags int recognitionFlags) {
        if (DBG) Slog.d(TAG, "startRecognition(" + recognitionFlags + ")");
        synchronized (mLock) {
            return startRecognitionLocked(recognitionFlags, null /* data */) == STATUS_OK;
        }
    }

@@ -1193,6 +1261,7 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
        }
    }

    @GuardedBy("mLock")
    private Intent getManageIntentLocked(@KeyphraseEnrollmentInfo.ManageActions int action) {
        if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
            throw new IllegalStateException(
@@ -1225,6 +1294,7 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
            }

            mAvailability = STATE_INVALID;
            mIsAvailabilityOverriddenByTestApi = false;
            notifyStateChangedLocked();

            if (mSupportHotwordDetectionService) {
@@ -1253,6 +1323,15 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
                return;
            }

            // Because this method reflects an update from the system service models, we should not
            // update the client of an availability change when the availability has been overridden
            // via a test API.
            if (mIsAvailabilityOverriddenByTestApi) {
                Slog.w(TAG, "Suppressing system availability update. "
                        + "Availability is overridden by test API.");
                return;
            }

            // Stop the recognition before proceeding.
            // This is done because we want to stop the recognition on an older model if it changed
            // or was deleted.
@@ -1272,11 +1351,29 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
            }

            // Execute a refresh availability task - which should then notify of a change.
            new RefreshAvailabiltyTask().execute();
            new RefreshAvailabilityTask().execute();
        }
    }

    private int startRecognitionLocked(int recognitionFlags) {
    @GuardedBy("mLock")
    private int startRecognitionLocked(int recognitionFlags,
            @Nullable byte[] data) {
        if (DBG) {
            Slog.d(TAG, "startRecognition("
                    + recognitionFlags
                    + ", " + Arrays.toString(data) + ")");
        }
        if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
            throw new IllegalStateException(
                    "startRecognition called on an invalid detector or error state");
        }

        // Check if we can start/stop a recognition.
        if (mAvailability != STATE_KEYPHRASE_ENROLLED) {
            throw new UnsupportedOperationException(
                    "Recognition for the given keyphrase is not supported");
        }

        KeyphraseRecognitionExtra[] recognitionExtra = new KeyphraseRecognitionExtra[1];
        // TODO: Do we need to do something about the confidence level here?
        recognitionExtra[0] = new KeyphraseRecognitionExtra(mKeyphraseMetadata.getId(),
@@ -1300,7 +1397,7 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
            code = mSoundTriggerSession.startRecognition(
                    mKeyphraseMetadata.getId(), mLocale.toLanguageTag(), mInternalCallback,
                    new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers,
                            recognitionExtra, null /* additional data */, audioCapabilities),
                            recognitionExtra, data, audioCapabilities),
                    runInBatterySaver);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
@@ -1312,6 +1409,7 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
        return code;
    }

    @GuardedBy("mLock")
    private int stopRecognitionLocked() {
        int code;
        try {
@@ -1327,6 +1425,7 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
        return code;
    }

    @GuardedBy("mLock")
    private int setParameterLocked(@ModelParams int modelParam, int value) {
        try {
            int code = mSoundTriggerSession.setParameter(mKeyphraseMetadata.getId(), modelParam,
@@ -1342,6 +1441,7 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
        }
    }

    @GuardedBy("mLock")
    private int getParameterLocked(@ModelParams int modelParam) {
        try {
            return mSoundTriggerSession.getParameter(mKeyphraseMetadata.getId(), modelParam);
@@ -1350,6 +1450,7 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
        }
    }

    @GuardedBy("mLock")
    @Nullable
    private ModelParamRange queryParameterLocked(@ModelParams int modelParam) {
        try {
@@ -1366,15 +1467,19 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
        }
    }

    @GuardedBy("mLock")
    private void updateAndNotifyStateChangedLocked(int availability) {
        if (DBG) {
            Slog.d(TAG, "Hotword availability changed from " + mAvailability
                    + " -> " + availability);
        }
        if (!mIsAvailabilityOverriddenByTestApi) {
            mAvailability = availability;
        }
        notifyStateChangedLocked();
    }

    @GuardedBy("mLock")
    private void notifyStateChangedLocked() {
        Message message = Message.obtain(mHandler, MSG_AVAILABILITY_CHANGED);
        message.arg1 = mAvailability;
@@ -1496,7 +1601,7 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
        }
    }

    class RefreshAvailabiltyTask extends AsyncTask<Void, Void, Void> {
    class RefreshAvailabilityTask extends AsyncTask<Void, Void, Void> {

        @Override
        public Void doInBackground(Void... params) {