diff options
3 files changed, 88 insertions, 16 deletions
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 307c55cfc2e7..d0405f0e5991 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -267,6 +267,12 @@ import java.util.function.Consumer; * they can switch to it, to confirm with the system that they know about it * and want to make it available for use.</p> * </ul> + * + * <p>If your app targets Android 11 (API level 30) or higher, the methods in + * this class each return a filtered result by the rules of + * <a href="/training/basics/intents/package-visibility">package visibility</a>, + * except for the currently connected IME. Apps having a query for the + * {@link InputMethod#SERVICE_INTERFACE} see all IMEs.</p> */ @SystemService(Context.INPUT_METHOD_SERVICE) @RequiresFeature(PackageManager.FEATURE_INPUT_METHODS) diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 00974e44e8bd..2be84d059286 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -2036,9 +2036,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (resolvedUserIds.length != 1) { return Collections.emptyList(); } + final int callingUid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); try { - return getInputMethodListLocked(resolvedUserIds[0], directBootAwareness); + return getInputMethodListLocked( + resolvedUserIds[0], directBootAwareness, callingUid); } finally { Binder.restoreCallingIdentity(ident); } @@ -2057,9 +2059,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (resolvedUserIds.length != 1) { return Collections.emptyList(); } + final int callingUid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); try { - return getEnabledInputMethodListLocked(resolvedUserIds[0]); + return getEnabledInputMethodListLocked(resolvedUserIds[0], callingUid); } finally { Binder.restoreCallingIdentity(ident); } @@ -2090,12 +2093,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId, - @DirectBootAwareness int directBootAwareness) { + @DirectBootAwareness int directBootAwareness, int callingUid) { final ArrayList<InputMethodInfo> methodList; + final InputMethodSettings settings; if (userId == mSettings.getCurrentUserId() && directBootAwareness == DirectBootAwareness.AUTO) { // Create a copy. methodList = new ArrayList<>(mMethodList); + settings = mSettings; } else { final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); methodList = new ArrayList<>(); @@ -2104,19 +2109,31 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap, methodList, directBootAwareness); + settings = new InputMethodSettings(mContext, methodMap, userId, true /* copyOnWrite */); } + // filter caller's access to input methods + methodList.removeIf(imi -> + !canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings)); return methodList; } @GuardedBy("ImfLock.class") - private List<InputMethodInfo> getEnabledInputMethodListLocked(@UserIdInt int userId) { + private List<InputMethodInfo> getEnabledInputMethodListLocked(@UserIdInt int userId, + int callingUid) { + final ArrayList<InputMethodInfo> methodList; + final InputMethodSettings settings; if (userId == mSettings.getCurrentUserId()) { - return mSettings.getEnabledInputMethodListLocked(); + methodList = mSettings.getEnabledInputMethodListLocked(); + settings = mSettings; + } else { + final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId); + settings = new InputMethodSettings(mContext, methodMap, userId, true /* copyOnWrite */); + methodList = settings.getEnabledInputMethodListLocked(); } - final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId); - final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap, userId, - true); - return settings.getEnabledInputMethodListLocked(); + // filter caller's access to input methods + methodList.removeIf(imi -> + !canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings)); + return methodList; } @GuardedBy("ImfLock.class") @@ -2155,10 +2172,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } synchronized (ImfLock.class) { + final int callingUid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); try { return getEnabledInputMethodSubtypeListLocked(imiId, - allowsImplicitlyEnabledSubtypes, userId); + allowsImplicitlyEnabledSubtypes, userId, callingUid); } finally { Binder.restoreCallingIdentity(ident); } @@ -2167,7 +2185,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId, - boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) { + boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId, int callingUid) { if (userId == mSettings.getCurrentUserId()) { final InputMethodInfo imi; String selectedMethodId = getSelectedMethodIdLocked(); @@ -2176,7 +2194,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } else { imi = mMethodMap.get(imiId); } - if (imi == null) { + if (imi == null || !canCallerAccessInputMethod( + imi.getPackageName(), callingUid, userId, mSettings)) { return Collections.emptyList(); } return mSettings.getEnabledInputMethodSubtypeListLocked( @@ -2189,6 +2208,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap, userId, true); + if (!canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings)) { + return Collections.emptyList(); + } return settings.getEnabledInputMethodSubtypeListLocked( imi, allowsImplicitlyEnabledSubtypes); } @@ -5438,6 +5460,34 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return true; } + /** + * Filter the access to the input method by rules of the package visibility. Return {@code true} + * if the given input method is the currently selected one or visible to the caller. + * + * @param targetPkgName The package name of input method to check. + * @param callingUid The caller that is going to access the input method. + * @param userId The user ID where the input method resides. + * @param settings The input method settings under the given user ID. + * @return {@code true} if caller is able to access the input method. + */ + private boolean canCallerAccessInputMethod(@NonNull String targetPkgName, int callingUid, + @UserIdInt int userId, @NonNull InputMethodSettings settings) { + final String methodId = settings.getSelectedInputMethod(); + final ComponentName selectedInputMethod = methodId != null + ? InputMethodUtils.convertIdToComponentName(methodId) : null; + if (selectedInputMethod != null + && selectedInputMethod.getPackageName().equals(targetPkgName)) { + return true; + } + final boolean canAccess = !mPackageManagerInternal.filterAppAccess( + targetPkgName, callingUid, userId); + if (DEBUG && !canAccess) { + Slog.d(TAG, "Input method " + targetPkgName + + " is not visible to the caller " + callingUid); + } + return canAccess; + } + private void publishLocalService() { LocalServices.addService(InputMethodManagerInternal.class, new LocalServiceImpl()); } @@ -5459,14 +5509,15 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) { synchronized (ImfLock.class) { - return getInputMethodListLocked(userId, DirectBootAwareness.AUTO); + return getInputMethodListLocked(userId, DirectBootAwareness.AUTO, + Process.SYSTEM_UID); } } @Override public List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId) { synchronized (ImfLock.class) { - return getEnabledInputMethodListLocked(userId); + return getEnabledInputMethodListLocked(userId, Process.SYSTEM_UID); } } @@ -6096,8 +6147,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub try (PrintWriter pr = shellCommand.getOutPrintWriter()) { for (int userId : userIds) { final List<InputMethodInfo> methods = all - ? getInputMethodListLocked(userId, DirectBootAwareness.AUTO) - : getEnabledInputMethodListLocked(userId); + ? getInputMethodListLocked( + userId, DirectBootAwareness.AUTO, Process.SHELL_UID) + : getEnabledInputMethodListLocked(userId, Process.SHELL_UID); if (userIds.length > 1) { pr.print("User #"); pr.print(userId); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java index d12f1131acbe..c7ff8caf176b 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java @@ -20,11 +20,13 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserHandleAware; import android.annotation.UserIdInt; +import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; +import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.os.Build; import android.os.UserHandle; @@ -998,4 +1000,16 @@ final class InputMethodUtils { } return new int[]{sourceUserId}; } + + /** + * Convert the input method ID to a component name + * + * @param id A unique ID for this input method. + * @return The component name of the input method. + * @see InputMethodInfo#computeId(ResolveInfo) + */ + @Nullable + public static ComponentName convertIdToComponentName(@NonNull String id) { + return ComponentName.unflattenFromString(id); + } } |