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