1 /*
2  * Copyright 2017 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.mobileer.oboetester;
18 
19 import android.content.Context;
20 import android.media.AudioManager;
21 import android.util.AttributeSet;
22 import android.view.LayoutInflater;
23 import android.view.View;
24 import android.widget.AdapterView;
25 import android.widget.CheckBox;
26 import android.widget.Spinner;
27 import android.widget.TableRow;
28 import android.widget.TextView;
29 import android.widget.LinearLayout;
30 
31 import com.mobileer.audio_device.AudioDeviceListEntry;
32 import com.mobileer.audio_device.AudioDeviceSpinner;
33 
34 /**
35  * View for Editing a requested StreamConfiguration
36  * and displaying the actual StreamConfiguration.
37  */
38 
39 public class StreamConfigurationView extends LinearLayout {
40 
41     private StreamConfiguration  mRequestedConfiguration;
42     private StreamConfiguration  mActualConfiguration;
43 
44     protected Spinner mNativeApiSpinner;
45     private TextView mActualNativeApiView;
46 
47     private TextView mActualMMapView;
48     private CheckBox mRequestedMMapView;
49     private TextView mActualExclusiveView;
50     private TextView mActualPerformanceView;
51     private Spinner  mPerformanceSpinner;
52     private CheckBox mRequestedExclusiveView;
53     private CheckBox mChannelConversionBox;
54     private CheckBox mFormatConversionBox;
55     private Spinner  mChannelCountSpinner;
56     private TextView mActualChannelCountView;
57     private TextView mActualFormatView;
58 
59     private TableRow mInputPresetTableRow;
60     private Spinner  mInputPresetSpinner;
61     private TextView mActualInputPresetView;
62 
63     private TableRow mUsageTableRow;
64     private Spinner  mUsageSpinner;
65     private TextView mActualUsageView;
66 
67     private Spinner  mFormatSpinner;
68     private Spinner  mSampleRateSpinner;
69     private Spinner  mRateConversionQualitySpinner;
70     private TextView mActualSampleRateView;
71     private LinearLayout mHideableView;
72 
73     private AudioDeviceSpinner mDeviceSpinner;
74     private TextView mActualSessionIdView;
75     private CheckBox mRequestAudioEffect;
76 
77     private TextView mStreamInfoView;
78     private TextView mStreamStatusView;
79     private TextView mOptionExpander;
80     private String mHideSettingsText;
81     private String mShowSettingsText;
82 
83     // Create an anonymous implementation of OnClickListener
84     private View.OnClickListener mToggleListener = new View.OnClickListener() {
85         public void onClick(View v) {
86             if (mHideableView.isShown()) {
87                 hideSettingsView();
88             } else {
89                 showSettingsView();
90             }
91         }
92     };
93 
yesOrNo(boolean b)94     public static String yesOrNo(boolean b) {
95         return b ?  "YES" : "NO";
96     }
97 
updateSettingsViewText()98     private void updateSettingsViewText() {
99         if (mHideableView.isShown()) {
100             mOptionExpander.setText(mHideSettingsText);
101         } else {
102             mOptionExpander.setText(mShowSettingsText);
103         }
104     }
105 
showSettingsView()106     public void showSettingsView() {
107         mHideableView.setVisibility(View.VISIBLE);
108         updateSettingsViewText();
109     }
110 
hideSampleRateMenu()111     public void hideSampleRateMenu() {
112         if (mSampleRateSpinner != null) {
113             mSampleRateSpinner.setVisibility(View.GONE);
114         }
115     }
116 
hideSettingsView()117     public void hideSettingsView() {
118         mHideableView.setVisibility(View.GONE);
119         updateSettingsViewText();
120     }
121 
StreamConfigurationView(Context context)122     public StreamConfigurationView(Context context) {
123         super(context);
124         initializeViews(context);
125     }
126 
StreamConfigurationView(Context context, AttributeSet attrs)127     public StreamConfigurationView(Context context, AttributeSet attrs) {
128         super(context, attrs);
129         initializeViews(context);
130     }
131 
StreamConfigurationView(Context context, AttributeSet attrs, int defStyle)132     public StreamConfigurationView(Context context,
133                                    AttributeSet attrs,
134                                    int defStyle) {
135         super(context, attrs, defStyle);
136         initializeViews(context);
137     }
138 
139     /**
140      * Inflates the views in the layout.
141      *
142      * @param context
143      *           the current context for the view.
144      */
initializeViews(Context context)145     private void initializeViews(Context context) {
146         LayoutInflater inflater = (LayoutInflater) context
147                 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
148         inflater.inflate(R.layout.stream_config, this);
149 
150         mHideSettingsText = getResources().getString(R.string.hint_hide_settings);
151         mShowSettingsText = getResources().getString(R.string.hint_show_settings);
152 
153         mHideableView = (LinearLayout) findViewById(R.id.hideableView);
154 
155         mOptionExpander = (TextView) findViewById(R.id.toggle_stream_config);
156         mOptionExpander.setOnClickListener(mToggleListener);
157 
158         mNativeApiSpinner = (Spinner) findViewById(R.id.spinnerNativeApi);
159         mNativeApiSpinner.setOnItemSelectedListener(new NativeApiSpinnerListener());
160         mNativeApiSpinner.setSelection(StreamConfiguration.NATIVE_API_UNSPECIFIED);
161 
162         mActualNativeApiView = (TextView) findViewById(R.id.actualNativeApi);
163 
164         mChannelConversionBox = (CheckBox) findViewById(R.id.checkChannelConversion);
165         mChannelConversionBox.setOnClickListener(new View.OnClickListener() {
166             @Override
167             public void onClick(View v) {
168                 mRequestedConfiguration.setChannelConversionAllowed(mChannelConversionBox.isChecked());
169             }
170         });
171 
172         mFormatConversionBox = (CheckBox) findViewById(R.id.checkFormatConversion);
173         mFormatConversionBox.setOnClickListener(new View.OnClickListener() {
174             @Override
175             public void onClick(View v) {
176                 mRequestedConfiguration.setFormatConversionAllowed(mFormatConversionBox.isChecked());
177             }
178         });
179 
180         mActualMMapView = (TextView) findViewById(R.id.actualMMap);
181         mRequestedMMapView = (CheckBox) findViewById(R.id.requestedMMapEnable);
182         mRequestedMMapView.setOnClickListener(new View.OnClickListener() {
183             @Override
184             public void onClick(View v) {
185                 mRequestedConfiguration.setMMap(mRequestedMMapView.isChecked());
186             }
187         });
188         boolean mmapSupported = NativeEngine.isMMapSupported();
189         mRequestedMMapView.setEnabled(mmapSupported);
190         mRequestedMMapView.setChecked(mmapSupported);
191 
192         mActualExclusiveView = (TextView) findViewById(R.id.actualExclusiveMode);
193         mRequestedExclusiveView = (CheckBox) findViewById(R.id.requestedExclusiveMode);
194         mRequestedExclusiveView.setOnClickListener(new View.OnClickListener() {
195             @Override
196             public void onClick(View v) {
197                 mRequestedConfiguration.setSharingMode(mRequestedExclusiveView.isChecked()
198                         ? StreamConfiguration.SHARING_MODE_EXCLUSIVE
199                         : StreamConfiguration.SHARING_MODE_SHARED);
200             }
201         });
202 
203         boolean mmapExclusiveSupported = NativeEngine.isMMapExclusiveSupported();
204         mRequestedExclusiveView.setEnabled(mmapExclusiveSupported);
205         mRequestedExclusiveView.setChecked(mmapExclusiveSupported);
206 
207         mActualSessionIdView = (TextView) findViewById(R.id.sessionId);
208         mRequestAudioEffect = (CheckBox) findViewById(R.id.requestAudioEffect);
209         mRequestAudioEffect.setOnClickListener(new View.OnClickListener() {
210             @Override
211             public void onClick(View v) {
212                 mRequestedConfiguration.setSessionId(mRequestAudioEffect.isChecked()
213                         ? StreamConfiguration.SESSION_ID_ALLOCATE
214                         : StreamConfiguration.SESSION_ID_NONE);
215             }
216         });
217 
218         mActualSampleRateView = (TextView) findViewById(R.id.actualSampleRate);
219         mSampleRateSpinner = (Spinner) findViewById(R.id.spinnerSampleRate);
220         mSampleRateSpinner.setOnItemSelectedListener(new SampleRateSpinnerListener());
221 
222         mActualChannelCountView = (TextView) findViewById(R.id.actualChannelCount);
223         mChannelCountSpinner = (Spinner) findViewById(R.id.spinnerChannelCount);
224         mChannelCountSpinner.setOnItemSelectedListener(new ChannelCountSpinnerListener());
225 
226         mActualFormatView = (TextView) findViewById(R.id.actualAudioFormat);
227         mFormatSpinner = (Spinner) findViewById(R.id.spinnerFormat);
228         mFormatSpinner.setOnItemSelectedListener(new FormatSpinnerListener());
229 
230         mRateConversionQualitySpinner = (Spinner) findViewById(R.id.spinnerSRCQuality);
231         mRateConversionQualitySpinner.setOnItemSelectedListener(new RateConversionQualitySpinnerListener());
232 
233         mActualPerformanceView = (TextView) findViewById(R.id.actualPerformanceMode);
234         mPerformanceSpinner = (Spinner) findViewById(R.id.spinnerPerformanceMode);
235         mPerformanceSpinner.setOnItemSelectedListener(new PerformanceModeSpinnerListener());
236         mPerformanceSpinner.setSelection(StreamConfiguration.PERFORMANCE_MODE_LOW_LATENCY
237                 - StreamConfiguration.PERFORMANCE_MODE_NONE);
238 
239         mInputPresetTableRow = (TableRow) findViewById(R.id.rowInputPreset);
240         mActualInputPresetView = (TextView) findViewById(R.id.actualInputPreset);
241         mInputPresetSpinner = (Spinner) findViewById(R.id.spinnerInputPreset);
242         mInputPresetSpinner.setOnItemSelectedListener(new InputPresetSpinnerListener());
243         mInputPresetSpinner.setSelection(2); // TODO need better way to select voice recording default
244 
245         mUsageTableRow = (TableRow) findViewById(R.id.rowUsage);
246         mActualUsageView = (TextView) findViewById(R.id.actualUsage);
247         mUsageSpinner = (Spinner) findViewById(R.id.spinnerUsage);
248         mUsageSpinner.setOnItemSelectedListener(new UsageSpinnerListener());
249         mUsageSpinner.setSelection(0); // TODO need better way to select Media default
250 
251         mStreamInfoView = (TextView) findViewById(R.id.streamInfo);
252 
253         mStreamStatusView = (TextView) findViewById(R.id.statusView);
254 
255         mDeviceSpinner = (AudioDeviceSpinner) findViewById(R.id.devices_spinner);
256         mDeviceSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
257             @Override
258             public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
259                 int id =  ((AudioDeviceListEntry) mDeviceSpinner.getSelectedItem()).getId();
260                 mRequestedConfiguration.setDeviceId(id);
261             }
262 
263             @Override
264             public void onNothingSelected(AdapterView<?> adapterView) {
265                 mRequestedConfiguration.setDeviceId(StreamConfiguration.UNSPECIFIED);
266             }
267         });
268 
269         showSettingsView();
270     }
271 
setOutput(boolean output)272     public void setOutput(boolean output) {
273         String ioText;
274         if (output) {
275             mDeviceSpinner.setDirectionType(AudioManager.GET_DEVICES_OUTPUTS);
276             ioText = "OUTPUT";
277         } else {
278             mDeviceSpinner.setDirectionType(AudioManager.GET_DEVICES_INPUTS);
279             ioText = "INPUT";
280         }
281         mHideSettingsText = getResources().getString(R.string.hint_hide_settings) + " - " + ioText;
282         mShowSettingsText = getResources().getString(R.string.hint_show_settings) + " - " + ioText;
283         updateSettingsViewText();
284 
285         // Don't show InputPresets for output streams.
286         mInputPresetTableRow.setVisibility(output ? View.GONE : View.VISIBLE);
287         // Don't show Usage for input streams.
288         mUsageTableRow.setVisibility(output ? View.VISIBLE : View.GONE);
289     }
290 
291     private class NativeApiSpinnerListener implements android.widget.AdapterView.OnItemSelectedListener {
292         @Override
onItemSelected(AdapterView<?> parent, View view, int pos, long id)293         public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
294             mRequestedConfiguration.setNativeApi(pos);
295         }
296 
297         @Override
onNothingSelected(AdapterView<?> parent)298         public void onNothingSelected(AdapterView<?> parent) {
299             mRequestedConfiguration.setNativeApi(StreamConfiguration.NATIVE_API_UNSPECIFIED);
300         }
301     }
302 
303     private class PerformanceModeSpinnerListener implements android.widget.AdapterView.OnItemSelectedListener {
304         @Override
onItemSelected(AdapterView<?> parent, View view, int performanceMode, long id)305         public void onItemSelected(AdapterView<?> parent, View view, int performanceMode, long id) {
306             mRequestedConfiguration.setPerformanceMode(performanceMode
307                     + StreamConfiguration.PERFORMANCE_MODE_NONE);
308         }
309 
310         @Override
onNothingSelected(AdapterView<?> parent)311         public void onNothingSelected(AdapterView<?> parent) {
312             mRequestedConfiguration.setPerformanceMode(StreamConfiguration.PERFORMANCE_MODE_NONE);
313         }
314     }
315 
316     private class ChannelCountSpinnerListener implements android.widget.AdapterView.OnItemSelectedListener {
317         @Override
onItemSelected(AdapterView<?> parent, View view, int pos, long id)318         public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
319             mRequestedConfiguration.setChannelCount(pos);
320         }
321 
322         @Override
onNothingSelected(AdapterView<?> parent)323         public void onNothingSelected(AdapterView<?> parent) {
324             mRequestedConfiguration.setChannelCount(StreamConfiguration.UNSPECIFIED);
325         }
326     }
327 
328     private class SampleRateSpinnerListener implements android.widget.AdapterView.OnItemSelectedListener {
329         @Override
onItemSelected(AdapterView<?> parent, View view, int pos, long id)330         public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
331             String text = parent.getItemAtPosition(pos).toString();
332             int sampleRate = Integer.parseInt(text);
333             mRequestedConfiguration.setSampleRate(sampleRate);
334         }
335 
336         @Override
onNothingSelected(AdapterView<?> parent)337         public void onNothingSelected(AdapterView<?> parent) {
338             mRequestedConfiguration.setSampleRate(StreamConfiguration.UNSPECIFIED);
339         }
340     }
341 
342     private class FormatSpinnerListener implements android.widget.AdapterView.OnItemSelectedListener {
343         @Override
onItemSelected(AdapterView<?> parent, View view, int pos, long id)344         public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
345             // Menu position matches actual enum value!
346             mRequestedConfiguration.setFormat(pos);
347         }
348 
349         @Override
onNothingSelected(AdapterView<?> parent)350         public void onNothingSelected(AdapterView<?> parent) {
351             mRequestedConfiguration.setFormat(StreamConfiguration.UNSPECIFIED);
352         }
353     }
354 
355     private class InputPresetSpinnerListener implements android.widget.AdapterView.OnItemSelectedListener {
356         @Override
onItemSelected(AdapterView<?> parent, View view, int pos, long id)357         public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
358             String text = parent.getItemAtPosition(pos).toString();
359             int inputPreset = StreamConfiguration.convertTextToInputPreset(text);
360             mRequestedConfiguration.setInputPreset(inputPreset);
361         }
362 
363         @Override
onNothingSelected(AdapterView<?> parent)364         public void onNothingSelected(AdapterView<?> parent) {
365             mRequestedConfiguration.setInputPreset(StreamConfiguration.INPUT_PRESET_GENERIC);
366         }
367     }
368 
369     private class UsageSpinnerListener implements android.widget.AdapterView.OnItemSelectedListener {
370         @Override
onItemSelected(AdapterView<?> parent, View view, int pos, long id)371         public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
372             String text = parent.getItemAtPosition(pos).toString();
373             int usage = StreamConfiguration.convertTextToUsage(text);
374             mRequestedConfiguration.setUsage(usage);
375         }
376 
377         @Override
onNothingSelected(AdapterView<?> parent)378         public void onNothingSelected(AdapterView<?> parent) {
379             mRequestedConfiguration.setUsage(StreamConfiguration.INPUT_PRESET_GENERIC);
380         }
381     }
382 
383     private class RateConversionQualitySpinnerListener
384             implements android.widget.AdapterView.OnItemSelectedListener {
385         @Override
onItemSelected(AdapterView<?> parent, View view, int pos, long id)386         public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
387             // Menu position matches actual enum value!
388             mRequestedConfiguration.setRateConversionQuality(pos);
389         }
390 
391         @Override
onNothingSelected(AdapterView<?> parent)392         public void onNothingSelected(AdapterView<?> parent) {
393             mRequestedConfiguration.setRateConversionQuality(StreamConfiguration.RATE_CONVERSION_QUALITY_HIGH);
394         }
395     }
396 
setChildrenEnabled(boolean enabled)397     public void setChildrenEnabled(boolean enabled) {
398         mNativeApiSpinner.setEnabled(enabled);
399         mRequestedMMapView.setEnabled(enabled);
400         mPerformanceSpinner.setEnabled(enabled);
401         mRequestedExclusiveView.setEnabled(enabled);
402         mChannelConversionBox.setEnabled(enabled);
403         mFormatConversionBox.setEnabled(enabled);
404         mChannelCountSpinner.setEnabled(enabled);
405         mInputPresetSpinner.setEnabled(enabled);
406         mUsageSpinner.setEnabled(enabled);
407         mFormatSpinner.setEnabled(enabled);
408         mSampleRateSpinner.setEnabled(enabled);
409         mRateConversionQualitySpinner.setEnabled(enabled);
410         mDeviceSpinner.setEnabled(enabled);
411         mRequestAudioEffect.setEnabled(enabled);
412     }
413 
414     // This must be called on the UI thread.
updateDisplay()415     void updateDisplay() {
416         int value;
417 
418         value = mActualConfiguration.getNativeApi();
419         mActualNativeApiView.setText(StreamConfiguration.convertNativeApiToText(value));
420 
421         mActualMMapView.setText(yesOrNo(mActualConfiguration.isMMap()));
422         int sharingMode = mActualConfiguration.getSharingMode();
423         boolean isExclusive = (sharingMode == StreamConfiguration.SHARING_MODE_EXCLUSIVE);
424         mActualExclusiveView.setText(yesOrNo(isExclusive));
425 
426         value = mActualConfiguration.getPerformanceMode();
427         mActualPerformanceView.setText(StreamConfiguration.convertPerformanceModeToText(value));
428         mActualPerformanceView.requestLayout();
429 
430         value = mActualConfiguration.getFormat();
431         mActualFormatView.setText(StreamConfiguration.convertFormatToText(value));
432         mActualFormatView.requestLayout();
433 
434         value = mActualConfiguration.getInputPreset();
435         mActualInputPresetView.setText(StreamConfiguration.convertInputPresetToText(value));
436         mActualInputPresetView.requestLayout();
437 
438         value = mActualConfiguration.getUsage();
439         mActualUsageView.setText(StreamConfiguration.convertUsageToText(value));
440         mActualUsageView.requestLayout();
441 
442         mActualChannelCountView.setText(mActualConfiguration.getChannelCount() + "");
443         mActualSampleRateView.setText(mActualConfiguration.getSampleRate() + "");
444         mActualSessionIdView.setText("S#: " + mActualConfiguration.getSessionId());
445 
446         boolean isMMap = mActualConfiguration.isMMap();
447         mStreamInfoView.setText("burst = " + mActualConfiguration.getFramesPerBurst()
448                 + ", capacity = " + mActualConfiguration.getBufferCapacityInFrames()
449                 + ", devID = " + mActualConfiguration.getDeviceId()
450                 + ", " + (mActualConfiguration.isMMap() ? "MMAP" : "Legacy")
451                 + (isMMap ? ", " + StreamConfiguration.convertSharingModeToText(sharingMode) : "")
452         );
453 
454         mHideableView.requestLayout();
455     }
456 
457     // This must be called on the UI thread.
setStatusText(String msg)458     public void setStatusText(String msg) {
459         mStreamStatusView.setText(msg);
460     }
461 
getRequestedConfiguration()462     protected StreamConfiguration getRequestedConfiguration() {
463         return mRequestedConfiguration;
464     }
465 
setRequestedConfiguration(StreamConfiguration configuration)466     public void setRequestedConfiguration(StreamConfiguration configuration) {
467         mRequestedConfiguration = configuration;
468         if (configuration != null) {
469             mRateConversionQualitySpinner.setSelection(configuration.getRateConversionQuality());
470             mChannelConversionBox.setChecked(configuration.getChannelConversionAllowed());
471             mFormatConversionBox.setChecked(configuration.getFormatConversionAllowed());
472         }
473     }
474 
getActualConfiguration()475     protected StreamConfiguration getActualConfiguration() {
476         return mActualConfiguration;
477     }
setActualConfiguration(StreamConfiguration configuration)478     public void setActualConfiguration(StreamConfiguration configuration) {
479         mActualConfiguration = configuration;
480     }
481 
setExclusiveMode(boolean b)482     public void setExclusiveMode(boolean b) {
483         mRequestedExclusiveView.setChecked(b);
484         mRequestedConfiguration.setSharingMode(b
485                 ? StreamConfiguration.SHARING_MODE_EXCLUSIVE
486                 : StreamConfiguration.SHARING_MODE_SHARED);
487     }
488 
setFormat(int format)489     public void setFormat(int format) {
490         mFormatSpinner.setSelection(format); // position matches format
491         mRequestedConfiguration.setFormat(format);
492     }
493 
setFormatConversionAllowed(boolean allowed)494     public void setFormatConversionAllowed(boolean allowed) {
495         mFormatConversionBox.setChecked(allowed);
496         mRequestedConfiguration.setFormatConversionAllowed(allowed);
497     }
498 }
499