Unload logic for generic and keyphrase sound models.

 When delete is called, now the STH unloads the corresponding models.

Fix bug with keyphrase where a delete call didn't unload (or stop)
the keyphrase model.

Bug: 27279380
Change-Id: Ia34f713d2aecef4102c0f0ccc57b8d2e5febe4bb
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index b4c4bf8..7ee7423 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -199,6 +199,7 @@
                 }
                 modelData.setHandle(handle[0]);
                 modelData.setLoaded();
+                Slog.d(TAG, "Generic sound model loaded with handle:" + handle[0]);
             }
             modelData.setCallback(callback);
             modelData.setRecognitionConfig(recognitionConfig);
@@ -227,7 +228,7 @@
 
         synchronized (mLock) {
             if (DBG) {
-                Slog.d(TAG, "startRecognition for keyphraseId=" + keyphraseId
+                Slog.d(TAG, "startKeyphraseRecognition for keyphraseId=" + keyphraseId
                         + " soundModel=" + soundModel + ", listener=" + listener.asBinder()
                         + ", recognitionConfig=" + recognitionConfig);
                 Slog.d(TAG, "moduleProperties=" + mModuleProperties);
@@ -243,13 +244,13 @@
             }
 
             if (mModuleProperties == null) {
-                Slog.w(TAG, "Attempting startRecognition without the capability");
+                Slog.w(TAG, "Attempting startKeyphraseRecognition without the capability");
                 return STATUS_ERROR;
             }
             if (mModule == null) {
                 mModule = SoundTrigger.attachModule(mModuleProperties.id, this, null);
                 if (mModule == null) {
-                    Slog.w(TAG, "startRecognition cannot attach to sound trigger module");
+                    Slog.w(TAG, "startKeyphraseRecognition cannot attach to sound trigger module");
                     return STATUS_ERROR;
                 }
             }
@@ -348,26 +349,29 @@
             }
 
             if (currentCallback == null || !modelData.isModelStarted()) {
-                // startRecognition hasn't been called or it failed.
-                Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition");
+                // startGenericRecognition hasn't been called or it failed.
+                Slog.w(TAG, "Attempting stopGenericRecognition without a successful" +
+                        " startGenericRecognition");
                 return STATUS_ERROR;
             }
             if (currentCallback.asBinder() != listener.asBinder()) {
                 // We don't allow a different listener to stop the recognition than the one
                 // that started it.
-                Slog.w(TAG, "Attempting stopRecognition for another recognition");
+                Slog.w(TAG, "Attempting stopGenericRecognition for another recognition");
                 return STATUS_ERROR;
             }
 
-            int status = stopGenericRecognitionLocked(modelData, false /* don't notify for synchronous calls */);
+            int status = stopGenericRecognitionLocked(modelData,
+                    false /* don't notify for synchronous calls */);
             if (status != SoundTrigger.STATUS_OK) {
+                Slog.w(TAG, "stopGenericRecognition failed: " + status);
                 return status;
             }
 
             // We leave the sound model loaded but not started, this helps us when we start
             // back.
             // Also clear the internal state once the recognition has been stopped.
-            modelData.clearState();
+            modelData.setLoaded();
             modelData.clearCallback();
             if (!computeRecognitionRunning()) {
                 internalClearGlobalStateLocked();
@@ -471,6 +475,66 @@
         return mModuleProperties;
     }
 
+    int unloadKeyphraseSoundModel(int keyphraseId) {
+        if (mModule == null || mCurrentKeyphraseModelHandle == INVALID_VALUE) {
+            return STATUS_ERROR;
+        }
+        if (mKeyphraseId != keyphraseId) {
+            Slog.w(TAG, "Given sound model is not the one loaded.");
+            return STATUS_ERROR;
+        }
+
+        synchronized (mLock) {
+            // Stop recognition if it's the current one.
+            mRequested = false;
+            int status = updateRecognitionLocked(false /* don't notify */);
+            if (status != SoundTrigger.STATUS_OK) {
+                Slog.w(TAG, "Stop recognition failed for keyphrase ID:" + status);
+            }
+
+            status = mModule.unloadSoundModel(mCurrentKeyphraseModelHandle);
+            if (status != SoundTrigger.STATUS_OK) {
+                Slog.w(TAG, "unloadKeyphraseSoundModel call failed with " + status);
+            }
+            internalClearKeyphraseSoundModelLocked();
+            return status;
+        }
+    }
+
+    int unloadGenericSoundModel(UUID modelId) {
+        if (modelId == null || mModule == null) {
+            return STATUS_ERROR;
+        }
+        ModelData modelData = mGenericModelDataMap.get(modelId);
+        if (modelData == null) {
+            Slog.w(TAG, "Unload error: Attempting unload invalid generic model with id:" + modelId);
+            return STATUS_ERROR;
+        }
+        synchronized (mLock) {
+            if (!modelData.isModelLoaded()) {
+                // Nothing to do here.
+                Slog.i(TAG, "Unload: Given generic model is not loaded:" + modelId);
+                return STATUS_OK;
+            }
+            if (modelData.isModelStarted()) {
+                int status = stopGenericRecognitionLocked(modelData,
+                        false /* don't notify for synchronous calls */);
+                if (status != SoundTrigger.STATUS_OK) {
+                    Slog.w(TAG, "stopGenericRecognition failed: " + status);
+                }
+            }
+
+            int status = mModule.unloadSoundModel(modelData.getHandle());
+            if (status != SoundTrigger.STATUS_OK) {
+                Slog.w(TAG, "unloadGenericSoundModel() call failed with " + status);
+                Slog.w(TAG, "unloadGenericSoundModel() force-marking model as unloaded.");
+            }
+            mGenericModelDataMap.remove(modelId);
+            if (DBG) dumpGenericModelState();
+            return status;
+        }
+    }
+
     //---- SoundTrigger.StatusListener methods
     @Override
     public void onRecognition(RecognitionEvent event) {
@@ -913,6 +977,7 @@
                 }
             }
         } else {
+            Slog.i(TAG, "startRecognition successful.");
             modelData.setStarted();
             // Notify of resume if needed.
             if (notify) {
@@ -923,6 +988,7 @@
                 }
             }
         }
