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 android.app.AlertDialog; 20 import android.content.Context; 21 import android.provider.Settings; 22 import android.speech.tts.TextToSpeech; 23 import android.speech.tts.TtsEngines; 24 25 import androidx.annotation.NonNull; 26 import androidx.annotation.Nullable; 27 import androidx.annotation.VisibleForTesting; 28 29 import com.android.car.settings.R; 30 import com.android.car.settings.common.Logger; 31 import com.android.car.ui.AlertDialogBuilder; 32 33 import java.util.Locale; 34 35 /** Handles interactions with TTS playback settings. */ 36 class TtsPlaybackSettingsManager { 37 38 private static final Logger LOG = new Logger(TtsPlaybackSettingsManager.class); 39 40 /** 41 * Maximum speech rate value. 42 */ 43 public static final int MAX_SPEECH_RATE = 600; 44 45 /** 46 * Minimum speech rate value. 47 */ 48 public static final int MIN_SPEECH_RATE = 10; 49 50 /** 51 * Maximum voice pitch value. 52 */ 53 public static final int MAX_VOICE_PITCH = 400; 54 55 /** 56 * Minimum voice pitch value. 57 */ 58 public static final int MIN_VOICE_PITCH = 25; 59 60 /** 61 * Scaling factor used to convert speech rate and pitch values between {@link Settings.Secure} 62 * and {@link TextToSpeech}. 63 */ 64 public static final float SCALING_FACTOR = 100.0f; 65 private static final String UTTERANCE_ID = "Sample"; 66 67 private final Context mContext; 68 private final TextToSpeech mTts; 69 private final TtsEngines mEnginesHelper; 70 TtsPlaybackSettingsManager(Context context, @NonNull TextToSpeech tts, @NonNull TtsEngines enginesHelper)71 TtsPlaybackSettingsManager(Context context, @NonNull TextToSpeech tts, 72 @NonNull TtsEngines enginesHelper) { 73 mContext = context; 74 mTts = tts; 75 mEnginesHelper = enginesHelper; 76 } 77 updateSpeechRate(int speechRate)78 void updateSpeechRate(int speechRate) { 79 Settings.Secure.putInt( 80 mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_RATE, speechRate); 81 mTts.setSpeechRate(speechRate / SCALING_FACTOR); 82 LOG.d("TTS default rate changed, now " + speechRate); 83 } 84 getCurrentSpeechRate()85 int getCurrentSpeechRate() { 86 return Settings.Secure.getInt(mContext.getContentResolver(), 87 Settings.Secure.TTS_DEFAULT_RATE, TextToSpeech.Engine.DEFAULT_RATE); 88 } 89 resetSpeechRate()90 void resetSpeechRate() { 91 updateSpeechRate(TextToSpeech.Engine.DEFAULT_RATE); 92 } 93 updateVoicePitch(int pitch)94 void updateVoicePitch(int pitch) { 95 Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_PITCH, 96 pitch); 97 mTts.setPitch(pitch / SCALING_FACTOR); 98 LOG.d("TTS default pitch changed, now " + pitch); 99 } 100 getCurrentVoicePitch()101 int getCurrentVoicePitch() { 102 return Settings.Secure.getInt(mContext.getContentResolver(), 103 Settings.Secure.TTS_DEFAULT_PITCH, TextToSpeech.Engine.DEFAULT_PITCH); 104 } 105 resetVoicePitch()106 void resetVoicePitch() { 107 updateVoicePitch(TextToSpeech.Engine.DEFAULT_PITCH); 108 } 109 110 /** 111 * Returns the currently stored locale for the given tts engine. It can return {@code null}, if 112 * it is configured to use the system default locale. 113 */ 114 @Nullable getStoredTtsLocale()115 Locale getStoredTtsLocale() { 116 Locale currentLocale = null; 117 if (!mEnginesHelper.isLocaleSetToDefaultForEngine(mTts.getCurrentEngine())) { 118 currentLocale = mEnginesHelper.getLocalePrefForEngine(mTts.getCurrentEngine()); 119 } 120 return currentLocale; 121 } 122 123 /** 124 * Similar to {@link #getStoredTtsLocale()}, but returns the language of the voice registered 125 * to the actual TTS object. It is possible for the TTS voice to be {@code null} if TTS is not 126 * yet initialized. 127 */ 128 @Nullable getEffectiveTtsLocale()129 Locale getEffectiveTtsLocale() { 130 if (mTts.getVoice() == null) { 131 return null; 132 } 133 return mEnginesHelper.parseLocaleString(mTts.getVoice().getLocale().toString()); 134 } 135 136 /** 137 * Attempts to update the default tts locale. Returns {@code true} if successful, false 138 * otherwise. 139 */ updateTtsLocale(Locale newLocale)140 boolean updateTtsLocale(Locale newLocale) { 141 int resultCode = mTts.setLanguage((newLocale != null) ? newLocale : Locale.getDefault()); 142 boolean success = resultCode != TextToSpeech.LANG_NOT_SUPPORTED 143 && resultCode != TextToSpeech.LANG_MISSING_DATA; 144 if (success) { 145 mEnginesHelper.updateLocalePrefForEngine(mTts.getCurrentEngine(), newLocale); 146 } 147 148 return success; 149 } 150 speakSampleText(String text)151 void speakSampleText(String text) { 152 boolean networkRequired = mTts.getVoice().isNetworkConnectionRequired(); 153 Locale defaultLocale = getEffectiveTtsLocale(); 154 if (!networkRequired || networkRequired && mTts.isLanguageAvailable(defaultLocale) 155 >= TextToSpeech.LANG_AVAILABLE) { 156 mTts.speak(text, TextToSpeech.QUEUE_FLUSH, /* params= */ null, UTTERANCE_ID); 157 } else { 158 displayNetworkAlert(); 159 } 160 } 161 displayNetworkAlert()162 private void displayNetworkAlert() { 163 AlertDialog dialog = createNetworkAlertDialog(); 164 dialog.show(); 165 } 166 167 @VisibleForTesting createNetworkAlertDialog()168 AlertDialog createNetworkAlertDialog() { 169 return new AlertDialogBuilder(mContext) 170 .setTitle(android.R.string.dialog_alert_title) 171 .setMessage(R.string.tts_engine_network_required) 172 .setCancelable(false) 173 .setPositiveButton(android.R.string.ok, null).create(); 174 } 175 } 176