1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.inputmethod; 18 19 import android.annotation.NonNull; 20 import android.annotation.UserIdInt; 21 import android.app.ActivityManager; 22 import android.view.WindowManager; 23 import android.view.inputmethod.EditorInfo; 24 25 import com.android.internal.inputmethod.InputMethodDebug; 26 import com.android.internal.inputmethod.StartInputReason; 27 28 import java.io.PrintWriter; 29 import java.time.Instant; 30 import java.time.ZoneId; 31 import java.time.format.DateTimeFormatter; 32 import java.util.Locale; 33 34 /** 35 * A ring buffer to store the history of {@link StartInputInfo}. 36 */ 37 final class StartInputHistory { 38 /** 39 * Entry size for non low-RAM devices. 40 * 41 * <p>TODO: Consider to follow what other system services have been doing to manage 42 * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p> 43 */ 44 private static final int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 32; 45 46 /** 47 * Entry size for low-RAM devices. 48 * 49 * <p>TODO: Consider to follow what other system services have been doing to manage 50 * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p> 51 */ 52 private static final int ENTRY_SIZE_FOR_LOW_RAM_DEVICE = 5; 53 getEntrySize()54 private static int getEntrySize() { 55 if (ActivityManager.isLowRamDeviceStatic()) { 56 return ENTRY_SIZE_FOR_LOW_RAM_DEVICE; 57 } else { 58 return ENTRY_SIZE_FOR_HIGH_RAM_DEVICE; 59 } 60 } 61 62 /** 63 * Backing store for the ring buffer. 64 */ 65 private final Entry[] mEntries = new Entry[getEntrySize()]; 66 67 /** 68 * An index of {@link #mEntries}, to which next 69 * {@link #addEntry(StartInputInfo)} should 70 * write. 71 */ 72 private int mNextIndex = 0; 73 74 /** 75 * Recyclable entry to store the information in {@link StartInputInfo}. 76 */ 77 private static final class Entry { 78 int mSequenceNumber; 79 long mTimestamp; 80 long mWallTime; 81 @UserIdInt 82 int mImeUserId; 83 @NonNull 84 String mImeTokenString; 85 int mImeDisplayId; 86 @NonNull 87 String mImeId; 88 @StartInputReason 89 int mStartInputReason; 90 boolean mRestarting; 91 @UserIdInt 92 int mTargetUserId; 93 int mTargetDisplayId; 94 @NonNull 95 String mTargetWindowString; 96 @NonNull 97 EditorInfo mEditorInfo; 98 @WindowManager.LayoutParams.SoftInputModeFlags 99 int mTargetWindowSoftInputMode; 100 int mClientBindSequenceNumber; 101 Entry(@onNull StartInputInfo original)102 Entry(@NonNull StartInputInfo original) { 103 set(original); 104 } 105 set(@onNull StartInputInfo original)106 void set(@NonNull StartInputInfo original) { 107 mSequenceNumber = original.mSequenceNumber; 108 mTimestamp = original.mTimestamp; 109 mWallTime = original.mWallTime; 110 mImeUserId = original.mImeUserId; 111 // Intentionally convert to String so as not to keep a strong reference to a Binder 112 // object. 113 mImeTokenString = String.valueOf(original.mImeToken); 114 mImeDisplayId = original.mImeDisplayId; 115 mImeId = original.mImeId; 116 mStartInputReason = original.mStartInputReason; 117 mRestarting = original.mRestarting; 118 mTargetUserId = original.mTargetUserId; 119 mTargetDisplayId = original.mTargetDisplayId; 120 // Intentionally convert to String so as not to keep a strong reference to a Binder 121 // object. 122 mTargetWindowString = String.valueOf(original.mTargetWindow); 123 mEditorInfo = original.mEditorInfo; 124 mTargetWindowSoftInputMode = original.mTargetWindowSoftInputMode; 125 mClientBindSequenceNumber = original.mClientBindSequenceNumber; 126 } 127 } 128 129 /** 130 * Add a new entry and discard the oldest entry as needed. 131 * 132 * @param info {@link StartInputInfo} to be added. 133 */ addEntry(@onNull StartInputInfo info)134 void addEntry(@NonNull StartInputInfo info) { 135 final int index = mNextIndex; 136 if (mEntries[index] == null) { 137 mEntries[index] = new Entry(info); 138 } else { 139 mEntries[index].set(info); 140 } 141 mNextIndex = (mNextIndex + 1) % mEntries.length; 142 } 143 dump(@onNull PrintWriter pw, @NonNull String prefix)144 void dump(@NonNull PrintWriter pw, @NonNull String prefix) { 145 final DateTimeFormatter formatter = 146 DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US) 147 .withZone(ZoneId.systemDefault()); 148 149 for (int i = 0; i < mEntries.length; ++i) { 150 final Entry entry = mEntries[(i + mNextIndex) % mEntries.length]; 151 if (entry == null) { 152 continue; 153 } 154 pw.print(prefix); 155 pw.println("StartInput #" + entry.mSequenceNumber + ":"); 156 157 pw.print(prefix); 158 pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime)) 159 + " (timestamp=" + entry.mTimestamp + ")" 160 + " reason=" 161 + InputMethodDebug.startInputReasonToString(entry.mStartInputReason) 162 + " restarting=" + entry.mRestarting); 163 164 pw.print(prefix); 165 pw.print(" imeToken=" + entry.mImeTokenString + " [" + entry.mImeId + "]"); 166 pw.print(" imeUserId=" + entry.mImeUserId); 167 pw.println(" imeDisplayId=" + entry.mImeDisplayId); 168 169 pw.print(prefix); 170 pw.println(" targetWin=" + entry.mTargetWindowString 171 + " [" + entry.mEditorInfo.packageName + "]" 172 + " targetUserId=" + entry.mTargetUserId 173 + " targetDisplayId=" + entry.mTargetDisplayId 174 + " clientBindSeq=" + entry.mClientBindSequenceNumber); 175 176 pw.print(prefix); 177 pw.println(" softInputMode=" + InputMethodDebug.softInputModeToString( 178 entry.mTargetWindowSoftInputMode)); 179 180 pw.print(prefix); 181 pw.println(" inputType=0x" + Integer.toHexString(entry.mEditorInfo.inputType) 182 + " imeOptions=0x" + Integer.toHexString(entry.mEditorInfo.imeOptions) 183 + " fieldId=0x" + Integer.toHexString(entry.mEditorInfo.fieldId) 184 + " fieldName=" + entry.mEditorInfo.fieldName 185 + " actionId=" + entry.mEditorInfo.actionId 186 + " actionLabel=" + entry.mEditorInfo.actionLabel); 187 } 188 } 189 } 190