• 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 android.content.ContentResolver;
20 import android.content.SharedPreferences;
21 import android.preference.Preference;
22 import android.preference.PreferenceScreen;
23 import android.preference.TwoStatePreference;
24 import android.provider.Settings;
25 import android.provider.Settings.SettingNotFoundException;
26 import android.text.TextUtils;
27 import android.util.Log;
28 import android.view.inputmethod.InputMethodInfo;
29 import android.view.inputmethod.InputMethodSubtype;
30 
31 import com.android.internal.inputmethod.InputMethodUtils;
32 import com.android.settings.SettingsPreferenceFragment;
33 
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.List;
37 import java.util.Map;
38 
39 // TODO: Consolidate this with {@link InputMethodSettingValuesWrapper}.
40 class InputMethodAndSubtypeUtil {
41 
42     private static final boolean DEBUG = false;
43     static final String TAG = "InputMethdAndSubtypeUtil";
44 
45     private static final char INPUT_METHOD_SEPARATER = ':';
46     private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
47     private static final int NOT_A_SUBTYPE_ID = -1;
48 
49     private static final TextUtils.SimpleStringSplitter sStringInputMethodSplitter
50             = new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
51 
52     private static final TextUtils.SimpleStringSplitter sStringInputMethodSubtypeSplitter
53             = new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
54 
55     // InputMethods and subtypes are saved in the settings as follows:
56     // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
buildInputMethodsAndSubtypesString( final HashMap<String, HashSet<String>> imeToSubtypesMap)57     static String buildInputMethodsAndSubtypesString(
58             final HashMap<String, HashSet<String>> imeToSubtypesMap) {
59         final StringBuilder builder = new StringBuilder();
60         for (final String imi : imeToSubtypesMap.keySet()) {
61             if (builder.length() > 0) {
62                 builder.append(INPUT_METHOD_SEPARATER);
63             }
64             final HashSet<String> subtypeIdSet = imeToSubtypesMap.get(imi);
65             builder.append(imi);
66             for (final String subtypeId : subtypeIdSet) {
67                 builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
68             }
69         }
70         return builder.toString();
71     }
72 
buildInputMethodsString(final HashSet<String> imiList)73     private static String buildInputMethodsString(final HashSet<String> imiList) {
74         final StringBuilder builder = new StringBuilder();
75         for (final String imi : imiList) {
76             if (builder.length() > 0) {
77                 builder.append(INPUT_METHOD_SEPARATER);
78             }
79             builder.append(imi);
80         }
81         return builder.toString();
82     }
83 
getInputMethodSubtypeSelected(ContentResolver resolver)84     private static int getInputMethodSubtypeSelected(ContentResolver resolver) {
85         try {
86             return Settings.Secure.getInt(resolver,
87                     Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE);
88         } catch (SettingNotFoundException e) {
89             return NOT_A_SUBTYPE_ID;
90         }
91     }
92 
isInputMethodSubtypeSelected(ContentResolver resolver)93     private static boolean isInputMethodSubtypeSelected(ContentResolver resolver) {
94         return getInputMethodSubtypeSelected(resolver) != NOT_A_SUBTYPE_ID;
95     }
96 
putSelectedInputMethodSubtype(ContentResolver resolver, int hashCode)97     private static void putSelectedInputMethodSubtype(ContentResolver resolver, int hashCode) {
98         Settings.Secure.putInt(resolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, hashCode);
99     }
100 
101     // Needs to modify InputMethodManageService if you want to change the format of saved string.
getEnabledInputMethodsAndSubtypeList( ContentResolver resolver)102     private static HashMap<String, HashSet<String>> getEnabledInputMethodsAndSubtypeList(
103             ContentResolver resolver) {
104         final String enabledInputMethodsStr = Settings.Secure.getString(
105                 resolver, Settings.Secure.ENABLED_INPUT_METHODS);
106         if (DEBUG) {
107             Log.d(TAG, "--- Load enabled input methods: " + enabledInputMethodsStr);
108         }
109         return parseInputMethodsAndSubtypesString(enabledInputMethodsStr);
110     }
111 
parseInputMethodsAndSubtypesString( final String inputMethodsAndSubtypesString)112     static HashMap<String, HashSet<String>> parseInputMethodsAndSubtypesString(
113             final String inputMethodsAndSubtypesString) {
114         final HashMap<String, HashSet<String>> subtypesMap = new HashMap<>();
115         if (TextUtils.isEmpty(inputMethodsAndSubtypesString)) {
116             return subtypesMap;
117         }
118         sStringInputMethodSplitter.setString(inputMethodsAndSubtypesString);
119         while (sStringInputMethodSplitter.hasNext()) {
120             final String nextImsStr = sStringInputMethodSplitter.next();
121             sStringInputMethodSubtypeSplitter.setString(nextImsStr);
122             if (sStringInputMethodSubtypeSplitter.hasNext()) {
123                 final HashSet<String> subtypeIdSet = new HashSet<>();
124                 // The first element is {@link InputMethodInfoId}.
125                 final String imiId = sStringInputMethodSubtypeSplitter.next();
126                 while (sStringInputMethodSubtypeSplitter.hasNext()) {
127                     subtypeIdSet.add(sStringInputMethodSubtypeSplitter.next());
128                 }
129                 subtypesMap.put(imiId, subtypeIdSet);
130             }
131         }
132         return subtypesMap;
133     }
134 
enableInputMethodSubtypesOf(final ContentResolver resolver, final String imiId, final HashSet<String> enabledSubtypeIdSet)135     static void enableInputMethodSubtypesOf(final ContentResolver resolver, final String imiId,
136             final HashSet<String> enabledSubtypeIdSet) {
137         final HashMap<String, HashSet<String>> enabledImeAndSubtypeIdsMap =
138                 getEnabledInputMethodsAndSubtypeList(resolver);
139         enabledImeAndSubtypeIdsMap.put(imiId, enabledSubtypeIdSet);
140         final String enabledImesAndSubtypesString = buildInputMethodsAndSubtypesString(
141                 enabledImeAndSubtypeIdsMap);
142         Settings.Secure.putString(resolver,
143                 Settings.Secure.ENABLED_INPUT_METHODS, enabledImesAndSubtypesString);
144     }
145 
getDisabledSystemIMEs(ContentResolver resolver)146     private static HashSet<String> getDisabledSystemIMEs(ContentResolver resolver) {
147         HashSet<String> set = new HashSet<>();
148         String disabledIMEsStr = Settings.Secure.getString(
149                 resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS);
150         if (TextUtils.isEmpty(disabledIMEsStr)) {
151             return set;
152         }
153         sStringInputMethodSplitter.setString(disabledIMEsStr);
154         while(sStringInputMethodSplitter.hasNext()) {
155             set.add(sStringInputMethodSplitter.next());
156         }
157         return set;
158     }
159 
saveInputMethodSubtypeList(SettingsPreferenceFragment context, ContentResolver resolver, List<InputMethodInfo> inputMethodInfos, boolean hasHardKeyboard)160     static void saveInputMethodSubtypeList(SettingsPreferenceFragment context,
161             ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
162             boolean hasHardKeyboard) {
163         String currentInputMethodId = Settings.Secure.getString(resolver,
164                 Settings.Secure.DEFAULT_INPUT_METHOD);
165         final int selectedInputMethodSubtype = getInputMethodSubtypeSelected(resolver);
166         final HashMap<String, HashSet<String>> enabledIMEsAndSubtypesMap =
167                 getEnabledInputMethodsAndSubtypeList(resolver);
168         final HashSet<String> disabledSystemIMEs = getDisabledSystemIMEs(resolver);
169 
170         boolean needsToResetSelectedSubtype = false;
171         for (final InputMethodInfo imi : inputMethodInfos) {
172             final String imiId = imi.getId();
173             final Preference pref = context.findPreference(imiId);
174             if (pref == null) {
175                 continue;
176             }
177             // In the choose input method screen or in the subtype enabler screen,
178             // <code>pref</code> is an instance of TwoStatePreference.
179             final boolean isImeChecked = (pref instanceof TwoStatePreference) ?
180                     ((TwoStatePreference) pref).isChecked()
181                     : enabledIMEsAndSubtypesMap.containsKey(imiId);
182             final boolean isCurrentInputMethod = imiId.equals(currentInputMethodId);
183             final boolean systemIme = InputMethodUtils.isSystemIme(imi);
184             if ((!hasHardKeyboard && InputMethodSettingValuesWrapper.getInstance(
185                     context.getActivity()).isAlwaysCheckedIme(imi, context.getActivity()))
186                     || isImeChecked) {
187                 if (!enabledIMEsAndSubtypesMap.containsKey(imiId)) {
188                     // imiId has just been enabled
189                     enabledIMEsAndSubtypesMap.put(imiId, new HashSet<String>());
190                 }
191                 final HashSet<String> subtypesSet = enabledIMEsAndSubtypesMap.get(imiId);
192 
193                 boolean subtypePrefFound = false;
194                 final int subtypeCount = imi.getSubtypeCount();
195                 for (int i = 0; i < subtypeCount; ++i) {
196                     final InputMethodSubtype subtype = imi.getSubtypeAt(i);
197                     final String subtypeHashCodeStr = String.valueOf(subtype.hashCode());
198                     final TwoStatePreference subtypePref = (TwoStatePreference) context
199                             .findPreference(imiId + subtypeHashCodeStr);
200                     // In the Configure input method screen which does not have subtype preferences.
201                     if (subtypePref == null) {
202                         continue;
203                     }
204                     if (!subtypePrefFound) {
205                         // Once subtype preference is found, subtypeSet needs to be cleared.
206                         // Because of system change, hashCode value could have been changed.
207                         subtypesSet.clear();
208                         // If selected subtype preference is disabled, needs to reset.
209                         needsToResetSelectedSubtype = true;
210                         subtypePrefFound = true;
211                     }
212                     if (subtypePref.isChecked()) {
213                         subtypesSet.add(subtypeHashCodeStr);
214                         if (isCurrentInputMethod) {
215                             if (selectedInputMethodSubtype == subtype.hashCode()) {
216                                 // Selected subtype is still enabled, there is no need to reset
217                                 // selected subtype.
218                                 needsToResetSelectedSubtype = false;
219                             }
220                         }
221                     } else {
222                         subtypesSet.remove(subtypeHashCodeStr);
223                     }
224                 }
225             } else {
226                 enabledIMEsAndSubtypesMap.remove(imiId);
227                 if (isCurrentInputMethod) {
228                     // We are processing the current input method, but found that it's not enabled.
229                     // This means that the current input method has been uninstalled.
230                     // If currentInputMethod is already uninstalled, InputMethodManagerService will
231                     // find the applicable IME from the history and the system locale.
232                     if (DEBUG) {
233                         Log.d(TAG, "Current IME was uninstalled or disabled.");
234                     }
235                     currentInputMethodId = null;
236                 }
237             }
238             // If it's a disabled system ime, add it to the disabled list so that it
239             // doesn't get enabled automatically on any changes to the package list
240             if (systemIme && hasHardKeyboard) {
241                 if (disabledSystemIMEs.contains(imiId)) {
242                     if (isImeChecked) {
243                         disabledSystemIMEs.remove(imiId);
244                     }
245                 } else {
246                     if (!isImeChecked) {
247                         disabledSystemIMEs.add(imiId);
248                     }
249                 }
250             }
251         }
252 
253         final String enabledIMEsAndSubtypesString = buildInputMethodsAndSubtypesString(
254                 enabledIMEsAndSubtypesMap);
255         final String disabledSystemIMEsString = buildInputMethodsString(disabledSystemIMEs);
256         if (DEBUG) {
257             Log.d(TAG, "--- Save enabled inputmethod settings. :" + enabledIMEsAndSubtypesString);
258             Log.d(TAG, "--- Save disabled system inputmethod settings. :"
259                     + disabledSystemIMEsString);
260             Log.d(TAG, "--- Save default inputmethod settings. :" + currentInputMethodId);
261             Log.d(TAG, "--- Needs to reset the selected subtype :" + needsToResetSelectedSubtype);
262             Log.d(TAG, "--- Subtype is selected :" + isInputMethodSubtypeSelected(resolver));
263         }
264 
265         // Redefines SelectedSubtype when all subtypes are unchecked or there is no subtype
266         // selected. And if the selected subtype of the current input method was disabled,
267         // We should reset the selected input method's subtype.
268         if (needsToResetSelectedSubtype || !isInputMethodSubtypeSelected(resolver)) {
269             if (DEBUG) {
270                 Log.d(TAG, "--- Reset inputmethod subtype because it's not defined.");
271             }
272             putSelectedInputMethodSubtype(resolver, NOT_A_SUBTYPE_ID);
273         }
274 
275         Settings.Secure.putString(resolver,
276                 Settings.Secure.ENABLED_INPUT_METHODS, enabledIMEsAndSubtypesString);
277         if (disabledSystemIMEsString.length() > 0) {
278             Settings.Secure.putString(resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS,
279                     disabledSystemIMEsString);
280         }
281         // If the current input method is unset, InputMethodManagerService will find the applicable
282         // IME from the history and the system locale.
283         Settings.Secure.putString(resolver, Settings.Secure.DEFAULT_INPUT_METHOD,
284                 currentInputMethodId != null ? currentInputMethodId : "");
285     }
286 
loadInputMethodSubtypeList(final SettingsPreferenceFragment context, final ContentResolver resolver, final List<InputMethodInfo> inputMethodInfos, final Map<String, List<Preference>> inputMethodPrefsMap)287     static void loadInputMethodSubtypeList(final SettingsPreferenceFragment context,
288             final ContentResolver resolver, final List<InputMethodInfo> inputMethodInfos,
289             final Map<String, List<Preference>> inputMethodPrefsMap) {
290         final HashMap<String, HashSet<String>> enabledSubtypes =
291                 getEnabledInputMethodsAndSubtypeList(resolver);
292 
293         for (final InputMethodInfo imi : inputMethodInfos) {
294             final String imiId = imi.getId();
295             final Preference pref = context.findPreference(imiId);
296             if (pref instanceof TwoStatePreference) {
297                 final TwoStatePreference subtypePref = (TwoStatePreference) pref;
298                 final boolean isEnabled = enabledSubtypes.containsKey(imiId);
299                 subtypePref.setChecked(isEnabled);
300                 if (inputMethodPrefsMap != null) {
301                     for (final Preference childPref: inputMethodPrefsMap.get(imiId)) {
302                         childPref.setEnabled(isEnabled);
303                     }
304                 }
305                 setSubtypesPreferenceEnabled(context, inputMethodInfos, imiId, isEnabled);
306             }
307         }
308         updateSubtypesPreferenceChecked(context, inputMethodInfos, enabledSubtypes);
309     }
310 
setSubtypesPreferenceEnabled(final SettingsPreferenceFragment context, final List<InputMethodInfo> inputMethodProperties, final String id, final boolean enabled)311     static void setSubtypesPreferenceEnabled(final SettingsPreferenceFragment context,
312             final List<InputMethodInfo> inputMethodProperties, final String id,
313             final boolean enabled) {
314         final PreferenceScreen preferenceScreen = context.getPreferenceScreen();
315         for (final InputMethodInfo imi : inputMethodProperties) {
316             if (id.equals(imi.getId())) {
317                 final int subtypeCount = imi.getSubtypeCount();
318                 for (int i = 0; i < subtypeCount; ++i) {
319                     final InputMethodSubtype subtype = imi.getSubtypeAt(i);
320                     final TwoStatePreference pref = (TwoStatePreference) preferenceScreen
321                             .findPreference(id + subtype.hashCode());
322                     if (pref != null) {
323                         pref.setEnabled(enabled);
324                     }
325                 }
326             }
327         }
328     }
329 
updateSubtypesPreferenceChecked(final SettingsPreferenceFragment context, final List<InputMethodInfo> inputMethodProperties, final HashMap<String, HashSet<String>> enabledSubtypes)330     private static void updateSubtypesPreferenceChecked(final SettingsPreferenceFragment context,
331             final List<InputMethodInfo> inputMethodProperties,
332             final HashMap<String, HashSet<String>> enabledSubtypes) {
333         final PreferenceScreen preferenceScreen = context.getPreferenceScreen();
334         for (final InputMethodInfo imi : inputMethodProperties) {
335             final String id = imi.getId();
336             if (!enabledSubtypes.containsKey(id)) {
337                 // There is no need to enable/disable subtypes of disabled IMEs.
338                 continue;
339             }
340             final HashSet<String> enabledSubtypesSet = enabledSubtypes.get(id);
341             final int subtypeCount = imi.getSubtypeCount();
342             for (int i = 0; i < subtypeCount; ++i) {
343                 final InputMethodSubtype subtype = imi.getSubtypeAt(i);
344                 final String hashCode = String.valueOf(subtype.hashCode());
345                 if (DEBUG) {
346                     Log.d(TAG, "--- Set checked state: " + "id" + ", " + hashCode + ", "
347                             + enabledSubtypesSet.contains(hashCode));
348                 }
349                 final TwoStatePreference pref = (TwoStatePreference) preferenceScreen
350                         .findPreference(id + hashCode);
351                 if (pref != null) {
352                     pref.setChecked(enabledSubtypesSet.contains(hashCode));
353                 }
354             }
355         }
356     }
357 
removeUnnecessaryNonPersistentPreference(final Preference pref)358     static void removeUnnecessaryNonPersistentPreference(final Preference pref) {
359         final String key = pref.getKey();
360         if (pref.isPersistent() || key == null) {
361             return;
362         }
363         final SharedPreferences prefs = pref.getSharedPreferences();
364         if (prefs != null && prefs.contains(key)) {
365             prefs.edit().remove(key).apply();
366         }
367     }
368 }
369