blob: 673a0bd57df3eef178c8ffb4013710b677b68dbe [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.settings.tts;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.Intent;
import android.provider.Settings;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TtsEngines;
import android.speech.tts.Voice;
import androidx.lifecycle.Lifecycle;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
import com.android.car.settings.CarSettingsRobolectricTestRunner;
import com.android.car.settings.R;
import com.android.car.settings.common.ActivityResultCallback;
import com.android.car.settings.common.LogicalPreferenceGroup;
import com.android.car.settings.common.PreferenceControllerTestHelper;
import com.android.car.settings.common.SeekBarPreference;
import com.android.car.settings.testutils.ShadowSecureSettings;
import com.android.car.settings.testutils.ShadowTextToSpeech;
import com.android.car.settings.testutils.ShadowTtsEngines;
import com.google.android.collect.Lists;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import java.util.Locale;
@RunWith(CarSettingsRobolectricTestRunner.class)
@Config(shadows = {ShadowTextToSpeech.class, ShadowTtsEngines.class,
ShadowSecureSettings.class})
public class TtsPlaybackPreferenceControllerTest {
private static final String DEFAULT_ENGINE_NAME = "com.android.car.settings.tts.test.default";
private static final TextToSpeech.EngineInfo ENGINE_INFO = new TextToSpeech.EngineInfo();
private static final Voice VOICE = new Voice("Test Name", Locale.ENGLISH, /* quality= */0,
/* latency= */ 0, /* requiresNetworkConnection= */ true, /* features= */ null);
static {
ENGINE_INFO.label = "Test Engine";
ENGINE_INFO.name = "com.android.car.settings.tts.test.other";
}
private Context mContext;
private PreferenceControllerTestHelper<TtsPlaybackPreferenceController>
mControllerHelper;
private TtsPlaybackPreferenceController mController;
private PreferenceGroup mPreferenceGroup;
private ListPreference mDefaultLanguagePreference;
private SeekBarPreference mSpeechRatePreference;
private SeekBarPreference mVoicePitchPreference;
private Preference mResetPreference;
@Mock
private TextToSpeech mTextToSpeech;
@Mock
private TtsEngines mEnginesHelper;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
ShadowTextToSpeech.setInstance(mTextToSpeech);
ShadowTtsEngines.setInstance(mEnginesHelper);
mContext = RuntimeEnvironment.application;
mPreferenceGroup = new LogicalPreferenceGroup(mContext);
mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
TtsPlaybackPreferenceController.class, mPreferenceGroup);
mDefaultLanguagePreference = new ListPreference(mContext);
mDefaultLanguagePreference.setKey(mContext.getString(R.string.pk_tts_default_language));
mPreferenceGroup.addPreference(mDefaultLanguagePreference);
mSpeechRatePreference = new SeekBarPreference(mContext);
mSpeechRatePreference.setKey(mContext.getString(R.string.pk_tts_speech_rate));
mPreferenceGroup.addPreference(mSpeechRatePreference);
mVoicePitchPreference = new SeekBarPreference(mContext);
mVoicePitchPreference.setKey(mContext.getString(R.string.pk_tts_pitch));
mPreferenceGroup.addPreference(mVoicePitchPreference);
mResetPreference = new Preference(mContext);
mResetPreference.setKey(mContext.getString(R.string.pk_tts_reset));
mPreferenceGroup.addPreference(mResetPreference);
mController = mControllerHelper.getController();
when(mTextToSpeech.getCurrentEngine()).thenReturn(ENGINE_INFO.name);
when(mTextToSpeech.getVoice()).thenReturn(VOICE);
when(mEnginesHelper.parseLocaleString(VOICE.getLocale().toString())).thenReturn(
VOICE.getLocale());
when(mEnginesHelper.parseLocaleString(Locale.CANADA.toString())).thenReturn(Locale.CANADA);
when(mEnginesHelper.parseLocaleString(Locale.KOREA.toString())).thenReturn(Locale.KOREA);
Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_RATE,
TextToSpeech.Engine.DEFAULT_RATE);
Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_PITCH,
TextToSpeech.Engine.DEFAULT_PITCH);
}
@After
public void tearDown() {
ShadowTtsEngines.reset();
ShadowTextToSpeech.reset();
ShadowSecureSettings.reset();
}
@Test
public void onCreate_startsCheckVoiceData() {
mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
verify(mControllerHelper.getMockFragmentController()).startActivityForResult(
intent.capture(), eq(TtsPlaybackPreferenceController.VOICE_DATA_CHECK),
any(ActivityResultCallback.class));
assertThat(intent.getValue().getAction()).isEqualTo(
TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
assertThat(intent.getValue().getPackage()).isEqualTo(ENGINE_INFO.name);
}
@Test
public void voiceDataCheck_processActivityResult_dataIsNull_defaultSynthRemainsUnchanged() {
mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_SYNTH,
DEFAULT_ENGINE_NAME);
mController.processActivityResult(
TtsPlaybackPreferenceController.VOICE_DATA_CHECK,
TextToSpeech.Engine.CHECK_VOICE_DATA_PASS, /* data= */ null);
assertThat(Settings.Secure.getString(mContext.getContentResolver(),
Settings.Secure.TTS_DEFAULT_SYNTH)).isEqualTo(DEFAULT_ENGINE_NAME);
}
@Test
public void voiceDataCheck_processActivityResult_dataIsNotNull_updatesDefaultSynth() {
mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_SYNTH,
DEFAULT_ENGINE_NAME);
Intent data = new Intent();
mController.processActivityResult(
TtsPlaybackPreferenceController.VOICE_DATA_CHECK,
TextToSpeech.Engine.CHECK_VOICE_DATA_PASS, data);
assertThat(Settings.Secure.getString(mContext.getContentResolver(),
Settings.Secure.TTS_DEFAULT_SYNTH)).isEqualTo(ENGINE_INFO.name);
}
@Test
public void voiceDataCheck_processActivityResult_checkSuccess_hasVoices_populatesPreference() {
mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
// Check that the length is 0 initially.
assertThat(mDefaultLanguagePreference.getEntries()).isNull();
Intent data = new Intent();
data.putStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES,
Lists.newArrayList(
Locale.ENGLISH.toString(),
Locale.CANADA.toString(),
Locale.KOREA.toString()
));
ShadowTextToSpeech.callInitializationCallbackWithStatus(TextToSpeech.SUCCESS);
mController.processActivityResult(TtsPlaybackPreferenceController.VOICE_DATA_CHECK,
TextToSpeech.Engine.CHECK_VOICE_DATA_PASS, data);
// Length is 3 languages + default language.
assertThat(mDefaultLanguagePreference.getEntries().length).isEqualTo(4);
}
@Test
public void getSampleText_processActivityResult_dataIsNull_setsDefaultText() {
mController.processActivityResult(TtsPlaybackPreferenceController.GET_SAMPLE_TEXT,
TextToSpeech.LANG_AVAILABLE, /* data= */ null);
assertThat(mController.getSampleText()).isEqualTo(
mContext.getString(R.string.tts_default_sample_string));
}
@Test
public void getSampleText_processActivityResult_emptyText_setsDefaultText() {
String testData = "";
Intent data = new Intent();
data.putExtra(TextToSpeech.Engine.EXTRA_SAMPLE_TEXT, testData);
mController.processActivityResult(TtsPlaybackPreferenceController.GET_SAMPLE_TEXT,
TextToSpeech.LANG_AVAILABLE, data);
assertThat(mController.getSampleText()).isEqualTo(
mContext.getString(R.string.tts_default_sample_string));
}
@Test
public void getSampleText_processActivityResult_dataIsNotNull_setsCorrectText() {
String testData = "Test sample text";
Intent data = new Intent();
data.putExtra(TextToSpeech.Engine.EXTRA_SAMPLE_TEXT, testData);
mController.processActivityResult(TtsPlaybackPreferenceController.GET_SAMPLE_TEXT,
TextToSpeech.LANG_AVAILABLE, data);
assertThat(mController.getSampleText()).isEqualTo(testData);
}
@Test
public void defaultLanguage_handlePreferenceChanged_passEmptyValue_setsDefault() {
mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
Intent data = new Intent();
data.putStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES,
Lists.newArrayList(
Locale.ENGLISH.toString(),
Locale.CANADA.toString(),
Locale.KOREA.toString()
));
mController.processActivityResult(TtsPlaybackPreferenceController.VOICE_DATA_CHECK,
TextToSpeech.Engine.CHECK_VOICE_DATA_PASS, data);
// Test change listener.
mDefaultLanguagePreference.callChangeListener("");
verify(mTextToSpeech).setLanguage(Locale.getDefault());
}
@Test
public void defaultLanguage_handlePreferenceChanged_passLocale_setsLocale() {
mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
Intent data = new Intent();
data.putStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES,
Lists.newArrayList(
Locale.ENGLISH.toString(),
Locale.CANADA.toString(),
Locale.KOREA.toString()
));
mController.processActivityResult(TtsPlaybackPreferenceController.VOICE_DATA_CHECK,
TextToSpeech.Engine.CHECK_VOICE_DATA_PASS, data);
// Test change listener.
mDefaultLanguagePreference.callChangeListener(Locale.ENGLISH.toString());
verify(mTextToSpeech).setLanguage(Locale.ENGLISH);
}
@Test
public void defaultLanguage_handlePreferenceChanged_passLocale_setsSummary() {
mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
Intent data = new Intent();
data.putStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES,
Lists.newArrayList(
Locale.ENGLISH.toString(),
Locale.CANADA.toString(),
Locale.KOREA.toString()
));
mController.processActivityResult(TtsPlaybackPreferenceController.VOICE_DATA_CHECK,
TextToSpeech.Engine.CHECK_VOICE_DATA_PASS, data);
mDefaultLanguagePreference.callChangeListener(Locale.ENGLISH.toString());
assertThat(mDefaultLanguagePreference.getSummary()).isEqualTo(
Locale.ENGLISH.getDisplayName());
}
@Test
public void speechRate_handlePreferenceChanged_updatesSecureSettings() {
mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
int newSpeechRate = TextToSpeech.Engine.DEFAULT_RATE + 40;
mSpeechRatePreference.callChangeListener(newSpeechRate);
assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.TTS_DEFAULT_RATE, TextToSpeech.Engine.DEFAULT_RATE)).isEqualTo(
newSpeechRate);
}
@Test
public void speechRate_handlePreferenceChanged_updatesTts() {
mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
int newSpeechRate = TextToSpeech.Engine.DEFAULT_RATE + 40;
mSpeechRatePreference.callChangeListener(newSpeechRate);
verify(mTextToSpeech).setSpeechRate(
newSpeechRate / TtsPlaybackSettingsManager.SCALING_FACTOR);
}
@Test
public void speechRate_handlePreferenceChanged_speaksSampleText() {
mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
int newSpeechRate = TextToSpeech.Engine.DEFAULT_RATE + 40;
mSpeechRatePreference.callChangeListener(newSpeechRate);
verify(mTextToSpeech).speak(any(), eq(TextToSpeech.QUEUE_FLUSH), isNull(), eq("Sample"));
}
@Test
public void voicePitch_handlePreferenceChanged_updatesSecureSettings() {
mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
int newVoicePitch = TextToSpeech.Engine.DEFAULT_PITCH + 40;
mVoicePitchPreference.callChangeListener(newVoicePitch);
assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.TTS_DEFAULT_PITCH, TextToSpeech.Engine.DEFAULT_PITCH)).isEqualTo(
newVoicePitch);
}
@Test
public void voicePitch_handlePreferenceChanged_updatesTts() {
mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
int newVoicePitch = TextToSpeech.Engine.DEFAULT_PITCH + 40;
mVoicePitchPreference.callChangeListener(newVoicePitch);
verify(mTextToSpeech).setPitch(
newVoicePitch / TtsPlaybackSettingsManager.SCALING_FACTOR);
}
@Test
public void voicePitch_handlePreferenceChanged_speaksSampleText() {
mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
int newVoicePitch = TextToSpeech.Engine.DEFAULT_PITCH + 40;
mVoicePitchPreference.callChangeListener(newVoicePitch);
verify(mTextToSpeech).speak(any(), eq(TextToSpeech.QUEUE_FLUSH), isNull(), eq("Sample"));
}
@Test
public void refreshUi_notInitialized_disablesPreference() {
mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
mController.refreshUi();
assertThat(mDefaultLanguagePreference.isEnabled()).isFalse();
// Default language preference is always hidden
assertThat(mDefaultLanguagePreference.isVisible()).isFalse();
assertThat(mSpeechRatePreference.isEnabled()).isFalse();
assertThat(mVoicePitchPreference.isEnabled()).isFalse();
assertThat(mResetPreference.isEnabled()).isFalse();
}
@Test
public void refreshUi_initialized_defaultLocaleIsNull_disablesPreference() {
when(mEnginesHelper.parseLocaleString(any())).thenReturn(null);
mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
ShadowTextToSpeech.callInitializationCallbackWithStatus(TextToSpeech.SUCCESS);
mController.refreshUi();
assertThat(mDefaultLanguagePreference.isEnabled()).isFalse();
// Default language preference is always hidden
assertThat(mDefaultLanguagePreference.isVisible()).isFalse();
assertThat(mSpeechRatePreference.isEnabled()).isFalse();
assertThat(mVoicePitchPreference.isEnabled()).isFalse();
assertThat(mResetPreference.isEnabled()).isFalse();
}
@Test
public void refreshUi_defaultLocaleNotSupported_disablesPreferencesExceptLanguage() {
mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
ShadowTextToSpeech.callInitializationCallbackWithStatus(TextToSpeech.SUCCESS);
Intent data = new Intent();
data.putStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES,
Lists.newArrayList(
// English is default locale, but isn't available.
Locale.CANADA.toString(),
Locale.KOREA.toString()
));
mController.processActivityResult(TtsPlaybackPreferenceController.VOICE_DATA_CHECK,
TextToSpeech.Engine.CHECK_VOICE_DATA_PASS, data);
mController.refreshUi();
assertThat(mDefaultLanguagePreference.isEnabled()).isTrue();
// Default language preference is always hidden
assertThat(mDefaultLanguagePreference.isVisible()).isFalse();
assertThat(mSpeechRatePreference.isEnabled()).isFalse();
assertThat(mVoicePitchPreference.isEnabled()).isFalse();
assertThat(mResetPreference.isEnabled()).isFalse();
}
@Test
public void refreshUi_initialized_defaultLocaleSupported_enablesPreference() {
mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
ShadowTextToSpeech.callInitializationCallbackWithStatus(TextToSpeech.SUCCESS);
Intent data = new Intent();
data.putStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES,
Lists.newArrayList(
Locale.ENGLISH.toString(),
Locale.CANADA.toString(),
Locale.KOREA.toString()
));
mController.processActivityResult(TtsPlaybackPreferenceController.VOICE_DATA_CHECK,
TextToSpeech.Engine.CHECK_VOICE_DATA_PASS, data);
mController.refreshUi();
assertThat(mPreferenceGroup.isEnabled()).isTrue();
assertThat(mDefaultLanguagePreference.isEnabled()).isTrue();
// Default language preference is always hidden
assertThat(mDefaultLanguagePreference.isVisible()).isFalse();
assertThat(mSpeechRatePreference.isEnabled()).isTrue();
assertThat(mVoicePitchPreference.isEnabled()).isTrue();
assertThat(mResetPreference.isEnabled()).isTrue();
}
}