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