SoundTrigger API improvements.

This CL implements the SoundTrigger API improvements as given in b/22860713. Only the java-level
parts are implemented in this CL.

Key changes include:

* Addition of a SoundTriggerManager/SoundTriggerDetector system API to manage
  the sound-trigger based sound models.
* Addition of a SoundTriggerService service that manages all sound models
  including voice (keyphrase) and sound-trigger based models.
* Includes logic to write sound-trigger based models to the database.
* VoiceInteractionManager service now uses SoundTriggerService instead of
  SoundTriggerHelper.

Bug: 22860713
Change-Id: I7b5c0ed80702527c4460372efeb5e542d3693a69
diff --git a/tests/SoundTriggerTestApp/Android.mk b/tests/SoundTriggerTestApp/Android.mk
new file mode 100644
index 0000000..7bcab5e
--- /dev/null
+++ b/tests/SoundTriggerTestApp/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := SoundTriggerTestApp
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_PRIVILEGED_MODULE := true
+
+include $(BUILD_PACKAGE)
diff --git a/tests/SoundTriggerTestApp/AndroidManifest.xml b/tests/SoundTriggerTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..40619da
--- /dev/null
+++ b/tests/SoundTriggerTestApp/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.test.soundtrigger">
+
+    <uses-permission android:name="android.permission.MANAGE_SOUND_TRIGGER" />
+    <application
+         android:permission="android.permission.MANAGE_SOUND_TRIGGER">
+        <activity
+            android:name="TestSoundTriggerActivity"
+            android:label="SoundTrigger Test Application"
+            android:theme="@android:style/Theme.Material.Light.Voice">
+            <intent-filter>
+                <action android:name="com.android.intent.action.MANAGE_SOUND_TRIGGER" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/SoundTriggerTestApp/res/layout/main.xml b/tests/SoundTriggerTestApp/res/layout/main.xml
new file mode 100644
index 0000000..9d2b9d9
--- /dev/null
+++ b/tests/SoundTriggerTestApp/res/layout/main.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 Google Inc.
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    >
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/enroll"
+        android:onClick="onEnrollButtonClicked"
+        android:padding="20dp" />
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/reenroll"
+        android:onClick="onReEnrollButtonClicked"
+        android:padding="20dp" />
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/unenroll"
+        android:onClick="onUnEnrollButtonClicked"
+        android:padding="20dp" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/SoundTriggerTestApp/res/values/strings.xml b/tests/SoundTriggerTestApp/res/values/strings.xml
new file mode 100644
index 0000000..07bac2a
--- /dev/null
+++ b/tests/SoundTriggerTestApp/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 Google Inc.
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <string name="enroll">Enroll</string>
+    <string name="reenroll">Re-enroll</string>
+    <string name="unenroll">Un-enroll</string>
+</resources>
\ No newline at end of file
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
new file mode 100644
index 0000000..98713bd2
--- /dev/null
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.soundtrigger;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.SoundTriggerModel;
+import android.media.soundtrigger.SoundTriggerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import com.android.internal.app.ISoundTriggerService;
+
+import java.util.UUID;
+
+/**
+ * Utility class for the managing sound trigger sound models.
+ */
+public class SoundTriggerUtil {
+    private static final String TAG = "TestSoundTriggerUtil:Hotsound";
+
+    private final ISoundTriggerService mSoundTriggerService;
+    private final SoundTriggerManager mSoundTriggerManager;
+    private final Context mContext;
+
+    public SoundTriggerUtil(Context context) {
+        mSoundTriggerService = ISoundTriggerService.Stub.asInterface(
+                ServiceManager.getService(Context.SOUND_TRIGGER_SERVICE));
+        mSoundTriggerManager = (SoundTriggerManager) context.getSystemService(
+                Context.SOUND_TRIGGER_SERVICE);
+        mContext = context;
+    }
+
+    /**
+     * Adds/Updates a sound model.
+     * The sound model must contain a valid UUID.
+     *
+     * @param soundModel The sound model to add/update.
+     */
+    public boolean addOrUpdateSoundModel(SoundTriggerModel soundModel) {
+        try {
+            mSoundTriggerService.updateSoundModel(soundModel);
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in updateSoundModel", e);
+        }
+        return true;
+    }
+
+    public void addOrUpdateSoundModel(SoundTriggerManager.Model soundModel) {
+        mSoundTriggerManager.updateModel(soundModel);
+    }
+
+    /**
+     * Gets the sound model for the given keyphrase, null if none exists.
+     * If a sound model for a given keyphrase exists, and it needs to be updated,
+     * it should be obtained using this method, updated and then passed in to
+     * {@link #addOrUpdateSoundModel(SoundTriggerModel)} without changing the IDs.
+     *
+     * @param modelId The model ID to look-up the sound model for.
+     * @return The sound model if one was found, null otherwise.
+     */
+    @Nullable
+    public SoundTriggerModel getSoundModel(UUID modelId) {
+        SoundTriggerModel model = null;
+        try {
+            model = mSoundTriggerService.getSoundModel(new ParcelUuid(modelId));
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in updateKeyphraseSoundModel");
+        }
+
+        if (model == null) {
+            Log.w(TAG, "No models present for the gien keyphrase ID");
+            return null;
+        } else {
+            return model;
+        }
+    }
+
+    /**
+     * Deletes the sound model for the given keyphrase id.
+     *
+     * @param modelId The model ID to look-up the sound model for.
+     * @return {@code true} if the call succeeds, {@code false} otherwise.
+     */
+    @Nullable
+    public boolean deleteSoundModel(UUID modelId) {
+        try {
+            mSoundTriggerService.deleteSoundModel(new ParcelUuid(modelId));
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in updateSoundModel");
+        }
+        return true;
+    }
+
+    public void deleteSoundModelUsingManager(UUID modelId) {
+            mSoundTriggerManager.deleteModel(modelId);
+    }
+}
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java
new file mode 100644
index 0000000..82890c1
--- /dev/null
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.soundtrigger;
+
+import java.util.Random;
+import java.util.UUID;
+
+import android.app.Activity;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.SoundTriggerModel;
+import android.media.soundtrigger.SoundTriggerManager;
+import android.os.Bundle;
+import android.os.UserManager;
+import android.util.Log;
+import android.view.View;
+import android.widget.Toast;
+
+public class TestSoundTriggerActivity extends Activity {
+    private static final String TAG = "TestSoundTriggerActivity";
+    private static final boolean DBG = true;
+
+    private SoundTriggerUtil mSoundTriggerUtil;
+    private Random mRandom;
+    private UUID mModelUuid = UUID.randomUUID();
+    private UUID mModelUuid2 = UUID.randomUUID();
+    private UUID mVendorUuid = UUID.randomUUID();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        if (DBG) Log.d(TAG, "onCreate");
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+        mSoundTriggerUtil = new SoundTriggerUtil(this);
+        mRandom = new Random();
+    }
+
+    /**
+     * Called when the user clicks the enroll button.
+     * Performs a fresh enrollment.
+     */
+    public void onEnrollButtonClicked(View v) {
+        // Generate a fake model to push.
+        byte[] data = new byte[1024];
+        mRandom.nextBytes(data);
+        SoundTriggerModel model = new SoundTriggerModel(mModelUuid, mVendorUuid, data);
+
+        boolean status = mSoundTriggerUtil.addOrUpdateSoundModel(model);
+        if (status) {
+            Toast.makeText(
+                    this, "Successfully created sound trigger model UUID=" + mModelUuid, Toast.LENGTH_SHORT)
+                    .show();
+        } else {
+            Toast.makeText(this, "Failed to enroll!!!" + mModelUuid, Toast.LENGTH_SHORT).show();
+        }
+
+        // Test the SoundManager API.
+        SoundTriggerManager.Model tmpModel = SoundTriggerManager.Model.create(mModelUuid2,
+                mVendorUuid, data);
+        mSoundTriggerUtil.addOrUpdateSoundModel(tmpModel);
+    }
+
+    /**
+     * Called when the user clicks the un-enroll button.
+     * Clears the enrollment information for the user.
+     */
+    public void onUnEnrollButtonClicked(View v) {
+        SoundTriggerModel soundModel = mSoundTriggerUtil.getSoundModel(mModelUuid);
+        if (soundModel == null) {
+            Toast.makeText(this, "Sound model not found!!!", Toast.LENGTH_SHORT).show();
+            return;
+        }
+        boolean status = mSoundTriggerUtil.deleteSoundModel(mModelUuid);
+        if (status) {
+            Toast.makeText(this, "Successfully deleted model UUID=" + soundModel.uuid,
+                    Toast.LENGTH_SHORT)
+                    .show();
+        } else {
+            Toast.makeText(this, "Failed to delete sound model!!!", Toast.LENGTH_SHORT).show();
+        }
+        mSoundTriggerUtil.deleteSoundModelUsingManager(mModelUuid2);
+    }
+
+    /**
+     * Called when the user clicks the re-enroll button.
+     * Uses the previously enrolled sound model and makes changes to it before pushing it back.
+     */
+    public void onReEnrollButtonClicked(View v) {
+        SoundTriggerModel soundModel = mSoundTriggerUtil.getSoundModel(mModelUuid);
+        if (soundModel == null) {
+            Toast.makeText(this, "Sound model not found!!!", Toast.LENGTH_SHORT).show();
+            return;
+        }
+        // Generate a fake model to push.
+        byte[] data = new byte[2048];
+        mRandom.nextBytes(data);
+        SoundTriggerModel updated = new SoundTriggerModel(soundModel.uuid,
+                soundModel.vendorUuid, data);
+        boolean status = mSoundTriggerUtil.addOrUpdateSoundModel(updated);
+        if (status) {
+            Toast.makeText(this, "Successfully re-enrolled, model UUID=" + updated.uuid,
+                    Toast.LENGTH_SHORT)
+                    .show();
+        } else {
+            Toast.makeText(this, "Failed to re-enroll!!!", Toast.LENGTH_SHORT).show();
+        }
+    }
+}