Skip to content

Commit cebbedd

Browse files
committed
refactor: simplify Android keyboard parsing
1 parent 27548f0 commit cebbedd

1 file changed

Lines changed: 101 additions & 55 deletions

File tree

src/platforms/android/device-input-state.ts

Lines changed: 101 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,28 @@ const ANDROID_TEXT_VARIATION_VISIBLE_PASSWORD = 0x00000090;
2525
const ANDROID_KEYBOARD_DISMISS_MAX_ATTEMPTS = 2;
2626
const ANDROID_KEYBOARD_DISMISS_RETRY_DELAY_MS = 120;
2727
const ANDROID_KEYCODE_ESCAPE = '111';
28+
const ANDROID_KEYBOARD_VISIBILITY_KEYS = [
29+
'mInputShown',
30+
'mIsInputViewShown',
31+
'isInputViewShown',
32+
'mDecorViewVisible',
33+
'mWindowVisible',
34+
'mInShowWindow',
35+
];
36+
const ANDROID_KEYBOARD_CLASS_BY_INPUT_CLASS = new Map<number, AndroidKeyboardType>([
37+
[ANDROID_INPUT_TYPE_CLASS_NUMBER, 'number'],
38+
[ANDROID_INPUT_TYPE_CLASS_PHONE, 'phone'],
39+
[ANDROID_INPUT_TYPE_CLASS_DATETIME, 'datetime'],
40+
]);
41+
const ANDROID_EMAIL_TEXT_VARIATIONS = new Set([
42+
ANDROID_TEXT_VARIATION_EMAIL_ADDRESS,
43+
ANDROID_TEXT_VARIATION_WEB_EMAIL_ADDRESS,
44+
]);
45+
const ANDROID_PASSWORD_TEXT_VARIATIONS = new Set([
46+
ANDROID_TEXT_VARIATION_PASSWORD,
47+
ANDROID_TEXT_VARIATION_WEB_PASSWORD,
48+
ANDROID_TEXT_VARIATION_VISIBLE_PASSWORD,
49+
]);
2850

2951
type AndroidKeyboardType =
3052
| 'text'
@@ -122,22 +144,8 @@ export async function dismissAndroidKeyboardWithAdb(
122144
}
123145

