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

Commit 451fd9a6 authored by Nikolas Havrikov's avatar Nikolas Havrikov Committed by Android (Google) Code Review
Browse files

Merge changes I6aaf56f0,I11641791,I92c43548,I1afed9f5,If31a2ed7, ...

* changes:
  Remove unused method isKeyguardLocked
  Move clearing current method to controller
  Rename method clearing sessions appropriately
  Extract hiding status bar icon
  Reduce binding controller coupling to IMMS
  Factor out redundant getter calls
  Avoid unwarranted local variable
  Move setting curId and bindTime for symmetry
  Move and split removing window token
  Move adding fresh window token to controller
  Remove unwarranted local variable
  Move binding current method into the controller
  Extract binding current method logic
  Extract unbinding main IME connection
  Extract unbinding visible IME connection
  Move connectedness booleans into controller
  Split binding main and visible connection
  Move binding current method to controller
  Remove parameter from current input binding
  Move unbinding current method logic to controller
  Extract removing current window token
  Extract setting current method uid
  Inline getters and setters in binding controller
  Move IMMS service connection to the binding controller
  Add accessors for client-related members of IMMS
  Change IMMS not to implement ServiceConnection
  Move more encapsulated IMMS members
  Encapsulate more IMMS members
  Move visible connection state to ImeBindingController
  Encapsulate mVisibleBound in IMMS
  Encapsulate mVisibleConnection in IMMS
  Move encapsulated members to  binding controller
  Introduce InputMethodBindingController
  Avoid calculating the same time span twice
  Extract isSelectedMethodBound method
  Extract reusing current connection logic
  Extract part of the client switch logic
  Extract adding new window token in own method
  Extract updating binding intent
  Encapsulate IMMS members
  Make members final in IMMS where appropriate
  Use method references where available
  Remove unused member mIsLowRam
parents ef292d27 017575ee
Loading
Loading
Loading
Loading
+476 −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 com.android.server.inputmethod;

import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;

import static com.android.server.inputmethod.InputMethodManagerService.MSG_INITIALIZE_IME;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManagerInternal;
import android.content.res.Resources;
import android.inputmethodservice.InputMethodService;
import android.os.Binder;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.Slog;
import android.view.IWindowManager;
import android.view.WindowManager;
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodInfo;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.InputBindResult;
import com.android.internal.inputmethod.UnbindReason;
import com.android.internal.view.IInputMethod;
import com.android.server.wm.WindowManagerInternal;

/**
 * A controller managing the state of the input method binding.
 */
final class InputMethodBindingController {
    static final boolean DEBUG = false;
    private static final String TAG = InputMethodBindingController.class.getSimpleName();

    @NonNull private final InputMethodManagerService mService;
    @NonNull private final Context mContext;
    @NonNull private final ArrayMap<String, InputMethodInfo> mMethodMap;
    @NonNull private final InputMethodUtils.InputMethodSettings mSettings;
    @NonNull private final PackageManagerInternal mPackageManagerInternal;
    @NonNull private final IWindowManager mIWindowManager;
    @NonNull private final WindowManagerInternal mWindowManagerInternal;
    @NonNull private final Resources mRes;

    private long mLastBindTime;
    private boolean mHasConnection;
    @Nullable private String mCurId;
    @Nullable private String mSelectedMethodId;
    @Nullable private Intent mCurIntent;
    @Nullable private IInputMethod mCurMethod;
    private int mCurMethodUid = Process.INVALID_UID;
    private IBinder mCurToken;
    private int mCurSeq;
    private boolean mVisibleBound;

    /**
     * Binding flags for establishing connection to the {@link InputMethodService}.
     */
    private static final int IME_CONNECTION_BIND_FLAGS =
            Context.BIND_AUTO_CREATE
                    | Context.BIND_NOT_VISIBLE
                    | Context.BIND_NOT_FOREGROUND
                    | Context.BIND_IMPORTANT_BACKGROUND;
    /**
     * Binding flags for establishing connection to the {@link InputMethodService} when
     * config_killableInputMethods is enabled.
     */
    private static final int IME_CONNECTION_LOW_PRIORITY_BIND_FLAGS =
            Context.BIND_AUTO_CREATE
                    | Context.BIND_REDUCTION_FLAGS;
    /**
     * Binding flags used only while the {@link InputMethodService} is showing window.
     */
    private static final int IME_VISIBLE_BIND_FLAGS =
            Context.BIND_AUTO_CREATE
                    | Context.BIND_TREAT_LIKE_ACTIVITY
                    | Context.BIND_FOREGROUND_SERVICE
                    | Context.BIND_INCLUDE_CAPABILITIES
                    | Context.BIND_SHOWING_UI
                    | Context.BIND_SCHEDULE_LIKE_TOP_APP;

