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