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