Remove inkWindow after stylus idle-timeout

After stylus hasnt been used for handwriting in last X minutes,
remove the the stylus handwriting window.

Bug: 243571274
Test: StylusHandwritingTest
Change-Id: I641be63e028ea9cbfd882ce318d30aad71fd8a4e
This commit is contained in:
Taran Singh
2022-09-13 00:47:34 +00:00
parent b4fa71513c
commit cfef0232ca
10 changed files with 154 additions and 9 deletions

View File

@@ -3135,6 +3135,7 @@ package android.view.inputmethod {
method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int);
method public boolean hasActiveInputConnection(@Nullable android.view.View);
method public boolean isInputMethodPickerShown();
method @RequiresPermission("android.permission.TEST_INPUT_METHOD") public void setStylusWindowIdleTimeoutForTest(long);
field public static final long CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING = 214016041L; // 0xcc1a029L
}

View File

@@ -17,6 +17,7 @@
package android.inputmethodservice;
import android.annotation.BinderThread;
import android.annotation.DurationMillisLong;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -82,6 +83,7 @@ class IInputMethodWrapper extends IInputMethod.Stub
private static final int DO_FINISH_STYLUS_HANDWRITING = 130;
private static final int DO_UPDATE_TOOL_TYPE = 140;
private static final int DO_REMOVE_STYLUS_HANDWRITING_WINDOW = 150;
private static final int DO_SET_STYLUS_WINDOW_IDLE_TIMEOUT = 160;
final WeakReference<InputMethodServiceInternal> mTarget;
final Context mContext;
@@ -151,7 +153,7 @@ class IInputMethodWrapper extends IInputMethod.Stub
final InputMethodServiceInternal target = mTarget.get();
switch (msg.what) {
case DO_DUMP: {
SomeArgs args = (SomeArgs)msg.obj;
SomeArgs args = (SomeArgs) msg.obj;
if (isValid(inputMethod, target, "DO_DUMP")) {
final FileDescriptor fd = (FileDescriptor) args.arg1;
final PrintWriter fout = (PrintWriter) args.arg2;
@@ -201,7 +203,7 @@ class IInputMethodWrapper extends IInputMethod.Stub
}
return;
case DO_CREATE_SESSION: {
SomeArgs args = (SomeArgs)msg.obj;
SomeArgs args = (SomeArgs) msg.obj;
if (isValid(inputMethod, target, "DO_CREATE_SESSION")) {
inputMethod.createSession(new InputMethodSessionCallbackWrapper(
mContext, (InputChannel) args.arg1,
@@ -216,7 +218,7 @@ class IInputMethodWrapper extends IInputMethod.Stub
}
return;
case DO_SHOW_SOFT_INPUT: {
final SomeArgs args = (SomeArgs)msg.obj;
final SomeArgs args = (SomeArgs) msg.obj;
if (isValid(inputMethod, target, "DO_SHOW_SOFT_INPUT")) {
inputMethod.showSoftInputWithToken(
msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1);
@@ -287,6 +289,10 @@ class IInputMethodWrapper extends IInputMethod.Stub
}
return;
}
case DO_SET_STYLUS_WINDOW_IDLE_TIMEOUT: {
inputMethod.setStylusWindowIdleTimeoutForTest((long) msg.obj);
return;
}
}
Log.w(TAG, "Unhandled message code: " + msg.what);
}
@@ -300,7 +306,7 @@ class IInputMethodWrapper extends IInputMethod.Stub
}
if (target.getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
fout.println("Permission Denial: can't dump InputMethodManager from from pid="
+ Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid());
@@ -473,6 +479,13 @@ class IInputMethodWrapper extends IInputMethod.Stub
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_STYLUS_HANDWRITING_WINDOW));
}
@BinderThread
@Override
public void setStylusWindowIdleTimeoutForTest(@DurationMillisLong long timeout) {
mCaller.executeOrSendMessage(
mCaller.obtainMessageO(DO_SET_STYLUS_WINDOW_IDLE_TIMEOUT, timeout));
}
private static boolean isValid(InputMethod inputMethod, InputMethodServiceInternal target,
String msg) {
if (inputMethod != null && target != null && !target.isServiceDestroyed()) {

View File

@@ -57,6 +57,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.AnyThread;
import android.annotation.CallSuper;
import android.annotation.DrawableRes;
import android.annotation.DurationMillisLong;
import android.annotation.IntDef;
import android.annotation.MainThread;
import android.annotation.NonNull;
@@ -362,6 +363,11 @@ public class InputMethodService extends AbstractInputMethodService {
private static final long STYLUS_HANDWRITING_IDLE_TIMEOUT_MAX_MS =
STYLUS_HANDWRITING_IDLE_TIMEOUT_MS * 3;
/**
* Stylus idle-timeout after which stylus {@code InkWindow} will be removed.
*/
private static final long STYLUS_WINDOW_IDLE_TIMEOUT_MILLIS = 5 * 60 * 1000; // 5 minutes.
/**
* A circular buffer of size MAX_EVENTS_BUFFER in case IME is taking too long to add ink view.
**/
@@ -372,6 +378,8 @@ public class InputMethodService extends AbstractInputMethodService {
private Runnable mImeSurfaceRemoverRunnable;
private Runnable mFinishHwRunnable;
private long mStylusHwSessionsTimeout = STYLUS_HANDWRITING_IDLE_TIMEOUT_MS;
private Runnable mStylusWindowIdleTimeoutRunnable;
private long mStylusWindowIdleTimeoutForTest;
/**
* Returns whether {@link InputMethodService} is responsible for rendering the back button and
@@ -1035,7 +1043,6 @@ public class InputMethodService extends AbstractInputMethodService {
mInkWindow = new InkWindow(mWindow.getContext());
mInkWindow.setToken(mToken);
}
// TODO(b/243571274): set an idle-timeout after which InkWindow is removed.
mInkWindow.initOnly();
}
@@ -1057,6 +1064,15 @@ public class InputMethodService extends AbstractInputMethodService {
InputMethodService.this.removeStylusHandwritingWindow();
}
/**
* {@inheritDoc}
* @hide
*/
@Override
public void setStylusWindowIdleTimeoutForTest(@DurationMillisLong long timeout) {
mStylusWindowIdleTimeoutForTest = timeout;
}
/**
* {@inheritDoc}
*/
@@ -2485,6 +2501,11 @@ public class InputMethodService extends AbstractInputMethodService {
});
}
}
// Create a stylus window idle-timeout after which InkWindow is removed.
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
scheduleStylusWindowIdleTimeout();
}
}
/**
@@ -2545,7 +2566,6 @@ public class InputMethodService extends AbstractInputMethodService {
mHandwritingEventReceiver.dispose();
mHandwritingEventReceiver = null;
// TODO(b/243571274): set an idle-timeout after which InkWindow is removed.
mInkWindow.hide(false /* remove */);
mPrivOps.resetStylusHandwriting(requestId);
@@ -2569,9 +2589,41 @@ public class InputMethodService extends AbstractInputMethodService {
}
private void removeHandwritingInkWindow() {
mInkWindow.hide(true /* remove */);
mInkWindow.destroy();
mInkWindow = null;
cancelStylusWindowIdleTimeout();
mOnPreparedStylusHwCalled = false;
mStylusWindowIdleTimeoutRunnable = null;
if (mInkWindow != null) {
mInkWindow.hide(true /* remove */);
mInkWindow.destroy();
mInkWindow = null;
}
}
private void cancelStylusWindowIdleTimeout() {
if (mStylusWindowIdleTimeoutRunnable != null && mHandler != null) {
mHandler.removeCallbacks(mStylusWindowIdleTimeoutRunnable);
}
}
private void scheduleStylusWindowIdleTimeout() {
if (mHandler == null) {
return;
}
cancelStylusWindowIdleTimeout();
long timeout = (mStylusWindowIdleTimeoutForTest > 0)
? mStylusWindowIdleTimeoutForTest : STYLUS_WINDOW_IDLE_TIMEOUT_MILLIS;
mHandler.postDelayed(getStylusWindowIdleTimeoutRunnable(), timeout);
}
private Runnable getStylusWindowIdleTimeoutRunnable() {
if (mStylusWindowIdleTimeoutRunnable == null) {
mStylusWindowIdleTimeoutRunnable = () -> {
removeHandwritingInkWindow();
mStylusWindowIdleTimeoutRunnable = null;
};
}
return mStylusWindowIdleTimeoutRunnable;
}
/**

View File

@@ -17,6 +17,7 @@
package android.view.inputmethod;
import android.annotation.AnyThread;
import android.annotation.DurationMillisLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -264,4 +265,14 @@ final class IInputMethodManagerInvoker {
throw e.rethrowFromSystemServer();
}
}
@AnyThread
void setStylusWindowIdleTimeoutForTest(
IInputMethodClient client, @DurationMillisLong long timeout) {
try {
mTarget.setStylusWindowIdleTimeoutForTest(client, timeout);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}

View File

@@ -16,6 +16,7 @@
package android.view.inputmethod;
import android.annotation.DurationMillisLong;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -417,4 +418,12 @@ public interface InputMethod {
default void removeStylusHandwritingWindow() {
// intentionally empty
}
/**
* Set a stylus idle-timeout after which handwriting {@code InkWindow} will be removed.
* @hide
*/
default void setStylusWindowIdleTimeoutForTest(@DurationMillisLong long timeout) {
// intentionally empty
}
}

