/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.view.inputmethod; import android.Manifest; import android.annotation.AnyThread; import android.annotation.DurationMillisLong; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresNoPermission; import android.annotation.RequiresPermission; import android.annotation.UserIdInt; import android.content.Context; import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; import android.view.WindowManager; import android.window.ImeOnBackInvokedDispatcher; import com.android.internal.inputmethod.DirectBootAwareness; import com.android.internal.inputmethod.IBooleanListener; import com.android.internal.inputmethod.IConnectionlessHandwritingCallback; import com.android.internal.inputmethod.IImeTracker; import com.android.internal.inputmethod.IInputMethodClient; import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; import com.android.internal.inputmethod.IRemoteInputConnection; import com.android.internal.inputmethod.InputBindResult; import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.inputmethod.StartInputFlags; import com.android.internal.inputmethod.StartInputReason; import com.android.internal.view.IInputMethodManager; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; /** * A global wrapper to directly invoke {@link IInputMethodManager} IPCs. * *

All public static methods are guaranteed to be thread-safe.

* *

All public methods are guaranteed to do nothing when {@link IInputMethodManager} is * unavailable.

* *

If you want to use any of this method outside of {@code android.view.inputmethod}, create * a wrapper method in {@link InputMethodManagerGlobal} instead of making this class public.

*/ final class IInputMethodManagerGlobalInvoker { @Nullable private static volatile IInputMethodManager sServiceCache = null; @Nullable private static volatile IImeTracker sTrackerServiceCache = null; private static int sCurStartInputSeq = 0; /** * @return {@code true} if {@link IInputMethodManager} is available. */ @AnyThread static boolean isAvailable() { return getService() != null; } @AnyThread @Nullable static IInputMethodManager getService() { IInputMethodManager service = sServiceCache; if (service == null) { if (InputMethodManager.isInEditModeInternal()) { return null; } service = IInputMethodManager.Stub.asInterface( ServiceManager.getService(Context.INPUT_METHOD_SERVICE)); if (service == null) { return null; } sServiceCache = service; } return service; } @AnyThread private static void handleRemoteExceptionOrRethrow(@NonNull RemoteException e, @Nullable Consumer exceptionHandler) { if (exceptionHandler != null) { exceptionHandler.accept(e); } else { throw e.rethrowFromSystemServer(); } } /** * Invokes {@link IInputMethodManager#startProtoDump(byte[], int, String)}. * * @param protoDump client or service side information to be stored by the server * @param source where the information is coming from, refer to * {@link com.android.internal.inputmethod.ImeTracing#IME_TRACING_FROM_CLIENT} and * {@link com.android.internal.inputmethod.ImeTracing#IME_TRACING_FROM_IMS} * @param where where the information is coming from. * @param exceptionHandler an optional {@link RemoteException} handler. */ @AnyThread @RequiresNoPermission static void startProtoDump(byte[] protoDump, int source, String where, @Nullable Consumer exceptionHandler) { final IInputMethodManager service = getService(); if (service == null) { return; } try { service.startProtoDump(protoDump, source, where); } catch (RemoteException e) { handleRemoteExceptionOrRethrow(e, exceptionHandler); } } /** * Invokes {@link IInputMethodManager#startImeTrace()}. * * @param exceptionHandler an optional {@link RemoteException} handler. */ @AnyThread @RequiresPermission(Manifest.permission.CONTROL_UI_TRACING) static void startImeTrace(@Nullable Consumer exceptionHandler) { final IInputMethodManager service = getService(); if (service == null) { return; } try { service.startImeTrace(); } catch (RemoteException e) { handleRemoteExceptionOrRethrow(e, exceptionHandler); } } /** * Invokes {@link IInputMethodManager#stopImeTrace()}. * * @param exceptionHandler an optional {@link RemoteException} handler. */ @AnyThread @RequiresPermission(Manifest.permission.CONTROL_UI_TRACING) static void stopImeTrace(@Nullable Consumer exceptionHandler) { final IInputMethodManager service = getService(); if (service == null) { return; } try { service.stopImeTrace(); } catch (RemoteException e) { handleRemoteExceptionOrRethrow(e, exceptionHandler); } } /** * Invokes {@link IInputMethodManager#isImeTraceEnabled()}. * * @return The return value of {@link IInputMethodManager#isImeTraceEnabled()}. */ @AnyThread @RequiresNoPermission static boolean isImeTraceEnabled() { final IInputMethodManager service = getService(); if (service == null) { return false; } try { return service.isImeTraceEnabled(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Invokes {@link IInputMethodManager#removeImeSurface()} */ @AnyThread @RequiresPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW) static void removeImeSurface(@Nullable Consumer exceptionHandler) { final IInputMethodManager service = getService(); if (service == null) { return; } try { service.removeImeSurface(); } catch (RemoteException e) { handleRemoteExceptionOrRethrow(e, exceptionHandler); } } @AnyThread static void addClient(@NonNull IInputMethodClient client, @NonNull IRemoteInputConnection fallbackInputConnection, int untrustedDisplayId) { final IInputMethodManager service = getService(); if (service == null) { return; } try { service.addClient(client, fallbackInputConnection, untrustedDisplayId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @AnyThread @Nullable @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true) static InputMethodInfo getCurrentInputMethodInfoAsUser(@UserIdInt int userId) { final IInputMethodManager service = getService(); if (service == null) { return null; } try { return service.getCurrentInputMethodInfoAsUser(userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @AnyThread @NonNull @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true) static List getInputMethodList(@UserIdInt int userId, @DirectBootAwareness int directBootAwareness) { final IInputMethodManager service = getService(); if (service == null) { return new ArrayList<>(); } try { return service.getInputMethodList(userId, directBootAwareness); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @AnyThread @NonNull @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true) static List getEnabledInputMethodList(@UserIdInt int userId) { final IInputMethodManager service = getService(); if (service == null) { return new ArrayList<>(); } try { return service.getEnabledInputMethodList(userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @AnyThread @NonNull @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true) static List getEnabledInputMethodSubtypeList(@Nullable String imiId, boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) { final IInputMethodManager service = getService(); if (service == null) { return new ArrayList<>(); } try { return service.getEnabledInputMethodSubtypeList(imiId, allowsImplicitlyEnabledSubtypes, userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @AnyThread @Nullable @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true) static InputMethodSubtype getLastInputMethodSubtype(@UserIdInt int userId) { final IInputMethodManager service = getService(); if (service == null) { return null; } try { return service.getLastInputMethodSubtype(userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @AnyThread static boolean showSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken, @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, int lastClickToolType, @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { final IInputMethodManager service = getService(); if (service == null) { return false; } try { return service.showSoftInput(client, windowToken, statsToken, flags, lastClickToolType, resultReceiver, reason); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @AnyThread static boolean hideSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken, @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { final IInputMethodManager service = getService(); if (service == null) { return false; } try { return service.hideSoftInput(client, windowToken, statsToken, flags, resultReceiver, reason); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } // TODO(b/293640003): Remove method once Flags.useZeroJankProxy() is enabled. @AnyThread @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD) static void hideSoftInputFromServerForTest() { final IInputMethodManager service = getService(); if (service == null) { return; } try { service.hideSoftInputFromServerForTest(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @AnyThread @NonNull @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true) static InputBindResult startInputOrWindowGainedFocus(@StartInputReason int startInputReason, @NonNull IInputMethodClient client, @Nullable IBinder windowToken, @StartInputFlags int startInputFlags, @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, @WindowManager.LayoutParams.Flags int windowFlags, @Nullable EditorInfo editorInfo, @Nullable IRemoteInputConnection remoteInputConnection, @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, int unverifiedTargetSdkVersion, @UserIdInt int userId, @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { final IInputMethodManager service = getService(); if (service == null) { return InputBindResult.NULL; } try { return service.startInputOrWindowGainedFocus(startInputReason, client, windowToken, startInputFlags, softInputMode, windowFlags, editorInfo, remoteInputConnection, remoteAccessibilityInputConnection, unverifiedTargetSdkVersion, userId, imeDispatcher); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Returns a sequence number for startInput. */ @AnyThread @NonNull @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true) static int startInputOrWindowGainedFocusAsync(@StartInputReason int startInputReason, @NonNull IInputMethodClient client, @Nullable IBinder windowToken, @StartInputFlags int startInputFlags, @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, @WindowManager.LayoutParams.Flags int windowFlags, @Nullable EditorInfo editorInfo, @Nullable IRemoteInputConnection remoteInputConnection, @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, int unverifiedTargetSdkVersion, @UserIdInt int userId, @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { final IInputMethodManager service = getService(); if (service == null) { return -1; } try { service.startInputOrWindowGainedFocusAsync(startInputReason, client, windowToken, startInputFlags, softInputMode, windowFlags, editorInfo, remoteInputConnection, remoteAccessibilityInputConnection, unverifiedTargetSdkVersion, userId, imeDispatcher, advanceAngGetStartInputSequenceNumber()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } return sCurStartInputSeq; } private static int advanceAngGetStartInputSequenceNumber() { return ++sCurStartInputSeq; } @AnyThread static void showInputMethodPickerFromClient(@NonNull IInputMethodClient client, int auxiliarySubtypeMode) { final IInputMethodManager service = getService(); if (service == null) { return; } try { service.showInputMethodPickerFromClient(client, auxiliarySubtypeMode); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @AnyThread @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) static void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) { final IInputMethodManager service = getService(); if (service == null) { return; } try { service.showInputMethodPickerFromSystem(auxiliarySubtypeMode, displayId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @AnyThread @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD) static boolean isInputMethodPickerShownForTest() { final IInputMethodManager service = getService(); if (service == null) { return false; } try { return service.isInputMethodPickerShownForTest(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @AnyThread @Nullable @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true) static InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId) { final IInputMethodManager service = getService(); if (service == null) { return null; } try { return service.getCurrentInputMethodSubtype(userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @AnyThread @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true) static void setAdditionalInputMethodSubtypes(@NonNull String imeId, @NonNull InputMethodSubtype[] subtypes, @UserIdInt int userId) { final IInputMethodManager service = getService(); if (service == null) { return; } try { service.setAdditionalInputMethodSubtypes(imeId, subtypes, userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @AnyThread @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true) static void setExplicitlyEnabledInputMethodSubtypes(@NonNull String imeId, @NonNull int[] subtypeHashCodes, @UserIdInt int userId) { final IInputMethodManager service = getService(); if (service == null) { return; } try { service.setExplicitlyEnabledInputMethodSubtypes(imeId, subtypeHashCodes, userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @AnyThread static int getInputMethodWindowVisibleHeight(@NonNull IInputMethodClient client) { final IInputMethodManager service = getService(); if (service == null) { return 0; } try { return service.getInputMethodWindowVisibleHeight(client); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @AnyThread static void reportPerceptibleAsync(@NonNull IBinder windowToken, boolean perceptible) { final IInputMethodManager service = getService(); if (service == null) { return; } try { service.reportPerceptibleAsync(windowToken, perceptible); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @AnyThread static void removeImeSurfaceFromWindowAsync(@NonNull IBinder windowToken) { final IInputMethodManager service = getService(); if (service == null) { return; } try { service.removeImeSurfaceFromWindowAsync(windowToken); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @AnyThread static void startStylusHandwriting(@NonNull IInputMethodClient client) { final IInputMethodManager service = getService(); if (service == null) { return; } try { service.startStylusHandwriting(client); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @AnyThread static boolean startConnectionlessStylusHandwriting( @NonNull IInputMethodClient client, @UserIdInt int userId, @Nullable CursorAnchorInfo cursorAnchorInfo, @Nullable String delegatePackageName, @Nullable String delegatorPackageName, @NonNull IConnectionlessHandwritingCallback callback) { final IInputMethodManager service = getService(); if (service == null) { return false; } try { service.startConnectionlessStylusHandwriting(client, userId, cursorAnchorInfo, delegatePackageName, delegatorPackageName, callback); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } return true; } @AnyThread static void prepareStylusHandwritingDelegation( @NonNull IInputMethodClient client, @UserIdInt int userId, @NonNull String delegatePackageName, @NonNull String delegatorPackageName) { final IInputMethodManager service = getService(); if (service == null) { return; } try { service.prepareStylusHandwritingDelegation( client, userId, delegatePackageName, delegatorPackageName); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @AnyThread static boolean acceptStylusHandwritingDelegation( @NonNull IInputMethodClient client, @UserIdInt int userId, @NonNull String delegatePackageName, @NonNull String delegatorPackageName, @InputMethodManager.HandwritingDelegateFlags int flags) { final IInputMethodManager service = getService(); if (service == null) { return false; } try { return service.acceptStylusHandwritingDelegation( client, userId, delegatePackageName, delegatorPackageName, flags); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** Returns {@code true} if method is invoked */ @AnyThread static boolean acceptStylusHandwritingDelegationAsync( @NonNull IInputMethodClient client, @UserIdInt int userId, @NonNull String delegatePackageName, @NonNull String delegatorPackageName, @InputMethodManager.HandwritingDelegateFlags int flags, @NonNull IBooleanListener callback) { final IInputMethodManager service = getService(); if (service == null) { return false; } try { service.acceptStylusHandwritingDelegationAsync( client, userId, delegatePackageName, delegatorPackageName, flags, callback); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } return true; } @AnyThread @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true) static boolean isStylusHandwritingAvailableAsUser( @UserIdInt int userId, boolean connectionless) { final IInputMethodManager service = getService(); if (service == null) { return false; } try { return service.isStylusHandwritingAvailableAsUser(userId, connectionless); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @AnyThread @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD) static void addVirtualStylusIdForTestSession(IInputMethodClient client) { final IInputMethodManager service = getService(); if (service == null) { return; } try { service.addVirtualStylusIdForTestSession(client); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @AnyThread @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD) static void setStylusWindowIdleTimeoutForTest( IInputMethodClient client, @DurationMillisLong long timeout) { final IInputMethodManager service = getService(); if (service == null) { return; } try { service.setStylusWindowIdleTimeoutForTest(client, timeout); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** @see com.android.server.inputmethod.ImeTrackerService#onStart */ @AnyThread @NonNull static ImeTracker.Token onStart(@NonNull String tag, int uid, @ImeTracker.Type int type, @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason, boolean fromUser) { final var service = getImeTrackerService(); if (service == null) { // Create token with "empty" binder if the service was not found. return ImeTracker.Token.empty(tag); } try { return service.onStart(tag, uid, type, origin, reason, fromUser); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** @see com.android.server.inputmethod.ImeTrackerService#onProgress */ @AnyThread static void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) { final IImeTracker service = getImeTrackerService(); if (service == null) { return; } try { service.onProgress(binder, phase); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** @see com.android.server.inputmethod.ImeTrackerService#onFailed */ @AnyThread static void onFailed(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) { final IImeTracker service = getImeTrackerService(); if (service == null) { return; } try { service.onFailed(statsToken, phase); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** @see com.android.server.inputmethod.ImeTrackerService#onCancelled */ @AnyThread static void onCancelled(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) { final IImeTracker service = getImeTrackerService(); if (service == null) { return; } try { service.onCancelled(statsToken, phase); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** @see com.android.server.inputmethod.ImeTrackerService#onShown */ @AnyThread static void onShown(@NonNull ImeTracker.Token statsToken) { final IImeTracker service = getImeTrackerService(); if (service == null) { return; } try { service.onShown(statsToken); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** @see com.android.server.inputmethod.ImeTrackerService#onHidden */ @AnyThread static void onHidden(@NonNull ImeTracker.Token statsToken) { final IImeTracker service = getImeTrackerService(); if (service == null) { return; } try { service.onHidden(statsToken); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** @see com.android.server.inputmethod.ImeTrackerService#hasPendingImeVisibilityRequests */ @AnyThread @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD) static boolean hasPendingImeVisibilityRequests() { final var service = getImeTrackerService(); if (service == null) { return true; } try { return service.hasPendingImeVisibilityRequests(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @AnyThread @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD) static void finishTrackingPendingImeVisibilityRequests() { final var service = getImeTrackerService(); if (service == null) { return; } try { service.finishTrackingPendingImeVisibilityRequests(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @AnyThread @Nullable private static IImeTracker getImeTrackerService() { var trackerService = sTrackerServiceCache; if (trackerService == null) { final var service = getService(); if (service == null) { return null; } try { trackerService = service.getImeTrackerService(); if (trackerService == null) { return null; } sTrackerServiceCache = trackerService; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } return trackerService; } }