am 7573ba5f: am 82d56d82: Merge "Make hotword availability a callback" into lmp-dev
* commit '7573ba5f00c5c7470942bae5d5c8a0cecd002333': Make hotword availability a callback
This commit is contained in:
@@ -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";
|
||||
|
||||
@@ -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,20 +129,39 @@ 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.
|
||||
*
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,12 @@ public class MainInteractionService extends VoiceInteractionService {
|
||||
static final String TAG = "MainInteractionService";
|
||||
|
||||
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");
|
||||
|
||||
Reference in New Issue
Block a user