Merge "SoundTriggerHelper: improve in call behavior" into rvc-dev
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 6c0f2b0..edbdd4e 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -40,6 +40,9 @@
 import android.hardware.soundtrigger.SoundTriggerModule;
 import android.os.Binder;
 import android.os.DeadObjectException;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.os.PowerManager;
 import android.os.PowerManager.ServiceType;
 import android.os.RemoteException;
@@ -113,6 +116,13 @@
 
     private PowerSaveModeListener mPowerSaveModeListener;
 
+    // Handler to process call state changes will delay to allow time for the audio
+    // and sound trigger HALs to process the end of call notifications
+    // before we re enable pending recognition requests.
+    private final Handler mHandler;
+    private static final int MSG_CALL_STATE_CHANGED = 0;
+    private static final int CALL_INACTIVE_MSG_DELAY_MS = 1000;
+
     SoundTriggerHelper(Context context) {
         ArrayList <ModuleProperties> modules = new ArrayList<>();
         int status = SoundTrigger.listModules(modules);
@@ -130,6 +140,31 @@
             // TODO: Figure out how to determine which module corresponds to the DSP hardware.
             mModuleProperties = modules.get(0);
         }
+
+        Looper looper = Looper.myLooper();
+        if (looper == null) {
+            looper = Looper.getMainLooper();
+        }
+        if (looper != null) {
+            mHandler = new Handler(looper) {
+                @Override
+                public void handleMessage(Message msg) {
+                    switch (msg.what) {
+                        case MSG_CALL_STATE_CHANGED:
+                            synchronized (mLock) {
+                                onCallStateChangedLocked(
+                                        TelephonyManager.CALL_STATE_OFFHOOK == msg.arg1);
+                            }
+                            break;
+                        default:
+                            Slog.e(TAG, "unknown message in handler:" + msg.what);
+                            break;
+                    }
+                }
+            };
+        } else {
+            mHandler = null;
+        }
     }
 
     /**
@@ -227,6 +262,37 @@
         return status;
     }
 
+    private int prepareForRecognition(ModelData modelData) {
+        if (mModule == null) {
+            mModule = SoundTrigger.attachModule(mModuleProperties.getId(), this, null);
+            if (mModule == null) {
+                Slog.w(TAG, "prepareForRecognition: cannot attach to sound trigger module");
+                return STATUS_ERROR;
+            }
+        }
+        // Load the model if it is not loaded.
+        if (!modelData.isModelLoaded()) {
+            // Before we try and load this model, we should first make sure that any other
+            // models that don't have an active recognition/dead callback are unloaded. Since
+            // there is a finite limit on the number of models that the hardware may be able to
+            // have loaded, we want to make sure there's room for our model.
+            stopAndUnloadDeadModelsLocked();
+            int[] handle = new int[] { 0 };
+            int status = mModule.loadSoundModel(modelData.getSoundModel(), handle);
+            if (status != SoundTrigger.STATUS_OK) {
+                Slog.w(TAG, "prepareForRecognition: loadSoundModel failed with status: " + status);
+                return status;
+            }
+            modelData.setHandle(handle[0]);
+            modelData.setLoaded();
+            if (DBG) {
+                Slog.d(TAG, "prepareForRecognition: Sound model loaded with handle:" + handle[0]);
+            }
+        }
+        return STATUS_OK;
+    }
+
+
     /**
      * Starts recognition for the given sound model. A single routine for both keyphrase and
      * generic sound models.
@@ -248,12 +314,17 @@
                 Slog.w(TAG, "Attempting startRecognition without the capability");
                 return STATUS_ERROR;
             }
-            if (mModule == null) {
-                mModule = SoundTrigger.attachModule(mModuleProperties.getId(), this, null);
-                if (mModule == null) {
-                    Slog.w(TAG, "startRecognition cannot attach to sound trigger module");
-                    return STATUS_ERROR;
+
+            IRecognitionStatusCallback oldCallback = modelData.getCallback();
+            if (oldCallback != null && oldCallback.asBinder() != callback.asBinder()) {
+                Slog.w(TAG, "Canceling previous recognition for model id: "
+                        + modelData.getModelId());
+                try {
+                    oldCallback.onError(STATUS_ERROR);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "RemoteException in onDetectionStopped", e);
                 }
+                modelData.clearCallback();
             }
 
             // If the existing SoundModel is different (for the same UUID for Generic and same
@@ -287,48 +358,25 @@
                 }
             }
 
-            IRecognitionStatusCallback oldCallback = modelData.getCallback();
-            if (oldCallback != null && oldCallback.asBinder() != callback.asBinder()) {
-                Slog.w(TAG, "Canceling previous recognition for model id: " +
-                        modelData.getModelId());
-                try {
-                    oldCallback.onError(STATUS_ERROR);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "RemoteException in onDetectionStopped", e);
-                }
-                modelData.clearCallback();
-            }
-
-            // Load the model if it is not loaded.
-            if (!modelData.isModelLoaded()) {
-                // Before we try and load this model, we should first make sure that any other
-                // models that don't have an active recognition/dead callback are unloaded. Since
-                // there is a finite limit on the number of models that the hardware may be able to
-                // have loaded, we want to make sure there's room for our model.
-                stopAndUnloadDeadModelsLocked();
-                int[] handle = new int[] { INVALID_VALUE };
-                int status = mModule.loadSoundModel(soundModel, handle);
-                if (status != SoundTrigger.STATUS_OK) {
-                    Slog.w(TAG, "loadSoundModel call failed with " + status);
-                    return status;
-                }
-                if (handle[0] == INVALID_VALUE) {
-                    Slog.w(TAG, "loadSoundModel call returned invalid sound model handle");
-                    return STATUS_ERROR;
-                }
-                modelData.setHandle(handle[0]);
-                modelData.setLoaded();
-                Slog.d(TAG, "Sound model loaded with handle:" + handle[0]);
-            }
             modelData.setCallback(callback);
             modelData.setRequested(true);
             modelData.setRecognitionConfig(recognitionConfig);
             modelData.setSoundModel(soundModel);
 
-            int status = startRecognitionLocked(modelData,
+            if (!isRecognitionAllowed()) {
+                initializeTelephonyAndPowerStateListeners();
+                return STATUS_OK;
+            }
+
+            int status = prepareForRecognition(modelData);
+            if (status != STATUS_OK) {
+                Slog.w(TAG, "startRecognition failed to prepare model for recognition");
+                return status;
+            }
+            status = startRecognitionLocked(modelData,
                     false /* Don't notify for synchronous calls */);
 