View File

@@ -36,8 +36,10 @@ import static com.android.internal.inputmethod.StartInputReason.BOUND_TO_IMMS;
import static com.android.internal.inputmethod.StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION;
import static com.android.internal.inputmethod.StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION;
import android.Manifest;
import android.annotation.DisplayContext;
import android.annotation.DrawableRes;
import android.annotation.DurationMillisLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresFeature;
@@ -2575,6 +2577,20 @@ public final class InputMethodManager {
}
}
/**
* Set a stylus idle-timeout after which handwriting {@code InkWindow} will be removed.
* <p> This API is for tests only.</p>
* @param timeout to set in milliseconds. To reset to default, use a value <= zero.
* @hide
*/
@RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
@TestApi
public void setStylusWindowIdleTimeoutForTest(@DurationMillisLong long timeout) {
synchronized (mH) {
mServiceInvoker.setStylusWindowIdleTimeoutForTest(mClient, timeout);
}
}
/**
* An empty method only to avoid crashes of apps that call this method via reflection and do not
* handle {@link NoSuchMethodException} in a graceful manner.

View File

@@ -87,4 +87,6 @@ oneway interface IInputMethod {
void finishStylusHandwriting();
void removeStylusHandwritingWindow();
void setStylusWindowIdleTimeoutForTest(long timeout);
}

View File

@@ -144,4 +144,10 @@ interface IInputMethodManager {
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ "android.Manifest.permission.INJECT_EVENTS)")
void addVirtualStylusIdForTestSession(in IInputMethodClient client);
/** Set a stylus idle-timeout after which handwriting {@code InkWindow} will be removed. */
@EnforcePermission("TEST_INPUT_METHOD")
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ "android.Manifest.permission.TEST_INPUT_METHOD)")
void setStylusWindowIdleTimeoutForTest(in IInputMethodClient client, long timeout);
}

