@@ -6,6 +6,7 @@ const { execWithRetriesAndLogs, spawnWithRetriesAndLogs, spawnAndLog } = require
66const { getAdbPath } = require ( '../../../../../utils/environment' ) ;
77const logger = require ( '../../../../../utils/logger' ) ;
88const { escape } = require ( '../../../../../utils/pipeCommands' ) ;
9+ const retry = require ( '../../../../../utils/retry' ) ;
910const DeviceHandle = require ( '../tools/DeviceHandle' ) ;
1011const EmulatorHandle = require ( '../tools/EmulatorHandle' ) ;
1112
@@ -19,6 +20,14 @@ const DEFAULT_INSTALL_OPTIONS = {
1920 retries : 3 ,
2021} ;
2122
23+ const ENROLLED_FINGER_ID = 1 ;
24+ const UNENROLLED_FINGER_ID = 99 ;
25+ const DEFAULT_BIOMETRIC_PIN = '0000' ;
26+ const ENROLLED_FACE_HIT = 1 ;
27+ const UNENROLLED_FACE_HIT = 2 ;
28+ const BOOT_WAIT_RETRIES = 240 ;
29+ const BOOT_WAIT_INTERVAL_MS = 2500 ;
30+
2231class ADB {
2332 constructor ( ) {
2433 this . _cachedApiLevels = new Map ( ) ;
@@ -46,6 +55,11 @@ class ADB {
4655 return { devices, stdout } ;
4756 }
4857
58+ async root ( deviceId ) {
59+ await this . adbCmd ( deviceId , 'root' ) ;
60+ await this . adbCmd ( deviceId , 'wait-for-device' ) ;
61+ }
62+
4963 async getState ( deviceId ) {
5064 try {
5165 const output = await this . adbCmd ( deviceId , `get-state` , {
@@ -375,6 +389,111 @@ class ADB {
375389 return ( await this . adbCmd ( deviceId , `emu "${ escape . inQuotedString ( cmd ) } "` , options ) ) . stdout . trim ( ) ;
376390 }
377391
392+ async matchFinger ( deviceId ) {
393+ await this . emu ( deviceId , `finger touch ${ ENROLLED_FINGER_ID } ` ) ;
394+ await this . emu ( deviceId , `finger remove` ) ;
395+ }
396+
397+ async unmatchFinger ( deviceId ) {
398+ await this . emu ( deviceId , `finger touch ${ UNENROLLED_FINGER_ID } ` ) ;
399+ await this . emu ( deviceId , `finger remove` ) ;
400+ }
401+
402+ async setBiometricEnrollment ( deviceId , enabled ) {
403+ await this . root ( deviceId ) ;
404+ if ( enabled ) {
405+ await this . shell ( deviceId , `locksettings clear --old ${ DEFAULT_BIOMETRIC_PIN } ` ) . catch ( ( ) => { } ) ;
406+ await this . shell ( deviceId , `locksettings set-pin ${ DEFAULT_BIOMETRIC_PIN } ` ) ;
407+ await this . shell ( deviceId , `setprop persist.vendor.fingerprint.virtual.enrollments 1` ) ;
408+ await this . shell ( deviceId , `cmd fingerprint sync` ) ;
409+ } else {
410+ await this . shell ( deviceId , `cmd fingerprint sync` ) ;
411+ await this . shell ( deviceId , `setprop persist.vendor.fingerprint.virtual.enrollments 0` ) ;
412+ await this . shell ( deviceId , `locksettings clear --old ${ DEFAULT_BIOMETRIC_PIN } ` ) . catch ( ( ) => { } ) ;
413+ }
414+ }
415+
416+ async setFaceEnrollment ( deviceId , enabled ) {
417+ await this . root ( deviceId ) ;
418+ if ( enabled ) {
419+ const alreadyActive = await this . _isFaceVirtualHalActive ( deviceId ) ;
420+ if ( ! alreadyActive ) {
421+ await this . shell ( deviceId , `device_config set_sync_disabled_for_tests persistent` ) . catch ( ( ) => { } ) ;
422+ await this . shell ( deviceId , `device_config put biometrics_framework com.android.server.biometrics.face_vhal_feature true` ) ;
423+ await this . shell ( deviceId , `settings put secure biometric_virtual_enabled 1` ) ;
424+ await this . shell ( deviceId , `setprop persist.vendor.face.virtual.strength strong` ) ;
425+ await this . shell ( deviceId , `setprop persist.vendor.face.virtual.type RGB` ) ;
426+ const reverses = await this . _listReverses ( deviceId ) ;
427+ await this . reboot ( deviceId ) ;
428+ await this . root ( deviceId ) ;
429+ await this . _restoreReverses ( deviceId , reverses ) ;
430+ }
431+ await this . shell ( deviceId , `locksettings clear --old ${ DEFAULT_BIOMETRIC_PIN } ` ) . catch ( ( ) => { } ) ;
432+ await this . shell ( deviceId , `locksettings set-pin ${ DEFAULT_BIOMETRIC_PIN } ` ) ;
433+ await this . shell ( deviceId , `setprop persist.vendor.face.virtual.enrollments 1` ) ;
434+ await this . shell ( deviceId , `cmd face sync` ) ;
435+ } else {
436+ await this . shell ( deviceId , `cmd face sync` ) . catch ( ( ) => { } ) ;
437+ await this . shell ( deviceId , `setprop persist.vendor.face.virtual.enrollments 0` ) ;
438+ await this . shell ( deviceId , `locksettings clear --old ${ DEFAULT_BIOMETRIC_PIN } ` ) . catch ( ( ) => { } ) ;
439+ }
440+ }
441+
442+ async _isFaceVirtualHalActive ( deviceId ) {
443+ try {
444+ const virtualEnabled = await this . shell ( deviceId , `settings get secure biometric_virtual_enabled` , { silent : true , retries : 0 } ) ;
445+ const featureFlag = await this . shell ( deviceId , `device_config get biometrics_framework com.android.server.biometrics.face_vhal_feature` , { silent : true , retries : 0 } ) ;
446+ return String ( virtualEnabled ) . trim ( ) === '1' && String ( featureFlag ) . trim ( ) === 'true' ;
447+ } catch ( _ ) {
448+ return false ;
449+ }
450+ }
451+
452+ async _listReverses ( deviceId ) {
453+ try {
454+ const { stdout } = await this . adbCmd ( deviceId , 'reverse --list' ) ;
455+ return ( stdout || '' )
456+ . split ( '\n' )
457+ . map ( line => line . match ( / ( t c p : \d + ) \s + ( t c p : \d + ) / ) )
458+ . filter ( Boolean )
459+ . map ( m => ( { local : m [ 1 ] , remote : m [ 2 ] } ) ) ;
460+ } catch ( _ ) {
461+ return [ ] ;
462+ }
463+ }
464+
465+ async _restoreReverses ( deviceId , reverses ) {
466+ for ( const { local, remote } of reverses ) {
467+ await this . adbCmd ( deviceId , `reverse ${ local } ${ remote } ` ) . catch ( ( ) => { } ) ;
468+ }
469+ }
470+
471+ async matchFace ( deviceId ) {
472+ await this . root ( deviceId ) ;
473+ await this . shell ( deviceId , `setprop vendor.face.virtual.enrollment_hit ${ ENROLLED_FACE_HIT } ` ) ;
474+ }
475+
476+ async unmatchFace ( deviceId ) {
477+ await this . root ( deviceId ) ;
478+ await this . shell ( deviceId , `setprop vendor.face.virtual.enrollment_hit ${ UNENROLLED_FACE_HIT } ` ) ;
479+ }
480+
481+ async reboot ( deviceId ) {
482+ await this . adbCmd ( deviceId , 'reboot' ) ;
483+ await this . adbCmd ( deviceId , 'wait-for-device' ) ;
484+ await this . _waitForBootComplete ( deviceId ) ;
485+ }
486+
487+ async _waitForBootComplete ( deviceId ) {
488+ await retry ( { retries : BOOT_WAIT_RETRIES , interval : BOOT_WAIT_INTERVAL_MS , shouldUnref : true } , async ( ) => {
489+ if ( ! await this . isBootComplete ( deviceId ) ) {
490+ throw new DetoxRuntimeError ( {
491+ message : `Waited for ${ deviceId } to complete booting for too long!` ,
492+ } ) ;
493+ }
494+ } ) ;
495+ }
496+
378497 async reverse ( deviceId , port ) {
379498 return this . adbCmd ( deviceId , `reverse tcp:${ port } tcp:${ port } ` ) ;
380499 }
0 commit comments