1 /* 2 * Copyright (C) 2011 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.settings.tts; 18 19 import android.app.AlertDialog; 20 import android.content.Context; 21 import android.content.DialogInterface; 22 import android.content.Intent; 23 import android.os.Bundle; 24 import android.speech.tts.TextToSpeech.EngineInfo; 25 import android.support.v7.preference.Preference; 26 import android.support.v7.preference.PreferenceViewHolder; 27 import android.util.Log; 28 import android.view.View; 29 import android.widget.Checkable; 30 import android.widget.CompoundButton; 31 import android.widget.RadioButton; 32 33 import com.android.settings.R; 34 import com.android.settings.SettingsActivity; 35 import com.android.settings.Utils; 36 37 38 public class TtsEnginePreference extends Preference { 39 40 private static final String TAG = "TtsEnginePreference"; 41 42 /** 43 * Key for the name of the TTS engine passed in to the engine 44 * settings fragment {@link TtsEngineSettingsFragment}. 45 */ 46 static final String FRAGMENT_ARGS_NAME = "name"; 47 48 /** 49 * Key for the label of the TTS engine passed in to the engine 50 * settings fragment. This is used as the title of the fragment 51 * {@link TtsEngineSettingsFragment}. 52 */ 53 static final String FRAGMENT_ARGS_LABEL = "label"; 54 55 /** 56 * Key for the voice data data passed in to the engine settings 57 * fragmetn {@link TtsEngineSettingsFragment}. 58 */ 59 static final String FRAGMENT_ARGS_VOICES = "voices"; 60 61 /** 62 * The preference activity that owns this preference. Required 63 * for instantiating the engine specific settings screen. 64 */ 65 private final SettingsActivity mSettingsActivity; 66 67 /** 68 * The engine information for the engine this preference represents. 69 * Contains it's name, label etc. which are used for display. 70 */ 71 private final EngineInfo mEngineInfo; 72 73 /** 74 * The shared radio button state, which button is checked etc. 75 */ 76 private final RadioButtonGroupState mSharedState; 77 78 /** 79 * When true, the change callbacks on the radio button will not 80 * fire. 81 */ 82 private volatile boolean mPreventRadioButtonCallbacks; 83 84 private View mSettingsIcon; 85 private RadioButton mRadioButton; 86 private Intent mVoiceCheckData; 87 88 private final CompoundButton.OnCheckedChangeListener mRadioChangeListener = 89 new CompoundButton.OnCheckedChangeListener() { 90 @Override 91 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 92 onRadioButtonClicked(buttonView, isChecked); 93 } 94 }; 95 96 public TtsEnginePreference(Context context, EngineInfo info, RadioButtonGroupState state, 97 SettingsActivity prefActivity) { 98 super(context); 99 setLayoutResource(R.layout.preference_tts_engine); 100 101 mSharedState = state; 102 mSettingsActivity = prefActivity; 103 mEngineInfo = info; 104 mPreventRadioButtonCallbacks = false; 105 106 setKey(mEngineInfo.name); 107 setTitle(mEngineInfo.label); 108 } 109 110 @Override 111 public void onBindViewHolder(PreferenceViewHolder view) { 112 super.onBindViewHolder(view); 113 114 if (mSharedState == null) { 115 throw new IllegalStateException("Call to getView() before a call to" + 116 "setSharedState()"); 117 } 118 119 final RadioButton rb = (RadioButton) view.findViewById(R.id.tts_engine_radiobutton); 120 rb.setOnCheckedChangeListener(mRadioChangeListener); 121 rb.setText(mEngineInfo.label); 122 123 boolean isChecked = getKey().equals(mSharedState.getCurrentKey()); 124 if (isChecked) { 125 mSharedState.setCurrentChecked(rb); 126 } 127 128 mPreventRadioButtonCallbacks = true; 129 rb.setChecked(isChecked); 130 mPreventRadioButtonCallbacks = false; 131 132 mRadioButton = rb; 133 134 mSettingsIcon = view.findViewById(R.id.tts_engine_settings); 135 // Will be enabled only the engine has passed the voice check, and 136 // is currently enabled. 137 mSettingsIcon.setEnabled(isChecked && mVoiceCheckData != null); 138 if (!isChecked) { 139 mSettingsIcon.setAlpha(Utils.DISABLED_ALPHA); 140 } 141 mSettingsIcon.setOnClickListener(new View.OnClickListener() { 142 @Override 143 public void onClick(View v) { 144 Bundle args = new Bundle(); 145 args.putString(FRAGMENT_ARGS_NAME, mEngineInfo.name); 146 args.putString(FRAGMENT_ARGS_LABEL, mEngineInfo.label); 147 if (mVoiceCheckData != null) { 148 args.putParcelable(FRAGMENT_ARGS_VOICES, mVoiceCheckData); 149 } 150 151 // Note that we use this instead of the (easier to use) 152 // SettingsActivity.startPreferenceFragment because the 153 // title will not be updated correctly in the fragment 154 // breadcrumb since it isn't inflated from the XML layout. 155 mSettingsActivity.startPreferencePanel( 156 TtsEngineSettingsFragment.class.getName(), 157 args, 0, mEngineInfo.label, null, 0); 158 } 159 }); 160 161 if (mVoiceCheckData != null) { 162 mSettingsIcon.setEnabled(mRadioButton.isChecked()); 163 } 164 } 165 166 public void setVoiceDataDetails(Intent data) { 167 mVoiceCheckData = data; 168 // This might end up running before getView aboive, in which 169 // case mSettingsIcon && mRadioButton will be null. In this case 170 // getView will set the right values. 171 if (mSettingsIcon != null && mRadioButton != null) { 172 if (mRadioButton.isChecked()) { 173 mSettingsIcon.setEnabled(true); 174 } else { 175 mSettingsIcon.setEnabled(false); 176 mSettingsIcon.setAlpha(Utils.DISABLED_ALPHA); 177 } 178 } 179 } 180 181 private boolean shouldDisplayDataAlert() { 182 return !mEngineInfo.system; 183 } 184 185 186 private void displayDataAlert( 187 DialogInterface.OnClickListener positiveOnClickListener, 188 DialogInterface.OnClickListener negativeOnClickListener) { 189 Log.i(TAG, "Displaying data alert for :" + mEngineInfo.name); 190 191 AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); 192 builder.setTitle(android.R.string.dialog_alert_title) 193 .setMessage(getContext().getString( 194 R.string.tts_engine_security_warning, mEngineInfo.label)) 195 .setCancelable(true) 196 .setPositiveButton(android.R.string.ok, positiveOnClickListener) 197 .setNegativeButton(android.R.string.cancel, negativeOnClickListener); 198 199 AlertDialog dialog = builder.create(); 200 dialog.show(); 201 } 202 203 204 private void onRadioButtonClicked(final CompoundButton buttonView, 205 boolean isChecked) { 206 if (mPreventRadioButtonCallbacks || 207 (mSharedState.getCurrentChecked() == buttonView)) { 208 return; 209 } 210 211 if (isChecked) { 212 // Should we alert user? if that's true, delay making engine current one. 213 if (shouldDisplayDataAlert()) { 214 displayDataAlert(new DialogInterface.OnClickListener() { 215 @Override 216 public void onClick(DialogInterface dialog, int which) { 217 makeCurrentEngine(buttonView); 218 } 219 },new DialogInterface.OnClickListener() { 220 @Override 221 public void onClick(DialogInterface dialog, int which) { 222 // Undo the click. 223 buttonView.setChecked(false); 224 } 225 }); 226 } else { 227 // Privileged engine, set it current 228 makeCurrentEngine(buttonView); 229 } 230 } else { 231 mSettingsIcon.setEnabled(false); 232 } 233 } 234 235 private void makeCurrentEngine(Checkable current) { 236 if (mSharedState.getCurrentChecked() != null) { 237 mSharedState.getCurrentChecked().setChecked(false); 238 } 239 mSharedState.setCurrentChecked(current); 240 mSharedState.setCurrentKey(getKey()); 241 callChangeListener(mSharedState.getCurrentKey()); 242 mSettingsIcon.setEnabled(true); 243 } 244 245 246 /** 247 * Holds all state that is common to this group of radio buttons, such 248 * as the currently selected key and the currently checked compound button. 249 * (which corresponds to this key). 250 */ 251 public interface RadioButtonGroupState { 252 String getCurrentKey(); 253 Checkable getCurrentChecked(); 254 255 void setCurrentKey(String key); 256 void setCurrentChecked(Checkable current); 257 } 258 259 } 260