99
1010import { Adb , AdbServerClient } from "@yume-chan/adb" ;
1111import { AdbServerNodeTcpConnector } from "@yume-chan/adb-server-node-tcp" ;
12- import { ReadableStream } from "@yume-chan/stream-extra" ;
13- import { PackageManager } from "@yume-chan/android-bin" ;
1412import Device = AdbServerClient . Device ;
1513
16- import { readFile , stat } from "node:fs/promises" ;
17- import { resolve } from "node:path" ;
18-
1914import Controller from "../../core/Controller.ts" ;
2015import { ENV_EXTRA_VERBOSE , ENV_VERBOSE } from "../../index.ts" ;
2116import { ScrcpyServer } from "../scrcpy/ScrcpyServer.ts" ;
2217import { getLogger } from "@logtape/logtape" ;
2318import DeviceFinder from "./DeviceFinder.ts" ;
24- import { ON_DEVICE_ADB_GLOBAL_SETTINGS } from "../../core/Constants .ts" ;
19+ import { HeadsetSetup } from "./HeadsetSetup .ts" ;
2520
2621// Override the log function
2722const logger = getLogger ( [ "android" , "AdbManager" ] ) ;
@@ -30,6 +25,7 @@ export class AdbManager {
3025 controller : Controller ;
3126 adbServer ! : AdbServerClient ;
3227 videoStreamServer : ScrcpyServer ;
28+ headsetSetup ! : HeadsetSetup ;
3329 // Keep list of serial of devices with a stream already starting
3430 clientCurrentlyStreaming : Device [ ] = [ ] ;
3531 observer ! : AdbServerClient . DeviceObserver ; //!: AdbServerClient;
@@ -46,6 +42,7 @@ export class AdbManager {
4642 logger . info ( "Connect to device's ADB server" ) ;
4743
4844 this . videoStreamServer = new ScrcpyServer ( this ) ;
45+ this . headsetSetup = new HeadsetSetup ( this . adbServer ) ;
4946 }
5047
5148 async init ( ) {
@@ -61,8 +58,8 @@ export class AdbManager {
6158 // Sanitize stale ADB entries on startup (side-effect: disconnects offline ones)
6259 if ( this . isDeviceReady ( device ) ) {
6360 // Apply M2L2 headset settings only if the device is ready
64- this . checkAdbParameters ( device ) . catch ( e =>
65- logger . error ( `[${ device . serial } ] Unexpected error in checkAdbParameters : {e}` , { e } )
61+ this . headsetSetup . setupHeadset ( device ) . catch ( e =>
62+ logger . error ( `[${ device . serial } ] Unexpected error in setupHeadset : {e}` , { e } )
6663 ) ;
6764 }
6865
@@ -81,8 +78,8 @@ export class AdbManager {
8178 this . startNewStream ( device ) . catch ( e =>
8279 logger . error ( `[${ device . serial } ] Unexpected error in startNewStream: {e}` , { e } )
8380 ) ;
84- this . checkAdbParameters ( device ) . catch ( e =>
85- logger . error ( `[${ device . serial } ] Unexpected error in checkAdbParameters : {e}` , { e } )
81+ this . headsetSetup . setupHeadset ( device ) . catch ( e =>
82+ logger . error ( `[${ device . serial } ] Unexpected error in setupHeadset : {e}` , { e } )
8683 ) ;
8784 }
8885 } ) ;
@@ -256,150 +253,4 @@ export class AdbManager {
256253 return success
257254 }
258255
259- async checkAdbParameters ( device : Device ) {
260- if ( ! this . isDeviceReady ( device ) ) return ;
261-
262- // Only modify headsets, don't jam phone's parameters
263- if ( ! device . model ?. startsWith ( "Quest_" ) ) return ;
264-
265- logger . debug ( `[${ device . serial } ] Checking on-device global ADB settings...` ) ;
266-
267- let adb : Adb ;
268- try {
269- adb = new Adb ( await this . adbServer . createTransport ( device ) ) ;
270- } catch ( e ) {
271- logger . warn ( `[${ device . serial } ] Could not open ADB transport to check parameters — device may not be authorized yet: {e}` , { e } ) ;
272- return ;
273- }
274-
275- for ( const [ globalSetting , globalSettingValue ] of Object . entries ( ON_DEVICE_ADB_GLOBAL_SETTINGS ) as [
276- keyof typeof ON_DEVICE_ADB_GLOBAL_SETTINGS ,
277- typeof ON_DEVICE_ADB_GLOBAL_SETTINGS [ keyof typeof ON_DEVICE_ADB_GLOBAL_SETTINGS ]
278- ] [ ] )
279- {
280- if ( ! await this . checkAdbParameter ( adb , globalSetting , globalSettingValue ) ) {
281- logger . debug ( `[${ device . serial } ] ADB parameters for '${ globalSetting } ' isn't correct, fixing it...` ) ;
282- await this . setAdbParameter ( adb , globalSetting , globalSettingValue ) ;
283- if ( ! await this . checkAdbParameter ( adb , globalSetting , globalSettingValue ) ) {
284- logger . warn ( `[${ device . serial } ] Couldn't properly set setting ${ globalSetting } , skipping it...` ) ;
285- }
286- }
287- }
288- logger . debug ( `[${ device . serial } ] All on-device global ADB settings are good` ) ;
289-
290- await this . checkRequiredApps ( adb , device . serial ) ;
291- }
292-
293- async checkRequiredApps ( adb : Adb , serial : string ) {
294- const REQUIRED_APP = "com.tpn.adbautoenable" ;
295- const REQUIRED_PERMISSION = "android.permission.WRITE_SECURE_SETTINGS" ;
296-
297- logger . debug ( `[${ serial } ] Checking required app '${ REQUIRED_APP } '...` ) ;
298-
299- // Check if app is installed
300- const pmProcess = await adb . subprocess . noneProtocol . spawn ( `pm list packages ${ REQUIRED_APP } ` ) ;
301- let pmOutput = "" ;
302- // @ts -expect-error
303- for await ( const chunk of pmProcess . output . pipeThrough ( new TextDecoderStream ( ) ) ) {
304- pmOutput += chunk ;
305- }
306-
307- if ( ! pmOutput . includes ( `package:${ REQUIRED_APP } ` ) ) {
308- logger . warn ( `[${ serial } ] Required app '${ REQUIRED_APP } ' is not installed — installing it...` ) ;
309- const installed = await this . installApk ( adb , serial , resolve ( "toolkit" , `${ REQUIRED_APP } .apk` ) ) ;
310- if ( ! installed ) return ;
311- }
312- logger . debug ( `[${ serial } ] '${ REQUIRED_APP } ' is installed` ) ;
313-
314- // Check if WRITE_SECURE_SETTINGS is already granted
315- const dumpsysProcess = await adb . subprocess . noneProtocol . spawn ( `dumpsys package ${ REQUIRED_APP } ` ) ;
316- let dumpsysOutput = "" ;
317- // @ts -expect-error
318- for await ( const chunk of dumpsysProcess . output . pipeThrough ( new TextDecoderStream ( ) ) ) {
319- dumpsysOutput += chunk ;
320- }
321-
322- // The permission line looks like: "android.permission.WRITE_SECURE_SETTINGS: granted=true"
323- const permissionGranted = dumpsysOutput . includes ( `${ REQUIRED_PERMISSION } : granted=true` ) ;
324-
325- if ( permissionGranted ) {
326- logger . debug ( `[${ serial } ] '${ REQUIRED_APP } ' already has ${ REQUIRED_PERMISSION } ` ) ;
327- return ;
328- }
329-
330- logger . debug ( `[${ serial } ] Granting ${ REQUIRED_PERMISSION } to '${ REQUIRED_APP } '...` ) ;
331- const grantProcess = await adb . subprocess . noneProtocol . spawn ( `pm grant ${ REQUIRED_APP } ${ REQUIRED_PERMISSION } ` ) ;
332- let grantOutput = "" ;
333- // @ts -expect-error
334- for await ( const chunk of grantProcess . output . pipeThrough ( new TextDecoderStream ( ) ) ) {
335- grantOutput += chunk ;
336- }
337-
338- if ( grantOutput . trim ( ) ) {
339- logger . error ( `[${ serial } ] Failed to grant ${ REQUIRED_PERMISSION } to '${ REQUIRED_APP } ': ${ grantOutput . trim ( ) } ` ) ;
340- } else {
341- logger . info ( `[${ serial } ] Successfully granted ${ REQUIRED_PERMISSION } to '${ REQUIRED_APP } '` ) ;
342- }
343- }
344-
345- async installApk ( adb : Adb , serial : string , apkPath : string ) : Promise < boolean > {
346- let apkBytes : Uint8Array ;
347- let apkSize : number ;
348- try {
349- apkBytes = new Uint8Array ( await readFile ( apkPath ) ) ;
350- apkSize = ( await stat ( apkPath ) ) . size ;
351- } catch ( e ) {
352- logger . error ( `[${ serial } ] Could not read APK at '${ apkPath } ': {e}` , { e } ) ;
353- return false ;
354- }
355-
356- try {
357- const pm = new PackageManager ( adb ) ;
358- await pm . installStream ( apkSize , new ReadableStream ( {
359- start : ( controller ) => {
360- controller . enqueue ( apkBytes ) ;
361- controller . close ( ) ;
362- } ,
363- } ) ) ;
364- logger . info ( `[${ serial } ] Successfully installed APK '${ apkPath } '` ) ;
365- return true ;
366- } catch ( e ) {
367- logger . error ( `[${ serial } ] Failed to install APK '${ apkPath } ': {e}` , { e } ) ;
368- return false ;
369- }
370- }
371-
372- async checkAdbParameter ( adb : Adb , globalParam : string , expectedValue : any ) : Promise < boolean > {
373- let result : any ;
374-
375- const process = await adb . subprocess . noneProtocol . spawn ( "settings get global " + globalParam ) ;
376- // @ts -expect-error
377- for await ( const chunk of process . output . pipeThrough ( new TextDecoderStream ( ) ) ) {
378- result = chunk ;
379- }
380- // Cleaning trailing '\n' from chunk reading
381- result = result . trim ( ) ;
382-
383- logger . trace ( `[${ adb . serial } ] Checking ADB parameters '${ globalParam } ' = ${ result } and should be ${ expectedValue } (${ result == expectedValue } )` ) ;
384-
385- return result == expectedValue ;
386- }
387-
388- async setAdbParameter ( adb : Adb , globalParam : string , expectedValue : any ) {
389- let result : any ;
390-
391- const process = await adb . subprocess . noneProtocol . spawn ( [ "settings put global " , globalParam , expectedValue ] ) ;
392- // @ts -expect-error
393- for await ( const chunk of process . output . pipeThrough ( new TextDecoderStream ( ) ) ) {
394- result = chunk ;
395- }
396-
397- logger . trace ( `[${ adb . serial } ] Setting ADB parameters '${ globalParam } ' = ${ expectedValue } and it ${ result == undefined ? "worked" : "failed" } ` ) ;
398-
399- if ( result != undefined ) {
400- logger . error ( `[${ adb . serial } ] Something happened while setting the ADB setting '${ globalParam } '` ) ;
401- logger . error ( result . toString ( ) ) ;
402- }
403- }
404-
405256}
0 commit comments