Updates to the sound trigger test app
Moved the logic for talking to the system service into a service in the
app - this now lets you close the activity and still be able to receive
events. Additionally, we get CLI support using commands with the intent
com.android.intent.action.MANAGE_SOUND_TRIGGER.
Bug: 29073629
Change-Id: Ie904b73b4414f2c9fded013aeb5e6c6c3a67f5d3
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestActivity.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestActivity.java
new file mode 100644
index 0000000..4841bc5
--- /dev/null
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestActivity.java
@@ -0,0 +1,329 @@
+/*
+ * 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.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.text.Editable;
+import android.text.method.ScrollingMovementMethod;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.ScrollView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.test.soundtrigger.SoundTriggerTestService.SoundTriggerTestBinder;
+
+public class SoundTriggerTestActivity extends Activity implements SoundTriggerTestService.UserActivity {
+ private static final String TAG = "SoundTriggerTest";
+ private static final int AUDIO_PERMISSIONS_REQUEST = 1;
+
+ private SoundTriggerTestService mService = null;
+
+ private static UUID mSelectedModelUuid = null;
+
+ private Map<RadioButton, UUID> mButtonModelUuidMap;
+ private Map<UUID, RadioButton> mModelButtons;
+ private Map<UUID, String> mModelNames;
+ private List<RadioButton> mModelRadioButtons;
+
+ private TextView mDebugView = null;
+ private ScrollView mScrollView = null;
+ private Button mPlayTriggerButton = null;
+ private PowerManager.WakeLock mScreenWakelock;
+ private Handler mHandler;
+ private RadioGroup mRadioGroup;
+ private CheckBox mCaptureAudioCheckBox;
+ private Button mPlayCapturedAudioButton = null;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ // Make sure that this activity can punch through the lockscreen if needed.
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |
+ WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ mDebugView = (TextView) findViewById(R.id.console);
+ mScrollView = (ScrollView) findViewById(R.id.scroller_id);
+ mRadioGroup = (RadioGroup) findViewById(R.id.model_group_id);
+ mPlayTriggerButton = (Button) findViewById(R.id.play_trigger_id);
+ mDebugView.setText(mDebugView.getText(), TextView.BufferType.EDITABLE);
+ mDebugView.setMovementMethod(new ScrollingMovementMethod());
+ mCaptureAudioCheckBox = (CheckBox) findViewById(R.id.caputre_check_box);
+ mPlayCapturedAudioButton = (Button) findViewById(R.id.play_captured_id);
+ mHandler = new Handler();
+ mButtonModelUuidMap = new HashMap();
+ mModelButtons = new HashMap();
+ mModelNames = new HashMap();
+ mModelRadioButtons = new LinkedList();
+
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+ // Make sure that the service is started, so even if our activity goes down, we'll still
+ // have a request for it to run.
+ startService(new Intent(getBaseContext(), SoundTriggerTestService.class));
+
+ // Bind to SoundTriggerTestService.
+ Intent intent = new Intent(this, SoundTriggerTestService.class);
+ bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ // Unbind from the service.
+ if (mService != null) {
+ mService.setUserActivity(null);
+ unbindService(mConnection);
+ }
+ }
+
+ @Override
+ public void addModel(UUID modelUuid, String name) {
+ // Create a new widget for this model, and insert everything we'd need into the map.
+ RadioButton button = new RadioButton(this);
+ mModelRadioButtons.add(button);
+ button.setText(name);
+ button.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ onRadioButtonClicked(v);
+ }
+ });
+ mButtonModelUuidMap.put(button, modelUuid);
+ mModelButtons.put(modelUuid, button);
+ mModelNames.put(modelUuid, name);
+
+ // Sort all the radio buttons by name, then push them into the group in order.
+ Collections.sort(mModelRadioButtons, new Comparator<RadioButton>(){
+ @Override
+ public int compare(RadioButton button0, RadioButton button1) {
+ return button0.getText().toString().compareTo(button1.getText().toString());
+ }
+ });
+ mRadioGroup.removeAllViews();
+ for (View v : mModelRadioButtons) {
+ mRadioGroup.addView(v);
+ }
+
+ // If we don't have something selected, select this first thing.
+ if (mSelectedModelUuid == null || mSelectedModelUuid.equals(modelUuid)) {
+ button.setChecked(true);
+ onRadioButtonClicked(button);
+ }
+ }
+
+ @Override
+ public void setModelState(UUID modelUuid, String state) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ String newButtonText = mModelNames.get(modelUuid);
+ if (state != null) {
+ newButtonText += ": " + state;
+ }
+ mModelButtons.get(modelUuid).setText(newButtonText);
+ updateSelectModelSpecificUiElements();
+ }
+ });
+ }
+
+ @Override
+ public void showMessage(String msg, boolean showToast) {
+ // Append the message to the text field, then show the toast if requested.
+ this.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ ((Editable) mDebugView.getText()).append(msg + "\n");
+ mScrollView.post(new Runnable() {
+ public void run() {
+ mScrollView.smoothScrollTo(0, mDebugView.getBottom());
+ }
+ });
+ if (showToast) {
+ Toast.makeText(SoundTriggerTestActivity.this, msg, Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void handleDetection(UUID modelUuid) {
+ screenWakeup();
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ screenRelease();
+ }
+ }, 1000L);
+ }
+
+ private void screenWakeup() {
+ if (mScreenWakelock == null) {
+ PowerManager pm = ((PowerManager)getSystemService(POWER_SERVICE));
+ mScreenWakelock = pm.newWakeLock(
+ PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, TAG);
+ }
+ mScreenWakelock.acquire();
+ }
+
+ private void screenRelease() {
+ mScreenWakelock.release();
+ }
+
+ public void onLoadButtonClicked(View v) {
+ if (mService == null) {
+ Log.e(TAG, "Could not load sound model: not bound to SoundTriggerTestService");
+ } else {
+ mService.loadModel(mSelectedModelUuid);
+ }
+ }
+
+ public void onUnloadButtonClicked(View v) {
+ if (mService == null) {
+ Log.e(TAG, "Can't unload model: not bound to SoundTriggerTestService");
+ } else {
+ mService.unloadModel(mSelectedModelUuid);
+ }
+ }
+
+ public void onReloadButtonClicked(View v) {
+ if (mService == null) {
+ Log.e(TAG, "Can't reload model: not bound to SoundTriggerTestService");
+ } else {
+ mService.reloadModel(mSelectedModelUuid);
+ }
+ }
+
+ public void onStartRecognitionButtonClicked(View v) {
+ if (mService == null) {
+ Log.e(TAG, "Can't start recognition: not bound to SoundTriggerTestService");
+ } else {
+ mService.startRecognition(mSelectedModelUuid);
+ }
+ }
+
+ public void onStopRecognitionButtonClicked(View v) {
+ if (mService == null) {
+ Log.e(TAG, "Can't stop recognition: not bound to SoundTriggerTestService");
+ } else {
+ mService.stopRecognition(mSelectedModelUuid);
+ }
+ }
+
+ public synchronized void onPlayTriggerButtonClicked(View v) {
+ if (mService == null) {
+ Log.e(TAG, "Can't play trigger audio: not bound to SoundTriggerTestService");
+ } else {
+ mService.playTriggerAudio(mSelectedModelUuid);
+ }
+ }
+
+ public synchronized void onCaptureAudioCheckboxClicked(View v) {
+ // See if we have the right permissions
+ if (!mService.hasMicrophonePermission()) {
+ requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO},
+ AUDIO_PERMISSIONS_REQUEST);
+ return;
+ } else {
+ mService.setCaptureAudio(mSelectedModelUuid, mCaptureAudioCheckBox.isChecked());
+ }
+ }
+
+ @Override
+ public synchronized void onRequestPermissionsResult(int requestCode, String permissions[],
+ int[] grantResults) {
+ if (requestCode == AUDIO_PERMISSIONS_REQUEST) {
+ if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
+ // Make sure that the check box is set to false.
+ mCaptureAudioCheckBox.setChecked(false);
+ }
+ mService.setCaptureAudio(mSelectedModelUuid, mCaptureAudioCheckBox.isChecked());
+ }
+ }
+
+ public synchronized void onPlayCapturedAudioButtonClicked(View v) {
+ if (mService == null) {
+ Log.e(TAG, "Can't play captured audio: not bound to SoundTriggerTestService");
+ } else {
+ mService.playCapturedAudio(mSelectedModelUuid);
+ }
+ }
+
+ public synchronized void onRadioButtonClicked(View view) {
+ // Is the button now checked?
+ boolean checked = ((RadioButton) view).isChecked();
+ if (checked) {
+ mSelectedModelUuid = mButtonModelUuidMap.get(view);
+ showMessage("Selected " + mModelNames.get(mSelectedModelUuid), false);
+ updateSelectModelSpecificUiElements();
+ }
+ }
+
+ private synchronized void updateSelectModelSpecificUiElements() {
+ // Set the play trigger button to be enabled only if we actually have some audio.
+ mPlayTriggerButton.setEnabled(mService.modelHasTriggerAudio((mSelectedModelUuid)));
+ // Similar logic for the captured audio.
+ mCaptureAudioCheckBox.setChecked(
+ mService.modelWillCaptureTriggerAudio(mSelectedModelUuid));
+ mPlayCapturedAudioButton.setEnabled(mService.modelHasCapturedAudio((mSelectedModelUuid)));
+ }
+
+ private ServiceConnection mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ synchronized (SoundTriggerTestActivity.this) {
+ // We've bound to LocalService, cast the IBinder and get LocalService instance
+ SoundTriggerTestBinder binder = (SoundTriggerTestBinder) service;
+ mService = binder.getService();
+ mService.setUserActivity(SoundTriggerTestActivity.this);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName arg0) {
+ synchronized (SoundTriggerTestActivity.this) {
+ mService.setUserActivity(null);
+ mService = null;
+ }
+ }
+ };
+}
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java
new file mode 100644
index 0000000..0ff95c4
--- /dev/null
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java
@@ -0,0 +1,720 @@
+/*
+ * 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.Manifest;
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.AudioTrack;
+import android.media.MediaPlayer;
+import android.media.soundtrigger.SoundTriggerDetector;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Random;
+import java.util.UUID;
+
+public class SoundTriggerTestService extends Service {
+ private static final String TAG = "SoundTriggerTestSrv";
+ private static final String INTENT_ACTION = "com.android.intent.action.MANAGE_SOUND_TRIGGER";
+
+ // Binder given to clients.
+ private final IBinder mBinder;
+ private final Map<UUID, ModelInfo> mModelInfoMap;
+ private SoundTriggerUtil mSoundTriggerUtil;
+ private Random mRandom;
+ private UserActivity mUserActivity;
+
+ public interface UserActivity {
+ void addModel(UUID modelUuid, String state);
+ void setModelState(UUID modelUuid, String state);
+ void showMessage(String msg, boolean showToast);
+ void handleDetection(UUID modelUuid);
+ }
+
+ public SoundTriggerTestService() {
+ super();
+ mRandom = new Random();
+ mModelInfoMap = new HashMap();
+ mBinder = new SoundTriggerTestBinder();
+ }
+
+ @Override
+ public synchronized int onStartCommand(Intent intent, int flags, int startId) {
+ if (mModelInfoMap.isEmpty()) {
+ mSoundTriggerUtil = new SoundTriggerUtil(this);
+ loadModelsInDataDir();
+ }
+
+ // If we get killed, after returning from here, restart
+ return START_STICKY;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(INTENT_ACTION);
+ registerReceiver(mBroadcastReceiver, filter);
+
+ // Make sure the data directory exists, and we're the owner of it.
+ try {
+ getFilesDir().mkdir();
+ } catch (Exception e) {
+ // Don't care - we either made it, or it already exists.
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ stopAllRecognitions();
+ unregisterReceiver(mBroadcastReceiver);
+ }
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent != null && INTENT_ACTION.equals(intent.getAction())) {
+ String command = intent.getStringExtra("command");
+ if (command == null) {
+ Log.e(TAG, "No 'command' specified in " + INTENT_ACTION);
+ } else {
+ try {
+ if (command.equals("load")) {
+ loadModel(getModelUuidFromIntent(intent));
+ } else if (command.equals("unload")) {
+ unloadModel(getModelUuidFromIntent(intent));
+ } else if (command.equals("start")) {
+ startRecognition(getModelUuidFromIntent(intent));
+ } else if (command.equals("stop")) {
+ stopRecognition(getModelUuidFromIntent(intent));
+ } else if (command.equals("play_trigger")) {
+ playTriggerAudio(getModelUuidFromIntent(intent));
+ } else if (command.equals("play_captured")) {
+ playCapturedAudio(getModelUuidFromIntent(intent));
+ } else if (command.equals("set_capture")) {
+ setCaptureAudio(getModelUuidFromIntent(intent),
+ intent.getBooleanExtra("enabled", true));
+ } else if (command.equals("set_capture_timeout")) {
+ setCaptureAudioTimeout(getModelUuidFromIntent(intent),
+ intent.getIntExtra("timeout", 5000));
+ } else {
+ Log.e(TAG, "Unknown command '" + command + "'");
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to process " + command, e);
+ }
+ }
+ }
+ }
+ };
+
+ private UUID getModelUuidFromIntent(Intent intent) {
+ // First, see if the specified the UUID straight up.
+ String value = intent.getStringExtra("modelUuid");
+ if (value != null) {
+ return UUID.fromString(value);
+ }
+
+ // If they specified a name, use that to iterate through the map of models and find it.
+ value = intent.getStringExtra("name");
+ if (value != null) {
+ for (ModelInfo modelInfo : mModelInfoMap.values()) {
+ if (value.equals(modelInfo.name)) {
+ return modelInfo.modelUuid;
+ }
+ }
+ Log.e(TAG, "Failed to find a matching model with name '" + value + "'");
+ }
+
+ // We couldn't figure out what they were asking for.
+ throw new RuntimeException("Failed to get model from intent - specify either " +
+ "'modelUuid' or 'name'");
+ }
+
+ /**
+ * Will be called when the service is killed (through swipe aways, not if we're force killed).
+ */
+ @Override
+ public void onTaskRemoved(Intent rootIntent) {
+ super.onTaskRemoved(rootIntent);
+ stopAllRecognitions();
+ stopSelf();
+ }
+
+ @Override
+ public synchronized IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ public class SoundTriggerTestBinder extends Binder {
+ SoundTriggerTestService getService() {
+ // Return instance of our parent so clients can call public methods.
+ return SoundTriggerTestService.this;
+ }
+ }
+
+ public synchronized void setUserActivity(UserActivity activity) {
+ mUserActivity = activity;
+ if (mUserActivity != null) {
+ for (Map.Entry<UUID, ModelInfo> entry : mModelInfoMap.entrySet()) {
+ mUserActivity.addModel(entry.getKey(), entry.getValue().name);
+ mUserActivity.setModelState(entry.getKey(), entry.getValue().state);
+ }
+ }
+ }
+
+ private synchronized void stopAllRecognitions() {
+ for (ModelInfo modelInfo : mModelInfoMap.values()) {
+ if (modelInfo.detector != null) {
+ Log.i(TAG, "Stopping recognition for " + modelInfo.name);
+ try {
+ modelInfo.detector.stopRecognition();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to stop recognition", e);
+ }
+ }
+ }
+ }
+
+ // Helper struct for holding information about a model.
+ public static class ModelInfo {
+ public String name;
+ public String state;
+ public UUID modelUuid;
+ public UUID vendorUuid;
+ public MediaPlayer triggerAudioPlayer;
+ public SoundTriggerDetector detector;
+ public byte modelData[];
+ public boolean captureAudio;
+ public int captureAudioMs;
+ public AudioTrack captureAudioTrack;
+ }
+
+ private GenericSoundModel createNewSoundModel(ModelInfo modelInfo) {
+ return new GenericSoundModel(modelInfo.modelUuid, modelInfo.vendorUuid,
+ modelInfo.modelData);
+ }
+
+ public synchronized void loadModel(UUID modelUuid) {
+ ModelInfo modelInfo = mModelInfoMap.get(modelUuid);
+ if (modelInfo == null) {
+ postError("Could not find model for: " + modelUuid.toString());
+ return;
+ }
+
+ postMessage("Loading model: " + modelInfo.name);
+
+ GenericSoundModel soundModel = createNewSoundModel(modelInfo);
+
+ boolean status = mSoundTriggerUtil.addOrUpdateSoundModel(soundModel);
+ if (status) {
+ postToast("Successfully loaded " + modelInfo.name + ", UUID=" + soundModel.uuid);
+ setModelState(modelInfo, "Loaded");
+ } else {
+ postErrorToast("Failed to load " + modelInfo.name + ", UUID=" + soundModel.uuid + "!");
+ setModelState(modelInfo, "Failed to load");
+ }
+ }
+
+ public synchronized void unloadModel(UUID modelUuid) {
+ ModelInfo modelInfo = mModelInfoMap.get(modelUuid);
+ if (modelInfo == null) {
+ postError("Could not find model for: " + modelUuid.toString());
+ return;
+ }
+
+ postMessage("Unloading model: " + modelInfo.name);
+
+ GenericSoundModel soundModel = mSoundTriggerUtil.getSoundModel(modelUuid);
+ if (soundModel == null) {
+ postErrorToast("Sound model not found for " + modelInfo.name + "!");
+ return;
+ }
+ modelInfo.detector = null;
+ boolean status = mSoundTriggerUtil.deleteSoundModel(modelUuid);
+ if (status) {
+ postToast("Successfully unloaded " + modelInfo.name + ", UUID=" + soundModel.uuid);
+ setModelState(modelInfo, "Unloaded");
+ } else {
+ postErrorToast("Failed to unload " +
+ modelInfo.name + ", UUID=" + soundModel.uuid + "!");
+ setModelState(modelInfo, "Failed to unload");
+ }
+ }
+
+ public synchronized void reloadModel(UUID modelUuid) {
+ ModelInfo modelInfo = mModelInfoMap.get(modelUuid);
+ if (modelInfo == null) {
+ postError("Could not find model for: " + modelUuid.toString());
+ return;
+ }
+ postMessage("Reloading model: " + modelInfo.name);
+ GenericSoundModel soundModel = mSoundTriggerUtil.getSoundModel(modelUuid);
+ if (soundModel == null) {
+ postErrorToast("Sound model not found for " + modelInfo.name + "!");
+ return;
+ }
+ GenericSoundModel updated = createNewSoundModel(modelInfo);
+ boolean status = mSoundTriggerUtil.addOrUpdateSoundModel(updated);
+ if (status) {
+ postToast("Successfully reloaded " + modelInfo.name + ", UUID=" + modelInfo.modelUuid);
+ setModelState(modelInfo, "Reloaded");
+ } else {
+ postErrorToast("Failed to reload "
+ + modelInfo.name + ", UUID=" + modelInfo.modelUuid + "!");
+ setModelState(modelInfo, "Failed to reload");
+ }
+ }
+
+ public synchronized void startRecognition(UUID modelUuid) {
+ ModelInfo modelInfo = mModelInfoMap.get(modelUuid);
+ if (modelInfo == null) {
+ postError("Could not find model for: " + modelUuid.toString());
+ return;
+ }
+
+ if (modelInfo.detector == null) {
+ postMessage("Creating SoundTriggerDetector for " + modelInfo.name);
+ modelInfo.detector = mSoundTriggerUtil.createSoundTriggerDetector(
+ modelUuid, new DetectorCallback(modelInfo));
+ }
+
+ postMessage("Starting recognition for " + modelInfo.name + ", UUID=" + modelInfo.modelUuid);
+ if (modelInfo.detector.startRecognition(modelInfo.captureAudio ?
+ SoundTriggerDetector.RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO :
+ SoundTriggerDetector.RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS)) {
+ setModelState(modelInfo, "Started");
+ } else {
+ postErrorToast("Fast failure attempting to start recognition for " +
+ modelInfo.name + ", UUID=" + modelInfo.modelUuid);
+ setModelState(modelInfo, "Failed to start");
+ }
+ }
+
+ public synchronized void stopRecognition(UUID modelUuid) {
+ ModelInfo modelInfo = mModelInfoMap.get(modelUuid);
+ if (modelInfo == null) {
+ postError("Could not find model for: " + modelUuid.toString());
+ return;
+ }
+
+ if (modelInfo.detector == null) {
+ postErrorToast("Stop called on null detector for " +
+ modelInfo.name + ", UUID=" + modelInfo.modelUuid);
+ return;
+ }
+ postMessage("Triggering stop recognition for " +
+ modelInfo.name + ", UUID=" + modelInfo.modelUuid);
+ if (modelInfo.detector.stopRecognition()) {
+ setModelState(modelInfo, "Stopped");
+ } else {
+ postErrorToast("Fast failure attempting to stop recognition for " +
+ modelInfo.name + ", UUID=" + modelInfo.modelUuid);
+ setModelState(modelInfo, "Failed to stop");
+ }
+ }
+
+ public synchronized void playTriggerAudio(UUID modelUuid) {
+ ModelInfo modelInfo = mModelInfoMap.get(modelUuid);
+ if (modelInfo == null) {
+ postError("Could not find model for: " + modelUuid.toString());
+ return;
+ }
+ if (modelInfo.triggerAudioPlayer != null) {
+ postMessage("Playing trigger audio for " + modelInfo.name);
+ modelInfo.triggerAudioPlayer.start();
+ } else {
+ postMessage("No trigger audio for " + modelInfo.name);
+ }
+ }
+
+ public synchronized void playCapturedAudio(UUID modelUuid) {
+ ModelInfo modelInfo = mModelInfoMap.get(modelUuid);
+ if (modelInfo == null) {
+ postError("Could not find model for: " + modelUuid.toString());
+ return;
+ }
+ if (modelInfo.captureAudioTrack != null) {
+ postMessage("Playing captured audio for " + modelInfo.name);
+ modelInfo.captureAudioTrack.stop();
+ modelInfo.captureAudioTrack.reloadStaticData();
+ modelInfo.captureAudioTrack.play();
+ } else {
+ postMessage("No captured audio for " + modelInfo.name);
+ }
+ }
+
+ public synchronized void setCaptureAudioTimeout(UUID modelUuid, int captureTimeoutMs) {
+ ModelInfo modelInfo = mModelInfoMap.get(modelUuid);
+ if (modelInfo == null) {
+ postError("Could not find model for: " + modelUuid.toString());
+ return;
+ }
+ modelInfo.captureAudioMs = captureTimeoutMs;
+ Log.i(TAG, "Set " + modelInfo.name + " capture audio timeout to " +
+ captureTimeoutMs + "ms");
+ }
+
+ public synchronized void setCaptureAudio(UUID modelUuid, boolean captureAudio) {
+ ModelInfo modelInfo = mModelInfoMap.get(modelUuid);
+ if (modelInfo == null) {
+ postError("Could not find model for: " + modelUuid.toString());
+ return;
+ }
+ modelInfo.captureAudio = captureAudio;
+ Log.i(TAG, "Set " + modelInfo.name + " capture audio to " + captureAudio);
+ }
+
+ public synchronized boolean hasMicrophonePermission() {
+ return getBaseContext().checkSelfPermission(Manifest.permission.RECORD_AUDIO)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ public synchronized boolean modelHasTriggerAudio(UUID modelUuid) {
+ ModelInfo modelInfo = mModelInfoMap.get(modelUuid);
+ return modelInfo != null && modelInfo.triggerAudioPlayer != null;
+ }
+
+ public synchronized boolean modelWillCaptureTriggerAudio(UUID modelUuid) {
+ ModelInfo modelInfo = mModelInfoMap.get(modelUuid);
+ return modelInfo != null && modelInfo.captureAudio;
+ }
+
+ public synchronized boolean modelHasCapturedAudio(UUID modelUuid) {
+ ModelInfo modelInfo = mModelInfoMap.get(modelUuid);
+ return modelInfo != null && modelInfo.captureAudioTrack != null;
+ }
+
+ private void loadModelsInDataDir() {
+ // Load all the models in the data dir.
+ boolean loadedModel = false;
+ for (File file : getFilesDir().listFiles()) {
+ // Find meta-data in .properties files, ignore everything else.
+ if (!file.getName().endsWith(".properties")) {
+ continue;
+ }
+ try {
+ Properties properties = new Properties();
+ properties.load(new FileInputStream(file));
+ createModelInfo(properties);
+ loadedModel = true;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to load properties file " + file.getName());
+ }
+ }
+
+ // Create a few dummy models if we didn't load anything.
+ if (!loadedModel) {
+ Properties dummyModelProperties = new Properties();
+ for (String name : new String[]{"1", "2", "3"}) {
+ dummyModelProperties.setProperty("name", "Model " + name);
+ createModelInfo(dummyModelProperties);
+ }
+ }
+ }
+
+ /** Parses a Properties collection to generate a sound model.
+ *
+ * Missing keys are filled in with default/random values.
+ * @param properties Has the required 'name' property, but the remaining 'modelUuid',
+ * 'vendorUuid', 'triggerAudio', and 'dataFile' optional properties.
+ *
+ */
+ private synchronized void createModelInfo(Properties properties) {
+ try {
+ ModelInfo modelInfo = new ModelInfo();
+
+ if (!properties.containsKey("name")) {
+ throw new RuntimeException("must have a 'name' property");
+ }
+ modelInfo.name = properties.getProperty("name");
+
+ if (properties.containsKey("modelUuid")) {
+ modelInfo.modelUuid = UUID.fromString(properties.getProperty("modelUuid"));
+ } else {
+ modelInfo.modelUuid = UUID.randomUUID();
+ }
+
+ if (properties.containsKey("vendorUuid")) {
+ modelInfo.vendorUuid = UUID.fromString(properties.getProperty("vendorUuid"));
+ } else {
+ modelInfo.vendorUuid = UUID.randomUUID();
+ }
+
+ if (properties.containsKey("triggerAudio")) {
+ modelInfo.triggerAudioPlayer = MediaPlayer.create(this, Uri.parse(
+ getFilesDir().getPath() + "/" + properties.getProperty("triggerAudio")));
+ if (modelInfo.triggerAudioPlayer.getDuration() == 0) {
+ modelInfo.triggerAudioPlayer.release();
+ modelInfo.triggerAudioPlayer = null;
+ }
+ }
+
+ if (properties.containsKey("dataFile")) {
+ File modelDataFile = new File(
+ getFilesDir().getPath() + "/" + properties.getProperty("dataFile"));
+ modelInfo.modelData = new byte[(int) modelDataFile.length()];
+ FileInputStream input = new FileInputStream(modelDataFile);
+ input.read(modelInfo.modelData, 0, modelInfo.modelData.length);
+ } else {
+ modelInfo.modelData = new byte[1024];
+ mRandom.nextBytes(modelInfo.modelData);
+ }
+
+ modelInfo.captureAudioMs = Integer.parseInt((String) properties.getOrDefault(
+ "captureAudioDurationMs", "5000"));
+
+ // TODO: Add property support for keyphrase models when they're exposed by the
+ // service.
+
+ // Update our maps containing the button -> id and id -> modelInfo.
+ mModelInfoMap.put(modelInfo.modelUuid, modelInfo);
+ if (mUserActivity != null) {
+ mUserActivity.addModel(modelInfo.modelUuid, modelInfo.name);
+ mUserActivity.setModelState(modelInfo.modelUuid, modelInfo.state);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Error parsing properties for " + properties.getProperty("name"), e);
+ }
+ }
+
+ private class CaptureAudioRecorder implements Runnable {
+ private final ModelInfo mModelInfo;
+ private final SoundTriggerDetector.EventPayload mEvent;
+
+ public CaptureAudioRecorder(ModelInfo modelInfo, SoundTriggerDetector.EventPayload event) {
+ mModelInfo = modelInfo;
+ mEvent = event;
+ }
+
+ @Override
+ public void run() {
+ AudioFormat format = mEvent.getCaptureAudioFormat();
+ if (format == null) {
+ postErrorToast("No audio format in recognition event.");
+ return;
+ }
+
+ AudioRecord audioRecord = null;
+ AudioTrack playbackTrack = null;
+ try {
+ // Inform the audio flinger that we really do want the stream from the soundtrigger.
+ AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder();
+ attributesBuilder.setInternalCapturePreset(1999);
+ AudioAttributes attributes = attributesBuilder.build();
+
+ // Make sure we understand this kind of playback so we know how many bytes to read.
+ String encoding;
+ int bytesPerSample;
+ switch (format.getEncoding()) {
+ case AudioFormat.ENCODING_PCM_8BIT:
+ encoding = "8bit";
+ bytesPerSample = 1;
+ break;
+ case AudioFormat.ENCODING_PCM_16BIT:
+ encoding = "16bit";
+ bytesPerSample = 2;
+ break;
+ case AudioFormat.ENCODING_PCM_FLOAT:
+ encoding = "float";
+ bytesPerSample = 4;
+ break;
+ default:
+ throw new RuntimeException("Unhandled audio format in event");
+ }
+
+ int bytesRequired = format.getSampleRate() * format.getChannelCount() *
+ bytesPerSample * mModelInfo.captureAudioMs / 1000;
+ int minBufferSize = AudioRecord.getMinBufferSize(
+ format.getSampleRate(), format.getChannelMask(), format.getEncoding());
+ if (minBufferSize > bytesRequired) {
+ bytesRequired = minBufferSize;
+ }
+
+ // Make an AudioTrack so we can play the data back out after it's finished
+ // recording.
+ try {
+ int channelConfig = AudioFormat.CHANNEL_OUT_MONO;
+ if (format.getChannelCount() == 2) {
+ channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
+ } else if (format.getChannelCount() >= 3) {
+ throw new RuntimeException(
+ "Too many channels in captured audio for playback");
+ }
+
+ playbackTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
+ format.getSampleRate(), channelConfig, format.getEncoding(),
+ bytesRequired, AudioTrack.MODE_STATIC);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception creating playback track", e);
+ postErrorToast("Failed to create playback track: " + e.getMessage());
+ }
+
+ audioRecord = new AudioRecord(attributes, format, bytesRequired,
+ mEvent.getCaptureSession());
+
+ byte[] buffer = new byte[bytesRequired];
+
+ // Create a file so we can save the output data there for analysis later.
+ FileOutputStream fos = null;
+ try {
+ fos = new FileOutputStream( new File(
+ getFilesDir() + File.separator + mModelInfo.name.replace(' ', '_') +
+ "_capture_" + format.getChannelCount() + "ch_" +
+ format.getSampleRate() + "hz_" + encoding + ".pcm"));
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to open output for saving PCM data", e);
+ postErrorToast("Failed to open output for saving PCM data: " + e.getMessage());
+ }
+
+ // Inform the user we're recording.
+ setModelState(mModelInfo, "Recording");
+ audioRecord.startRecording();
+ while (bytesRequired > 0) {
+ int bytesRead = audioRecord.read(buffer, 0, buffer.length);
+ if (bytesRead == -1) {
+ break;
+ }
+ if (fos != null) {
+ fos.write(buffer, 0, bytesRead);
+ }
+ if (playbackTrack != null) {
+ playbackTrack.write(buffer, 0, bytesRead);
+ }
+ bytesRequired -= bytesRead;
+ }
+ audioRecord.stop();
+ } catch (Exception e) {
+ Log.e(TAG, "Error recording trigger audio", e);
+ postErrorToast("Error recording trigger audio: " + e.getMessage());
+ } finally {
+ if (audioRecord != null) {
+ audioRecord.release();
+ }
+ synchronized (SoundTriggerTestService.this) {
+ if (mModelInfo.captureAudioTrack != null) {
+ mModelInfo.captureAudioTrack.release();
+ }
+ mModelInfo.captureAudioTrack = playbackTrack;
+ }
+ setModelState(mModelInfo, "Recording finished");
+ }
+ }
+ }
+
+ // Implementation of SoundTriggerDetector.Callback.
+ private class DetectorCallback extends SoundTriggerDetector.Callback {
+ private final ModelInfo mModelInfo;
+
+ public DetectorCallback(ModelInfo modelInfo) {
+ mModelInfo = modelInfo;
+ }
+
+ public void onAvailabilityChanged(int status) {
+ postMessage(mModelInfo.name + "Availability changed to: " + status);
+ }
+
+ public void onDetected(SoundTriggerDetector.EventPayload event) {
+ postMessage(mModelInfo.name + "onDetected(): " + eventPayloadToString(event));
+ synchronized (SoundTriggerTestService.this) {
+ if (mUserActivity != null) {
+ mUserActivity.handleDetection(mModelInfo.modelUuid);
+ }
+ if (mModelInfo.captureAudio) {
+ new Thread(new CaptureAudioRecorder(mModelInfo, event)).start();
+ }
+ }
+ }
+
+ public void onError() {
+ postMessage(mModelInfo.name + "onError()");
+ setModelState(mModelInfo, "Error");
+ }
+
+ public void onRecognitionPaused() {
+ postMessage(mModelInfo.name + " onRecognitionPaused()");
+ setModelState(mModelInfo, "Paused");
+ }
+
+ public void onRecognitionResumed() {
+ postMessage(mModelInfo.name + "onRecognitionResumed()");
+ setModelState(mModelInfo, "Resumed");
+ }
+ }
+
+ private String eventPayloadToString(SoundTriggerDetector.EventPayload event) {
+ String result = "EventPayload(";
+ AudioFormat format = event.getCaptureAudioFormat();
+ result = result + "AudioFormat: " + ((format == null) ? "null" : format.toString());
+ byte[] triggerAudio = event.getTriggerAudio();
+ result = result + "TriggerAudio: " + (triggerAudio == null ? "null" : triggerAudio.length);
+ result = result + "CaptureSession: " + event.getCaptureSession();
+ result += " )";
+ return result;
+ }
+
+ private void postMessage(String msg) {
+ showMessage(msg, Log.INFO, false);
+ }
+
+ private void postError(String msg) {
+ showMessage(msg, Log.ERROR, false);
+ }
+
+ private void postToast(String msg) {
+ showMessage(msg, Log.INFO, true);
+ }
+
+ private void postErrorToast(String msg) {
+ showMessage(msg, Log.ERROR, true);
+ }
+
+ /** Logs the message at the specified level, then forwards it to the activity if present. */
+ private synchronized void showMessage(String msg, int logLevel, boolean showToast) {
+ Log.println(logLevel, TAG, msg);
+ if (mUserActivity != null) {
+ mUserActivity.showMessage(msg, showToast);
+ }
+ }
+
+ private synchronized void setModelState(ModelInfo modelInfo, String state) {
+ modelInfo.state = state;
+ if (mUserActivity != null) {
+ mUserActivity.setModelState(modelInfo.modelUuid, modelInfo.state);
+ }
+ }
+}
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
index 1c95c25..8e5ed32 100644
--- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
@@ -18,7 +18,6 @@
import android.annotation.Nullable;
import android.content.Context;
-import android.hardware.soundtrigger.SoundTrigger;
import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
import android.media.soundtrigger.SoundTriggerDetector;
import android.media.soundtrigger.SoundTriggerManager;
@@ -36,7 +35,7 @@
* Utility class for the managing sound trigger sound models.
*/
public class SoundTriggerUtil {
- private static final String TAG = "TestSoundTriggerUtil:Hotsound";
+ private static final String TAG = "SoundTriggerTestUtil";
private final ISoundTriggerService mSoundTriggerService;
private final SoundTriggerManager mSoundTriggerManager;
@@ -68,10 +67,6 @@
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,
@@ -91,7 +86,7 @@
}
if (model == null) {
- Log.w(TAG, "No models present for the gien keyphrase ID");
+ Log.w(TAG, "No models present for the given keyphrase ID");
return null;
} else {
return model;
@@ -109,18 +104,14 @@
try {
mSoundTriggerService.deleteSoundModel(new ParcelUuid(modelId));
} catch (RemoteException e) {
- Log.e(TAG, "RemoteException in updateSoundModel");
+ Log.e(TAG, "RemoteException in deleteSoundModel");
+ return false;
}
return true;
}
- public void deleteSoundModelUsingManager(UUID modelId) {
- mSoundTriggerManager.deleteModel(modelId);
- }
-
public SoundTriggerDetector createSoundTriggerDetector(UUID modelId,
SoundTriggerDetector.Callback callback) {
return mSoundTriggerManager.createSoundTriggerDetector(modelId, callback, null);
}
-
}
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java
deleted file mode 100644
index 5fd38e9..0000000
--- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java
+++ /dev/null
@@ -1,400 +0,0 @@
-/*
- * 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.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Random;
-import java.util.UUID;
-
-import android.app.Activity;
-import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
-import android.hardware.soundtrigger.SoundTrigger;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.MediaPlayer;
-import android.media.soundtrigger.SoundTriggerDetector;
-import android.media.soundtrigger.SoundTriggerManager;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.PowerManager;
-import android.os.UserManager;
-import android.text.Editable;
-import android.text.method.ScrollingMovementMethod;
-import android.util.Log;
-import android.view.View;
-import android.widget.Button;
-import android.widget.RadioButton;
-import android.widget.RadioButton;
-import android.widget.RadioGroup;
-import android.widget.ScrollView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-public class TestSoundTriggerActivity extends Activity {
- private static final String TAG = "TestSoundTriggerActivity";
- private static final boolean DBG = false;
-
- private SoundTriggerUtil mSoundTriggerUtil;
- private Random mRandom;
-
- private Map<Integer, ModelInfo> mModelInfoMap;
- private Map<View, Integer> mModelIdMap;
-
- private TextView mDebugView = null;
- private int mSelectedModelId = -1;
- private ScrollView mScrollView = null;
- private Button mPlayTriggerButton = null;
- private PowerManager.WakeLock mScreenWakelock;
- private Handler mHandler;
- private RadioGroup mRadioGroup;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- if (DBG) Log.d(TAG, "onCreate");
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- mDebugView = (TextView) findViewById(R.id.console);
- mScrollView = (ScrollView) findViewById(R.id.scroller_id);
- mRadioGroup = (RadioGroup) findViewById(R.id.model_group_id);
- mPlayTriggerButton = (Button) findViewById(R.id.play_trigger_id);
- mDebugView.setText(mDebugView.getText(), TextView.BufferType.EDITABLE);
- mDebugView.setMovementMethod(new ScrollingMovementMethod());
- mSoundTriggerUtil = new SoundTriggerUtil(this);
- mRandom = new Random();
- mHandler = new Handler();
-
- mModelInfoMap = new HashMap();
- mModelIdMap = new HashMap();
-
- setVolumeControlStream(AudioManager.STREAM_MUSIC);
-
- // Load all the models in the data dir.
- for (File file : getFilesDir().listFiles()) {
- // Find meta-data in .properties files, ignore everything else.
- if (!file.getName().endsWith(".properties")) {
- continue;
- }
- try {
- Properties properties = new Properties();
- properties.load(new FileInputStream(file));
- createModelInfoAndWidget(properties);
- } catch (Exception e) {
- Log.e(TAG, "Failed to load properties file " + file.getName());
- }
- }
-
- // Create a few dummy models if we didn't load anything.
- if (mModelIdMap.isEmpty()) {
- Properties dummyModelProperties = new Properties();
- for (String name : new String[]{"One", "Two", "Three"}) {
- dummyModelProperties.setProperty("name", "Model " + name);
- createModelInfoAndWidget(dummyModelProperties);
- }
- }
- }
-
- private void createModelInfoAndWidget(Properties properties) {
- try {
- ModelInfo modelInfo = new ModelInfo();
-
- if (!properties.containsKey("name")) {
- throw new RuntimeException("must have a 'name' property");
- }
- modelInfo.name = properties.getProperty("name");
-
- if (properties.containsKey("modelUuid")) {
- modelInfo.modelUuid = UUID.fromString(properties.getProperty("modelUuid"));
- } else {
- modelInfo.modelUuid = UUID.randomUUID();
- }
-
- if (properties.containsKey("vendorUuid")) {
- modelInfo.vendorUuid = UUID.fromString(properties.getProperty("vendorUuid"));
- } else {
- modelInfo.vendorUuid = UUID.randomUUID();
- }
-
- if (properties.containsKey("triggerAudio")) {
- modelInfo.triggerAudioPlayer = MediaPlayer.create(this, Uri.parse(
- getFilesDir().getPath() + "/" + properties.getProperty("triggerAudio")));
- }
-
- if (properties.containsKey("dataFile")) {
- File modelDataFile = new File(
- getFilesDir().getPath() + "/" + properties.getProperty("dataFile"));
- modelInfo.modelData = new byte[(int) modelDataFile.length()];
- FileInputStream input = new FileInputStream(modelDataFile);
- input.read(modelInfo.modelData, 0, modelInfo.modelData.length);
- } else {
- modelInfo.modelData = new byte[1024];
- mRandom.nextBytes(modelInfo.modelData);
- }
-
- // TODO: Add property support for keyphrase models when they're exposed by the
- // service. Also things like how much audio they should record with the capture session
- // provided in the callback.
-
- // Add a widget into the radio group.
- RadioButton button = new RadioButton(this);
- mRadioGroup.addView(button);
- button.setText(modelInfo.name);
- button.setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- onRadioButtonClicked(v);
- }
- });
-
- // Update our maps containing the button -> id and id -> modelInfo.
- int newModelId = mModelIdMap.size() + 1;
- mModelIdMap.put(button, newModelId);
- mModelInfoMap.put(newModelId, modelInfo);
-
- // If we don't have something selected, select this first thing.
- if (mSelectedModelId < 0) {
- button.setChecked(true);
- onRadioButtonClicked(button);
- }
- } catch (IOException e) {
- Log.e(TAG, "Error parsing properties for " + properties.getProperty("name"), e);
- }
- }
-
- private void postMessage(String msg) {
- Log.i(TAG, "Posted: " + msg);
- ((Editable) mDebugView.getText()).append(msg + "\n");
- if ((mDebugView.getMeasuredHeight() - mScrollView.getScrollY()) <=
- (mScrollView.getHeight() + mDebugView.getLineHeight())) {
- scrollToBottom();
- }
- }
-
- private void scrollToBottom() {
- mScrollView.post(new Runnable() {
- public void run() {
- mScrollView.smoothScrollTo(0, mDebugView.getBottom());
- }
- });
- }
-
- private synchronized UUID getSelectedUuid() {
- return mModelInfoMap.get(mSelectedModelId).modelUuid;
- }
-
- private synchronized void setDetector(SoundTriggerDetector detector) {
- mModelInfoMap.get(mSelectedModelId).detector = detector;
- }
-
- private synchronized SoundTriggerDetector getDetector() {
- return mModelInfoMap.get(mSelectedModelId).detector;
- }
-
- 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();
- }
-
- /** TODO: Should return the abstract sound model that can be then sent to the service. */
- private GenericSoundModel createNewSoundModel() {
- ModelInfo modelInfo = mModelInfoMap.get(mSelectedModelId);
- return new GenericSoundModel(modelInfo.modelUuid, modelInfo.vendorUuid,
- modelInfo.modelData);
- }
-
- /**
- * Called when the user clicks the enroll button.
- * Performs a fresh enrollment.
- */
- public void onEnrollButtonClicked(View v) {
- postMessage("Loading model: " + mSelectedModelId);
-
- GenericSoundModel model = createNewSoundModel();
-
- boolean status = mSoundTriggerUtil.addOrUpdateSoundModel(model);
- if (status) {
- Toast.makeText(
- this, "Successfully created sound trigger model UUID=" + model.uuid,
- Toast.LENGTH_SHORT).show();
- } else {
- Toast.makeText(this, "Failed to enroll!!!" + model.uuid, Toast.LENGTH_SHORT).show();
- }
-
- // Test the SoundManager API.
- }
-
- /**
- * Called when the user clicks the un-enroll button.
- * Clears the enrollment information for the user.
- */
- public void onUnEnrollButtonClicked(View v) {
- postMessage("Unloading model: " + mSelectedModelId);
- UUID modelUuid = getSelectedUuid();
- GenericSoundModel soundModel = mSoundTriggerUtil.getSoundModel(modelUuid);
- if (soundModel == null) {
- Toast.makeText(this, "Sound model not found!!!", Toast.LENGTH_SHORT).show();
- return;
- }
- boolean status = mSoundTriggerUtil.deleteSoundModel(modelUuid);
- 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();
- }
- }
-
- /**
- * 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) {
- postMessage("Re-loading model: " + mSelectedModelId);
- UUID modelUuid = getSelectedUuid();
- GenericSoundModel soundModel = mSoundTriggerUtil.getSoundModel(modelUuid);
- if (soundModel == null) {
- Toast.makeText(this, "Sound model not found!!!", Toast.LENGTH_SHORT).show();
- return;
- }
- GenericSoundModel updated = createNewSoundModel();
- 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();
- }
- }
-
- public void onStartRecognitionButtonClicked(View v) {
- UUID modelUuid = getSelectedUuid();
- SoundTriggerDetector detector = getDetector();
- if (detector == null) {
- Log.i(TAG, "Created an instance of the SoundTriggerDetector for model #" +
- mSelectedModelId);
- postMessage("Created an instance of the SoundTriggerDetector for model #" +
- mSelectedModelId);
- detector = mSoundTriggerUtil.createSoundTriggerDetector(modelUuid,
- new DetectorCallback());
- setDetector(detector);
- }
- postMessage("Triggering start recognition for model: " + mSelectedModelId);
- if (!detector.startRecognition(
- SoundTriggerDetector.RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS)) {
- Log.e(TAG, "Fast failure attempting to start recognition.");
- postMessage("Fast failure attempting to start recognition:" + mSelectedModelId);
- }
- }
-
- public void onStopRecognitionButtonClicked(View v) {
- SoundTriggerDetector detector = getDetector();
- if (detector == null) {
- Log.e(TAG, "Stop called on null detector.");
- postMessage("Error: Stop called on null detector.");
- return;
- }
- postMessage("Triggering stop recognition for model: " + mSelectedModelId);
- if (!detector.stopRecognition()) {
- Log.e(TAG, "Fast failure attempting to stop recognition.");
- postMessage("Fast failure attempting to stop recognition: " + mSelectedModelId);
- }
- }
-
- public synchronized void onRadioButtonClicked(View view) {
- // Is the button now checked?
- boolean checked = ((RadioButton) view).isChecked();
- if (checked) {
- mSelectedModelId = mModelIdMap.get(view);
- ModelInfo modelInfo = mModelInfoMap.get(mSelectedModelId);
- postMessage("Selected " + modelInfo.name);
-
- // Set the play trigger button to be enabled only if we actually have some audio.
- mPlayTriggerButton.setEnabled(modelInfo.triggerAudioPlayer != null);
- }
- }
-
- public synchronized void onPlayTriggerButtonClicked(View v) {
- ModelInfo modelInfo = mModelInfoMap.get(mSelectedModelId);
- modelInfo.triggerAudioPlayer.start();
- postMessage("Playing trigger audio for " + modelInfo.name);
- }
-
- // Helper struct for holding information about a model.
- private static class ModelInfo {
- public String name;
- public UUID modelUuid;
- public UUID vendorUuid;
- public MediaPlayer triggerAudioPlayer;
- public SoundTriggerDetector detector;
- public byte modelData[];
- };
-
- // Implementation of SoundTriggerDetector.Callback.
- public class DetectorCallback extends SoundTriggerDetector.Callback {
- public void onAvailabilityChanged(int status) {
- postMessage("Availability changed to: " + status);
- }
-
- public void onDetected(SoundTriggerDetector.EventPayload event) {
- postMessage("onDetected(): " + eventPayloadToString(event));
- screenWakeup();
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- screenRelease();
- }
- }, 1000L);
- }
-
- public void onError() {
- postMessage("onError()");
- }
-
- public void onRecognitionPaused() {
- postMessage("onRecognitionPaused()");
- }
-
- public void onRecognitionResumed() {
- postMessage("onRecognitionResumed()");
- }
- }
-
- private String eventPayloadToString(SoundTriggerDetector.EventPayload event) {
- String result = "EventPayload(";
- AudioFormat format = event.getCaptureAudioFormat();
- result = result + "AudioFormat: " + ((format == null) ? "null" : format.toString());
- byte[] triggerAudio = event.getTriggerAudio();
- result = result + "TriggerAudio: " + (triggerAudio == null ? "null" : triggerAudio.length);
- result = result + "CaptureSession: " + event.getCaptureSession();
- result += " )";
- return result;
- }
-}