OSDN Git Service

Make hotword availability a callback
authorSandeep Siddhartha <sansid@google.com>
Tue, 22 Jul 2014 16:49:49 +0000 (09:49 -0700)
committerSandeep Siddhartha <sansid@google.com>
Wed, 23 Jul 2014 00:11:52 +0000 (17:11 -0700)
This helps us make the list sound models operation an async one, it also helps us
with the case where a detector is invalidated, so the client doesn't have to keep checking the
state.

Synchronize DatabaseHelper methods on its instance so that other VoiceInteractionManagerService
calls aren't blocked on db writes/reads.
It's still possible for the list operation to be blocked on update and vice-versa

Change-Id: Ib8ec4ac5056b62d443038560ce31d0641b4627b0

api/current.txt
core/java/android/service/voice/AlwaysOnHotwordDetector.java
core/java/android/service/voice/VoiceInteractionService.java
services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java

index 2b2ede4..dab7718 100644 (file)
@@ -27330,7 +27330,6 @@ package android.service.trust {
 package android.service.voice {
 
   public class AlwaysOnHotwordDetector {
 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);
     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 {
   }
 
   public static abstract interface AlwaysOnHotwordDetector.Callback {
+    method public abstract void onAvailabilityChanged(int);
     method public abstract void onDetected(byte[]);
     method public abstract void onDetectionStopped();
   }
     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 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";
     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";
index 27a7b8e..d685cc5 100644 (file)
@@ -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.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;
 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 {
  * 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.
     /**
      * 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;
 
      */
     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;
     // 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";
             = 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;
 
     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 IVoiceInteractionManagerService mModelManagementService;
     private final SoundTriggerListener mInternalCallback;
     private final Callback mExternalCallback;
-    private final boolean mDisabled;
     private final Object mLock = new Object();
     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;
 
     /**
      * 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 {
         /**
 
     /**
      * 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
          * 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) {
             KeyphraseEnrollmentInfo keyphraseEnrollmentInfo,
             IVoiceInteractionService voiceInteractionService,
             IVoiceInteractionManagerService modelManagementService) {
-        mInvalidated = false;
         mText = text;
         mLocale = locale;
         mKeyphraseEnrollmentInfo = keyphraseEnrollmentInfo;
         mKeyphraseMetadata = mKeyphraseEnrollmentInfo.getKeyphraseMetadata(text, locale);
         mExternalCallback = callback;
         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;
         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.
     }
 
     /**
      * 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) {
      */
     public int getSupportedRecognitionModes() {
         synchronized (mLock) {
@@ -216,7 +214,9 @@ public class AlwaysOnHotwordDetector {
     }
 
     private int getSupportedRecognitionModesLocked() {
     }
 
     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");
         }
             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.
      *        {@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) {
      */
     public int startRecognition(int recognitionFlags) {
         synchronized (mLock) {
@@ -242,7 +242,8 @@ public class AlwaysOnHotwordDetector {
     }
 
     private int startRecognitionLocked(int recognitionFlags) {
     }
 
     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");
         }
             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.
      *
      * @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) {
      */
     public int stopRecognition() {
         synchronized (mLock) {
@@ -283,7 +284,8 @@ public class AlwaysOnHotwordDetector {
     }
 
     private int stopRecognitionLocked() {
     }
 
     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");
         }
             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.
      *        {@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) {
      */
     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");
         }
             throw new UnsupportedOperationException(
                     "Managing the given keyphrase is not supported");
         }
@@ -327,34 +331,6 @@ public class AlwaysOnHotwordDetector {
         return mKeyphraseEnrollmentInfo.getManageKeyphraseIntent(action, mText, mLocale);
     }
 
         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.
     /**
      * 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) {
      */
     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.
         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 */
     }
 
     /** @hide */
@@ -437,6 +398,9 @@ public class AlwaysOnHotwordDetector {
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
         @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;
                 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;
+        }
+    }
 }
 }
index 0c2ba26..82e23c4 100644 (file)
@@ -193,17 +193,6 @@ public class VoiceInteractionService extends Service {
                 mHotwordDetector.onSoundModelsChanged();
             }
         }
                 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() {
     }
 
     /**
     }
 
     /**
index 50be1dc..1e0d6de 100644 (file)
@@ -100,29 +100,32 @@ public class DatabaseHelper extends SQLiteOpenHelper {
     }
 
     public boolean addOrUpdateKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
     }
 
     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);
         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) {
      * 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() {
     }
 
     /**
      * 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 + "'";
         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 "";
         }
         if (users == null || users.length == 0) {
             return "";
         }
@@ -255,7 +262,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
         return csv.substring(0, csv.length() - 1);
     }
 
         return csv.substring(0, csv.length() - 1);
     }
 
-    private int[] getArrayForCommaSeparatedString(String text) {
+    private static int[] getArrayForCommaSeparatedString(String text) {
         if (TextUtils.isEmpty(text)) {
             return null;
         }
         if (TextUtils.isEmpty(text)) {
             return null;
         }
index 7b2e4f1..5d9e107 100644 (file)
@@ -266,13 +266,13 @@ public class VoiceInteractionManagerService extends SystemService {
                                 + Manifest.permission.MANAGE_VOICE_KEYPHRASES);
                     }
                 }
                                 + 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");
                 }
                 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();
                         }
                         // 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);
             }
         }
 
             }
         }
 
index e750bb6..cc710f9 100644 (file)
@@ -30,6 +30,12 @@ public class MainInteractionService extends VoiceInteractionService {
 
     private final Callback mHotwordCallback = new Callback() {
         @Override
 
     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");
         }
         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);
                 + 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
     }
 
     @Override
@@ -73,12 +68,13 @@ public class MainInteractionService extends VoiceInteractionService {
         return START_NOT_STICKY;
     }
 
         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");
         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");
                 break;
             case AlwaysOnHotwordDetector.STATE_HARDWARE_UNAVAILABLE:
                 Log.i(TAG, "STATE_HARDWARE_UNAVAILABLE");