• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 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 com.android.settings.SettingsPreferenceFragment;
20 
21 import android.content.ContentResolver;
22 import android.content.Context;
23 import android.content.pm.ApplicationInfo;
24 import android.content.pm.PackageManager;
25 import android.content.res.Resources;
26 import android.preference.CheckBoxPreference;
27 import android.preference.Preference;
28 import android.preference.PreferenceScreen;
29 import android.provider.Settings;
30 import android.provider.Settings.SettingNotFoundException;
31 import android.text.TextUtils;
32 import android.util.Log;
33 import android.view.inputmethod.InputMethodInfo;
34 import android.view.inputmethod.InputMethodManager;
35 import android.view.inputmethod.InputMethodSubtype;
36 
37 import java.util.HashMap;
38 import java.util.HashSet;
39 import java.util.List;
40 import java.util.Locale;
41 import java.util.Map;
42 
43 public class InputMethodAndSubtypeUtil {
44 
45     private static final boolean DEBUG = false;
46     static final String TAG = "InputMethdAndSubtypeUtil";
47 
48     private static final char INPUT_METHOD_SEPARATER = ':';
49     private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
50     private static final int NOT_A_SUBTYPE_ID = -1;
51     private static final Locale ENGLISH_LOCALE = new Locale("en");
52 
53     private static final TextUtils.SimpleStringSplitter sStringInputMethodSplitter
54             = new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
55 
56     private static final TextUtils.SimpleStringSplitter sStringInputMethodSubtypeSplitter
57             = new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
58 
buildEnabledInputMethodsString( StringBuilder builder, String imi, HashSet<String> subtypes)59     private static void buildEnabledInputMethodsString(
60             StringBuilder builder, String imi, HashSet<String> subtypes) {
61         builder.append(imi);
62         // Inputmethod and subtypes are saved in the settings as follows:
63         // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
64         for (String subtypeId: subtypes) {
65             builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
66         }
67     }
68 
buildInputMethodsAndSubtypesString( StringBuilder builder, HashMap<String, HashSet<String>> imsList)69     public static void buildInputMethodsAndSubtypesString(
70             StringBuilder builder, HashMap<String, HashSet<String>> imsList) {
71         boolean needsAppendSeparator = false;
72         for (String imi: imsList.keySet()) {
73             if (needsAppendSeparator) {
74                 builder.append(INPUT_METHOD_SEPARATER);
75             } else {
76                 needsAppendSeparator = true;
77             }
78             buildEnabledInputMethodsString(builder, imi, imsList.get(imi));
79         }
80     }
81 
buildDisabledSystemInputMethods( StringBuilder builder, HashSet<String> imes)82     public static void buildDisabledSystemInputMethods(
83             StringBuilder builder, HashSet<String> imes) {
84         boolean needsAppendSeparator = false;
85         for (String ime: imes) {
86             if (needsAppendSeparator) {
87                 builder.append(INPUT_METHOD_SEPARATER);
88             } else {
89                 needsAppendSeparator = true;
90             }
91             builder.append(ime);
92         }
93     }
94 
getInputMethodSubtypeSelected(ContentResolver resolver)95     private static int getInputMethodSubtypeSelected(ContentResolver resolver) {
96         try {
97             return Settings.Secure.getInt(resolver,
98                     Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE);
99         } catch (SettingNotFoundException e) {
100             return NOT_A_SUBTYPE_ID;
101         }
102     }
103 
isInputMethodSubtypeSelected(ContentResolver resolver)104     private static boolean isInputMethodSubtypeSelected(ContentResolver resolver) {
105         return getInputMethodSubtypeSelected(resolver) != NOT_A_SUBTYPE_ID;
106     }
107 
putSelectedInputMethodSubtype(ContentResolver resolver, int hashCode)108     private static void putSelectedInputMethodSubtype(ContentResolver resolver, int hashCode) {
109         Settings.Secure.putInt(resolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, hashCode);
110     }
111 
112     // Needs to modify InputMethodManageService if you want to change the format of saved string.
getEnabledInputMethodsAndSubtypeList( ContentResolver resolver)113     private static HashMap<String, HashSet<String>> getEnabledInputMethodsAndSubtypeList(
114             ContentResolver resolver) {
115         final String enabledInputMethodsStr = Settings.Secure.getString(
116                 resolver, Settings.Secure.ENABLED_INPUT_METHODS);
117         HashMap<String, HashSet<String>> imsList
118                 = new HashMap<String, HashSet<String>>();
119         if (DEBUG) {
120             Log.d(TAG, "--- Load enabled input methods: " + enabledInputMethodsStr);
121         }
122 
123         if (TextUtils.isEmpty(enabledInputMethodsStr)) {
124             return imsList;
125         }
126         sStringInputMethodSplitter.setString(enabledInputMethodsStr);
127         while (sStringInputMethodSplitter.hasNext()) {
128             String nextImsStr = sStringInputMethodSplitter.next();
129             sStringInputMethodSubtypeSplitter.setString(nextImsStr);
130             if (sStringInputMethodSubtypeSplitter.hasNext()) {
131                 HashSet<String> subtypeHashes = new HashSet<String>();
132                 // The first element is ime id.
133                 String imeId = sStringInputMethodSubtypeSplitter.next();
134                 while (sStringInputMethodSubtypeSplitter.hasNext()) {
135                     subtypeHashes.add(sStringInputMethodSubtypeSplitter.next());
136                 }
137                 imsList.put(imeId, subtypeHashes);
138             }
139         }
140         return imsList;
141     }
142 
getDisabledSystemIMEs(ContentResolver resolver)143     private static HashSet<String> getDisabledSystemIMEs(ContentResolver resolver) {
144         HashSet<String> set = new HashSet<String>();
145         String disabledIMEsStr = Settings.Secure.getString(
146                 resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS);
147         if (TextUtils.isEmpty(disabledIMEsStr)) {
148             return set;
149         }
150         sStringInputMethodSplitter.setString(disabledIMEsStr);
151         while(sStringInputMethodSplitter.hasNext()) {
152             set.add(sStringInputMethodSplitter.next());
153         }
154         return set;
155     }
156 
getCurrentInputMethodName(Context context, ContentResolver resolver, InputMethodManager imm, List<InputMethodInfo> imis, PackageManager pm)157     public static CharSequence getCurrentInputMethodName(Context context, ContentResolver resolver,
158             InputMethodManager imm, List<InputMethodInfo> imis, PackageManager pm) {
159         if (resolver == null || imis == null) return null;
160         final String currentInputMethodId = Settings.Secure.getString(resolver,
161                 Settings.Secure.DEFAULT_INPUT_METHOD);
162         if (TextUtils.isEmpty(currentInputMethodId)) return null;
163         for (InputMethodInfo imi : imis) {
164             if (currentInputMethodId.equals(imi.getId())) {
165                 final InputMethodSubtype subtype = imm.getCurrentInputMethodSubtype();
166                 final CharSequence imiLabel = imi.loadLabel(pm);
167                 final CharSequence summary = subtype != null
168                         ? TextUtils.concat(subtype.getDisplayName(context,
169                                     imi.getPackageName(), imi.getServiceInfo().applicationInfo),
170                                             (TextUtils.isEmpty(imiLabel) ?
171                                                     "" : " - " + imiLabel))
172                         : imiLabel;
173                 return summary;
174             }
175         }
176         return null;
177     }
178 
saveInputMethodSubtypeList(SettingsPreferenceFragment context, ContentResolver resolver, List<InputMethodInfo> inputMethodInfos, boolean hasHardKeyboard)179     public static void saveInputMethodSubtypeList(SettingsPreferenceFragment context,
180             ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
181             boolean hasHardKeyboard) {
182         String currentInputMethodId = Settings.Secure.getString(resolver,
183                 Settings.Secure.DEFAULT_INPUT_METHOD);
184         final int selectedInputMethodSubtype = getInputMethodSubtypeSelected(resolver);
185         HashMap<String, HashSet<String>> enabledIMEAndSubtypesMap =
186                 getEnabledInputMethodsAndSubtypeList(resolver);
187         HashSet<String> disabledSystemIMEs = getDisabledSystemIMEs(resolver);
188 
189         final int imiCount = inputMethodInfos.size();
190         boolean needsToResetSelectedSubtype = false;
191         for (InputMethodInfo imi : inputMethodInfos) {
192             final String imiId = imi.getId();
193             Preference pref = context.findPreference(imiId);
194             if (pref == null) continue;
195             // In the Configure input method screen or in the subtype enabler screen.
196             // pref is instance of CheckBoxPreference in the Configure input method screen.
197             final boolean isImeChecked = (pref instanceof CheckBoxPreference) ?
198                     ((CheckBoxPreference) pref).isChecked()
199                     : enabledIMEAndSubtypesMap.containsKey(imiId);
200             final boolean isCurrentInputMethod = imiId.equals(currentInputMethodId);
201             final boolean systemIme = isSystemIme(imi);
202             if ((!hasHardKeyboard && isAlwaysCheckedIme(imi, context.getActivity(), imiCount))
203                     || isImeChecked) {
204                 if (!enabledIMEAndSubtypesMap.containsKey(imiId)) {
205                     // imiId has just been enabled
206                     enabledIMEAndSubtypesMap.put(imiId, new HashSet<String>());
207                 }
208                 HashSet<String> subtypesSet = enabledIMEAndSubtypesMap.get(imiId);
209 
210                 boolean subtypePrefFound = false;
211                 final int subtypeCount = imi.getSubtypeCount();
212                 for (int i = 0; i < subtypeCount; ++i) {
213                     InputMethodSubtype subtype = imi.getSubtypeAt(i);
214                     final String subtypeHashCodeStr = String.valueOf(subtype.hashCode());
215                     CheckBoxPreference subtypePref = (CheckBoxPreference) context.findPreference(
216                             imiId + subtypeHashCodeStr);
217                     // In the Configure input method screen which does not have subtype preferences.
218                     if (subtypePref == null) continue;
219                     if (!subtypePrefFound) {
220                         // Once subtype checkbox is found, subtypeSet needs to be cleared.
221                         // Because of system change, hashCode value could have been changed.
222                         subtypesSet.clear();
223                         // If selected subtype preference is disabled, needs to reset.
224                         needsToResetSelectedSubtype = true;
225                         subtypePrefFound = true;
226                     }
227                     if (subtypePref.isChecked()) {
228                         subtypesSet.add(subtypeHashCodeStr);
229                         if (isCurrentInputMethod) {
230                             if (selectedInputMethodSubtype == subtype.hashCode()) {
231                                 // Selected subtype is still enabled, there is no need to reset
232                                 // selected subtype.
233                                 needsToResetSelectedSubtype = false;
234                             }
235                         }
236                     } else {
237                         subtypesSet.remove(subtypeHashCodeStr);
238                     }
239                 }
240             } else {
241                 enabledIMEAndSubtypesMap.remove(imiId);
242                 if (isCurrentInputMethod) {
243                     // We are processing the current input method, but found that it's not enabled.
244                     // This means that the current input method has been uninstalled.
245                     // If currentInputMethod is already uninstalled, InputMethodManagerService will
246                     // find the applicable IME from the history and the system locale.
247                     if (DEBUG) {
248                         Log.d(TAG, "Current IME was uninstalled or disabled.");
249                     }
250                     currentInputMethodId = null;
251                 }
252             }
253             // If it's a disabled system ime, add it to the disabled list so that it
254             // doesn't get enabled automatically on any changes to the package list
255             if (systemIme && hasHardKeyboard) {
256                 if (disabledSystemIMEs.contains(imiId)) {
257                     if (isImeChecked) {
258                         disabledSystemIMEs.remove(imiId);
259                     }
260                 } else {
261                     if (!isImeChecked) {
262                         disabledSystemIMEs.add(imiId);
263                     }
264                 }
265             }
266         }
267 
268         StringBuilder builder = new StringBuilder();
269         buildInputMethodsAndSubtypesString(builder, enabledIMEAndSubtypesMap);
270         StringBuilder disabledSysImesBuilder = new StringBuilder();
271         buildDisabledSystemInputMethods(disabledSysImesBuilder, disabledSystemIMEs);
272         if (DEBUG) {
273             Log.d(TAG, "--- Save enabled inputmethod settings. :" + builder.toString());
274             Log.d(TAG, "--- Save disable system inputmethod settings. :"
275                     + disabledSysImesBuilder.toString());
276             Log.d(TAG, "--- Save default inputmethod settings. :" + currentInputMethodId);
277             Log.d(TAG, "--- Needs to reset the selected subtype :" + needsToResetSelectedSubtype);
278             Log.d(TAG, "--- Subtype is selected :" + isInputMethodSubtypeSelected(resolver));
279         }
280 
281         // Redefines SelectedSubtype when all subtypes are unchecked or there is no subtype
282         // selected. And if the selected subtype of the current input method was disabled,
283         // We should reset the selected input method's subtype.
284         if (needsToResetSelectedSubtype || !isInputMethodSubtypeSelected(resolver)) {
285             if (DEBUG) {
286                 Log.d(TAG, "--- Reset inputmethod subtype because it's not defined.");
287             }
288             putSelectedInputMethodSubtype(resolver, NOT_A_SUBTYPE_ID);
289         }
290 
291         Settings.Secure.putString(resolver,
292                 Settings.Secure.ENABLED_INPUT_METHODS, builder.toString());
293         if (disabledSysImesBuilder.length() > 0) {
294             Settings.Secure.putString(resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS,
295                     disabledSysImesBuilder.toString());
296         }
297         // If the current input method is unset, InputMethodManagerService will find the applicable
298         // IME from the history and the system locale.
299         Settings.Secure.putString(resolver, Settings.Secure.DEFAULT_INPUT_METHOD,
300                 currentInputMethodId != null ? currentInputMethodId : "");
301     }
302 
loadInputMethodSubtypeList( SettingsPreferenceFragment context, ContentResolver resolver, List<InputMethodInfo> inputMethodInfos, final Map<String, List<Preference>> inputMethodPrefsMap)303     public static void loadInputMethodSubtypeList(
304             SettingsPreferenceFragment context, ContentResolver resolver,
305             List<InputMethodInfo> inputMethodInfos,
306             final Map<String, List<Preference>> inputMethodPrefsMap) {
307         HashMap<String, HashSet<String>> enabledSubtypes =
308                 getEnabledInputMethodsAndSubtypeList(resolver);
309 
310         for (InputMethodInfo imi : inputMethodInfos) {
311             final String imiId = imi.getId();
312             Preference pref = context.findPreference(imiId);
313             if (pref != null && pref instanceof CheckBoxPreference) {
314                 CheckBoxPreference checkBoxPreference = (CheckBoxPreference) pref;
315                 boolean isEnabled = enabledSubtypes.containsKey(imiId);
316                 checkBoxPreference.setChecked(isEnabled);
317                 if (inputMethodPrefsMap != null) {
318                     for (Preference childPref: inputMethodPrefsMap.get(imiId)) {
319                         childPref.setEnabled(isEnabled);
320                     }
321                 }
322                 setSubtypesPreferenceEnabled(context, inputMethodInfos, imiId, isEnabled);
323             }
324         }
325         updateSubtypesPreferenceChecked(context, inputMethodInfos, enabledSubtypes);
326     }
327 
setSubtypesPreferenceEnabled(SettingsPreferenceFragment context, List<InputMethodInfo> inputMethodProperties, String id, boolean enabled)328     public static void setSubtypesPreferenceEnabled(SettingsPreferenceFragment context,
329             List<InputMethodInfo> inputMethodProperties, String id, boolean enabled) {
330         PreferenceScreen preferenceScreen = context.getPreferenceScreen();
331         for (InputMethodInfo imi : inputMethodProperties) {
332             if (id.equals(imi.getId())) {
333                 final int subtypeCount = imi.getSubtypeCount();
334                 for (int i = 0; i < subtypeCount; ++i) {
335                     InputMethodSubtype subtype = imi.getSubtypeAt(i);
336                     CheckBoxPreference pref = (CheckBoxPreference) preferenceScreen.findPreference(
337                             id + subtype.hashCode());
338                     if (pref != null) {
339                         pref.setEnabled(enabled);
340                     }
341                 }
342             }
343         }
344     }
345 
updateSubtypesPreferenceChecked(SettingsPreferenceFragment context, List<InputMethodInfo> inputMethodProperties, HashMap<String, HashSet<String>> enabledSubtypes)346     public static void updateSubtypesPreferenceChecked(SettingsPreferenceFragment context,
347             List<InputMethodInfo> inputMethodProperties,
348             HashMap<String, HashSet<String>> enabledSubtypes) {
349         PreferenceScreen preferenceScreen = context.getPreferenceScreen();
350         for (InputMethodInfo imi : inputMethodProperties) {
351             String id = imi.getId();
352             if (!enabledSubtypes.containsKey(id)) break;
353             final HashSet<String> enabledSubtypesSet = enabledSubtypes.get(id);
354             final int subtypeCount = imi.getSubtypeCount();
355             for (int i = 0; i < subtypeCount; ++i) {
356                 InputMethodSubtype subtype = imi.getSubtypeAt(i);
357                 String hashCode = String.valueOf(subtype.hashCode());
358                 if (DEBUG) {
359                     Log.d(TAG, "--- Set checked state: " + "id" + ", " + hashCode + ", "
360                             + enabledSubtypesSet.contains(hashCode));
361                 }
362                 CheckBoxPreference pref = (CheckBoxPreference) preferenceScreen.findPreference(
363                         id + hashCode);
364                 if (pref != null) {
365                     pref.setChecked(enabledSubtypesSet.contains(hashCode));
366                 }
367             }
368         }
369     }
370 
isSystemIme(InputMethodInfo property)371     public static boolean isSystemIme(InputMethodInfo property) {
372         return (property.getServiceInfo().applicationInfo.flags
373                 & ApplicationInfo.FLAG_SYSTEM) != 0;
374     }
375 
isAuxiliaryIme(InputMethodInfo imi)376     public static boolean isAuxiliaryIme(InputMethodInfo imi) {
377         return imi.isAuxiliaryIme();
378     }
379 
isAlwaysCheckedIme(InputMethodInfo imi, Context context, int imiCount)380     public static boolean isAlwaysCheckedIme(InputMethodInfo imi, Context context, int imiCount) {
381         if (imiCount <= 1) {
382             return true;
383         }
384         if (!isSystemIme(imi)) {
385             return false;
386         }
387         if (isAuxiliaryIme(imi)) {
388             return false;
389         }
390         if (isValidDefaultIme(imi, context)) {
391             return true;
392         }
393         return containsSubtypeOf(imi, ENGLISH_LOCALE.getLanguage());
394     }
395 
isValidDefaultIme(InputMethodInfo imi, Context context)396     private static boolean isValidDefaultIme(InputMethodInfo imi, Context context) {
397         if (imi.getIsDefaultResourceId() != 0) {
398             try {
399                 Resources res = context.createPackageContext(
400                         imi.getPackageName(), 0).getResources();
401                 if (res.getBoolean(imi.getIsDefaultResourceId())
402                         && containsSubtypeOf(imi, context.getResources().getConfiguration().
403                                 locale.getLanguage())) {
404                     return true;
405                 }
406             } catch (PackageManager.NameNotFoundException ex) {
407             } catch (Resources.NotFoundException ex) {
408             }
409         }
410         return false;
411     }
412 
containsSubtypeOf(InputMethodInfo imi, String language)413     private static boolean containsSubtypeOf(InputMethodInfo imi, String language) {
414         final int N = imi.getSubtypeCount();
415         for (int i = 0; i < N; ++i) {
416             if (imi.getSubtypeAt(i).getLocale().startsWith(language)) {
417                 return true;
418             }
419         }
420         return false;
421     }
422 }
423