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

Commit dcf3068f authored by Sandeep Siddhartha's avatar Sandeep Siddhartha
Browse files

Fix the Locale story in the hotword API

Tighten the API by taking in a locale rather than a string tag.
Tighten the checks when reading the enrollment metadata, bail out if any
attribute is missing or invalid.
Add missing recycle call for a TypedArray

Stop recognition when sound model(s) change. This is needed during
un-enrollment/re-enrollment.

Bug: 17187528
Bug: 17205230
Change-Id: Idb00b51ef8c4ea0a8f8993decea582223181fa3d
parent 7653a30e
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -27463,7 +27463,7 @@ package android.service.voice {
  public class VoiceInteractionService extends android.app.Service {
    ctor public VoiceInteractionService();
    method public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(java.lang.String, java.lang.String, android.service.voice.AlwaysOnHotwordDetector.Callback);
    method public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(java.lang.String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback);
    method public static boolean isActiveService(android.content.Context, android.content.ComponentName);
    method public android.os.IBinder onBind(android.content.Intent);
    method public void onReady();
+66 −30
Original line number Diff line number Diff line
@@ -25,6 +25,8 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.service.voice.AlwaysOnHotwordDetector;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Slog;
import android.util.Xml;
@@ -35,6 +37,7 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

/**
 * Enrollment information about the different available keyphrases.
@@ -151,33 +154,8 @@ public class KeyphraseEnrollmentInfo {

            TypedArray array = res.obtainAttributes(attrs,
                    com.android.internal.R.styleable.VoiceEnrollmentApplication);
            int searchKeyphraseId = array.getInt(
                    com.android.internal.R.styleable.VoiceEnrollmentApplication_searchKeyphraseId,
                    -1);
            if (searchKeyphraseId != -1) {
                String searchKeyphrase = array.getString(com.android.internal.R.styleable
                        .VoiceEnrollmentApplication_searchKeyphrase);
                if (searchKeyphrase == null) {
                    searchKeyphrase = "";
                }
                String searchKeyphraseSupportedLocales =
                        array.getString(com.android.internal.R.styleable
                                .VoiceEnrollmentApplication_searchKeyphraseSupportedLocales);
                String[] supportedLocales = new String[0];
                // Get all the supported locales from the comma-delimted string.
                if (searchKeyphraseSupportedLocales != null
                        && !searchKeyphraseSupportedLocales.isEmpty()) {
                    supportedLocales = searchKeyphraseSupportedLocales.split(",");
                }
                int recognitionModes = array.getInt(com.android.internal.R.styleable
                        .VoiceEnrollmentApplication_searchKeyphraseRecognitionFlags, 0);
                mKeyphrases = new KeyphraseMetadata[1];
                mKeyphrases[0] = new KeyphraseMetadata(
                        searchKeyphraseId, searchKeyphrase, supportedLocales, recognitionModes);
            } else {
                mParseError = "searchKeyphraseId not specified in meta-data";
                return;
            }
            initializeKeyphrasesFromTypedArray(array);
            array.recycle();
        } catch (XmlPullParserException e) {
            mParseError = "Error parsing keyphrase enrollment meta-data: " + e;
            Slog.w(TAG, "error parsing keyphrase enrollment meta-data", e);
@@ -195,6 +173,65 @@ public class KeyphraseEnrollmentInfo {
        }
    }

    private void initializeKeyphrasesFromTypedArray(TypedArray array) {
        // Get the keyphrase ID.
        int searchKeyphraseId = array.getInt(
                com.android.internal.R.styleable.VoiceEnrollmentApplication_searchKeyphraseId, -1);
        if (searchKeyphraseId <= 0) {
            mParseError = "No valid searchKeyphraseId specified in meta-data";
            Slog.w(TAG, mParseError);
            return;
        }

        // Get the keyphrase text.
        String searchKeyphrase = array.getString(
                com.android.internal.R.styleable.VoiceEnrollmentApplication_searchKeyphrase);
        if (searchKeyphrase == null) {
            mParseError = "No valid searchKeyphrase specified in meta-data";
            Slog.w(TAG, mParseError);
            return;
        }

        // Get the supported locales.
        String searchKeyphraseSupportedLocales = array.getString(
                com.android.internal.R.styleable
                        .VoiceEnrollmentApplication_searchKeyphraseSupportedLocales);
        if (searchKeyphraseSupportedLocales == null) {
            mParseError = "No valid searchKeyphraseSupportedLocales specified in meta-data";
            Slog.w(TAG, mParseError);
            return;
        }
        ArraySet<Locale> locales = new ArraySet<>();
        // Try adding locales if the locale string is non-empty.
        if (!TextUtils.isEmpty(searchKeyphraseSupportedLocales)) {
            try {
                String[] supportedLocalesDelimited = searchKeyphraseSupportedLocales.split(",");
                for (int i = 0; i < supportedLocalesDelimited.length; i++) {
                    locales.add(Locale.forLanguageTag(supportedLocalesDelimited[i]));
                }
            } catch (Exception ex) {
                // We catch a generic exception here because we don't want the system service
                // to be affected by a malformed metadata because invalid locales were specified
                // by the system application.
                mParseError = "Error reading searchKeyphraseSupportedLocales from meta-data";
                Slog.w(TAG, mParseError, ex);
                return;
            }
        }

        // Get the supported recognition modes.
        int recognitionModes = array.getInt(com.android.internal.R.styleable
                .VoiceEnrollmentApplication_searchKeyphraseRecognitionFlags, -1);
        if (recognitionModes < 0) {
            mParseError = "No valid searchKeyphraseRecognitionFlags specified in meta-data";
            Slog.w(TAG, mParseError);
            return;
        }
        mKeyphrases = new KeyphraseMetadata[1];
        mKeyphrases[0] = new KeyphraseMetadata(searchKeyphraseId, searchKeyphrase, locales,
                recognitionModes);
    }

    public String getParseError() {
        return mParseError;
    }
@@ -217,11 +254,10 @@ public class KeyphraseEnrollmentInfo {
     *        or {@link AlwaysOnHotwordDetector#MANAGE_ACTION_UN_ENROLL}
     * @param keyphrase The keyphrase that the user needs to be enrolled to.
     * @param locale The locale for which the enrollment needs to be performed.
     *        This is a Java locale, for example "en_US".
     * @return An {@link Intent} to manage the keyphrase. This can be null if managing the
     *         given keyphrase/locale combination isn't possible.
     */
    public Intent getManageKeyphraseIntent(int action, String keyphrase, String locale) {
    public Intent getManageKeyphraseIntent(int action, String keyphrase, Locale locale) {
        if (mEnrollmentPackage == null || mEnrollmentPackage.isEmpty()) {
            Slog.w(TAG, "No enrollment application exists");
            return null;
@@ -248,7 +284,7 @@ public class KeyphraseEnrollmentInfo {
     * @return The metadata, if the enrollment client supports the given keyphrase
     *         and locale, null otherwise.
     */
    public KeyphraseMetadata getKeyphraseMetadata(String keyphrase, String locale) {
    public KeyphraseMetadata getKeyphraseMetadata(String keyphrase, Locale locale) {
        if (mKeyphrases == null || mKeyphrases.length == 0) {
            Slog.w(TAG, "Enrollment application doesn't support keyphrases");
            return null;
+6 −7
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package android.hardware.soundtrigger;

import android.util.ArraySet;

import java.util.Locale;

/**
 * A Voice Keyphrase metadata read from the enrollment application.
 *
@@ -26,17 +28,14 @@ import android.util.ArraySet;
public class KeyphraseMetadata {
    public final int id;
    public final String keyphrase;
    public final ArraySet<String> supportedLocales;
    public final ArraySet<Locale> supportedLocales;
    public final int recognitionModeFlags;

    public KeyphraseMetadata(int id, String keyphrase, String[] supportedLocales,
    public KeyphraseMetadata(int id, String keyphrase, ArraySet<Locale> supportedLocales,
            int recognitionModeFlags) {
        this.id = id;
        this.keyphrase = keyphrase;
        this.supportedLocales = new ArraySet<String>(supportedLocales.length);
        for (String locale : supportedLocales) {
            this.supportedLocales.add(locale);
        }
        this.supportedLocales = supportedLocales;
        this.recognitionModeFlags = recognitionModeFlags;
    }

@@ -56,7 +55,7 @@ public class KeyphraseMetadata {
    /**
     * @return Indicates if we support the given locale.
     */
    public boolean supportsLocale(String locale) {
    public boolean supportsLocale(Locale locale) {
        return supportedLocales.isEmpty() || supportedLocales.contains(locale);
    }
}
+10 −5
Original line number Diff line number Diff line
@@ -39,10 +39,10 @@ import android.util.Slog;

import com.android.internal.app.IVoiceInteractionManagerService;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Locale;

/**
 * A class that lets a VoiceInteractionService implementation interact with
@@ -167,7 +167,7 @@ public class AlwaysOnHotwordDetector {
    private static final int MSG_DETECTION_RESUME = 5;

    private final String mText;
    private final String mLocale;
    private final Locale mLocale;
    /**
     * The metadata of the Keyphrase, derived from the enrollment application.
     * This may be null if this keyphrase isn't supported by the enrollment application.
@@ -317,7 +317,7 @@ public class AlwaysOnHotwordDetector {
     *
     * @hide
     */
    public AlwaysOnHotwordDetector(String text, String locale, Callback callback,
    public AlwaysOnHotwordDetector(String text, Locale locale, Callback callback,
            KeyphraseEnrollmentInfo keyphraseEnrollmentInfo,
            IVoiceInteractionService voiceInteractionService,
            IVoiceInteractionManagerService modelManagementService) {
@@ -491,8 +491,6 @@ public class AlwaysOnHotwordDetector {
     */
    void onSoundModelsChanged() {
        synchronized (mLock) {
            // FIXME: This should stop the recognition if it was using an enrolled sound model
            // that's no longer available.
            if (mAvailability == STATE_INVALID
                    || mAvailability == STATE_HARDWARE_UNAVAILABLE
                    || mAvailability == STATE_KEYPHRASE_UNSUPPORTED) {
@@ -500,6 +498,13 @@ public class AlwaysOnHotwordDetector {
                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.
            // The availability change callback should ensure that the client starts recognition
            // again if needed.
            stopRecognitionLocked();

            // Execute a refresh availability task - which should then notify of a change.
            new RefreshAvailabiltyTask().execute();
        }
+14 −3
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import com.android.internal.app.IVoiceInteractionManagerService;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Locale;


/**
@@ -163,7 +164,7 @@ public class VoiceInteractionService extends Service {
     * Called during service initialization to tell you when the system is ready
     * to receive interaction from it. You should generally do initialization here
     * rather than in {@link #onCreate()}. Methods such as {@link #startSession(Bundle)} and
     * {@link #createAlwaysOnHotwordDetector(String, String, android.service.voice.AlwaysOnHotwordDetector.Callback)}
     * {@link #createAlwaysOnHotwordDetector(String, Locale, android.service.voice.AlwaysOnHotwordDetector.Callback)}
     * will not be operational until this point.
     */
    public void onReady() {
@@ -199,6 +200,17 @@ public class VoiceInteractionService extends Service {
        }
    }

    /**
     * FIXME: Remove once the prebuilts are updated.
     *
     * @hide
     */
    @Deprecated
    public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(
            String keyphrase, String locale, AlwaysOnHotwordDetector.Callback callback) {
        return createAlwaysOnHotwordDetector(keyphrase, new Locale(locale), callback);
    }

    /**
     * Creates an {@link AlwaysOnHotwordDetector} for the given keyphrase and locale.
     * This instance must be retained and used by the client.
@@ -207,12 +219,11 @@ public class VoiceInteractionService extends Service {
     *
     * @param keyphrase The keyphrase that's being used, for example "Hello Android".
     * @param locale The locale for which the enrollment needs to be performed.
     *        This is a Java locale, for example "en_US".
     * @param callback The callback to notify of detection events.
     * @return An always-on hotword detector for the given keyphrase and locale.
     */
    public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(
            String keyphrase, String locale, AlwaysOnHotwordDetector.Callback callback) {
            String keyphrase, Locale locale, AlwaysOnHotwordDetector.Callback callback) {
        if (mSystemService == null) {
            throw new IllegalStateException("Not available until onReady() is called");
        }
Loading