View File

@@ -286,4 +286,13 @@ final class IInputMethodInvoker {
logRemoteException(e);
}
}
@AnyThread
void setStylusWindowIdleTimeoutForTest(long timeout) {
try {
mTarget.setStylusWindowIdleTimeoutForTest(timeout);
} catch (RemoteException e) {
logRemoteException(e);
}
}
}

View File

@@ -60,6 +60,7 @@ import android.annotation.AnyThread;
import android.annotation.BinderThread;
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.DurationMillisLong;
import android.annotation.EnforcePermission;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -4463,6 +4464,31 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
}
/**
* Helper method to set a stylus idle-timeout after which handwriting {@code InkWindow}
* will be removed.
* @param timeout to set in milliseconds. To reset to default, use a value <= zero.
*/
@BinderThread
@EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
@Override
public void setStylusWindowIdleTimeoutForTest(
IInputMethodClient client, @DurationMillisLong long timeout) {
int uid = Binder.getCallingUid();
synchronized (ImfLock.class) {
if (!canInteractWithImeLocked(uid, client, "setStylusWindowIdleTimeoutForTest")) {
return;
}
final long ident = Binder.clearCallingIdentity();
try {
if (DEBUG) Slog.v(TAG, "Setting stylus window idle timeout");
getCurMethodLocked().setStylusWindowIdleTimeoutForTest(timeout);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
@GuardedBy("ImfLock.class")
private void removeVirtualStylusIdForTestSessionLocked() {
removeStylusDeviceIdLocked(VIRTUAL_STYLUS_ID_FOR_TEST);