    /**
     * Binding flags for establishing connection to the {@link InputMethodService}.
     *
     * <p>
     * This defaults to {@link InputMethodBindingController#IME_CONNECTION_BIND_FLAGS} unless
     * config_killableInputMethods is enabled, in which case this takes the value of
     * {@link InputMethodBindingController#IME_CONNECTION_LOW_PRIORITY_BIND_FLAGS}.
     */
    private final int mImeConnectionBindFlags;

    InputMethodBindingController(@NonNull InputMethodManagerService service) {
        mService = service;
        mContext = mService.mContext;
        mMethodMap = mService.mMethodMap;
        mSettings = mService.mSettings;
        mPackageManagerInternal = mService.mPackageManagerInternal;
        mIWindowManager = mService.mIWindowManager;
        mWindowManagerInternal = mService.mWindowManagerInternal;
        mRes = mService.mRes;

        // If configured, use low priority flags to make the IME killable by the lowmemorykiller
        final boolean lowerIMEPriority = mRes.getBoolean(
                com.android.internal.R.bool.config_killableInputMethods);

        if (lowerIMEPriority) {
            mImeConnectionBindFlags =
                    InputMethodBindingController.IME_CONNECTION_LOW_PRIORITY_BIND_FLAGS;
        } else {
            mImeConnectionBindFlags = InputMethodBindingController.IME_CONNECTION_BIND_FLAGS;
        }
    }

    /**
     * Time that we last initiated a bind to the input method, to determine
     * if we should try to disconnect and reconnect to it.
     */
    long getLastBindTime() {
        return mLastBindTime;
    }

    /**
     * Set to true if our ServiceConnection is currently actively bound to
     * a service (whether or not we have gotten its IBinder back yet).
     */
    boolean hasConnection() {
        return mHasConnection;
    }

    /**
     * Id obtained with {@link InputMethodInfo#getId()} for the input method that we are currently
     * connected to or in the process of connecting to.
     *
     * <p>This can be {@code null} when no input method is connected.</p>
     *
     * @see #getSelectedMethodId()
     */
    @Nullable
    String getCurId() {
        return mCurId;
    }

    /**
     * Id obtained with {@link InputMethodInfo#getId()} for the currently selected input method.
     * This is to be synchronized with the secure settings keyed with
     * {@link android.provider.Settings.Secure#DEFAULT_INPUT_METHOD}.
     *
     * <p>This can be transiently {@code null} when the system is re-initializing input method
     * settings, e.g., the system locale is just changed.</p>
     *
     * <p>Note that {@link #getCurId()} is used to track which IME is being connected to
     * {@link com.android.server.inputmethod.InputMethodManagerService}.</p>
     *
     * @see #getCurId()
     */
    @Nullable
    String getSelectedMethodId() {
        return mSelectedMethodId;
    }

    void setSelectedMethodId(@Nullable String selectedMethodId) {
        mSelectedMethodId = selectedMethodId;
    }

    /**
     * The token we have made for the currently active input method, to
     * identify it in the future.
     */
    IBinder getCurToken() {
        return mCurToken;
    }

    /**
     * The Intent used to connect to the current input method.
     */
    @Nullable
    Intent getCurIntent() {
        return mCurIntent;
    }

    /**
     * The current binding sequence number, incremented every time there is
     * a new bind performed.
     */
    int getSequenceNumber() {
        return mCurSeq;
    }

    /**
     * Increase the current binding sequence number by one.
     * Reset to 1 on overflow.
     */
    void advanceSequenceNumber() {
        mCurSeq += 1;
        if (mCurSeq <= 0) {
            mCurSeq = 1;
        }
    }

