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