diff options
6 files changed, 270 insertions, 222 deletions
diff --git a/api/current.txt b/api/current.txt index 2b2ede4..dab7718 100644 --- a/api/current.txt +++ b/api/current.txt @@ -27330,7 +27330,6 @@ package android.service.trust { package android.service.voice { public class AlwaysOnHotwordDetector { - method public int getAvailability(); method public android.content.Intent getManageIntent(int); method public int getSupportedRecognitionModes(); method public int startRecognition(int); @@ -27352,6 +27351,7 @@ package android.service.voice { } public static abstract interface AlwaysOnHotwordDetector.Callback { + method public abstract void onAvailabilityChanged(int); method public abstract void onDetected(byte[]); method public abstract void onDetectionStopped(); } @@ -27363,7 +27363,6 @@ package android.service.voice { method public android.os.IBinder onBind(android.content.Intent); method public void onReady(); method public void onShutdown(); - method public void onSoundModelsChanged(); method public void startSession(android.os.Bundle); field public static final java.lang.String SERVICE_INTERFACE = "android.service.voice.VoiceInteractionService"; field public static final java.lang.String SERVICE_META_DATA = "android.voice_interaction"; diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index 27a7b8e..d685cc5 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -27,6 +27,7 @@ import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra; import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; +import android.os.AsyncTask; import android.os.Handler; import android.os.Message; import android.os.RemoteException; @@ -41,7 +42,7 @@ import java.util.List; * always-on keyphrase detection APIs. */ public class AlwaysOnHotwordDetector { - //---- States of Keyphrase availability. Return codes for getAvailability() ----// + //---- States of Keyphrase availability. Return codes for onAvailabilityChanged() ----// /** * Indicates that this hotword detector is no longer valid for any recognition * and should not be used anymore. @@ -66,6 +67,11 @@ public class AlwaysOnHotwordDetector { */ public static final int STATE_KEYPHRASE_ENROLLED = 2; + /** + * Indicates that the detector isn't ready currently. + */ + private static final int STATE_NOT_READY = 0; + // Keyphrase management actions. Used in getManageIntent() ----// /** Indicates that we need to enroll. */ public static final int MANAGE_ACTION_ENROLL = 0; @@ -104,9 +110,12 @@ public class AlwaysOnHotwordDetector { = SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION; static final String TAG = "AlwaysOnHotwordDetector"; + // TODO: Set to false. + static final boolean DBG = true; - private static final int MSG_HOTWORD_DETECTED = 1; - private static final int MSG_DETECTION_STOPPED = 2; + private static final int MSG_STATE_CHANGED = 1; + private static final int MSG_HOTWORD_DETECTED = 2; + private static final int MSG_DETECTION_STOPPED = 3; private final String mText; private final String mLocale; @@ -120,21 +129,40 @@ public class AlwaysOnHotwordDetector { private final IVoiceInteractionManagerService mModelManagementService; private final SoundTriggerListener mInternalCallback; private final Callback mExternalCallback; - private final boolean mDisabled; private final Object mLock = new Object(); + private final Handler mHandler; /** * The sound model for the keyphrase, derived from the model management service * (IVoiceInteractionManagerService). May be null if the keyphrase isn't enrolled yet. */ private KeyphraseSoundModel mEnrolledSoundModel; - private boolean mInvalidated; + private int mAvailability = STATE_NOT_READY; /** * Callbacks for always-on hotword detection. */ public interface Callback { /** + * Called when the hotword availability changes. + * This indicates a change in the availability of recognition for the given keyphrase. + * It's called at least once with the initial availability.<p/> + * + * Availability implies whether the hardware on this system is capable of listening for + * the given keyphrase or not. <p/> + * If the return code is one of {@link #STATE_HARDWARE_UNAVAILABLE} or + * {@link #STATE_KEYPHRASE_UNSUPPORTED}, + * detection is not possible and no further interaction should be + * performed with this detector. <br/> + * If it is {@link #STATE_KEYPHRASE_UNENROLLED} the caller may choose to begin + * an enrollment flow for the keyphrase. <br/> + * and for {@link #STATE_KEYPHRASE_ENROLLED} a recognition can be started as desired. <p/> + * + * If the return code is {@link #STATE_INVALID}, this detector is stale. + * A new detector should be obtained for use in the future. + */ + void onAvailabilityChanged(int status); + /** * Called when the keyphrase is spoken. * * @param data Optional trigger audio data, if it was requested during @@ -160,54 +188,24 @@ public class AlwaysOnHotwordDetector { KeyphraseEnrollmentInfo keyphraseEnrollmentInfo, IVoiceInteractionService voiceInteractionService, IVoiceInteractionManagerService modelManagementService) { - mInvalidated = false; mText = text; mLocale = locale; mKeyphraseEnrollmentInfo = keyphraseEnrollmentInfo; mKeyphraseMetadata = mKeyphraseEnrollmentInfo.getKeyphraseMetadata(text, locale); mExternalCallback = callback; - mInternalCallback = new SoundTriggerListener(new MyHandler()); + mHandler = new MyHandler(); + mInternalCallback = new SoundTriggerListener(mHandler); mVoiceInteractionService = voiceInteractionService; mModelManagementService = modelManagementService; - if (mKeyphraseMetadata != null) { - mEnrolledSoundModel = internalGetKeyphraseSoundModelLocked(mKeyphraseMetadata.id); - } - int initialAvailability = internalGetAvailabilityLocked(); - mDisabled = (initialAvailability == STATE_HARDWARE_UNAVAILABLE) - || (initialAvailability == STATE_KEYPHRASE_UNSUPPORTED); - } - - /** - * Gets the state of always-on hotword detection for the given keyphrase and locale - * on this system. - * Availability implies that the hardware on this system is capable of listening for - * the given keyphrase or not. <p/> - * If the return code is one of {@link #STATE_HARDWARE_UNAVAILABLE} or - * {@link #STATE_KEYPHRASE_UNSUPPORTED}, no further interaction should be performed with this - * detector. <br/> - * If the state is {@link #STATE_KEYPHRASE_UNENROLLED} the caller may choose to begin - * an enrollment flow for the keyphrase. <br/> - * For {@value #STATE_KEYPHRASE_ENROLLED} a recognition can be started as desired. <br/> - * If the return code is {@link #STATE_INVALID}, this detector is stale and must not be used. - * A new detector should be obtained and used. - * - * @return Indicates if always-on hotword detection is available for the given keyphrase. - * The return code is one of {@link #STATE_HARDWARE_UNAVAILABLE}, - * {@link #STATE_KEYPHRASE_UNSUPPORTED}, {@link #STATE_KEYPHRASE_UNENROLLED}, - * {@link #STATE_KEYPHRASE_ENROLLED}, or {@link #STATE_INVALID}. - */ - public int getAvailability() { - synchronized (mLock) { - return internalGetAvailabilityLocked(); - } + new RefreshAvailabiltyTask().execute(); } /** * Gets the recognition modes supported by the associated keyphrase. * * @throws UnsupportedOperationException if the keyphrase itself isn't supported. - * Callers should check the availability by calling {@link #getAvailability()} - * before calling this method to avoid this exception. + * Callers should only call this method after a supported state callback on + * {@link Callback#onAvailabilityChanged(int)} to avoid this exception. */ public int getSupportedRecognitionModes() { synchronized (mLock) { @@ -216,7 +214,9 @@ public class AlwaysOnHotwordDetector { } private int getSupportedRecognitionModesLocked() { - if (mDisabled) { + // This method only makes sense if we can actually support a recognition. + if (mAvailability != STATE_KEYPHRASE_ENROLLED + && mAvailability != STATE_KEYPHRASE_UNENROLLED) { throw new UnsupportedOperationException( "Getting supported recognition modes for the keyphrase is not supported"); } @@ -232,8 +232,8 @@ public class AlwaysOnHotwordDetector { * {@link #RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO}. * @return {@link #STATUS_OK} if the call succeeds, an error code otherwise. * @throws UnsupportedOperationException if the recognition isn't supported. - * Callers should check the availability by calling {@link #getAvailability()} - * before calling this method to avoid this exception. + * Callers should only call this method after a supported state callback on + * {@link Callback#onAvailabilityChanged(int)} to avoid this exception. */ public int startRecognition(int recognitionFlags) { synchronized (mLock) { @@ -242,7 +242,8 @@ public class AlwaysOnHotwordDetector { } private int startRecognitionLocked(int recognitionFlags) { - if (internalGetAvailabilityLocked() != STATE_KEYPHRASE_ENROLLED) { + // This method only makes sense if we can start a recognition. + if (mAvailability != STATE_KEYPHRASE_ENROLLED) { throw new UnsupportedOperationException( "Recognition for the given keyphrase is not supported"); } @@ -273,8 +274,8 @@ public class AlwaysOnHotwordDetector { * * @return {@link #STATUS_OK} if the call succeeds, an error code otherwise. * @throws UnsupportedOperationException if the recognition isn't supported. - * Callers should check the availability by calling {@link #getAvailability()} - * before calling this method to avoid this exception. + * Callers should only call this method after a supported state callback on + * {@link Callback#onAvailabilityChanged(int)} to avoid this exception. */ public int stopRecognition() { synchronized (mLock) { @@ -283,7 +284,8 @@ public class AlwaysOnHotwordDetector { } private int stopRecognitionLocked() { - if (internalGetAvailabilityLocked() != STATE_KEYPHRASE_ENROLLED) { + // This method only makes sense if we can start a recognition. + if (mAvailability != STATE_KEYPHRASE_ENROLLED) { throw new UnsupportedOperationException( "Recognition for the given keyphrase is not supported"); } @@ -310,11 +312,13 @@ public class AlwaysOnHotwordDetector { * {@link #MANAGE_ACTION_UN_ENROLL}. * @return An {@link Intent} to manage the given keyphrase. * @throws UnsupportedOperationException if managing they keyphrase isn't supported. - * Callers should check the availability by calling {@link #getAvailability()} - * before calling this method to avoid this exception. + * Callers should only call this method after a supported state callback on + * {@link Callback#onAvailabilityChanged(int)} to avoid this exception. */ public Intent getManageIntent(int action) { - if (mDisabled) { + // This method only makes sense if we can actually support a recognition. + if (mAvailability != STATE_KEYPHRASE_ENROLLED + && mAvailability != STATE_KEYPHRASE_UNENROLLED) { throw new UnsupportedOperationException( "Managing the given keyphrase is not supported"); } @@ -327,34 +331,6 @@ public class AlwaysOnHotwordDetector { return mKeyphraseEnrollmentInfo.getManageKeyphraseIntent(action, mText, mLocale); } - private int internalGetAvailabilityLocked() { - if (mInvalidated) { - return STATE_INVALID; - } - - ModuleProperties dspModuleProperties = null; - try { - dspModuleProperties = - mModelManagementService.getDspModuleProperties(mVoiceInteractionService); - } catch (RemoteException e) { - Slog.w(TAG, "RemoteException in getDspProperties!"); - } - // No DSP available - if (dspModuleProperties == null) { - return STATE_HARDWARE_UNAVAILABLE; - } - // No enrollment application supports this keyphrase/locale - if (mKeyphraseMetadata == null) { - return STATE_KEYPHRASE_UNSUPPORTED; - } - - // This keyphrase hasn't been enrolled. - if (mEnrolledSoundModel == null) { - return STATE_KEYPHRASE_UNENROLLED; - } - return STATE_KEYPHRASE_ENROLLED; - } - /** * Invalidates this hotword detector so that any future calls to this result * in an IllegalStateException. @@ -363,7 +339,8 @@ public class AlwaysOnHotwordDetector { */ void invalidate() { synchronized (mLock) { - mInvalidated = true; + mAvailability = STATE_INVALID; + notifyStateChangedLocked(); } } @@ -376,38 +353,22 @@ public class AlwaysOnHotwordDetector { synchronized (mLock) { // TODO: This should stop the recognition if it was using an enrolled sound model // that's no longer available. - if (mKeyphraseMetadata != null) { - mEnrolledSoundModel = internalGetKeyphraseSoundModelLocked(mKeyphraseMetadata.id); + if (mAvailability == STATE_INVALID + || mAvailability == STATE_HARDWARE_UNAVAILABLE + || mAvailability == STATE_KEYPHRASE_UNSUPPORTED) { + Slog.w(TAG, "Received onSoundModelsChanged for an unsupported keyphrase/config"); + return; } + + // Execute a refresh availability task - which should then notify of a change. + new RefreshAvailabiltyTask().execute(); } } - /** - * @return The corresponding {@link KeyphraseSoundModel} or null if none is found. - */ - private KeyphraseSoundModel internalGetKeyphraseSoundModelLocked(int keyphraseId) { - List<KeyphraseSoundModel> soundModels; - try { - soundModels = mModelManagementService - .listRegisteredKeyphraseSoundModels(mVoiceInteractionService); - if (soundModels == null || soundModels.isEmpty()) { - Slog.i(TAG, "No available sound models for keyphrase ID: " + keyphraseId); - return null; - } - for (KeyphraseSoundModel soundModel : soundModels) { - if (soundModel.keyphrases == null) { - continue; - } - for (Keyphrase keyphrase : soundModel.keyphrases) { - if (keyphrase.id == keyphraseId) { - return soundModel; - } - } - } - } catch (RemoteException e) { - Slog.w(TAG, "RemoteException in listRegisteredKeyphraseSoundModels!"); - } - return null; + private void notifyStateChangedLocked() { + Message message = Message.obtain(mHandler, MSG_STATE_CHANGED); + message.arg1 = mAvailability; + message.sendToTarget(); } /** @hide */ @@ -437,6 +398,9 @@ public class AlwaysOnHotwordDetector { @Override public void handleMessage(Message msg) { switch (msg.what) { + case MSG_STATE_CHANGED: + mExternalCallback.onAvailabilityChanged(msg.arg1); + break; case MSG_HOTWORD_DETECTED: mExternalCallback.onDetected((byte[]) msg.obj); break; @@ -447,4 +411,95 @@ public class AlwaysOnHotwordDetector { } } } + + class RefreshAvailabiltyTask extends AsyncTask<Void, Void, Void> { + + @Override + public Void doInBackground(Void... params) { + int availability = internalGetInitialAvailability(); + KeyphraseSoundModel soundModel = null; + // Fetch the sound model if the availability is one of the supported ones. + if (availability == STATE_NOT_READY + || availability == STATE_KEYPHRASE_UNENROLLED + || availability == STATE_KEYPHRASE_ENROLLED) { + soundModel = + internalGetKeyphraseSoundModel(mKeyphraseMetadata.id); + if (soundModel == null) { + availability = STATE_KEYPHRASE_UNENROLLED; + } else { + availability = STATE_KEYPHRASE_ENROLLED; + } + } + + synchronized (mLock) { + if (DBG) { + Slog.d(TAG, "Hotword availability changed from " + mAvailability + + " -> " + availability); + } + mAvailability = availability; + mEnrolledSoundModel = soundModel; + notifyStateChangedLocked(); + } + return null; + } + + /** + * @return The initial availability without checking the enrollment status. + */ + private int internalGetInitialAvailability() { + synchronized (mLock) { + // This detector has already been invalidated. + if (mAvailability == STATE_INVALID) { + return STATE_INVALID; + } + } + + ModuleProperties dspModuleProperties = null; + try { + dspModuleProperties = + mModelManagementService.getDspModuleProperties(mVoiceInteractionService); + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException in getDspProperties!"); + } + // No DSP available + if (dspModuleProperties == null) { + return STATE_HARDWARE_UNAVAILABLE; + } + // No enrollment application supports this keyphrase/locale + if (mKeyphraseMetadata == null) { + return STATE_KEYPHRASE_UNSUPPORTED; + } + return STATE_NOT_READY; + } + + /** + * @return The corresponding {@link KeyphraseSoundModel} or null if none is found. + */ + private KeyphraseSoundModel internalGetKeyphraseSoundModel(int keyphraseId) { + List<KeyphraseSoundModel> soundModels; + try { + soundModels = mModelManagementService + .listRegisteredKeyphraseSoundModels(mVoiceInteractionService); + if (soundModels == null || soundModels.isEmpty()) { + Slog.i(TAG, "No available sound models for keyphrase ID: " + keyphraseId); + return null; + } + for (int i = 0; i < soundModels.size(); i++) { + KeyphraseSoundModel soundModel = soundModels.get(i); + if (soundModel.keyphrases == null || soundModel.keyphrases.length == 0) { + continue; + } + for (int j = 0; i < soundModel.keyphrases.length; j++) { + Keyphrase keyphrase = soundModel.keyphrases[j]; + if (keyphrase.id == keyphraseId) { + return soundModel; + } + } + } + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException in listRegisteredKeyphraseSoundModels!"); + } + return null; + } + } } diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 0c2ba26..82e23c4 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -193,17 +193,6 @@ public class VoiceInteractionService extends Service { mHotwordDetector.onSoundModelsChanged(); } } - onSoundModelsChanged(); - } - - /** - * Called when the sound models available for recognition change. - * This may be called if a new sound model is available or - * an existing one is updated or removed. - * Implementations must check the availability of the hotword detector if they own one - * by calling {@link AlwaysOnHotwordDetector#getAvailability()} before calling into it. - */ - public void onSoundModelsChanged() { } /** diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java index 50be1dc..1e0d6de 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java @@ -100,29 +100,32 @@ public class DatabaseHelper extends SQLiteOpenHelper { } public boolean addOrUpdateKeyphraseSoundModel(KeyphraseSoundModel soundModel) { - SQLiteDatabase db = getWritableDatabase(); - ContentValues values = new ContentValues(); - // Generate a random ID for the model. - values.put(SoundModelContract.KEY_ID, soundModel.uuid.toString()); - values.put(SoundModelContract.KEY_DATA, soundModel.data); - values.put(SoundModelContract.KEY_TYPE, SoundTrigger.SoundModel.TYPE_KEYPHRASE); - - boolean status = true; - if (db.insertWithOnConflict( - SoundModelContract.TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE) != -1) { - for (Keyphrase keyphrase : soundModel.keyphrases) { - status &= addOrUpdateKeyphrase(db, soundModel.uuid, keyphrase); + synchronized(this) { + SQLiteDatabase db = getWritableDatabase(); + ContentValues values = new ContentValues(); + // Generate a random ID for the model. + values.put(SoundModelContract.KEY_ID, soundModel.uuid.toString()); + values.put(SoundModelContract.KEY_DATA, soundModel.data); + values.put(SoundModelContract.KEY_TYPE, SoundTrigger.SoundModel.TYPE_KEYPHRASE); + + boolean status = true; + if (db.insertWithOnConflict(SoundModelContract.TABLE, null, values, + SQLiteDatabase.CONFLICT_REPLACE) != -1) { + for (Keyphrase keyphrase : soundModel.keyphrases) { + status &= addOrUpdateKeyphraseLocked(db, soundModel.uuid, keyphrase); + } + db.close(); + return status; + } else { + Slog.w(TAG, "Failed to persist sound model to database"); + db.close(); + return false; } - db.close(); - return status; - } else { - Slog.w(TAG, "Failed to persist sound model to database"); - db.close(); - return false; } } - private boolean addOrUpdateKeyphrase(SQLiteDatabase db, UUID modelId, Keyphrase keyphrase) { + private boolean addOrUpdateKeyphraseLocked( + SQLiteDatabase db, UUID modelId, Keyphrase keyphrase) { ContentValues values = new ContentValues(); values.put(KeyphraseContract.KEY_ID, keyphrase.id); values.put(KeyphraseContract.KEY_RECOGNITION_MODES, keyphrase.recognitionModes); @@ -143,62 +146,66 @@ public class DatabaseHelper extends SQLiteOpenHelper { * Deletes the sound model and associated keyphrases. */ public boolean deleteKeyphraseSoundModel(UUID uuid) { - SQLiteDatabase db = getWritableDatabase(); - String modelId = uuid.toString(); - String soundModelClause = SoundModelContract.KEY_ID + "=" + modelId; - boolean status = true; - if (db.delete(SoundModelContract.TABLE, soundModelClause, null) == 0) { - Slog.w(TAG, "No sound models deleted from the database"); - status = false; - } - String keyphraseClause = KeyphraseContract.KEY_SOUND_MODEL_ID + "=" + modelId; - if (db.delete(KeyphraseContract.TABLE, keyphraseClause, null) == 0) { - Slog.w(TAG, "No keyphrases deleted from the database"); - status = false; + synchronized(this) { + SQLiteDatabase db = getWritableDatabase(); + String modelId = uuid.toString(); + String soundModelClause = SoundModelContract.KEY_ID + "=" + modelId; + boolean status = true; + if (db.delete(SoundModelContract.TABLE, soundModelClause, null) == 0) { + Slog.w(TAG, "No sound models deleted from the database"); + status = false; + } + String keyphraseClause = KeyphraseContract.KEY_SOUND_MODEL_ID + "=" + modelId; + if (db.delete(KeyphraseContract.TABLE, keyphraseClause, null) == 0) { + Slog.w(TAG, "No keyphrases deleted from the database"); + status = false; + } + db.close(); + return status; } - db.close(); - return status; } /** * Lists all the keyphrase sound models currently registered with the system. */ public List<KeyphraseSoundModel> getKephraseSoundModels() { - List<KeyphraseSoundModel> models = new ArrayList<>(); - String selectQuery = "SELECT * FROM " + SoundModelContract.TABLE; - SQLiteDatabase db = getReadableDatabase(); - Cursor c = db.rawQuery(selectQuery, null); - - // looping through all rows and adding to list - if (c.moveToFirst()) { - do { - int type = c.getInt(c.getColumnIndex(SoundModelContract.KEY_TYPE)); - if (type != SoundTrigger.SoundModel.TYPE_KEYPHRASE) { - // Ignore non-keyphrase sound models. - continue; - } - String id = c.getString(c.getColumnIndex(SoundModelContract.KEY_ID)); - byte[] data = c.getBlob(c.getColumnIndex(SoundModelContract.KEY_DATA)); - // Get all the keyphrases for this this sound model. - // Validate the sound model. - if (id == null) { - Slog.w(TAG, "Ignoring sound model since it doesn't specify an ID"); - continue; - } - KeyphraseSoundModel model = new KeyphraseSoundModel( - UUID.fromString(id), data, getKeyphrasesForSoundModel(db, id)); - if (DBG) { - Slog.d(TAG, "Adding model: " + model); - } - models.add(model); - } while (c.moveToNext()); + synchronized(this) { + List<KeyphraseSoundModel> models = new ArrayList<>(); + String selectQuery = "SELECT * FROM " + SoundModelContract.TABLE; + SQLiteDatabase db = getReadableDatabase(); + Cursor c = db.rawQuery(selectQuery, null); + + // looping through all rows and adding to list + if (c.moveToFirst()) { + do { + int type = c.getInt(c.getColumnIndex(SoundModelContract.KEY_TYPE)); + if (type != SoundTrigger.SoundModel.TYPE_KEYPHRASE) { + // Ignore non-keyphrase sound models. + continue; + } + String id = c.getString(c.getColumnIndex(SoundModelContract.KEY_ID)); + byte[] data = c.getBlob(c.getColumnIndex(SoundModelContract.KEY_DATA)); + // Get all the keyphrases for this this sound model. + // Validate the sound model. + if (id == null) { + Slog.w(TAG, "Ignoring sound model since it doesn't specify an ID"); + continue; + } + KeyphraseSoundModel model = new KeyphraseSoundModel( + UUID.fromString(id), data, getKeyphrasesForSoundModelLocked(db, id)); + if (DBG) { + Slog.d(TAG, "Adding model: " + model); + } + models.add(model); + } while (c.moveToNext()); + } + c.close(); + db.close(); + return models; } - c.close(); - db.close(); - return models; } - private Keyphrase[] getKeyphrasesForSoundModel(SQLiteDatabase db, String modelId) { + private Keyphrase[] getKeyphrasesForSoundModelLocked(SQLiteDatabase db, String modelId) { List<Keyphrase> keyphrases = new ArrayList<>(); String selectQuery = "SELECT * FROM " + KeyphraseContract.TABLE + " WHERE " + KeyphraseContract.KEY_SOUND_MODEL_ID + " = '" + modelId + "'"; @@ -243,7 +250,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { } - private String getCommaSeparatedString(int[] users) { + private static String getCommaSeparatedString(int[] users) { if (users == null || users.length == 0) { return ""; } @@ -255,7 +262,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { return csv.substring(0, csv.length() - 1); } - private int[] getArrayForCommaSeparatedString(String text) { + private static int[] getArrayForCommaSeparatedString(String text) { if (TextUtils.isEmpty(text)) { return null; } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 7b2e4f1..5d9e107 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -266,13 +266,13 @@ public class VoiceInteractionManagerService extends SystemService { + Manifest.permission.MANAGE_VOICE_KEYPHRASES); } } + } - final long caller = Binder.clearCallingIdentity(); - try { - return mDbHelper.getKephraseSoundModels(); - } finally { - Binder.restoreCallingIdentity(caller); - } + final long caller = Binder.clearCallingIdentity(); + try { + return mDbHelper.getKephraseSoundModels(); + } finally { + Binder.restoreCallingIdentity(caller); } } @@ -287,29 +287,31 @@ public class VoiceInteractionManagerService extends SystemService { if (model == null) { throw new IllegalArgumentException("Model must not be null"); } + } - final long caller = Binder.clearCallingIdentity(); - try { - boolean success = false; - if (model.keyphrases == null) { - // If the keyphrases are not present in the model, delete the model. - success = mDbHelper.deleteKeyphraseSoundModel(model.uuid); - } else { - // Else update the model. - success = mDbHelper.addOrUpdateKeyphraseSoundModel(model); - } - if (success) { + final long caller = Binder.clearCallingIdentity(); + try { + boolean success = false; + if (model.keyphrases == null) { + // If the keyphrases are not present in the model, delete the model. + success = mDbHelper.deleteKeyphraseSoundModel(model.uuid); + } else { + // Else update the model. + success = mDbHelper.addOrUpdateKeyphraseSoundModel(model); + } + if (success) { + synchronized (this) { // Notify the voice interaction service of a change in sound models. if (mImpl != null && mImpl.mService != null) { mImpl.notifySoundModelsChangedLocked(); } - return SoundTriggerHelper.STATUS_OK; - } else { - return SoundTriggerHelper.STATUS_ERROR; } - } finally { - Binder.restoreCallingIdentity(caller); + return SoundTriggerHelper.STATUS_OK; + } else { + return SoundTriggerHelper.STATUS_ERROR; } + } finally { + Binder.restoreCallingIdentity(caller); } } diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java index e750bb6..cc710f9 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java @@ -30,6 +30,12 @@ public class MainInteractionService extends VoiceInteractionService { private final Callback mHotwordCallback = new Callback() { @Override + public void onAvailabilityChanged(int status) { + Log.i(TAG, "onAvailabilityChanged(" + status + ")"); + hotwordAvailabilityChangeHelper(status); + } + + @Override public void onDetected(byte[] data) { Log.i(TAG, "onDetected"); } @@ -51,17 +57,6 @@ public class MainInteractionService extends VoiceInteractionService { + Arrays.toString(getKeyphraseEnrollmentInfo().listKeyphraseMetadata())); mHotwordDetector = createAlwaysOnHotwordDetector("Hello There", "en-US", mHotwordCallback); - testHotwordAvailabilityStates(); - } - - @Override - public void onSoundModelsChanged() { - int availability = mHotwordDetector.getAvailability(); - Log.i(TAG, "Hotword availability = " + availability); - if (availability == AlwaysOnHotwordDetector.STATE_INVALID) { - mHotwordDetector = createAlwaysOnHotwordDetector("Hello There", "en-US", mHotwordCallback); - } - testHotwordAvailabilityStates(); } @Override @@ -73,12 +68,13 @@ public class MainInteractionService extends VoiceInteractionService { return START_NOT_STICKY; } - private void testHotwordAvailabilityStates() { - int availability = mHotwordDetector.getAvailability(); + private void hotwordAvailabilityChangeHelper(int availability) { Log.i(TAG, "Hotword availability = " + availability); switch (availability) { case AlwaysOnHotwordDetector.STATE_INVALID: Log.i(TAG, "STATE_INVALID"); + mHotwordDetector = + createAlwaysOnHotwordDetector("Hello There", "en-US", mHotwordCallback); break; case AlwaysOnHotwordDetector.STATE_HARDWARE_UNAVAILABLE: Log.i(TAG, "STATE_HARDWARE_UNAVAILABLE"); |