    /**
     * If non-null, this is the input method service we are currently connected
     * to.
     */
    @Nullable
    IInputMethod getCurMethod() {
        return mCurMethod;
    }

    /**
     * If not {@link Process#INVALID_UID}, then the UID of {@link #getCurIntent()}.
     */
    int getCurMethodUid() {
        return mCurMethodUid;
    }

    /**
     * Indicates whether {@link #getVisibleConnection} is currently in use.
     */
    boolean isVisibleBound() {
        return mVisibleBound;
    }

    /**
     * Used to bring IME service up to visible adjustment while it is being shown.
     */
    @NonNull
    ServiceConnection getVisibleConnection() {
        return mVisibleConnection;
    }

    private final ServiceConnection mVisibleConnection = new ServiceConnection() {
        @Override public void onBindingDied(ComponentName name) {
            synchronized (mMethodMap) {
                if (mVisibleBound) {
                    unbindVisibleConnectionLocked();
                }
            }
        }

        @Override public void onServiceConnected(ComponentName name, IBinder service) {
        }

        @Override public void onServiceDisconnected(ComponentName name) {
        }
    };

    /**
     * Used to bind the IME while it is not currently being shown.
     */
    private final ServiceConnection mMainConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.onServiceConnected");
            synchronized (mMethodMap) {
                if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
                    mCurMethod = IInputMethod.Stub.asInterface(service);
                    updateCurrentMethodUidLocked();
                    if (mCurToken == null) {
                        Slog.w(TAG, "Service connected without a token!");
                        unbindCurrentMethodLocked();
                        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                        return;
                    }
                    if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
                    // Dispatch display id for InputMethodService to update context display.
                    mService.executeOrSendMessage(mCurMethod,
                            mService.mCaller.obtainMessageIOO(MSG_INITIALIZE_IME,
                                    mMethodMap.get(mSelectedMethodId).getConfigChanges(),
                                    mCurMethod, mCurToken));
                    mService.scheduleNotifyImeUidToAudioService(mCurMethodUid);
                    mService.reRequestCurrentClientSessionLocked();
                }
            }
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }

        @GuardedBy("mMethodMap")
        private void updateCurrentMethodUidLocked() {
            final String curMethodPackage = mCurIntent.getComponent().getPackageName();
            final int curMethodUid = mPackageManagerInternal.getPackageUid(
                    curMethodPackage, 0 /* flags */, mSettings.getCurrentUserId());
            if (curMethodUid < 0) {
                Slog.e(TAG, "Failed to get UID for package=" + curMethodPackage);
                mCurMethodUid = Process.INVALID_UID;
            } else {
                mCurMethodUid = curMethodUid;
            }
        }

        @Override
        public void onServiceDisconnected(@NonNull ComponentName name) {
            // Note that mContext.unbindService(this) does not trigger this.  Hence if we are
            // here the
            // disconnection is not intended by IMMS (e.g. triggered because the current IMS
            // crashed),
            // which is irregular but can eventually happen for everyone just by continuing
            // using the
            // device.  Thus it is important to make sure that all the internal states are
            // properly
            // refreshed when this method is called back.  Running
            //    adb install -r <APK that implements the current IME>
            // would be a good way to trigger such a situation.
            synchronized (mMethodMap) {
                if (DEBUG) {
                    Slog.v(TAG, "Service disconnected: " + name + " mCurIntent=" + mCurIntent);
                }
                if (mCurMethod != null && mCurIntent != null
                        && name.equals(mCurIntent.getComponent())) {
                    // We consider this to be a new bind attempt, since the system
                    // should now try to restart the service for us.
                    mLastBindTime = SystemClock.uptimeMillis();
                    mService.clearClientSessionsLocked();
                    mService.clearInputShowRequestLocked();
                    mService.unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME);
                }
            }
        }
    };

    @GuardedBy("mMethodMap")
    void unbindCurrentMethodLocked() {
        if (mVisibleBound) {
            unbindVisibleConnectionLocked();
        }

        if (mHasConnection) {
            unbindMainConnectionLocked();
        }

        if (mCurToken != null) {
            removeCurrentTokenLocked();
            mService.resetSystemUiLocked();
        }

        mCurId = null;
        mService.clearClientSessionsLocked();
    }

    @GuardedBy("mMethodMap")
    void clearCurMethodLocked() {
        mCurMethod = null;
        mCurMethodUid = Process.INVALID_UID;
    }

    @GuardedBy("mMethodMap")
    private void removeCurrentTokenLocked() {
        int curTokenDisplayId = mService.getCurTokenDisplayId();

        if (DEBUG) {
            Slog.v(TAG,
                    "Removing window token: " + mCurToken + " for display: " + curTokenDisplayId);
        }
        mWindowManagerInternal.removeWindowToken(mCurToken, false /* removeWindows */,
                false /* animateExit */, curTokenDisplayId);
        mCurToken = null;
    }

    @GuardedBy("mMethodMap")
    @NonNull
    InputBindResult bindCurrentMethodLocked(int displayIdToShowIme) {
        InputMethodInfo info = mMethodMap.get(mSelectedMethodId);
        if (info == null) {
            throw new IllegalArgumentException("Unknown id: " + mSelectedMethodId);
        }

        mCurIntent = createImeBindingIntent(info.getComponent());

        if (bindCurrentInputMethodServiceMainConnectionLocked()) {
            mCurId = info.getId();
            mLastBindTime = SystemClock.uptimeMillis();

            addFreshWindowTokenLocked(displayIdToShowIme);
            return new InputBindResult(
                    InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
                    null, null, mCurId, mCurSeq, false);
        }

        Slog.w(InputMethodManagerService.TAG,
                "Failure connecting to input method service: " + mCurIntent);
        mCurIntent = null;
        return InputBindResult.IME_NOT_CONNECTED;
    }

    @NonNull
    private Intent createImeBindingIntent(ComponentName component) {
        Intent intent = new Intent(InputMethod.SERVICE_INTERFACE);
        intent.setComponent(component);
        intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
                com.android.internal.R.string.input_method_binding_label);
        intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
                mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS),
                PendingIntent.FLAG_IMMUTABLE));
        return intent;
    }

    @GuardedBy("mMethodMap")
    private void addFreshWindowTokenLocked(int displayIdToShowIme) {
        mCurToken = new Binder();

        mService.setCurTokenDisplayId(displayIdToShowIme);

        try {
            if (DEBUG) {
                Slog.v(TAG, "Adding window token: " + mCurToken + " for display: "
                        + displayIdToShowIme);
            }
            mIWindowManager.addWindowToken(mCurToken, WindowManager.LayoutParams.TYPE_INPUT_METHOD,
                    displayIdToShowIme, null /* options */);
        } catch (RemoteException e) {
            Slog.e(TAG, "Could not add window token " + mCurToken + " for display "
                    + displayIdToShowIme, e);
        }
    }

    @GuardedBy("mMethodMap")
    void unbindMainConnectionLocked() {
        mContext.unbindService(mMainConnection);
        mHasConnection = false;
    }

    @GuardedBy("mMethodMap")
    void unbindVisibleConnectionLocked() {
        mContext.unbindService(mVisibleConnection);
        mVisibleBound = false;
    }

    @GuardedBy("mMethodMap")
    private boolean bindCurrentInputMethodServiceLocked(ServiceConnection conn, int flags) {
        if (mCurIntent == null || conn == null) {
            Slog.e(TAG, "--- bind failed: service = " + mCurIntent + ", conn = " + conn);
            return false;
        }
        return mContext.bindServiceAsUser(mCurIntent, conn, flags,
                new UserHandle(mSettings.getCurrentUserId()));
    }

    @GuardedBy("mMethodMap")
    boolean bindCurrentInputMethodServiceVisibleConnectionLocked() {
        mVisibleBound = bindCurrentInputMethodServiceLocked(mVisibleConnection,
                IME_VISIBLE_BIND_FLAGS);
        return mVisibleBound;
    }

    @GuardedBy("mMethodMap")
    boolean bindCurrentInputMethodServiceMainConnectionLocked() {
        mHasConnection = bindCurrentInputMethodServiceLocked(mMainConnection,
                mImeConnectionBindFlags);
        return mHasConnection;
    }

}
+284 −387

File changed.

Preview size limit exceeded, changes collapsed.