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