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 sInputMethodSplitter.setString(getEnabledInputMethodsConcatenatedIds(resolver)); 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