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

Commit 8cf8f716 authored by Sandeep Siddhartha's avatar Sandeep Siddhartha
Browse files

Fix issues with multiple languages and multi-users

For multi-user the issue was looking into the user ID of the current
process instead of the active user. The current process was the system
process and the call to UserManager was returning a user handle that
wasn't of any use while trying to map sound models to a user.

For language, the issue was that we were incorrectly just looking up the
model based on the keyphrase id, however we should have also taken the
enrolled model's locale into account.

Explicitly document that for model management the string representation of locales
is a BCP47 language tag.

Remove debug logging.

Bug: 16798166
Bug: 17462570
Bug: 17463511
Change-Id: Ieffb3e218de63f6e7f40af9705dced481a35b0ad
parent a2f945e2
Loading
Loading
Loading
Loading
+5 −6
Original line number Original line Diff line number Diff line
@@ -170,8 +170,7 @@ public class AlwaysOnHotwordDetector {
            = SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION;
            = SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION;


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


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


    /**
    /**
     * Lists the registered Sound model for keyphrase detection.
     * Gets the registered Sound model for keyphrase detection for the current user.
     * May be null if no matching sound models exist.
     * 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);
    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.
     * Gets the properties of the DSP hardware on this device, null if not present.
     */
     */
    SoundTrigger.ModuleProperties getDspModuleProperties(in IVoiceInteractionService service);
    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.
     * Starts a recognition for the given keyphrase.
     */
     */
    int startRecognition(in IVoiceInteractionService service, int keyphraseId,
    int startRecognition(in IVoiceInteractionService service, int keyphraseId,
            in IRecognitionStatusCallback callback,
            in String bcp47Locale, in IRecognitionStatusCallback callback,
            in SoundTrigger.RecognitionConfig recognitionConfig);
            in SoundTrigger.RecognitionConfig recognitionConfig);
    /**
    /**
     * Stops a recognition for the given keyphrase.
     * Stops a recognition for the given keyphrase.
+48 −32
Original line number Original line Diff line number Diff line
@@ -24,10 +24,10 @@ import android.database.sqlite.SQLiteOpenHelper;
import android.hardware.soundtrigger.SoundTrigger;
import android.hardware.soundtrigger.SoundTrigger;
import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
import android.os.UserManager;
import android.text.TextUtils;
import android.text.TextUtils;
import android.util.Slog;
import android.util.Slog;


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


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


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


    private final UserManager mUserManager;

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


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


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

            try {
            try {
                return db.delete(SoundModelContract.TABLE, soundModelClause, null) != 0;
                return db.delete(SoundModelContract.TABLE, soundModelClause, null) != 0;
            } finally {
            } finally {
@@ -147,11 +146,15 @@ public class DatabaseHelper extends SQLiteOpenHelper {
     *
     *
     * TODO: We only support one keyphrase currently.
     * 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) {
        synchronized(this) {
            // Find the corresponding sound model ID for the keyphrase.
            // Find the corresponding sound model ID for the keyphrase.
            String selectQuery = "SELECT  * FROM " + SoundModelContract.TABLE
            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();
            SQLiteDatabase db = getReadableDatabase();
            Cursor c = db.rawQuery(selectQuery, null);
            Cursor c = db.rawQuery(selectQuery, null);


@@ -160,14 +163,16 @@ public class DatabaseHelper extends SQLiteOpenHelper {
                    do {
                    do {
                        int type = c.getInt(c.getColumnIndex(SoundModelContract.KEY_TYPE));
                        int type = c.getInt(c.getColumnIndex(SoundModelContract.KEY_TYPE));
                        if (type != SoundTrigger.SoundModel.TYPE_KEYPHRASE) {
                        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;
                            continue;
                        }
                        }


                        String modelUuid = c.getString(
                        String modelUuid = c.getString(
                                c.getColumnIndex(SoundModelContract.KEY_MODEL_UUID));
                                c.getColumnIndex(SoundModelContract.KEY_MODEL_UUID));
                        if (modelUuid == null) {
                        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;
                            continue;
                        }
                        }


@@ -176,7 +181,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
                                c.getColumnIndex(SoundModelContract.KEY_RECOGNITION_MODES));
                                c.getColumnIndex(SoundModelContract.KEY_RECOGNITION_MODES));
                        int[] users = getArrayForCommaSeparatedString(
                        int[] users = getArrayForCommaSeparatedString(
                                c.getString(c.getColumnIndex(SoundModelContract.KEY_USERS)));
                                c.getString(c.getColumnIndex(SoundModelContract.KEY_USERS)));
                        String locale = c.getString(
                        String modelLocale = c.getString(
                                c.getColumnIndex(SoundModelContract.KEY_LOCALE));
                                c.getColumnIndex(SoundModelContract.KEY_LOCALE));
                        String text = c.getString(
                        String text = c.getString(
                                c.getColumnIndex(SoundModelContract.KEY_HINT_TEXT));
                                c.getColumnIndex(SoundModelContract.KEY_HINT_TEXT));
@@ -184,28 +189,37 @@ public class DatabaseHelper extends SQLiteOpenHelper {
                        // Only add keyphrases meant for the current user.
                        // Only add keyphrases meant for the current user.
                        if (users == null) {
                        if (users == null) {
                            // No users present in the keyphrase.
                            // 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;
                            continue;
                        }
                        }


                        boolean isAvailableForCurrentUser = false;
                        boolean isAvailableForCurrentUser = false;
                        int currentUser = mUserManager.getUserHandle();
                        for (int user : users) {
                        for (int user : users) {
                            if (currentUser == user) {
                            if (userHandle == user) {
                                isAvailableForCurrentUser = true;
                                isAvailableForCurrentUser = true;
                                break;
                                break;
                            }
                            }
                        }
                        }
                        if (!isAvailableForCurrentUser) {
                        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;
                            continue;
                        } else {
                            if (DBG) Slog.d(TAG, "Found a SoundModel for user: " + userHandle);
                        }
                        }


                        Keyphrase[] keyphrases = new Keyphrase[1];
                        Keyphrase[] keyphrases = new Keyphrase[1];
                        keyphrases[0] = new Keyphrase(
                        keyphrases[0] = new Keyphrase(
                                keyphraseId, recognitionModes, locale, text, users);
                                keyphraseId, recognitionModes, modelLocale, text, users);
                        return new KeyphraseSoundModel(UUID.fromString(modelUuid),
                        KeyphraseSoundModel model = new KeyphraseSoundModel(
                                UUID.fromString(modelUuid),
                                null /* FIXME use vendor UUID */, data, keyphrases);
                                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());
                    } while (c.moveToNext());
                }
                }
                Slog.w(TAG, "No SoundModel available for the given keyphrase");
                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) {
    private static String getCommaSeparatedString(int[] users) {
        if (users == null || users.length == 0) {
        if (users == null) {
            return "";
            return "";
        }
        }
        String csv = "";
        StringBuilder sb = new StringBuilder();
        for (int user : users) {
        for (int i = 0; i < users.length; i++) {
            csv += String.valueOf(user);
            if (i != 0) {
            csv += ",";
                sb.append(',');
            }
            sb.append(users[i]);
        }
        }
        return csv.substring(0, csv.length() - 1);
        return sb.toString();
    }
    }


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


    /**
    /**
     * Return codes for {@link #startRecognition(int, KeyphraseSoundModel,
     * 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
            if (mCurrentSoundModelHandle != INVALID_VALUE
                    && !soundModel.uuid.equals(mCurrentSoundModelUuid)) {
                    && (!soundModel.uuid.equals(mCurrentSoundModelUuid) || mStarted)) {
                Slog.w(TAG, "Unloading previous sound model");
                Slog.w(TAG, "Unloading previous sound model");
                int status = mModule.unloadSoundModel(mCurrentSoundModelHandle);
                int status = mModule.unloadSoundModel(mCurrentSoundModelHandle);
                if (status != SoundTrigger.STATUS_OK) {
                if (status != SoundTrigger.STATUS_OK) {
+30 −12
Original line number Original line Diff line number Diff line
@@ -446,7 +446,7 @@ public class VoiceInteractionManagerService extends SystemService {
        //----------------- Model management APIs --------------------------------//
        //----------------- Model management APIs --------------------------------//


        @Override
        @Override
        public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId) {
        public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, String bcp47Locale) {
            synchronized (this) {
            synchronized (this) {
                if (mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
                if (mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
                        != PackageManager.PERMISSION_GRANTED) {
                        != 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();
            final long caller = Binder.clearCallingIdentity();
            try {
            try {
                return mDbHelper.getKeyphraseSoundModel(keyphraseId);
                return mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUid, bcp47Locale);
            } finally {
            } finally {
                Binder.restoreCallingIdentity(caller);
                Binder.restoreCallingIdentity(caller);
            }
            }
@@ -495,7 +500,7 @@ public class VoiceInteractionManagerService extends SystemService {
        }
        }


        @Override
        @Override
        public int deleteKeyphraseSoundModel(int keyphraseId) {
        public int deleteKeyphraseSoundModel(int keyphraseId, String bcp47Locale) {
            synchronized (this) {
            synchronized (this) {
                if (mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
                if (mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
                        != PackageManager.PERMISSION_GRANTED) {
                        != 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();
            final long caller = Binder.clearCallingIdentity();
            boolean deleted = false;
            boolean deleted = false;
            try {
            try {
                KeyphraseSoundModel soundModel = mDbHelper.getKeyphraseSoundModel(keyphraseId);
                deleted = mDbHelper.deleteKeyphraseSoundModel(keyphraseId, callingUid, bcp47Locale);
                if (soundModel != null) {
                    deleted = mDbHelper.deleteKeyphraseSoundModel(soundModel.uuid);
                }
                return deleted ? SoundTriggerHelper.STATUS_OK : SoundTriggerHelper.STATUS_ERROR;
                return deleted ? SoundTriggerHelper.STATUS_OK : SoundTriggerHelper.STATUS_ERROR;
            } finally {
            } finally {
                if (deleted) {
                if (deleted) {
@@ -527,7 +535,8 @@ public class VoiceInteractionManagerService extends SystemService {


        //----------------- SoundTrigger APIs --------------------------------//
        //----------------- SoundTrigger APIs --------------------------------//
        @Override
        @Override
        public boolean isEnrolledForKeyphrase(IVoiceInteractionService service, int keyphraseId) {
        public boolean isEnrolledForKeyphrase(IVoiceInteractionService service, int keyphraseId,
                String bcp47Locale) {
            synchronized (this) {
            synchronized (this) {
                if (mImpl == null || mImpl.mService == null
                if (mImpl == null || mImpl.mService == null
                        || service.asBinder() != mImpl.mService.asBinder()) {
                        || 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();
            final long caller = Binder.clearCallingIdentity();
            try {
            try {
                KeyphraseSoundModel model = mDbHelper.getKeyphraseSoundModel(keyphraseId);
                KeyphraseSoundModel model =
                        mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUid, bcp47Locale);
                return model != null;
                return model != null;
            } finally {
            } finally {
                Binder.restoreCallingIdentity(caller);
                Binder.restoreCallingIdentity(caller);
@@ -566,7 +581,8 @@ public class VoiceInteractionManagerService extends SystemService {


        @Override
        @Override
        public int startRecognition(IVoiceInteractionService service, int keyphraseId,
        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.
            // Allow the call if this is the current voice interaction service.
            synchronized (this) {
            synchronized (this) {
                if (mImpl == null || mImpl.mService == null
                if (mImpl == null || mImpl.mService == null
@@ -575,14 +591,16 @@ public class VoiceInteractionManagerService extends SystemService {
                            "Caller is not the current voice interaction service");
                            "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");
                    throw new IllegalArgumentException("Illegal argument(s) in startRecognition");
                }
                }
            }
            }


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