Avoid IME restart for configChanges

Handle onConfigurationChanged() in order to prevent restarting
InputMethodService everytime. We introduce a new API attribute
"configChanges" in InputMethod(attrs.xml) which when declared
by IME, will be responsible for handling mentioned
configuration changes.

Bug: 167948419
Test: atest InputMethodServiceTest
    Manually:
      1. Patch Ie91e7a8e06b80864ef9409031e8543858552d70d to use dual
         display area.
      2. Open applications with editors on both display areas.
      3. Attach a debug point for IMS#onConfigurationChanged().
      4. Make sure IMS#resetStateForNewConfiguration() is not called
         when IME moves between these two identical DisplayAreas

Change-Id: Iff88b768c6b06cf5cf1fe9e97ee97f8f78e6f0bd
This commit is contained in:
Taran Singh
2021-01-29 16:42:52 +00:00
parent a84fbe7f17
commit d6f8a1ada1
10 changed files with 203 additions and 15 deletions

View File

@@ -171,7 +171,7 @@ class IInputMethodWrapper extends IInputMethod.Stub
SomeArgs args = (SomeArgs) msg.obj;
try {
inputMethod.initializeInternal((IBinder) args.arg1, msg.arg1,
(IInputMethodPrivilegedOperations) args.arg2);
(IInputMethodPrivilegedOperations) args.arg2, (int) args.arg3);
} finally {
args.recycle();
}
@@ -280,9 +280,10 @@ class IInputMethodWrapper extends IInputMethod.Stub
@BinderThread
@Override
public void initializeInternal(IBinder token, int displayId,
IInputMethodPrivilegedOperations privOps) {
IInputMethodPrivilegedOperations privOps, int configChanges) {
mCaller.executeOrSendMessage(
mCaller.obtainMessageIOO(DO_INITIALIZE_INTERNAL, displayId, token, privOps));
mCaller.obtainMessageIOOO(DO_INITIALIZE_INTERNAL, displayId, token, privOps,
configChanges));
}
@BinderThread

View File

@@ -70,6 +70,7 @@ import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -131,6 +132,7 @@ import android.widget.TextView;
import android.window.WindowMetricsHelper;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.IInputContentUriToken;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.inputmethod.InputMethodPrivilegedOperations;
@@ -513,6 +515,8 @@ public class InputMethodService extends AbstractInputMethodService {
private boolean mIsAutomotive;
private Handler mHandler;
private boolean mImeSurfaceScheduledForRemoval;
private Configuration mLastKnownConfig;
private int mHandledConfigChanges;
/**
* An opaque {@link Binder} token of window requesting {@link InputMethodImpl#showSoftInput}
@@ -588,12 +592,14 @@ public class InputMethodService extends AbstractInputMethodService {
@MainThread
@Override
public final void initializeInternal(@NonNull IBinder token, int displayId,
IInputMethodPrivilegedOperations privilegedOperations) {
IInputMethodPrivilegedOperations privilegedOperations,
int configChanges) {
if (InputMethodPrivilegedOperationsRegistry.isRegistered(token)) {
Log.w(TAG, "The token has already registered, ignore this initialization.");
return;
}
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initializeInternal");
mHandledConfigChanges = configChanges;
mPrivOps.set(privilegedOperations);
InputMethodPrivilegedOperationsRegistry.put(token, mPrivOps);
updateInputMethodDisplay(displayId);
@@ -821,6 +827,9 @@ public class InputMethodService extends AbstractInputMethodService {
setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition);
}
final boolean isVisible = isInputViewShown();
if (isVisible && getResources() != null) {
mLastKnownConfig = getResources().getConfiguration();
}
final boolean visibilityChanged = isVisible != wasVisible;
if (resultReceiver != null) {
resultReceiver.send(visibilityChanged
@@ -1428,10 +1437,37 @@ public class InputMethodService extends AbstractInputMethodService {
* state: {@link #onStartInput} if input is active, and
* {@link #onCreateInputView} and {@link #onStartInputView} and related
* appropriate functions if the UI is displayed.
* <p>Starting with {@link Build.VERSION_CODES#S}, IMEs can opt into handling configuration
* changes themselves instead of being restarted with
* {@link android.R.styleable#InputMethod_configChanges}.
*/
@Override public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
resetStateForNewConfiguration();
if (shouldImeRestartForConfig(newConfig)) {
resetStateForNewConfiguration();
}
}
/**
* @return {@code true} if {@link InputMethodService} needs to restart to handle
* .{@link #onConfigurationChanged(Configuration)}
*/
@VisibleForTesting
boolean shouldImeRestartForConfig(@NonNull Configuration newConfig) {
if (mLastKnownConfig == null) {
return true;
}
// If the new config is the same as the config this Service is already running with,
// then don't bother calling resetStateForNewConfiguration.
int diff = mLastKnownConfig.diffPublicOnly(newConfig);
if (diff != 0) {
// remove attrs not-relevant to IME service.
diff &= ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
diff &= ActivityInfo.CONFIG_KEYBOARD;
diff &= ActivityInfo.CONFIG_NAVIGATION;
}
int unhandledDiff = (diff & ~mHandledConfigChanges);
return unhandledDiff != 0;
}
private void resetStateForNewConfiguration() {
@@ -3181,7 +3217,17 @@ public class InputMethodService extends AbstractInputMethodService {
requestHideSelf(InputMethodManager.HIDE_NOT_ALWAYS);
}
}
@VisibleForTesting
void setLastKnownConfig(@NonNull Configuration config) {
mLastKnownConfig = config;
}
@VisibleForTesting
void setHandledConfigChanges(int configChanges) {
mHandledConfigChanges = configChanges;
}
void startExtractingText(boolean inputChanged) {
final ExtractEditText eet = mExtractEditText;
if (eet != null && getCurrentInputStarted()