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 android.app.AlertDialog; 20 import android.content.ActivityNotFoundException; 21 import android.content.Context; 22 import android.content.DialogInterface; 23 import android.content.Intent; 24 import android.preference.Preference; 25 import android.preference.Preference.OnPreferenceChangeListener; 26 import android.preference.Preference.OnPreferenceClickListener; 27 import android.preference.SwitchPreference; 28 import android.text.TextUtils; 29 import android.util.Log; 30 import android.view.inputmethod.InputMethodInfo; 31 import android.view.inputmethod.InputMethodManager; 32 import android.view.inputmethod.InputMethodSubtype; 33 import android.widget.Toast; 34 35 import com.android.internal.inputmethod.InputMethodUtils; 36 import com.android.settings.R; 37 38 import java.text.Collator; 39 import java.util.ArrayList; 40 import java.util.List; 41 42 /** 43 * Input method preference. 44 * 45 * This preference represents an IME. It is used for two purposes. 1) An instance with a switch 46 * is used to enable or disable the IME. 2) An instance without a switch is used to invoke the 47 * setting activity of the IME. 48 */ 49 class InputMethodPreference extends SwitchPreference implements OnPreferenceClickListener, 50 OnPreferenceChangeListener { 51 private static final String TAG = InputMethodPreference.class.getSimpleName(); 52 private static final String EMPTY_TEXT = ""; 53 54 interface OnSavePreferenceListener { 55 /** 56 * Called when this preference needs to be saved its state. 57 * 58 * Note that this preference is non-persistent and needs explicitly to be saved its state. 59 * Because changing one IME state may change other IMEs' state, this is a place to update 60 * other IMEs' state as well. 61 * 62 * @param pref This preference. 63 */ onSaveInputMethodPreference(InputMethodPreference pref)64 public void onSaveInputMethodPreference(InputMethodPreference pref); 65 } 66 67 private final InputMethodInfo mImi; 68 private final boolean mHasPriorityInSorting; 69 private final OnSavePreferenceListener mOnSaveListener; 70 private final InputMethodSettingValuesWrapper mInputMethodSettingValues; 71 private final boolean mIsAllowedByOrganization; 72 73 private AlertDialog mDialog = null; 74 75 /** 76 * A preference entry of an input method. 77 * 78 * @param context The Context this is associated with. 79 * @param imi The {@link InputMethodInfo} of this preference. 80 * @param isImeEnabler true if this preference is the IME enabler that has enable/disable 81 * switches for all available IMEs, not the list of enabled IMEs. 82 * @param isAllowedByOrganization false if the IME has been disabled by a device or profile 83 owner. 84 * @param onSaveListener The listener called when this preference has been changed and needs 85 * to save the state to shared preference. 86 */ InputMethodPreference(final Context context, final InputMethodInfo imi, final boolean isImeEnabler, final boolean isAllowedByOrganization, final OnSavePreferenceListener onSaveListener)87 InputMethodPreference(final Context context, final InputMethodInfo imi, 88 final boolean isImeEnabler, final boolean isAllowedByOrganization, 89 final OnSavePreferenceListener onSaveListener) { 90 super(context); 91 setPersistent(false); 92 mImi = imi; 93 mIsAllowedByOrganization = isAllowedByOrganization; 94 mOnSaveListener = onSaveListener; 95 if (!isImeEnabler) { 96 // Hide switch widget. 97 setWidgetLayoutResource(0 /* widgetLayoutResId */); 98 } 99 // Disable on/off switch texts. 100 setSwitchTextOn(EMPTY_TEXT); 101 setSwitchTextOff(EMPTY_TEXT); 102 setKey(imi.getId()); 103 setTitle(imi.loadLabel(context.getPackageManager())); 104 final String settingsActivity = imi.getSettingsActivity(); 105 if (TextUtils.isEmpty(settingsActivity)) { 106 setIntent(null); 107 } else { 108 // Set an intent to invoke settings activity of an input method. 109 final Intent intent = new Intent(Intent.ACTION_MAIN); 110 intent.setClassName(imi.getPackageName(), settingsActivity); 111 setIntent(intent); 112 } 113 mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(context); 114 mHasPriorityInSorting = InputMethodUtils.isSystemIme(imi) 115 && mInputMethodSettingValues.isValidSystemNonAuxAsciiCapableIme(imi, context); 116 setOnPreferenceClickListener(this); 117 setOnPreferenceChangeListener(this); 118 } 119 getInputMethodInfo()120 public InputMethodInfo getInputMethodInfo() { 121 return mImi; 122 } 123 isImeEnabler()124 private boolean isImeEnabler() { 125 // If this {@link SwitchPreference} doesn't have a widget layout, we explicitly hide the 126 // switch widget at constructor. 127 return getWidgetLayoutResource() != 0; 128 } 129 130 @Override onPreferenceChange(final Preference preference, final Object newValue)131 public boolean onPreferenceChange(final Preference preference, final Object newValue) { 132 // Always returns false to prevent default behavior. 133 // See {@link TwoStatePreference#onClick()}. 134 if (!isImeEnabler()) { 135 // Prevent disabling an IME because this preference is for invoking a settings activity. 136 return false; 137 } 138 if (isChecked()) { 139 // Disable this IME. 140 setChecked(false); 141 mOnSaveListener.onSaveInputMethodPreference(this); 142 return false; 143 } 144 if (InputMethodUtils.isSystemIme(mImi)) { 145 // Enable a system IME. No need to show a security warning dialog. 146 setChecked(true); 147 mOnSaveListener.onSaveInputMethodPreference(this); 148 return false; 149 } 150 // Enable a 3rd party IME. 151 showSecurityWarnDialog(mImi); 152 return false; 153 } 154 155 @Override onPreferenceClick(final Preference preference)156 public boolean onPreferenceClick(final Preference preference) { 157 // Always returns true to prevent invoking an intent without catching exceptions. 158 // See {@link Preference#performClick(PreferenceScreen)}/ 159 if (isImeEnabler()) { 160 // Prevent invoking a settings activity because this preference is for enabling and 161 // disabling an input method. 162 return true; 163 } 164 final Context context = getContext(); 165 try { 166 final Intent intent = getIntent(); 167 if (intent != null) { 168 // Invoke a settings activity of an input method. 169 context.startActivity(intent); 170 } 171 } catch (final ActivityNotFoundException e) { 172 Log.d(TAG, "IME's Settings Activity Not Found", e); 173 final String message = context.getString( 174 R.string.failed_to_open_app_settings_toast, 175 mImi.loadLabel(context.getPackageManager())); 176 Toast.makeText(context, message, Toast.LENGTH_LONG).show(); 177 } 178 return true; 179 } 180 updatePreferenceViews()181 void updatePreferenceViews() { 182 final boolean isAlwaysChecked = mInputMethodSettingValues.isAlwaysCheckedIme( 183 mImi, getContext()); 184 // Only when this preference has a switch and an input method should be always enabled, 185 // this preference should be disabled to prevent accidentally disabling an input method. 186 setEnabled(!((isAlwaysChecked && isImeEnabler()) || (!mIsAllowedByOrganization))); 187 setChecked(mInputMethodSettingValues.isEnabledImi(mImi)); 188 setSummary(getSummaryString()); 189 } 190 getInputMethodManager()191 private InputMethodManager getInputMethodManager() { 192 return (InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 193 } 194 getSummaryString()195 private String getSummaryString() { 196 final Context context = getContext(); 197 if (!mIsAllowedByOrganization) { 198 return context.getString(R.string.accessibility_feature_or_input_method_not_allowed); 199 } 200 final InputMethodManager imm = getInputMethodManager(); 201 final List<InputMethodSubtype> subtypes = imm.getEnabledInputMethodSubtypeList(mImi, true); 202 final ArrayList<CharSequence> subtypeLabels = new ArrayList<>(); 203 for (final InputMethodSubtype subtype : subtypes) { 204 final CharSequence label = subtype.getDisplayName( 205 context, mImi.getPackageName(), mImi.getServiceInfo().applicationInfo); 206 subtypeLabels.add(label); 207 } 208 // TODO: A delimiter of subtype labels should be localized. 209 return TextUtils.join(", ", subtypeLabels); 210 } 211 showSecurityWarnDialog(final InputMethodInfo imi)212 private void showSecurityWarnDialog(final InputMethodInfo imi) { 213 if (mDialog != null && mDialog.isShowing()) { 214 mDialog.dismiss(); 215 } 216 final Context context = getContext(); 217 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 218 builder.setCancelable(true /* cancelable */); 219 builder.setTitle(android.R.string.dialog_alert_title); 220 final CharSequence label = imi.getServiceInfo().applicationInfo.loadLabel( 221 context.getPackageManager()); 222 builder.setMessage(context.getString(R.string.ime_security_warning, label)); 223 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 224 @Override 225 public void onClick(final DialogInterface dialog, final int which) { 226 // The user confirmed to enable a 3rd party IME. 227 setChecked(true); 228 mOnSaveListener.onSaveInputMethodPreference(InputMethodPreference.this); 229 notifyChanged(); 230 } 231 }); 232 builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { 233 @Override 234 public void onClick(final DialogInterface dialog, final int which) { 235 // The user canceled to enable a 3rd party IME. 236 setChecked(false); 237 mOnSaveListener.onSaveInputMethodPreference(InputMethodPreference.this); 238 notifyChanged(); 239 } 240 }); 241 mDialog = builder.create(); 242 mDialog.show(); 243 } 244 compareTo(final InputMethodPreference rhs, final Collator collator)245 int compareTo(final InputMethodPreference rhs, final Collator collator) { 246 if (this == rhs) { 247 return 0; 248 } 249 if (mHasPriorityInSorting == rhs.mHasPriorityInSorting) { 250 final CharSequence t0 = getTitle(); 251 final CharSequence t1 = rhs.getTitle(); 252 if (TextUtils.isEmpty(t0)) { 253 return 1; 254 } 255 if (TextUtils.isEmpty(t1)) { 256 return -1; 257 } 258 return collator.compare(t0.toString(), t1.toString()); 259 } 260 // Prefer always checked system IMEs 261 return mHasPriorityInSorting ? -1 : 1; 262 } 263 } 264