+        if (DBG) dumpGenericModelState();
         return status;
     }
 
@@ -951,9 +1017,17 @@
                 }
             }
         }
+        if (DBG) dumpGenericModelState();
         return status;
     }
 
+    private void dumpGenericModelState() {
+        for (UUID modelId : mGenericModelDataMap.keySet()) {
+            ModelData modelData = mGenericModelDataMap.get(modelId);
+            Slog.i(TAG, "Model :" + modelData.toString());
+        }
+    }
+
     // Computes whether we have any recognition running at all (voice or generic). Sets
     // the mRecognitionRunning variable with the result.
     private boolean computeRecognitionRunning() {
@@ -1069,5 +1143,18 @@
         synchronized RecognitionConfig getRecognitionConfig() {
             return mRecognitionConfig;
         }
+
+        String stateToString() {
+            switch(mModelState) {
+                case MODEL_NOTLOADED: return "NOT_LOADED";
+                case MODEL_LOADED: return "LOADED";
+                case MODEL_STARTED: return "STARTED";
+            }
+            return "Unknown state";
+        }
+
+        public String toString() {
+            return "Handle: " + mModelHandle + "ModelState: " + stateToString();
+        }
     }
 }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
index 7722876..113431f 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
@@ -75,5 +75,7 @@
 
     public abstract ModuleProperties getModuleProperties();
 
+    public abstract int unloadKeyphraseModel(int keyphaseId);
+
     public abstract void dump(FileDescriptor fd, PrintWriter pw, String[] args);
 }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 251f314..a4c1210 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -122,10 +122,10 @@
         public int startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback,
                 RecognitionConfig config) {
             enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+            if (!isInitialized()) return STATUS_ERROR;
             if (DEBUG) {
                 Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid);
             }
-            if (!isInitialized()) return STATUS_ERROR;
 
             GenericSoundModel model = getSoundModel(parcelUuid);
             if (model == null) {
@@ -173,6 +173,8 @@
             if (DEBUG) {
                 Slog.i(TAG, "deleteSoundModel(): id = " + soundModelId);
             }
+            // Unload the model if it is loaded.
+            mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid());
             mDbHelper.deleteGenericSoundModel(soundModelId.getUuid());
         }
     }
@@ -216,6 +218,12 @@
         }
 
         @Override
