2024-08-22 09:22:52 +08:00
package net.geedge.api.util ;
import cn.hutool.core.codec.Base32Codec ;
import cn.hutool.core.thread.NamedThreadFactory ;
import cn.hutool.log.Log ;
2024-09-04 14:19:02 +08:00
import net.geedge.api.entity.EnvApiYml ;
2024-08-22 09:22:52 +08:00
import net.geedge.common.APIException ;
import net.geedge.common.Constant ;
import net.geedge.common.RCode ;
import net.geedge.common.T ;
2024-11-14 14:28:30 +08:00
import java.io.* ;
2024-11-26 10:08:24 +08:00
import java.nio.file.Path ;
2024-08-22 09:22:52 +08:00
import java.nio.file.Paths ;
import java.util.* ;
import java.util.concurrent.* ;
import java.util.regex.Matcher ;
import java.util.regex.Pattern ;
2024-10-31 17:02:52 +08:00
import java.util.stream.Collectors ;
2024-08-22 09:22:52 +08:00
public class AdbUtil {
private static final Log log = Log . get ( ) ;
private static AdbUtil instance ;
2024-09-11 15:46:20 +08:00
private static String DEFAULT_DROIDVNC_NG_PKG_NAME = " net.christianbeier.droidvnc_ng " ;
2024-08-23 10:44:27 +08:00
private static String DEFAULT_DROIDVNC_NG_APK_PATH = " ./lib/droidvnc-np-2.6.0.apk " ;
private static String DEFAULT_DROIDVNC_NG_DEFAULTS_JSON_PATH = " ./lib/droidvnc-np-defaults.json " ;
2024-11-25 15:01:29 +08:00
private static String ANDROID_LAUNCHER = " ./lib/android-launcher.py " ;
2024-08-23 10:44:27 +08:00
2024-08-22 09:22:52 +08:00
private String serial ;
private String host ;
private Integer port ;
2024-09-09 13:55:39 +08:00
private Integer vncPort ;
2024-10-31 18:07:04 +08:00
private CommandExec commandExec ;
2024-10-31 17:02:52 +08:00
2024-11-14 14:28:30 +08:00
private boolean interrupt ;
2024-08-22 09:22:52 +08:00
private ExecutorService threadPool ;
2024-10-31 17:02:52 +08:00
2024-08-22 09:22:52 +08:00
public String getSerial ( ) {
return T . StrUtil . isNotEmpty ( this . serial ) ? serial : String . format ( " %s:%s " , this . host , this . port ) ;
}
2024-11-14 14:28:30 +08:00
public void setInterrupt ( boolean interrupt ) {
this . interrupt = interrupt ;
}
2024-08-22 09:22:52 +08:00
public record CommandResult ( Integer exitCode , String output ) {
}
2024-10-31 18:07:04 +08:00
public AdbUtil ( EnvApiYml . Adb adb , CommandExec commandExec ) {
2024-08-22 09:22:52 +08:00
this . serial = T . StrUtil . emptyToDefault ( adb . getSerial ( ) , " " ) ;
this . host = adb . getHost ( ) ;
this . port = adb . getPort ( ) ;
2024-09-09 13:55:39 +08:00
this . vncPort = adb . getVncPort ( ) ;
2024-10-31 18:07:04 +08:00
this . commandExec = commandExec ;
2024-08-23 10:44:27 +08:00
// adb connect
2024-09-29 11:21:54 +08:00
if ( ! this . connect ( ) ) {
log . error ( " [connect error, program exit] " ) ;
Runtime . getRuntime ( ) . halt ( 1 ) ;
}
2024-08-23 10:44:27 +08:00
// init
2024-09-29 11:21:54 +08:00
this . init ( true ) ;
}
public static AdbUtil getInstance ( ) {
if ( instance = = null ) {
throw new IllegalArgumentException ( " Object has not been instantiated. " ) ;
}
return instance ;
2024-08-22 09:22:52 +08:00
}
2024-10-31 18:07:04 +08:00
public static AdbUtil getInstance ( EnvApiYml . Adb connInfo , CommandExec commandExec ) {
2024-08-22 09:22:52 +08:00
if ( instance = = null ) {
synchronized ( AdbUtil . class ) {
if ( instance = = null ) {
2024-10-31 18:07:04 +08:00
instance = new AdbUtil ( connInfo , commandExec ) ;
2024-08-22 09:22:52 +08:00
}
}
}
return instance ;
}
/ * *
* connect
* /
2024-09-29 11:21:54 +08:00
public boolean connect ( ) {
2024-08-22 09:22:52 +08:00
if ( T . StrUtil . isNotEmpty ( this . serial ) ) {
// local
AdbDevice adbDevice = this . getAdbDevice ( ) ;
log . info ( " [connect] [result: {}] " , T . JSONUtil . toJsonStr ( adbDevice ) ) ;
if ( null = = adbDevice | | ! adbDevice . isAvailable ( ) ) {
2024-09-29 11:21:54 +08:00
return false ;
2024-08-22 09:22:52 +08:00
}
} else {
// remote
2024-10-31 18:07:04 +08:00
String result = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-22 09:22:52 +08:00
. buildConnectCommand ( this . host , this . port )
. build ( ) ) ;
log . info ( " [connect] [result: {}] " , result ) ;
if ( ! T . StrUtil . contains ( result , " connected " ) ) {
2024-09-29 11:21:54 +08:00
return false ;
2024-08-22 09:22:52 +08:00
}
}
2024-09-29 11:21:54 +08:00
return true ;
2024-08-23 10:44:27 +08:00
}
/ * *
* init
* su root
* install droidVNC NG
* /
2024-09-29 11:21:54 +08:00
public void init ( boolean install ) {
2024-08-22 09:22:52 +08:00
// adb root
2024-10-31 18:07:04 +08:00
String result = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-22 09:22:52 +08:00
. serial ( this . getSerial ( ) )
. buildRootCommand ( )
. build ( )
) ;
2024-08-23 10:44:27 +08:00
log . info ( " [init] [adb root] [result: {}] " , result ) ;
2024-09-29 11:21:54 +08:00
if ( install ) {
// install droidVNC NG
2024-10-31 18:07:04 +08:00
CommandResult installed = this . install ( DEFAULT_DROIDVNC_NG_APK_PATH , true , true ) ;
2024-09-29 11:21:54 +08:00
log . info ( " [init] [install droidVNC NG] [result: {}] " , installed ) ;
2024-08-23 10:44:27 +08:00
2024-09-29 11:21:54 +08:00
// 上传默认配置
2024-10-31 18:07:04 +08:00
this . execShellCommand ( " shell mkdir -p /storage/emulated/0/Android/data/net.christianbeier.droidvnc_ng/files " ) ;
2024-09-29 11:21:54 +08:00
this . push ( DEFAULT_DROIDVNC_NG_DEFAULTS_JSON_PATH , " /storage/emulated/0/Android/data/net.christianbeier.droidvnc_ng/files/defaults.json " ) ;
2024-08-23 10:44:27 +08:00
2024-09-29 11:21:54 +08:00
// 无障碍权限
2024-10-31 18:07:04 +08:00
this . execShellCommand ( " shell settings put secure enabled_accessibility_services net.christianbeier.droidvnc_ng/.InputService:$(settings get secure enabled_accessibility_services) " ) ;
2024-09-29 11:21:54 +08:00
// 存储空间权限
2024-10-31 18:07:04 +08:00
this . execShellCommand ( " shell pm grant net.christianbeier.droidvnc_ng android.permission.WRITE_EXTERNAL_STORAGE " ) ;
2024-09-29 11:21:54 +08:00
// 屏幕录制权限
2024-10-31 18:07:04 +08:00
this . execShellCommand ( " shell appops set net.christianbeier.droidvnc_ng PROJECT_MEDIA allow " ) ;
2024-09-29 11:21:54 +08:00
// ACTION_STOP
2024-10-31 18:07:04 +08:00
this . execShellCommand ( " shell am start-foreground-service -n net.christianbeier.droidvnc_ng/.MainService -a net.christianbeier.droidvnc_ng.ACTION_STOP --es net.christianbeier.droidvnc_ng.EXTRA_ACCESS_KEY d042e2b5d5f348588a4e1a243eb7a9a0 " ) ;
2024-09-29 11:21:54 +08:00
}
2024-08-23 10:44:27 +08:00
2024-09-29 11:21:54 +08:00
// ACTION_START
2024-10-31 18:07:04 +08:00
this . execShellCommand ( " shell am start-foreground-service -n net.christianbeier.droidvnc_ng/.MainService -a net.christianbeier.droidvnc_ng.ACTION_START --es net.christianbeier.droidvnc_ng.EXTRA_ACCESS_KEY d042e2b5d5f348588a4e1a243eb7a9a0 " ) ;
2024-09-12 15:51:50 +08:00
// 添加自定义链
this . addAswOutputChain ( ) ;
2024-08-22 09:22:52 +08:00
}
/ * *
* status
*
* @return
* /
public Map < Object , Object > status ( ) {
Map < Object , Object > m = T . MapUtil . builder ( )
. put ( " platform " , " android " )
. build ( ) ;
AdbDevice device = this . getAdbDevice ( ) ;
m . put ( " status " , device . isAvailable ( ) ? " online " : " offline " ) ;
Map < String , String > prop = this . getProp ( ) ;
m . put ( " name " , T . MapUtil . getStr ( prop , " ro.product.name " , " " ) ) ;
m . put ( " brand " , T . MapUtil . getStr ( prop , " ro.product.brand " , " " ) ) ;
m . put ( " model " , T . MapUtil . getStr ( prop , " ro.product.model " , " " ) ) ;
m . put ( " version " , T . MapUtil . getStr ( prop , " ro.build.version.release " , " " ) ) ;
m . put ( " resolution " , T . MapUtil . getStr ( prop , " wm.size " , " " ) ) ;
// 默认为真机
String type = " device " ;
for ( Map . Entry < String , String > entry : prop . entrySet ( ) ) {
// 根据 ro.build.* 这一组配置值判定是否为模拟器,如果包含 emulator、sdk 则为模拟器
if ( entry . getKey ( ) . contains ( " ro.build " ) ) {
String value = entry . getValue ( ) ;
if ( T . StrUtil . containsAnyIgnoreCase ( value , " emulator " , " sdk " ) ) {
type = " emulator " ;
break ;
}
}
}
m . put ( " type " , type ) ;
// check root
2024-10-31 18:07:04 +08:00
String checkRootResult = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-22 09:22:52 +08:00
. serial ( this . getSerial ( ) )
. buildCheckRootCommand ( )
. build ( )
) ;
m . put ( " root " , ! T . StrUtil . containsIgnoreCase ( checkRootResult , " Permission denied " ) ) ;
return m ;
}
/ * *
* getAdbDevice
* adb devices - l
*
* @return
* /
private AdbDevice getAdbDevice ( ) {
2024-10-31 18:07:04 +08:00
String result = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-22 09:22:52 +08:00
. buildDevicesCommand ( )
. build ( )
) ;
List < AdbDevice > list = T . ListUtil . list ( true ) ;
String [ ] lines = result . split ( " \\ n " ) ;
for ( String line : lines ) {
if ( line . startsWith ( " * " ) | | line . startsWith ( " List " ) | | T . StrUtil . isEmpty ( line ) )
continue ;
list . add ( new AdbDevice ( line ) ) ;
}
AdbDevice adbDevice = list . stream ( )
. filter ( pojo - > T . StrUtil . equals ( pojo . getSerial ( ) , this . getSerial ( ) ) )
. findFirst ( )
. orElse ( null ) ;
return adbDevice ;
}
/ * *
* getProp
* adb shell getprop
*
* @return
* /
private Map < String , String > getProp ( ) {
2024-10-31 18:07:04 +08:00
String result = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-22 09:22:52 +08:00
. serial ( this . getSerial ( ) )
. buildGetpropCommand ( )
. build ( )
) ;
Map < String , String > prop = new LinkedHashMap < > ( ) ;
Pattern pattern = Pattern . compile ( " \\ [(.*?) \\ ]: \\ [(.*?) \\ ] " ) ;
Matcher matcher = pattern . matcher ( result ) ;
while ( matcher . find ( ) ) {
String key = matcher . group ( 1 ) . trim ( ) ;
String value = matcher . group ( 2 ) . trim ( ) ;
prop . put ( key , value ) ;
}
// 分辨率 Physical size: 1440x3040
2024-10-31 18:07:04 +08:00
String wmSize = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-22 09:22:52 +08:00
. serial ( this . getSerial ( ) )
. buildWmSizeCommand ( )
. build ( )
) ;
prop . put ( " wm.size " , T . StrUtil . trim ( wmSize . replaceAll ( " Physical size: " , " " ) ) ) ;
return prop ;
}
/ * *
* md5sum
* /
private CommandResult md5sum ( String path ) {
2024-10-31 18:07:04 +08:00
String result = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-22 09:22:52 +08:00
. serial ( this . getSerial ( ) )
. buildMd5sumCommand ( path )
. build ( )
) ;
log . info ( " [md5sum] [path: {}] [result: {}] " , path , result ) ;
if ( T . StrUtil . isNotEmpty ( result ) ) {
String md5 = result . split ( " \\ s+ " ) [ 0 ] ;
return new CommandResult ( 0 , md5 ) ;
}
return new CommandResult ( 1 , " " ) ;
}
/ * *
* push
* 0 success ; ! 0 failed
* /
public CommandResult push ( String local , String remote ) {
2024-10-31 18:07:04 +08:00
String result = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-22 09:22:52 +08:00
. serial ( this . getSerial ( ) )
. buildPushCommand ( local , remote )
. build ( )
) ;
log . info ( " [push] [local: {}] [remote: {}] [result: {}] " , local , remote , result ) ;
return new CommandResult ( T . StrUtil . contains ( result , " failed " ) ? 1 : 0 , result ) ;
}
/ * *
* pull
* 0 success ; ! 0 failed
* /
2024-10-31 18:07:04 +08:00
public CommandResult pull ( String remote , String local ) {
String result = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-22 09:22:52 +08:00
. serial ( this . getSerial ( ) )
. buildPullCommand ( remote , local )
. build ( )
) ;
log . info ( " [pull] [remote: {}] [local: {}] [result: {}] " , remote , local , result ) ;
return new CommandResult ( T . StrUtil . containsAny ( result , " file pulled " , " files pulled " ) ? 0 : 1 , result ) ;
}
/ * *
* list dir
* ls - l
* stat filename
* /
public List < Map > listDir ( String path ) {
2024-10-31 18:07:04 +08:00
String result = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-22 09:22:52 +08:00
. serial ( this . getSerial ( ) )
. buildLsDirCommand ( path )
. build ( )
) ;
if ( T . StrUtil . contains ( result , " No such file or directory " ) ) {
log . warn ( " [listDir] [path: {}] [result: {}] " , path , result ) ;
throw new APIException ( RCode . NOT_EXISTS ) ;
}
if ( T . StrUtil . contains ( result , " Permission denied " ) ) {
log . warn ( " [listDir] [path: {}] [result: {}] " , path , result ) ;
throw new APIException ( RCode . NOT_PERMISSION ) ;
}
List < CompletableFuture < Map > > futureList = T . ListUtil . list ( false ) ;
List < Map > listDir = T . ListUtil . list ( true ) ;
String [ ] lines = result . split ( " \\ n " ) ;
boolean isDir = false ;
for ( String line : lines ) {
if ( line . startsWith ( " total " ) ) {
isDir = true ;
continue ;
}
String [ ] split = line . split ( " \\ s+ " ) ;
String name ;
// link file|dir
if ( 10 = = split . length ) {
name = split [ 7 ] ;
} else {
name = split [ split . length - 1 ] ;
}
String statFilePath = isDir ? Paths . get ( path ) . resolve ( name ) . toString ( ) : path ;
2024-09-04 18:11:56 +08:00
String statCommand = " shell stat -c \" '%N %A %g %u %s %a %X %Y' \" " + statFilePath ;
2024-08-22 09:22:52 +08:00
futureList . add (
CompletableFuture . supplyAsync ( ( ) - > {
2024-10-31 18:07:04 +08:00
String statResult = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-22 09:22:52 +08:00
. serial ( this . getSerial ( ) )
. buildShellCommand ( statCommand . replaceAll ( " \\ \\ " , " / " ) )
. build ( )
) ;
// reverse result
List < String > list = Arrays . asList ( statResult . split ( " \\ s+ " ) ) ;
Collections . reverse ( list ) ;
2024-09-04 18:11:56 +08:00
String fullName = list . get ( 7 ) . replaceAll ( " '|` " , " " ) ;
2024-08-22 09:22:52 +08:00
Map < String , Object > relMap = T . MapUtil . newHashMap ( ) ;
relMap . put ( " name " , name ) ;
relMap . put ( " value " , T . MapUtil . builder ( )
. put ( " id " , Base32Codec . Base32Encoder . ENCODER . encode ( fullName . getBytes ( ) ) )
. put ( " fullName " , fullName )
2024-09-04 18:11:56 +08:00
. put ( " gid " , Long . parseLong ( list . get ( 5 ) ) )
. put ( " uid " , Long . parseLong ( list . get ( 4 ) ) )
. put ( " size " , Long . parseLong ( list . get ( 3 ) ) )
2024-08-22 09:22:52 +08:00
. put ( " permissions " , Long . parseLong ( list . get ( 2 ) ) )
. put ( " cts " , Long . parseLong ( list . get ( 1 ) ) )
. put ( " uts " , Long . parseLong ( list . get ( 0 ) ) )
2024-09-04 18:11:56 +08:00
. put ( " isDir " , list . get ( 6 ) . startsWith ( " d " ) )
. put ( " isBlk " , list . get ( 6 ) . startsWith ( " b " ) )
. put ( " isFifo " , list . get ( 6 ) . startsWith ( " p " ) )
. put ( " isLink " , list . get ( 6 ) . startsWith ( " l " ) )
. put ( " isReg " , list . get ( 6 ) . startsWith ( " - " ) )
. put ( " isSock " , list . get ( 6 ) . startsWith ( " s " ) )
2024-08-22 09:22:52 +08:00
. build ( ) ) ;
return relMap ;
} , getThreadPool ( ) )
) ;
Map < Object , Object > m = T . MapUtil . builder ( )
. put ( " name " , name )
. build ( ) ;
listDir . add ( m ) ;
}
try {
CompletableFuture . allOf ( futureList . toArray ( new CompletableFuture [ 0 ] ) ) . get ( ) ;
futureList . forEach ( f - > {
Map map = f . getNow ( null ) ;
if ( T . MapUtil . isNotEmpty ( map ) ) {
String name = T . MapUtil . getStr ( map , " name " ) ;
Map fileAttr = listDir . stream ( ) . filter ( m - > T . MapUtil . getStr ( m , " name " ) . equals ( name ) ) . findFirst ( ) . get ( ) ;
fileAttr . putAll ( T . MapUtil . get ( map , " value " , Map . class ) ) ;
}
} ) ;
} catch ( Exception e ) {
log . warn ( e ) ;
}
return listDir ;
}
/ * *
* listApp
* adb shell pm list packages
*
* @return
* /
public List < Map > listApp ( String arg ) {
2024-10-31 18:07:04 +08:00
String result = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-22 09:22:52 +08:00
. serial ( this . getSerial ( ) )
. buildPmListPackagesCommand ( arg )
. build ( )
) ;
List < Map > listApp = T . ListUtil . list ( true ) ;
List < CompletableFuture < Map > > futureList = T . ListUtil . list ( false ) ;
String prefix = " package: " ;
String [ ] lines = result . split ( " \\ n " ) ;
for ( String line : lines ) {
String packageName = T . StrUtil . trim ( line . substring ( prefix . length ( ) ) ) ;
2024-09-11 15:46:20 +08:00
if ( T . StrUtil . equals ( DEFAULT_DROIDVNC_NG_PKG_NAME , packageName ) ) continue ;
2024-08-22 09:22:52 +08:00
2024-10-31 18:07:04 +08:00
String dumpsysResult = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-22 09:22:52 +08:00
. serial ( this . getSerial ( ) )
. buildShellCommand ( " shell dumpsys package " + packageName )
. build ( )
) ;
String [ ] split = dumpsysResult . split ( " \\ n " ) ;
String version = " " , apkPath = " " ;
for ( String s : split ) {
if ( s . contains ( " versionName= " ) ) {
version = T . StrUtil . trim ( s ) . replaceAll ( " versionName= " , " " ) ;
}
if ( s . contains ( " path: " ) ) {
apkPath = T . StrUtil . trim ( s ) . replaceAll ( " path: " , " " ) ;
}
}
if ( T . StrUtil . isNotEmpty ( apkPath ) ) {
String finalApkPath = apkPath ;
futureList . add (
CompletableFuture . supplyAsync ( ( ) - > {
try {
CommandResult md5sumRes = this . md5sum ( finalApkPath ) ;
String md5Value = md5sumRes . output ( ) ;
File localApk = T . FileUtil . file ( Constant . TEMP_PATH , md5Value + " .apk " ) ;
if ( ! T . FileUtil . exist ( localApk ) ) {
2024-10-31 18:07:04 +08:00
CommandResult pulled = this . pull ( finalApkPath , localApk . getAbsolutePath ( ) ) ;
2024-08-22 09:22:52 +08:00
if ( 0 ! = pulled . exitCode ( ) ) {
log . warn ( " [listApp] [pull apk error] [pkg: {}] " , packageName ) ;
return null ;
}
}
ApkUtil apkUtil = new ApkUtil ( ) ;
ApkInfo apkInfo = apkUtil . parseApk ( localApk . getAbsolutePath ( ) ) ;
String appName = apkInfo . getLabel ( ) ;
2024-09-12 10:44:57 +08:00
// String iconFilename = apkInfo.getIcon();
// String base64IconDate = apkUtil.extractFileFromApk(localApk.getAbsolutePath(), iconFilename);
// if (T.StrUtil.isNotEmpty(base64IconDate)) {
// base64IconDate = "data:image/jpeg;base64," + base64IconDate;
// }
2024-08-22 09:22:52 +08:00
Map < String , Object > relMap = T . MapUtil . newHashMap ( ) ;
relMap . put ( " pkg " , packageName ) ;
relMap . put ( " value " , T . MapUtil . builder ( )
. put ( " name " , appName )
2024-09-12 10:44:57 +08:00
// .put("icon", base64IconDate)
2024-08-22 09:22:52 +08:00
. build ( ) ) ;
return relMap ;
} catch ( Exception e ) {
log . error ( e , " [listApp] [parse apk] [pkg: {}] " , packageName ) ;
}
return null ;
} , getThreadPool ( ) )
) ;
}
Map < Object , Object > m = T . MapUtil . builder ( )
. put ( " packageName " , packageName )
. put ( " version " , version )
. build ( ) ;
listApp . add ( m ) ;
}
try {
CompletableFuture . allOf ( futureList . toArray ( new CompletableFuture [ 0 ] ) ) . get ( ) ;
futureList . forEach ( f - > {
Map map = f . getNow ( null ) ;
if ( T . MapUtil . isNotEmpty ( map ) ) {
String pkg = T . MapUtil . getStr ( map , " pkg " ) ;
Map appAttr = listApp . stream ( ) . filter ( m - > T . MapUtil . getStr ( m , " packageName " ) . equals ( pkg ) ) . findFirst ( ) . get ( ) ;
appAttr . putAll ( T . MapUtil . get ( map , " value " , Map . class ) ) ;
}
} ) ;
} catch ( Exception e ) {
log . warn ( e ) ;
}
return listApp ;
}
/ * *
* install app
* adb install apk
* /
2024-10-31 18:07:04 +08:00
public CommandResult install ( String localFilePath , boolean isDebugApk , boolean isReInstall ) {
String result = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-22 09:22:52 +08:00
. serial ( this . getSerial ( ) )
. buildInstallCommand ( localFilePath , isDebugApk , isReInstall )
2024-10-31 17:02:52 +08:00
. build ( ) ) ;
2024-08-22 09:22:52 +08:00
log . info ( " [install] [localFilePath: {}] [isDebugApk: {}] [isReInstall: {}] [result: {}] " , localFilePath , isDebugApk , isReInstall , result ) ;
return new CommandResult ( T . StrUtil . containsAny ( result , " Success " ) ? 0 : 1 , result ) ;
}
/ * *
* uninstall app
* adb uninstall package_name
* /
public CommandResult uninstall ( String packageName ) {
2024-10-31 18:07:04 +08:00
String result = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-22 09:22:52 +08:00
. serial ( this . getSerial ( ) )
. buildUnInstallCommand ( packageName )
. build ( )
) ;
log . info ( " [uninstall] [packageName: {}] [result: {}] " , packageName , result ) ;
return new CommandResult ( T . StrUtil . containsAny ( result , " Success " ) ? 0 : 1 , result ) ;
}
2024-10-18 17:17:53 +08:00
/ * *
* stop app
* adb shell am force - stop package_name
* /
2024-10-31 18:07:04 +08:00
public CommandResult stopApp ( String packageName ) {
String result = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-10-18 17:17:53 +08:00
. serial ( this . getSerial ( ) )
. buildStopAppCommand ( packageName )
. build ( )
) ;
log . info ( " [stopApp] [packageName: {}] [result: {}] " , packageName , result ) ;
return new CommandResult ( T . StrUtil . isEmpty ( result ) ? 0 : 1 , result ) ;
}
2024-08-22 09:22:52 +08:00
/ * *
* iptables - F
* iptables - X
* /
2024-08-27 15:46:51 +08:00
@Deprecated
2024-08-22 09:22:52 +08:00
private void cleanIptables ( ) {
// Delete all rules in chain or all chains
2024-10-31 18:07:04 +08:00
commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-22 09:22:52 +08:00
. serial ( this . getSerial ( ) )
. buildShellCommand ( " shell iptables -F " )
. build ( )
) ;
// Delete user-defined chain
2024-10-31 18:07:04 +08:00
commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-22 09:22:52 +08:00
. serial ( this . getSerial ( ) )
. buildShellCommand ( " shell iptables -X " )
. build ( )
) ;
}
2024-08-27 15:46:51 +08:00
/ * *
* list tcpdump
* /
public List < Map > listTcpdump ( ) {
2024-10-31 18:07:04 +08:00
String result = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-27 15:46:51 +08:00
. serial ( this . getSerial ( ) )
. buildShellCommand ( String . format ( " shell \" ps -ef | grep tcpdump | grep -v grep | grep capture_ | awk '{print $NF}' \" " ) )
. build ( ) ) ;
List < Map > list = T . ListUtil . list ( true ) ;
String [ ] lines = result . split ( " \\ n " ) ;
for ( String line : lines ) {
try {
String fileName = T . FileUtil . mainName ( line ) ;
String taskId = " " , packageName = " " ;
if ( fileName . contains ( " capture_all_ " ) ) {
taskId = fileName . replaceAll ( " capture_all_ " , " " ) ;
} else {
String [ ] split = fileName . split ( " _ " ) ;
packageName = split [ 2 ] ;
taskId = split [ split . length - 1 ] ;
}
Map < Object , Object > m = T . MapUtil . builder ( )
. put ( " id " , taskId )
. put ( " packageName " , packageName )
. build ( ) ;
list . add ( m ) ;
} catch ( Exception e ) {
log . warn ( e , " [listTcpdump] [get task info error] [line: {}] " , line ) ;
}
}
return list ;
}
2024-08-22 09:22:52 +08:00
/ * *
* start Tcpdump
* tcpdump pcap
* /
2024-10-31 18:07:04 +08:00
public CommandResult startTcpdump ( String packageName ) {
2024-08-22 09:22:52 +08:00
String taskId = T . IdUtil . fastSimpleUUID ( ) ;
if ( T . StrUtil . isNotEmpty ( packageName ) ) {
log . info ( " [startTcpdump] [capture app package] [pkg: {}] " , packageName ) ;
2024-10-31 18:07:04 +08:00
String dumpsysResult = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-22 09:22:52 +08:00
. serial ( this . getSerial ( ) )
. buildShellCommand ( " shell dumpsys package " + packageName )
. build ( )
) ;
String [ ] lines = dumpsysResult . split ( " \\ n " ) ;
String userId = Arrays . stream ( lines )
. filter ( s - > T . StrUtil . contains ( s , " userId= " ) )
. findFirst ( )
. map ( s - > T . StrUtil . trim ( s ) . replaceAll ( " userId= " , " " ) )
. orElseThrow ( ( ) - > new APIException ( " Not found userId by package name. package name: " + packageName ) ) ;
2024-10-31 18:07:04 +08:00
commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-22 09:22:52 +08:00
. serial ( this . getSerial ( ) )
. buildShellCommand ( String . format ( " shell iptables -A OUTPUT -m owner --uid-owner %s -j CONNMARK --set-mark %s " , userId , userId ) )
. build ( ) ) ;
2024-10-31 18:07:04 +08:00
commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-22 09:22:52 +08:00
. serial ( this . getSerial ( ) )
. buildShellCommand ( String . format ( " shell iptables -A INPUT -m connmark --mark %s -j NFLOG --nflog-group %s " , userId , userId ) )
. build ( ) ) ;
2024-10-31 18:07:04 +08:00
commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-22 09:22:52 +08:00
. serial ( this . getSerial ( ) )
. buildShellCommand ( String . format ( " shell iptables -A OUTPUT -m connmark --mark %s -j NFLOG --nflog-group %s " , userId , userId ) )
. build ( ) ) ;
2024-10-31 18:07:04 +08:00
String ruleList = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-22 09:22:52 +08:00
. serial ( this . getSerial ( ) )
. buildShellCommand ( " shell iptables -L " )
. build ( ) ) ;
log . info ( " [startTcpdump] [iptables -L] [result: {}] " , ruleList ) ;
2024-08-27 15:46:51 +08:00
// pcap 格式: capture_{userId}_{pcakageName}_{taskId}.pcap
String pcapFilePath = " /data/local/tmp/capture_ " + userId + " _ " + packageName + " _ " + taskId + " .pcap " ;
2024-10-31 18:07:04 +08:00
commandExec . execForProcess ( AdbCommandBuilder . builder ( )
2024-08-22 09:22:52 +08:00
. serial ( this . getSerial ( ) )
2024-09-10 17:10:38 +08:00
. buildShellCommand ( String . format ( " shell tcpdump -i nflog:%s -w %s & " , userId , pcapFilePath ) )
2024-08-22 09:22:52 +08:00
. build ( ) ) ;
} else {
log . info ( " [startTcpdump] [capture all package] " ) ;
2024-08-27 15:46:51 +08:00
// pcap 格式: capture_all_{taskId}.pcap
String pcapFilePath = " /data/local/tmp/capture_all_ " + taskId + " .pcap " ;
2024-10-31 18:07:04 +08:00
commandExec . execForProcess ( AdbCommandBuilder . builder ( )
2024-08-22 09:22:52 +08:00
. serial ( this . getSerial ( ) )
2024-09-09 13:55:39 +08:00
. buildShellCommand ( String . format ( " shell tcpdump not port %s -w %s & " , this . vncPort , pcapFilePath ) )
2024-08-22 09:22:52 +08:00
. build ( ) ) ;
}
2024-10-31 18:07:04 +08:00
String result = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-22 09:22:52 +08:00
. serial ( this . getSerial ( ) )
. buildShellCommand ( String . format ( " shell \" ps -ef | grep tcpdump | grep -v grep | grep %s | awk '{print $2}' \" " , taskId ) )
. build ( ) ) ;
log . info ( " [startTcpdump] [taskId: {}] [tcpdump pid: {}] " , taskId , result ) ;
return new CommandResult ( T . StrUtil . isNotEmpty ( result ) ? 0 : 1 , taskId ) ;
}
/ * *
* stop tcpdump
* kill - INT { pid }
* /
2024-10-31 18:07:04 +08:00
public CommandResult stopTcpdump ( String id ) {
String pcapFilePath = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-27 15:46:51 +08:00
. serial ( this . getSerial ( ) )
. buildShellCommand ( String . format ( " shell \" ps -ef | grep tcpdump | grep -v grep | grep %s | awk '{print $NF}' \" " , id ) )
. build ( ) ) ;
if ( T . StrUtil . isNotEmpty ( pcapFilePath ) ) {
if ( ! pcapFilePath . contains ( " capture_all_ " ) ) {
// 删除 iptables rule
String [ ] split = T . FileUtil . mainName ( pcapFilePath ) . split ( " _ " ) ;
String userId = split [ 1 ] ;
log . info ( " [stopTcpdump] [remove iptables rule] [userId: {}] " , userId ) ;
2024-10-31 18:07:04 +08:00
commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-27 15:46:51 +08:00
. serial ( this . getSerial ( ) )
. buildShellCommand ( String . format ( " shell iptables -D OUTPUT -m owner --uid-owner %s -j CONNMARK --set-mark %s " , userId , userId ) )
. build ( ) ) ;
2024-10-31 18:07:04 +08:00
commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-27 15:46:51 +08:00
. serial ( this . getSerial ( ) )
. buildShellCommand ( String . format ( " shell iptables -D INPUT -m connmark --mark %s -j NFLOG --nflog-group %s " , userId , userId ) )
. build ( ) ) ;
2024-10-31 18:07:04 +08:00
commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-27 15:46:51 +08:00
. serial ( this . getSerial ( ) )
. buildShellCommand ( String . format ( " shell iptables -D OUTPUT -m connmark --mark %s -j NFLOG --nflog-group %s " , userId , userId ) )
. build ( ) ) ;
}
}
2024-10-31 18:07:04 +08:00
String result = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-22 09:22:52 +08:00
. serial ( this . getSerial ( ) )
. buildShellCommand ( String . format ( " shell \" ps -ef | grep tcpdump | grep -v grep | grep %s | awk '{print $2}' | xargs kill -INT \" " , id ) )
. build ( ) ) ;
2024-09-10 16:54:16 +08:00
// 等待 tcpdump 资源释放, 避免出现错误: The capture file appears to have been cut short in the middle of a packet.
for ( int i = 0 ; i < 10 ; i + + ) {
T . ThreadUtil . sleep ( 500 ) ;
2024-10-31 18:07:04 +08:00
String str = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-09-10 16:54:16 +08:00
. serial ( this . getSerial ( ) )
. buildShellCommand ( String . format ( " shell \" ps -ef | grep tcpdump | grep -v grep | grep %s \" " , id ) )
. build ( ) ) ;
log . info ( " [stopTcpdump] [id: {}] [is running: {}] " , id , str ) ;
if ( T . StrUtil . isEmpty ( str ) ) {
break ;
}
}
2024-08-27 15:46:51 +08:00
log . info ( " [stopTcpdump] [id: {}] [pcapFilePath: {}] [result: {}] " , id , pcapFilePath , result ) ;
if ( T . StrUtil . isEmpty ( result ) ) {
return new CommandResult ( 0 , pcapFilePath ) ;
}
return new CommandResult ( 1 , result ) ;
2024-08-22 09:22:52 +08:00
}
2024-08-23 10:44:27 +08:00
/ * *
* exec shell command
* /
2024-10-31 18:07:04 +08:00
public void execShellCommand ( String shellCmd ) {
String result = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-23 10:44:27 +08:00
. serial ( this . getSerial ( ) )
. buildShellCommand ( shellCmd )
. build ( ) ) ;
log . info ( " [execShellCommand] [shellCmd: {}] [result: {}] " , shellCmd , result ) ;
}
2024-08-22 09:22:52 +08:00
2024-08-27 15:46:51 +08:00
/ * *
* exec shell command
* /
public String execShellCommand ( String cmd , Integer timeout ) {
2024-11-04 11:24:41 +08:00
String result = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-08-27 15:46:51 +08:00
. serial ( this . getSerial ( ) )
. buildShellCommand ( " shell " + cmd )
. build ( ) ) ;
2024-11-04 11:24:41 +08:00
return result ;
2024-08-27 15:46:51 +08:00
}
2024-09-12 15:51:50 +08:00
/ * *
* 1 . 添加自定义链
* 2 . 自定义链添加到 OUTPUT 链中
* /
private void addAswOutputChain ( ) {
// name=ASW_OUTPUT
2024-10-31 18:07:04 +08:00
this . execShellCommand ( " shell iptables -N ASW_OUTPUT " ) ;
2024-09-12 15:51:50 +08:00
2024-10-31 18:07:04 +08:00
String outputChainResult = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-09-12 15:51:50 +08:00
. serial ( this . getSerial ( ) )
. buildShellCommand ( String . format ( " shell \" iptables -L OUTPUT --line-numbers | grep ASW_OUTPUT \" " ) )
. build ( ) ) ;
2024-11-04 11:24:41 +08:00
log . info ( " [addAswOutputChain] [ASW_OUTPUT in OUTPUT Chain] [exist: {}] " , T . StrUtil . isEmpty ( outputChainResult ) ) ;
2024-09-12 15:51:50 +08:00
if ( T . StrUtil . isEmpty ( outputChainResult ) ) {
// ASW_OUTPUT 添加到 OUTPUT 链中
2024-10-31 18:07:04 +08:00
this . execShellCommand ( " shell iptables -A OUTPUT -j ASW_OUTPUT " ) ;
2024-09-12 15:51:50 +08:00
}
}
/ * *
* ASW_OUTPUT chain rules
* iptables - nL ASW_OUTPUT - - line - numbers
* /
public List < Map > listAcl ( ) {
2024-10-31 18:07:04 +08:00
String result = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-09-12 15:51:50 +08:00
. serial ( this . getSerial ( ) )
. buildIptablesLnRulesCommand ( " ASW_OUTPUT " )
. build ( ) ) ;
List < Map > chainList = T . ListUtil . list ( true ) ;
String [ ] lines = result . split ( " \\ n " ) ;
for ( String line : lines ) {
String [ ] split = line . split ( " \\ s+ " ) ;
String chainIndex = T . StrUtil . trim ( split [ 0 ] ) ;
if ( T . StrUtil . isNumeric ( chainIndex ) ) {
String protocol = T . StrUtil . trim ( split [ 2 ] ) ;
Map < Object , Object > m = T . MapUtil . builder ( )
. put ( " num " , Integer . valueOf ( chainIndex ) )
. put ( " protocol " , protocol )
. build ( ) ;
String destIp = T . StrUtil . trim ( split [ 5 ] ) ;
if ( ! T . StrUtil . equals ( " 0.0.0.0/0 " , destIp ) ) {
m . put ( " ip " , destIp ) ;
}
if ( split . length = = 8 ) {
String dpt = T . StrUtil . trim ( split [ 7 ] ) ;
dpt = dpt . replaceAll ( " dpt: " , " " ) ;
if ( T . StrUtil . isNumeric ( chainIndex ) ) {
m . put ( " port " , Integer . valueOf ( dpt ) ) ;
}
}
chainList . add ( m ) ;
}
}
return chainList ;
}
/ * *
* add chain rule
* iptables - A ASW_OUTPUT - p prot - d ip - - dport port - j ACCEPT
* /
public void addAcl ( String protocol , String ip , String port ) {
// add chain
this . addAswOutputChain ( ) ;
// add chain ruls
2024-10-31 18:07:04 +08:00
String result = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-09-12 15:51:50 +08:00
. serial ( this . getSerial ( ) )
. buildIptablesAddRuleCommand ( " ASW_OUTPUT " , protocol , ip , port )
. build ( ) ) ;
log . info ( " [addAcl] [protocol: {}] [ip: {}] [port: {}] [result: {}] " , protocol , ip , port , result ) ;
}
/ * *
* del chain rule
* iptables - D ASW_OUTPUT - p prot - d ip - - dport port - j ACCEPT
* /
public void deleteAcl ( String protocol , String ip , String port ) {
// add chain ruls
2024-10-31 18:07:04 +08:00
String result = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-09-12 15:51:50 +08:00
. serial ( this . getSerial ( ) )
. buildIptablesDelRuleCommand ( " ASW_OUTPUT " , protocol , ip , port )
. build ( ) ) ;
log . info ( " [deleteAcl] [protocol: {}] [ip: {}] [port: {}] [result: {}] " , protocol , ip , port , result ) ;
}
2024-11-12 15:17:00 +08:00
public CommandResult clearAppData ( String packageName ) {
String result = commandExec . exec ( AdbCommandBuilder . builder ( )
. serial ( this . getSerial ( ) )
. buildClearAppDateCommand ( packageName )
. build ( ) ) ;
log . info ( " [clearAppData] [packageName: {}] " , packageName ) ;
return new CommandResult ( T . StrUtil . containsAny ( result , " Success " ) ? 0 : 1 , result ) ;
}
2024-09-12 15:51:50 +08:00
/ * *
* flushAcl
* iptables - F ASW_OUTPUT
* /
public CommandResult flushAcl ( ) {
2024-10-31 18:07:04 +08:00
String result = commandExec . exec ( AdbCommandBuilder . builder ( )
2024-09-12 15:51:50 +08:00
. serial ( this . getSerial ( ) )
. buildIptablesFlushRuleCommand ( " ASW_OUTPUT " )
. build ( ) ) ;
log . info ( " [flushAcl] [result: {}] " , result ) ;
return new CommandResult ( T . StrUtil . isNotEmpty ( result ) ? 1 : 0 , result ) ;
2024-11-18 14:50:34 +08:00
}
public boolean findPackageInstall ( String packageName ) {
String result = commandExec . exec ( AdbCommandBuilder . builder ( )
. serial ( this . getSerial ( ) )
. buildCheckPackage ( packageName )
. build ( )
) ;
return T . StrUtil . equals ( result , packageName ) ;
}
public List findPackageNameList ( ) {
String result = commandExec . exec ( AdbCommandBuilder . builder ( )
. serial ( this . getSerial ( ) )
. buildFindPackageNameList ( )
. build ( )
) ;
List < String > packageNameList = T . ListUtil . list ( true ) ;
String prefix = " package: " ;
String [ ] lines = result . split ( " \\ n " ) ;
for ( String line : lines ) {
String packageName = T . StrUtil . trim ( line . substring ( prefix . length ( ) ) ) ;
if ( T . StrUtil . equals ( DEFAULT_DROIDVNC_NG_PKG_NAME , packageName ) ) continue ;
packageNameList . add ( packageName ) ;
}
return packageNameList ;
}
public CommandResult startApp ( String packageName ) {
String result = commandExec . exec ( AdbCommandBuilder . builder ( )
. serial ( this . getSerial ( ) )
. buildStartAPP ( packageName )
. build ( )
) ;
log . info ( " [startApp] [result: {}] " , result ) ;
return new CommandResult ( T . StrUtil . isNotEmpty ( result ) ? 1 : 0 , result ) ;
2024-09-12 15:51:50 +08:00
}
2024-08-22 09:22:52 +08:00
private synchronized ExecutorService getThreadPool ( ) {
if ( threadPool = = null ) {
threadPool = new ThreadPoolExecutor (
5 ,
10 ,
30 ,
TimeUnit . SECONDS ,
new ArrayBlockingQueue < Runnable > ( 10000 ) ,
new NamedThreadFactory ( " API- " , true ) ) ;
}
return threadPool ;
}
2024-10-31 17:02:52 +08:00
2024-11-26 10:08:24 +08:00
public CommandResult execPlaybook ( String scriptPath , String tid , String packageName , String type , File logFile ) {
2024-10-29 10:37:46 +08:00
log . info ( " [execPlaybook] [begin!] [serial:{}] " , this . getSerial ( ) ) ;
2024-11-26 10:08:24 +08:00
List < String > command ;
File environment = null ;
if ( T . StrUtil . equals ( type , " python " ) ) {
// check requirements.txt
Path parent = Paths . get ( scriptPath ) . getParent ( ) ;
Path resolve = parent . resolve ( " requirements.txt " ) ;
command = new PythonCommandBuilder ( " python " )
. buildRunPythonScript ( scriptPath , tid , packageName , this . getSerial ( ) )
. build ( ) ;
if ( T . FileUtil . exist ( resolve . toString ( ) ) ) {
// create venv
environment = T . FileUtil . file ( parent . toString ( ) , " environment " ) ;
commandExec . exec ( new PythonCommandBuilder ( " python " ) . buildCreateVenv ( environment . getAbsolutePath ( ) ) . build ( ) ) ;
commandExec . exec ( new PythonCommandBuilder ( T . StrUtil . concat ( true , environment . getAbsolutePath ( ) , " /bin/pip " ) ) . buildUpgradePip ( ) . build ( ) ) ;
commandExec . exec ( new PythonCommandBuilder ( T . StrUtil . concat ( true , environment . getAbsolutePath ( ) , " /bin/pip " ) ) . buildInstallRequirements ( resolve . toString ( ) ) . build ( ) ) ;
command = new PythonCommandBuilder ( T . StrUtil . concat ( true , environment . getAbsolutePath ( ) , " /bin/python " ) )
. buildRunPythonScript ( scriptPath , tid , packageName , this . getSerial ( ) )
. build ( ) ;
}
} else {
command = new PythonCommandBuilder ( " python " )
. buildRunAirScript ( ANDROID_LAUNCHER , scriptPath , tid , packageName , this . getSerial ( ) )
. build ( ) ;
}
2024-10-31 18:07:04 +08:00
Process process = commandExec . execForProcess ( command ) ;
2024-10-17 17:10:45 +08:00
2024-11-04 11:24:41 +08:00
T . FileUtil . appendString ( T . StrUtil . concat ( true , " $ " , command . stream ( ) . collect ( Collectors . joining ( " " ) ) , " \ n " ) , logFile , " UTF-8 " ) ;
InputStreamReader inputStreamReader = null ;
BufferedReader bufferedReader = null ;
2024-10-17 17:10:45 +08:00
try {
2024-11-04 11:24:41 +08:00
inputStreamReader = new InputStreamReader ( process . getInputStream ( ) , " UTF-8 " ) ;
bufferedReader = new BufferedReader ( inputStreamReader ) ;
String line ;
while ( ( line = bufferedReader . readLine ( ) ) ! = null ) {
2024-11-14 14:28:30 +08:00
if ( T . ObjectUtil . isNotNull ( interrupt ) & & interrupt ) {
log . info ( " [PlaybookRunnable] [execPlaybook] [stop exec playbook] " ) ;
process . destroyForcibly ( ) ;
}
2024-11-04 11:24:41 +08:00
// 处理每一行输出
T . FileUtil . appendString ( T . StrUtil . concat ( true , line , " \ n " ) , logFile , " UTF-8 " ) ;
}
2024-10-17 17:10:45 +08:00
int exitCode = process . waitFor ( ) ;
2024-11-04 11:24:41 +08:00
return new CommandResult ( exitCode , T . StrUtil . EMPTY ) ;
2024-10-17 17:10:45 +08:00
} catch ( Exception e ) {
process . destroyForcibly ( ) ;
throw new APIException ( RCode . ERROR ) ;
} finally {
2024-11-26 10:08:24 +08:00
T . FileUtil . del ( environment ) ;
2024-11-04 11:24:41 +08:00
T . IoUtil . close ( inputStreamReader ) ;
T . IoUtil . close ( bufferedReader ) ;
2024-10-17 17:10:45 +08:00
}
}
2024-08-22 09:22:52 +08:00
}