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         requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO},
101                 AUDIO_PERMISSIONS_REQUEST);
102     }
103 
104     @Override
onDestroy()105     protected void onDestroy() {
106         super.onDestroy();
107 
108         // Unbind from the service.
109         if (mService != null) {
110             mService.setUserActivity(null);
111             unbindService(mConnection);
112         }
113     }
114 
115     @Override
addModel(UUID modelUuid, String name)116     public void addModel(UUID modelUuid, String name) {
117         // Create a new widget for this model, and insert everything we'd need into the map.
118         RadioButton button = new RadioButton(this);
119         mModelRadioButtons.add(button);
120         button.setText(name);
121         button.setOnClickListener(new View.OnClickListener() {
122             public void onClick(View v) {
123                 onRadioButtonClicked(v);
124             }
125         });
126         mButtonModelUuidMap.put(button, modelUuid);
127         mModelButtons.put(modelUuid, button);
128         mModelNames.put(modelUuid, name);
129 
130         // Sort all the radio buttons by name, then push them into the group in order.
131         Collections.sort(mModelRadioButtons, new Comparator<RadioButton>(){
132             @Override
133             public int compare(RadioButton button0, RadioButton button1) {
134                 return button0.getText().toString().compareTo(button1.getText().toString());
135             }
136         });
137         mRadioGroup.removeAllViews();
138         for (View v : mModelRadioButtons) {
139             mRadioGroup.addView(v);
140         }
141 
142         // If we don't have something selected, select this first thing.
143         if (mSelectedModelUuid == null || mSelectedModelUuid.equals(modelUuid)) {
144             button.setChecked(true);
145             onRadioButtonClicked(button);
146         }
147     }
148 
149     @Override
setModelState(UUID modelUuid, String state)150     public void setModelState(UUID modelUuid, String state) {
151         runOnUiThread(new Runnable() {
152             @Override
153             public void run() {
154                 String newButtonText = mModelNames.get(modelUuid);
155                 if (state != null) {
156                     newButtonText += ": " + state;
157                 }
158                 mModelButtons.get(modelUuid).setText(newButtonText);
159                 updateSelectModelSpecificUiElements();
160             }
161         });
162     }
163 
164     @Override
showMessage(String msg, boolean showToast)165     public void showMessage(String msg, boolean showToast) {
166         // Append the message to the text field, then show the toast if requested.
167         this.runOnUiThread(new Runnable() {
168             @Override
169             public void run() {
170                 ((Editable) mDebugView.getText()).append(msg + "\n");
171                 mScrollView.post(new Runnable() {
172                     public void run() {
173                         mScrollView.smoothScrollTo(0, mDebugView.getBottom());
174                     }
175                 });
176                 if (showToast) {
177                     Toast.makeText(SoundTriggerTestActivity.this, msg, Toast.LENGTH_SHORT).show();
178                 }
179             }
180         });
181     }
182 
183     @Override
handleDetection(UUID modelUuid)184     public void handleDetection(UUID modelUuid) {
185         screenWakeup();
186         mHandler.postDelayed(new Runnable() {
187             @Override
188             public void run() {
189                 screenRelease();
190             }
191         }, 1000L);
192     }
193 
screenWakeup()194     private void screenWakeup() {
195         if (mScreenWakelock == null) {
196             PowerManager pm = ((PowerManager)getSystemService(POWER_SERVICE));
197             mScreenWakelock =  pm.newWakeLock(
198                     PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, TAG);
199         }
200         mScreenWakelock.acquire();
201     }
202 
screenRelease()203     private void screenRelease() {
204         mScreenWakelock.release();
205     }
206 
onLoadButtonClicked(View v)207     public void onLoadButtonClicked(View v) {
208         if (mService == null) {
209             Log.e(TAG, "Could not load sound model: not bound to SoundTriggerTestService");
210         } else {
211             mService.loadModel(mSelectedModelUuid);
212         }
213     }
214 
onUnloadButtonClicked(View v)215     public void onUnloadButtonClicked(View v) {
216         if (mService == null) {
217            Log.e(TAG, "Can't unload model: not bound to SoundTriggerTestService");
218         } else {
219             mService.unloadModel(mSelectedModelUuid);
220         }
221     }
222 
onReloadButtonClicked(View v)223     public void onReloadButtonClicked(View v) {
224         if (mService == null) {
225             Log.e(TAG, "Can't reload model: not bound to SoundTriggerTestService");
226         } else {
227             mService.reloadModel(mSelectedModelUuid);
228         }
229     }
230 
onStartRecognitionButtonClicked(View v)231     public void onStartRecognitionButtonClicked(View v) {
232         if (mService == null) {
233             Log.e(TAG, "Can't start recognition: not bound to SoundTriggerTestService");
234         } else {
235             mService.startRecognition(mSelectedModelUuid);
236         }
237     }
238 
onStopRecognitionButtonClicked(View v)239     public void onStopRecognitionButtonClicked(View v) {
240         if (mService == null) {
241             Log.e(TAG, "Can't stop recognition: not bound to SoundTriggerTestService");
242         } else {
243             mService.stopRecognition(mSelectedModelUuid);
244         }
245     }
246 
onPlayTriggerButtonClicked(View v)247     public synchronized void onPlayTriggerButtonClicked(View v) {
248         if (mService == null) {
249             Log.e(TAG, "Can't play trigger audio: not bound to SoundTriggerTestService");
250         } else {
251             mService.playTriggerAudio(mSelectedModelUuid);
252         }
253     }
254 
onGetModelStateButtonClicked(View v)255     public synchronized void onGetModelStateButtonClicked(View v) {
256         if (mService == null) {
257             Log.e(TAG, "Can't get model state: not bound to SoundTriggerTestService");
258         } else {
259             mService.getModelState(mSelectedModelUuid);
260         }
261     }
262 
onCaptureAudioCheckboxClicked(View v)263     public synchronized void onCaptureAudioCheckboxClicked(View v) {
264         // See if we have the right permissions
265         if (mService == null) {
266             Log.e(TAG, "Can't set capture audio: not bound to SoundTriggerTestService");
267         } else {
268             if (!mService.hasMicrophonePermission()) {
269                 requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO},
270                         AUDIO_PERMISSIONS_REQUEST);
271                 return;
272             } else {
273                 mService.setCaptureAudio(mSelectedModelUuid, mCaptureAudioCheckBox.isChecked());
274             }
275         }
276     }
277 
278     @Override
onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults)279     public synchronized void onRequestPermissionsResult(int requestCode, String permissions[],
280                                                         int[] grantResults) {
281         if (requestCode == AUDIO_PERMISSIONS_REQUEST) {
282             if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
283                 // Make sure that the check box is set to false.
284                 mCaptureAudioCheckBox.setChecked(false);
285             } else {
286                 // After granted Record_Audio permission, start and bind the service.
287                 // so we can run that sound trigger capability,
288                 // even if our activity goes down, we'll still have a request for it to run.
289                 startService(new Intent(getBaseContext(), SoundTriggerTestService.class));
290                 // Bind to SoundTriggerTestService.
291                 Intent intent = new Intent(this, SoundTriggerTestService.class);
292                 bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
293             }
294         }
295     }
296 
onPlayCapturedAudioButtonClicked(View v)297     public synchronized void onPlayCapturedAudioButtonClicked(View v) {
298         if (mService == null) {
299             Log.e(TAG, "Can't play captured audio: not bound to SoundTriggerTestService");
300         } else {
301             mService.playCapturedAudio(mSelectedModelUuid);
302         }
303     }
304 
onRadioButtonClicked(View view)305     public synchronized void onRadioButtonClicked(View view) {
306         // Is the button now checked?
307         boolean checked = ((RadioButton) view).isChecked();
308         if (checked) {
309             mSelectedModelUuid = mButtonModelUuidMap.get(view);
310             showMessage("Selected " + mModelNames.get(mSelectedModelUuid), false);
311             updateSelectModelSpecificUiElements();
312         }
313     }
314 
updateSelectModelSpecificUiElements()315     private synchronized void updateSelectModelSpecificUiElements() {
316         // Set the play trigger button to be enabled only if we actually have some audio.
317         mPlayTriggerButton.setEnabled(mService.modelHasTriggerAudio((mSelectedModelUuid)));
318         // Similar logic for the captured audio.
319         mCaptureAudioCheckBox.setChecked(
320                 mService.modelWillCaptureTriggerAudio(mSelectedModelUuid));
321         mPlayCapturedAudioButton.setEnabled(mService.modelHasCapturedAudio((mSelectedModelUuid)));
322     }
323 
324     private ServiceConnection mConnection = new ServiceConnection() {
325         @Override
326         public void onServiceConnected(ComponentName className, IBinder service) {
327             synchronized (SoundTriggerTestActivity.this) {
328                 // We've bound to LocalService, cast the IBinder and get LocalService instance
329                 SoundTriggerTestBinder binder = (SoundTriggerTestBinder) service;
330                 mService = binder.getService();
331                 mService.setUserActivity(SoundTriggerTestActivity.this);
332             }
333         }
334 
335         @Override
336         public void onServiceDisconnected(ComponentName arg0) {
337             synchronized (SoundTriggerTestActivity.this) {
338                 mService.setUserActivity(null);
339                 mService = null;
340             }
341         }
342     };
343 }
344