• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.settings.tts;
2 
3 import static android.provider.Settings.Secure.TTS_DEFAULT_SYNTH;
4 
5 import android.app.settings.SettingsEnums;
6 import android.content.Context;
7 import android.content.DialogInterface;
8 import android.graphics.drawable.Drawable;
9 import android.os.Bundle;
10 import android.speech.tts.TextToSpeech;
11 import android.speech.tts.TextToSpeech.EngineInfo;
12 import android.speech.tts.TtsEngines;
13 import android.util.Log;
14 
15 import androidx.appcompat.app.AlertDialog;
16 
17 import com.android.settings.R;
18 import com.android.settings.search.BaseSearchIndexProvider;
19 import com.android.settings.widget.RadioButtonPickerFragment;
20 import com.android.settingslib.search.SearchIndexable;
21 import com.android.settingslib.widget.CandidateInfo;
22 
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 
28 @SearchIndexable
29 public class TtsEnginePreferenceFragment extends RadioButtonPickerFragment {
30     private static final String TAG = "TtsEnginePrefFragment";
31 
32     /**
33      * The previously selected TTS engine. Useful for rollbacks if the users choice is not loaded or
34      * fails a voice integrity check.
35      */
36     private String mPreviousEngine;
37 
38     private TextToSpeech mTts = null;
39     private TtsEngines mEnginesHelper = null;
40     private Context mContext;
41     private Map<String, EngineCandidateInfo> mEngineMap;
42     /**
43      * The initialization listener used when the user changes his choice of engine (as opposed to
44      * when then screen is being initialized for the first time).
45      */
46     private final TextToSpeech.OnInitListener mUpdateListener =
47             new TextToSpeech.OnInitListener() {
48                 @Override
49                 public void onInit(int status) {
50                     onUpdateEngine(status);
51                 }
52             };
53 
54     @Override
onCreate(Bundle savedInstanceState)55     public void onCreate(Bundle savedInstanceState) {
56         mContext = getContext().getApplicationContext();
57         mEnginesHelper = new TtsEngines(mContext);
58         mEngineMap = new HashMap<>();
59         mTts = new TextToSpeech(mContext, null);
60 
61         super.onCreate(savedInstanceState);
62     }
63 
64     @Override
onDestroy()65     public void onDestroy() {
66         super.onDestroy();
67         if (mTts != null) {
68             mTts.shutdown();
69             mTts = null;
70         }
71     }
72 
73     @Override
getMetricsCategory()74     public int getMetricsCategory() {
75         return SettingsEnums.TTS_ENGINE_SETTINGS;
76     }
77 
78     /**
79      * Step 3: We have now bound to the TTS engine the user requested. We will attempt to check
80      * voice data for the engine if we successfully bound to it, or revert to the previous engine if
81      * we didn't.
82      */
onUpdateEngine(int status)83     public void onUpdateEngine(int status) {
84         if (status == TextToSpeech.SUCCESS) {
85             Log.d(TAG, "Updating engine: Successfully bound to the engine: "
86                     + mTts.getCurrentEngine());
87             android.provider.Settings.Secure.putString(
88                     mContext.getContentResolver(), TTS_DEFAULT_SYNTH, mTts.getCurrentEngine());
89         } else {
90             Log.d(TAG, "Updating engine: Failed to bind to engine, reverting.");
91             if (mPreviousEngine != null) {
92                 // This is guaranteed to at least bind, since mPreviousEngine would be
93                 // null if the previous bind to this engine failed.
94                 mTts = new TextToSpeech(mContext, null, mPreviousEngine);
95                 updateCheckedState(mPreviousEngine);
96             }
97             mPreviousEngine = null;
98         }
99     }
100 
101     @Override
onRadioButtonConfirmed(String selectedKey)102     protected void onRadioButtonConfirmed(String selectedKey) {
103         final EngineCandidateInfo info = mEngineMap.get(selectedKey);
104         // Should we alert user? if that's true, delay making engine current one.
105         if (shouldDisplayDataAlert(info)) {
106             displayDataAlert(info, (dialog, which) -> {
107                 setDefaultKey(selectedKey);
108             });
109         } else {
110             // Privileged engine, set it current
111             setDefaultKey(selectedKey);
112         }
113     }
114 
115     @Override
getCandidates()116     protected List<? extends CandidateInfo> getCandidates() {
117         final List<EngineCandidateInfo> infos = new ArrayList<>();
118         final List<EngineInfo> engines = mEnginesHelper.getEngines();
119         for (EngineInfo engine : engines) {
120             final EngineCandidateInfo info = new EngineCandidateInfo(engine);
121             infos.add(info);
122             mEngineMap.put(engine.name, info);
123         }
124         return infos;
125     }
126 
127     @Override
getDefaultKey()128     protected String getDefaultKey() {
129         return mEnginesHelper.getDefaultEngine();
130     }
131 
132     @Override
setDefaultKey(String key)133     protected boolean setDefaultKey(String key) {
134         updateDefaultEngine(key);
135         updateCheckedState(key);
136         return true;
137     }
138 
139     @Override
getPreferenceScreenResId()140     protected int getPreferenceScreenResId() {
141         return R.xml.tts_engine_picker;
142     }
143 
shouldDisplayDataAlert(EngineCandidateInfo info)144     private boolean shouldDisplayDataAlert(EngineCandidateInfo info) {
145         return !info.isSystem();
146     }
147 
displayDataAlert(EngineCandidateInfo info, DialogInterface.OnClickListener positiveOnClickListener)148     private void displayDataAlert(EngineCandidateInfo info,
149             DialogInterface.OnClickListener positiveOnClickListener) {
150         Log.i(TAG, "Displaying data alert for :" + info.getKey());
151 
152         final AlertDialog dialog = new AlertDialog.Builder(getPrefContext())
153                 .setTitle(android.R.string.dialog_alert_title)
154                 .setMessage(mContext.getString(
155                         R.string.tts_engine_security_warning, info.loadLabel()))
156                 .setCancelable(true)
157                 .setPositiveButton(android.R.string.ok, positiveOnClickListener)
158                 .setNegativeButton(android.R.string.cancel, null)
159                 .create();
160 
161         dialog.show();
162     }
163 
updateDefaultEngine(String engine)164     private void updateDefaultEngine(String engine) {
165         Log.d(TAG, "Updating default synth to : " + engine);
166 
167         // Keep track of the previous engine that was being used. So that
168         // we can reuse the previous engine.
169         //
170         // Note that if TextToSpeech#getCurrentEngine is not null, it means at
171         // the very least that we successfully bound to the engine service.
172         mPreviousEngine = mTts.getCurrentEngine();
173 
174         // Step 1: Shut down the existing TTS engine.
175         Log.i(TAG, "Shutting down current tts engine");
176         if (mTts != null) {
177             try {
178                 mTts.shutdown();
179                 mTts = null;
180             } catch (Exception e) {
181                 Log.e(TAG, "Error shutting down TTS engine" + e);
182             }
183         }
184 
185         // Step 2: Connect to the new TTS engine.
186         // Step 3 is continued on #onUpdateEngine (below) which is called when
187         // the app binds successfully to the engine.
188         Log.i(TAG, "Updating engine : Attempting to connect to engine: " + engine);
189         mTts = new TextToSpeech(mContext, mUpdateListener, engine);
190         Log.i(TAG, "Success");
191     }
192 
193     public static class EngineCandidateInfo extends CandidateInfo {
194         private final EngineInfo mEngineInfo;
195 
EngineCandidateInfo(EngineInfo engineInfo)196         EngineCandidateInfo(EngineInfo engineInfo) {
197             super(true /* enabled */);
198             mEngineInfo = engineInfo;
199         }
200 
201         @Override
loadLabel()202         public CharSequence loadLabel() {
203             return mEngineInfo.label;
204         }
205 
206         @Override
loadIcon()207         public Drawable loadIcon() {
208             return null;
209         }
210 
211         @Override
getKey()212         public String getKey() {
213             return mEngineInfo.name;
214         }
215 
isSystem()216         public boolean isSystem() {
217             return mEngineInfo.system;
218         }
219     }
220 
221     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
222             new BaseSearchIndexProvider(R.xml.tts_engine_picker);
223 }
224