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
17package com.android.settings.tts;
18
19import android.app.AlertDialog;
20import android.content.Context;
21import android.content.DialogInterface;
22import android.content.Intent;
23import android.os.Bundle;
24import android.preference.Preference;
25import android.speech.tts.TextToSpeech.EngineInfo;
26import android.util.Log;
27import android.view.View;
28import android.view.ViewGroup;
29import android.widget.Checkable;
30import android.widget.CompoundButton;
31import android.widget.RadioButton;
32
33
34import com.android.settings.R;
35import com.android.settings.SettingsActivity;
36import com.android.settings.Utils;
37
38
39public 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