Fix settings crash from recording audio with a bad microphone configuration.

Test: Manually using qt-tv-dev, on Deadpool and Balto

Bug: b/143857201

Change-Id: I5167b63b803d375ca57a9b5b56be27161edcbc0c
diff --git a/Settings/res/values/strings.xml b/Settings/res/values/strings.xml
index 08077de..848d2ae 100644
--- a/Settings/res/values/strings.xml
+++ b/Settings/res/values/strings.xml
@@ -1683,4 +1683,10 @@
 
     <!-- Title of the preference displaying the duration of empty audio received. [CHAR LIMIT=50] -->
     <string name="empty_audio_duration_title">Duration of empty audio</string>
+
+    <!-- Notification that audio recording failed to start. [CHAR LIMIT=100] -->
+    <string name="show_audio_recording_start_failed">Failed to start recording audio.</string>
+
+    <!-- Notification that audio recording failed. [CHAR LIMIT=100] -->
+    <string name="show_audio_recording_failed"> Audio recording failed.</string>
 </resources>
diff --git a/Settings/src/com/android/tv/settings/system/development/DevelopmentFragment.java b/Settings/src/com/android/tv/settings/system/development/DevelopmentFragment.java
index 7d38b4b..cd360d4 100644
--- a/Settings/src/com/android/tv/settings/system/development/DevelopmentFragment.java
+++ b/Settings/src/com/android/tv/settings/system/development/DevelopmentFragment.java
@@ -72,6 +72,7 @@
 import com.android.tv.settings.SettingsPreferenceFragment;
 import com.android.tv.settings.system.development.audio.AudioDebug;
 import com.android.tv.settings.system.development.audio.AudioMetrics;
+import com.android.tv.settings.system.development.audio.AudioReaderException;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -286,7 +287,7 @@
         mContentResolver = getActivity().getContentResolver();
 
         mAudioDebug = new AudioDebug(getActivity(),
-                () -> onAudioTrackRecorded(),
+                (boolean successful) -> onAudioRecorded(successful),
                 (AudioMetrics.Data data) -> updateAudioRecordingMetrics(data));
 
         super.onCreate(icicle);
@@ -1230,18 +1231,31 @@
 
     private void writeRecordAudioOptions() {
         if (mRecordAudio.isChecked()) {
-            mAudioDebug.startRecording();
+            try {
+                mAudioDebug.startRecording();
+            } catch (AudioReaderException e) {
+                mRecordAudio.setChecked(false);
+                Toast errorToast = Toast.makeText(getContext(),
+                        getString(R.string.show_audio_recording_start_failed), Toast.LENGTH_SHORT);
+                errorToast.show();
+                Log.e(TAG, "Unable to start recording audio from the microphone", e);
+            }
         } else {
             mAudioDebug.stopRecording();
         }
     }
 
