diff --git a/api/current.xml b/api/current.xml index a8666694107c5..07ac09966c281 100644 --- a/api/current.xml +++ b/api/current.xml @@ -215713,6 +215713,19 @@ + + + + mInputMethodProperties; + + private final TextUtils.SimpleStringSplitter mStringColonSplitter + = new TextUtils.SimpleStringSplitter(':'); + + private String mLastInputMethodId; + private String mLastTickedInputMethodId; + + private AlertDialog mDialog = null; + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + Configuration config = getResources().getConfiguration(); + mHaveHardKeyboard = (config.keyboard == Configuration.KEYBOARD_QWERTY); + onCreateIMM(); + setPreferenceScreen(createPreferenceHierarchy()); + } + + @Override + protected void onResume() { + super.onResume(); + loadInputMethodSubtypeList(); + } + + @Override + protected void onPause() { + super.onPause(); + saveInputMethodSubtypeList(); + } + + @Override + public boolean onPreferenceTreeClick( + PreferenceScreen preferenceScreen, Preference preference) { + + if (preference instanceof CheckBoxPreference) { + final CheckBoxPreference chkPref = (CheckBoxPreference) preference; + final String id = chkPref.getKey(); + // TODO: Check subtype or not here + if (chkPref.isChecked()) { + InputMethodInfo selImi = null; + final int N = mInputMethodProperties.size(); + for (int i = 0; i < N; i++) { + InputMethodInfo imi = mInputMethodProperties.get(i); + if (id.equals(imi.getId())) { + selImi = imi; + if (isSystemIme(imi)) { + setSubtypesPreferenceEnabled(id, true); + // This is a built-in IME, so no need to warn. + mLastTickedInputMethodId = id; + return super.onPreferenceTreeClick(preferenceScreen, preference); + } + break; + } + } + if (selImi == null) { + return super.onPreferenceTreeClick(preferenceScreen, preference); + } + chkPref.setChecked(false); + if (mDialog == null) { + mDialog = (new AlertDialog.Builder(this)) + .setTitle(android.R.string.dialog_alert_title) + .setIcon(android.R.drawable.ic_dialog_alert) + .setCancelable(true) + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + chkPref.setChecked(true); + setSubtypesPreferenceEnabled(id, true); + mLastTickedInputMethodId = id; + } + + }) + .setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + } + + }) + .create(); + } else { + if (mDialog.isShowing()) { + mDialog.dismiss(); + } + } + mDialog.setMessage(getResources().getString( + com.android.internal.R.string.ime_enabler_security_warning, + selImi.getServiceInfo().applicationInfo.loadLabel(getPackageManager()))); + mDialog.show(); + } else { + if (id.equals(mLastTickedInputMethodId)) { + mLastTickedInputMethodId = null; + } + setSubtypesPreferenceEnabled(id, false); + } + } + return super.onPreferenceTreeClick(preferenceScreen, preference); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (mDialog != null) { + mDialog.dismiss(); + mDialog = null; + } + } + + private void onCreateIMM() { + InputMethodManager imm = (InputMethodManager) getSystemService( + Context.INPUT_METHOD_SERVICE); + + // TODO: Change mInputMethodProperties to Map + mInputMethodProperties = imm.getInputMethodList(); + + mLastInputMethodId = Settings.Secure.getString(getContentResolver(), + Settings.Secure.DEFAULT_INPUT_METHOD); + } + + private PreferenceScreen createPreferenceHierarchy() { + // Root + PreferenceScreen root = getPreferenceManager().createPreferenceScreen(this); + + int N = (mInputMethodProperties == null ? 0 : mInputMethodProperties.size()); + // TODO: Use iterator. + for (int i = 0; i < N; ++i) { + PreferenceCategory keyboardSettingsCategory = new PreferenceCategory(this); + root.addPreference(keyboardSettingsCategory); + InputMethodInfo property = mInputMethodProperties.get(i); + String prefKey = property.getId(); + + PackageManager pm = getPackageManager(); + CharSequence label = property.loadLabel(pm); + boolean systemIME = isSystemIme(property); + + keyboardSettingsCategory.setTitle(label); + + // Add a check box. + // Don't show the toggle if it's the only keyboard in the system, or it's a system IME. + if (mHaveHardKeyboard || (N > 1 && !systemIME)) { + CheckBoxPreference chkbxPref = new CheckBoxPreference(this); + chkbxPref.setKey(prefKey); + chkbxPref.setTitle(label); + keyboardSettingsCategory.addPreference(chkbxPref); + } + + ArrayList subtypes = property.getSubtypes(); + if (subtypes.size() > 0) { + PreferenceCategory subtypesCategory = new PreferenceCategory(this); + subtypesCategory.setTitle(getResources().getString( + com.android.internal.R.string.ime_enabler_subtype_title, label)); + root.addPreference(subtypesCategory); + for (InputMethodSubtype subtype: subtypes) { + CharSequence subtypeLabel; + int nameResId = subtype.getNameResId(); + if (nameResId != 0) { + subtypeLabel = pm.getText(property.getPackageName(), nameResId, + property.getServiceInfo().applicationInfo); + } else { + int modeResId = subtype.getModeResId(); + CharSequence language = subtype.getLocale(); + CharSequence mode = modeResId == 0 ? null + : pm.getText(property.getPackageName(), modeResId, + property.getServiceInfo().applicationInfo); + // TODO: Use more friendly Title and UI + subtypeLabel = (mode == null ? "" : mode) + "," + + (language == null ? "" : language); + } + CheckBoxPreference chkbxPref = new CheckBoxPreference(this); + chkbxPref.setKey(prefKey + subtype.hashCode()); + chkbxPref.setTitle(subtypeLabel); + chkbxPref.setSummary(label); + subtypesCategory.addPreference(chkbxPref); + } + } + } + return root; + } + + private void loadInputMethodSubtypeList() { + final HashSet enabled = new HashSet(); + String enabledStr = Settings.Secure.getString(getContentResolver(), + Settings.Secure.ENABLED_INPUT_METHODS); + if (enabledStr != null) { + final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter; + splitter.setString(enabledStr); + while (splitter.hasNext()) { + enabled.add(splitter.next()); + } + } + + // Update the statuses of the Check Boxes. + int N = mInputMethodProperties.size(); + // TODO: Use iterator. + for (int i = 0; i < N; ++i) { + final String id = mInputMethodProperties.get(i).getId(); + CheckBoxPreference pref = (CheckBoxPreference) findPreference( + mInputMethodProperties.get(i).getId()); + if (pref != null) { + boolean isEnabled = enabled.contains(id); + pref.setChecked(isEnabled); + setSubtypesPreferenceEnabled(id, isEnabled); + } + } + mLastTickedInputMethodId = null; + } + + private void saveInputMethodSubtypeList() { + StringBuilder builder = new StringBuilder(); + StringBuilder disabledSysImes = new StringBuilder(); + + int firstEnabled = -1; + int N = mInputMethodProperties.size(); + for (int i = 0; i < N; ++i) { + final InputMethodInfo property = mInputMethodProperties.get(i); + final String id = property.getId(); + CheckBoxPreference pref = (CheckBoxPreference) findPreference(id); + boolean currentInputMethod = id.equals(mLastInputMethodId); + boolean systemIme = isSystemIme(property); + // TODO: Append subtypes by using the separator ";" + if (((N == 1 || systemIme) && !mHaveHardKeyboard) + || (pref != null && pref.isChecked())) { + if (builder.length() > 0) builder.append(':'); + builder.append(id); + if (firstEnabled < 0) { + firstEnabled = i; + } + } else if (currentInputMethod) { + mLastInputMethodId = mLastTickedInputMethodId; + } + // If it's a disabled system ime, add it to the disabled list so that it + // doesn't get enabled automatically on any changes to the package list + if (pref != null && !pref.isChecked() && systemIme && mHaveHardKeyboard) { + if (disabledSysImes.length() > 0) disabledSysImes.append(":"); + disabledSysImes.append(id); + } + } + + // If the last input method is unset, set it as the first enabled one. + if (TextUtils.isEmpty(mLastInputMethodId)) { + if (firstEnabled >= 0) { + mLastInputMethodId = mInputMethodProperties.get(firstEnabled).getId(); + } else { + mLastInputMethodId = null; + } + } + + Settings.Secure.putString(getContentResolver(), + Settings.Secure.ENABLED_INPUT_METHODS, builder.toString()); + Settings.Secure.putString(getContentResolver(), + Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS, disabledSysImes.toString()); + Settings.Secure.putString(getContentResolver(), + Settings.Secure.DEFAULT_INPUT_METHOD, + mLastInputMethodId != null ? mLastInputMethodId : ""); + } + + private void setSubtypesPreferenceEnabled(String id, boolean enabled) { + PreferenceScreen preferenceScreen = getPreferenceScreen(); + final int N = mInputMethodProperties.size(); + // TODO: Use iterator. + for (int i = 0; i < N; i++) { + InputMethodInfo imi = mInputMethodProperties.get(i); + if (id.equals(imi.getId())) { + for (InputMethodSubtype subtype: imi.getSubtypes()) { + preferenceScreen.findPreference(id + subtype.hashCode()).setEnabled(enabled); + } + } + } + } + + private boolean isSystemIme(InputMethodInfo property) { + return (property.getServiceInfo().applicationInfo.flags + & ApplicationInfo.FLAG_SYSTEM) != 0; + } +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 3a1d76f861670..f15d0a48cba3c 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1373,6 +1373,11 @@ android:permission="android.permission.BIND_WALLPAPER"> + + + diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 8b4f91f69ba70..f0ab7e31eeac8 100755 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -2385,5 +2385,9 @@ %d of %d + + This input method may be able to collect all the text you type, including personal data like passwords and credit card numbers. It comes from the application %1$s. Use this input method? + + Select inputmethods in %1$s diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index 9a5423c0c889b..dc4194cb16831 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -25,6 +25,7 @@ import com.android.internal.view.IInputMethodClient; import com.android.internal.view.IInputMethodManager; import com.android.internal.view.IInputMethodSession; import com.android.internal.view.InputBindResult; +import com.android.internal.view.InputMethodAndSubtypeEnabler; import com.android.server.StatusBarManagerService; @@ -97,6 +98,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub static final int MSG_SHOW_IM_PICKER = 1; static final int MSG_SHOW_IM_SUBTYPE_PICKER = 2; + static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 3; static final int MSG_UNBIND_INPUT = 1000; static final int MSG_BIND_INPUT = 1010; @@ -1225,7 +1227,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub synchronized (mMethodMap) { if (mCurClient == null || client == null || mCurClient.client.asBinder() != client.asBinder()) { - Slog.w(TAG, "Ignoring showInputMethodDialogFromClient of uid " + Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid " + Binder.getCallingUid() + ": " + client); } @@ -1237,13 +1239,26 @@ public class InputMethodManagerService extends IInputMethodManager.Stub synchronized (mMethodMap) { if (mCurClient == null || client == null || mCurClient.client.asBinder() != client.asBinder()) { - Slog.w(TAG, "Ignoring showInputSubtypeMethodDialogFromClient of: " + client); + Slog.w(TAG, "Ignoring showInputMethodSubtypePickerFromClient of: " + client); } mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_PICKER); } } + public void showInputMethodAndSubtypeEnablerFromClient( + IInputMethodClient client, String topId) { + // TODO: Handle topId for setting the top position of the list activity + synchronized (mMethodMap) { + if (mCurClient == null || client == null + || mCurClient.client.asBinder() != client.asBinder()) { + Slog.w(TAG, "Ignoring showInputMethodAndSubtypeEnablerFromClient of: " + client); + } + + mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_ENABLER); + } + } + public void setInputMethod(IBinder token, String id) { setInputMethodWithSubtype(token, id, NOT_A_SUBTYPE_ID); } @@ -1336,6 +1351,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub showInputMethodSubtypeMenu(); return true; + case MSG_SHOW_IM_SUBTYPE_ENABLER: + showInputMethodAndSubtypeEnabler(); + return true; + // --------------------------------------------------------- case MSG_UNBIND_INPUT: @@ -1528,6 +1547,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub showInputMethodMenuInternal(true); } + private void showInputMethodAndSubtypeEnabler() { + Intent intent = new Intent(); + intent.setClassName("android", InputMethodAndSubtypeEnabler.class.getCanonicalName()); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + mContext.startActivity(intent); + } + private void showInputMethodMenuInternal(boolean showSubtypes) { if (DEBUG) Slog.v(TAG, "Show switching menu");