diff options
7 files changed, 151 insertions, 97 deletions
diff --git a/api/current.txt b/api/current.txt index 0140318..6dc2bb1 100644 --- a/api/current.txt +++ b/api/current.txt @@ -27222,7 +27222,6 @@ package android.service.voice { public class AlwaysOnHotwordDetector { method public int getAvailability(); method public android.content.Intent getManageIntent(int); - method public int getRecognitionStatus(); method public int getSupportedRecognitionModes(); method public int startRecognition(int); method public int stopRecognition(); @@ -27237,18 +27236,12 @@ package android.service.voice { field public static final int RECOGNITION_FLAG_NONE = 0; // 0x0 field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2 field public static final int RECOGNITION_MODE_VOICE_TRIGGER = 1; // 0x1 - field public static final int RECOGNITION_STATUS_ACTIVE = 16; // 0x10 - field public static final int RECOGNITION_STATUS_DISABLED_TEMPORARILY = 8; // 0x8 - field public static final int RECOGNITION_STATUS_NOT_AVAILABLE = 1; // 0x1 - field public static final int RECOGNITION_STATUS_NOT_REQUESTED = 2; // 0x2 - field public static final int RECOGNITION_STATUS_REQUESTED = 4; // 0x4 field public static final int STATUS_ERROR = -2147483648; // 0x80000000 field public static final int STATUS_OK = 0; // 0x0 } public static abstract interface AlwaysOnHotwordDetector.Callback { method public abstract void onDetected(byte[]); - method public abstract void onDetectionStarted(); method public abstract void onDetectionStopped(); } diff --git a/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl b/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl index 5738909..038d7ef 100644 --- a/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl +++ b/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl @@ -29,10 +29,6 @@ oneway interface IRecognitionStatusCallback { */ void onDetected(in byte[] data); /** - * Called when the detection for the associated keyphrase starts. - */ - void onDetectionStarted(); - /** * Called when the detection for the associated keyphrase stops. */ void onDetectionStopped(); diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java index 7b0a678..9a5cd9b 100644 --- a/core/java/android/hardware/soundtrigger/SoundTrigger.java +++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java @@ -247,7 +247,7 @@ public class SoundTrigger { String text = in.readString(); int[] users = null; int numUsers = in.readInt(); - if (numUsers > 0) { + if (numUsers >= 0) { users = new int[numUsers]; in.readIntArray(users); } @@ -264,7 +264,7 @@ public class SoundTrigger { dest.writeInt(users.length); dest.writeIntArray(users); } else { - dest.writeInt(0); + dest.writeInt(-1); } } @@ -349,7 +349,7 @@ public class SoundTrigger { UUID uuid = UUID.fromString(in.readString()); byte[] data = null; int dataLength = in.readInt(); - if (dataLength > 0) { + if (dataLength >= 0) { data = new byte[dataLength]; in.readByteArray(data); } @@ -369,10 +369,16 @@ public class SoundTrigger { dest.writeInt(data.length); dest.writeByteArray(data); } else { - dest.writeInt(0); + dest.writeInt(-1); } dest.writeTypedArray(keyphrases, 0); } + + @Override + public String toString() { + return "KeyphraseSoundModel [keyphrases=" + Arrays.toString(keyphrases) + ", uuid=" + + uuid + ", type=" + type + ", data? " + (data != null) + "]"; + } } /** @@ -471,7 +477,7 @@ public class SoundTrigger { in.createTypedArray(KeyphraseRecognitionExtra.CREATOR); byte[] data = null; int dataLength = in.readInt(); - if (dataLength > 0) { + if (dataLength >= 0) { data = new byte[dataLength]; in.readByteArray(data); } @@ -486,7 +492,7 @@ public class SoundTrigger { dest.writeInt(data.length); dest.writeByteArray(data); } else { - dest.writeInt(0); + dest.writeInt(-1); } } @@ -494,6 +500,12 @@ public class SoundTrigger { public int describeContents() { return 0; } + + @Override + public String toString() { + return "RecognitionConfig [captureRequested=" + captureRequested + ", keyphrases=" + + Arrays.toString(keyphrases) + ", data? " + (data != null) + "]"; + } } /** diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index c4ed8c5..ad30f44 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -27,6 +27,8 @@ 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.Handler; +import android.os.Message; import android.os.RemoteException; import android.util.Slog; @@ -72,18 +74,6 @@ public class AlwaysOnHotwordDetector { public static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR; public static final int STATUS_OK = SoundTrigger.STATUS_OK; - //---- Keyphrase recognition status ----// - /** Indicates that recognition is not available. */ - public static final int RECOGNITION_STATUS_NOT_AVAILABLE = 0x01; - /** Indicates that recognition has not been requested. */ - public static final int RECOGNITION_STATUS_NOT_REQUESTED = 0x02; - /** Indicates that recognition has been requested. */ - public static final int RECOGNITION_STATUS_REQUESTED = 0x04; - /** Indicates that recognition has been temporarily disabled. */ - public static final int RECOGNITION_STATUS_DISABLED_TEMPORARILY = 0x08; - /** Indicates that recognition is currently active . */ - public static final int RECOGNITION_STATUS_ACTIVE = 0x10; - //-- Flags for startRecogntion ----// /** Empty flag for {@link #startRecognition(int)}. */ public static final int RECOGNITION_FLAG_NONE = 0; @@ -96,15 +86,22 @@ public class AlwaysOnHotwordDetector { //---- Recognition mode flags ----// // Must be kept in sync with the related attribute defined as searchKeyphraseRecognitionFlags. - /** Simple recognition of the key phrase. Returned by {@link #getRecognitionStatus()} */ + /** + * Simple recognition of the key phrase. Returned by {@link #getSupportedRecognitionModes()} + */ public static final int RECOGNITION_MODE_VOICE_TRIGGER = SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER; - /** Trigger only if one user is identified. Returned by {@link #getRecognitionStatus()} */ + /** + * Trigger only if one user is identified. Returned by {@link #getSupportedRecognitionModes()} + */ public static final int RECOGNITION_MODE_USER_IDENTIFICATION = SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION; static final String TAG = "AlwaysOnHotwordDetector"; + private static final int MSG_HOTWORD_DETECTED = 1; + private static final int MSG_DETECTION_STOPPED = 2; + private final String mText; private final String mLocale; /** @@ -118,12 +115,11 @@ public class AlwaysOnHotwordDetector { */ private final KeyphraseSoundModel mEnrolledSoundModel; private final KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo; - private final int mAvailability; private final IVoiceInteractionService mVoiceInteractionService; private final IVoiceInteractionManagerService mModelManagementService; private final SoundTriggerListener mInternalCallback; - - private int mRecognitionState; + private final Callback mExternalCallback; + private final boolean mDisabled; /** * Callbacks for always-on hotword detection. @@ -137,10 +133,6 @@ public class AlwaysOnHotwordDetector { */ void onDetected(byte[] data); /** - * Called when the detection for the associated keyphrase starts. - */ - void onDetectionStarted(); - /** * Called when the detection for the associated keyphrase stops. */ void onDetectionStopped(); @@ -163,7 +155,8 @@ public class AlwaysOnHotwordDetector { mLocale = locale; mKeyphraseEnrollmentInfo = keyphraseEnrollmentInfo; mKeyphraseMetadata = mKeyphraseEnrollmentInfo.getKeyphraseMetadata(text, locale); - mInternalCallback = new SoundTriggerListener(callback); + mExternalCallback = callback; + mInternalCallback = new SoundTriggerListener(new MyHandler()); mVoiceInteractionService = voiceInteractionService; mModelManagementService = modelManagementService; if (mKeyphraseMetadata != null) { @@ -171,7 +164,9 @@ public class AlwaysOnHotwordDetector { } else { mEnrolledSoundModel = null; } - mAvailability = internalGetAvailability(); + int initialAvailability = internalGetAvailabilityLocked(); + mDisabled = (initialAvailability == KEYPHRASE_HARDWARE_UNAVAILABLE) + || (initialAvailability == KEYPHRASE_UNSUPPORTED); } /** @@ -186,7 +181,9 @@ public class AlwaysOnHotwordDetector { * {@link #KEYPHRASE_ENROLLED}. */ public int getAvailability() { - return mAvailability; + synchronized (this) { + return internalGetAvailabilityLocked(); + } } /** @@ -197,8 +194,13 @@ public class AlwaysOnHotwordDetector { * before calling this method to avoid this exception. */ public int getSupportedRecognitionModes() { - if (mAvailability == KEYPHRASE_HARDWARE_UNAVAILABLE - || mAvailability == KEYPHRASE_UNSUPPORTED) { + synchronized (this) { + return getSupportedRecognitionModesLocked(); + } + } + + private int getSupportedRecognitionModesLocked() { + if (mDisabled) { throw new UnsupportedOperationException( "Getting supported recognition modes for the keyphrase is not supported"); } @@ -207,17 +209,6 @@ public class AlwaysOnHotwordDetector { } /** - * Gets the status of the recognition. - * @return A flag comprised of {@link #RECOGNITION_STATUS_NOT_AVAILABLE}, - * {@link #RECOGNITION_STATUS_NOT_REQUESTED}, {@link #RECOGNITION_STATUS_REQUESTED}, - * {@link #RECOGNITION_STATUS_DISABLED_TEMPORARILY} and - * {@link #RECOGNITION_STATUS_ACTIVE}. - */ - public int getRecognitionStatus() { - return mRecognitionState; - } - - /** * Starts recognition for the associated keyphrase. * * @param recognitionFlags The flags to control the recognition properties. @@ -229,16 +220,19 @@ public class AlwaysOnHotwordDetector { * before calling this method to avoid this exception. */ public int startRecognition(int recognitionFlags) { - if (mAvailability != KEYPHRASE_ENROLLED - || (mRecognitionState&RECOGNITION_STATUS_NOT_AVAILABLE) != 0) { + synchronized (this) { + return startRecognitionLocked(recognitionFlags); + } + } + + private int startRecognitionLocked(int recognitionFlags) { + if (internalGetAvailabilityLocked() != KEYPHRASE_ENROLLED) { throw new UnsupportedOperationException( "Recognition for the given keyphrase is not supported"); } - mRecognitionState &= RECOGNITION_STATUS_REQUESTED; KeyphraseRecognitionExtra[] recognitionExtra = new KeyphraseRecognitionExtra[1]; // TODO: Do we need to do something about the confidence level here? - // TODO: Take in captureTriggerAudio as a method param here. recognitionExtra[0] = new KeyphraseRecognitionExtra(mKeyphraseMetadata.id, mKeyphraseMetadata.recognitionModeFlags, new ConfidenceLevel[0]); boolean captureTriggerAudio = @@ -267,12 +261,17 @@ public class AlwaysOnHotwordDetector { * before calling this method to avoid this exception. */ public int stopRecognition() { - if (mAvailability != KEYPHRASE_ENROLLED) { + synchronized (this) { + return stopRecognitionLocked(); + } + } + + private synchronized int stopRecognitionLocked() { + if (internalGetAvailabilityLocked() != KEYPHRASE_ENROLLED) { throw new UnsupportedOperationException( "Recognition for the given keyphrase is not supported"); } - mRecognitionState &= ~RECOGNITION_STATUS_NOT_REQUESTED; int code = STATUS_ERROR; try { code = mModelManagementService.stopRecognition( @@ -299,8 +298,7 @@ public class AlwaysOnHotwordDetector { * before calling this method to avoid this exception. */ public Intent getManageIntent(int action) { - if (mAvailability == KEYPHRASE_HARDWARE_UNAVAILABLE - || mAvailability == KEYPHRASE_UNSUPPORTED) { + if (mDisabled) { throw new UnsupportedOperationException( "Managing the given keyphrase is not supported"); } @@ -313,7 +311,7 @@ public class AlwaysOnHotwordDetector { return mKeyphraseEnrollmentInfo.getManageKeyphraseIntent(action, mText, mLocale); } - private int internalGetAvailability() { + private int internalGetAvailabilityLocked() { ModuleProperties dspModuleProperties = null; try { dspModuleProperties = @@ -323,21 +321,16 @@ public class AlwaysOnHotwordDetector { } // No DSP available if (dspModuleProperties == null) { - mRecognitionState = RECOGNITION_STATUS_NOT_AVAILABLE; return KEYPHRASE_HARDWARE_UNAVAILABLE; } // No enrollment application supports this keyphrase/locale if (mKeyphraseMetadata == null) { - mRecognitionState = RECOGNITION_STATUS_NOT_AVAILABLE; return KEYPHRASE_UNSUPPORTED; } // This keyphrase hasn't been enrolled. if (mEnrolledSoundModel == null) { - mRecognitionState = RECOGNITION_STATUS_NOT_AVAILABLE; return KEYPHRASE_UNENROLLED; } - // Mark recognition as available - mRecognitionState &= ~RECOGNITION_STATUS_NOT_AVAILABLE; return KEYPHRASE_ENROLLED; } @@ -358,7 +351,6 @@ public class AlwaysOnHotwordDetector { continue; } for (Keyphrase keyphrase : soundModel.keyphrases) { - // TODO: Check the user handle here to only load a model for the current user. if (keyphrase.id == keyphraseId) { return soundModel; } @@ -372,28 +364,39 @@ public class AlwaysOnHotwordDetector { /** @hide */ static final class SoundTriggerListener extends IRecognitionStatusCallback.Stub { - private final Callback mCallback; + private final Handler mHandler; - public SoundTriggerListener(Callback callback) { - this.mCallback = callback; + public SoundTriggerListener(Handler handler) { + mHandler = handler; } @Override public void onDetected(byte[] data) { - Slog.i(TAG, "onKeyphraseSpoken"); - mCallback.onDetected(data); + Slog.i(TAG, "onDetected"); + Message message = Message.obtain(mHandler, MSG_HOTWORD_DETECTED); + message.obj = data; + message.sendToTarget(); } @Override - public void onDetectionStarted() { - // TODO: Set the RECOGNITION_STATUS_ACTIVE flag here. - mCallback.onDetectionStarted(); + public void onDetectionStopped() { + Slog.i(TAG, "onDetectionStopped"); + mHandler.sendEmptyMessage(MSG_DETECTION_STOPPED); } + } + class MyHandler extends Handler { @Override - public void onDetectionStopped() { - // TODO: Unset the RECOGNITION_STATUS_ACTIVE flag here. - mCallback.onDetectionStopped(); + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_HOTWORD_DETECTED: + mExternalCallback.onDetected((byte[]) msg.obj); + break; + case MSG_DETECTION_STOPPED: + mExternalCallback.onDetectionStopped(); + default: + super.handleMessage(msg); + } } } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java index eed2d44..50be1dc 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java @@ -24,6 +24,7 @@ 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; @@ -38,6 +39,7 @@ import java.util.UUID; */ public class DatabaseHelper extends SQLiteOpenHelper { static final String TAG = "SoundModelDBHelper"; + static final boolean DBG = false; private static final String NAME = "sound_model.db"; private static final int VERSION = 2; @@ -75,8 +77,11 @@ public class DatabaseHelper extends SQLiteOpenHelper { + SoundModelContract.KEY_TYPE + " INTEGER," + SoundModelContract.KEY_DATA + " BLOB" + ")"; + private final UserManager mUserManager; + public DatabaseHelper(Context context) { super(context, NAME, null, VERSION); + mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); } @Override @@ -175,8 +180,17 @@ public class DatabaseHelper extends SQLiteOpenHelper { 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. - models.add(new KeyphraseSoundModel( - UUID.fromString(id), data, getKeyphrasesForSoundModel(db, id))); + // 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()); } c.close(); @@ -200,6 +214,25 @@ public class DatabaseHelper extends SQLiteOpenHelper { String locale = c.getString(c.getColumnIndex(KeyphraseContract.KEY_LOCALE)); String hintText = c.getString(c.getColumnIndex(KeyphraseContract.KEY_HINT_TEXT)); + // 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"); + continue; + } + boolean isAvailableForCurrentUser = false; + int currentUser = mUserManager.getUserHandle(); + for (int user : users) { + if (currentUser == user) { + isAvailableForCurrentUser = true; + break; + } + } + if (!isAvailableForCurrentUser) { + Slog.w(TAG, "Ignoring keyphrase since it's not for the current user"); + continue; + } + keyphrases.add(new Keyphrase(id, modes, locale, hintText, users)); } while (c.moveToNext()); } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java index 6842f7d..4430586 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java @@ -38,8 +38,9 @@ import java.util.ArrayList; */ public class SoundTriggerHelper implements SoundTrigger.StatusListener { static final String TAG = "SoundTriggerHelper"; + static final boolean DBG = false; // TODO: Remove this. - static final int TEMP_KEYPHRASE_ID = 1; + static final int TEMP_KEYPHRASE_ID = 100; /** * Return codes for {@link #startRecognition(int, KeyphraseSoundModel, @@ -101,20 +102,32 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { KeyphraseSoundModel soundModel, IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) { + if (DBG) { + Slog.d(TAG, "startRecognition for keyphraseId=" + keyphraseId + + " soundModel=" + soundModel + ", listener=" + listener + + ", recognitionConfig=" + recognitionConfig); + Slog.d(TAG, "moduleProperties=" + moduleProperties); + Slog.d(TAG, "# of current listeners=" + mActiveListeners.size()); + Slog.d(TAG, "mCurrentSoundModelHandle=" + mCurrentSoundModelHandle); + } if (moduleProperties == null || mModule == null) { Slog.w(TAG, "Attempting startRecognition without the capability"); return STATUS_ERROR; } + if (mCurrentSoundModelHandle != INVALID_SOUND_MODEL_HANDLE) { + Slog.w(TAG, "Canceling previous recognition"); + // TODO: Inspect the return codes here. + mModule.unloadSoundModel(mCurrentSoundModelHandle); + mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE; + } + + // If the previous recognition was by a different listener, + // Notify them that it was stopped. IRecognitionStatusCallback oldListener = mActiveListeners.get(keyphraseId); - if (oldListener != null && oldListener != listener) { - if (mCurrentSoundModelHandle != INVALID_SOUND_MODEL_HANDLE) { - Slog.w(TAG, "Canceling previous recognition"); - // TODO: Inspect the return codes here. - mModule.unloadSoundModel(mCurrentSoundModelHandle); - } + if (oldListener != null && oldListener.asBinder() != listener.asBinder()) { try { - mActiveListeners.get(keyphraseId).onDetectionStopped(); + oldListener.onDetectionStopped(); } catch (RemoteException e) { Slog.w(TAG, "RemoteException in onDetectionStopped"); } @@ -159,17 +172,26 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. */ synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) { + if (DBG) { + Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId + + ", listener=" + listener); + Slog.d(TAG, "# of current listeners = " + mActiveListeners.size()); + } + if (moduleProperties == null || mModule == null) { Slog.w(TAG, "Attempting stopRecognition without the capability"); return STATUS_ERROR; } IRecognitionStatusCallback currentListener = mActiveListeners.get(keyphraseId); - if (currentListener == null) { + if (listener == null) { + Slog.w(TAG, "Attempting stopRecognition without a valid listener"); + return STATUS_ERROR; + } if (currentListener == null) { // startRecognition hasn't been called or it failed. Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition"); return STATUS_ERROR; - } else if (currentListener != listener) { + } else if (currentListener.asBinder() != listener.asBinder()) { // TODO: Figure out if this should match the listener that was passed in during // startRecognition, or should we allow a different listener to stop the recognition, // in which case we don't need to pass in a listener here. diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java index edb28ea..e74307f 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java @@ -35,11 +35,6 @@ public class MainInteractionService extends VoiceInteractionService { } @Override - public void onDetectionStarted() { - Log.i(TAG, "onDetectionStarted"); - } - - @Override public void onDetectionStopped() { Log.i(TAG, "onDetectionStopped"); } |
