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

Commit 80ea66b1 authored by Sandeep Siddhartha's avatar Sandeep Siddhartha Committed by Android (Google) Code Review
Browse files

Merge "Fix issues with multiple languages and multi-users" into lmp-dev

parents ae1990f4 8cf8f716
Loading
Loading
Loading
Loading
+5 −6
Original line number Diff line number Diff line
@@ -170,8 +170,7 @@ public class AlwaysOnHotwordDetector {
            = SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION;

    static final String TAG = "AlwaysOnHotwordDetector";
    // TODO: Set to false.
    static final boolean DBG = true;
    static final boolean DBG = false;

    private static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR;
    private static final int STATUS_OK = SoundTrigger.STATUS_OK;
@@ -575,7 +574,7 @@ public class AlwaysOnHotwordDetector {
        int code = STATUS_ERROR;
        try {
            code = mModelManagementService.startRecognition(mVoiceInteractionService,
                    mKeyphraseMetadata.id, mInternalCallback,
                    mKeyphraseMetadata.id, mLocale.toLanguageTag(), mInternalCallback,
                    new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers,
                            recognitionExtra, null /* additional data */));
        } catch (RemoteException e) {
@@ -690,7 +689,7 @@ public class AlwaysOnHotwordDetector {
            if (availability == STATE_NOT_READY
                    || availability == STATE_KEYPHRASE_UNENROLLED
                    || availability == STATE_KEYPHRASE_ENROLLED) {
                enrolled = internalGetIsEnrolled(mKeyphraseMetadata.id);
                enrolled = internalGetIsEnrolled(mKeyphraseMetadata.id, mLocale);
                if (!enrolled) {
                    availability = STATE_KEYPHRASE_UNENROLLED;
                } else {
@@ -741,10 +740,10 @@ public class AlwaysOnHotwordDetector {
        /**
         * @return The corresponding {@link KeyphraseSoundModel} or null if none is found.
         */
        private boolean internalGetIsEnrolled(int keyphraseId) {
        private boolean internalGetIsEnrolled(int keyphraseId, Locale locale) {
            try {
                return mModelManagementService.isEnrolledForKeyphrase(
                        mVoiceInteractionService, keyphraseId);
                        mVoiceInteractionService, keyphraseId, locale.toLanguageTag());
            } catch (RemoteException e) {
                Slog.w(TAG, "RemoteException in listRegisteredKeyphraseSoundModels!", e);
            }
+23 −11
Original line number Diff line number Diff line
@@ -33,32 +33,44 @@ interface IVoiceInteractionManagerService {
    void finish(IBinder token);

    /**
     * Lists the registered Sound model for keyphrase detection.
     * May be null if no matching sound models exist.
     * Gets the registered Sound model for keyphrase detection for the current user.
     * May be null if no matching sound model exists.
     *
     * @param keyphraseId The unique identifier for the keyphrase.
     * @param bcp47Locale The BCP47 language tag  for the keyphrase's locale.
     */
    SoundTrigger.KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId);
    SoundTrigger.KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, in String bcp47Locale);
    /**
     * Updates the given keyphrase sound model. Adds the model if it doesn't exist currently.
     * Add/Update the given keyphrase sound model.
     */
    int updateKeyphraseSoundModel(in SoundTrigger.KeyphraseSoundModel model);
    /**
     * Deletes the given keyphrase sound model.
     * Deletes the given keyphrase sound model for the current user.
     *
     * @param keyphraseId The unique identifier for the keyphrase.
     * @param bcp47Locale The BCP47 language tag  for the keyphrase's locale.
     */
    int deleteKeyphraseSoundModel(int keyphraseId);
    int deleteKeyphraseSoundModel(int keyphraseId, in String bcp47Locale);

    /**
     * Indicates if there's a keyphrase sound model available for the given keyphrase ID.
     */
    boolean isEnrolledForKeyphrase(IVoiceInteractionService service, int keyphraseId);
    /**
     * Gets the properties of the DSP hardware on this device, null if not present.
     */
    SoundTrigger.ModuleProperties getDspModuleProperties(in IVoiceInteractionService service);
    /**
     * Indicates if there's a keyphrase sound model available for the given keyphrase ID.
     * This performs the check for the current user.
     *
     * @param service The current VoiceInteractionService.
     * @param keyphraseId The unique identifier for the keyphrase.
     * @param bcp47Locale The BCP47 language tag  for the keyphrase's locale.
     */
    boolean isEnrolledForKeyphrase(IVoiceInteractionService service, int keyphraseId,
            String bcp47Locale);
    /**
     * Starts a recognition for the given keyphrase.
     */
    int startRecognition(in IVoiceInteractionService service, int keyphraseId,
            in IRecognitionStatusCallback callback,
            in String bcp47Locale, in IRecognitionStatusCallback callback,
            in SoundTrigger.RecognitionConfig recognitionConfig);
    /**
     * Stops a recognition for the given keyphrase.
+48 −32
Original line number Diff line number Diff line
@@ -24,10 +24,10 @@ import android.database.sqlite.SQLiteOpenHelper;
import android.hardware.soundtrigger.SoundTrigger;
import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
import android.os.UserManager;
import android.text.TextUtils;
import android.util.Slog;

import java.util.Locale;
import java.util.UUID;

/**
@@ -37,8 +37,7 @@ import java.util.UUID;
 */
public class DatabaseHelper extends SQLiteOpenHelper {
    static final String TAG = "SoundModelDBHelper";
    // TODO: Set to false.
    static final boolean DBG = true;
    static final boolean DBG = false;

    private static final String NAME = "sound_model.db";
    private static final int VERSION = 4;
@@ -67,11 +66,8 @@ public class DatabaseHelper extends SQLiteOpenHelper {
            + SoundModelContract.KEY_HINT_TEXT + " TEXT,"
            + SoundModelContract.KEY_USERS + " TEXT" + ")";

    private final UserManager mUserManager;

    public DatabaseHelper(Context context) {
        super(context, NAME, null, VERSION);
        mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
    }

    @Override
@@ -122,17 +118,20 @@ public class DatabaseHelper extends SQLiteOpenHelper {
    /**
     * Deletes the sound model and associated keyphrases.
     */
    public boolean deleteKeyphraseSoundModel(UUID modelUuid) {
        if (modelUuid == null) {
            Slog.w(TAG, "Model UUID must be specified for deletion");
    public boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale) {
        // Sanitize the locale to guard against SQL injection.
        bcp47Locale = Locale.forLanguageTag(bcp47Locale).toLanguageTag();
        synchronized(this) {
            KeyphraseSoundModel soundModel = getKeyphraseSoundModel(keyphraseId, userHandle,
                    bcp47Locale);
            if (soundModel == null) {
                return false;
            }

        synchronized(this) {
            // Delete all sound models for the given keyphrase and specified user.
            SQLiteDatabase db = getWritableDatabase();
            String soundModelClause = SoundModelContract.KEY_MODEL_UUID + "='"
                    + modelUuid.toString() + "'";

            String soundModelClause = SoundModelContract.KEY_MODEL_UUID
                    + "='" + soundModel.uuid.toString() + "'";
            try {
                return db.delete(SoundModelContract.TABLE, soundModelClause, null) != 0;
            } finally {
@@ -147,11 +146,15 @@ public class DatabaseHelper extends SQLiteOpenHelper {
     *
     * TODO: We only support one keyphrase currently.
     */
    public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId) {
    public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle,
            String bcp47Locale) {
        // Sanitize the locale to guard against SQL injection.
        bcp47Locale = Locale.forLanguageTag(bcp47Locale).toLanguageTag();
        synchronized(this) {
            // Find the corresponding sound model ID for the keyphrase.
            String selectQuery = "SELECT  * FROM " + SoundModelContract.TABLE
                    + " WHERE " + SoundModelContract.KEY_KEYPHRASE_ID + " = '" + keyphraseId + "'";
                    + " WHERE " + SoundModelContract.KEY_KEYPHRASE_ID + "= '" + keyphraseId
                    + "' AND " + SoundModelContract.KEY_LOCALE + "='" + bcp47Locale + "'";
            SQLiteDatabase db = getReadableDatabase();
            Cursor c = db.rawQuery(selectQuery, null);

@@ -160,14 +163,16 @@ public class DatabaseHelper extends SQLiteOpenHelper {
                    do {
                        int type = c.getInt(c.getColumnIndex(SoundModelContract.KEY_TYPE));
                        if (type != SoundTrigger.SoundModel.TYPE_KEYPHRASE) {
                            Slog.w(TAG, "Ignoring sound model since it's type is incorrect");
                            if (DBG) {
                                Slog.w(TAG, "Ignoring SoundModel since it's type is incorrect");
                            }
                            continue;
                        }

                        String modelUuid = c.getString(
                                c.getColumnIndex(SoundModelContract.KEY_MODEL_UUID));
                        if (modelUuid == null) {
                            Slog.w(TAG, "Ignoring sound model since it doesn't specify an ID");
                            Slog.w(TAG, "Ignoring SoundModel since it doesn't specify an ID");
                            continue;
                        }

@@ -176,7 +181,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
                                c.getColumnIndex(SoundModelContract.KEY_RECOGNITION_MODES));
                        int[] users = getArrayForCommaSeparatedString(
                                c.getString(c.getColumnIndex(SoundModelContract.KEY_USERS)));
                        String locale = c.getString(
                        String modelLocale = c.getString(
                                c.getColumnIndex(SoundModelContract.KEY_LOCALE));
                        String text = c.getString(
                                c.getColumnIndex(SoundModelContract.KEY_HINT_TEXT));
@@ -184,28 +189,37 @@ public class DatabaseHelper extends SQLiteOpenHelper {
                        // Only add keyphrases meant for the current user.
                        if (users == null) {
                            // No users present in the keyphrase.
                            Slog.w(TAG, "Ignoring keyphrase since it doesn't specify users");
                            Slog.w(TAG, "Ignoring SoundModel since it doesn't specify users");
                            continue;
                        }

                        boolean isAvailableForCurrentUser = false;
                        int currentUser = mUserManager.getUserHandle();
                        for (int user : users) {
                            if (currentUser == user) {
                            if (userHandle == user) {
                                isAvailableForCurrentUser = true;
                                break;
                            }
                        }
                        if (!isAvailableForCurrentUser) {
                            Slog.w(TAG, "Ignoring keyphrase since it's not for the current user");
                            if (DBG) {
                                Slog.w(TAG, "Ignoring SoundModel since user handles don't match");
                            }
                            continue;
                        } else {
                            if (DBG) Slog.d(TAG, "Found a SoundModel for user: " + userHandle);
                        }

                        Keyphrase[] keyphrases = new Keyphrase[1];
                        keyphrases[0] = new Keyphrase(
                                keyphraseId, recognitionModes, locale, text, users);
                        return new KeyphraseSoundModel(UUID.fromString(modelUuid),
                                keyphraseId, recognitionModes, modelLocale, text, users);
                        KeyphraseSoundModel model = new KeyphraseSoundModel(
                                UUID.fromString(modelUuid),
                                null /* FIXME use vendor UUID */, data, keyphrases);
                        if (DBG) {
                            Slog.d(TAG, "Found SoundModel for the given keyphrase/locale/user: "
                                    + model);
                        }
                        return model;
                    } while (c.moveToNext());
                }
                Slog.w(TAG, "No SoundModel available for the given keyphrase");
@@ -218,15 +232,17 @@ public class DatabaseHelper extends SQLiteOpenHelper {
    }

    private static String getCommaSeparatedString(int[] users) {
        if (users == null || users.length == 0) {
        if (users == null) {
            return "";
        }
        String csv = "";
        for (int user : users) {
            csv += String.valueOf(user);
            csv += ",";
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < users.length; i++) {
            if (i != 0) {
                sb.append(',');
            }
            sb.append(users[i]);
        }
        return csv.substring(0, csv.length() - 1);
        return sb.toString();
    }

    private static int[] getArrayForCommaSeparatedString(String text) {
+8 −3
Original line number Diff line number Diff line
@@ -50,8 +50,7 @@ import java.util.UUID;
 */
public class SoundTriggerHelper implements SoundTrigger.StatusListener {
    static final String TAG = "SoundTriggerHelper";
    // TODO: Set to false.
    static final boolean DBG = true;
    static final boolean DBG = false;

    /**
     * Return codes for {@link #startRecognition(int, KeyphraseSoundModel,
@@ -166,8 +165,14 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
                }
            }

            // Unload the previous model if the current one isn't invalid
            // and, it's not the same as the new one, or we are already started
            // if we are already started, we can get multiple calls to start
            // if the underlying sound model changes, in which case we should unload and reload.
            // The model reuse helps only in cases when we trigger and stop internally
            // without a start recognition call.
            if (mCurrentSoundModelHandle != INVALID_VALUE
                    && !soundModel.uuid.equals(mCurrentSoundModelUuid)) {
                    && (!soundModel.uuid.equals(mCurrentSoundModelUuid) || mStarted)) {
                Slog.w(TAG, "Unloading previous sound model");
                int status = mModule.unloadSoundModel(mCurrentSoundModelHandle);
                if (status != SoundTrigger.STATUS_OK) {
+30 −12
Original line number Diff line number Diff line
@@ -446,7 +446,7 @@ public class VoiceInteractionManagerService extends SystemService {
        //----------------- Model management APIs --------------------------------//

        @Override
        public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId) {
        public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, String bcp47Locale) {
            synchronized (this) {
                if (mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
                        != PackageManager.PERMISSION_GRANTED) {
@@ -455,9 +455,14 @@ public class VoiceInteractionManagerService extends SystemService {
                }
            }

            if (bcp47Locale == null) {
                throw new IllegalArgumentException("Illegal argument(s) in getKeyphraseSoundModel");
            }

            final int callingUid = UserHandle.getCallingUserId();
            final long caller = Binder.clearCallingIdentity();
            try {
                return mDbHelper.getKeyphraseSoundModel(keyphraseId);
                return mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUid, bcp47Locale);
            } finally {
                Binder.restoreCallingIdentity(caller);
            }
@@ -495,7 +500,7 @@ public class VoiceInteractionManagerService extends SystemService {
        }

        @Override
        public int deleteKeyphraseSoundModel(int keyphraseId) {
        public int deleteKeyphraseSoundModel(int keyphraseId, String bcp47Locale) {
            synchronized (this) {
                if (mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
                        != PackageManager.PERMISSION_GRANTED) {
@@ -504,13 +509,16 @@ public class VoiceInteractionManagerService extends SystemService {
                }
            }

            if (bcp47Locale == null) {
                throw new IllegalArgumentException(
                        "Illegal argument(s) in deleteKeyphraseSoundModel");
            }

            final int callingUid = UserHandle.getCallingUserId();
            final long caller = Binder.clearCallingIdentity();
            boolean deleted = false;
            try {
                KeyphraseSoundModel soundModel = mDbHelper.getKeyphraseSoundModel(keyphraseId);
                if (soundModel != null) {
                    deleted = mDbHelper.deleteKeyphraseSoundModel(soundModel.uuid);
                }
                deleted = mDbHelper.deleteKeyphraseSoundModel(keyphraseId, callingUid, bcp47Locale);
                return deleted ? SoundTriggerHelper.STATUS_OK : SoundTriggerHelper.STATUS_ERROR;
            } finally {
                if (deleted) {
@@ -527,7 +535,8 @@ public class VoiceInteractionManagerService extends SystemService {

        //----------------- SoundTrigger APIs --------------------------------//
        @Override
        public boolean isEnrolledForKeyphrase(IVoiceInteractionService service, int keyphraseId) {
        public boolean isEnrolledForKeyphrase(IVoiceInteractionService service, int keyphraseId,
                String bcp47Locale) {
            synchronized (this) {
                if (mImpl == null || mImpl.mService == null
                        || service.asBinder() != mImpl.mService.asBinder()) {
@@ -536,9 +545,15 @@ public class VoiceInteractionManagerService extends SystemService {
                }
            }

            if (bcp47Locale == null) {
                throw new IllegalArgumentException("Illegal argument(s) in isEnrolledForKeyphrase");
            }

            final int callingUid = UserHandle.getCallingUserId();
            final long caller = Binder.clearCallingIdentity();
            try {
                KeyphraseSoundModel model = mDbHelper.getKeyphraseSoundModel(keyphraseId);
                KeyphraseSoundModel model =
                        mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUid, bcp47Locale);
                return model != null;
            } finally {
                Binder.restoreCallingIdentity(caller);
@@ -566,7 +581,8 @@ public class VoiceInteractionManagerService extends SystemService {

        @Override
        public int startRecognition(IVoiceInteractionService service, int keyphraseId,
                IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig) {
                String bcp47Locale, IRecognitionStatusCallback callback,
                RecognitionConfig recognitionConfig) {
            // Allow the call if this is the current voice interaction service.
            synchronized (this) {
                if (mImpl == null || mImpl.mService == null
@@ -575,14 +591,16 @@ public class VoiceInteractionManagerService extends SystemService {
                            "Caller is not the current voice interaction service");
                }

                if (callback == null || recognitionConfig == null) {
                if (callback == null || recognitionConfig == null || bcp47Locale == null) {
                    throw new IllegalArgumentException("Illegal argument(s) in startRecognition");
                }
            }

            int callingUid = UserHandle.getCallingUserId();
            final long caller = Binder.clearCallingIdentity();
            try {
                KeyphraseSoundModel soundModel = mDbHelper.getKeyphraseSoundModel(keyphraseId);
                KeyphraseSoundModel soundModel =
                        mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUid, bcp47Locale);
                if (soundModel == null
                        || soundModel.uuid == null
                        || soundModel.keyphrases == null) {