• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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             setCheckedInternal(false);
146             return false;
147         }
148         if (InputMethodUtils.isSystemIme(mImi)) {
149             // Enable a system IME. No need to show a security warning dialog,
150             // but we might need to prompt if it's not Direct Boot aware.
151             if (mImi.getServiceInfo().directBootAware) {
152                 setCheckedInternal(true);
153             } else {
154                 showDirectBootWarnDialog();
155             }
156         } else {
157             // Once security is confirmed, we might prompt if the IME isn't
158             // Direct Boot aware.
159             showSecurityWarnDialog();
160         }
161         return false;
162     }
163 
164     @Override
onPreferenceClick(final Preference preference)165     public boolean onPreferenceClick(final Preference preference) {
166         // Always returns true to prevent invoking an intent without catching exceptions.
167         // See {@link Preference#performClick(PreferenceScreen)}/
168         if (isImeEnabler()) {
169             // Prevent invoking a settings activity because this preference is for enabling and
170             // disabling an input method.
171             return true;
172         }
173         final Context context = getContext();
174         try {
175             final Intent intent = getIntent();
176             if (intent != null) {
177                 // Invoke a settings activity of an input method.
178                 context.startActivity(intent);
179             }
180         } catch (final ActivityNotFoundException e) {
181             Log.d(TAG, "IME's Settings Activity Not Found", e);
182             final String message = context.getString(
183                     R.string.failed_to_open_app_settings_toast,
184                     mImi.loadLabel(context.getPackageManager()));
185             Toast.makeText(context, message, Toast.LENGTH_LONG).show();
186         }
187         return true;
188     }
189 
updatePreferenceViews()190     void updatePreferenceViews() {
191         final boolean isAlwaysChecked = mInputMethodSettingValues.isAlwaysCheckedIme(
192                 mImi, getContext());
193         // When this preference has a switch and an input method should be always enabled,
194         // this preference should be disabled to prevent accidentally disabling an input method.
195         // This preference should also be disabled in case the admin does not allow this input
196         // method.
197         if (isAlwaysChecked && isImeEnabler()) {
198             setDisabledByAdmin(null);
199             setEnabled(false);
200         } else if (!mIsAllowedByOrganization) {
201             EnforcedAdmin admin =
202                     RestrictedLockUtils.checkIfInputMethodDisallowed(getContext(),
203                             mImi.getPackageName(), UserHandle.myUserId());
204             setDisabledByAdmin(admin);
205         } else {
206             setEnabled(true);
207         }
208         setChecked(mInputMethodSettingValues.isEnabledImi(mImi));
209         if (!isDisabledByAdmin()) {
210             setSummary(getSummaryString());
211         }
212     }
213 
getInputMethodManager()214     private InputMethodManager getInputMethodManager() {
215         return (InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
216     }
217 
getSummaryString()218     private String getSummaryString() {
219         final InputMethodManager imm = getInputMethodManager();
220         final List<InputMethodSubtype> subtypes = imm.getEnabledInputMethodSubtypeList(mImi, true);
221         return InputMethodAndSubtypeUtil.getSubtypeLocaleNameListAsSentence(
222                 subtypes, getContext(), mImi);
223     }
224 
setCheckedInternal(boolean checked)225     private void setCheckedInternal(boolean checked) {
226         super.setChecked(checked);
227         mOnSaveListener.onSaveInputMethodPreference(InputMethodPreference.this);
228         notifyChanged();
229     }
230 
showSecurityWarnDialog()231     private void showSecurityWarnDialog() {
232         if (mDialog != null && mDialog.isShowing()) {
233             mDialog.dismiss();
234         }
235         final Context context = getContext();
236         final AlertDialog.Builder builder = new AlertDialog.Builder(context);
237         builder.setCancelable(true /* cancelable */);
238         builder.setTitle(android.R.string.dialog_alert_title);
239         final CharSequence label = mImi.getServiceInfo().applicationInfo.loadLabel(
240                 context.getPackageManager());
241         builder.setMessage(context.getString(R.string.ime_security_warning, label));
242         builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
243             @Override
244             public void onClick(final DialogInterface dialog, final int which) {
245                 // The user confirmed to enable a 3rd party IME, but we might
246                 // need to prompt if it's not Direct Boot aware.
247                 if (mImi.getServiceInfo().directBootAware) {
248                     setCheckedInternal(true);
249                 } else {
250                     showDirectBootWarnDialog();
251                 }
252             }
253         });
254         builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
255             @Override
256             public void onClick(final DialogInterface dialog, final int which) {
257                 // The user canceled to enable a 3rd party IME.
258                 setCheckedInternal(false);
259             }
260         });
261         mDialog = builder.create();
262         mDialog.show();
263     }
264 
showDirectBootWarnDialog()265     private void showDirectBootWarnDialog() {
266         if (mDialog != null && mDialog.isShowing()) {
267             mDialog.dismiss();
268         }
269         final Context context = getContext();
270         final AlertDialog.Builder builder = new AlertDialog.Builder(context);
271         builder.setCancelable(true /* cancelable */);
272         builder.setMessage(context.getText(R.string.direct_boot_unaware_dialog_message));
273         builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
274             @Override
275             public void onClick(final DialogInterface dialog, final int which) {
276                 setCheckedInternal(true);
277             }
278         });
279         builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
280             @Override
281             public void onClick(final DialogInterface dialog, final int which) {
282                 setCheckedInternal(false);
283             }
284         });
285         mDialog = builder.create();
286         mDialog.show();
287     }
288 
compareTo(final InputMethodPreference rhs, final Collator collator)289     int compareTo(final InputMethodPreference rhs, final Collator collator) {
290         if (this == rhs) {
291             return 0;
292         }
293         if (mHasPriorityInSorting == rhs.mHasPriorityInSorting) {
294             final CharSequence t0 = getTitle();
295             final CharSequence t1 = rhs.getTitle();
296             if (TextUtils.isEmpty(t0)) {
297                 return 1;
298             }
299             if (TextUtils.isEmpty(t1)) {
300                 return -1;
301             }
302             return collator.compare(t0.toString(), t1.toString());
303         }
304         // Prefer always checked system IMEs
305         return mHasPriorityInSorting ? -1 : 1;
306     }
307 }
308