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