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);
+ }
+}