• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car.settings.tts;
18 
19 import static android.provider.Settings.Secure.TTS_DEFAULT_SYNTH;
20 
21 import android.car.drivingstate.CarUxRestrictions;
22 import android.content.Context;
23 import android.provider.Settings;
24 import android.speech.tts.TextToSpeech;
25 import android.speech.tts.TtsEngines;
26 import android.text.TextUtils;
27 
28 import androidx.annotation.VisibleForTesting;
29 import androidx.preference.PreferenceGroup;
30 
31 import com.android.car.settings.R;
32 import com.android.car.settings.common.FragmentController;
33 import com.android.car.settings.common.Logger;
34 import com.android.car.settings.common.PreferenceController;
35 import com.android.car.ui.preference.CarUiPreference;
36 
37 /** Populates the possible tts engines to set as the preferred engine. */
38 public class PreferredEngineOptionsPreferenceController extends
39         PreferenceController<PreferenceGroup> {
40 
41     private static final Logger LOG = new Logger(PreferredEngineOptionsPreferenceController.class);
42 
43     private final TtsEngines mEnginesHelper;
44     private String mPreviousEngine;
45     private TextToSpeech mTts;
46 
PreferredEngineOptionsPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)47     public PreferredEngineOptionsPreferenceController(Context context, String preferenceKey,
48             FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
49         super(context, preferenceKey, fragmentController, uxRestrictions);
50         mEnginesHelper = createEnginesHelper();
51     }
52 
53     @Override
getPreferenceType()54     protected Class<PreferenceGroup> getPreferenceType() {
55         return PreferenceGroup.class;
56     }
57 
58     /**
59      * Creates the initial TTS object and constructs the related preferences when underlying
60      * fragment is created.
61      */
62     @Override
onCreateInternal()63     protected void onCreateInternal() {
64         mTts = createTts(/* listener= */ null, /* engine= */ null);
65 
66         for (TextToSpeech.EngineInfo engine : mEnginesHelper.getEngines()) {
67             CarUiPreference preference = new CarUiPreference(getContext());
68             preference.setKey(engine.name);
69             preference.setTitle(engine.label);
70             preference.setShowChevron(false);
71             preference.setOnPreferenceClickListener(pref -> {
72                 TextToSpeech.EngineInfo current = mEnginesHelper.getEngineInfo(
73                         mTts.getCurrentEngine());
74                 if (areEnginesEqual(current, engine)) {
75                     return false;
76                 }
77                 updateDefaultEngine(engine.name);
78                 return true;
79             });
80             getPreference().addPreference(preference);
81         }
82     }
83 
84     /** Cleans up the TTS object and clears the preferences representing the TTS engines. */
85     @Override
onDestroyInternal()86     protected void onDestroyInternal() {
87         if (mTts != null) {
88             mTts.shutdown();
89             mTts = null;
90         }
91     }
92 
93     @Override
updateState(PreferenceGroup preference)94     protected void updateState(PreferenceGroup preference) {
95         TextToSpeech.EngineInfo current = mEnginesHelper.getEngineInfo(mTts.getCurrentEngine());
96         for (int i = 0; i < preference.getPreferenceCount(); i++) {
97             CarUiPreference pref = (CarUiPreference) preference.getPreference(i);
98             if (areEnginesEqual(current, pref.getKey(), pref.getTitle())) {
99                 pref.setSummary(R.string.text_to_speech_current_engine);
100             } else {
101                 pref.setSummary("");
102             }
103         }
104     }
105 
areEnginesEqual(TextToSpeech.EngineInfo engine1, TextToSpeech.EngineInfo engine2)106     private boolean areEnginesEqual(TextToSpeech.EngineInfo engine1,
107             TextToSpeech.EngineInfo engine2) {
108         return areEnginesEqual(engine1, engine2.name, engine2.label);
109     }
110 
areEnginesEqual(TextToSpeech.EngineInfo engine, CharSequence name, CharSequence label)111     private boolean areEnginesEqual(TextToSpeech.EngineInfo engine, CharSequence name,
112             CharSequence label) {
113         return TextUtils.equals(engine.name, name) && TextUtils.equals(engine.label, label);
114     }
115 
updateDefaultEngine(String engineName)116     private void updateDefaultEngine(String engineName) {
117         LOG.d("Updating default synth to : " + engineName);
118 
119         // Keep track of the previous engine that was being used. So that
120         // we can reuse the previous engine.
121         //
122         // Note that if TextToSpeech#getCurrentEngine is not null, it means at
123         // the very least that we successfully bound to the engine service.
124         mPreviousEngine = mTts.getCurrentEngine();
125 
126         // Step 1: Shut down the existing TTS engine.
127         LOG.i("Shutting down current tts engine");
128         if (mTts != null) {
129             mTts.shutdown();
130         }
131 
132         // Step 2: Connect to the new TTS engine.
133         // Step 3 is continued on #onUpdateEngine (below) which is called when
134         // the app binds successfully to the engine.
135         LOG.i("Updating engine : Attempting to connect to engine: " + engineName);
136         mTts = createTts(status -> {
137             if (isStarted()) {
138                 onUpdateEngine(status);
139                 refreshUi();
140             }
141         }, engineName);
142         LOG.i("Success");
143     }
144 
145     /**
146      * We have now bound to the TTS engine the user requested. We will attempt to check voice data
147      * for the engine if we successfully bound to it, or revert to the previous engine if we
148      * didn't.
149      */
150     @VisibleForTesting
onUpdateEngine(int status)151     void onUpdateEngine(int status) {
152         if (status == TextToSpeech.SUCCESS) {
153             LOG.d("Updating engine: Successfully bound to the engine: "
154                     + mTts.getCurrentEngine());
155             Settings.Secure.putString(getContext().getContentResolver(), TTS_DEFAULT_SYNTH,
156                     mTts.getCurrentEngine());
157         } else {
158             LOG.d("Updating engine: Failed to bind to engine, reverting.");
159             if (mPreviousEngine != null) {
160                 // This is guaranteed to at least bind, since mPreviousEngine would be
161                 // null if the previous bind to this engine failed.
162                 mTts = createTts(/* listener= */ null, mPreviousEngine);
163             }
164             mPreviousEngine = null;
165         }
166     }
167 
168     @VisibleForTesting
createEnginesHelper()169     TtsEngines createEnginesHelper() {
170         return new TtsEngines(getContext());
171     }
172 
173     @VisibleForTesting
createTts(TextToSpeech.OnInitListener listener, String engine)174     TextToSpeech createTts(TextToSpeech.OnInitListener listener, String engine) {
175         return new TextToSpeech(getContext(), listener, engine);
176     }
177 }
178