/* * 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; } } }; }