1 /*
2  * Copyright (C) 2013 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.server.inputmethod;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.UserHandleAware;
22 import android.annotation.UserIdInt;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.pm.ApplicationInfo;
26 import android.content.pm.PackageManager;
27 import android.content.pm.PackageManagerInternal;
28 import android.content.pm.ResolveInfo;
29 import android.content.res.Resources;
30 import android.os.Build;
31 import android.os.UserHandle;
32 import android.provider.Settings;
33 import android.text.TextUtils;
34 import android.util.ArraySet;
35 import android.util.Slog;
36 import android.view.inputmethod.InputMethodInfo;
37 import android.view.inputmethod.InputMethodSubtype;
38 import android.view.textservice.SpellCheckerInfo;
39 
40 import com.android.internal.annotations.VisibleForTesting;
41 import com.android.internal.inputmethod.StartInputFlags;
42 import com.android.server.LocalServices;
43 import com.android.server.pm.UserManagerInternal;
44 import com.android.server.textservices.TextServicesManagerInternal;
45 
46 import java.io.PrintWriter;
47 import java.util.ArrayList;
48 import java.util.List;
49 import java.util.StringJoiner;
50 import java.util.function.Consumer;
51 
52 /**
53  * This class provides random static utility methods for {@link InputMethodManagerService} and its
54  * utility classes.
55  *
56  * <p>This class is intentionally package-private.  Utility methods here are tightly coupled with
57  * implementation details in {@link InputMethodManagerService}.  Hence this class is not suitable
58  * for other components to directly use.</p>
59  */
60 final class InputMethodUtils {
61     public static final boolean DEBUG = false;
62     static final int NOT_A_SUBTYPE_ID = -1;
63     private static final String TAG = "InputMethodUtils";
64 
65     // The string for enabled input method is saved as follows:
66     // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
67     static final char INPUT_METHOD_SEPARATOR = ':';
68     static final char INPUT_METHOD_SUBTYPE_SEPARATOR = ';';
69 
InputMethodUtils()70     private InputMethodUtils() {
71         // This utility class is not publicly instantiable.
72     }
73 
74     // ----------------------------------------------------------------------
75 
canAddToLastInputMethod(InputMethodSubtype subtype)76     static boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
77         if (subtype == null) return true;
78         return !subtype.isAuxiliary();
79     }
80 
81     @UserHandleAware
setNonSelectedSystemImesDisabledUntilUsed(PackageManager packageManagerForUser, List<InputMethodInfo> enabledImis)82     static void setNonSelectedSystemImesDisabledUntilUsed(PackageManager packageManagerForUser,
83             List<InputMethodInfo> enabledImis) {
84         if (DEBUG) {
85             Slog.d(TAG, "setNonSelectedSystemImesDisabledUntilUsed");
86         }
87         final String[] systemImesDisabledUntilUsed = Resources.getSystem().getStringArray(
88                 com.android.internal.R.array.config_disabledUntilUsedPreinstalledImes);
89         if (systemImesDisabledUntilUsed == null || systemImesDisabledUntilUsed.length == 0) {
90             return;
91         }
92         // Only the current spell checker should be treated as an enabled one.
93         final SpellCheckerInfo currentSpellChecker =
94                 TextServicesManagerInternal.get().getCurrentSpellCheckerForUser(
95                         packageManagerForUser.getUserId());
96         for (final String packageName : systemImesDisabledUntilUsed) {
97             if (DEBUG) {
98                 Slog.d(TAG, "check " + packageName);
99             }
100             boolean enabledIme = false;
101             for (int j = 0; j < enabledImis.size(); ++j) {
102                 final InputMethodInfo imi = enabledImis.get(j);
103                 if (packageName.equals(imi.getPackageName())) {
104                     enabledIme = true;
105                     break;
106                 }
107             }
108             if (enabledIme) {
109                 // enabled ime. skip
110                 continue;
111             }
112             if (currentSpellChecker != null
113                     && packageName.equals(currentSpellChecker.getPackageName())) {
114                 // enabled spell checker. skip
115                 if (DEBUG) {
116                     Slog.d(TAG, packageName + " is the current spell checker. skip");
117                 }
118                 continue;
119             }
120             ApplicationInfo ai;
121             try {
122                 ai = packageManagerForUser.getApplicationInfo(packageName,
123                         PackageManager.ApplicationInfoFlags.of(
124                                 PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS));
125             } catch (PackageManager.NameNotFoundException e) {
126                 // This is not an error.  No need to show scary error messages.
127                 if (DEBUG) {
128                     Slog.d(TAG, packageName
129                             + " does not exist for userId=" + packageManagerForUser.getUserId());
130                 }
131                 continue;
132             }
133             if (ai == null) {
134                 // No app found for packageName
135                 continue;
136             }
137             final boolean isSystemPackage = (ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
138             if (!isSystemPackage) {
139                 continue;
140             }
141             setDisabledUntilUsed(packageManagerForUser, packageName);
142         }
143     }
144 
setDisabledUntilUsed(PackageManager packageManagerForUser, String packageName)145     private static void setDisabledUntilUsed(PackageManager packageManagerForUser,
146             String packageName) {
147         final int state;
148         try {
149             state = packageManagerForUser.getApplicationEnabledSetting(packageName);
150         } catch (IllegalArgumentException e) {
151             Slog.w(TAG, "getApplicationEnabledSetting failed. packageName=" + packageName
152                     + " userId=" + packageManagerForUser.getUserId(), e);
153             return;
154         }
155         if (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
156                 || state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
157             if (DEBUG) {
158                 Slog.d(TAG, "Update state(" + packageName + "): DISABLED_UNTIL_USED");
159             }
160             try {
161                 packageManagerForUser.setApplicationEnabledSetting(packageName,
162                         PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
163                         0 /* newState */);
164             } catch (IllegalArgumentException e) {
165                 Slog.w(TAG, "setApplicationEnabledSetting failed. packageName=" + packageName
166                         + " userId=" + packageManagerForUser.getUserId(), e);
167                 return;
168             }
169         } else {
170             if (DEBUG) {
171                 Slog.d(TAG, packageName + " is already DISABLED_UNTIL_USED");
172             }
173         }
174     }
175 
176     /**
177      * Returns true if a package name belongs to a UID.
178      *
179      * <p>This is a simple wrapper of
180      * {@link PackageManagerInternal#getPackageUid(String, long, int)}.</p>
181      * @param packageManagerInternal the {@link PackageManagerInternal} object to be used for the
182      *                               validation.
183      * @param uid the UID to be validated.
184      * @param packageName the package name.
185      * @return {@code true} if the package name belongs to the UID.
186      */
checkIfPackageBelongsToUid(PackageManagerInternal packageManagerInternal, int uid, String packageName)187     static boolean checkIfPackageBelongsToUid(PackageManagerInternal packageManagerInternal,
188             int uid, String packageName) {
189         // PackageManagerInternal#getPackageUid() doesn't check MATCH_INSTANT/MATCH_APEX as of
190         // writing. So setting 0 should be fine.
191         return packageManagerInternal.isSameApp(packageName, /* flags= */ 0, uid,
192             UserHandle.getUserId(uid));
193     }
194 
isSoftInputModeStateVisibleAllowed(int targetSdkVersion, @StartInputFlags int startInputFlags)195     static boolean isSoftInputModeStateVisibleAllowed(int targetSdkVersion,
196             @StartInputFlags int startInputFlags) {
197         if (targetSdkVersion < Build.VERSION_CODES.P) {
198             // for compatibility.
199             return true;
200         }
201         if ((startInputFlags & StartInputFlags.VIEW_HAS_FOCUS) == 0) {
202             return false;
203         }
204         if ((startInputFlags & StartInputFlags.IS_TEXT_EDITOR) == 0) {
205             return false;
206         }
207         return true;
208     }
209 
210     /**
211      * Converts a user ID, which can be a pseudo user ID such as {@link UserHandle#USER_ALL} to a
212      * list of real user IDs.
213      *
214      * @param userIdToBeResolved A user ID. Two pseudo user ID {@link UserHandle#USER_CURRENT} and
215      *                           {@link UserHandle#USER_ALL} are also supported
216      * @param currentUserId A real user ID, which will be used when {@link UserHandle#USER_CURRENT}
217      *                      is specified in {@code userIdToBeResolved}.
218      * @param warningWriter A {@link PrintWriter} to output some debug messages. {@code null} if
219      *                      no debug message is required.
220      * @return An integer array that contain user IDs.
221      */
resolveUserId(@serIdInt int userIdToBeResolved, @UserIdInt int currentUserId, @Nullable PrintWriter warningWriter)222     static int[] resolveUserId(@UserIdInt int userIdToBeResolved,
223             @UserIdInt int currentUserId, @Nullable PrintWriter warningWriter) {
224         final UserManagerInternal userManagerInternal =
225                 LocalServices.getService(UserManagerInternal.class);
226 
227         if (userIdToBeResolved == UserHandle.USER_ALL) {
228             return userManagerInternal.getUserIds();
229         }
230 
231         final int sourceUserId;
232         if (userIdToBeResolved == UserHandle.USER_CURRENT) {
233             sourceUserId = currentUserId;
234         } else if (userIdToBeResolved < 0) {
235             if (warningWriter != null) {
236                 warningWriter.print("Pseudo user ID ");
237                 warningWriter.print(userIdToBeResolved);
238                 warningWriter.println(" is not supported.");
239             }
240             return new int[]{};
241         } else if (userManagerInternal.exists(userIdToBeResolved)) {
242             sourceUserId = userIdToBeResolved;
243         } else {
244             if (warningWriter != null) {
245                 warningWriter.print("User #");
246                 warningWriter.print(userIdToBeResolved);
247                 warningWriter.println(" does not exit.");
248             }
249             return new int[]{};
250         }
251         return new int[]{sourceUserId};
252     }
253 
254     /**
255      * Returns a list of enabled IME IDs to address Bug 261723412.
256      *
257      * <p>This is a temporary workaround until we come up with a better solution. Do not use this
258      * for anything other than Bug 261723412.</p>
259      *
260      * @param context {@link Context} object to query secure settings.
261      * @param userId User ID to query about.
262      * @return A list of enabled IME IDs.
263      */
264     @NonNull
getEnabledInputMethodIdsForFiltering(@onNull Context context, @UserIdInt int userId)265     static List<String> getEnabledInputMethodIdsForFiltering(@NonNull Context context,
266             @UserIdInt int userId) {
267         final String enabledInputMethodsStr = TextUtils.nullIfEmpty(
268                 SecureSettingsWrapper.getString(Settings.Secure.ENABLED_INPUT_METHODS, null,
269                         userId));
270         final ArrayList<String> result = new ArrayList<>();
271         splitEnabledImeStr(enabledInputMethodsStr, result::add);
272         return result;
273     }
274 
275     /**
276      * Split enabled IME string ({@link Settings.Secure#ENABLED_INPUT_METHODS}) into IME IDs.
277      *
278      * @param text a text formatted with {@link Settings.Secure#ENABLED_INPUT_METHODS}.
279      * @param consumer {@link Consumer} called back when a new IME ID is found.
280      */
281     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
splitEnabledImeStr(@ullable String text, @NonNull Consumer<String> consumer)282     static void splitEnabledImeStr(@Nullable String text, @NonNull Consumer<String> consumer) {
283         if (TextUtils.isEmpty(text)) {
284             return;
285         }
286         final TextUtils.SimpleStringSplitter inputMethodSplitter =
287                 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
288         final TextUtils.SimpleStringSplitter subtypeSplitter =
289                 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
290         inputMethodSplitter.setString(text);
291         while (inputMethodSplitter.hasNext()) {
292             String nextImsStr = inputMethodSplitter.next();
293             subtypeSplitter.setString(nextImsStr);
294             if (subtypeSplitter.hasNext()) {
295                 // The first element is ime id.
296                 consumer.accept(subtypeSplitter.next());
297             }
298         }
299     }
300 
301     /**
302      * Concat given IME IDs with an existing enabled IME
303      * ({@link Settings.Secure#ENABLED_INPUT_METHODS}).
304      *
305      * @param existingEnabledImeId an existing {@link Settings.Secure#ENABLED_INPUT_METHODS} to
306      *                             which {@code imeIDs} will be added.
307      * @param imeIds an array of IME IDs to be added. For IME IDs that are already seen in
308      *               {@code existingEnabledImeId} will be skipped.
309      * @return a new enabled IME ID string that can be stored in
310      *         {@link Settings.Secure#ENABLED_INPUT_METHODS}.
311      */
312     @NonNull
concatEnabledImeIds(@onNull String existingEnabledImeId, @NonNull String... imeIds)313     static String concatEnabledImeIds(@NonNull String existingEnabledImeId,
314             @NonNull String... imeIds) {
315         final ArraySet<String> alreadyEnabledIds = new ArraySet<>();
316         final StringJoiner joiner = new StringJoiner(Character.toString(INPUT_METHOD_SEPARATOR));
317         if (!TextUtils.isEmpty(existingEnabledImeId)) {
318             splitEnabledImeStr(existingEnabledImeId, alreadyEnabledIds::add);
319             joiner.add(existingEnabledImeId);
320         }
321         for (String id : imeIds) {
322             if (!alreadyEnabledIds.contains(id)) {
323                 joiner.add(id);
324                 alreadyEnabledIds.add(id);
325             }
326         }
327         return joiner.toString();
328     }
329 
330     /**
331      * Convert the input method ID to a component name
332      *
333      * @param id A unique ID for this input method.
334      * @return The component name of the input method.
335      * @see InputMethodInfo#computeId(ResolveInfo)
336      */
337     @Nullable
convertIdToComponentName(@onNull String id)338     public static ComponentName convertIdToComponentName(@NonNull String id) {
339         return ComponentName.unflattenFromString(id);
340     }
341 }
342