mirror of
https://github.com/gkd-kit/gkd.git
synced 2024-11-15 19:22:26 +08:00
This commit is contained in:
parent
de140488aa
commit
128a6cdf60
|
@ -1,5 +1,6 @@
|
|||
package li.songe.gkd.debug
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.blankj.utilcode.util.LogUtils
|
||||
|
@ -29,7 +30,6 @@ import kotlinx.coroutines.flow.first
|
|||
import kotlinx.serialization.Serializable
|
||||
import li.songe.gkd.app
|
||||
import li.songe.gkd.appScope
|
||||
import li.songe.gkd.composition.CompositionService
|
||||
import li.songe.gkd.data.AppInfo
|
||||
import li.songe.gkd.data.DeviceInfo
|
||||
import li.songe.gkd.data.GkdAction
|
||||
|
@ -57,135 +57,43 @@ import li.songe.gkd.util.updateSubscription
|
|||
import java.io.File
|
||||
|
||||
|
||||
class HttpService : CompositionService({
|
||||
val context = this
|
||||
val scope = CoroutineScope(Dispatchers.IO)
|
||||
class HttpService : Service() {
|
||||
private val scope = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
val httpSubsItem = SubsItem(
|
||||
id = LOCAL_HTTP_SUBS_ID,
|
||||
order = -1,
|
||||
enableUpdate = false,
|
||||
)
|
||||
|
||||
fun createServer(port: Int): CIOApplicationEngine {
|
||||
return embeddedServer(CIO, port) {
|
||||
install(KtorCorsPlugin)
|
||||
install(KtorErrorPlugin)
|
||||
install(ContentNegotiation) { json(keepNullJson) }
|
||||
|
||||
routing {
|
||||
get("/") { call.respondText(ContentType.Text.Html) { "<script type='module' src='$SERVER_SCRIPT_URL'></script>" } }
|
||||
route("/api") {
|
||||
// Deprecated
|
||||
get("/device") { call.respond(DeviceInfo.instance) }
|
||||
|
||||
post("/getServerInfo") { call.respond(ServerInfo()) }
|
||||
|
||||
// Deprecated
|
||||
get("/snapshot") {
|
||||
val id = call.request.queryParameters["id"]?.toLongOrNull()
|
||||
?: throw RpcError("miss id")
|
||||
val fp = File(SnapshotExt.getSnapshotPath(id))
|
||||
if (!fp.exists()) {
|
||||
throw RpcError("对应快照不存在")
|
||||
}
|
||||
call.respondFile(fp)
|
||||
}
|
||||
post("/getSnapshot") {
|
||||
val data = call.receive<ReqId>()
|
||||
val fp = File(SnapshotExt.getSnapshotPath(data.id))
|
||||
if (!fp.exists()) {
|
||||
throw RpcError("对应快照不存在")
|
||||
}
|
||||
call.respond(fp)
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
get("/screenshot") {
|
||||
val id = call.request.queryParameters["id"]?.toLongOrNull()
|
||||
?: throw RpcError("miss id")
|
||||
val fp = File(SnapshotExt.getScreenshotPath(id))
|
||||
if (!fp.exists()) {
|
||||
throw RpcError("对应截图不存在")
|
||||
}
|
||||
call.respondFile(fp)
|
||||
}
|
||||
post("/getScreenshot") {
|
||||
val data = call.receive<ReqId>()
|
||||
val fp = File(SnapshotExt.getScreenshotPath(data.id))
|
||||
if (!fp.exists()) {
|
||||
throw RpcError("对应截图不存在")
|
||||
}
|
||||
call.respondFile(fp)
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
get("/captureSnapshot") {
|
||||
call.respond(captureSnapshot())
|
||||
}
|
||||
post("/captureSnapshot") {
|
||||
call.respond(captureSnapshot())
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
get("/snapshots") {
|
||||
call.respond(DbSet.snapshotDao.query().first())
|
||||
}
|
||||
post("/getSnapshots") {
|
||||
call.respond(DbSet.snapshotDao.query().first())
|
||||
}
|
||||
|
||||
post("/updateSubscription") {
|
||||
val subscription =
|
||||
RawSubscription.parse(call.receiveText(), json5 = false)
|
||||
.copy(
|
||||
id = LOCAL_HTTP_SUBS_ID,
|
||||
name = "内存订阅",
|
||||
version = 0,
|
||||
author = "@gkd-kit/inspect"
|
||||
)
|
||||
updateSubscription(subscription)
|
||||
DbSet.subsItemDao.insert((subsItemsFlow.value.find { s -> s.id == httpSubsItem.id }
|
||||
?: httpSubsItem).copy(mtime = System.currentTimeMillis()))
|
||||
call.respond(RpcOk())
|
||||
}
|
||||
post("/execSelector") {
|
||||
if (!GkdAbService.isRunning.value) {
|
||||
throw RpcError("无障碍没有运行")
|
||||
}
|
||||
val gkdAction = call.receive<GkdAction>()
|
||||
call.respond(GkdAbService.execAction(gkdAction))
|
||||
}
|
||||
private var server: CIOApplicationEngine? = null
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
isRunning.value = true
|
||||
localNetworkIpsFlow.value = getIpAddressInLocalNetwork()
|
||||
val httpServerPortFlow = storeFlow.map(scope) { s -> s.httpServerPort }
|
||||
scope.launchTry(Dispatchers.IO) {
|
||||
httpServerPortFlow.collect { port ->
|
||||
server?.stop()
|
||||
server = try {
|
||||
createServer(port).apply { start() }
|
||||
} catch (e: Exception) {
|
||||
LogUtils.d("HTTP服务启动失败", e)
|
||||
null
|
||||
}
|
||||
if (server == null) {
|
||||
toast("HTTP服务启动失败,您可以尝试切换端口后重新启动")
|
||||
stopSelf()
|
||||
return@collect
|
||||
}
|
||||
createNotif(
|
||||
this@HttpService,
|
||||
httpChannel.id,
|
||||
httpNotif.copy(text = "HTTP服务-端口$port")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var server: CIOApplicationEngine? = null
|
||||
scope.launchTry(Dispatchers.IO) {
|
||||
storeFlow.map(scope) { s -> s.httpServerPort }.collect { port ->
|
||||
server?.stop()
|
||||
server = try {
|
||||
createServer(port).apply { start() }
|
||||
} catch (e: Exception) {
|
||||
LogUtils.d("HTTP服务启动失败", e)
|
||||
null
|
||||
}
|
||||
if (server == null) {
|
||||
toast("HTTP服务启动失败,您可以尝试切换端口后重新启动")
|
||||
stopSelf()
|
||||
return@collect
|
||||
}
|
||||
createNotif(
|
||||
context, httpChannel.id, httpNotif.copy(text = "HTTP服务正在运行-端口$port")
|
||||
)
|
||||
LogUtils.d(*getIpAddressInLocalNetwork().map { host -> "http://${host}:${port}" }
|
||||
.toList().toTypedArray())
|
||||
}
|
||||
}
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
isRunning.value = false
|
||||
localNetworkIpsFlow.value = emptyList()
|
||||
|
||||
|
||||
onDestroy {
|
||||
scope.launchTry(Dispatchers.IO) {
|
||||
server?.stop()
|
||||
if (storeFlow.value.autoClearMemorySubs) {
|
||||
|
@ -196,13 +104,6 @@ class HttpService : CompositionService({
|
|||
}
|
||||
}
|
||||
|
||||
isRunning.value = true
|
||||
localNetworkIpsFlow.value = getIpAddressInLocalNetwork()
|
||||
onDestroy {
|
||||
isRunning.value = false
|
||||
localNetworkIpsFlow.value = emptyList()
|
||||
}
|
||||
}) {
|
||||
companion object {
|
||||
val isRunning = MutableStateFlow(false)
|
||||
val localNetworkIpsFlow = MutableStateFlow(emptyList<String>())
|
||||
|
@ -213,8 +114,9 @@ class HttpService : CompositionService({
|
|||
fun start(context: Context = app) {
|
||||
context.startForegroundService(Intent(context, HttpService::class.java))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?) = null
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
@ -242,4 +144,105 @@ fun clearHttpSubs() {
|
|||
deleteSubscription(LOCAL_HTTP_SUBS_ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val httpSubsItem by lazy {
|
||||
SubsItem(
|
||||
id = LOCAL_HTTP_SUBS_ID,
|
||||
order = -1,
|
||||
enableUpdate = false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createServer(port: Int): CIOApplicationEngine {
|
||||
return embeddedServer(CIO, port) {
|
||||
install(KtorCorsPlugin)
|
||||
install(KtorErrorPlugin)
|
||||
install(ContentNegotiation) { json(keepNullJson) }
|
||||
routing {
|
||||
get("/") { call.respondText(ContentType.Text.Html) { "<script type='module' src='$SERVER_SCRIPT_URL'></script>" } }
|
||||
route("/api") {
|
||||
// Deprecated
|
||||
get("/device") { call.respond(DeviceInfo.instance) }
|
||||
|
||||
post("/getServerInfo") { call.respond(ServerInfo()) }
|
||||
|
||||
// Deprecated
|
||||
get("/snapshot") {
|
||||
val id = call.request.queryParameters["id"]?.toLongOrNull()
|
||||
?: throw RpcError("miss id")
|
||||
val fp = File(SnapshotExt.getSnapshotPath(id))
|
||||
if (!fp.exists()) {
|
||||
throw RpcError("对应快照不存在")
|
||||
}
|
||||
call.respondFile(fp)
|
||||
}
|
||||
post("/getSnapshot") {
|
||||
val data = call.receive<ReqId>()
|
||||
val fp = File(SnapshotExt.getSnapshotPath(data.id))
|
||||
if (!fp.exists()) {
|
||||
throw RpcError("对应快照不存在")
|
||||
}
|
||||
call.respond(fp)
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
get("/screenshot") {
|
||||
val id = call.request.queryParameters["id"]?.toLongOrNull()
|
||||
?: throw RpcError("miss id")
|
||||
val fp = File(SnapshotExt.getScreenshotPath(id))
|
||||
if (!fp.exists()) {
|
||||
throw RpcError("对应截图不存在")
|
||||
}
|
||||
call.respondFile(fp)
|
||||
}
|
||||
post("/getScreenshot") {
|
||||
val data = call.receive<ReqId>()
|
||||
val fp = File(SnapshotExt.getScreenshotPath(data.id))
|
||||
if (!fp.exists()) {
|
||||
throw RpcError("对应截图不存在")
|
||||
}
|
||||
call.respondFile(fp)
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
get("/captureSnapshot") {
|
||||
call.respond(captureSnapshot())
|
||||
}
|
||||
post("/captureSnapshot") {
|
||||
call.respond(captureSnapshot())
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
get("/snapshots") {
|
||||
call.respond(DbSet.snapshotDao.query().first())
|
||||
}
|
||||
post("/getSnapshots") {
|
||||
call.respond(DbSet.snapshotDao.query().first())
|
||||
}
|
||||
|
||||
post("/updateSubscription") {
|
||||
val subscription =
|
||||
RawSubscription.parse(call.receiveText(), json5 = false)
|
||||
.copy(
|
||||
id = LOCAL_HTTP_SUBS_ID,
|
||||
name = "内存订阅",
|
||||
version = 0,
|
||||
author = "@gkd-kit/inspect"
|
||||
)
|
||||
updateSubscription(subscription)
|
||||
DbSet.subsItemDao.insert((subsItemsFlow.value.find { s -> s.id == httpSubsItem.id }
|
||||
?: httpSubsItem).copy(mtime = System.currentTimeMillis()))
|
||||
call.respond(RpcOk())
|
||||
}
|
||||
post("/execSelector") {
|
||||
if (!GkdAbService.isRunning.value) {
|
||||
throw RpcError("无障碍没有运行")
|
||||
}
|
||||
val gkdAction = call.receive<GkdAction>()
|
||||
call.respond(GkdAbService.execAction(gkdAction))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ package li.songe.gkd.notif
|
|||
import li.songe.gkd.app
|
||||
import li.songe.gkd.util.SafeR
|
||||
|
||||
|
||||
data class Notif(
|
||||
val id: Int,
|
||||
val smallIcon: Int = SafeR.ic_status,
|
||||
|
@ -10,6 +11,7 @@ data class Notif(
|
|||
val text: String,
|
||||
val ongoing: Boolean,
|
||||
val autoCancel: Boolean,
|
||||
val uri: String? = null,
|
||||
)
|
||||
|
||||
val abNotif by lazy {
|
||||
|
@ -27,6 +29,7 @@ val screenshotNotif by lazy {
|
|||
text = "截屏服务正在运行",
|
||||
ongoing = true,
|
||||
autoCancel = false,
|
||||
uri = "gkd://page/1",
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -36,6 +39,7 @@ val floatingNotif by lazy {
|
|||
text = "悬浮窗按钮正在显示",
|
||||
ongoing = true,
|
||||
autoCancel = false,
|
||||
uri = "gkd://page/1",
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -45,5 +49,6 @@ val httpNotif by lazy {
|
|||
text = "HTTP服务正在运行",
|
||||
ongoing = true,
|
||||
autoCancel = false,
|
||||
uri = "gkd://page/1",
|
||||
)
|
||||
}
|
|
@ -7,6 +7,7 @@ import android.app.Service
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
|
@ -21,20 +22,24 @@ fun createChannel(context: Context, notifChannel: NotifChannel) {
|
|||
}
|
||||
|
||||
fun createNotif(context: Service, channelId: String, notif: Notif) {
|
||||
val intent = Intent(context, MainActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
}
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
context, notif.id, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
context,
|
||||
0,
|
||||
Intent(context, MainActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
notif.uri?.let { data = Uri.parse(it) }
|
||||
},
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
|
||||
val builder = NotificationCompat.Builder(context, channelId)
|
||||
val notification = NotificationCompat.Builder(context, channelId)
|
||||
.setSmallIcon(notif.smallIcon)
|
||||
.setContentTitle(notif.title).setContentText(notif.text).setContentIntent(pendingIntent)
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT).setOngoing(notif.ongoing)
|
||||
.setContentTitle(notif.title)
|
||||
.setContentText(notif.text)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setOngoing(notif.ongoing)
|
||||
.setAutoCancel(notif.autoCancel)
|
||||
|
||||
val notification = builder.build()
|
||||
.build()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
context.startForeground(
|
||||
notif.id, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST
|
||||
|
|
|
@ -17,11 +17,14 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
|||
import com.blankj.utilcode.util.LogUtils
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.generated.destinations.AdvancedPageDestination
|
||||
import com.ramcosta.composedestinations.utils.toDestinationsNavigator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import li.songe.gkd.MainActivity
|
||||
import li.songe.gkd.OpenFileActivity
|
||||
import li.songe.gkd.OpenSchemeActivity
|
||||
import li.songe.gkd.data.importData
|
||||
import li.songe.gkd.util.LocalNavController
|
||||
import li.songe.gkd.util.ProfileTransitions
|
||||
import li.songe.gkd.util.launchTry
|
||||
import li.songe.gkd.util.toast
|
||||
|
@ -35,6 +38,7 @@ data class BottomNavItem(
|
|||
@Composable
|
||||
fun HomePage() {
|
||||
val context = LocalContext.current as MainActivity
|
||||
val navController = LocalNavController.current
|
||||
val vm = viewModel<HomeVm>()
|
||||
val tab by vm.tabFlow.collectAsState()
|
||||
|
||||
|
@ -47,12 +51,11 @@ fun HomePage() {
|
|||
|
||||
val currentPage = pages.find { p -> p.navItem.label == tab.label } ?: controlPage
|
||||
|
||||
val intent = context.intent
|
||||
LaunchedEffect(key1 = intent, block = {
|
||||
intent ?: return@LaunchedEffect
|
||||
LaunchedEffect(key1 = null, block = {
|
||||
val intent = context.intent ?: return@LaunchedEffect
|
||||
context.intent = null
|
||||
LogUtils.d(intent)
|
||||
val uri = intent.data ?: return@LaunchedEffect
|
||||
val uri = intent.data?.normalizeScheme() ?: return@LaunchedEffect
|
||||
val source = intent.getStringExtra("source")
|
||||
if (source == OpenFileActivity::class.qualifiedName) {
|
||||
vm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||
|
@ -60,8 +63,13 @@ fun HomePage() {
|
|||
vm.tabFlow.value = subsPage.navItem
|
||||
importData(uri)
|
||||
}
|
||||
} else if (source == OpenSchemeActivity::class.qualifiedName) {
|
||||
LogUtils.d(uri)
|
||||
} else if (uri.scheme == "gkd" && uri.host == "page") {
|
||||
delay(300)
|
||||
when (uri.path) {
|
||||
"/1" -> {
|
||||
navController.toDestinationsNavigator().navigate(AdvancedPageDestination)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -72,7 +80,8 @@ fun HomePage() {
|
|||
bottomBar = {
|
||||
NavigationBar {
|
||||
pages.forEach { page ->
|
||||
NavigationBarItem(selected = tab.label == page.navItem.label,
|
||||
NavigationBarItem(
|
||||
selected = tab.label == page.navItem.label,
|
||||
modifier = Modifier,
|
||||
onClick = {
|
||||
vm.tabFlow.value = page.navItem
|
||||
|
|
Loading…
Reference in New Issue
Block a user