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.inputmethod; 18 19 import com.android.internal.inputmethod.InputMethodUtils; 20 import com.android.settings.R; 21 import com.android.settings.SettingsPreferenceFragment; 22 import com.android.settings.Utils; 23 24 import android.app.AlertDialog; 25 import android.app.Fragment; 26 import android.content.ActivityNotFoundException; 27 import android.content.Context; 28 import android.content.DialogInterface; 29 import android.content.Intent; 30 import android.content.res.Configuration; 31 import android.os.Bundle; 32 import android.preference.CheckBoxPreference; 33 import android.preference.Preference; 34 import android.preference.PreferenceActivity; 35 import android.provider.Settings; 36 import android.text.TextUtils; 37 import android.util.Log; 38 import android.view.View; 39 import android.view.View.OnClickListener; 40 import android.view.View.OnLongClickListener; 41 import android.view.inputmethod.InputMethodInfo; 42 import android.view.inputmethod.InputMethodManager; 43 import android.view.inputmethod.InputMethodSubtype; 44 import android.widget.ImageView; 45 import android.widget.TextView; 46 import android.widget.Toast; 47 48 import java.text.Collator; 49 import java.util.List; 50 51 public class InputMethodPreference extends CheckBoxPreference { 52 private static final String TAG = InputMethodPreference.class.getSimpleName(); 53 private final SettingsPreferenceFragment mFragment; 54 private final InputMethodInfo mImi; 55 private final InputMethodManager mImm; 56 private final boolean mIsValidSystemNonAuxAsciiCapableIme; 57 private final Intent mSettingsIntent; 58 private final boolean mIsSystemIme; 59 private final Collator mCollator; 60 61 private AlertDialog mDialog = null; 62 private ImageView mInputMethodSettingsButton; 63 private TextView mTitleText; 64 private TextView mSummaryText; 65 private View mInputMethodPref; 66 private OnPreferenceChangeListener mOnImePreferenceChangeListener; 67 68 private final OnClickListener mPrefOnclickListener = new OnClickListener() { 69 @Override 70 public void onClick(View arg0) { 71 if (!isEnabled()) { 72 return; 73 } 74 if (isChecked()) { 75 setChecked(false, true /* save */); 76 } else { 77 if (mIsSystemIme) { 78 setChecked(true, true /* save */); 79 } else { 80 showSecurityWarnDialog(mImi, InputMethodPreference.this); 81 } 82 } 83 } 84 }; 85 86 public InputMethodPreference(SettingsPreferenceFragment fragment, Intent settingsIntent, 87 InputMethodManager imm, InputMethodInfo imi) { 88 super(fragment.getActivity(), null, R.style.InputMethodPreferenceStyle); 89 setLayoutResource(R.layout.preference_inputmethod); 90 setWidgetLayoutResource(R.layout.preference_inputmethod_widget); 91 mFragment = fragment; 92 mSettingsIntent = settingsIntent; 93 mImm = imm; 94 mImi = imi; 95 mIsSystemIme = InputMethodUtils.isSystemIme(imi); 96 mCollator = Collator.getInstance(fragment.getResources().getConfiguration().locale); 97 final Context context = fragment.getActivity(); 98 mIsValidSystemNonAuxAsciiCapableIme = InputMethodSettingValuesWrapper 99 .getInstance(context).isValidSystemNonAuxAsciiCapableIme(imi, context); 100 updatePreferenceViews(); 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 } 161 updatePreferenceViews(); 162 } 163 164 @Override 165 public void setEnabled(boolean enabled) { 166 super.setEnabled(enabled); 167 updatePreferenceViews(); 168 } 169 170 public void updatePreferenceViews() { 171 final boolean isAlwaysChecked = 172 InputMethodSettingValuesWrapper.getInstance(getContext()).isAlwaysCheckedIme( 173 mImi, getContext()); 174 if (isAlwaysChecked) { 175 super.setChecked(true); 176 super.setEnabled(false); 177 } else { 178 super.setEnabled(true); 179 } 180 final boolean checked = isChecked(); 181 if (mInputMethodSettingsButton != null) { 182 mInputMethodSettingsButton.setEnabled(checked); 183 mInputMethodSettingsButton.setClickable(checked); 184 mInputMethodSettingsButton.setFocusable(checked); 185 if (!checked) { 186 mInputMethodSettingsButton.setAlpha(Utils.DISABLED_ALPHA); 187 } 188 } 189 if (mTitleText != null) { 190 mTitleText.setEnabled(true); 191 } 192 if (mSummaryText != null) { 193 mSummaryText.setEnabled(checked); 194 } 195 if (mInputMethodPref != null) { 196 mInputMethodPref.setEnabled(true); 197 mInputMethodPref.setLongClickable(checked); 198 final boolean enabled = isEnabled(); 199 mInputMethodPref.setOnClickListener(enabled ? mPrefOnclickListener : null); 200 if (!enabled) { 201 mInputMethodPref.setBackgroundColor(0); 202 } 203 } 204 updateSummary(); 205 } 206 207 public static boolean startFragment( 208 Fragment fragment, String fragmentClass, int requestCode, Bundle extras) { 209 if (fragment.getActivity() instanceof PreferenceActivity) { 210 PreferenceActivity preferenceActivity = (PreferenceActivity)fragment.getActivity(); 211 preferenceActivity.startPreferencePanel(fragmentClass, extras, 0, null, fragment, 212 requestCode); 213 return true; 214 } else { 215 Log.w(TAG, "Parent isn't PreferenceActivity, thus there's no way to launch the " 216 + "given Fragment (name: " + fragmentClass + ", requestCode: " + requestCode 217 + ")"); 218 return false; 219 } 220 } 221 222 private String getSummaryString() { 223 final StringBuilder builder = new StringBuilder(); 224 final List<InputMethodSubtype> subtypes = mImm.getEnabledInputMethodSubtypeList(mImi, true); 225 for (InputMethodSubtype subtype : subtypes) { 226 if (builder.length() > 0) { 227 builder.append(", "); 228 } 229 final CharSequence subtypeLabel = subtype.getDisplayName(mFragment.getActivity(), 230 mImi.getPackageName(), mImi.getServiceInfo().applicationInfo); 231 builder.append(subtypeLabel); 232 } 233 return builder.toString(); 234 } 235 236 private void updateSummary() { 237 final String summary = getSummaryString(); 238 if (TextUtils.isEmpty(summary)) { 239 return; 240 } 241 setSummary(summary); 242 } 243 244 /** 245 * Sets the checkbox state and optionally saves the settings. 246 * @param checked whether to check the box 247 * @param save whether to save IME settings 248 */ 249 private void setChecked(boolean checked, boolean save) { 250 final boolean wasChecked = isChecked(); 251 super.setChecked(checked); 252 if (save) { 253 saveImeSettings(); 254 if (wasChecked != checked && mOnImePreferenceChangeListener != null) { 255 mOnImePreferenceChangeListener.onPreferenceChange(this, checked); 256 } 257 } 258 } 259 260 public void setOnImePreferenceChangeListener(OnPreferenceChangeListener listener) { 261 mOnImePreferenceChangeListener = listener; 262 } 263 264 private void showSecurityWarnDialog(InputMethodInfo imi, final InputMethodPreference chkPref) { 265 if (mDialog != null && mDialog.isShowing()) { 266 mDialog.dismiss(); 267 } 268 mDialog = (new AlertDialog.Builder(mFragment.getActivity())) 269 .setTitle(android.R.string.dialog_alert_title) 270 .setIconAttribute(android.R.attr.alertDialogIcon) 271 .setCancelable(true) 272 .setPositiveButton(android.R.string.ok, 273 new DialogInterface.OnClickListener() { 274 @Override 275 public void onClick(DialogInterface dialog, int which) { 276 chkPref.setChecked(true, true); 277 } 278 }) 279 .setNegativeButton(android.R.string.cancel, 280 new DialogInterface.OnClickListener() { 281 @Override 282 public void onClick(DialogInterface dialog, int which) { 283 } 284 }) 285 .create(); 286 mDialog.setMessage(mFragment.getResources().getString(R.string.ime_security_warning, 287 imi.getServiceInfo().applicationInfo.loadLabel( 288 mFragment.getActivity().getPackageManager()))); 289 mDialog.show(); 290 } 291 292 @Override 293 public int compareTo(Preference p) { 294 if (!(p instanceof InputMethodPreference)) { 295 return super.compareTo(p); 296 } 297 final InputMethodPreference imp = (InputMethodPreference) p; 298 final boolean priority0 = mIsSystemIme && mIsValidSystemNonAuxAsciiCapableIme; 299 final boolean priority1 = imp.mIsSystemIme && imp.mIsValidSystemNonAuxAsciiCapableIme; 300 if (priority0 == priority1) { 301 final CharSequence t0 = getTitle(); 302 final CharSequence t1 = imp.getTitle(); 303 if (TextUtils.isEmpty(t0)) { 304 return 1; 305 } 306 if (TextUtils.isEmpty(t1)) { 307 return -1; 308 } 309 return mCollator.compare(t0.toString(), t1.toString()); 310 } 311 // Prefer always checked system IMEs 312 return priority0 ? -1 : 1; 313 } 314 315 private void saveImeSettings() { 316 InputMethodAndSubtypeUtil.saveInputMethodSubtypeList( 317 mFragment, mFragment.getActivity().getContentResolver(), mImm.getInputMethodList(), 318 mFragment.getResources().getConfiguration().keyboard 319 == Configuration.KEYBOARD_QWERTY); 320 } 321 } 322