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

Commit 86d5047d authored by Yohei Yukawa's avatar Yohei Yukawa Committed by Android (Google) Code Review
Browse files

Merge "Extract out inner classes from IMMS" into main

parents 8f308d1e 64a82f9a
Loading
Loading
Loading
Loading
+0 −341
Original line number Diff line number Diff line
@@ -67,7 +67,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UiThread;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -196,21 +195,16 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.security.InvalidParameterException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.OptionalInt;
import java.util.WeakHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.IntConsumer;

@@ -730,344 +724,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
    private final CopyOnWriteArrayList<InputMethodListListener> mInputMethodListListeners =
            new CopyOnWriteArrayList<>();

    /**
     * Internal state snapshot when
     * {@link IInputMethod#startInput(IInputMethod.StartInputParams)} is about to be called.
     *
     * <p>Calling that IPC endpoint basically means that
     * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
     * back in the current IME process shortly, which will also affect what the current IME starts
     * receiving from {@link InputMethodService#getCurrentInputConnection()}. In other words, this
     * snapshot will be taken every time when {@link InputMethodManagerService} is initiating a new
     * logical input session between the client application and the current IME.</p>
     *
     * <p>Be careful to not keep strong references to this object forever, which can prevent
     * {@link StartInputInfo#mImeToken} and {@link StartInputInfo#mTargetWindow} from being GC-ed.
     * </p>
     */
    private static class StartInputInfo {
        private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);

        final int mSequenceNumber;
        final long mTimestamp;
        final long mWallTime;
        @UserIdInt
        final int mImeUserId;
        @NonNull
        final IBinder mImeToken;
        final int mImeDisplayId;
        @NonNull
        final String mImeId;
        @StartInputReason
        final int mStartInputReason;
        final boolean mRestarting;
        @UserIdInt
        final int mTargetUserId;
        final int mTargetDisplayId;
        @Nullable
        final IBinder mTargetWindow;
        @NonNull
        final EditorInfo mEditorInfo;
        @SoftInputModeFlags
        final int mTargetWindowSoftInputMode;
        final int mClientBindSequenceNumber;

        StartInputInfo(@UserIdInt int imeUserId, @NonNull IBinder imeToken, int imeDisplayId,
                @NonNull String imeId, @StartInputReason int startInputReason, boolean restarting,
                @UserIdInt int targetUserId, int targetDisplayId, @Nullable IBinder targetWindow,
                @NonNull EditorInfo editorInfo, @SoftInputModeFlags int targetWindowSoftInputMode,
                int clientBindSequenceNumber) {
            mSequenceNumber = sSequenceNumber.getAndIncrement();
            mTimestamp = SystemClock.uptimeMillis();
            mWallTime = System.currentTimeMillis();
            mImeUserId = imeUserId;
            mImeToken = imeToken;
            mImeDisplayId = imeDisplayId;
            mImeId = imeId;
            mStartInputReason = startInputReason;
            mRestarting = restarting;
            mTargetUserId = targetUserId;
            mTargetDisplayId = targetDisplayId;
            mTargetWindow = targetWindow;
            mEditorInfo = editorInfo;
            mTargetWindowSoftInputMode = targetWindowSoftInputMode;
            mClientBindSequenceNumber = clientBindSequenceNumber;
        }
    }

    @GuardedBy("ImfLock.class")
    private final WeakHashMap<IBinder, IBinder> mImeTargetWindowMap = new WeakHashMap<>();

    @VisibleForTesting
    static final class SoftInputShowHideHistory {
        private final Entry[] mEntries = new Entry[16];
        private int mNextIndex = 0;
        private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);

        static final class Entry {
            final int mSequenceNumber = sSequenceNumber.getAndIncrement();
            @Nullable
            final ClientState mClientState;
            @SoftInputModeFlags
            final int mFocusedWindowSoftInputMode;
            @SoftInputShowHideReason
            final int mReason;
            // The timing of handling showCurrentInputLocked() or hideCurrentInputLocked().
            final long mTimestamp;
            final long mWallTime;
            final boolean mInFullscreenMode;
            @NonNull
            final String mFocusedWindowName;
            @Nullable
            final EditorInfo mEditorInfo;
            @NonNull
            final String mRequestWindowName;
            @Nullable
            final String mImeControlTargetName;
            @Nullable
            final String mImeTargetNameFromWm;
            @Nullable
            final String mImeSurfaceParentName;

            Entry(ClientState client, EditorInfo editorInfo,
                    String focusedWindowName, @SoftInputModeFlags int softInputMode,
                    @SoftInputShowHideReason int reason,
                    boolean inFullscreenMode, String requestWindowName,
                    @Nullable String imeControlTargetName, @Nullable String imeTargetName,
                    @Nullable String imeSurfaceParentName) {
                mClientState = client;
                mEditorInfo = editorInfo;
                mFocusedWindowName = focusedWindowName;
                mFocusedWindowSoftInputMode = softInputMode;
                mReason = reason;
                mTimestamp = SystemClock.uptimeMillis();
                mWallTime = System.currentTimeMillis();
                mInFullscreenMode = inFullscreenMode;
                mRequestWindowName = requestWindowName;
                mImeControlTargetName = imeControlTargetName;
                mImeTargetNameFromWm = imeTargetName;
                mImeSurfaceParentName = imeSurfaceParentName;
            }
        }

        void addEntry(@NonNull Entry entry) {
            final int index = mNextIndex;
            mEntries[index] = entry;
            mNextIndex = (mNextIndex + 1) % mEntries.length;
        }

        void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
            final DateTimeFormatter formatter =
                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
                            .withZone(ZoneId.systemDefault());

            for (int i = 0; i < mEntries.length; ++i) {
                final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
                if (entry == null) {
                    continue;
                }
                pw.print(prefix);
                pw.println("SoftInputShowHide #" + entry.mSequenceNumber + ":");

                pw.print(prefix);
                pw.println("  time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
                        + " (timestamp=" + entry.mTimestamp + ")");

                pw.print(prefix);
                pw.print("  reason=" + InputMethodDebug.softInputDisplayReasonToString(
                        entry.mReason));
                pw.println(" inFullscreenMode=" + entry.mInFullscreenMode);

                pw.print(prefix);
                pw.println("  requestClient=" + entry.mClientState);

                pw.print(prefix);
                pw.println("  focusedWindowName=" + entry.mFocusedWindowName);

                pw.print(prefix);
                pw.println("  requestWindowName=" + entry.mRequestWindowName);

                pw.print(prefix);
                pw.println("  imeControlTargetName=" + entry.mImeControlTargetName);

                pw.print(prefix);
                pw.println("  imeTargetNameFromWm=" + entry.mImeTargetNameFromWm);

                pw.print(prefix);
                pw.println("  imeSurfaceParentName=" + entry.mImeSurfaceParentName);

                pw.print(prefix);
                pw.print("  editorInfo:");
                if (entry.mEditorInfo != null) {
                    pw.print(" inputType=" + entry.mEditorInfo.inputType);
                    pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
                    pw.println(" fieldId (viewId)=" + entry.mEditorInfo.fieldId);
                } else {
                    pw.println(" null");
                }

                pw.print(prefix);
                pw.println("  focusedWindowSoftInputMode=" + InputMethodDebug.softInputModeToString(
                        entry.mFocusedWindowSoftInputMode));
            }
        }
    }

    /**
     * A ring buffer to store the history of {@link StartInputInfo}.
     */
    private static final class StartInputHistory {
        /**
         * Entry size for non low-RAM devices.
         *
         * <p>TODO: Consider to follow what other system services have been doing to manage
         * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
         */
        private static final int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 32;

        /**
         * Entry size for low-RAM devices.
         *
         * <p>TODO: Consider to follow what other system services have been doing to manage
         * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
         */
        private static final int ENTRY_SIZE_FOR_LOW_RAM_DEVICE = 5;

        private static int getEntrySize() {
            if (ActivityManager.isLowRamDeviceStatic()) {
                return ENTRY_SIZE_FOR_LOW_RAM_DEVICE;
            } else {
                return ENTRY_SIZE_FOR_HIGH_RAM_DEVICE;
            }
        }

        /**
         * Backing store for the ring buffer.
         */
        private final Entry[] mEntries = new Entry[getEntrySize()];

        /**
         * An index of {@link #mEntries}, to which next {@link #addEntry(StartInputInfo)} should
         * write.
         */
        private int mNextIndex = 0;

        /**
         * Recyclable entry to store the information in {@link StartInputInfo}.
         */
        private static final class Entry {
            int mSequenceNumber;
            long mTimestamp;
            long mWallTime;
            @UserIdInt
            int mImeUserId;
            @NonNull
            String mImeTokenString;
            int mImeDisplayId;
            @NonNull
            String mImeId;
            @StartInputReason
            int mStartInputReason;
            boolean mRestarting;
            @UserIdInt
            int mTargetUserId;
            int mTargetDisplayId;
            @NonNull
            String mTargetWindowString;
            @NonNull
            EditorInfo mEditorInfo;
            @SoftInputModeFlags
            int mTargetWindowSoftInputMode;
            int mClientBindSequenceNumber;

            Entry(@NonNull StartInputInfo original) {
                set(original);
            }

            void set(@NonNull StartInputInfo original) {
                mSequenceNumber = original.mSequenceNumber;
                mTimestamp = original.mTimestamp;
                mWallTime = original.mWallTime;
                mImeUserId = original.mImeUserId;
                // Intentionally convert to String so as not to keep a strong reference to a Binder
                // object.
                mImeTokenString = String.valueOf(original.mImeToken);
                mImeDisplayId = original.mImeDisplayId;
                mImeId = original.mImeId;
                mStartInputReason = original.mStartInputReason;
                mRestarting = original.mRestarting;
                mTargetUserId = original.mTargetUserId;
                mTargetDisplayId = original.mTargetDisplayId;
                // Intentionally convert to String so as not to keep a strong reference to a Binder
                // object.
                mTargetWindowString = String.valueOf(original.mTargetWindow);
                mEditorInfo = original.mEditorInfo;
                mTargetWindowSoftInputMode = original.mTargetWindowSoftInputMode;
                mClientBindSequenceNumber = original.mClientBindSequenceNumber;
            }
        }

        /**
         * Add a new entry and discard the oldest entry as needed.
         * @param info {@link StartInputInfo} to be added.
         */
        void addEntry(@NonNull StartInputInfo info) {
            final int index = mNextIndex;
            if (mEntries[index] == null) {
                mEntries[index] = new Entry(info);
            } else {
                mEntries[index].set(info);
            }
            mNextIndex = (mNextIndex + 1) % mEntries.length;
        }

        void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
            final DateTimeFormatter formatter =
                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
                            .withZone(ZoneId.systemDefault());

            for (int i = 0; i < mEntries.length; ++i) {
                final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
                if (entry == null) {
                    continue;
                }
                pw.print(prefix);
                pw.println("StartInput #" + entry.mSequenceNumber + ":");

                pw.print(prefix);
                pw.println("  time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
                        + " (timestamp=" + entry.mTimestamp + ")"
                        + " reason="
                        + InputMethodDebug.startInputReasonToString(entry.mStartInputReason)
                        + " restarting=" + entry.mRestarting);

                pw.print(prefix);
                pw.print("  imeToken=" + entry.mImeTokenString + " [" + entry.mImeId + "]");
                pw.print(" imeUserId=" + entry.mImeUserId);
                pw.println(" imeDisplayId=" + entry.mImeDisplayId);

                pw.print(prefix);
                pw.println("  targetWin=" + entry.mTargetWindowString
                        + " [" + entry.mEditorInfo.packageName + "]"
                        + " targetUserId=" + entry.mTargetUserId
                        + " targetDisplayId=" + entry.mTargetDisplayId
                        + " clientBindSeq=" + entry.mClientBindSequenceNumber);

                pw.print(prefix);
                pw.println("  softInputMode=" + InputMethodDebug.softInputModeToString(
                        entry.mTargetWindowSoftInputMode));

                pw.print(prefix);
                pw.println("  inputType=0x" + Integer.toHexString(entry.mEditorInfo.inputType)
                        + " imeOptions=0x" + Integer.toHexString(entry.mEditorInfo.imeOptions)
                        + " fieldId=0x" + Integer.toHexString(entry.mEditorInfo.fieldId)
                        + " fieldName=" + entry.mEditorInfo.fieldName
                        + " actionId=" + entry.mEditorInfo.actionId
                        + " actionLabel=" + entry.mEditorInfo.actionLabel);
            }
        }
    }

    @GuardedBy("ImfLock.class")
    @NonNull
    private final StartInputHistory mStartInputHistory = new StartInputHistory();
