Skip to content

Commit

Permalink
增加截屏,文件管理
Browse files Browse the repository at this point in the history
增加截屏,文件管理
  • Loading branch information
GangJust committed Mar 10, 2023
1 parent 23a9134 commit cafbc80
Show file tree
Hide file tree
Showing 39 changed files with 1,481 additions and 147 deletions.
14 changes: 8 additions & 6 deletions src/main/kotlin/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.*
import app.state.pages.FileSystemState
import app.view.HomeUI
import app.view.pages.CustomScaffold
import app.view.pages.ErrorPage
Expand Down Expand Up @@ -84,10 +85,10 @@ class App(private var application: ApplicationScope) : AbstractView<AppState>()
@Composable
private fun ConfigWindow() {
val windowState = rememberWindowState(
size = DpSize(320.dp, 280.dp),
size = DpSize(380.dp, 280.dp),
position = WindowPosition.Aligned(Alignment.Center),
)
val adbDir = mutableStateOf("")
val adbDir = mutableStateOf("tools")

Window(
onCloseRequest = { application.exitApplication() },
Expand All @@ -99,7 +100,7 @@ class App(private var application: ApplicationScope) : AbstractView<AppState>()
content = {
Card(
shape = RoundedCornerShape(12.dp),
elevation = 2.dp,
elevation = 4.dp,
border = BorderStroke(1.dp, ColorRes.icon.copy(alpha = 0.3f)),
modifier = Modifier.padding(12.dp),
content = {
Expand Down Expand Up @@ -135,7 +136,7 @@ class App(private var application: ApplicationScope) : AbstractView<AppState>()
CustomTextField(
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp).fillMaxWidth(),
value = adbDir,
hintText = "请输入可执行adb所在路径",
hintText = "请输入可执行adb所在路径 (默认tools即可)",
textStyle = TextStyleRes.bodyMedium,
fillMaxWidth = true,
onValueChange = { adbDir.value = it },
Expand All @@ -157,7 +158,8 @@ class App(private var application: ApplicationScope) : AbstractView<AppState>()
return@TextButton
}

val adbFile = adbDirFile.listFiles { _, name -> name.contains("^adb(\\.exe)?\$".toRegex()) }
val adbFile =
adbDirFile.listFiles { _, name -> name.contains("^adb(\\.exe)?\$".toRegex()) }
if (adbFile == null || adbFile.isEmpty()) {
ComposeToast.show("可执行adb不存在!")
return@TextButton
Expand Down Expand Up @@ -216,7 +218,7 @@ class App(private var application: ApplicationScope) : AbstractView<AppState>()
content = {
Card(
shape = RoundedCornerShape(12.dp),
elevation = 2.dp,
elevation = 4.dp,
border = BorderStroke(1.dp, ColorRes.icon.copy(alpha = 0.3f)),
modifier = Modifier.padding(12.dp),
content = {
Expand Down
9 changes: 9 additions & 0 deletions src/main/kotlin/app/logic/pages/FileSystemLogic.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package app.logic.pages

import base.mvvm.AbstractLogic

class FileSystemLogic : AbstractLogic() {
override fun dispose() {

}
}
4 changes: 2 additions & 2 deletions src/main/kotlin/app/logic/pages/ViewLayoutLogic.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ class ViewLayoutLogic : AbstractLogic() {
* 可能存在问题: 比如 MiUi 会报 FileNotFoundException “/data/system/theme_config/theme_compatibility.xml”
* 但是仍然能够取到布局信息
*/
suspend fun uiautomatorDump(device: String, block: (success:String, fail:String) -> Unit): String {
suspend fun uiautomatorDump(device: String, block: (success: String, fail: String) -> Unit): String {
val command = "adb exec-out uiautomator dump /dev/tty".formatAdbCommand(device)

var layoutXml = ""
ShellUtils.shell(command) { success, error ->
ShellUtils.shell(command = command) { success, error ->
if (error.isNotBlank() && !success.contains("<?xml")) {
block.invoke("", "布局读取失败!")
return@shell
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/app/model/AppDesc.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ data class AppDesc(
var lastUpdateTime: String = "",
var apkSigningVersion: String = "unknown",
var installedPath: String = "unknown",
var length: Int = 0,
var length: Long = 0,
)
19 changes: 19 additions & 0 deletions src/main/kotlin/app/model/FileModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package app.model

data class FileModel(
var name: String,
var path: String,
var typePerm: String = "",
var ownerName: String = "",
var groupName: String = "",
var lastModifyTime: String = "",
var type: FileType = FileType.UNKNOWN,
)

enum class FileType {
FILE,
DIR,
LINK_DIR,
LINK_FILE,
UNKNOWN,
}
24 changes: 18 additions & 6 deletions src/main/kotlin/app/state/HomeState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,20 @@ class HomeState : AbstractState<HomeLogic>() {
val leftMenuSelectIndex = mutableStateOf(0)

// leftMenuIconList.size == leftMenuTitleList.size
val leftMenuIconList = mutableStateListOf(IconRes.browseActivity, IconRes.apkDocument, IconRes.portForward, IconRes.viewLayout)
val leftMenuTitleList = mutableStateListOf("活动信息", "应用管理", "端口转发", "布局分析")
val leftMenuIconList = mutableStateListOf(
IconRes.browseActivity,
IconRes.apkDocument,
IconRes.fileSystem,
IconRes.portForward,
IconRes.viewLayout,
)
val leftMenuTitleList = mutableStateListOf(
"活动信息",
"应用管理",
"文件管理",
"端口转发",
"布局分析",
)

val currentDevice = mutableStateOf("")
val devicesList = mutableStateListOf<String>()
Expand All @@ -39,7 +51,7 @@ class HomeState : AbstractState<HomeLogic>() {
delay(200L)

val command = "adb devices".formatAdbCommand("")
val devices = ShellUtils.shell(command).trim()
val devices = ShellUtils.shell(command = command).trim()
val splitList = devices.split("\n").filter { it.trim().isNotEmpty() }

//每次加载重置
Expand Down Expand Up @@ -71,15 +83,15 @@ class HomeState : AbstractState<HomeLogic>() {
/// 获取某个ADB设备的系统架构
private suspend fun getAbi(device: String): String {
val command = "adb shell getprop ro.product.cpu.abi".formatAdbCommand(device)
val abi = ShellUtils.shell(command)
val abi = ShellUtils.shell(command = command)
if (abi.isEmpty()) return "unknown"
return abi.trim()
}

/// 获取某个ADB设备的系统品牌
private suspend fun getBrand(device: String): String {
val command = "adb shell getprop ro.product.brand".formatAdbCommand(device)
val brand = ShellUtils.shell(command)
val brand = ShellUtils.shell(command = command)
if (brand.isEmpty()) return "unknown"
return brand.trim()
}
Expand All @@ -88,7 +100,7 @@ class HomeState : AbstractState<HomeLogic>() {
fun wifiConnect(ipAndPort: String, block: (Boolean) -> Unit) {
launch {
val command = "adb connect $ipAndPort".formatAdbCommand("")
ShellUtils.shell(command) { success, error ->
ShellUtils.shell(command = command) { success, error ->
if (error.isNotBlank() || !success.contains("connected")) {
block.invoke(false)
return@shell
Expand Down
40 changes: 27 additions & 13 deletions src/main/kotlin/app/state/pages/ActivityState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,21 @@ import app.state.HomeState
import app.view.HomeUI
import base.mvvm.AbstractState
import base.mvvm.StateManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.withContext
import compose.ComposeToast
import utils.ShellUtils
import utils.formatAdbCommand
import extensions.middle
import extensions.pathSeparator
import kotlinx.coroutines.*
import java.util.Calendar
import javax.swing.filechooser.FileSystemView

class ActivityState : AbstractState<ActivityLogic>() {
override fun createLogic() = ActivityLogic()
private val homeState = StateManager.findState<HomeState>(HomeUI::class.java)
private val device = homeState?.currentDevice?.value ?: ""

private var job: Job? = null
val packageName = mutableStateOf("")
val processName = mutableStateOf("")
val launchActivity = mutableStateOf("")
Expand All @@ -35,15 +37,24 @@ class ActivityState : AbstractState<ActivityLogic>() {
}

/// 屏幕截图(并保存)
suspend fun screenshot(): Unit {
val command = "adb exec-out screencap -p > ${Calendar.getInstance().timeInMillis}.png"
ShellUtils.shell(command)
fun screenshot() {
launch {
val desktop = FileSystemView.getFileSystemView().homeDirectory.path.pathSeparator()
val command = "adb exec-out screencap -p > \"${desktop}/${Calendar.getInstance().timeInMillis}.png\"".formatAdbCommand(device)
ShellUtils.shell(command = command) { _, error ->
if (error.isNotBlank()) {
ComposeToast.show("屏幕截取失败!")
return@shell
}
ComposeToast.show("屏幕截取成功!")
}
}
}

/// 获取当前包名
private suspend fun getPackageName() {
val command = "adb shell dumpsys activity activities | grep packageName".formatAdbCommand(device)
val result = ShellUtils.shell(command)
val result = ShellUtils.shell(command = command)
if (result.isBlank()) {
packageName.value = "没有获取到包名, 请检查ADB设备是否已断开"
return
Expand All @@ -58,7 +69,7 @@ class ActivityState : AbstractState<ActivityLogic>() {
/// 获取当前进程
private suspend fun getProcessName() {
val command = "adb shell dumpsys activity activities | grep processName".formatAdbCommand(device)
val result = ShellUtils.shell(command)
val result = ShellUtils.shell(command = command)
if (result.isBlank()) {
processName.value = "没有获取到进程, 请检查ADB设备是否已断开"
return
Expand All @@ -73,7 +84,7 @@ class ActivityState : AbstractState<ActivityLogic>() {
/// 获取启动活动
private suspend fun getLaunchActivity() {
val command = "adb shell dumpsys activity activities | grep mActivityComponent".formatAdbCommand(device)
val result = ShellUtils.shell(command)
val result = ShellUtils.shell(command = command)
if (result.isBlank()) {
launchActivity.value = "没有获取到启动活动, 请检查ADB设备是否已断开"
return
Expand All @@ -88,7 +99,7 @@ class ActivityState : AbstractState<ActivityLogic>() {
/// 获取前台Activity
private suspend fun getResumedActivity() {
val command = "adb shell dumpsys activity activities | grep mResumedActivity".formatAdbCommand(device)
val result = ShellUtils.shell(command)
val result = ShellUtils.shell(command = command)
if (result.isBlank()) {
resumedActivity.value = "没有获取到前台活动, 请检查ADB设备是否锁屏或已断开"
return
Expand All @@ -102,7 +113,7 @@ class ActivityState : AbstractState<ActivityLogic>() {
/// 获取上次Activity
private suspend fun getLastHistoryActivity() {
val command = "adb shell dumpsys activity activities | grep mLastPausedActivity".formatAdbCommand(device)
val result = ShellUtils.shell(command)
val result = ShellUtils.shell(command = command)

if (result.isBlank()) {
lastPausedActivity.value = "没有获取到上次活动, 请检查ADB设备是否已断开"
Expand All @@ -117,7 +128,7 @@ class ActivityState : AbstractState<ActivityLogic>() {
/// 获取某个Package下的Activity的堆栈列表(一般是当前package)
private suspend fun getStackActivities(packageName: String) {
val command = "adb shell dumpsys activity activities | grep $packageName | grep Activities".formatAdbCommand(device)
val result = ShellUtils.shell(command)
val result = ShellUtils.shell(command = command)

stackActivities.clear()
if (result.isBlank()) {
Expand Down Expand Up @@ -147,7 +158,10 @@ class ActivityState : AbstractState<ActivityLogic>() {

/// 加载当前Activity
fun loadActivity() {
launch {
if (job?.isActive == true) {
job?.cancel("重新获取Activity信息!")
}
job = launch {
withContext(Dispatchers.IO) { getPackageName() }
//并发
val a1 = async(Dispatchers.IO) { getProcessName() }
Expand Down
27 changes: 16 additions & 11 deletions src/main/kotlin/app/state/pages/AppManagerState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import compose.ComposeOverlay
import compose.ComposeToast
import extensions.regexFind
import extensions.right
import kotlinx.coroutines.async
import kotlinx.coroutines.*
import utils.*
import java.io.File
import javax.swing.filechooser.FileSystemView
Expand All @@ -25,8 +25,10 @@ class AppManagerState : AbstractState<AppManagerLogic>() {
private val homeState = StateManager.findState<HomeState>(HomeUI::class.java)
private val device = homeState?.currentDevice?.value ?: ""

val currentTabIndex = mutableStateOf(0)

private var job: Job? = null

val currentTabIndex = mutableStateOf(0)
private val allAppLazyListState = LazyListState()
private val systemAppLazyListState = LazyListState()
private val userAppLazyListState = LazyListState()
Expand Down Expand Up @@ -90,7 +92,7 @@ class AppManagerState : AbstractState<AppManagerLogic>() {
val command = "adb shell pm path $packageName".formatAdbCommand(device)

var appPath = ""
ShellUtils.shell(command) { success, error ->
ShellUtils.shell(command = command) { success, error ->
if (error.isNotEmpty()) return@shell
appPath = success.trim().right("package:")
}
Expand All @@ -102,7 +104,7 @@ class AppManagerState : AbstractState<AppManagerLogic>() {
private suspend fun getAppBasicDesc(packageName: String, appDesc: AppDesc) {
val command = "adb shell dumpsys package $packageName".formatAdbCommand(device)

ShellUtils.shell(command) { success, error ->
ShellUtils.shell(command = command) { success, error ->
if (error.isNotEmpty()) return@shell
appDesc.firstInstallTime = success.regexFind(Regex("firstInstallTime=(.*)\\s"), -1)
appDesc.lastUpdateTime = success.regexFind(Regex("lastUpdateTime=(.*)\\s"), -1)
Expand All @@ -120,7 +122,7 @@ class AppManagerState : AbstractState<AppManagerLogic>() {
val command = "adb shell stat -c '%s' $installedPath".formatAdbCommand(device)

var appLength = ""
ShellUtils.shell(command) { success, error ->
ShellUtils.shell(command = command) { success, error ->
if (error.isNotEmpty()) return@shell
appLength = success.trim()
}
Expand All @@ -134,7 +136,7 @@ class AppManagerState : AbstractState<AppManagerLogic>() {
appDesc.packageName = packageName
appDesc.isSystemApp = isSystemApp
appDesc.installedPath = getAppInstallPath(packageName)
appDesc.length = getAppLength(appDesc.installedPath).toIntOrNull() ?: 0
appDesc.length = getAppLength(appDesc.installedPath).toLongOrNull() ?: 0
getAppBasicDesc(packageName, appDesc)

return appDesc
Expand All @@ -159,7 +161,7 @@ class AppManagerState : AbstractState<AppManagerLogic>() {

val packageNameList = mutableListOf<String>()

ShellUtils.shell(command) { success, error ->
ShellUtils.shell(command = command) { success, error ->
if (error.isNotEmpty()) return@shell
success.split("\n").forEach {
if (it.trim().isNotEmpty()) {
Expand Down Expand Up @@ -199,7 +201,10 @@ class AppManagerState : AbstractState<AppManagerLogic>() {

///加载App列表
private fun loadAppList() {
launch {
if (job?.isActive == true) {
job?.cancel("重新加载App列表!")
}
job = launch {
val a1 = async { loadSystemApp() }
val a2 = async { loadUserApp() }
}
Expand Down Expand Up @@ -310,7 +315,7 @@ class AppManagerState : AbstractState<AppManagerLogic>() {
val command = "adb install -r \"$path\"".formatAdbCommand(device)
apkInstallMessage.value = "正在安装, 请注意设备安装弹窗.."
launch {
ShellUtils.shell(command) { success, error ->
ShellUtils.shell(command = command) { success, error ->
if (error.isNotEmpty()) {
apkInstallMessage.value = "安装失败!\n$error"
return@shell
Expand All @@ -330,7 +335,7 @@ class AppManagerState : AbstractState<AppManagerLogic>() {
val desktop = FileSystemView.getFileSystemView().homeDirectory
val exportApkName = File(desktop, appDesc.packageName.plus(".apk"))
val command = "adb pull ${appDesc.installedPath} ${exportApkName.absolutePath}".formatAdbCommand(device)
ShellUtils.shell(command) { success, error ->
ShellUtils.shell(command = command) { success, error ->
if (error.isNotBlank()) {
ComposeToast.show("导出失败!")
return@shell
Expand All @@ -351,7 +356,7 @@ class AppManagerState : AbstractState<AppManagerLogic>() {

launch {
val command = "adb uninstall ${appDesc.packageName}".formatAdbCommand(device)
ShellUtils.shell(command) { success, error ->
ShellUtils.shell(command = command) { success, error ->
if (error.isNotBlank()) {
ComposeToast.show("卸载失败!")
return@shell
Expand Down
Loading

0 comments on commit cafbc80

Please sign in to comment.