IME API: InputConnecton#setImeTemporarilyConsumesInput(boolean)

Add API to set IME will temporarily consumes the input, for
this case, the cursor will be invisible in the app.

Bug: 175362887
Test: atest FrameworksCoreTests:TextViewTest
Test: atest CtsWidgetTestCases:TextViewTest

Change-Id: Ic04cacfd73f1f4cb254bb16caf6b04c00c91a318
This commit is contained in:
Lan Wei
2020-12-11 18:15:13 +08:00
parent f749076910
commit 30fe6aa964
10 changed files with 147 additions and 3 deletions

View File

@@ -50616,6 +50616,7 @@ package android.view.inputmethod {
method public boolean sendKeyEvent(android.view.KeyEvent);
method public boolean setComposingRegion(int, int);
method public boolean setComposingText(CharSequence, int);
method public default boolean setImeTemporarilyConsumesInput(boolean);
method public boolean setSelection(int, int);
field public static final int CURSOR_UPDATE_IMMEDIATE = 1; // 0x1
field public static final int CURSOR_UPDATE_MONITOR = 2; // 0x2

View File

@@ -163,11 +163,13 @@ public class BaseInputConnection implements InputConnection {
}
/**
* Default implementation calls {@link #finishComposingText()}.
* Default implementation calls {@link #finishComposingText()} and
* {@code setImeTemporarilyConsumesInput(false)}.
*/
@CallSuper
public void closeConnection() {
finishComposingText();
setImeTemporarilyConsumesInput(false);
}
/**

View File

@@ -1002,4 +1002,22 @@ public interface InputConnection {
*/
boolean commitContent(@NonNull InputContentInfo inputContentInfo, int flags,
@Nullable Bundle opts);
/**
* Called by the input method to indicate that it temporarily consumes all input for itself,
* or no longer does so.
*
* <p>Editors should reflect that they are temporarily not receiving input by hiding the
* cursor if {@code imeTemporarilyConsumesInput} is {@code true}, and resume showing the
* cursor if it is {@code false}.
*
* @param imeTemporarilyConsumesInput {@code true} when the IME is temporarily consuming input
* and the cursor should be hidden, {@code false} when input to the editor resumes and the
* cursor should be shown again.
* @return {@code true} on success, {@code false} if the input connection is no longer valid, or
* the protocol is not supported.
*/
default boolean setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput) {
return false;
}
}

View File

@@ -335,4 +335,13 @@ public class InputConnectionWrapper implements InputConnection {
public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
return mTarget.commitContent(inputContentInfo, flags, opts);
}
/**
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
@Override
public boolean setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput) {
return mTarget.setImeTemporarilyConsumesInput(imeTemporarilyConsumesInput);
}
}

View File

@@ -494,6 +494,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private TextUtils.TruncateAt mEllipsize;
// A flag to indicate the cursor was hidden by IME.
private boolean mImeTemporarilyConsumesInput;
// Whether cursor is visible without regard to {@link mImeTemporarilyConsumesInput}.
// {code true} is the default value.
private boolean mCursorVisibleFromAttr = true;
static class Drawables {
static final int LEFT = 0;
static final int TOP = 1;
@@ -10496,7 +10503,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* Set whether the cursor is visible. The default is true. Note that this property only
* makes sense for editable TextView.
* makes sense for editable TextView. If IME is temporarily consuming the input, the cursor will
* be always invisible, visibility will be updated as the last state when IME does not consume
* the input anymore.
*
* @see #isCursorVisible()
*
@@ -10504,6 +10513,25 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
@android.view.RemotableViewMethod
public void setCursorVisible(boolean visible) {
mCursorVisibleFromAttr = visible;
updateCursorVisibleInternal();
}
/**
* Sets the IME is temporarily consuming the input and make the cursor invisible if
* {@code imeTemporarilyConsumesInput} is {@code true}. Otherwise, make the cursor visible.
*
* @param imeTemporarilyConsumesInput {@code true} if IME is temporarily consuming the input
*
* @hide
*/
public void setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput) {
mImeTemporarilyConsumesInput = imeTemporarilyConsumesInput;
updateCursorVisibleInternal();
}
private void updateCursorVisibleInternal() {
boolean visible = mCursorVisibleFromAttr && !mImeTemporarilyConsumesInput;
if (visible && mEditor == null) return; // visible is the default value with no edit data
createEditorIfNeeded();
if (mEditor.mCursorVisible != visible) {
@@ -10518,7 +10546,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
* @return whether or not the cursor is visible (assuming this TextView is editable)
* @return whether or not the cursor is visible (assuming this TextView is editable). This
* method may return {@code false} when the IME is temporarily consuming the input even if the
* {@code mEditor.mCursorVisible} attribute is {@code true} or {@code #setCursorVisible(true)}
* is called.
*
* @see #setCursorVisible(boolean)
*

View File

@@ -79,6 +79,7 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub {
private static final int DO_CLOSE_CONNECTION = 150;
private static final int DO_COMMIT_CONTENT = 160;
private static final int DO_GET_SURROUNDING_TEXT = 41;
private static final int DO_SET_IME_TEMPORARILY_CONSUMES_INPUT = 170;
@GuardedBy("mLock")
@@ -266,6 +267,16 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub {
dispatchMessage(mH.obtainMessage(DO_COMMIT_CONTENT, flags, 0 /* unused */, args));
}
/**
* Dispatches the request for setting ime temporarily consumes input.
*
* <p>See {@link InputConnection#setImeTemporarilyConsumesInput(boolean)}.
*/
public void setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput) {
dispatchMessage(obtainMessageB(DO_SET_IME_TEMPORARILY_CONSUMES_INPUT,
imeTemporarilyConsumesInput));
}
void dispatchMessage(Message msg) {
// If we are calling this from the main thread, then we can call
// right through. Otherwise, we need to send the message to the
@@ -811,6 +822,22 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub {
}
return;
}
case DO_SET_IME_TEMPORARILY_CONSUMES_INPUT: {
Trace.traceBegin(Trace.TRACE_TAG_INPUT,
"InputConnection#setImeTemporarilyConsumesInput");
try {
InputConnection ic = getInputConnection();
if (ic == null || !isActive()) {
Log.w(TAG,
"setImeTemporarilyConsumesInput on inactive InputConnection");
return;
}
ic.setImeTemporarilyConsumesInput(msg.arg1 == 1);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_INPUT);
}
return;
}
}
Log.w(TAG, "Unhandled message code: " + msg.what);
}
@@ -837,4 +864,8 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub {
args.arg2 = arg2;
return mH.obtainMessage(what, 0, 0, args);
}
Message obtainMessageB(int what, boolean arg1) {
return mH.obtainMessage(what, arg1 ? 1 : 0, 0);
}
}

