From a6a2072452064974f6de101c418727c5d17d67b4 Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Thu, 28 Jul 2022 09:04:20 +0900 Subject: [PATCH] Enable background users to call IMM#getCurrentInputMethodSubtype() This is a follow up CL to my previous CLs [1][2], which let query APIs defined in InputMethodManager support background users. With this CL, InputMethodManager#getCurrentInputMethodSubtype() is also fully supported under multi-user / multi-profile environment. [1]: I192a0f5a1375170d17a4c08af94f23966dbaea8b 7f8ee4b9ddd31ad36a12c5278b27990dc76011cc [2]: I48f57dc7184e85bdb422fd9d1d56e60381654125 e960986554fb7969d83c57689abbcee949d6a37b Bug: 34886274 Bug: 122164939 Bug: 237316307 Test: atest CtsInputMethodTestCases:InputMethodSubtypeTest Change-Id: Icb09f9cb1a4147884faa9952b2e03ec4afa9f0b1 --- .../IInputMethodManagerInvoker.java | 4 +- .../view/inputmethod/InputMethodManager.java | 2 +- .../internal/view/IInputMethodManager.aidl | 5 +- .../InputMethodManagerService.java | 30 +++++++++-- .../server/inputmethod/InputMethodUtils.java | 50 +++++++++++++++++++ 5 files changed, 82 insertions(+), 9 deletions(-) diff --git a/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java index 60edf357a53d7..d21ceb2ec79e2 100644 --- a/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java +++ b/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java @@ -182,9 +182,9 @@ final class IInputMethodManagerInvoker { @AnyThread @Nullable - InputMethodSubtype getCurrentInputMethodSubtype() { + InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId) { try { - return mTarget.getCurrentInputMethodSubtype(); + return mTarget.getCurrentInputMethodSubtype(userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 8ce134e7e1014..e7abbbcee7cad 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -3374,7 +3374,7 @@ public final class InputMethodManager { */ @Nullable public InputMethodSubtype getCurrentInputMethodSubtype() { - return mServiceInvoker.getCurrentInputMethodSubtype(); + return mServiceInvoker.getCurrentInputMethodSubtype(UserHandle.myUserId()); } /** diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index eb8b8603cc30e..d5f835295ee78 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -88,7 +88,10 @@ interface IInputMethodManager { + "android.Manifest.permission.TEST_INPUT_METHOD)") boolean isInputMethodPickerShownForTest(); - @nullable InputMethodSubtype getCurrentInputMethodSubtype(); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " + + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)") + @nullable InputMethodSubtype getCurrentInputMethodSubtype(int userId); + void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes); // This is kept due to @UnsupportedAppUsage. // TODO(Bug 113914148): Consider removing this. diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index e20f0df597300..af95b4f54eb42 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -5284,18 +5284,38 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub /** * Gets the current subtype of this input method. + * + * @param userId User ID to be queried about. + * @return The current {@link InputMethodSubtype} for the specified user. */ + @Nullable @Override - public InputMethodSubtype getCurrentInputMethodSubtype() { + public InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId) { + if (UserHandle.getCallingUserId() != userId) { + mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); + } synchronized (ImfLock.class) { - // TODO: Make this work even for non-current users? - if (!calledFromValidUserLocked()) { - return null; + if (mSettings.getCurrentUserId() == userId) { + return getCurrentInputMethodSubtypeLocked(); } - return getCurrentInputMethodSubtypeLocked(); + + final ArrayMap methodMap = queryMethodMapForUser(userId); + final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap, + userId, false); + return settings.getCurrentInputMethodSubtypeForNonCurrentUsers(); } } + /** + * Returns the current {@link InputMethodSubtype} for the current user. + * + *

CAVEATS: You must also update + * {@link InputMethodSettings#getCurrentInputMethodSubtypeForNonCurrentUsers()} + * when you update the algorithm of this method.

+ * + *

TODO: Address code duplication between this and + * {@link InputMethodSettings#getCurrentInputMethodSubtypeForNonCurrentUsers()}.

+ */ @GuardedBy("ImfLock.class") InputMethodSubtype getCurrentInputMethodSubtypeLocked() { String selectedMethodId = getSelectedMethodIdLocked(); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java index da6d4b8becf90..dd43e90c548b1 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java @@ -818,6 +818,56 @@ final class InputMethodUtils { } } + /** + * A variant of {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()} for + * non-current users. + * + *

TODO: Address code duplication between this and + * {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()}.

+ * + * @return {@link InputMethodSubtype} if exists. {@code null} otherwise. + */ + @Nullable + InputMethodSubtype getCurrentInputMethodSubtypeForNonCurrentUsers() { + final String selectedMethodId = getSelectedInputMethod(); + if (selectedMethodId == null) { + return null; + } + final InputMethodInfo imi = mMethodMap.get(selectedMethodId); + if (imi == null || imi.getSubtypeCount() == 0) { + return null; + } + + final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode(); + if (subtypeHashCode != InputMethodUtils.NOT_A_SUBTYPE_ID) { + final int subtypeIndex = SubtypeUtils.getSubtypeIdFromHashCode(imi, + subtypeHashCode); + if (subtypeIndex >= 0) { + return imi.getSubtypeAt(subtypeIndex); + } + } + + // If there are no selected subtypes, the framework will try to find the most applicable + // subtype from explicitly or implicitly enabled subtypes. + final List explicitlyOrImplicitlyEnabledSubtypes = + getEnabledInputMethodSubtypeListLocked(imi, true); + // If there is only one explicitly or implicitly enabled subtype, just returns it. + if (explicitlyOrImplicitlyEnabledSubtypes.isEmpty()) { + return null; + } + if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) { + return explicitlyOrImplicitlyEnabledSubtypes.get(0); + } + final InputMethodSubtype subtype = SubtypeUtils.findLastResortApplicableSubtypeLocked( + mRes, explicitlyOrImplicitlyEnabledSubtypes, SubtypeUtils.SUBTYPE_MODE_KEYBOARD, + null, true); + if (subtype != null) { + return subtype; + } + return SubtypeUtils.findLastResortApplicableSubtypeLocked(mRes, + explicitlyOrImplicitlyEnabledSubtypes, null, null, true); + } + public void dumpLocked(final Printer pw, final String prefix) { pw.println(prefix + "mCurrentUserId=" + mCurrentUserId); pw.println(prefix + "mCurrentProfileIds=" + Arrays.toString(mCurrentProfileIds));