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