1 /* 2 * Copyright (C) 2019 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.car.settings.inputmethod; 18 19 import android.app.admin.DevicePolicyManager; 20 import android.content.ContentResolver; 21 import android.content.Context; 22 import android.content.pm.PackageManager; 23 import android.content.pm.PackageManager.NameNotFoundException; 24 import android.graphics.Color; 25 import android.graphics.drawable.ColorDrawable; 26 import android.graphics.drawable.Drawable; 27 import android.provider.Settings; 28 import android.text.TextUtils; 29 import android.view.inputmethod.InputMethodInfo; 30 import android.view.inputmethod.InputMethodManager; 31 import android.view.inputmethod.InputMethodSubtype; 32 33 import androidx.annotation.NonNull; 34 import androidx.annotation.VisibleForTesting; 35 36 import com.android.settingslib.inputmethod.InputMethodAndSubtypeUtil; 37 38 import java.util.List; 39 import java.util.stream.Collectors; 40 41 /** Keyboard utility class. */ 42 public final class InputMethodUtil { 43 /** 44 * Delimiter for Enabled Input Methods' concatenated string. 45 */ 46 public static final char INPUT_METHOD_DELIMITER = ':'; 47 /** 48 * A list of past and present Google Voice Typing package names 49 */ 50 public static final List<String> GVT_PACKAGE_NAMES = List.of( 51 "com.google.android.tts", 52 "com.google.android.carassistant", 53 "com.google.android.googlequicksearchbox" 54 ); 55 /** 56 * Splitter for Enabled Input Methods' concatenated string. 57 */ 58 public static final TextUtils.SimpleStringSplitter sInputMethodSplitter = 59 new TextUtils.SimpleStringSplitter(INPUT_METHOD_DELIMITER); 60 @VisibleForTesting 61 static final Drawable NO_ICON = new ColorDrawable(Color.TRANSPARENT); 62 InputMethodUtil()63 private InputMethodUtil() { 64 } 65 66 /** Returns permitted list of enabled input methods. */ getPermittedAndEnabledInputMethodList( InputMethodManager imm, DevicePolicyManager dpm)67 public static List<InputMethodInfo> getPermittedAndEnabledInputMethodList( 68 InputMethodManager imm, DevicePolicyManager dpm) { 69 List<InputMethodInfo> inputMethodInfos = imm.getEnabledInputMethodList(); 70 if (inputMethodInfos != null) { 71 // permittedList == null means all input methods are allowed. 72 List<String> permittedList = dpm.getPermittedInputMethodsForCurrentUser(); 73 74 inputMethodInfos = inputMethodInfos.stream().filter(info -> { 75 boolean isAllowedByOrganization = permittedList == null 76 || permittedList.contains(info.getPackageName()); 77 // Hide "Google voice typing" IME. 78 boolean isGoogleVoiceTyping = 79 InputMethodUtil.GVT_PACKAGE_NAMES.contains(info.getPackageName()); 80 return isAllowedByOrganization && !isGoogleVoiceTyping; 81 }).collect(Collectors.toList()); 82 } 83 return inputMethodInfos; 84 } 85 86 /** Returns package icon. */ getPackageIcon(@onNull PackageManager packageManager, @NonNull InputMethodInfo inputMethodInfo)87 public static Drawable getPackageIcon(@NonNull PackageManager packageManager, 88 @NonNull InputMethodInfo inputMethodInfo) { 89 Drawable icon; 90 try { 91 icon = packageManager.getApplicationIcon(inputMethodInfo.getPackageName()); 92 } catch (NameNotFoundException e) { 93 icon = NO_ICON; 94 } 95 96 return icon; 97 } 98 99 /** Returns package label. */ getPackageLabel(@onNull PackageManager packageManager, @NonNull InputMethodInfo inputMethodInfo)100 public static String getPackageLabel(@NonNull PackageManager packageManager, 101 @NonNull InputMethodInfo inputMethodInfo) { 102 return inputMethodInfo.loadLabel(packageManager).toString(); 103 } 104 105 /** Returns input method summary. */ getSummaryString(@onNull Context context, @NonNull InputMethodManager inputMethodManager, @NonNull InputMethodInfo inputMethodInfo)106 public static String getSummaryString(@NonNull Context context, 107 @NonNull InputMethodManager inputMethodManager, 108 @NonNull InputMethodInfo inputMethodInfo) { 109 List<InputMethodSubtype> subtypes = 110 inputMethodManager.getEnabledInputMethodSubtypeList( 111 inputMethodInfo, /* allowsImplicitlySelectedSubtypes= */ true); 112 return InputMethodAndSubtypeUtil.getSubtypeLocaleNameListAsSentence( 113 subtypes, context, inputMethodInfo); 114 } 115 116 /** 117 * Check if input method is enabled. 118 * 119 * @return {@code true} if the input method is enabled. 120 */ isInputMethodEnabled(ContentResolver resolver, InputMethodInfo inputMethodInfo)121 public static boolean isInputMethodEnabled(ContentResolver resolver, 122 InputMethodInfo inputMethodInfo) { 123 String enabledImes = getEnabledInputMethodsConcatenatedIds(resolver); 124 if (TextUtils.isEmpty(enabledImes)) { 125 return false; 126 } 127 sInputMethodSplitter.setString(enabledImes); 128 while (sInputMethodSplitter.hasNext()) { 129 String inputMethodId = sInputMethodSplitter.next(); 130 if (inputMethodId.equals(inputMethodInfo.getId())) { 131 return true; 132 } 133 } 134 return false; 135 } 136 137 /** 138 * Enable an input method using its InputMethodInfo. 139 */ enableInputMethod(ContentResolver resolver, InputMethodInfo inputMethodInfo)140 public static void enableInputMethod(ContentResolver resolver, 141 InputMethodInfo inputMethodInfo) { 142 if (isInputMethodEnabled(resolver, inputMethodInfo)) { 143 return; 144 } 145 146 StringBuilder builder = new StringBuilder(); 147 builder.append(getEnabledInputMethodsConcatenatedIds(resolver)); 148 149 if (!builder.toString().isEmpty()) { 150 builder.append(INPUT_METHOD_DELIMITER); 151 } 152 153 builder.append(inputMethodInfo.getId()); 154 155 setEnabledInputMethodsConcatenatedIds(resolver, builder.toString()); 156 } 157 158 /** 159 * Disable an input method if its not the default system input method or if there exists another 160 * enabled input method that can also be set as the default system input method. 161 */ disableInputMethod(Context context, InputMethodManager inputMethodManager, InputMethodInfo inputMethodInfo)162 public static void disableInputMethod(Context context, InputMethodManager inputMethodManager, 163 InputMethodInfo inputMethodInfo) { 164 List<InputMethodInfo> enabledInputMethodInfos = inputMethodManager 165 .getEnabledInputMethodList(); 166 StringBuilder builder = new StringBuilder(); 167 168 boolean foundAnotherEnabledDefaultInputMethod = false; 169 boolean isSystemDefault = isDefaultInputMethod(context.getContentResolver(), 170 inputMethodInfo); 171 for (InputMethodInfo enabledInputMethodInfo : enabledInputMethodInfos) { 172 if (enabledInputMethodInfo.getId().equals(inputMethodInfo.getId())) { 173 continue; 174 } 175 176 if (builder.length() > 0) { 177 builder.append(INPUT_METHOD_DELIMITER); 178 } 179 180 builder.append(enabledInputMethodInfo.getId()); 181 182 if (isSystemDefault && enabledInputMethodInfo.isDefault(context)) { 183 foundAnotherEnabledDefaultInputMethod = true; 184 setDefaultInputMethodId(context.getContentResolver(), 185 enabledInputMethodInfo.getId()); 186 } 187 } 188 189 if (isSystemDefault && !foundAnotherEnabledDefaultInputMethod) { 190 return; 191 } 192 193 setEnabledInputMethodsConcatenatedIds(context.getContentResolver(), builder.toString()); 194 } 195 getEnabledInputMethodsConcatenatedIds(ContentResolver resolver)196 private static String getEnabledInputMethodsConcatenatedIds(ContentResolver resolver) { 197 return Settings.Secure.getString(resolver, Settings.Secure.ENABLED_INPUT_METHODS); 198 } 199 getDefaultInputMethodId(ContentResolver resolver)200 private static String getDefaultInputMethodId(ContentResolver resolver) { 201 return Settings.Secure.getString(resolver, Settings.Secure.DEFAULT_INPUT_METHOD); 202 } 203 isDefaultInputMethod(ContentResolver resolver, InputMethodInfo inputMethodInfo)204 private static boolean isDefaultInputMethod(ContentResolver resolver, 205 InputMethodInfo inputMethodInfo) { 206 return inputMethodInfo.getId().equals(getDefaultInputMethodId(resolver)); 207 } 208 setEnabledInputMethodsConcatenatedIds(ContentResolver resolver, String enabledInputMethodIds)209 private static void setEnabledInputMethodsConcatenatedIds(ContentResolver resolver, 210 String enabledInputMethodIds) { 211 Settings.Secure.putString(resolver, Settings.Secure.ENABLED_INPUT_METHODS, 212 enabledInputMethodIds); 213 } 214 setDefaultInputMethodId(ContentResolver resolver, String defaultInputMethodId)215 private static void setDefaultInputMethodId(ContentResolver resolver, 216 String defaultInputMethodId) { 217 Settings.Secure.putString(resolver, Settings.Secure.DEFAULT_INPUT_METHOD, 218 defaultInputMethodId); 219 } 220 } 221