Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 71ab752f authored by Taran Singh's avatar Taran Singh
Browse files

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.

This CL re-introduces [1] with fix: Use new Configuration instance for
IMS#mLastKnownConfig and also handle followup comments.

[1] Ib94fddadb0dae648cf73a4c1642e51edebd19f50

Note: this change has no impact for devices not using DisplayAreas.

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
    Also verify that bug 182604598 don't happen.

Change-Id: I43b6b80cdb35410554412ee1d3b0917ee3198272
parent 3bff4d5e
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -51914,6 +51914,7 @@ package android.view.inputmethod {
    method public int describeContents();
    method public void dump(android.util.Printer, String);
    method public android.content.ComponentName getComponent();
    method public int getConfigChanges();
    method public String getId();
    method public int getIsDefaultResourceId();
    method public String getPackageName();
+4 −0
Original line number Diff line number Diff line
@@ -2899,6 +2899,10 @@ package android.view.inputmethod {
    method @NonNull public static android.view.inputmethod.InlineSuggestionsResponse newInlineSuggestionsResponse(@NonNull java.util.List<android.view.inputmethod.InlineSuggestion>);
  }

  public final class InputMethodInfo implements android.os.Parcelable {
    ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, int);
  }

  public final class InputMethodManager {
    method public int getDisplayId();
    method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int);
+4 −3
Original line number Diff line number Diff line
@@ -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
+102 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.inputmethodservice;

import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Configuration;
import android.content.res.Resources;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;

/**
 * Helper class that takes care of Configuration change behavior of {@link InputMethodService}.
 * Note: this class is public for testing only. Never call any of it's methods for development
 * of IMEs.
 * @hide
 */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public final class ImsConfigurationTracker {

    /**
     * A constant value that represents {@link Configuration} has changed from the last time
     * {@link InputMethodService#onConfigurationChanged(Configuration)} was called.
     */
    private static final int CONFIG_CHANGED = -1;

    @Nullable
    private Configuration mLastKnownConfig = null;
    private int mHandledConfigChanges = 0;
    private boolean mInitialized = false;

    /**
     * Called from {@link InputMethodService.InputMethodImpl
     * #initializeInternal(IBinder, int, IInputMethodPrivilegedOperations, int)} ()}
     * @param handledConfigChanges Configuration changes declared handled by IME
     * {@link android.R.styleable#InputMethod_configChanges}.
     */
    @MainThread
    public void onInitialize(int handledConfigChanges) {
        Preconditions.checkState(!mInitialized, "onInitialize can be called only once.");
        mInitialized = true;
        mHandledConfigChanges = handledConfigChanges;
    }

    /**
     * Called from {@link InputMethodService.InputMethodImpl#onBindInput()}
     */
    @MainThread
    public void onBindInput(@Nullable Resources resources) {
        Preconditions.checkState(mInitialized,
                "onBindInput can be called only after onInitialize().");
        if (mLastKnownConfig == null && resources != null) {
            mLastKnownConfig = new Configuration(resources.getConfiguration());
        }
    }

    /**
     * Dynamically set handled configChanges.
     * Note: this method is public for testing only.
     */
    public void setHandledConfigChanges(int configChanges) {
        mHandledConfigChanges = configChanges;
    }

    /**
     * Called from {@link InputMethodService.InputMethodImpl#onConfigurationChanged(Configuration)}}
     */
    @MainThread
    public void onConfigurationChanged(@NonNull Configuration newConfig,
            @NonNull Runnable resetStateForNewConfigurationRunner) {
        if (!mInitialized) {
            return;
        }
        final int diff = mLastKnownConfig != null
                ? mLastKnownConfig.diffPublicOnly(newConfig) : CONFIG_CHANGED;
        // If the new config is the same as the config this Service is already running with,
        // then don't bother calling resetStateForNewConfiguration.
        final int unhandledDiff = (diff & ~mHandledConfigChanges);
        if (unhandledDiff != 0) {
            resetStateForNewConfigurationRunner.run();
        }
        if (diff != 0) {
            mLastKnownConfig = new Configuration(newConfig);
        }
    }
}
+9 −3
Original line number Diff line number Diff line
@@ -513,6 +513,7 @@ public class InputMethodService extends AbstractInputMethodService {
    private boolean mIsAutomotive;
    private Handler mHandler;
    private boolean mImeSurfaceScheduledForRemoval;
    private ImsConfigurationTracker mConfigTracker = new ImsConfigurationTracker();

    /**
     * An opaque {@link Binder} token of window requesting {@link InputMethodImpl#showSoftInput}
@@ -588,12 +589,13 @@ 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");
            mConfigTracker.onInitialize(configChanges);
            mPrivOps.set(privilegedOperations);
            InputMethodPrivilegedOperationsRegistry.put(token, mPrivOps);
            updateInputMethodDisplay(displayId);
@@ -663,6 +665,7 @@ public class InputMethodService extends AbstractInputMethodService {
            reportFullscreenMode();
            initialize();
            onBindInput();
            mConfigTracker.onBindInput(getResources());
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }

@@ -1428,10 +1431,13 @@ 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();
        mConfigTracker.onConfigurationChanged(newConfig, this::resetStateForNewConfiguration);
    }

    private void resetStateForNewConfiguration() {
Loading