Fixing TTS Settings crash on rotation caused by using deprecated API.
Test: manual - rotated phone while playing TTS
Fix: 145579714
Change-Id: I7232704c92ba8ec34769cf68afe2278e22532d86
diff --git a/src/com/android/settings/tts/TextToSpeechSettings.java b/src/com/android/settings/tts/TextToSpeechSettings.java
index 2bfc6ea..8e671f4 100644
--- a/src/com/android/settings/tts/TextToSpeechSettings.java
+++ b/src/com/android/settings/tts/TextToSpeechSettings.java
@@ -38,6 +38,7 @@
import android.util.Pair;
import androidx.appcompat.app.AlertDialog;
+import androidx.lifecycle.ViewModelProviders;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
@@ -205,13 +206,18 @@
mLocalePreference.setEnabled(entries.length > 0);
}
- mTts = new TextToSpeech(getActivity().getApplicationContext(), mInitListener);
+ final TextToSpeechViewModel ttsViewModel =
+ ViewModelProviders.of(this).get(TextToSpeechViewModel.class);
+ Pair<TextToSpeech, Boolean> ttsAndNew = ttsViewModel.getTtsAndWhetherNew(mInitListener);
+ mTts = ttsAndNew.first;
+ // If the TTS object is not newly created, we need to run the setup on the settings side to
+ // ensure that we can use the TTS object.
+ if (!ttsAndNew.second) {
+ successSetup();
+ }
setTtsUtteranceProgressListener();
initSettings();
-
- // Prevent restarting the TTS connection on rotation
- setRetainInstance(true);
}
@Override
@@ -227,13 +233,21 @@
return;
}
if (!mTts.getDefaultEngine().equals(mTts.getCurrentEngine())) {
+ final TextToSpeechViewModel ttsViewModel =
+ ViewModelProviders.of(this).get(TextToSpeechViewModel.class);
try {
- mTts.shutdown();
- mTts = null;
+ // If the current engine isn't the default engine shut down the current engine in
+ // preparation for creating the new engine.
+ ttsViewModel.shutdownTts();
} catch (Exception e) {
Log.e(TAG, "Error shutting down TTS engine" + e);
}
- mTts = new TextToSpeech(getActivity().getApplicationContext(), mInitListener);
+ final Pair<TextToSpeech, Boolean> ttsAndNew =
+ ttsViewModel.getTtsAndWhetherNew(mInitListener);
+ mTts = ttsAndNew.first;
+ if (!ttsAndNew.second) {
+ successSetup();
+ }
setTtsUtteranceProgressListener();
initSettings();
} else {
@@ -277,15 +291,6 @@
}
@Override
- public void onDestroy() {
- super.onDestroy();
- if (mTts != null) {
- mTts.shutdown();
- mTts = null;
- }
- }
-
- @Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
@@ -375,8 +380,7 @@
public void onInitEngine(int status) {
if (status == TextToSpeech.SUCCESS) {
if (DBG) Log.d(TAG, "TTS engine for settings screen initialized.");
- checkDefaultLocale();
- getActivity().runOnUiThread(() -> mLocalePreference.setEnabled(true));
+ successSetup();
} else {
if (DBG) {
Log.d(TAG,
@@ -386,6 +390,12 @@
}
}
+ /** Initialize TTS Settings on successful engine init. */
+ private void successSetup() {
+ checkDefaultLocale();
+ getActivity().runOnUiThread(() -> mLocalePreference.setEnabled(true));
+ }
+
private void checkDefaultLocale() {
Locale defaultLocale = mTts.getDefaultLanguage();
if (defaultLocale == null) {
diff --git a/src/com/android/settings/tts/TextToSpeechViewModel.java b/src/com/android/settings/tts/TextToSpeechViewModel.java
new file mode 100644
index 0000000..3c92010
--- /dev/null
+++ b/src/com/android/settings/tts/TextToSpeechViewModel.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2020 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.settings.tts;
+
+import android.app.Application;
+import android.speech.tts.TextToSpeech;
+import android.util.Pair;
+
+import androidx.lifecycle.AndroidViewModel;
+
+/**
+ * A helper view model to protect the TTS object from being destroyed and
+ * recreated on orientation change.
+*/
+public class TextToSpeechViewModel extends AndroidViewModel {
+ private TextToSpeech mTts;
+
+ // Save the application so we can use it as the TTS context
+ private final Application mApplication;
+
+ public TextToSpeechViewModel(Application application) {
+ super(application);
+
+ mApplication = application;
+ }
+
+ /*
+ * Since the view model now controls the TTS object, we need to handle shutting it down
+ * ourselves.
+ */
+ @Override
+ protected void onCleared() {
+ shutdownTts();
+ }
+
+ protected void shutdownTts() {
+ mTts.shutdown();
+ mTts = null;
+ }
+
+ /*
+ * An accessor method to get the TTS object. Returns a pair of the TTS object and a boolean
+ * indicating whether the TTS object was newly created or not.
+ */
+ protected Pair<TextToSpeech, Boolean> getTtsAndWhetherNew(
+ TextToSpeech.OnInitListener listener) {
+ boolean ttsCreated = false;
+ if (mTts == null) {
+ mTts = new TextToSpeech(this.mApplication, listener);
+ ttsCreated = true;
+ }
+
+ return Pair.create(mTts, ttsCreated);
+ }
+
+}