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.preference.PreferenceGroup; 29 30 import com.android.car.settings.R; 31 import com.android.car.settings.common.FragmentController; 32 import com.android.car.settings.common.Logger; 33 import com.android.car.settings.common.PreferenceController; 34 import com.android.car.ui.preference.CarUiPreference; 35 36 /** Populates the possible tts engines to set as the preferred engine. */ 37 public class PreferredEngineOptionsPreferenceController extends 38 PreferenceController<PreferenceGroup> { 39 40 private static final Logger LOG = new Logger(PreferredEngineOptionsPreferenceController.class); 41 42 private final TtsEngines mEnginesHelper; 43 private String mPreviousEngine; 44 private boolean mIsStarted; 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 = new TtsEngines(getContext()); 51 mIsStarted = false; 52 } 53 54 @Override getPreferenceType()55 protected Class<PreferenceGroup> getPreferenceType() { 56 return PreferenceGroup.class; 57 } 58 59 /** 60 * Creates the initial TTS object and constructs the related preferences when underlying 61 * fragment is created. 62 */ 63 @Override onCreateInternal()64 protected void onCreateInternal() { 65 mTts = new TextToSpeech(getContext(), /* listener= */ null); 66 67 for (TextToSpeech.EngineInfo engine : mEnginesHelper.getEngines()) { 68 CarUiPreference preference = new CarUiPreference(getContext()); 69 preference.setKey(engine.name); 70 preference.setTitle(engine.label); 71 preference.setShowChevron(false); 72 preference.setOnPreferenceClickListener(pref -> { 73 TextToSpeech.EngineInfo current = mEnginesHelper.getEngineInfo( 74 mTts.getCurrentEngine()); 75 if (TextUtils.equals(engine.label, current.label)) { 76 return false; 77 } 78 updateDefaultEngine(engine.name); 79 return true; 80 }); 81 getPreference().addPreference(preference); 82 } 83 } 84 85 /** Note that the preference controller was started. */ 86 @Override onStartInternal()87 protected void onStartInternal() { 88 mIsStarted = true; 89 } 90 91 /** Note that the preference controller was stopped. */ 92 @Override onStopInternal()93 protected void onStopInternal() { 94 mIsStarted = false; 95 } 96 97 /** Cleans up the TTS object and clears the preferences representing the TTS engines. */ 98 @Override onDestroyInternal()99 protected void onDestroyInternal() { 100 if (mTts != null) { 101 mTts.shutdown(); 102 mTts = null; 103 } 104 } 105 106 @Override updateState(PreferenceGroup preference)107 protected void updateState(PreferenceGroup preference) { 108 TextToSpeech.EngineInfo current = mEnginesHelper.getEngineInfo(mTts.getCurrentEngine()); 109 for (int i = 0; i < preference.getPreferenceCount(); i++) { 110 CarUiPreference pref = (CarUiPreference) preference.getPreference(i); 111 if (pref.getTitle().equals(current.label)) { 112 pref.setSummary(R.string.text_to_speech_current_engine); 113 } else { 114 pref.setSummary(""); 115 } 116 } 117 } 118 updateDefaultEngine(String engineName)119 private void updateDefaultEngine(String engineName) { 120 LOG.d("Updating default synth to : " + engineName); 121 122 // Keep track of the previous engine that was being used. So that 123 // we can reuse the previous engine. 124 // 125 // Note that if TextToSpeech#getCurrentEngine is not null, it means at 126 // the very least that we successfully bound to the engine service. 127 mPreviousEngine = mTts.getCurrentEngine(); 128 129 // Step 1: Shut down the existing TTS engine. 130 LOG.i("Shutting down current tts engine"); 131 if (mTts != null) { 132 mTts.shutdown(); 133 } 134 135 // Step 2: Connect to the new TTS engine. 136 // Step 3 is continued on #onUpdateEngine (below) which is called when 137 // the app binds successfully to the engine. 138 LOG.i("Updating engine : Attempting to connect to engine: " + engineName); 139 mTts = new TextToSpeech(getContext(), status -> { 140 if (mIsStarted) { 141 onUpdateEngine(status); 142 refreshUi(); 143 } 144 }, engineName); 145 LOG.i("Success"); 146 } 147 148 /** 149 * We have now bound to the TTS engine the user requested. We will attempt to check voice data 150 * for the engine if we successfully bound to it, or revert to the previous engine if we 151 * didn't. 152 */ onUpdateEngine(int status)153 private void onUpdateEngine(int status) { 154 if (status == TextToSpeech.SUCCESS) { 155 LOG.d("Updating engine: Successfully bound to the engine: " 156 + mTts.getCurrentEngine()); 157 Settings.Secure.putString(getContext().getContentResolver(), TTS_DEFAULT_SYNTH, 158 mTts.getCurrentEngine()); 159 } else { 160 LOG.d("Updating engine: Failed to bind to engine, reverting."); 161 if (mPreviousEngine != null) { 162 // This is guaranteed to at least bind, since mPreviousEngine would be 163 // null if the previous bind to this engine failed. 164 mTts = new TextToSpeech(getContext(), /* listener= */ null, mPreviousEngine); 165 } 166 mPreviousEngine = null; 167 } 168 } 169 } 170