124146
function parseAndroidKeyboardState(stdout: string): AndroidKeyboardState {
125-
const visibility = parseAndroidKeyboardVisibility(stdout);
126-
let visible = visibility ?? false;
127-
if (visibility === null) {
128-
const imeWindowVisibility = stdout.match(/\bmImeWindowVis=0x([0-9a-fA-F]+)\b/);
129-
if (imeWindowVisibility?.[1]) {
130-
const flags = Number.parseInt(imeWindowVisibility[1], 16);
131-
if (!Number.isNaN(flags)) {
132-
visible = (flags & 0x1) !== 0;
133-
}
134-
}
135-
}
136-
137-
const inputTypeMatches = Array.from(stdout.matchAll(/\binputType=0x([0-9a-fA-F]+)\b/gi));
138-
const lastInputType =
139-
inputTypeMatches.length > 0 ? inputTypeMatches[inputTypeMatches.length - 1]?.[1] : undefined;
140-
const inputType = lastInputType ? `0x${lastInputType.toLowerCase()}` : undefined;
147+
const visible = parseAndroidKeyboardVisibility(stdout) ?? parseLegacyImeWindowVisibility(stdout);
148+
const inputType = parseLastAndroidInputType(stdout);
141149
const focusedPackage = parseLastDumpsysValue(stdout, /\bpackageName=([A-Za-z0-9_.]+)\b/g);
142150
const focusedResourceId = parseLastDumpsysValue(
143151
stdout,
@@ -149,23 +157,10 @@ function parseAndroidKeyboardState(stdout: string): AndroidKeyboardState {
149157
focusedResourceId,
150158
inputMethodPackage,
151159
);
152-
if (
153-
!inputMethodPackage &&
154-
(isFallbackAndroidInputMethodPackage(focusedPackage) ||
155-
isFallbackAndroidInputMethodResource(focusedResourceId))
156-
) {
157-
emitDiagnostic({
158-
level: 'warn',
159-
phase: 'android_input_ownership_fallback',
160-
data: {
161-
focusedPackage,
162-
focusedResourceId,
163-
},
164-
});
165-
}
160+
emitAndroidInputOwnershipFallbackDiagnostic(focusedPackage, focusedResourceId, inputMethodPackage);
166161

167162
return {
168-
visible,
163+
visible: visible ?? false,
169164
inputType,
170165
type: inputType ? classifyAndroidKeyboardType(inputType) : undefined,
171166
inputMethodPackage,
@@ -175,6 +170,22 @@ function parseAndroidKeyboardState(stdout: string): AndroidKeyboardState {
175170
};
176171
}
177172

173+
function parseLegacyImeWindowVisibility(stdout: string): boolean | null {
174+
const imeWindowVisibility = stdout.match(/\bmImeWindowVis=0x([0-9a-fA-F]+)\b/);
175+
const rawFlags = imeWindowVisibility?.[1];
176+
if (!rawFlags) return null;
177+
178+
const flags = Number.parseInt(rawFlags, 16);
179+
if (Number.isNaN(flags)) return null;
180+
181+
return (flags & 0x1) !== 0;
182+
}
183+
184+
function parseLastAndroidInputType(stdout: string): string | undefined {
185+
const value = parseLastDumpsysValue(stdout, /\binputType=0x([0-9a-fA-F]+)\b/gi);
186+
return value ? `0x${value.toLowerCase()}` : undefined;
187+
}
188+
178189
function parseLastDumpsysValue(stdout: string, pattern: RegExp): string | undefined {
179190
let value: string | undefined;
180191
for (const match of stdout.matchAll(pattern)) {
@@ -183,55 +194,90 @@ function parseLastDumpsysValue(stdout: string, pattern: RegExp): string | undefi
183194
return value;
184195
}
185196

197+
function emitAndroidInputOwnershipFallbackDiagnostic(
198+
focusedPackage: string | undefined,
199+
focusedResourceId: string | undefined,
200+
inputMethodPackage: string | undefined,
201+
): void {
202+
if (inputMethodPackage) return;
203+
if (
204+
!isFallbackAndroidInputMethodPackage(focusedPackage) &&
205+
!isFallbackAndroidInputMethodResource(focusedResourceId)
206+
) {
207+
return;
208+
}
209+
210+
emitDiagnostic({
211+
level: 'warn',
212+
phase: 'android_input_ownership_fallback',
213+
data: {
214+
focusedPackage,
215+
focusedResourceId,
216+
},
217+
});
218+
}
219+
186220
function parseAndroidKeyboardVisibility(stdout: string): boolean | null {
221+
const latestByKey = parseLatestBooleanDumpsysValues(stdout, ANDROID_KEYBOARD_VISIBILITY_KEYS);
222+
return resolveAndroidKeyboardVisibility(latestByKey);
223+
}
224+
225+
function parseLatestBooleanDumpsysValues(stdout: string, keys: string[]): Map<string, boolean> {
187226
const latestByKey = new Map<string, boolean>();
188-
const pattern =
189-
/\b(mInputShown|mIsInputViewShown|isInputViewShown|mDecorViewVisible|mWindowVisible|mInShowWindow)=([a-zA-Z]+)\b/g;
227+
const pattern = new RegExp(`\\b(${keys.join('|')})=([a-zA-Z]+)\\b`, 'g');
190228
for (const match of stdout.matchAll(pattern)) {
191229
const key = match[1];
192230
const value = match[2]?.toLowerCase();
193231
if (!key || (value !== 'true' && value !== 'false')) continue;
194232
latestByKey.set(key, value === 'true');
195233
}
234+
return latestByKey;
235+
}
236+
237+
function resolveAndroidKeyboardVisibility(latestByKey: Map<string, boolean>): boolean | null {
196238
if (latestByKey.size === 0) return null;
197239

198-
const windowVisible =
199-
latestByKey.get('mWindowVisible') ??
200-
latestByKey.get('mDecorViewVisible') ??
201-
latestByKey.get('mInShowWindow');
240+
const windowVisible = firstDefinedBoolean(latestByKey, [
241+
'mWindowVisible',
242+
'mDecorViewVisible',
243+
'mInShowWindow',
244+
]);
202245
if (windowVisible !== undefined) return windowVisible;
203246

204247
const inputShown = latestByKey.get('mInputShown');
205248
if (inputShown !== undefined) return inputShown;
206249

207-
const inputViewShown =
208-
latestByKey.get('mIsInputViewShown') ?? latestByKey.get('isInputViewShown');
250+
const inputViewShown = firstDefinedBoolean(latestByKey, [
251+
'mIsInputViewShown',
252+
'isInputViewShown',
253+
]);
209254
return inputViewShown ?? null;
210255
}
211256

257+
function firstDefinedBoolean(
258+
values: Map<string, boolean>,
259+
keys: readonly string[],
260+
): boolean | undefined {
261+
for (const key of keys) {
262+
const value = values.get(key);
263+
if (value !== undefined) return value;
264+
}
265+
return undefined;
266+
}
267+
212268
function classifyAndroidKeyboardType(inputType: string): AndroidKeyboardType {
213269
const parsed = Number.parseInt(inputType.replace(/^0x/i, ''), 16);
214270
if (Number.isNaN(parsed)) return 'unknown';
271+
215272
const inputClass = parsed & ANDROID_INPUT_TYPE_CLASS_MASK;
216-
if (inputClass === ANDROID_INPUT_TYPE_CLASS_NUMBER) return 'number';
217-
if (inputClass === ANDROID_INPUT_TYPE_CLASS_PHONE) return 'phone';
218-
if (inputClass === ANDROID_INPUT_TYPE_CLASS_DATETIME) return 'datetime';
273+
const knownInputClass = ANDROID_KEYBOARD_CLASS_BY_INPUT_CLASS.get(inputClass);
274+
if (knownInputClass) return knownInputClass;
219275
if (inputClass !== ANDROID_INPUT_TYPE_CLASS_TEXT) return 'unknown';
220276

221277
const variation = parsed & ANDROID_INPUT_TYPE_VARIATION_MASK;
222-
if (
223-
variation === ANDROID_TEXT_VARIATION_EMAIL_ADDRESS ||
224-
variation === ANDROID_TEXT_VARIATION_WEB_EMAIL_ADDRESS
225-
) {
226-
return 'email';
227-
}
228-
if (
229-
variation === ANDROID_TEXT_VARIATION_PASSWORD ||
230-
variation === ANDROID_TEXT_VARIATION_WEB_PASSWORD ||
231-
variation === ANDROID_TEXT_VARIATION_VISIBLE_PASSWORD
232-
) {
233-
return 'password';
234-
}
278+
if (ANDROID_EMAIL_TEXT_VARIATIONS.has(variation)) return 'email';
279+
if (ANDROID_PASSWORD_TEXT_VARIATIONS.has(variation)) return 'password';
280+
235281
return 'text';
236282
}
237283

0 commit comments

Comments
 (0)