• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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