+        public int unloadKeyphraseModel(int keyphraseId) {
+            if (!isInitialized()) return STATUS_ERROR;
+            return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
+        }
+
+        @Override
         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!isInitialized()) return;
             mSoundTriggerHelper.dump(fd, pw, args);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 4a54643..6ab0b99 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -760,6 +760,10 @@
             final long caller = Binder.clearCallingIdentity();
             boolean deleted = false;
             try {
+                int unloadStatus = mSoundTriggerInternal.unloadKeyphraseModel(keyphraseId);
+                if (unloadStatus != SoundTriggerInternal.STATUS_OK) {
+                    Slog.w(TAG, "Unable to unload keyphrase sound model:" + unloadStatus);
+                }
                 deleted = mDbHelper.deleteKeyphraseSoundModel(keyphraseId, callingUid, bcp47Locale);
                 return deleted ? SoundTriggerInternal.STATUS_OK : SoundTriggerInternal.STATUS_ERROR;
             } finally {
diff --git a/tests/SoundTriggerTestApp/AndroidManifest.xml b/tests/SoundTriggerTestApp/AndroidManifest.xml
index a72b3dd..dc4cdb5 100644
--- a/tests/SoundTriggerTestApp/AndroidManifest.xml
+++ b/tests/SoundTriggerTestApp/AndroidManifest.xml
@@ -2,10 +2,12 @@
         package="com.android.test.soundtrigger">
 
     <uses-permission android:name="android.permission.MANAGE_SOUND_TRIGGER" />
+    <uses-permission android:name="android.permission.WAKE_LOCk" />
     <application>
         <activity
             android:name="TestSoundTriggerActivity"
             android:label="SoundTrigger Test Application"
+            android:screenOrientation="portrait"
             android:theme="@android:style/Theme.Material">
             <!--
             <intent-filter>
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java
index 3149783..95e2dcf 100644
--- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java
@@ -28,6 +28,8 @@
 import android.text.Editable;
 import android.text.method.ScrollingMovementMethod;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.PowerManager;
 import android.os.UserManager;
 import android.util.Log;
 import android.view.View;
@@ -54,6 +56,8 @@
     private TextView mDebugView = null;
     private int mSelectedModelId = 1;
     private ScrollView mScrollView = null;
+    private PowerManager.WakeLock mScreenWakelock;
+    private Handler mHandler;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -66,6 +70,7 @@
         mDebugView.setMovementMethod(new ScrollingMovementMethod());
         mSoundTriggerUtil = new SoundTriggerUtil(this);
         mRandom = new Random();
+        mHandler = new Handler();
     }
 
     private void postMessage(String msg) {
@@ -85,24 +90,43 @@
         });
     }
 
-    private UUID getSelectedUuid() {
+    private synchronized UUID getSelectedUuid() {
         if (mSelectedModelId == 2) return mModelUuid2;
         if (mSelectedModelId == 3) return mModelUuid3;
         return mModelUuid1;  // Default.
     }
 
-    private void setDetector(SoundTriggerDetector detector) {
-        if (mSelectedModelId == 2) mDetector2 = detector;
-        if (mSelectedModelId == 3) mDetector3 = detector;
+    private synchronized void setDetector(SoundTriggerDetector detector) {
+        if (mSelectedModelId == 2) {
+            mDetector2 = detector;
+            return;
+        }
+        if (mSelectedModelId == 3) {
+            mDetector3 = detector;
+            return;
+        }
         mDetector1 = detector;
     }
 
-    private SoundTriggerDetector getDetector() {
+    private synchronized SoundTriggerDetector getDetector() {
         if (mSelectedModelId == 2) return mDetector2;
         if (mSelectedModelId == 3) return mDetector3;
         return mDetector1;
     }
 
+    private void screenWakeup() {
+        PowerManager pm = ((PowerManager)getSystemService(POWER_SERVICE));
+        if (mScreenWakelock == null) {
+            mScreenWakelock =  pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "TAG");
+        }
+        mScreenWakelock.acquire();
+    }
+
+    private void screenRelease() {
+        PowerManager pm = ((PowerManager)getSystemService(POWER_SERVICE));
+        mScreenWakelock.release();
+    }
+
     /**
      * Called when the user clicks the enroll button.
      * Performs a fresh enrollment.
@@ -139,7 +163,7 @@
             Toast.makeText(this, "Sound model not found!!!", Toast.LENGTH_SHORT).show();
             return;
         }
-        boolean status = mSoundTriggerUtil.deleteSoundModel(mModelUuid1);
+        boolean status = mSoundTriggerUtil.deleteSoundModel(modelUuid);
         if (status) {
             Toast.makeText(this, "Successfully deleted model UUID=" + soundModel.uuid,
                     Toast.LENGTH_SHORT)
@@ -204,22 +228,28 @@
         }
     }
 
-    public void onRadioButtonClicked(View view) {
+    public synchronized void onRadioButtonClicked(View view) {
         // Is the button now checked?
         boolean checked = ((RadioButton) view).isChecked();
         // Check which radio button was clicked
         switch(view.getId()) {
             case R.id.model_one:
-                if (checked) mSelectedModelId = 1;
-                postMessage("Selected model one.");
+                if (checked) {
+                    mSelectedModelId = 1;
+                    postMessage("Selected model one.");
+                }
                 break;
             case R.id.model_two:
-                if (checked) mSelectedModelId = 2;
-                postMessage("Selected model two.");
+                if (checked) {
+                    mSelectedModelId = 2;
+                    postMessage("Selected model two.");
+                }
                 break;
             case R.id.model_three:
-                if (checked) mSelectedModelId = 3;
-                postMessage("Selected model three.");
+                if (checked) {
+                    mSelectedModelId = 3;
+                    postMessage("Selected model three.");
+                }
                 break;
         }
     }
@@ -232,6 +262,13 @@
 
         public void onDetected(SoundTriggerDetector.EventPayload event) {
             postMessage("onDetected(): " + eventPayloadToString(event));
+            screenWakeup();
+            mHandler.postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                   screenRelease();
+                }
+            }, 1000L);
         }
 
         public void onError() {