1 /*
2  * Copyright (C) 2021 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.tv.settings.library.settingslib;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.ContentResolver;
22 import android.content.Context;
23 import android.content.res.Configuration;
24 import android.icu.text.ListFormatter;
25 import android.provider.Settings;
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.app.LocaleHelper;
32 
33 import java.util.HashMap;
34 import java.util.HashSet;
35 import java.util.List;
36 import java.util.Locale;
37 
38 public class InputMethodAndSubtypeUtil {
39 
40     private static final boolean DEBUG = false;
41     private static final String TAG = "InputMethdAndSubtypeUtl";
42 
43     private static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
44     private static final char INPUT_METHOD_SEPARATER = ':';
45     private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
46     private static final int NOT_A_SUBTYPE_ID = -1;
47 
48     private static final TextUtils.SimpleStringSplitter sStringInputMethodSplitter
49             = new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
50 
51     private static final TextUtils.SimpleStringSplitter sStringInputMethodSubtypeSplitter
52             = new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
53 
54     // InputMethods and subtypes are saved in the settings as follows:
55     // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
buildInputMethodsAndSubtypesString( final HashMap<String, HashSet<String>> imeToSubtypesMap)56     public static String buildInputMethodsAndSubtypesString(
57             final HashMap<String, HashSet<String>> imeToSubtypesMap) {
58         final StringBuilder builder = new StringBuilder();
59         for (final String imi : imeToSubtypesMap.keySet()) {
60             if (builder.length() > 0) {
61                 builder.append(INPUT_METHOD_SEPARATER);
62             }
63             final HashSet<String> subtypeIdSet = imeToSubtypesMap.get(imi);
64             builder.append(imi);
65             for (final String subtypeId : subtypeIdSet) {
66                 builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
67             }
68         }
69         return builder.toString();
70     }
71 
buildInputMethodsString(final HashSet<String> imiList)72     private static String buildInputMethodsString(final HashSet<String> imiList) {
73         final StringBuilder builder = new StringBuilder();
74         for (final String imi : imiList) {
75             if (builder.length() > 0) {
76                 builder.append(INPUT_METHOD_SEPARATER);
77             }
78             builder.append(imi);
79         }
80         return builder.toString();
81     }
82 
getInputMethodSubtypeSelected(ContentResolver resolver)83     private static int getInputMethodSubtypeSelected(ContentResolver resolver) {
84         try {
85             return Settings.Secure.getInt(resolver,
86                     Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE);
87         } catch (Settings.SettingNotFoundException e) {
88             return NOT_A_SUBTYPE_ID;
89         }
90     }
91 
isInputMethodSubtypeSelected(ContentResolver resolver)92     private static boolean isInputMethodSubtypeSelected(ContentResolver resolver) {
93         return getInputMethodSubtypeSelected(resolver) != NOT_A_SUBTYPE_ID;
94     }
95 
putSelectedInputMethodSubtype(ContentResolver resolver, int hashCode)96     private static void putSelectedInputMethodSubtype(ContentResolver resolver, int hashCode) {
97         Settings.Secure.putInt(resolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, hashCode);
98     }
99 
100     // Needs to modify InputMethodManageService if you want to change the format of saved string.
getEnabledInputMethodsAndSubtypeList( ContentResolver resolver)101     static HashMap<String, HashSet<String>> getEnabledInputMethodsAndSubtypeList(
102             ContentResolver resolver) {
103         final String enabledInputMethodsStr = Settings.Secure.getString(
104                 resolver, Settings.Secure.ENABLED_INPUT_METHODS);
105         if (DEBUG) {
106             Log.d(TAG, "--- Load enabled input methods: " + enabledInputMethodsStr);
107         }
108         return parseInputMethodsAndSubtypesString(enabledInputMethodsStr);
109     }
110 
parseInputMethodsAndSubtypesString( final String inputMethodsAndSubtypesString)111     public static HashMap<String, HashSet<String>> parseInputMethodsAndSubtypesString(
112             final String inputMethodsAndSubtypesString) {
113         final HashMap<String, HashSet<String>> subtypesMap = new HashMap<>();
114         if (TextUtils.isEmpty(inputMethodsAndSubtypesString)) {
115             return subtypesMap;
116         }
117         sStringInputMethodSplitter.setString(inputMethodsAndSubtypesString);
118         while (sStringInputMethodSplitter.hasNext()) {
119             final String nextImsStr = sStringInputMethodSplitter.next();
120             sStringInputMethodSubtypeSplitter.setString(nextImsStr);
121             if (sStringInputMethodSubtypeSplitter.hasNext()) {
122                 final HashSet<String> subtypeIdSet = new HashSet<>();
123                 // The first element is {@link InputMethodInfoId}.
124                 final String imiId = sStringInputMethodSubtypeSplitter.next();
125                 while (sStringInputMethodSubtypeSplitter.hasNext()) {
126                     subtypeIdSet.add(sStringInputMethodSubtypeSplitter.next());
127                 }
128                 subtypesMap.put(imiId, subtypeIdSet);
129             }
130         }
131         return subtypesMap;
132     }
133 
getDisabledSystemIMEs(ContentResolver resolver)134     private static HashSet<String> getDisabledSystemIMEs(ContentResolver resolver) {
135         HashSet<String> set = new HashSet<>();
136         String disabledIMEsStr = Settings.Secure.getString(
137                 resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS);
138         if (TextUtils.isEmpty(disabledIMEsStr)) {
139             return set;
140         }
141         sStringInputMethodSplitter.setString(disabledIMEsStr);
142         while (sStringInputMethodSplitter.hasNext()) {
143             set.add(sStringInputMethodSplitter.next());
144         }
145         return set;
146     }
147 
148 
149     @NonNull
getSubtypeLocaleNameAsSentence(@ullable InputMethodSubtype subtype, @NonNull final Context context, @NonNull final InputMethodInfo inputMethodInfo)150     public static String getSubtypeLocaleNameAsSentence(@Nullable InputMethodSubtype subtype,
151             @NonNull final Context context, @NonNull final InputMethodInfo inputMethodInfo) {
152         if (subtype == null) {
153             return "";
154         }
155         final Locale locale = getDisplayLocale(context);
156         final CharSequence subtypeName = subtype.getDisplayName(context,
157                 inputMethodInfo.getPackageName(), inputMethodInfo.getServiceInfo()
158                         .applicationInfo);
159         return LocaleHelper.toSentenceCase(subtypeName.toString(), locale);
160     }
161 
162     @NonNull
getSubtypeLocaleNameListAsSentence( @onNull final List<InputMethodSubtype> subtypes, @NonNull final Context context, @NonNull final InputMethodInfo inputMethodInfo)163     public static String getSubtypeLocaleNameListAsSentence(
164             @NonNull final List<InputMethodSubtype> subtypes, @NonNull final Context context,
165             @NonNull final InputMethodInfo inputMethodInfo) {
166         if (subtypes.isEmpty()) {
167             return "";
168         }
169         final Locale locale = getDisplayLocale(context);
170         final int subtypeCount = subtypes.size();
171         final CharSequence[] subtypeNames = new CharSequence[subtypeCount];
172         for (int i = 0; i < subtypeCount; i++) {
173             subtypeNames[i] = subtypes.get(i).getDisplayName(context,
174                     inputMethodInfo.getPackageName(), inputMethodInfo.getServiceInfo()
175                             .applicationInfo);
176         }
177         return LocaleHelper.toSentenceCase(
178                 ListFormatter.getInstance(locale).format((Object[]) subtypeNames), locale);
179     }
180 
181     @NonNull
getDisplayLocale(@ullable final Context context)182     private static Locale getDisplayLocale(@Nullable final Context context) {
183         if (context == null) {
184             return Locale.getDefault();
185         }
186         if (context.getResources() == null) {
187             return Locale.getDefault();
188         }
189         final Configuration configuration = context.getResources().getConfiguration();
190         if (configuration == null) {
191             return Locale.getDefault();
192         }
193         final Locale configurationLocale = configuration.getLocales().get(0);
194         if (configurationLocale == null) {
195             return Locale.getDefault();
196         }
197         return configurationLocale;
198     }
199 
isValidNonAuxAsciiCapableIme(InputMethodInfo imi)200     public static boolean isValidNonAuxAsciiCapableIme(InputMethodInfo imi) {
201         if (imi.isAuxiliaryIme()) {
202             return false;
203         }
204         final int subtypeCount = imi.getSubtypeCount();
205         for (int i = 0; i < subtypeCount; ++i) {
206             final InputMethodSubtype subtype = imi.getSubtypeAt(i);
207             if (SUBTYPE_MODE_KEYBOARD.equalsIgnoreCase(subtype.getMode())
208                     && subtype.isAsciiCapable()) {
209                 return true;
210             }
211         }
212         return false;
213     }
214 }
215 
216