Fix synchronization issues in AlwaysOnHotwordDetector

- Remove unnecessary recognition status from AlwaysOnHotwordDetector

- Remove unnecessary recognition started callback from IRecognitionStatusCallback

- Fix a bug around the fact that we weren't picking up enrollment at runtime because
we were storing the availability at instantiation time.

- Handle 0-length arrays in SoundTrigger classes while parceling/unparceling

- Fix issue in SoundTrigger helper where we were not comparing binders for start/stop calls

- Unload the previous model when starting a new recognition

- Add more debug logging

Change-Id: Icc56d7f3dd1ffa49a8cfeea49080e3ab4d342c32
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 @@
   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 @@
     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 @@
      */
     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 @@
             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 @@
                 dest.writeInt(users.length);
                 dest.writeIntArray(users);
             } else {
-                dest.writeInt(0);
+                dest.writeInt(-1);
             }
         }
 
@@ -349,7 +349,7 @@
             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 @@
                 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 @@
                     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 @@
                 dest.writeInt(data.length);
                 dest.writeByteArray(data);
             } else {
-                dest.writeInt(0);
+                dest.writeInt(-1);
             }
         }
 
@@ -494,6 +500,12 @@
         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.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 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 @@
     //---- 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 @@
      */
     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 @@
          */
         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 @@
         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 @@
         } else {
             mEnrolledSoundModel = null;
         }
-        mAvailability = internalGetAvailability();
+        int initialAvailability = internalGetAvailabilityLocked();
+        mDisabled = (initialAvailability == KEYPHRASE_HARDWARE_UNAVAILABLE)
+                || (initialAvailability == KEYPHRASE_UNSUPPORTED);
     }
 
     /**
@@ -186,7 +181,9 @@
      *         {@link #KEYPHRASE_ENROLLED}.
      */
     public int getAvailability() {
-        return mAvailability;
+        synchronized (this) {
+            return internalGetAvailabilityLocked();
+        }
     }
 
     /**
@@ -197,8 +194,13 @@
      *         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 @@
     }
 
     /**
-     * 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 @@
      *         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 @@
      *         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 @@
      *         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 @@
         return mKeyphraseEnrollmentInfo.getManageKeyphraseIntent(action, mText, mLocale);
     }
 
-    private int internalGetAvailability() {
+    private int internalGetAvailabilityLocked() {
         ModuleProperties dspModuleProperties = null;
         try {
             dspModuleProperties =
@@ -323,21 +321,16 @@
         }
         // 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 @@
                     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 @@
 
     /** @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);
-        }
-
-        @Override
-        public void onDetectionStarted() {
-            // TODO: Set the RECOGNITION_STATUS_ACTIVE flag here.
-            mCallback.onDetectionStarted();
+            Slog.i(TAG, "onDetected");
+            Message message = Message.obtain(mHandler, MSG_HOTWORD_DETECTED);
+            message.obj = data;
+            message.sendToTarget();
         }
 
         @Override
         public void onDetectionStopped() {
-            // TODO: Unset the RECOGNITION_STATUS_ACTIVE flag here.
-            mCallback.onDetectionStopped();
+            Slog.i(TAG, "onDetectionStopped");
+            mHandler.sendEmptyMessage(MSG_DETECTION_STOPPED);
+        }
+    }
+
+    class MyHandler extends Handler {
+        @Override
+        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.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 @@
  */
 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 @@
             + 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 @@
                 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 @@
                 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 @@
  */
 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 @@
             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 @@
      * @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 @@
         }
 
         @Override
-        public void onDetectionStarted() {
-            Log.i(TAG, "onDetectionStarted");
-        }
-
-        @Override
         public void onDetectionStopped() {
             Log.i(TAG, "onDetectionStopped");
         }