Package visibility improvement for the InputMethodManager
A few APIs in the InputMethodManager disclose the installed input method packages to apps without holding the query package permission. This CL filters the caller's access to input methods by rules of package visibility, except for the currently selected input method. Updated APIs: - getInputMethodList - getEnabledInputMethodList - getEnabledInputMethodSubtypeList - getShortcutInputMethodsAndSubtypes Bug: 179783492 Bug: 179783499 Bug: 216823971 Test: atest CtsInputMethodServiceHostTestCases Test: atest CtsInputMethodTestCases Test: atest CtsInputMethodTestCases32 Test: atest CtsAppEnumerationTestCases Change-Id: I698b54192b827abeba475df90e099aefe6dd0e18
This commit is contained in:
@@ -265,6 +265,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)
|
||||
|
||||
@@ -2045,9 +2045,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);
|
||||
}
|
||||
@@ -2066,9 +2068,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);
|
||||
}
|
||||
@@ -2099,12 +2102,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<>();
|
||||
@@ -2113,19 +2118,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")
|
||||
@@ -2164,10 +2181,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);
|
||||
}
|
||||
@@ -2176,7 +2194,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();
|
||||
@@ -2185,7 +2203,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(
|
||||
@@ -2198,6 +2217,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);
|
||||
}
|
||||
@@ -5426,6 +5448,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());
|
||||
}
|
||||
@@ -5447,14 +5497,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6084,8 +6135,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);
|
||||
|
||||
@@ -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;
|
||||
@@ -1000,4 +1002,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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user