diff options
10 files changed, 125 insertions, 25 deletions
diff --git a/api/current.txt b/api/current.txt index d1e1108b23b7..5ab1c0171c61 100644 --- a/api/current.txt +++ b/api/current.txt @@ -2925,6 +2925,7 @@ package android.accessibilityservice { method public int getShowMode(); method public boolean removeOnShowModeChangedListener(@NonNull android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener); method public boolean setShowMode(int); + method public boolean switchToInputMethod(@NonNull String); } public static interface AccessibilityService.SoftKeyboardController.OnShowModeChangedListener { diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index c0fee6e9e5e9..e46840c0f467 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -1547,6 +1547,34 @@ public abstract class AccessibilityService extends Service { void onShowModeChanged(@NonNull SoftKeyboardController controller, @SoftKeyboardShowMode int showMode); } + + /** + * Switches the current IME for the user for whom the service is enabled. The change will + * persist until the current IME is explicitly changed again, and may persist beyond the + * life cycle of the requesting service. + * + * @param imeId The ID of the input method to make current. This IME must be installed and + * enabled. + * @return {@code true} if the current input method was successfully switched to the input + * method by {@code imeId}, + * {@code false} if the input method specified is not installed, not enabled, or + * otherwise not available to become the current IME + * + * @see android.view.inputmethod.InputMethodInfo#getId() + */ + public boolean switchToInputMethod(@NonNull String imeId) { + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().getConnection( + mService.mConnectionId); + if (connection != null) { + try { + return connection.switchToInputMethod(imeId); + } catch (RemoteException re) { + throw new RuntimeException(re); + } + } + return false; + } } /** diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 4841781170e1..656f87fe435b 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -91,6 +91,8 @@ interface IAccessibilityServiceConnection { void setSoftKeyboardCallbackEnabled(boolean enabled); + boolean switchToInputMethod(String imeId); + boolean isAccessibilityButtonAvailable(); void sendGesture(int sequence, in ParceledListSlice gestureSteps); diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java index 3586216ad421..93f4b5143c57 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java @@ -124,6 +124,10 @@ public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon public void setSoftKeyboardCallbackEnabled(boolean enabled) {} + public boolean switchToInputMethod(String imeId) { + return false; + } + public boolean isAccessibilityButtonAvailable() { return false; } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index cbff6bdcec77..37ac3ec6f105 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -35,6 +35,7 @@ import android.provider.Settings; import android.util.Slog; import android.view.Display; +import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; @@ -264,6 +265,24 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } @Override + public boolean switchToInputMethod(String imeId) { + synchronized (mLock) { + if (!hasRightsToCurrentUserLocked()) { + return false; + } + } + final boolean result; + final int callingUserId = UserHandle.getCallingUserId(); + final long identity = Binder.clearCallingIdentity(); + try { + result = InputMethodManagerInternal.get().switchToInputMethod(imeId, callingUserId); + } finally { + Binder.restoreCallingIdentity(identity); + } + return result; + } + + @Override public boolean isAccessibilityButtonAvailable() { synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java index 7dd4a7089954..7a8a112fae40 100644 --- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java @@ -297,6 +297,11 @@ class UiAutomationManager { } @Override + public boolean switchToInputMethod(String imeId) { + return false; + } + + @Override public boolean isAccessibilityButtonAvailable() { return false; } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index c40e8af73268..2c3c70fc3da2 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -72,6 +72,18 @@ public abstract class InputMethodManagerInternal { AutofillId autofillId, IInlineSuggestionsRequestCallback cb); /** + * Force switch to the enabled input method by {@code imeId} for current user. If the input + * method with {@code imeId} is not enabled or not installed, do nothing. + * + * @param imeId The input method ID to be switched to. + * @param userId The user ID to be queried. + * @return {@code true} if the current input method was successfully switched to the input + * method by {@code imeId}; {@code false} the input method with {@code imeId} is not available + * to be switched. + */ + public abstract boolean switchToInputMethod(String imeId, @UserIdInt int userId); + + /** * Fake implementation of {@link InputMethodManagerInternal}. All the methods do nothing. */ private static final InputMethodManagerInternal NOP = @@ -98,6 +110,11 @@ public abstract class InputMethodManagerInternal { public void onCreateInlineSuggestionsRequest(ComponentName componentName, AutofillId autofillId, IInlineSuggestionsRequestCallback cb) { } + + @Override + public boolean switchToInputMethod(String imeId, int userId) { + return false; + } }; /** diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 8b6b61499e9e..d4b13faf3195 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -4478,6 +4478,38 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + private boolean switchToInputMethod(String imeId, @UserIdInt int userId) { + synchronized (mMethodMap) { + if (userId == mSettings.getCurrentUserId()) { + if (!mMethodMap.containsKey(imeId) + || !mSettings.getEnabledInputMethodListLocked() + .contains(mMethodMap.get(imeId))) { + return false; // IME is not is found or not enabled. + } + setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID); + return true; + } + final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); + final ArrayList<InputMethodInfo> methodList = new ArrayList<>(); + final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = + new ArrayMap<>(); + AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); + queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, + methodMap, methodList); + final InputMethodSettings settings = new InputMethodSettings( + mContext.getResources(), mContext.getContentResolver(), methodMap, + userId, false); + if (!methodMap.containsKey(imeId) + || !settings.getEnabledInputMethodListLocked() + .contains(methodMap.get(imeId))) { + return false; // IME is not is found or not enabled. + } + settings.putSelectedInputMethod(imeId); + settings.putSelectedSubtype(NOT_A_SUBTYPE_ID); + return true; + } + } + private static final class LocalServiceImpl extends InputMethodManagerInternal { @NonNull private final InputMethodManagerService mService; @@ -4514,6 +4546,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub AutofillId autofillId, IInlineSuggestionsRequestCallback cb) { mService.onCreateInlineSuggestionsRequest(componentName, autofillId, cb); } + + @Override + public boolean switchToInputMethod(String imeId, int userId) { + return mService.switchToInputMethod(imeId, userId); + } } @BinderThread @@ -5065,31 +5102,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (!userHasDebugPriv(userId, shellCommand)) { continue; } - boolean failedToSelectUnknownIme = false; - if (userId == mSettings.getCurrentUserId()) { - if (mMethodMap.containsKey(imeId)) { - setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID); - } else { - failedToSelectUnknownIme = true; - } - } else { - final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); - final ArrayList<InputMethodInfo> methodList = new ArrayList<>(); - final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = - new ArrayMap<>(); - AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); - queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, - methodMap, methodList); - final InputMethodSettings settings = new InputMethodSettings( - mContext.getResources(), mContext.getContentResolver(), methodMap, - userId, false); - if (methodMap.containsKey(imeId)) { - settings.putSelectedInputMethod(imeId); - settings.putSelectedSubtype(NOT_A_SUBTYPE_ID); - } else { - failedToSelectUnknownIme = true; - } - } + boolean failedToSelectUnknownIme = !switchToInputMethod(imeId, userId); if (failedToSelectUnknownIme) { error.print("Unknown input method "); error.print(imeId); diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java index 1f9379c259cf..20d7955e0c77 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -200,6 +200,12 @@ public final class MultiClientInputMethodManagerService { Slog.w(TAG, "Failed to call onInlineSuggestionsUnsupported.", e); } } + + @Override + public boolean switchToInputMethod(String imeId, @UserIdInt int userId) { + reportNotSupported(); + return false; + } }); } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java index 591c3a385e23..c223f135d3df 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java @@ -804,6 +804,11 @@ public class AbstractAccessibilityServiceConnectionTest { } @Override + public boolean switchToInputMethod(String imeId) { + return false; + } + + @Override public boolean isAccessibilityButtonAvailable() throws RemoteException { return false; } |