-                                // Initialize power save, call active state monitoring logic.
+            // Initialize power save, call active state monitoring logic.
             if (status == STATUS_OK) {
                 initializeTelephonyAndPowerStateListeners();
             }
@@ -398,7 +446,8 @@
             if (DBG) {
                 Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId + ", callback =" +
                         callback.asBinder());
-                Slog.d(TAG, "current callback=" + (modelData == null ? "null" :
+                Slog.d(TAG, "current callback="
+                        + ((modelData == null || modelData.getCallback() == null) ? "null" :
                             modelData.getCallback().asBinder()));
             }
             int status = stopRecognition(modelData, callback);
@@ -507,8 +556,8 @@
         synchronized (mLock) {
             MetricsLogger.count(mContext, "sth_unload_keyphrase_sound_model", 1);
             ModelData modelData = getKeyphraseModelDataLocked(keyphraseId);
-            if (mModule == null || modelData == null || modelData.getHandle() == INVALID_VALUE ||
-                    !modelData.isKeyphraseModel()) {
+            if (mModule == null || modelData == null || !modelData.isModelLoaded()
+                    || !modelData.isKeyphraseModel()) {
                 return STATUS_ERROR;
             }
 
@@ -943,6 +992,10 @@
             return STATUS_OK;
         }
         if (start) {
+            int status = prepareForRecognition(model);
+            if (status != STATUS_OK) {
+                return status;
+            }
             return startRecognitionLocked(model, notify);
         } else {
             return stopRecognitionLocked(model, notify);
@@ -992,8 +1045,15 @@
         @Override
         public void onCallStateChanged(int state, String arg1) {
             if (DBG) Slog.d(TAG, "onCallStateChanged: " + state);
-            synchronized (mLock) {
-                onCallStateChangedLocked(TelephonyManager.CALL_STATE_OFFHOOK == state);
+
+            if (mHandler != null) {
+                synchronized (mLock) {
+                    mHandler.removeMessages(MSG_CALL_STATE_CHANGED);
+                    Message msg = mHandler.obtainMessage(MSG_CALL_STATE_CHANGED, state, 0);
+                    mHandler.sendMessageDelayed(
+                            msg, (TelephonyManager.CALL_STATE_OFFHOOK == state) ? 0
+                                    : CALL_INACTIVE_MSG_DELAY_MS);
+                }
             }
         }
     }
@@ -1216,9 +1276,8 @@
     // models.
     private int startRecognitionLocked(ModelData modelData, boolean notify) {
         IRecognitionStatusCallback callback = modelData.getCallback();
-        int handle = modelData.getHandle();
         RecognitionConfig config = modelData.getRecognitionConfig();
-        if (callback == null || handle == INVALID_VALUE || config == null) {
+        if (callback == null || !modelData.isModelLoaded() || config == null) {
             // Nothing to do here.
             Slog.w(TAG, "startRecognition: Bad data passed in.");
             MetricsLogger.count(mContext, "sth_start_recognition_error", 1);
@@ -1235,7 +1294,7 @@
         if (mModule == null) {
             return STATUS_ERROR;
         }
-        int status = mModule.startRecognition(handle, config);
+        int status = mModule.startRecognition(modelData.getHandle(), config);
         if (status != SoundTrigger.STATUS_OK) {
             Slog.w(TAG, "startRecognition failed with " + status);
             MetricsLogger.count(mContext, "sth_start_recognition_error", 1);
@@ -1376,7 +1435,7 @@
 
         // Model handle is an integer used by the HAL as an identifier for sound
         // models.
-        private int mModelHandle = INVALID_VALUE;
+        private int mModelHandle;
 
         // The SoundModel instance, one of KeyphraseSoundModel or GenericSoundModel.
         private SoundModel mSoundModel = null;
@@ -1436,7 +1495,6 @@
 
         synchronized void clearState() {
             mModelState = MODEL_NOTLOADED;
-            mModelHandle = INVALID_VALUE;
             mRecognitionConfig = null;
             mRequested = false;
             mCallback = null;