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