@@ -25,6 +25,28 @@ const ANDROID_TEXT_VARIATION_VISIBLE_PASSWORD = 0x00000090;
2525const ANDROID_KEYBOARD_DISMISS_MAX_ATTEMPTS = 2 ;
2626const ANDROID_KEYBOARD_DISMISS_RETRY_DELAY_MS = 120 ;
2727const 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
2951type AndroidKeyboardType =
3052 | 'text'
@@ -122,22 +144,8 @@ export async function dismissAndroidKeyboardWithAdb(
122144}
123145
124146function parseAndroidKeyboardState ( stdout : string ) : AndroidKeyboardState {
125- const visibility = parseAndroidKeyboardVisibility ( stdout ) ;
126- let visible = visibility ?? false ;
127- if ( visibility === null ) {
128- const imeWindowVisibility = stdout . match ( / \b m I m e W i n d o w V i s = 0 x ( [ 0 - 9 a - f A - 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 ( / \b i n p u t T y p e = 0 x ( [ 0 - 9 a - f A - 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 , / \b p a c k a g e N a m e = ( [ A - Z a - z 0 - 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 ( / \b m I m e W i n d o w V i s = 0 x ( [ 0 - 9 a - f A - 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 , / \b i n p u t T y p e = 0 x ( [ 0 - 9 a - f A - F ] + ) \b / gi) ;
186+ return value ? `0x${ value . toLowerCase ( ) } ` : undefined ;
187+ }
188+
178189function 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+
186220function 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 ( m I n p u t S h o w n | m I s I n p u t V i e w S h o w n | i s I n p u t V i e w S h o w n | m D e c o r V i e w V i s i b l e | m W i n d o w V i s i b l e | m I n S h o w W i n d o w ) = ( [ a - z A - 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+
212268function classifyAndroidKeyboardType ( inputType : string ) : AndroidKeyboardType {
213269 const parsed = Number . parseInt ( inputType . replace ( / ^ 0 x / 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