View File

@@ -85,4 +85,6 @@ import com.android.internal.inputmethod.ISurroundingTextResultCallback;
void getSurroundingText(int beforeLength, int afterLength, int flags,
ISurroundingTextResultCallback callback);
void setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput);
}

View File

@@ -524,6 +524,19 @@ public class InputConnectionWrapper implements InputConnection {
value, TAG, "commitContent()", mCancellationGroup, MAX_WAIT_TIME_MILLIS) != 0;
}
/**
* See {@link InputConnection#setImeTemporarilyConsumesInput(boolean)}.
*/
@AnyThread
public boolean setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput) {
try {
mIInputContext.setImeTemporarilyConsumesInput(imeTemporarilyConsumesInput);
return true;
} catch (RemoteException e) {
return false;
}
}
@AnyThread
private boolean isMethodMissing(@MissingMethodFlags final int methodFlag) {
return (mMissingMethods & methodFlag) == methodFlag;

View File

@@ -244,6 +244,15 @@ public class EditableInputConnection extends BaseInputConnection
return true;
}
@Override
public boolean setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput) {
if (mTextView == null) {
return super.setImeTemporarilyConsumesInput(imeTemporarilyConsumesInput);
}
mTextView.setImeTemporarilyConsumesInput(imeTemporarilyConsumesInput);
return true;
}
@Override
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);

View File

@@ -276,6 +276,34 @@ public class TextViewTest {
0, mTextView.getImeOptions() & EditorInfo.IME_FLAG_NO_FULLSCREEN);
}
@Test
@UiThreadTest
public void setSetImeTemporarilyConsumesInput_recoveryToVisible() {
mTextView = new TextView(mActivity);
mTextView.setCursorVisible(true);
assertTrue(mTextView.isCursorVisible());
mTextView.setImeTemporarilyConsumesInput(true);
assertFalse(mTextView.isCursorVisible());
mTextView.setImeTemporarilyConsumesInput(false);
assertTrue(mTextView.isCursorVisible());
}
@Test
@UiThreadTest
public void setSetImeTemporarilyConsumesInput_recoveryToInvisible() {
mTextView = new TextView(mActivity);
mTextView.setCursorVisible(false);
assertFalse(mTextView.isCursorVisible());
mTextView.setImeTemporarilyConsumesInput(true);
assertFalse(mTextView.isCursorVisible());
mTextView.setImeTemporarilyConsumesInput(false);
assertFalse(mTextView.isCursorVisible());
}
private String createLongText() {
int size = 600 * 1000;
final StringBuilder builder = new StringBuilder(size);