• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2019 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.google.sample.oboe.manualtest;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.os.Build;
24 import android.os.Bundle;
25 import android.text.method.ScrollingMovementMethod;
26 import android.view.View;
27 import android.widget.Button;
28 import android.widget.TextView;
29 
30 import java.io.IOException;
31 import java.util.Date;
32 
33 /**
34  * Guide the user through a series of tests plugging in and unplugging a headset.
35  * Print a summary at the end of any failures.
36  *
37  * TODO Test Input
38  */
39 public class TestDisconnectActivity extends TestAudioActivity implements Runnable {
40 
41     private static final String TEXT_SKIP = "SKIP";
42     private static final String TEXT_PASS = "PASS";
43     private static final String TEXT_FAIL = "FAIL !!!!";
44     public static final int POLL_DURATION_MILLIS = 50;
45     public static final int SETTLING_TIME_MILLIS = 600;
46     public static final int TIME_TO_FAILURE_MILLIS = 3000;
47 
48     private TextView     mInstructionsTextView;
49     private TextView     mAutoTextView;
50     private TextView     mStatusTextView;
51     private TextView     mPlugTextView;
52 
53     private Thread       mAutoThread;
54     private volatile boolean mThreadEnabled;
55     private volatile boolean mTestFailed;
56     private volatile boolean mSkipTest;
57     private volatile int mPlugCount;
58     private int          mTestCount;
59     private StringBuffer mFailedSummary;
60     private int          mPassCount;
61     private int          mFailCount;
62     private BroadcastReceiver mPluginReceiver = new PluginBroadcastReceiver();
63     private Button       mStartButton;
64     private Button       mStopButton;
65     private Button       mShareButton;
66     private Button       mFailButton;
67     private Button       mSkipButton;
68 
69     // Receive a broadcast Intent when a headset is plugged in or unplugged.
70     // Display a count on screen.
71     public class PluginBroadcastReceiver extends BroadcastReceiver {
72         @Override
onReceive(Context context, Intent intent)73         public void onReceive(Context context, Intent intent) {
74             mPlugCount++;
75             runOnUiThread(new Runnable() {
76                 @Override
77                 public void run() {
78                     String message = "Intent.HEADSET_PLUG #" + mPlugCount;
79                     mPlugTextView.setText(message);
80                 }
81             });
82         }
83     }
84 
85     @Override
inflateActivity()86     protected void inflateActivity() {
87         setContentView(R.layout.activity_test_disconnect);
88     }
89 
90     @Override
onCreate(Bundle savedInstanceState)91     protected void onCreate(Bundle savedInstanceState) {
92         super.onCreate(savedInstanceState);
93 
94         mInstructionsTextView = (TextView) findViewById(R.id.text_instructions);
95         mStatusTextView = (TextView) findViewById(R.id.text_status);
96         mPlugTextView = (TextView) findViewById(R.id.text_plug_events);
97         mAutoTextView = (TextView) findViewById(R.id.text_log);
98         mAutoTextView.setMovementMethod(new ScrollingMovementMethod());
99 
100         mStartButton = (Button) findViewById(R.id.button_start);
101         mStopButton = (Button) findViewById(R.id.button_stop);
102         mShareButton = (Button) findViewById(R.id.button_share);
103         mShareButton.setEnabled(false);
104         mFailButton = (Button) findViewById(R.id.button_fail);
105         mSkipButton = (Button) findViewById(R.id.button_skip);
106         updateStartStopButtons(false);
107         updateFailSkipButton(false);
108     }
109 
updateStartStopButtons(boolean running)110     private void updateStartStopButtons(boolean running) {
111         mStartButton.setEnabled(!running);
112         mStopButton.setEnabled(running);
113     }
114 
115     @Override
onStart()116     protected void onStart() {
117         super.onStart();
118         setActivityType(ACTIVITY_TEST_DISCONNECT);
119     }
120 
121     @Override
isOutput()122     boolean isOutput() {
123         return true;
124     }
125 
126     @Override
setupEffects(int sessionId)127     public void setupEffects(int sessionId) {
128     }
129 
updateFailSkipButton(final boolean running)130     private void updateFailSkipButton(final boolean running) {
131         runOnUiThread(new Runnable() {
132             @Override
133             public void run() {
134                 mFailButton.setEnabled(running);
135                 mSkipButton.setEnabled(running);
136             }
137         });
138     }
139 
140     // Write to scrollable TextView
log(final String text)141     private void log(final String text) {
142         runOnUiThread(new Runnable() {
143             @Override
144             public void run() {
145                 mAutoTextView.append(text);
146                 mAutoTextView.append("\n");
147             }
148         });
149     }
150 
151     // Write to status and command view
setInstructionsText(final String text)152     private void setInstructionsText(final String text) {
153         runOnUiThread(new Runnable() {
154             @Override
155             public void run() {
156                 mInstructionsTextView.setText(text);
157             }
158         });
159     }
160 
161     // Write to status and command view
setStatusText(final String text)162     private void setStatusText(final String text) {
163         runOnUiThread(new Runnable() {
164             @Override
165             public void run() {
166                 mStatusTextView.setText(text);
167             }
168         });
169     }
170 
logClear()171     private void logClear() {
172         runOnUiThread(new Runnable() {
173             @Override
174             public void run() {
175                 mAutoTextView.setText("");
176             }
177         });
178     }
179 
180     @Override
onResume()181     public void onResume() {
182         super.onResume();
183         IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
184         this.registerReceiver(mPluginReceiver, filter);
185     }
186 
187     @Override
onPause()188     public void onPause() {
189         this.unregisterReceiver(mPluginReceiver);
190         super.onPause();
191     }
192 
193     // Only call from UI thread.
onTestFinished()194     public void onTestFinished() {
195         updateStartStopButtons(false);
196         mShareButton.setEnabled(true);
197     }
198 
startAudioTest()199     public void startAudioTest() throws IOException {
200         openAudio();
201         startAudio();
202     }
203 
stopAudioTest()204     public void stopAudioTest() {
205         stopAudioQuiet();
206         closeAudio();
207     }
208 
onCancel(View view)209     public void onCancel(View view) {
210         stopAudioTest();
211         onTestFinished();
212     }
213 
214     // Called on UI thread
onStopAudioTest(View view)215     public void onStopAudioTest(View view) {
216         stopAudioTest();
217         onTestFinished();
218         keepScreenOn(false);
219     }
220 
onStartDisconnectTest(View view)221     public void onStartDisconnectTest(View view) {
222         updateStartStopButtons(true);
223         mThreadEnabled = true;
224         mAutoThread = new Thread(this);
225         mAutoThread.start();
226     }
227 
onStopDisconnectTest(View view)228     public void onStopDisconnectTest(View view) {
229         try {
230             if (mAutoThread != null) {
231                 mThreadEnabled = false;
232                 mAutoThread.interrupt();
233                 mAutoThread.join(100);
234                 mAutoThread = null;
235             }
236         } catch (InterruptedException e) {
237             e.printStackTrace();
238         }
239     }
240 
onFailTest(View view)241     public void onFailTest(View view) {
242         mTestFailed = true;
243     }
244 
onSkipTest(View view)245     public void onSkipTest(View view) {
246         mSkipTest = true;
247     }
248 
249     // Share text from log via GMail, Drive or other method.
onShareResult(View view)250     public void onShareResult(View view) {
251         Intent sharingIntent = new Intent(Intent.ACTION_SEND);
252         sharingIntent.setType("text/plain");
253 
254         String subjectText = "OboeTester Test Disconnect result " + getTimestampString();
255         sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subjectText);
256 
257         String shareBody = mAutoTextView.getText().toString();
258         sharingIntent.putExtra(Intent.EXTRA_TEXT, shareBody);
259 
260         startActivity(Intent.createChooser(sharingIntent, "Share using:"));
261     }
262 
getConfigText(StreamConfiguration config)263     private String getConfigText(StreamConfiguration config) {
264         return ((config.getDirection() == StreamConfiguration.DIRECTION_OUTPUT) ? "OUT" : "IN")
265                 + ", Perf = " + StreamConfiguration.convertPerformanceModeToText(
266                 config.getPerformanceMode())
267                 + ", " + StreamConfiguration.convertSharingModeToText(config.getSharingMode());
268     }
269 
testConfiguration(boolean isInput, int perfMode, int sharingMode, int channelCount, boolean requestPlugin)270     private void testConfiguration(boolean isInput,
271                                    int perfMode,
272                                    int sharingMode,
273                                    int channelCount,
274                                    boolean requestPlugin) throws InterruptedException {
275         String actualConfigText = "none";
276         mSkipTest = false;
277 
278         AudioInputTester    mAudioInTester = null;
279         AudioOutputTester   mAudioOutTester = null;
280 
281         clearStreamContexts();
282 
283         if (isInput) {
284             mAudioInTester = addAudioInputTester();
285         } else {
286             mAudioOutTester = addAudioOutputTester();
287         }
288 
289         // Configure settings
290         StreamConfiguration requestedConfig = (isInput)
291                 ? mAudioInTester.requestedConfiguration
292                 : mAudioOutTester.requestedConfiguration;
293         StreamConfiguration actualConfig = (isInput)
294                 ? mAudioInTester.actualConfiguration
295                 : mAudioOutTester.actualConfiguration;
296 
297         requestedConfig.reset();
298         requestedConfig.setPerformanceMode(perfMode);
299         requestedConfig.setSharingMode(sharingMode);
300         requestedConfig.setChannelCount(channelCount);
301 
302         log("========================== #" + mTestCount);
303         log("Requested:");
304         log(getConfigText(requestedConfig));
305 
306         // Give previous stream time to close and release resources. Avoid race conditions.
307         Thread.sleep(SETTLING_TIME_MILLIS);
308         if (!mThreadEnabled) return;
309         boolean openFailed = false;
310         AudioStreamBase stream = null;
311         try {
312             startAudioTest(); // this will fill in actualConfig
313             log("Actual:");
314             actualConfigText = getConfigText(actualConfig)
315                     + ", " + (actualConfig.isMMap() ? "MMAP" : "Legacy");
316             log(actualConfigText);
317 
318             stream = (isInput)
319                     ? mAudioInTester.getCurrentAudioStream()
320                     : mAudioOutTester.getCurrentAudioStream();
321         } catch (IOException e) {
322             openFailed = true;
323             log(e.getMessage());
324         }
325 
326         // The test is only worth running if we got the configuration we requested.
327         boolean valid = true;
328         if (!openFailed) {
329             if(actualConfig.getSharingMode() != sharingMode) {
330                 log("did not get requested sharing mode");
331                 valid = false;
332             }
333             if (actualConfig.getPerformanceMode() != perfMode) {
334                 log("did not get requested performance mode");
335                 valid = false;
336             }
337             if (actualConfig.getNativeApi() == StreamConfiguration.NATIVE_API_OPENSLES) {
338                 log("OpenSL ES does not support automatic disconnect");
339                 valid = false;
340             }
341         }
342 
343         int oldPlugCount = mPlugCount;
344         if (!openFailed && valid) {
345             mTestFailed = false;
346             updateFailSkipButton(true);
347             // poll for stream disconnected
348             while (!mTestFailed && mThreadEnabled && !mSkipTest &&
349                     stream.getState() == StreamConfiguration.STREAM_STATE_STARTING) {
350                 Thread.sleep(POLL_DURATION_MILLIS);
351             }
352             String message = (requestPlugin ? "Plug IN" : "UNplug") + " headset now!";
353             setStatusText("Testing:\n" + actualConfigText);
354             setInstructionsText(message);
355             int timeoutCount = 0;
356             // Wait for Java plug count to change or stream to disconnect.
357             while (!mTestFailed && mThreadEnabled && !mSkipTest &&
358                     stream.getState() == StreamConfiguration.STREAM_STATE_STARTED) {
359                 Thread.sleep(POLL_DURATION_MILLIS);
360                 if (mPlugCount > oldPlugCount) {
361                     timeoutCount = TIME_TO_FAILURE_MILLIS / POLL_DURATION_MILLIS;
362                     break;
363                 }
364             }
365             // Wait for timeout or stream to disconnect.
366             while (!mTestFailed && mThreadEnabled && !mSkipTest && (timeoutCount > 0) &&
367                     stream.getState() == StreamConfiguration.STREAM_STATE_STARTED) {
368                 Thread.sleep(POLL_DURATION_MILLIS);
369                 timeoutCount--;
370                 if (timeoutCount == 0) {
371                     mTestFailed = true;
372                 } else {
373                     setStatusText("Plug detected by Java.\nCounting down to Oboe failure: " + timeoutCount);
374                 }
375             }
376             setStatusText(mTestFailed ? "Failed" : "Passed - detected");
377         }
378         updateFailSkipButton(false);
379         setInstructionsText("Wait...");
380 
381         if (!openFailed) {
382             stopAudioTest();
383         }
384 
385         if (mSkipTest) valid = false;
386 
387         if (valid) {
388             if (openFailed) {
389                 mFailedSummary.append("------ #" + mTestCount);
390                 mFailedSummary.append("\n");
391                 mFailedSummary.append(getConfigText(requestedConfig));
392                 mFailedSummary.append("\n");
393                 mFailedSummary.append("Open failed!\n");
394                 mFailCount++;
395             } else {
396                 log("Result:");
397                 boolean passed = !mTestFailed;
398                 String resultText = requestPlugin ? "plugIN" : "UNplug";
399                 resultText += ", " + (passed ? TEXT_PASS : TEXT_FAIL);
400                 log(resultText);
401                 if (!passed) {
402                     mFailedSummary.append("------ #" + mTestCount);
403                     mFailedSummary.append("\n");
404                     mFailedSummary.append("  ");
405                     mFailedSummary.append(actualConfigText);
406                     mFailedSummary.append("\n");
407                     mFailedSummary.append("    ");
408                     mFailedSummary.append(resultText);
409                     mFailedSummary.append("\n");
410                     mFailCount++;
411                 } else {
412                     mPassCount++;
413                 }
414             }
415         } else {
416             log(TEXT_SKIP);
417         }
418         // Give hardware time to settle between tests.
419         Thread.sleep(1000);
420         mTestCount++;
421     }
422 
testConfiguration(boolean isInput, int performanceMode, int sharingMode)423     private void testConfiguration(boolean isInput, int performanceMode,
424                                    int sharingMode) throws InterruptedException {
425         int channelCount = 2;
426         boolean requestPlugin = true; // plug IN
427         testConfiguration(isInput, performanceMode, sharingMode, channelCount, requestPlugin);
428         requestPlugin = false; // UNplug
429         testConfiguration(isInput, performanceMode, sharingMode, channelCount, requestPlugin);
430     }
431 
testConfiguration(int performanceMode, int sharingMode)432     private void testConfiguration(int performanceMode,
433                                    int sharingMode) throws InterruptedException {
434         testConfiguration(false, performanceMode, sharingMode);
435         testConfiguration(true, performanceMode, sharingMode);
436     }
437 
438     @Override
run()439     public void run() {
440         mPlugCount = 0;
441         logClear();
442         log("=== STARTED at " + new Date());
443         log(Build.MANUFACTURER + " " + Build.PRODUCT);
444         log(Build.DISPLAY);
445         mFailedSummary = new StringBuffer();
446         mTestCount = 0;
447         mPassCount = 0;
448         mFailCount = 0;
449         // Try several different configurations.
450         try {
451             testConfiguration(StreamConfiguration.PERFORMANCE_MODE_LOW_LATENCY,
452                         StreamConfiguration.SHARING_MODE_EXCLUSIVE);
453             testConfiguration(StreamConfiguration.PERFORMANCE_MODE_LOW_LATENCY,
454                         StreamConfiguration.SHARING_MODE_SHARED);
455             testConfiguration(StreamConfiguration.PERFORMANCE_MODE_NONE,
456                         StreamConfiguration.SHARING_MODE_SHARED);
457         } catch (InterruptedException e) {
458             e.printStackTrace();
459         } finally {
460             stopAudioTest();
461             setInstructionsText("See summary below.");
462             setStatusText("Finished.");
463             log("\n==== SUMMARY ========");
464             if (mFailCount > 0) {
465                 log(mPassCount + " passed. " + mFailCount + " failed.");
466                 log("These tests FAILED:");
467                 log(mFailedSummary.toString());
468             } else {
469                 log("All tests PASSED.");
470             }
471             log("== FINISHED at " + new Date());
472             runOnUiThread(new Runnable() {
473                 @Override
474                 public void run() {
475                     onTestFinished();
476                 }
477             });
478             updateFailSkipButton(false);
479         }
480     }
481 
482 }
483