diff options
| author | 2024-02-08 00:37:03 -0800 | |
|---|---|---|
| committer | 2024-02-13 07:00:53 +0000 | |
| commit | a72c67178c44b296d4ebe44e0758a900c97b1496 (patch) | |
| tree | 8e9ab89815db01c4424611d523422d457df046c7 | |
| parent | c1e1505e585e2ded92a809fb9289550dbac40582 (diff) | |
Add connectionless handwriting support to input method info
Adds APIs for input methods to declare support for connectionless
handwriting sessions, and APIs to query support.
Bug: 300979854
Bug: 293898187
Test: atest InputMethodInfoTest
Test: atest StylusHandwritingTest
Change-Id: I16ff508187c9b08146f56801bf3a83fbc2aade73
10 files changed, 129 insertions, 11 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 076f016b6914..797f2553135e 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -1603,6 +1603,7 @@ package android { field public static final int supportedTypes = 16844369; // 0x1010651 field public static final int supportsAssist = 16844016; // 0x10104f0 field public static final int supportsBatteryGameMode = 16844374; // 0x1010656 + field @FlaggedApi("android.view.inputmethod.connectionless_handwriting") public static final int supportsConnectionlessStylusHandwriting; field public static final int supportsInlineSuggestions = 16844301; // 0x101060d field public static final int supportsInlineSuggestionsWithTouchExploration = 16844397; // 0x101066d field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1 @@ -56105,6 +56106,7 @@ package android.view.inputmethod { method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager); method public CharSequence loadLabel(android.content.pm.PackageManager); method public boolean shouldShowInInputMethodPicker(); + method @FlaggedApi("android.view.inputmethod.connectionless_handwriting") public boolean supportsConnectionlessStylusHandwriting(); method public boolean supportsStylusHandwriting(); method public boolean suppressesSpellChecker(); method public void writeToParcel(android.os.Parcel, int); @@ -56134,6 +56136,7 @@ package android.view.inputmethod { method public boolean isAcceptingText(); method public boolean isActive(android.view.View); method public boolean isActive(); + method @FlaggedApi("android.view.inputmethod.connectionless_handwriting") public boolean isConnectionlessStylusHandwritingAvailable(); method public boolean isFullscreenMode(); method public boolean isInputMethodSuppressingSpellChecker(); method public boolean isStylusHandwritingAvailable(); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index dd79c4a40cba..5b9032241349 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3901,7 +3901,7 @@ package android.view.inputmethod { } public final class InputMethodInfo implements android.os.Parcelable { - ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, @NonNull String, boolean, @NonNull String); + ctor @FlaggedApi("android.view.inputmethod.connectionless_handwriting") public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, @NonNull String, boolean, boolean, @NonNull String); ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, int); field public static final int COMPONENT_NAME_MAX_LENGTH = 1000; // 0x3e8 field public static final int MAX_IMES_PER_PACKAGE = 20; // 0x14 diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java index 89da04128bc8..474c61f0abbb 100644 --- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java +++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java @@ -530,13 +530,14 @@ final class IInputMethodManagerGlobalInvoker { @AnyThread @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true) - static boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) { + static boolean isStylusHandwritingAvailableAsUser( + @UserIdInt int userId, boolean connectionless) { final IInputMethodManager service = getService(); if (service == null) { return false; } try { - return service.isStylusHandwritingAvailableAsUser(userId); + return service.isStylusHandwritingAvailableAsUser(userId, connectionless); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java index b60efc1ee508..7c9678f11e0e 100644 --- a/core/java/android/view/inputmethod/InputMethodInfo.java +++ b/core/java/android/view/inputmethod/InputMethodInfo.java @@ -204,6 +204,9 @@ public final class InputMethodInfo implements Parcelable { */ private final boolean mSupportsStylusHandwriting; + /** The flag whether this IME supports connectionless stylus handwriting sessions. */ + private final boolean mSupportsConnectionlessStylusHandwriting; + /** * The stylus handwriting setting activity's name, used by the system settings to * launch the stylus handwriting specific setting activity of this input method. @@ -330,6 +333,9 @@ public final class InputMethodInfo implements Parcelable { com.android.internal.R.styleable.InputMethod_configChanges, 0); mSupportsStylusHandwriting = sa.getBoolean( com.android.internal.R.styleable.InputMethod_supportsStylusHandwriting, false); + mSupportsConnectionlessStylusHandwriting = sa.getBoolean( + com.android.internal.R.styleable + .InputMethod_supportsConnectionlessStylusHandwriting, false); stylusHandwritingSettingsActivity = sa.getString( com.android.internal.R.styleable.InputMethod_stylusHandwritingSettingsActivity); sa.recycle(); @@ -442,6 +448,7 @@ public final class InputMethodInfo implements Parcelable { mSubtypes = source.mSubtypes; mHandledConfigChanges = source.mHandledConfigChanges; mSupportsStylusHandwriting = source.mSupportsStylusHandwriting; + mSupportsConnectionlessStylusHandwriting = source.mSupportsConnectionlessStylusHandwriting; mForceDefault = source.mForceDefault; mStylusHandwritingSettingsActivityAttr = source.mStylusHandwritingSettingsActivityAttr; } @@ -463,6 +470,7 @@ public final class InputMethodInfo implements Parcelable { mSubtypes = new InputMethodSubtypeArray(source); mHandledConfigChanges = source.readInt(); mSupportsStylusHandwriting = source.readBoolean(); + mSupportsConnectionlessStylusHandwriting = source.readBoolean(); mStylusHandwritingSettingsActivityAttr = source.readString8(); mForceDefault = false; } @@ -479,6 +487,7 @@ public final class InputMethodInfo implements Parcelable { false /* inlineSuggestionsEnabled */, false /* isVrOnly */, false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */, false /* supportsStylusHandwriting */, + false /* supportConnectionlessStylusHandwriting */, null /* stylusHandwritingSettingsActivityAttr */, false /* inlineSuggestionsEnabled */); } @@ -488,9 +497,11 @@ public final class InputMethodInfo implements Parcelable { * @hide */ @TestApi + @FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING) public InputMethodInfo(@NonNull String packageName, @NonNull String className, @NonNull CharSequence label, @NonNull String settingsActivity, @NonNull String languageSettingsActivity, boolean supportStylusHandwriting, + boolean supportConnectionlessStylusHandwriting, @NonNull String stylusHandwritingSettingsActivityAttr) { this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */, settingsActivity, languageSettingsActivity, null /* subtypes */, @@ -498,8 +509,8 @@ public final class InputMethodInfo implements Parcelable { true /* supportsSwitchingToNextInputMethod */, false /* inlineSuggestionsEnabled */, false /* isVrOnly */, false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */, - supportStylusHandwriting, stylusHandwritingSettingsActivityAttr, - false /* inlineSuggestionsEnabled */); + supportStylusHandwriting, supportConnectionlessStylusHandwriting, + stylusHandwritingSettingsActivityAttr, false /* inlineSuggestionsEnabled */); } /** @@ -517,6 +528,7 @@ public final class InputMethodInfo implements Parcelable { false /* inlineSuggestionsEnabled */, false /* isVrOnly */, false /* isVirtualDeviceOnly */, handledConfigChanges, false /* supportsStylusHandwriting */, + false /* supportConnectionlessStylusHandwriting */, null /* stylusHandwritingSettingsActivityAttr */, false /* inlineSuggestionsEnabled */); } @@ -533,6 +545,7 @@ public final class InputMethodInfo implements Parcelable { true /* supportsSwitchingToNextInputMethod */, false /* inlineSuggestionsEnabled */, false /* isVrOnly */, false /* isVirtualDeviceOnly */, 0 /* handledconfigChanges */, false /* supportsStylusHandwriting */, + false /* supportConnectionlessStylusHandwriting */, null /* stylusHandwritingSettingsActivityAttr */, false /* inlineSuggestionsEnabled */); } @@ -549,6 +562,7 @@ public final class InputMethodInfo implements Parcelable { supportsSwitchingToNextInputMethod, false /* inlineSuggestionsEnabled */, isVrOnly, false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */, false /* supportsStylusHandwriting */, + false /* supportConnectionlessStylusHandwriting */, null /* stylusHandwritingSettingsActivityAttr */, false /* inlineSuggestionsEnabled */); } @@ -562,7 +576,8 @@ public final class InputMethodInfo implements Parcelable { int isDefaultResId, boolean forceDefault, boolean supportsSwitchingToNextInputMethod, boolean inlineSuggestionsEnabled, boolean isVrOnly, boolean isVirtualDeviceOnly, int handledConfigChanges, - boolean supportsStylusHandwriting, String stylusHandwritingSettingsActivityAttr, + boolean supportsStylusHandwriting, boolean supportsConnectionlessStylusHandwriting, + String stylusHandwritingSettingsActivityAttr, boolean supportsInlineSuggestionsWithTouchExploration) { final ServiceInfo si = ri.serviceInfo; mService = ri; @@ -583,6 +598,7 @@ public final class InputMethodInfo implements Parcelable { mIsVirtualDeviceOnly = isVirtualDeviceOnly; mHandledConfigChanges = handledConfigChanges; mSupportsStylusHandwriting = supportsStylusHandwriting; + mSupportsConnectionlessStylusHandwriting = supportsConnectionlessStylusHandwriting; mStylusHandwritingSettingsActivityAttr = stylusHandwritingSettingsActivityAttr; } @@ -763,6 +779,16 @@ public final class InputMethodInfo implements Parcelable { } /** + * Returns whether the IME supports connectionless stylus handwriting sessions. + * + * @attr ref android.R.styleable#InputMethod_supportsConnectionlessStylusHandwriting + */ + @FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING) + public boolean supportsConnectionlessStylusHandwriting() { + return mSupportsConnectionlessStylusHandwriting; + } + + /** * Returns {@link Intent} for stylus handwriting settings activity with * {@link Intent#getAction() Intent action} {@link #ACTION_STYLUS_HANDWRITING_SETTINGS} * if IME {@link #supportsStylusHandwriting() supports stylus handwriting}, else @@ -828,6 +854,8 @@ public final class InputMethodInfo implements Parcelable { + " mSuppressesSpellChecker=" + mSuppressesSpellChecker + " mShowInInputMethodPicker=" + mShowInInputMethodPicker + " mSupportsStylusHandwriting=" + mSupportsStylusHandwriting + + " mSupportsConnectionlessStylusHandwriting=" + + mSupportsConnectionlessStylusHandwriting + " mStylusHandwritingSettingsActivityAttr=" + mStylusHandwritingSettingsActivityAttr); pw.println(prefix + "mIsDefaultResId=0x" @@ -947,6 +975,7 @@ public final class InputMethodInfo implements Parcelable { mSubtypes.writeToParcel(dest); dest.writeInt(mHandledConfigChanges); dest.writeBoolean(mSupportsStylusHandwriting); + dest.writeBoolean(mSupportsConnectionlessStylusHandwriting); dest.writeString8(mStylusHandwritingSettingsActivityAttr); } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 3b07f27dc95a..6772efb27d06 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -566,8 +566,15 @@ public final class InputMethodManager { @GuardedBy("mH") private PropertyInvalidatedCache<Integer, Boolean> mStylusHandwritingAvailableCache; + /** Cached value for {@link #isConnectionlessStylusHandwritingAvailable} for userId. */ + @GuardedBy("mH") + private PropertyInvalidatedCache<Integer, Boolean> + mConnectionlessStylusHandwritingAvailableCache; + private static final String CACHE_KEY_STYLUS_HANDWRITING_PROPERTY = "cache_key.system_server.stylus_handwriting"; + private static final String CACHE_KEY_CONNECTIONLESS_STYLUS_HANDWRITING_PROPERTY = + "cache_key.system_server.connectionless_stylus_handwriting"; @GuardedBy("mH") private int mCursorSelStart; @@ -691,6 +698,17 @@ public final class InputMethodManager { PropertyInvalidatedCache.invalidateCache(CACHE_KEY_STYLUS_HANDWRITING_PROPERTY); } + /** + * Calling this will invalidate the local connectionless stylus handwriting availability cache, + * which forces the next query in any process to recompute the cache. + * + * @hide + */ + public static void invalidateLocalConnectionlessStylusHandwritingAvailabilityCaches() { + PropertyInvalidatedCache.invalidateCache( + CACHE_KEY_CONNECTIONLESS_STYLUS_HANDWRITING_PROPERTY); + } + private static boolean isAutofillUIShowing(View servedView) { AutofillManager afm = servedView.getContext().getSystemService(AutofillManager.class); return afm != null && afm.isAutofillUiShowing(); @@ -1584,7 +1602,7 @@ public final class InputMethodManager { @Override public Boolean recompute(Integer userId) { return IInputMethodManagerGlobalInvoker.isStylusHandwritingAvailableAsUser( - userId); + userId, /* connectionless= */ false); } }; } @@ -1594,6 +1612,30 @@ public final class InputMethodManager { } /** + * Returns {@code true} if the currently selected IME supports connectionless stylus handwriting + * sessions and is enabled. + */ + @FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING) + public boolean isConnectionlessStylusHandwritingAvailable() { + if (ActivityThread.currentApplication() == null) { + return false; + } + synchronized (mH) { + if (mConnectionlessStylusHandwritingAvailableCache == null) { + mConnectionlessStylusHandwritingAvailableCache = new PropertyInvalidatedCache<>( + /* maxEntries= */ 4, CACHE_KEY_CONNECTIONLESS_STYLUS_HANDWRITING_PROPERTY) { + @Override + public Boolean recompute(@NonNull Integer userId) { + return IInputMethodManagerGlobalInvoker.isStylusHandwritingAvailableAsUser( + userId, /* connectionless= */ true); + } + }; + } + return mConnectionlessStylusHandwritingAvailableCache.query(UserHandle.myUserId()); + } + } + + /** * Returns the list of installed input methods for the specified user. * * <p>{@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required when and only when diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 595bf3b8eb97..ca5d44111f74 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -158,7 +158,7 @@ interface IInputMethodManager { /** Returns {@code true} if currently selected IME supports Stylus handwriting. */ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)") - boolean isStylusHandwritingAvailableAsUser(int userId); + boolean isStylusHandwritingAvailableAsUser(int userId, boolean connectionless); /** add virtual stylus id for test Stylus handwriting session **/ @EnforcePermission("TEST_INPUT_METHOD") diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 3cd18939e2ca..321f9f9de457 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3978,6 +3978,26 @@ {@link android.inputmethodservice.InputMethodService#onFinishInput()}. --> <attr name="supportsStylusHandwriting" format="boolean" /> + <!-- Specifies whether the IME supports connectionless stylus handwriting sessions. A + connectionless session differs from a regular session in that the IME does not use an + input connection to communicate with a text editor. Instead, the IME directly returns + recognised handwritten text via an {@link + android.inputmethodservice.InputMethodService} handwriting lifecycle API. + + <p>If the IME supports connectionless sessions, apps or framework may start a + connectionless session when a stylus motion event sequence begins. {@link + android.inputmethodservice.InputMethodService#onStartConnectionlessStylusHandwriting} + is called. If the IME is ready for stylus input, it should return {code true} to start + the basic mode session. As in the regular session, the IME will receive stylus motion + events to the stylus handwriting window and should render ink to a view in this window. + When the user has stopped handwriting, the IME should end the session and deliver the + result by calling {@link + android.inputmethodservice.InputMethodService#finishConnectionlessStylusHandwriting}. + + The default value is {code false}. If {code true}, {@link + android.R.attr#supportsStylusHandwriting} should also be {code true}. + --> + <attr name="supportsConnectionlessStylusHandwriting" format="boolean" /> <!-- Class name of an activity that allows the user to modify the stylus handwriting settings for this service --> <attr name="stylusHandwritingSettingsActivity" format="string" /> diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index 56743908e281..6029d2377cdc 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -157,6 +157,8 @@ <public name="useLocalePreferredLineHeightForMinimum"/> <!-- @FlaggedApi("android.view.flags.sensitive_content_app_protection_api") --> <public name="contentSensitivity" /> + <!-- @FlaggedApi("android.view.inputmethod.connectionless_handwriting") --> + <public name="supportsConnectionlessStylusHandwriting" /> </staging-public-group> <staging-public-group type="id" first-id="0x01bc0000"> diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java index c8c0482f5a9d..a100fe06c407 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java @@ -77,6 +77,7 @@ final class InputMethodBindingController { @GuardedBy("ImfLock.class") private int mCurSeq; @GuardedBy("ImfLock.class") private boolean mVisibleBound; @GuardedBy("ImfLock.class") private boolean mSupportsStylusHw; + @GuardedBy("ImfLock.class") private boolean mSupportsConnectionlessStylusHw; @Nullable private CountDownLatch mLatchForTesting; @@ -243,10 +244,17 @@ final class InputMethodBindingController { /** * Returns {@code true} if current IME supports Stylus Handwriting. */ + @GuardedBy("ImfLock.class") boolean supportsStylusHandwriting() { return mSupportsStylusHw; } + /** Returns whether the current IME supports connectionless stylus handwriting sessions. */ + @GuardedBy("ImfLock.class") + boolean supportsConnectionlessStylusHandwriting() { + return mSupportsConnectionlessStylusHw; + } + /** * Used to bring IME service up to visible adjustment while it is being shown. */ @@ -298,6 +306,15 @@ final class InputMethodBindingController { if (supportsStylusHwChanged) { InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches(); } + boolean supportsConnectionlessStylusHwChanged = + mSupportsConnectionlessStylusHw + != info.supportsConnectionlessStylusHandwriting(); + if (supportsConnectionlessStylusHwChanged) { + mSupportsConnectionlessStylusHw = + info.supportsConnectionlessStylusHandwriting(); + InputMethodManager + .invalidateLocalConnectionlessStylusHandwritingAvailabilityCaches(); + } mService.initializeImeLocked(mCurMethod, mCurToken); mService.scheduleNotifyImeUidToAudioService(mCurMethodUid); mService.reRequestCurrentClientSessionLocked(); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 96c0c8a9a7b8..8f8993bd44d3 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -1980,7 +1980,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @Override - public boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) { + public boolean isStylusHandwritingAvailableAsUser( + @UserIdInt int userId, boolean connectionless) { if (UserHandle.getCallingUserId() != userId) { mContext.enforceCallingOrSelfPermission( Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); @@ -1993,14 +1994,17 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // Check if selected IME of current user supports handwriting. if (userId == mSettings.getUserId()) { - return mBindingController.supportsStylusHandwriting(); + return mBindingController.supportsStylusHandwriting() + && (!connectionless + || mBindingController.supportsConnectionlessStylusHandwriting()); } //TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList. //TODO(b/210039666): use cache. final InputMethodSettings settings = queryMethodMapForUser(userId); final InputMethodInfo imi = settings.getMethodMap().get( settings.getSelectedInputMethod()); - return imi != null && imi.supportsStylusHandwriting(); + return imi != null && imi.supportsStylusHandwriting() + && (!connectionless || imi.supportsConnectionlessStylusHandwriting()); } } |