+149 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.SystemClock;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;

import com.android.internal.inputmethod.InputMethodDebug;
import com.android.internal.inputmethod.SoftInputShowHideReason;

import java.io.PrintWriter;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicInteger;

final class SoftInputShowHideHistory {
    private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);

    private final Entry[] mEntries = new Entry[16];
    private int mNextIndex = 0;

    static final class Entry {
        final int mSequenceNumber = sSequenceNumber.getAndIncrement();
        @Nullable
        final ClientState mClientState;
        @WindowManager.LayoutParams.SoftInputModeFlags
        final int mFocusedWindowSoftInputMode;
        @SoftInputShowHideReason
        final int mReason;
        // The timing of handling showCurrentInputLocked() or hideCurrentInputLocked().
        final long mTimestamp;
        final long mWallTime;
        final boolean mInFullscreenMode;
        @NonNull
        final String mFocusedWindowName;
        @Nullable
        final EditorInfo mEditorInfo;
        @NonNull
        final String mRequestWindowName;
        @Nullable
        final String mImeControlTargetName;
        @Nullable
        final String mImeTargetNameFromWm;
        @Nullable
        final String mImeSurfaceParentName;

        Entry(ClientState client, EditorInfo editorInfo,
                String focusedWindowName,
                @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode,
                @SoftInputShowHideReason int reason,
                boolean inFullscreenMode, String requestWindowName,
                @Nullable String imeControlTargetName, @Nullable String imeTargetName,
                @Nullable String imeSurfaceParentName) {
            mClientState = client;
            mEditorInfo = editorInfo;
            mFocusedWindowName = focusedWindowName;
            mFocusedWindowSoftInputMode = softInputMode;
            mReason = reason;
            mTimestamp = SystemClock.uptimeMillis();
            mWallTime = System.currentTimeMillis();
            mInFullscreenMode = inFullscreenMode;
            mRequestWindowName = requestWindowName;
            mImeControlTargetName = imeControlTargetName;
            mImeTargetNameFromWm = imeTargetName;
            mImeSurfaceParentName = imeSurfaceParentName;
        }
    }

    void addEntry(@NonNull Entry entry) {
        final int index = mNextIndex;
        mEntries[index] = entry;
        mNextIndex = (mNextIndex + 1) % mEntries.length;
    }

    void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
        final DateTimeFormatter formatter =
                DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
                        .withZone(ZoneId.systemDefault());

        for (int i = 0; i < mEntries.length; ++i) {
            final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
            if (entry == null) {
                continue;
            }
            pw.print(prefix);
            pw.println("SoftInputShowHide #" + entry.mSequenceNumber + ":");

            pw.print(prefix);
            pw.println("  time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
                    + " (timestamp=" + entry.mTimestamp + ")");

            pw.print(prefix);
            pw.print("  reason=" + InputMethodDebug.softInputDisplayReasonToString(
                    entry.mReason));
            pw.println(" inFullscreenMode=" + entry.mInFullscreenMode);

            pw.print(prefix);
            pw.println("  requestClient=" + entry.mClientState);

            pw.print(prefix);
            pw.println("  focusedWindowName=" + entry.mFocusedWindowName);

            pw.print(prefix);
            pw.println("  requestWindowName=" + entry.mRequestWindowName);

            pw.print(prefix);
            pw.println("  imeControlTargetName=" + entry.mImeControlTargetName);

            pw.print(prefix);
            pw.println("  imeTargetNameFromWm=" + entry.mImeTargetNameFromWm);

            pw.print(prefix);
            pw.println("  imeSurfaceParentName=" + entry.mImeSurfaceParentName);

            pw.print(prefix);
            pw.print("  editorInfo:");
            if (entry.mEditorInfo != null) {
                pw.print(" inputType=" + entry.mEditorInfo.inputType);
                pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
                pw.println(" fieldId (viewId)=" + entry.mEditorInfo.fieldId);
            } else {
                pw.println(" null");
            }

            pw.print(prefix);
            pw.println("  focusedWindowSoftInputMode=" + InputMethodDebug.softInputModeToString(
                    entry.mFocusedWindowSoftInputMode));
        }
    }
}
+189 −0

File added.

Preview size limit exceeded, changes collapsed.

+98 −0

File added.

Preview size limit exceeded, changes collapsed.

+2 −2
Original line number Diff line number Diff line
@@ -91,8 +91,8 @@ public final class InputMethodManagerServiceTests {
    @Test
    public void testSoftInputShowHideHistoryDump_withNulls_doesntThrow() {
        var writer = new StringWriter();
        var history = new InputMethodManagerService.SoftInputShowHideHistory();
        history.addEntry(new InputMethodManagerService.SoftInputShowHideHistory.Entry(
        var history = new SoftInputShowHideHistory();
        history.addEntry(new SoftInputShowHideHistory.Entry(
                null,
                null,
                null,