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.inputmethod;
18
19import com.android.settings.R;
20import com.android.settings.SettingsPreferenceFragment;
21import com.android.settings.Utils;
22
23import android.app.AlertDialog;
24import android.app.Fragment;
25import android.content.ActivityNotFoundException;
26import android.content.DialogInterface;
27import android.content.Intent;
28import android.content.res.Configuration;
29import android.os.Bundle;
30import android.preference.CheckBoxPreference;
31import android.preference.Preference;
32import android.preference.PreferenceActivity;
33import android.provider.Settings;
34import android.text.TextUtils;
35import android.util.Log;
36import android.view.View;
37import android.view.View.OnClickListener;
38import android.view.View.OnLongClickListener;
39import android.view.inputmethod.InputMethodInfo;
40import android.view.inputmethod.InputMethodManager;
41import android.view.inputmethod.InputMethodSubtype;
42import android.widget.ImageView;
43import android.widget.TextView;
44import android.widget.Toast;
45
46import java.text.Collator;
47import java.util.Comparator;
48import java.util.List;
49
50public class InputMethodPreference extends CheckBoxPreference {
51    private static final String TAG = InputMethodPreference.class.getSimpleName();
52    private final SettingsPreferenceFragment mFragment;
53    private final InputMethodInfo mImi;
54    private final InputMethodManager mImm;
55    private final Intent mSettingsIntent;
56    private final boolean mAlwaysChecked;
57    private final boolean mIsSystemIme;
58    private final Collator mCollator;
59
60    private AlertDialog mDialog = null;
61    private ImageView mInputMethodSettingsButton;
62    private TextView mTitleText;
63    private TextView mSummaryText;
64    private View mInputMethodPref;
65
66    private final OnClickListener mPrefOnclickListener = new OnClickListener() {
67        @Override
68        public void onClick(View arg0) {
69            if (!isEnabled()) {
70                return;
71            }
72            if (isChecked()) {
73                setChecked(false, true /* save */);
74            } else {
75                if (mIsSystemIme) {
76                    setChecked(true, true /* save */);
77                } else {
78                    showSecurityWarnDialog(mImi, InputMethodPreference.this);
79                }
80            }
81        }
82    };
83
84    public InputMethodPreference(SettingsPreferenceFragment fragment, Intent settingsIntent,
85            InputMethodManager imm, InputMethodInfo imi, int imiCount) {
86        super(fragment.getActivity(), null, R.style.InputMethodPreferenceStyle);
87        setLayoutResource(R.layout.preference_inputmethod);
88        setWidgetLayoutResource(R.layout.preference_inputmethod_widget);
89        mFragment = fragment;
90        mSettingsIntent = settingsIntent;
91        mImm = imm;
92        mImi = imi;
93        updateSummary();
94        mAlwaysChecked = InputMethodAndSubtypeUtil.isAlwaysCheckedIme(
95                imi, fragment.getActivity(), imiCount);
96        mIsSystemIme = InputMethodAndSubtypeUtil.isSystemIme(imi);
97        if (mAlwaysChecked) {
98            setEnabled(false);
99        }
100        mCollator = Collator.getInstance(fragment.getResources().getConfiguration().locale);
101    }
102
103    @Override
104    protected void onBindView(View view) {
105        super.onBindView(view);
106        mInputMethodPref = view.findViewById(R.id.inputmethod_pref);
107        mInputMethodPref.setOnClickListener(mPrefOnclickListener);
108        mInputMethodSettingsButton = (ImageView)view.findViewById(R.id.inputmethod_settings);
109        mTitleText = (TextView)view.findViewById(android.R.id.title);
110        mSummaryText = (TextView)view.findViewById(android.R.id.summary);
111        final boolean hasSubtypes = mImi.getSubtypeCount() > 1;
112        final String imiId = mImi.getId();
113        if (hasSubtypes) {
114            mInputMethodPref.setOnLongClickListener(new OnLongClickListener() {
115                @Override
116                public boolean onLongClick(View arg0) {
117                    final Bundle bundle = new Bundle();
118                    bundle.putString(Settings.EXTRA_INPUT_METHOD_ID, imiId);
119                    startFragment(mFragment, InputMethodAndSubtypeEnabler.class.getName(),
120                            0, bundle);
121                    return true;
122                }
123            });
124        }
125
126        if (mSettingsIntent != null) {
127            mInputMethodSettingsButton.setOnClickListener(
128                    new OnClickListener() {
129                        @Override
130                        public void onClick(View arg0) {
131                            try {
132                                mFragment.startActivity(mSettingsIntent);
133                            } catch (ActivityNotFoundException e) {
134                                Log.d(TAG, "IME's Settings Activity Not Found: " + e);
135                                final String msg = mFragment.getString(
136                                        R.string.failed_to_open_app_settings_toast,
137                                        mImi.loadLabel(
138                                                mFragment.getActivity().getPackageManager()));
139                                Toast.makeText(
140                                        mFragment.getActivity(), msg, Toast.LENGTH_LONG).show();
141                            }
142                        }
143                    });
144        }
145        if (hasSubtypes) {
146            final OnLongClickListener listener = new OnLongClickListener() {
147                @Override
148                public boolean onLongClick(View arg0) {
149                    final Bundle bundle = new Bundle();
150                    bundle.putString(Settings.EXTRA_INPUT_METHOD_ID, imiId);
151                    startFragment(mFragment, InputMethodAndSubtypeEnabler.class.getName(),
152                            0, bundle);
153                    return true;
154                }
155            };
156            mInputMethodSettingsButton.setOnLongClickListener(listener);
157        }
158        if (mSettingsIntent == null) {
159            mInputMethodSettingsButton.setVisibility(View.GONE);
160        } else {
161            updatePreferenceViews();
162        }
163    }
164
165    @Override
166    public void setEnabled(boolean enabled) {
167        super.setEnabled(enabled);
168        updatePreferenceViews();
169    }
170
171    private void updatePreferenceViews() {
172        final boolean checked = isChecked();
173        if (mInputMethodSettingsButton != null) {
174            mInputMethodSettingsButton.setEnabled(checked);
175            mInputMethodSettingsButton.setClickable(checked);
176            mInputMethodSettingsButton.setFocusable(checked);
177            if (!checked) {
178                mInputMethodSettingsButton.setAlpha(Utils.DISABLED_ALPHA);
179            }
180        }
181        if (mTitleText != null) {
182            mTitleText.setEnabled(true);
183        }
184        if (mSummaryText != null) {
185            mSummaryText.setEnabled(checked);
186        }
187        if (mInputMethodPref != null) {
188            mInputMethodPref.setEnabled(true);
189            mInputMethodPref.setLongClickable(checked);
190            final boolean enabled = isEnabled();
191            mInputMethodPref.setOnClickListener(enabled ? mPrefOnclickListener : null);
192            if (!enabled) {
193                mInputMethodPref.setBackgroundColor(0);
194            }
195        }
196    }
197
198    public static boolean startFragment(
199            Fragment fragment, String fragmentClass, int requestCode, Bundle extras) {
200        if (fragment.getActivity() instanceof PreferenceActivity) {
201            PreferenceActivity preferenceActivity = (PreferenceActivity)fragment.getActivity();
202            preferenceActivity.startPreferencePanel(fragmentClass, extras, 0, null, fragment,
203                    requestCode);
204            return true;
205        } else {
206            Log.w(TAG, "Parent isn't PreferenceActivity, thus there's no way to launch the "
207                    + "given Fragment (name: " + fragmentClass + ", requestCode: " + requestCode
208                    + ")");
209            return false;
210        }
211    }
212
213    public String getSummaryString() {
214        final StringBuilder builder = new StringBuilder();
215        final List<InputMethodSubtype> subtypes = mImm.getEnabledInputMethodSubtypeList(mImi, true);
216        for (InputMethodSubtype subtype : subtypes) {
217            if (builder.length() > 0) {
218                builder.append(", ");
219            }
220            final CharSequence subtypeLabel = subtype.getDisplayName(mFragment.getActivity(),
221                    mImi.getPackageName(), mImi.getServiceInfo().applicationInfo);
222            builder.append(subtypeLabel);
223        }
224        return builder.toString();
225    }
226
227    public void updateSummary() {
228        final String summary = getSummaryString();
229        if (TextUtils.isEmpty(summary)) {
230            return;
231        }
232        setSummary(summary);
233    }
234
235    /**
236     * Sets the checkbox state and optionally saves the settings.
237     * @param checked whether to check the box
238     * @param save whether to save IME settings
239     */
240    public void setChecked(boolean checked, boolean save) {
241        super.setChecked(checked);
242        if (save) {
243            saveImeSettings();
244        }
245        updateSummary();
246    }
247
248    @Override
249    public void setChecked(boolean checked) {
250        setChecked(checked, false);
251    }
252
253    private void showSecurityWarnDialog(InputMethodInfo imi, final InputMethodPreference chkPref) {
254        if (mDialog != null && mDialog.isShowing()) {
255            mDialog.dismiss();
256        }
257        mDialog = (new AlertDialog.Builder(mFragment.getActivity()))
258                .setTitle(android.R.string.dialog_alert_title)
259                .setIconAttribute(android.R.attr.alertDialogIcon)
260                .setCancelable(true)
261                .setPositiveButton(android.R.string.ok,
262                        new DialogInterface.OnClickListener() {
263                    @Override
264                    public void onClick(DialogInterface dialog, int which) {
265                        chkPref.setChecked(true, true);
266                    }
267                })
268                .setNegativeButton(android.R.string.cancel,
269                        new DialogInterface.OnClickListener() {
270                    @Override
271                    public void onClick(DialogInterface dialog, int which) {
272                    }
273                })
274                .create();
275        mDialog.setMessage(mFragment.getResources().getString(R.string.ime_security_warning,
276                imi.getServiceInfo().applicationInfo.loadLabel(
277                        mFragment.getActivity().getPackageManager())));
278        mDialog.show();
279    }
280
281    @Override
282    public int compareTo(Preference p) {
283        if (!(p instanceof InputMethodPreference)) {
284            return super.compareTo(p);
285        }
286        final InputMethodPreference imp = (InputMethodPreference) p;
287        final boolean priority0 = mIsSystemIme && mAlwaysChecked;
288        final boolean priority1 = imp.mIsSystemIme && imp.mAlwaysChecked;
289        if (priority0 == priority1) {
290            final CharSequence t0 = getTitle();
291            final CharSequence t1 = imp.getTitle();
292            if (TextUtils.isEmpty(t0)) {
293                return 1;
294            }
295            if (TextUtils.isEmpty(t1)) {
296                return -1;
297            }
298            return mCollator.compare(t0.toString(), t1.toString());
299        }
300        // Prefer always checked system IMEs
301        return priority0 ? -1 : 1;
302    }
303
304    private void saveImeSettings() {
305        InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(
306                mFragment, mFragment.getActivity().getContentResolver(), mImm.getInputMethodList(),
307                mFragment.getResources().getConfiguration().keyboard
308                        == Configuration.KEYBOARD_QWERTY);
309    }
310}
311