The current mechanism to sync InputMethodService#mIsFullscreen to
InputMethodManager#mFullscreenMode is really fragile because
1. Currently the state change is notified via
InputConnection#reportFullscreenMode(), where InputConnection is
designed to be valid only while the IME has input focus to the
target widget.
2. In favor of performance InputMethodService (IMS) calls
InputConnection#reportFullscreenMode() only when #mIsFullscreen
changed. If InputConnection#reportFullscreenMode() failed, there
is no recovery mechanism.
3. Screen oriantation change is likely to cause Window/View focus
state change in the target application, which is likely to
invalidate the current InputConnection.
What our previous workaround [1] did for Bug
21455064 was actually
relaxing the rule 1 only for InputConnection#reportFullscreenMode().
However, my another CL [2] made the lifetime check of InputConnection a
bit more strict again, which revived the issue as Bug
28157836.
Probably a long-term fix would be to stop using InputConnection to sync
that boolean state between IMS and the application. However, it's too
late to do such a refactoring in N, hence this CL relaxes the rule 1
again keeping it as secure as possible.
The idea is that we allow InputConnection#reportFullscreenMode() to
update InputMethodManager#mFullscreenMode regardless of whether
InputConnection is active or not, as long as the InputConnection is
bound to the curent IME. Doing this as a short-term solution is
supporsed to not introduce any new risk because the active IME is
already able to mess up the InputMethodManager#mFullscreenMode by
calling InputConnection#reportFullscreenMode() on any other active
InputConnection. Bug
28406127 will track the long-term solution.
[1]: Id10315efc41d86407ccfb0a2d3956bcd7c0909b8
da589dffddaf4046d3b4fd8d14d5f984a1c4324a
[2]: If2a03bc84d318775fd4a197fa43acde086eda442
aaa38c9f1ae019f0fe8c3ba80630f26e582cc89c
Bug:
28157836
Change-Id: Iba184245a01a3b340f006bc4e415d304de3c2696
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.Trace;
+import android.text.TextUtils;
import android.text.style.SuggestionSpan;
import android.util.Log;
import android.util.Pools.Pool;
}
@Override
- protected void onReportFullscreenMode(boolean enabled) {
- mParentInputMethodManager.setFullscreenMode(enabled);
+ protected void onReportFullscreenMode(boolean enabled, boolean calledInBackground) {
+ mParentInputMethodManager.onReportFullscreenMode(enabled, calledInBackground,
+ getInputMethodId());
}
@Override
+ "connection=" + getInputConnection()
+ " finished=" + isFinished()
+ " mParentInputMethodManager.mActive=" + mParentInputMethodManager.mActive
+ + " mInputMethodId=" + getInputMethodId()
+ "}";
}
}
}
/** @hide */
- public void setFullscreenMode(boolean fullScreen) {
- mFullscreenMode = fullScreen;
+ public void onReportFullscreenMode(boolean fullScreen, boolean calledInBackground,
+ String inputMethodId) {
+ synchronized (mH) {
+ if (!calledInBackground || TextUtils.equals(mCurId, inputMethodId)) {
+ mFullscreenMode = fullScreen;
+ }
+ }
}
/** @hide */
* your UI, else returns false.
*/
public boolean isFullscreenMode() {
- return mFullscreenMode;
+ synchronized (mH) {
+ return mFullscreenMode;
+ }
}
-
+
/**
* Return true if the given view is the currently active view for the
* input method.
mCurId = res.id;
mNextUserActionNotificationSequenceNumber =
res.userActionNotificationSequenceNumber;
+ if (mServedInputConnectionWrapper != null) {
+ mServedInputConnectionWrapper.setInputMethodId(mCurId);
+ }
} else {
if (res.channel != null && res.channel != mCurChannel) {
res.channel.dispose();
private Object mLock = new Object();
@GuardedBy("mLock")
private boolean mFinished = false;
+ @GuardedBy("mLock")
+ private String mInputMethodId;
static class SomeArgs {
Object arg1;
}
}
+ public String getInputMethodId() {
+ synchronized (mLock) {
+ return mInputMethodId;
+ }
+ }
+
+ public void setInputMethodId(final String inputMethodId) {
+ synchronized (mLock) {
+ mInputMethodId = inputMethodId;
+ }
+ }
+
abstract protected boolean isActive();
/**
/**
* Called when the input method started or stopped full-screen mode.
- *
+ * @param enabled {@code true} if the input method starts full-screen mode.
+ * @param calledInBackground {@code true} if this input connection is in a state when incoming
+ * events are usually ignored.
*/
- abstract protected void onReportFullscreenMode(boolean enabled);
+ abstract protected void onReportFullscreenMode(boolean enabled, boolean calledInBackground);
public void getTextAfterCursor(int length, int flags, int seq, IInputContextCallback callback) {
dispatchMessage(obtainMessageIISC(DO_GET_TEXT_AFTER_CURSOR, length, flags, seq, callback));
}
case DO_REPORT_FULLSCREEN_MODE: {
InputConnection ic = getInputConnection();
- if (ic == null) {
+ boolean isBackground = false;
+ if (ic == null || !isActive()) {
Log.w(TAG, "reportFullscreenMode on inexistent InputConnection");
- return;
+ isBackground = true;
}
final boolean enabled = msg.arg1 == 1;
- ic.reportFullscreenMode(enabled);
- onReportFullscreenMode(enabled);
+ if (!isBackground) {
+ ic.reportFullscreenMode(enabled);
+ }
+ // Due to the nature of asynchronous event handling, currently InputMethodService
+ // has relied on the fact that #reportFullscreenMode() can be handled even when the
+ // InputConnection is inactive. We have to notify this event to InputMethodManager.
+ onReportFullscreenMode(enabled, isBackground);
return;
}
case DO_PERFORM_PRIVATE_COMMAND: {