1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.test.soundtrigger; 18 19 import java.util.Collections; 20 import java.util.Comparator; 21 import java.util.HashMap; 22 import java.util.LinkedList; 23 import java.util.List; 24 import java.util.Map; 25 import java.util.UUID; 26 27 import android.Manifest; 28 import android.app.Activity; 29 import android.content.ComponentName; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.content.ServiceConnection; 33 import android.content.pm.PackageManager; 34 import android.media.AudioManager; 35 import android.os.Bundle; 36 import android.os.Handler; 37 import android.os.IBinder; 38 import android.os.PowerManager; 39 import android.text.Editable; 40 import android.text.method.ScrollingMovementMethod; 41 import android.util.Log; 42 import android.view.View; 43 import android.view.WindowManager; 44 import android.widget.Button; 45 import android.widget.CheckBox; 46 import android.widget.RadioButton; 47 import android.widget.RadioGroup; 48 import android.widget.ScrollView; 49 import android.widget.TextView; 50 import android.widget.Toast; 51 52 import com.android.test.soundtrigger.SoundTriggerTestService.SoundTriggerTestBinder; 53 54 public class SoundTriggerTestActivity extends Activity implements SoundTriggerTestService.UserActivity { 55 private static final String TAG = "SoundTriggerTest"; 56 private static final int AUDIO_PERMISSIONS_REQUEST = 1; 57 58 private SoundTriggerTestService mService = null; 59 60 private static UUID mSelectedModelUuid = null; 61 62 private Map<RadioButton, UUID> mButtonModelUuidMap; 63 private Map<UUID, RadioButton> mModelButtons; 64 private Map<UUID, String> mModelNames; 65 private List<RadioButton> mModelRadioButtons; 66 67 private TextView mDebugView = null; 68 private ScrollView mScrollView = null; 69 private Button mPlayTriggerButton = null; 70 private PowerManager.WakeLock mScreenWakelock; 71 private Handler mHandler; 72 private RadioGroup mRadioGroup; 73 private CheckBox mCaptureAudioCheckBox; 74 private Button mPlayCapturedAudioButton = null; 75 76 @Override onCreate(Bundle savedInstanceState)77 protected void onCreate(Bundle savedInstanceState) { 78 // Make sure that this activity can punch through the lockscreen if needed. 79 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | 80 WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); 81 82 super.onCreate(savedInstanceState); 83 setContentView(R.layout.main); 84 mDebugView = findViewById(R.id.console); 85 mScrollView = findViewById(R.id.scroller_id); 86 mRadioGroup = findViewById(R.id.model_group_id); 87 mPlayTriggerButton = findViewById(R.id.play_trigger_id); 88 mDebugView.setText(mDebugView.getText(), TextView.BufferType.EDITABLE); 89 mDebugView.setMovementMethod(new ScrollingMovementMethod()); 90 mCaptureAudioCheckBox = findViewById(R.id.caputre_check_box); 91 mPlayCapturedAudioButton = findViewById(R.id.play_captured_id); 92 mHandler = new Handler(); 93 mButtonModelUuidMap = new HashMap(); 94 mModelButtons = new HashMap(); 95 mModelNames = new HashMap(); 96 mModelRadioButtons = new LinkedList(); 97 98 setVolumeControlStream(AudioManager.STREAM_MUSIC); 99 100 // Make sure that the service is started, so even if our activity goes down, we'll still 101 // have a request for it to run. 102 startService(new Intent(getBaseContext(), SoundTriggerTestService.class)); 103 104 // Bind to SoundTriggerTestService. 105 Intent intent = new Intent(this, SoundTriggerTestService.class); 106 bindService(intent, mConnection, Context.BIND_AUTO_CREATE); 107 } 108 109 @Override onDestroy()110 protected void onDestroy() { 111 super.onDestroy(); 112 113 // Unbind from the service. 114 if (mService != null) { 115 mService.setUserActivity(null); 116 unbindService(mConnection); 117 } 118 } 119 120 @Override addModel(UUID modelUuid, String name)121 public void addModel(UUID modelUuid, String name) { 122 // Create a new widget for this model, and insert everything we'd need into the map. 123 RadioButton button = new RadioButton(this); 124 mModelRadioButtons.add(button); 125 button.setText(name); 126 button.setOnClickListener(new View.OnClickListener() { 127 public void onClick(View v) { 128 onRadioButtonClicked(v); 129 } 130 }); 131 mButtonModelUuidMap.put(button, modelUuid); 132 mModelButtons.put(modelUuid, button); 133 mModelNames.put(modelUuid, name); 134 135 // Sort all the radio buttons by name, then push them into the group in order. 136 Collections.sort(mModelRadioButtons, new Comparator<RadioButton>(){ 137 @Override 138 public int compare(RadioButton button0, RadioButton button1) { 139 return button0.getText().toString().compareTo(button1.getText().toString()); 140 } 141 }); 142 mRadioGroup.removeAllViews(); 143 for (View v : mModelRadioButtons) { 144 mRadioGroup.addView(v); 145 } 146 147 // If we don't have something selected, select this first thing. 148 if (mSelectedModelUuid == null || mSelectedModelUuid.equals(modelUuid)) { 149 button.setChecked(true); 150 onRadioButtonClicked(button); 151 } 152 } 153 154 @Override setModelState(UUID modelUuid, String state)155 public void setModelState(UUID modelUuid, String state) { 156 runOnUiThread(new Runnable() { 157 @Override 158 public void run() { 159 String newButtonText = mModelNames.get(modelUuid); 160 if (state != null) { 161 newButtonText += ": " + state; 162 } 163 mModelButtons.get(modelUuid).setText(newButtonText); 164 updateSelectModelSpecificUiElements(); 165 } 166 }); 167 } 168 169 @Override showMessage(String msg, boolean showToast)170 public void showMessage(String msg, boolean showToast) { 171 // Append the message to the text field, then show the toast if requested. 172 this.runOnUiThread(new Runnable() { 173 @Override 174 public void run() { 175 ((Editable) mDebugView.getText()).append(msg + "\n"); 176 mScrollView.post(new Runnable() { 177 public void run() { 178 mScrollView.smoothScrollTo(0, mDebugView.getBottom()); 179 } 180 }); 181 if (showToast) { 182 Toast.makeText(SoundTriggerTestActivity.this, msg, Toast.LENGTH_SHORT).show(); 183 } 184 } 185 }); 186 } 187 188 @Override handleDetection(UUID modelUuid)189 public void handleDetection(UUID modelUuid) { 190 screenWakeup(); 191 mHandler.postDelayed(new Runnable() { 192 @Override 193 public void run() { 194 screenRelease(); 195 } 196 }, 1000L); 197 } 198 screenWakeup()199 private void screenWakeup() { 200 if (mScreenWakelock == null) { 201 PowerManager pm = ((PowerManager)getSystemService(POWER_SERVICE)); 202 mScreenWakelock = pm.newWakeLock( 203 PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, TAG); 204 } 205 mScreenWakelock.acquire(); 206 } 207 screenRelease()208 private void screenRelease() { 209 mScreenWakelock.release(); 210 } 211 onLoadButtonClicked(View v)212 public void onLoadButtonClicked(View v) { 213 if (mService == null) { 214 Log.e(TAG, "Could not load sound model: not bound to SoundTriggerTestService"); 215 } else { 216 mService.loadModel(mSelectedModelUuid); 217 } 218 } 219 onUnloadButtonClicked(View v)220 public void onUnloadButtonClicked(View v) { 221 if (mService == null) { 222 Log.e(TAG, "Can't unload model: not bound to SoundTriggerTestService"); 223 } else { 224 mService.unloadModel(mSelectedModelUuid); 225 } 226 } 227 onReloadButtonClicked(View v)228 public void onReloadButtonClicked(View v) { 229 if (mService == null) { 230 Log.e(TAG, "Can't reload model: not bound to SoundTriggerTestService"); 231 } else { 232 mService.reloadModel(mSelectedModelUuid); 233 } 234 } 235 onStartRecognitionButtonClicked(View v)236 public void onStartRecognitionButtonClicked(View v) { 237 if (mService == null) { 238 Log.e(TAG, "Can't start recognition: not bound to SoundTriggerTestService"); 239 } else { 240 mService.startRecognition(mSelectedModelUuid); 241 } 242 } 243 onStopRecognitionButtonClicked(View v)244 public void onStopRecognitionButtonClicked(View v) { 245 if (mService == null) { 246 Log.e(TAG, "Can't stop recognition: not bound to SoundTriggerTestService"); 247 } else { 248 mService.stopRecognition(mSelectedModelUuid); 249 } 250 } 251 onPlayTriggerButtonClicked(View v)252 public synchronized void onPlayTriggerButtonClicked(View v) { 253 if (mService == null) { 254 Log.e(TAG, "Can't play trigger audio: not bound to SoundTriggerTestService"); 255 } else { 256 mService.playTriggerAudio(mSelectedModelUuid); 257 } 258 } 259 onCaptureAudioCheckboxClicked(View v)260 public synchronized void onCaptureAudioCheckboxClicked(View v) { 261 // See if we have the right permissions 262 if (!mService.hasMicrophonePermission()) { 263 requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, 264 AUDIO_PERMISSIONS_REQUEST); 265 return; 266 } else { 267 mService.setCaptureAudio(mSelectedModelUuid, mCaptureAudioCheckBox.isChecked()); 268 } 269 } 270 271 @Override onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults)272 public synchronized void onRequestPermissionsResult(int requestCode, String permissions[], 273 int[] grantResults) { 274 if (requestCode == AUDIO_PERMISSIONS_REQUEST) { 275 if (grantResults[0] != PackageManager.PERMISSION_GRANTED) { 276 // Make sure that the check box is set to false. 277 mCaptureAudioCheckBox.setChecked(false); 278 } 279 mService.setCaptureAudio(mSelectedModelUuid, mCaptureAudioCheckBox.isChecked()); 280 } 281 } 282 onPlayCapturedAudioButtonClicked(View v)283 public synchronized void onPlayCapturedAudioButtonClicked(View v) { 284 if (mService == null) { 285 Log.e(TAG, "Can't play captured audio: not bound to SoundTriggerTestService"); 286 } else { 287 mService.playCapturedAudio(mSelectedModelUuid); 288 } 289 } 290 onRadioButtonClicked(View view)291 public synchronized void onRadioButtonClicked(View view) { 292 // Is the button now checked? 293 boolean checked = ((RadioButton) view).isChecked(); 294 if (checked) { 295 mSelectedModelUuid = mButtonModelUuidMap.get(view); 296 showMessage("Selected " + mModelNames.get(mSelectedModelUuid), false); 297 updateSelectModelSpecificUiElements(); 298 } 299 } 300 updateSelectModelSpecificUiElements()301 private synchronized void updateSelectModelSpecificUiElements() { 302 // Set the play trigger button to be enabled only if we actually have some audio. 303 mPlayTriggerButton.setEnabled(mService.modelHasTriggerAudio((mSelectedModelUuid))); 304 // Similar logic for the captured audio. 305 mCaptureAudioCheckBox.setChecked( 306 mService.modelWillCaptureTriggerAudio(mSelectedModelUuid)); 307 mPlayCapturedAudioButton.setEnabled(mService.modelHasCapturedAudio((mSelectedModelUuid))); 308 } 309 310 private ServiceConnection mConnection = new ServiceConnection() { 311 @Override 312 public void onServiceConnected(ComponentName className, IBinder service) { 313 synchronized (SoundTriggerTestActivity.this) { 314 // We've bound to LocalService, cast the IBinder and get LocalService instance 315 SoundTriggerTestBinder binder = (SoundTriggerTestBinder) service; 316 mService = binder.getService(); 317 mService.setUserActivity(SoundTriggerTestActivity.this); 318 } 319 } 320 321 @Override 322 public void onServiceDisconnected(ComponentName arg0) { 323 synchronized (SoundTriggerTestActivity.this) { 324 mService.setUserActivity(null); 325 mService = null; 326 } 327 } 328 }; 329 } 330