Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ed411fc284 | |||
| b7b0457685 | |||
| bdd458a64f |
BIN
KeyStore.jks
BIN
KeyStore.jks
Binary file not shown.
@@ -3,13 +3,9 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
|
||||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
|
||||||
tools:ignore="ScopedStorage" />
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
@@ -25,18 +21,17 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:label="@string/app_name"
|
|
||||||
android:theme="@style/Theme.FireflyPsAndorid">
|
android:theme="@style/Theme.FireflyPsAndorid">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".GolangServerService"
|
android:name=".GolangServerService"
|
||||||
android:foregroundServiceType="dataSync" />
|
android:foregroundServiceType="dataSync"
|
||||||
|
android:exported="false" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -8,73 +8,119 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
|
import android.os.PowerManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import libandroid.Libandroid
|
import libandroid.Libandroid
|
||||||
|
|
||||||
class GolangServerService : Service() {
|
class GolangServerService : Service() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val CHANNEL_ID = "GolangServerChannel"
|
const val CHANNEL_ID = "GolangServerChannel"
|
||||||
const val NOTIFICATION_ID = 1
|
const val NOTIFICATION_ID = 1
|
||||||
private const val TAG = "GolangServerService"
|
private const val TAG = "GolangServerService"
|
||||||
|
var isRunning by mutableStateOf(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var wakeLock: PowerManager.WakeLock? = null
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
createNotificationChannel()
|
createNotificationChannel()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
if (isRunning) {
|
||||||
|
Log.d(TAG, "❗ Server is already running")
|
||||||
|
return START_STICKY
|
||||||
|
}
|
||||||
|
isRunning = true
|
||||||
|
Log.d(TAG, "onStartCommand called")
|
||||||
|
|
||||||
val notificationIntent = Intent(this, MainActivity::class.java)
|
val notificationIntent = Intent(this, MainActivity::class.java)
|
||||||
val pendingIntent = PendingIntent.getActivity(
|
val pendingIntent = PendingIntent.getActivity(
|
||||||
this, 0, notificationIntent,
|
this,
|
||||||
PendingIntent.FLAG_IMMUTABLE
|
0,
|
||||||
|
notificationIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
)
|
)
|
||||||
|
|
||||||
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
|
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
|
||||||
.setContentTitle("Golang Server")
|
.setContentTitle("FireflyGO Server")
|
||||||
.setContentText("Server đang chạy")
|
.setContentText("FireflyGO is running...")
|
||||||
.setSmallIcon(R.drawable.ic_launcher_foreground)
|
.setSmallIcon(R.drawable.ic_launcher_foreground)
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(pendingIntent)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
startForeground(NOTIFICATION_ID, notification)
|
startForeground(NOTIFICATION_ID, notification)
|
||||||
|
|
||||||
// Chạy server trong thread riêng để tránh ANR
|
try {
|
||||||
|
val powerManager = getSystemService(POWER_SERVICE) as PowerManager
|
||||||
|
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "GolangServer::WakeLock")
|
||||||
|
wakeLock?.acquire()
|
||||||
|
Log.d(TAG, "✅ WakeLock acquired")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "❌ WakeLock failed", e)
|
||||||
|
}
|
||||||
|
|
||||||
Thread {
|
Thread {
|
||||||
try {
|
try {
|
||||||
val appDataPath = intent?.getStringExtra("appDataPath")
|
val appDataPath = intent?.getStringExtra("appDataPath")
|
||||||
|
|
||||||
if (appDataPath != null) {
|
if (appDataPath != null) {
|
||||||
Libandroid.setPathDataLocal(appDataPath)
|
Libandroid.setPathDataLocal(appDataPath)
|
||||||
Log.d(TAG, "✅ Set path data: $appDataPath")
|
Log.d(TAG, "✅ Set path data: $appDataPath")
|
||||||
} else {
|
} else {
|
||||||
|
isRunning = false
|
||||||
Log.e(TAG, "❌ appDataPath not received in intent")
|
Log.e(TAG, "❌ appDataPath not received in intent")
|
||||||
|
stopSelf()
|
||||||
|
return@Thread
|
||||||
}
|
}
|
||||||
|
|
||||||
Libandroid.setServerRunning(true)
|
Libandroid.setServerRunning(true)
|
||||||
|
isRunning = true
|
||||||
Log.d(TAG, "✅ Server started")
|
Log.d(TAG, "✅ Server started")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "❌ Error when start server:", e)
|
isRunning = false
|
||||||
|
Log.e(TAG, "❌ Error starting server", e)
|
||||||
|
stopSelf()
|
||||||
}
|
}
|
||||||
}.start()
|
}.start()
|
||||||
|
|
||||||
|
|
||||||
return START_STICKY
|
return START_STICKY
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
Log.d(TAG, "onDestroy called")
|
||||||
|
|
||||||
|
// 1. Tắt server
|
||||||
try {
|
try {
|
||||||
val result = Libandroid.setServerRunning(false)
|
val result = Libandroid.setServerRunning(false)
|
||||||
|
isRunning = false
|
||||||
Log.d(TAG, "Server shutdown result: $result")
|
Log.d(TAG, "Server shutdown result: $result")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Error shutting down server", e)
|
Log.e(TAG, "Error shutting down server", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. Giải phóng WakeLock nếu còn giữ
|
||||||
|
try {
|
||||||
|
wakeLock?.let {
|
||||||
|
if (it.isHeld) {
|
||||||
|
it.release()
|
||||||
|
Log.d(TAG, "✅ WakeLock released")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "❌ Failed to release WakeLock", e)
|
||||||
|
}
|
||||||
|
isRunning = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBind(intent: Intent?): IBinder? {
|
override fun onBind(intent: Intent?): IBinder? = null
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createNotificationChannel() {
|
private fun createNotificationChannel() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
@@ -83,11 +129,12 @@ class GolangServerService : Service() {
|
|||||||
"Golang Server Channel",
|
"Golang Server Channel",
|
||||||
NotificationManager.IMPORTANCE_LOW
|
NotificationManager.IMPORTANCE_LOW
|
||||||
).apply {
|
).apply {
|
||||||
description = "Notify Golang backend runing"
|
description = "Channel for running Golang backend in foreground"
|
||||||
}
|
}
|
||||||
|
|
||||||
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
notificationManager.createNotificationChannel(channel)
|
manager.createNotificationChannel(channel)
|
||||||
|
Log.d(TAG, "✅ Notification channel created")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.example.fireflypsandorid
|
package com.example.fireflypsandorid
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@@ -8,13 +9,11 @@ import androidx.activity.ComponentActivity
|
|||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.*
|
import androidx.compose.ui.*
|
||||||
import androidx.compose.ui.graphics.Brush
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
@@ -26,7 +25,7 @@ import com.example.fireflypsandorid.ui.theme.FireflyPsAndoridTheme
|
|||||||
import java.io.*
|
import java.io.*
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
private val TAG = "AppInit"
|
private val tag = "AppInit"
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -58,23 +57,23 @@ class MainActivity : ComponentActivity() {
|
|||||||
input.copyTo(output)
|
input.copyTo(output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Log.i(TAG, "✅ Copied $fileName to ${outFile.absolutePath}")
|
Log.i(tag, "✅ Copied $fileName to ${outFile.absolutePath}")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "❌ Failed to copy $fileName: ${e.message}")
|
Log.e(tag, "❌ Failed to copy $fileName: ${e.message}")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.i(TAG, "ℹ️ $fileName already exists at ${outFile.absolutePath}")
|
Log.i(tag, "ℹ️ $fileName already exists at ${outFile.absolutePath}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressLint("ImplicitSamInstance")
|
||||||
@Composable
|
@Composable
|
||||||
fun ServerControlScreen(appDataPath: String, modifier: Modifier = Modifier) {
|
fun ServerControlScreen(appDataPath: String, modifier: Modifier = Modifier) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
var isServerRunning by remember { mutableStateOf(false) }
|
val isRunning = GolangServerService.isRunning
|
||||||
|
val serverImage = if (isRunning)
|
||||||
val serverImage = if (isServerRunning)
|
|
||||||
painterResource(id = R.drawable.server_running)
|
painterResource(id = R.drawable.server_running)
|
||||||
else
|
else
|
||||||
painterResource(id = R.drawable.server_stopped)
|
painterResource(id = R.drawable.server_stopped)
|
||||||
@@ -115,8 +114,7 @@ fun ServerControlScreen(appDataPath: String, modifier: Modifier = Modifier) {
|
|||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
try {
|
try {
|
||||||
isServerRunning = !isServerRunning
|
if (!isRunning) {
|
||||||
if (isServerRunning) {
|
|
||||||
val intent = Intent(context, GolangServerService::class.java)
|
val intent = Intent(context, GolangServerService::class.java)
|
||||||
intent.putExtra("appDataPath", appDataPath)
|
intent.putExtra("appDataPath", appDataPath)
|
||||||
context.startService(intent)
|
context.startService(intent)
|
||||||
@@ -128,7 +126,7 @@ fun ServerControlScreen(appDataPath: String, modifier: Modifier = Modifier) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors = ButtonDefaults.buttonColors(
|
colors = ButtonDefaults.buttonColors(
|
||||||
containerColor = if (isServerRunning) Color(0xFFB71C1C) else Color(0xFFFF5722),
|
containerColor = if (isRunning) Color(0xFFB71C1C) else Color(0xFF2196F3),
|
||||||
contentColor = Color.White
|
contentColor = Color.White
|
||||||
),
|
),
|
||||||
shape = RoundedCornerShape(12.dp),
|
shape = RoundedCornerShape(12.dp),
|
||||||
@@ -137,7 +135,7 @@ fun ServerControlScreen(appDataPath: String, modifier: Modifier = Modifier) {
|
|||||||
.height(50.dp)
|
.height(50.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = if (isServerRunning) "Stop Server" else "Start Server",
|
text = if (isRunning) "Stop Server" else "Start Server",
|
||||||
fontSize = 20.sp
|
fontSize = 20.sp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -146,9 +144,9 @@ fun ServerControlScreen(appDataPath: String, modifier: Modifier = Modifier) {
|
|||||||
|
|
||||||
// Server status text
|
// Server status text
|
||||||
Text(
|
Text(
|
||||||
text = if (isServerRunning) "Server is running" else "Server is stopped",
|
text = if (isRunning) "Server is running" else "Server is stopped",
|
||||||
fontSize = 24.sp,
|
fontSize = 24.sp,
|
||||||
color = if (isServerRunning) Color(0xFF4CAF50) else Color.Gray
|
color = if (isRunning) Color(0xFF4CAF50) else Color.Gray
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">FireflyGo-3.4.5X</string>
|
<string name="app_name">FireflyGo-3.5.5X</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:ab23bd9a708e04ce0817aa7e3d05e81e3bd64e8846d0df54af9647b550d30859
|
oid sha256:26eaf2a6981aed15c5c7f6edc465505406f05567739054e71f2d3af60f111ef2
|
||||||
size 72329407
|
size 73415308
|
||||||
|
|||||||
Reference in New Issue
Block a user