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