From 357b2f60e147d18af9c8399b346a9e63de1baaf3 Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Tue, 14 Feb 2017 09:40:03 -0800 Subject: [PATCH] Take state snapshot in IMMS#attachNewInputLocked() With this CL, we can finally start taking a state snapshot every time IMMS#attachNewInputLocked() gets called, which allows us to include the last N (currently set to 16) history of such snapshots in dumpsys from InputMethodManagerService: adb shell dumpsys input_method This is a huge milestone for us to start relying more on historical debugging rather than logcat-based debugging. Test: Run `adb shell dumpsys input_method` to make sure that "mStartInputHistory" entries are updated. Bug: 35079353 Change-Id: Icb29c4ef0703553ead8654d3255a554de6d1a9b4 --- .../android/server/InputMethodManagerService.java | 185 ++++++++++++++++++++- 1 file changed, 182 insertions(+), 3 deletions(-) diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index b6b3c43a597b..dc987fac97e5 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -149,11 +149,15 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.nio.charset.StandardCharsets; import java.security.InvalidParameterException; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.WeakHashMap; +import java.util.concurrent.atomic.AtomicInteger; /** * This class provides a system service that manages input methods. @@ -525,20 +529,189 @@ public class InputMethodManagerService extends IInputMethodManager.Stub *

*/ private static class StartInputInfo { + private static final AtomicInteger sSequenceNumber = new AtomicInteger(0); + + final int mSequenceNumber; + final long mTimestamp; + final long mWallTime; @NonNull final IBinder mImeToken; + @NonNull + final String mImeId; + // @InputMethodClient.StartInputReason + final int mStartInputReason; + final boolean mRestarting; @Nullable final IBinder mTargetWindow; - - StartInputInfo(@NonNull IBinder imeToken, @Nullable IBinder targetWindow) { + @NonNull + final EditorInfo mEditorInfo; + final int mTargetWindowSoftInputMode; + final int mClientBindSequenceNumber; + + StartInputInfo(@NonNull IBinder imeToken, @NonNull String imeId, + /* @InputMethodClient.StartInputReason */ int startInputReason, boolean restarting, + @Nullable IBinder targetWindow, @NonNull EditorInfo editorInfo, + int targetWindowSoftInputMode, int clientBindSequenceNumber) { + mSequenceNumber = sSequenceNumber.getAndIncrement(); + mTimestamp = SystemClock.uptimeMillis(); + mWallTime = System.currentTimeMillis(); mImeToken = imeToken; + mImeId = imeId; + mStartInputReason = startInputReason; + mRestarting = restarting; mTargetWindow = targetWindow; + mEditorInfo = editorInfo; + mTargetWindowSoftInputMode = targetWindowSoftInputMode; + mClientBindSequenceNumber = clientBindSequenceNumber; } } @GuardedBy("mMethodMap") private final WeakHashMap mStartInputMap = new WeakHashMap<>(); + /** + * A ring buffer to store the history of {@link StartInputInfo}. + */ + private static final class StartInputHistory { + /** + * Entry size for non low-RAM devices. + * + *

TODO: Consider to follow what other system services have been doing to manage + * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).

+ */ + private final static int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 16; + + /** + * Entry size for non low-RAM devices. + * + *

TODO: Consider to follow what other system services have been doing to manage + * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).

+ */ + private final static 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 bugger. + */ + 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; + @NonNull + String mImeTokenString; + @NonNull + String mImeId; + /* @InputMethodClient.StartInputReason */ + int mStartInputReason; + boolean mRestarting; + @NonNull + String mTargetWindowString; + @NonNull + EditorInfo mEditorInfo; + int mTargetWindowSoftInputMode; + int mClientBindSequenceNumber; + + Entry(@NonNull StartInputInfo original) { + set(original); + } + + void set(@NonNull StartInputInfo original) { + mSequenceNumber = original.mSequenceNumber; + mTimestamp = original.mTimestamp; + mWallTime = original.mWallTime; + // Intentionally convert to String so as not to keep a strong reference to a Binder + // object. + mImeTokenString = String.valueOf(original.mImeToken); + mImeId = original.mImeId; + mStartInputReason = original.mStartInputReason; + mRestarting = original.mRestarting; + // 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 {@lin 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 SimpleDateFormat dataFormat = + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US); + + 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=" + dataFormat.format(new Date(entry.mWallTime)) + + " (timestamp=" + entry.mTimestamp + ")" + + " reason=" + + InputMethodClient.getStartInputReason(entry.mStartInputReason) + + " restarting=" + entry.mRestarting); + + pw.print(prefix); + pw.println(" imeToken=" + entry.mImeTokenString + " [" + entry.mImeId + "]"); + + pw.print(prefix); + pw.println(" targetWin=" + entry.mTargetWindowString + + " [" + entry.mEditorInfo.packageName + "]" + + " clientBindSeq=" + entry.mClientBindSequenceNumber); + + pw.print(prefix); + pw.println(" softInputMode=" + InputMethodClient.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("mMethodMap") + @NonNull + private final StartInputHistory mStartInputHistory = new StartInputHistory(); + class SettingsObserver extends ContentObserver { int mUserId; boolean mRegistered = false; @@ -1380,8 +1553,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } final Binder startInputToken = new Binder(); - final StartInputInfo info = new StartInputInfo(mCurToken, mCurFocusedWindow); + final StartInputInfo info = new StartInputInfo(mCurToken, mCurId, startInputReason, + !initial, mCurFocusedWindow, mCurAttribute, mCurFocusedWindowSoftInputMode, + mCurSeq); mStartInputMap.put(startInputToken, info); + mStartInputHistory.addEntry(info); final SessionState session = mCurClient.curSession; executeOrSendMessage(session.method, mCaller.obtainMessageIIOOOO( @@ -4156,6 +4332,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mSwitchingController.dump(p); p.println(" mSettings:"); mSettings.dumpLocked(p, " "); + + p.println(" mStartInputHistory:"); + mStartInputHistory.dump(pw, " "); } p.println(" "); -- 2.11.0