-    /** Called when an audio track has been recorded. Updates UI component states. */
-    private void onAudioTrackRecorded() {
-        mPlayRecordedAudio.setVisible(true);
-        mSaveAudio.setVisible(true);
-
+    /** Called when audio recording is finished. Updates UI component states. */
+    private void onAudioRecorded(boolean successful) {
+        mPlayRecordedAudio.setVisible(successful);
+        mSaveAudio.setVisible(successful);
         mRecordAudio.setChecked(false);
+
+        if (!successful) {
+            Toast errorToast = Toast.makeText(getContext(),
+                    getString(R.string.show_audio_recording_failed), Toast.LENGTH_SHORT);
+            errorToast.show();
+        }
     }
 
     /** Updates displayed audio recording metrics */
diff --git a/Settings/src/com/android/tv/settings/system/development/audio/AudioDebug.java b/Settings/src/com/android/tv/settings/system/development/audio/AudioDebug.java
index 56a53b4..f2100fe 100644
--- a/Settings/src/com/android/tv/settings/system/development/audio/AudioDebug.java
+++ b/Settings/src/com/android/tv/settings/system/development/audio/AudioDebug.java
@@ -50,10 +50,10 @@
     @Nullable
     private AudioTrack mAudioTrack;
 
-    /** Interface for receiving a notification for each successful audio recording. */
+    /** Interface for receiving a notification when audio recording finishes. */
     public interface AudioRecordedCallback {
-        /** Callback for receiving a notification for each successful audio recording. */
-        void onAudioRecorded();
+        /** Callback for receiving a notification when audio recording finishes. */
+        void onAudioRecorded(boolean successful);
     }
 
     /**
@@ -70,7 +70,7 @@
     }
 
     /** Starts recording audio. */
-    public void startRecording() {
+    public void startRecording() throws AudioReaderException {
         if (mAudioReader != null) {
             mAudioReader.stop();
         }
@@ -102,18 +102,26 @@
         int numShorts = audioBuffer.position();
         int numBytes = numShorts * 2;
 
-        mAudioTrack =
-                new AudioTrack.Builder()
-                        .setAudioFormat(
-                                new AudioFormat.Builder()
-                                        .setSampleRate(SAMPLE_RATE)
-                                        .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
-                                        .setEncoding(ENCODING)
-                                        .build()
-                        )
-                        .setTransferMode(AudioTrack.MODE_STATIC)
-                        .setBufferSizeInBytes(numBytes)
-                        .build();
+        Handler mainHandler = new Handler(mContext.getMainLooper());
+
+        try {
+            mAudioTrack =
+                    new AudioTrack.Builder()
+                            .setAudioFormat(
+                                    new AudioFormat.Builder()
+                                            .setSampleRate(SAMPLE_RATE)
+                                            .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
+                                            .setEncoding(ENCODING)
+                                            .build()
+                            )
+                            .setTransferMode(AudioTrack.MODE_STATIC)
+                            .setBufferSizeInBytes(numBytes)
+                            .build();
+        } catch (UnsupportedOperationException | IllegalArgumentException e) {
+            Log.e(TAG, "Failed to create AudioTrack", e);
+            mainHandler.post(() -> mAudioRecordedCallback.onAudioRecorded(false));
+            return;
+        }
 
         Log.i(TAG, String.format("AudioTrack state: %d", mAudioTrack.getState()));
 
@@ -121,13 +129,12 @@
                 AudioTrack.WRITE_BLOCKING);
         if (writeStatus > 0) {
             Log.i(TAG, String.format("Wrote %d bytes to an AudioTrack", numBytes));
-
-            Handler mainHandler = new Handler(mContext.getMainLooper());
-            mainHandler.post(() -> mAudioRecordedCallback.onAudioRecorded());
+            mainHandler.post(() -> mAudioRecordedCallback.onAudioRecorded(true));
         } else if (writeStatus == 0) {
             Log.e(TAG, "Received empty audio buffer");
         } else {
-            Log.e(TAG, String.format("Error writing to AudioTrack: %d", writeStatus));
+            Log.e(TAG, String.format("Error calling AudioTrack.write(): %d", writeStatus));
+            mainHandler.post(() -> mAudioRecordedCallback.onAudioRecorded(false));
         }
     }
 
diff --git a/Settings/src/com/android/tv/settings/system/development/audio/AudioMetrics.java b/Settings/src/com/android/tv/settings/system/development/audio/AudioMetrics.java
index cc5710f..b652f60 100644
--- a/Settings/src/com/android/tv/settings/system/development/audio/AudioMetrics.java
+++ b/Settings/src/com/android/tv/settings/system/development/audio/AudioMetrics.java
@@ -33,7 +33,7 @@
 
     private final UpdateMetricsCallback mCallback;
 
-    /** Contains mData to be exposed via the mCallback. */
+    /** Contains data to be exposed via the callback. */
     public static class Data {
 
         public Optional<Long> timeToStartReadMs = Optional.empty();
diff --git a/Settings/src/com/android/tv/settings/system/development/audio/AudioReader.java b/Settings/src/com/android/tv/settings/system/development/audio/AudioReader.java
index f7b2fc8..1f47b81 100644
--- a/Settings/src/com/android/tv/settings/system/development/audio/AudioReader.java
+++ b/Settings/src/com/android/tv/settings/system/development/audio/AudioReader.java
@@ -21,7 +21,6 @@
 import android.media.MediaRecorder;
 import android.util.Log;
 
-//import java.nio.ByteBuffer;
 import java.nio.ShortBuffer;
 import java.util.HashSet;
 import java.util.Set;
@@ -53,23 +52,26 @@
     /**
      * @param metrics Object for storing metrics.
      */
-    public AudioReader(AudioMetrics metrics) {
+    public AudioReader(AudioMetrics metrics) throws AudioReaderException {
         this.mMetrics = metrics;
 
         mMinBufferSize =
                 AudioRecord.getMinBufferSize(AudioDebug.SAMPLE_RATE, AudioFormat.CHANNEL_IN_DEFAULT,
                         AudioDebug.ENCODING);
-
-        mAudioRecord =
-                new AudioRecord.Builder()
-                        .setAudioFormat(
-                                new AudioFormat.Builder()
-                                        .setSampleRate(AudioDebug.SAMPLE_RATE)
-                                        .setEncoding(AudioDebug.ENCODING)
-                                        .build())
-                        .setAudioSource(MediaRecorder.AudioSource.VOICE_RECOGNITION)
-                        .setBufferSizeInBytes(2 * mMinBufferSize)
-                        .build();
+        try {
+            mAudioRecord =
+                    new AudioRecord.Builder()
+                            .setAudioFormat(
+                                    new AudioFormat.Builder()
+                                            .setSampleRate(AudioDebug.SAMPLE_RATE)
+                                            .setEncoding(AudioDebug.ENCODING)
+                                            .build())
+                            .setAudioSource(MediaRecorder.AudioSource.VOICE_RECOGNITION)
+                            .setBufferSizeInBytes(2 * mMinBufferSize)
+                            .build();
+        } catch (UnsupportedOperationException | IllegalArgumentException e) {
+            throw new AudioReaderException(e);
+        }
 
         Log.i(TAG, String.format("Constructed AudioRecord with buffer size %d", BUFFER_SIZE));
 
diff --git a/Settings/src/com/android/tv/settings/system/development/audio/AudioReaderException.java b/Settings/src/com/android/tv/settings/system/development/audio/AudioReaderException.java
new file mode 100644
index 0000000..18e4af4
--- /dev/null
+++ b/Settings/src/com/android/tv/settings/system/development/audio/AudioReaderException.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.settings.system.development.audio;
+
+/** Represents an error in an audio recording thread. */
+public class AudioReaderException extends Exception {
+    public AudioReaderException(Throwable cause) {
+        super(cause);
+    }
+}