/* * 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.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.speech.tts.TextToSpeech.EngineInfo; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceViewHolder; import android.util.Log; import android.view.View; import android.widget.Checkable; import android.widget.CompoundButton; import android.widget.RadioButton; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.Utils; public class TtsEnginePreference extends Preference { private static final String TAG = "TtsEnginePreference"; /** * Key for the name of the TTS engine passed in to the engine * settings fragment {@link TtsEngineSettingsFragment}. */ static final String FRAGMENT_ARGS_NAME = "name"; /** * Key for the label of the TTS engine passed in to the engine * settings fragment. This is used as the title of the fragment * {@link TtsEngineSettingsFragment}. */ static final String FRAGMENT_ARGS_LABEL = "label"; /** * Key for the voice data data passed in to the engine settings * fragmetn {@link TtsEngineSettingsFragment}. */ static final String FRAGMENT_ARGS_VOICES = "voices"; /** * The preference activity that owns this preference. Required * for instantiating the engine specific settings screen. */ private final SettingsActivity mSettingsActivity; /** * 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; /** * When true, the change callbacks on the radio button will not * fire. */ private volatile boolean mPreventRadioButtonCallbacks; private View mSettingsIcon; private RadioButton mRadioButton; private Intent mVoiceCheckData; 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, SettingsActivity prefActivity) { super(context); setLayoutResource(R.layout.preference_tts_engine); mSharedState = state; mSettingsActivity = prefActivity; 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 = (RadioButton) view.findViewById(R.id.tts_engine_radiobutton); rb.setOnCheckedChangeListener(mRadioChangeListener); rb.setText(mEngineInfo.label); boolean isChecked = getKey().equals(mSharedState.getCurrentKey()); if (isChecked) { mSharedState.setCurrentChecked(rb); } mPreventRadioButtonCallbacks = true; rb.setChecked(isChecked); mPreventRadioButtonCallbacks = false; mRadioButton = rb; mSettingsIcon = view.findViewById(R.id.tts_engine_settings); // Will be enabled only the engine has passed the voice check, and // is currently enabled. mSettingsIcon.setEnabled(isChecked && mVoiceCheckData != null); if (!isChecked) { mSettingsIcon.setAlpha(Utils.DISABLED_ALPHA); } mSettingsIcon.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Bundle args = new Bundle(); args.putString(FRAGMENT_ARGS_NAME, mEngineInfo.name); args.putString(FRAGMENT_ARGS_LABEL, mEngineInfo.label); if (mVoiceCheckData != null) { args.putParcelable(FRAGMENT_ARGS_VOICES, mVoiceCheckData); } // Note that we use this instead of the (easier to use) // SettingsActivity.startPreferenceFragment because the // title will not be updated correctly in the fragment // breadcrumb since it isn't inflated from the XML layout. mSettingsActivity.startPreferencePanel( TtsEngineSettingsFragment.class.getName(), args, 0, mEngineInfo.label, null, 0); } }); if (mVoiceCheckData != null) { mSettingsIcon.setEnabled(mRadioButton.isChecked()); } } public void setVoiceDataDetails(Intent data) { mVoiceCheckData = data; // This might end up running before getView aboive, in which // case mSettingsIcon && mRadioButton will be null. In this case // getView will set the right values. if (mSettingsIcon != null && mRadioButton != null) { if (mRadioButton.isChecked()) { mSettingsIcon.setEnabled(true); } else { mSettingsIcon.setEnabled(false); mSettingsIcon.setAlpha(Utils.DISABLED_ALPHA); } } } 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); } } else { mSettingsIcon.setEnabled(false); } } private void makeCurrentEngine(Checkable current) { if (mSharedState.getCurrentChecked() != null) { mSharedState.getCurrentChecked().setChecked(false); } mSharedState.setCurrentChecked(current); mSharedState.setCurrentKey(getKey()); callChangeListener(mSharedState.getCurrentKey()); mSettingsIcon.setEnabled(true); } /** * 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); } }