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