Fix cannot switch between multiple TTS engines
- We didn't reset reset radio button state when user click back while dialog was popped. Change the UI, when user really accepts the dialog content, then changes radio button state. - Migrate TtsEnginePreferenceFragment to RadioButtonPickerFragment Fixes: 135588938 Test: manual Change-Id: Ia824e08d59c77a23e6590ff0f5b7d897a73a1ff8
This commit is contained in:
@@ -18,6 +18,4 @@
|
|||||||
android:key="tts_engine_picker_screen"
|
android:key="tts_engine_picker_screen"
|
||||||
android:title="@string/tts_engine_preference_title">
|
android:title="@string/tts_engine_preference_title">
|
||||||
|
|
||||||
<PreferenceCategory android:key="tts_engine_preference_category"/>
|
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
@@ -1,186 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2011 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.settings.tts;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.speech.tts.TextToSpeech.EngineInfo;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.widget.Checkable;
|
|
||||||
import android.widget.CompoundButton;
|
|
||||||
import android.widget.RadioButton;
|
|
||||||
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.preference.Preference;
|
|
||||||
import androidx.preference.PreferenceViewHolder;
|
|
||||||
|
|
||||||
import com.android.settings.R;
|
|
||||||
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
|
||||||
|
|
||||||
|
|
||||||
public class TtsEnginePreference extends Preference {
|
|
||||||
|
|
||||||
private static final String TAG = "TtsEnginePreference";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The engine information for the engine this preference represents.
|
|
||||||
* Contains it's name, label etc. which are used for display.
|
|
||||||
*/
|
|
||||||
private final EngineInfo mEngineInfo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The shared radio button state, which button is checked etc.
|
|
||||||
*/
|
|
||||||
private final RadioButtonGroupState mSharedState;
|
|
||||||
private RadioButton mRadioButton;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When true, the change callbacks on the radio button will not
|
|
||||||
* fire.
|
|
||||||
*/
|
|
||||||
private volatile boolean mPreventRadioButtonCallbacks;
|
|
||||||
|
|
||||||
private final CompoundButton.OnCheckedChangeListener mRadioChangeListener =
|
|
||||||
new CompoundButton.OnCheckedChangeListener() {
|
|
||||||
@Override
|
|
||||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
|
||||||
onRadioButtonClicked(buttonView, isChecked);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public TtsEnginePreference(Context context, EngineInfo info, RadioButtonGroupState state) {
|
|
||||||
super(context);
|
|
||||||
|
|
||||||
setWidgetLayoutResource(R.layout.preference_widget_radiobutton);
|
|
||||||
setLayoutResource(R.layout.preference_radio);
|
|
||||||
setIconSpaceReserved(false);
|
|
||||||
|
|
||||||
mSharedState = state;
|
|
||||||
mEngineInfo = info;
|
|
||||||
mPreventRadioButtonCallbacks = false;
|
|
||||||
|
|
||||||
setKey(mEngineInfo.name);
|
|
||||||
setTitle(mEngineInfo.label);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(PreferenceViewHolder view) {
|
|
||||||
super.onBindViewHolder(view);
|
|
||||||
|
|
||||||
if (mSharedState == null) {
|
|
||||||
throw new IllegalStateException("Call to getView() before a call to" +
|
|
||||||
"setSharedState()");
|
|
||||||
}
|
|
||||||
|
|
||||||
final RadioButton rb = view.itemView.findViewById(android.R.id.checkbox);
|
|
||||||
rb.setOnCheckedChangeListener(mRadioChangeListener);
|
|
||||||
|
|
||||||
boolean isChecked = getKey().equals(mSharedState.getCurrentKey());
|
|
||||||
if (isChecked) {
|
|
||||||
mSharedState.setCurrentChecked(rb);
|
|
||||||
}
|
|
||||||
|
|
||||||
mPreventRadioButtonCallbacks = true;
|
|
||||||
rb.setChecked(isChecked);
|
|
||||||
mPreventRadioButtonCallbacks = false;
|
|
||||||
mRadioButton = rb;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick() {
|
|
||||||
mRadioButton.setChecked(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean shouldDisplayDataAlert() {
|
|
||||||
return !mEngineInfo.system;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void displayDataAlert(
|
|
||||||
DialogInterface.OnClickListener positiveOnClickListener,
|
|
||||||
DialogInterface.OnClickListener negativeOnClickListener) {
|
|
||||||
Log.i(TAG, "Displaying data alert for :" + mEngineInfo.name);
|
|
||||||
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
|
|
||||||
builder.setTitle(android.R.string.dialog_alert_title)
|
|
||||||
.setMessage(getContext().getString(
|
|
||||||
R.string.tts_engine_security_warning, mEngineInfo.label))
|
|
||||||
.setCancelable(true)
|
|
||||||
.setPositiveButton(android.R.string.ok, positiveOnClickListener)
|
|
||||||
.setNegativeButton(android.R.string.cancel, negativeOnClickListener);
|
|
||||||
|
|
||||||
AlertDialog dialog = builder.create();
|
|
||||||
dialog.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void onRadioButtonClicked(final CompoundButton buttonView,
|
|
||||||
boolean isChecked) {
|
|
||||||
if (mPreventRadioButtonCallbacks ||
|
|
||||||
(mSharedState.getCurrentChecked() == buttonView)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isChecked) {
|
|
||||||
// Should we alert user? if that's true, delay making engine current one.
|
|
||||||
if (shouldDisplayDataAlert()) {
|
|
||||||
displayDataAlert(new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
makeCurrentEngine(buttonView);
|
|
||||||
}
|
|
||||||
}, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
// Undo the click.
|
|
||||||
buttonView.setChecked(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Privileged engine, set it current
|
|
||||||
makeCurrentEngine(buttonView);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void makeCurrentEngine(Checkable current) {
|
|
||||||
if (mSharedState.getCurrentChecked() != null) {
|
|
||||||
mSharedState.getCurrentChecked().setChecked(false);
|
|
||||||
}
|
|
||||||
mSharedState.setCurrentChecked(current);
|
|
||||||
mSharedState.setCurrentKey(getKey());
|
|
||||||
callChangeListener(mSharedState.getCurrentKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds all state that is common to this group of radio buttons, such
|
|
||||||
* as the currently selected key and the currently checked compound button.
|
|
||||||
* (which corresponds to this key).
|
|
||||||
*/
|
|
||||||
public interface RadioButtonGroupState {
|
|
||||||
String getCurrentKey();
|
|
||||||
|
|
||||||
Checkable getCurrentChecked();
|
|
||||||
|
|
||||||
void setCurrentKey(String key);
|
|
||||||
|
|
||||||
void setCurrentChecked(Checkable current);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -4,111 +4,44 @@ import static android.provider.Settings.Secure.TTS_DEFAULT_SYNTH;
|
|||||||
|
|
||||||
import android.app.settings.SettingsEnums;
|
import android.app.settings.SettingsEnums;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.SearchIndexableResource;
|
import android.provider.SearchIndexableResource;
|
||||||
import android.speech.tts.TextToSpeech;
|
import android.speech.tts.TextToSpeech;
|
||||||
import android.speech.tts.TextToSpeech.EngineInfo;
|
import android.speech.tts.TextToSpeech.EngineInfo;
|
||||||
import android.speech.tts.TtsEngines;
|
import android.speech.tts.TtsEngines;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Checkable;
|
|
||||||
|
|
||||||
import androidx.preference.PreferenceCategory;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.SettingsPreferenceFragment;
|
|
||||||
import com.android.settings.search.BaseSearchIndexProvider;
|
import com.android.settings.search.BaseSearchIndexProvider;
|
||||||
import com.android.settings.search.Indexable;
|
import com.android.settings.search.Indexable;
|
||||||
import com.android.settings.tts.TtsEnginePreference.RadioButtonGroupState;
|
import com.android.settings.widget.RadioButtonPickerFragment;
|
||||||
import com.android.settingslib.search.SearchIndexable;
|
import com.android.settingslib.search.SearchIndexable;
|
||||||
|
import com.android.settingslib.widget.CandidateInfo;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@SearchIndexable
|
@SearchIndexable
|
||||||
public class TtsEnginePreferenceFragment extends SettingsPreferenceFragment
|
public class TtsEnginePreferenceFragment extends RadioButtonPickerFragment {
|
||||||
implements RadioButtonGroupState {
|
|
||||||
private static final String TAG = "TtsEnginePrefFragment";
|
private static final String TAG = "TtsEnginePrefFragment";
|
||||||
|
|
||||||
private static final int VOICE_DATA_INTEGRITY_CHECK = 1977;
|
|
||||||
|
|
||||||
/** The currently selected engine. */
|
|
||||||
private String mCurrentEngine;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The engine checkbox that is currently checked. Saves us a bit of effort in deducing the right
|
|
||||||
* one from the currently selected engine.
|
|
||||||
*/
|
|
||||||
private Checkable mCurrentChecked;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The previously selected TTS engine. Useful for rollbacks if the users choice is not loaded or
|
* The previously selected TTS engine. Useful for rollbacks if the users choice is not loaded or
|
||||||
* fails a voice integrity check.
|
* fails a voice integrity check.
|
||||||
*/
|
*/
|
||||||
private String mPreviousEngine;
|
private String mPreviousEngine;
|
||||||
|
|
||||||
private PreferenceCategory mEnginePreferenceCategory;
|
|
||||||
|
|
||||||
private TextToSpeech mTts = null;
|
private TextToSpeech mTts = null;
|
||||||
private TtsEngines mEnginesHelper = null;
|
private TtsEngines mEnginesHelper = null;
|
||||||
|
private Context mContext;
|
||||||
@Override
|
private Map<String, EngineCandidateInfo> mEngineMap;
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
addPreferencesFromResource(R.xml.tts_engine_picker);
|
|
||||||
|
|
||||||
mEnginePreferenceCategory =
|
|
||||||
(PreferenceCategory) findPreference("tts_engine_preference_category");
|
|
||||||
mEnginesHelper = new TtsEngines(getActivity().getApplicationContext());
|
|
||||||
|
|
||||||
mTts = new TextToSpeech(getActivity().getApplicationContext(), null);
|
|
||||||
|
|
||||||
initSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMetricsCategory() {
|
|
||||||
return SettingsEnums.TTS_ENGINE_SETTINGS;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
if (mTts != null) {
|
|
||||||
mTts.shutdown();
|
|
||||||
mTts = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initSettings() {
|
|
||||||
if (mTts != null) {
|
|
||||||
mCurrentEngine = mTts.getCurrentEngine();
|
|
||||||
}
|
|
||||||
|
|
||||||
mEnginePreferenceCategory.removeAll();
|
|
||||||
|
|
||||||
List<EngineInfo> engines = mEnginesHelper.getEngines();
|
|
||||||
for (EngineInfo engine : engines) {
|
|
||||||
TtsEnginePreference enginePref =
|
|
||||||
new TtsEnginePreference(getPrefContext(), engine, this);
|
|
||||||
mEnginePreferenceCategory.addPreference(enginePref);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Checkable getCurrentChecked() {
|
|
||||||
return mCurrentChecked;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getCurrentKey() {
|
|
||||||
return mCurrentEngine;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setCurrentChecked(Checkable current) {
|
|
||||||
mCurrentChecked = current;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The initialization listener used when the user changes his choice of engine (as opposed to
|
* The initialization listener used when the user changes his choice of engine (as opposed to
|
||||||
* when then screen is being initialized for the first time).
|
* when then screen is being initialized for the first time).
|
||||||
@@ -121,6 +54,116 @@ public class TtsEnginePreferenceFragment extends SettingsPreferenceFragment
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
mContext = getContext().getApplicationContext();
|
||||||
|
mEnginesHelper = new TtsEngines(mContext);
|
||||||
|
mEngineMap = new HashMap<>();
|
||||||
|
mTts = new TextToSpeech(mContext, null);
|
||||||
|
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
if (mTts != null) {
|
||||||
|
mTts.shutdown();
|
||||||
|
mTts = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMetricsCategory() {
|
||||||
|
return SettingsEnums.TTS_ENGINE_SETTINGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step 3: We have now bound to the TTS engine the user requested. We will attempt to check
|
||||||
|
* voice data for the engine if we successfully bound to it, or revert to the previous engine if
|
||||||
|
* we didn't.
|
||||||
|
*/
|
||||||
|
public void onUpdateEngine(int status) {
|
||||||
|
if (status == TextToSpeech.SUCCESS) {
|
||||||
|
Log.d(TAG, "Updating engine: Successfully bound to the engine: "
|
||||||
|
+ mTts.getCurrentEngine());
|
||||||
|
android.provider.Settings.Secure.putString(
|
||||||
|
mContext.getContentResolver(), TTS_DEFAULT_SYNTH, mTts.getCurrentEngine());
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Updating engine: Failed to bind to engine, reverting.");
|
||||||
|
if (mPreviousEngine != null) {
|
||||||
|
// This is guaranteed to at least bind, since mPreviousEngine would be
|
||||||
|
// null if the previous bind to this engine failed.
|
||||||
|
mTts = new TextToSpeech(mContext, null, mPreviousEngine);
|
||||||
|
updateCheckedState(mPreviousEngine);
|
||||||
|
}
|
||||||
|
mPreviousEngine = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onRadioButtonConfirmed(String selectedKey) {
|
||||||
|
final EngineCandidateInfo info = mEngineMap.get(selectedKey);
|
||||||
|
// Should we alert user? if that's true, delay making engine current one.
|
||||||
|
if (shouldDisplayDataAlert(info)) {
|
||||||
|
displayDataAlert(info, (dialog, which) -> {
|
||||||
|
setDefaultKey(selectedKey);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Privileged engine, set it current
|
||||||
|
setDefaultKey(selectedKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<? extends CandidateInfo> getCandidates() {
|
||||||
|
final List<EngineCandidateInfo> infos = new ArrayList<>();
|
||||||
|
final List<EngineInfo> engines = mEnginesHelper.getEngines();
|
||||||
|
for (EngineInfo engine : engines) {
|
||||||
|
final EngineCandidateInfo info = new EngineCandidateInfo(engine);
|
||||||
|
infos.add(info);
|
||||||
|
mEngineMap.put(engine.name, info);
|
||||||
|
}
|
||||||
|
return infos;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getDefaultKey() {
|
||||||
|
return mEnginesHelper.getDefaultEngine();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean setDefaultKey(String key) {
|
||||||
|
updateDefaultEngine(key);
|
||||||
|
updateCheckedState(key);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getPreferenceScreenResId() {
|
||||||
|
return R.xml.tts_engine_picker;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldDisplayDataAlert(EngineCandidateInfo info) {
|
||||||
|
return !info.isSystem();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void displayDataAlert(EngineCandidateInfo info,
|
||||||
|
DialogInterface.OnClickListener positiveOnClickListener) {
|
||||||
|
Log.i(TAG, "Displaying data alert for :" + info.getKey());
|
||||||
|
|
||||||
|
final AlertDialog dialog = new AlertDialog.Builder(getPrefContext())
|
||||||
|
.setTitle(android.R.string.dialog_alert_title)
|
||||||
|
.setMessage(mContext.getString(
|
||||||
|
R.string.tts_engine_security_warning, info.loadLabel()))
|
||||||
|
.setCancelable(true)
|
||||||
|
.setPositiveButton(android.R.string.ok, positiveOnClickListener)
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.create();
|
||||||
|
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
private void updateDefaultEngine(String engine) {
|
private void updateDefaultEngine(String engine) {
|
||||||
Log.d(TAG, "Updating default synth to : " + engine);
|
Log.d(TAG, "Updating default synth to : " + engine);
|
||||||
|
|
||||||
@@ -146,41 +189,36 @@ public class TtsEnginePreferenceFragment extends SettingsPreferenceFragment
|
|||||||
// Step 3 is continued on #onUpdateEngine (below) which is called when
|
// Step 3 is continued on #onUpdateEngine (below) which is called when
|
||||||
// the app binds successfully to the engine.
|
// the app binds successfully to the engine.
|
||||||
Log.i(TAG, "Updating engine : Attempting to connect to engine: " + engine);
|
Log.i(TAG, "Updating engine : Attempting to connect to engine: " + engine);
|
||||||
mTts = new TextToSpeech(getActivity().getApplicationContext(), mUpdateListener, engine);
|
mTts = new TextToSpeech(mContext, mUpdateListener, engine);
|
||||||
Log.i(TAG, "Success");
|
Log.i(TAG, "Success");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static class EngineCandidateInfo extends CandidateInfo {
|
||||||
* Step 3: We have now bound to the TTS engine the user requested. We will attempt to check
|
private final EngineInfo mEngineInfo;
|
||||||
* voice data for the engine if we successfully bound to it, or revert to the previous engine if
|
|
||||||
* we didn't.
|
|
||||||
*/
|
|
||||||
public void onUpdateEngine(int status) {
|
|
||||||
if (status == TextToSpeech.SUCCESS) {
|
|
||||||
Log.d(
|
|
||||||
|
|
||||||
TAG,
|
EngineCandidateInfo(EngineInfo engineInfo) {
|
||||||
"Updating engine: Successfully bound to the engine: "
|
super(true /* enabled */);
|
||||||
+ mTts.getCurrentEngine());
|
mEngineInfo = engineInfo;
|
||||||
android.provider.Settings.Secure.putString(
|
|
||||||
getContentResolver(), TTS_DEFAULT_SYNTH, mTts.getCurrentEngine());
|
|
||||||
} else {
|
|
||||||
Log.d(TAG, "Updating engine: Failed to bind to engine, reverting.");
|
|
||||||
if (mPreviousEngine != null) {
|
|
||||||
// This is guaranteed to at least bind, since mPreviousEngine would be
|
|
||||||
// null if the previous bind to this engine failed.
|
|
||||||
mTts =
|
|
||||||
new TextToSpeech(
|
|
||||||
getActivity().getApplicationContext(), null, mPreviousEngine);
|
|
||||||
}
|
|
||||||
mPreviousEngine = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setCurrentKey(String key) {
|
public CharSequence loadLabel() {
|
||||||
mCurrentEngine = key;
|
return mEngineInfo.label;
|
||||||
updateDefaultEngine(mCurrentEngine);
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Drawable loadIcon() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getKey() {
|
||||||
|
return mEngineInfo.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSystem() {
|
||||||
|
return mEngineInfo.system;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
|
public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
|
||||||
|
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* 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.settings.testutils.shadow;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.speech.tts.TextToSpeech;
|
||||||
|
import android.speech.tts.TtsEngines;
|
||||||
|
|
||||||
|
import org.robolectric.annotation.Implementation;
|
||||||
|
import org.robolectric.annotation.Implements;
|
||||||
|
import org.robolectric.annotation.Resetter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
@Implements(TtsEngines.class)
|
||||||
|
public class ShadowTtsEngines {
|
||||||
|
private static TtsEngines sInstance;
|
||||||
|
|
||||||
|
public static void setInstance(TtsEngines ttsEngines) {
|
||||||
|
sInstance = ttsEngines;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Resetter
|
||||||
|
public static void reset() {
|
||||||
|
sInstance = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Implementation
|
||||||
|
protected List<TextToSpeech.EngineInfo> getEngines() {
|
||||||
|
return sInstance.getEngines();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Implementation
|
||||||
|
protected TextToSpeech.EngineInfo getEngineInfo(String packageName) {
|
||||||
|
return sInstance.getEngineInfo(packageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Implementation
|
||||||
|
protected String getDefaultEngine() {
|
||||||
|
return sInstance.getDefaultEngine();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Implementation
|
||||||
|
protected Intent getSettingsIntent(String engine) {
|
||||||
|
return sInstance.getSettingsIntent(engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Implementation
|
||||||
|
protected boolean isEngineInstalled(String engine) {
|
||||||
|
return sInstance.isEngineInstalled(engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Implementation
|
||||||
|
protected boolean isLocaleSetToDefaultForEngine(String engineName) {
|
||||||
|
return sInstance.isLocaleSetToDefaultForEngine(engineName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Implementation
|
||||||
|
protected Locale getLocalePrefForEngine(String engineName) {
|
||||||
|
return sInstance.getLocalePrefForEngine(engineName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Implementation
|
||||||
|
protected synchronized void updateLocalePrefForEngine(String engineName, Locale newLocale) {
|
||||||
|
sInstance.updateLocalePrefForEngine(engineName, newLocale);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Implementation
|
||||||
|
protected Locale parseLocaleString(String localeString) {
|
||||||
|
return sInstance.parseLocaleString(localeString);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* 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.settings.tts;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.content.pm.ServiceInfo;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.speech.tts.TextToSpeech;
|
||||||
|
import android.speech.tts.TtsEngines;
|
||||||
|
|
||||||
|
import com.android.settings.testutils.shadow.ShadowTtsEngines;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
import org.robolectric.RuntimeEnvironment;
|
||||||
|
import org.robolectric.annotation.Config;
|
||||||
|
import org.robolectric.shadow.api.Shadow;
|
||||||
|
import org.robolectric.shadows.ShadowPackageManager;
|
||||||
|
import org.robolectric.shadows.androidx.fragment.FragmentController;
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
public class TtsEnginePreferenceFragmentTest {
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
private TtsEnginePreferenceFragment mTtsEnginePreferenceFragment;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
mContext = RuntimeEnvironment.application;
|
||||||
|
|
||||||
|
final ResolveInfo info = new ResolveInfo();
|
||||||
|
final ServiceInfo serviceInfo = spy(new ServiceInfo());
|
||||||
|
serviceInfo.packageName = mContext.getPackageName();
|
||||||
|
serviceInfo.name = mContext.getClass().getName();
|
||||||
|
info.serviceInfo = serviceInfo;
|
||||||
|
doReturn("title").when(serviceInfo).loadLabel(any(PackageManager.class));
|
||||||
|
doReturn(1).when(serviceInfo).getIconResource();
|
||||||
|
|
||||||
|
final ShadowPackageManager spm = Shadow.extract(mContext.getPackageManager());
|
||||||
|
spm.addResolveInfoForIntent(
|
||||||
|
new Intent(TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE), info);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
ShadowTtsEngines.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getCandidates_AddEngines_returnCorrectEngines() {
|
||||||
|
mTtsEnginePreferenceFragment = FragmentController.of(new TtsEnginePreferenceFragment(),
|
||||||
|
new Bundle())
|
||||||
|
.create()
|
||||||
|
.get();
|
||||||
|
|
||||||
|
assertThat(mTtsEnginePreferenceFragment.getCandidates().size()).isEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Config(shadows = {ShadowTtsEngines.class})
|
||||||
|
public void getDefaultKey_validKey_returnCorrectKey() {
|
||||||
|
final String TEST_ENGINE = "test_engine";
|
||||||
|
final TtsEngines engine = mock(TtsEngines.class);
|
||||||
|
ShadowTtsEngines.setInstance(engine);
|
||||||
|
mTtsEnginePreferenceFragment = FragmentController.of(new TtsEnginePreferenceFragment(),
|
||||||
|
new Bundle())
|
||||||
|
.create()
|
||||||
|
.get();
|
||||||
|
when(engine.getDefaultEngine()).thenReturn(TEST_ENGINE);
|
||||||
|
|
||||||
|
assertThat(mTtsEnginePreferenceFragment.getDefaultKey()).isEqualTo(TEST_ENGINE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Config(shadows = {ShadowTtsEngines.class})
|
||||||
|
public void setDefaultKey_validKey_callingTtsEngineFunction() {
|
||||||
|
final TtsEngines engine = mock(TtsEngines.class);
|
||||||
|
ShadowTtsEngines.setInstance(engine);
|
||||||
|
mTtsEnginePreferenceFragment = FragmentController.of(new TtsEnginePreferenceFragment(),
|
||||||
|
new Bundle())
|
||||||
|
.create()
|
||||||
|
.get();
|
||||||
|
|
||||||
|
mTtsEnginePreferenceFragment.setDefaultKey(mContext.getPackageName());
|
||||||
|
|
||||||
|
verify(engine).isEngineInstalled(mContext.getPackageName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setDefaultKey_validKey_updateCheckedState() {
|
||||||
|
mTtsEnginePreferenceFragment = spy(FragmentController.of(new TtsEnginePreferenceFragment(),
|
||||||
|
new Bundle())
|
||||||
|
.create()
|
||||||
|
.get());
|
||||||
|
|
||||||
|
mTtsEnginePreferenceFragment.setDefaultKey(mContext.getPackageName());
|
||||||
|
|
||||||
|
verify(mTtsEnginePreferenceFragment).updateCheckedState(mContext.getPackageName());
|
||||||
|
}
|
||||||
|
}
|
@@ -1,98 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.settings.tts;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.speech.tts.TextToSpeech;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.Checkable;
|
|
||||||
|
|
||||||
import androidx.preference.PreferenceViewHolder;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.robolectric.RobolectricTestRunner;
|
|
||||||
import org.robolectric.RuntimeEnvironment;
|
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
|
||||||
public class TtsEnginePreferenceTest {
|
|
||||||
|
|
||||||
private static final String KEY = "test_key";
|
|
||||||
|
|
||||||
private TtsEnginePreference mPreference;
|
|
||||||
private Context mContext;
|
|
||||||
private PreferenceViewHolder mViewHolder;
|
|
||||||
private View mRootView;
|
|
||||||
private FakeRadioButtonGroupState mState;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
mContext = RuntimeEnvironment.application;
|
|
||||||
|
|
||||||
final TextToSpeech.EngineInfo info = new TextToSpeech.EngineInfo();
|
|
||||||
info.system = true;
|
|
||||||
mState = new FakeRadioButtonGroupState();
|
|
||||||
mPreference = new TtsEnginePreference(mContext, info, mState);
|
|
||||||
mPreference.setKey(KEY);
|
|
||||||
|
|
||||||
// Create preference view holder
|
|
||||||
final LayoutInflater inflater = LayoutInflater.from(mContext);
|
|
||||||
mRootView = View.inflate(mContext, mPreference.getLayoutResource(), null /* parent */);
|
|
||||||
final ViewGroup widgetFrame = mRootView.findViewById(android.R.id.widget_frame);
|
|
||||||
inflater.inflate(mPreference.getWidgetLayoutResource(), widgetFrame);
|
|
||||||
mViewHolder = PreferenceViewHolder.createInstanceForTests(mRootView);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void onClick_shouldInvokeOnCheckedChangeListener() {
|
|
||||||
mPreference.onBindViewHolder(mViewHolder);
|
|
||||||
|
|
||||||
mPreference.onClick();
|
|
||||||
|
|
||||||
assertThat(mState.getCurrentKey()).isEqualTo(mPreference.getKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class FakeRadioButtonGroupState implements
|
|
||||||
TtsEnginePreference.RadioButtonGroupState {
|
|
||||||
|
|
||||||
private String mKey;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Checkable getCurrentChecked() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getCurrentKey() {
|
|
||||||
return mKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setCurrentChecked(Checkable current) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setCurrentKey(String key) {
|
|
||||||
mKey = key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Reference in New Issue
Block a user