Merge "Updates to the sound trigger test app" into nyc-mr1-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
e1fdcc49fd
@@ -1,25 +1,28 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.test.soundtrigger">
|
||||
|
||||
<uses-permission android:name="android.permission.CAPTURE_AUDIO_HOTWORD" />
|
||||
<uses-permission android:name="android.permission.MANAGE_SOUND_TRIGGER" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<application>
|
||||
<activity
|
||||
android:name="TestSoundTriggerActivity"
|
||||
android:name=".SoundTriggerTestActivity"
|
||||
android:label="SoundTrigger Test Application"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@android:style/Theme.Material">
|
||||
<!--
|
||||
<intent-filter>
|
||||
<action android:name="com.android.intent.action.MANAGE_SOUND_TRIGGER" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
-->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<service
|
||||
android:name=".SoundTriggerTestService"
|
||||
android:stopWithTask="false"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="com.android.intent.action.MANAGE_SOUND_TRIGGER" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -18,81 +18,107 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
>
|
||||
android:orientation="vertical">
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/load"
|
||||
android:onClick="onLoadButtonClicked"
|
||||
android:padding="20dp" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/start_recog"
|
||||
android:onClick="onStartRecognitionButtonClicked"
|
||||
android:padding="20dp" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/stop_recog"
|
||||
android:onClick="onStopRecognitionButtonClicked"
|
||||
android:padding="20dp" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/unload"
|
||||
android:onClick="onUnloadButtonClicked"
|
||||
android:padding="20dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/reload"
|
||||
android:onClick="onReloadButtonClicked"
|
||||
android:padding="20dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/play_trigger_id"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/play_trigger"
|
||||
android:onClick="onPlayTriggerButtonClicked"
|
||||
android:padding="20dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/enroll"
|
||||
android:onClick="onEnrollButtonClicked"
|
||||
android:padding="20dp" />
|
||||
android:layout_gravity="right">
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/reenroll"
|
||||
android:onClick="onReEnrollButtonClicked"
|
||||
android:padding="20dp" />
|
||||
<CheckBox
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/capture"
|
||||
android:id="@+id/caputre_check_box"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:padding="20dp" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/start_recog"
|
||||
android:onClick="onStartRecognitionButtonClicked"
|
||||
android:padding="20dp" />
|
||||
<Button
|
||||
android:id="@+id/play_captured_id"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/play_capture"
|
||||
android:padding="20dp"
|
||||
android:enabled="false" />
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/stop_recog"
|
||||
android:onClick="onStopRecognitionButtonClicked"
|
||||
android:padding="20dp" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/unenroll"
|
||||
android:onClick="onUnEnrollButtonClicked"
|
||||
android:padding="20dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/play_trigger_id"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/play_trigger"
|
||||
android:onClick="onPlayTriggerButtonClicked"
|
||||
android:padding="20dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<RadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<RadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/model_group_id"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="20dp"
|
||||
android:orientation="vertical">
|
||||
</RadioGroup>
|
||||
android:orientation="vertical" />
|
||||
|
||||
<ScrollView
|
||||
<ScrollView
|
||||
android:id="@+id/scroller_id"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="vertical"
|
||||
android:fillViewport="true">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/console"
|
||||
android:paddingTop="20pt"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_width="fill_parent"
|
||||
android:textSize="14dp"
|
||||
android:layout_weight="1.0"
|
||||
android:text="@string/none">
|
||||
</TextView>
|
||||
</ScrollView>
|
||||
<TextView
|
||||
android:id="@+id/console"
|
||||
android:paddingTop="20pt"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_width="fill_parent"
|
||||
android:textSize="14dp"
|
||||
android:layout_weight="1.0"
|
||||
android:text="@string/none" />
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -16,11 +16,14 @@
|
||||
-->
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
|
||||
<string name="enroll">Load</string>
|
||||
<string name="reenroll">Re-load</string>
|
||||
<string name="unenroll">Un-load</string>
|
||||
<string name="load">Load</string>
|
||||
<string name="reload">Reload</string>
|
||||
<string name="unload">Unload</string>
|
||||
<string name="start_recog">Start</string>
|
||||
<string name="stop_recog">Stop</string>
|
||||
<string name="play_trigger">Play Trigger Audio</string>
|
||||
<string name="capture">Capture Audio</string>
|
||||
<string name="stop_capture">Stop Capturing Audio</string>
|
||||
<string name="play_capture">Play Captured Audio</string>
|
||||
<string name="none">Debug messages appear here:\n</string>
|
||||
</resources>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ package com.android.test.soundtrigger;
|
||||
|
||||
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 @@ import java.util.UUID;
|
||||
* 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 @@ public class SoundTriggerUtil {
|
||||
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 @@ public class SoundTriggerUtil {
|
||||
}
|
||||
|
||||
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 @@ public class SoundTriggerUtil {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user