SAF测试阶段,验证中
This commit is contained in:
@@ -6,7 +6,7 @@ plugins {
|
||||
|
||||
android {
|
||||
namespace = "com.lxh.battery_health"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
compileSdk = 34
|
||||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
@@ -21,38 +21,39 @@ android {
|
||||
defaultConfig {
|
||||
applicationId = "com.lxh.battery_health"
|
||||
minSdk = flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
targetSdk = 34
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
multiDexEnabled = true
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
create("release") {
|
||||
// 这里配置你正式发布的签名
|
||||
// 如果还没有正式签名,debug 可以用 debug 签名替代
|
||||
keyAlias = "your_key_alias"
|
||||
keyPassword = "your_key_password"
|
||||
storeFile = file("your_keystore.jks")
|
||||
storePassword = "your_store_password"
|
||||
// 如果没有正式签名,可以用 debug 签名代替(测试用)
|
||||
// 正式发布时请替换为自己的密钥
|
||||
getByName("debug") {
|
||||
// debug 签名已经存在,无需额外配置
|
||||
}
|
||||
create("release") {
|
||||
// 如果有正式签名,在这里配置
|
||||
// keyAlias = "..."
|
||||
// keyPassword = "..."
|
||||
// storeFile = file("...")
|
||||
// storePassword = "..."
|
||||
}
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
// 如果没有正式签名,可以暂时用 debug 签名
|
||||
// 正式发布时再替换
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
// 🟢 关键:debug 版本包名加 .dev 后缀
|
||||
applicationIdSuffix = ".dev"
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
isDebuggable = true
|
||||
isMinifyEnabled = false
|
||||
isShrinkResources = false
|
||||
}
|
||||
|
||||
release {
|
||||
applicationIdSuffix = "" // 保持原包名
|
||||
// 🟢 如果还没有正式签名,先用 debug 签名替代
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
// release 版本不加后缀,保持原包名
|
||||
signingConfig = signingConfigs.getByName("debug") // 若没有正式签名,先用 debug 签名
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(
|
||||
@@ -61,16 +62,12 @@ android {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有 productFlavors,可以在这里配置
|
||||
// 如果没有,忽略即可
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// 如果项目中有其他依赖,添加在这里
|
||||
// 注意:sqflite、permission_handler 等 Flutter 插件会自动包含
|
||||
}
|
||||
// 无需额外添加 Activity KTX,因为已改用 startActivityForResult
|
||||
}
|
||||
|
||||
@@ -4,19 +4,34 @@ import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.net.Uri
|
||||
import android.os.BatteryManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.DocumentsContract
|
||||
import android.provider.OpenableColumns
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.annotation.NonNull
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.EventChannel
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
|
||||
class MainActivity : FlutterActivity() {
|
||||
// ---------- 充电状态 ----------
|
||||
private val CHARGE_CHANNEL = "com.lxh.battery_health/charge"
|
||||
private val EVENT_CHANNEL = "com.lxh.battery_health/charge_events"
|
||||
private var eventSink: EventChannel.EventSink? = null
|
||||
|
||||
// ---------- SAF 相关 ----------
|
||||
private val SAF_CHANNEL = "com.lxh.battery_health/saf"
|
||||
private var safResult: MethodChannel.Result? = null
|
||||
private var safUri: Uri? = null
|
||||
private val FOLDER_PICKER_REQUEST = 1001
|
||||
|
||||
// ---------- 充电广播接收器 ----------
|
||||
private val chargeReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
intent ?: return
|
||||
@@ -44,53 +59,198 @@ class MainActivity : FlutterActivity() {
|
||||
eventSink?.success(mapOf("isCharging" to isCharging, "chargeType" to chargeType))
|
||||
}
|
||||
|
||||
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHARGE_CHANNEL).setMethodCallHandler { call, result ->
|
||||
if (call.method == "getChargeState") {
|
||||
val isCharging = getBatteryStatus()
|
||||
val chargeType = if (isCharging) {
|
||||
val batteryIntent = registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
|
||||
val plug = batteryIntent?.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) ?: -1
|
||||
when (plug) {
|
||||
BatteryManager.BATTERY_PLUGGED_AC -> "AC"
|
||||
BatteryManager.BATTERY_PLUGGED_USB -> "USB"
|
||||
BatteryManager.BATTERY_PLUGGED_WIRELESS -> "WIRELESS"
|
||||
else -> "UNKNOWN"
|
||||
}
|
||||
} else {
|
||||
"NONE"
|
||||
}
|
||||
result.success(mapOf("isCharging" to isCharging, "chargeType" to chargeType))
|
||||
} else {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
EventChannel(flutterEngine.dartExecutor.binaryMessenger, EVENT_CHANNEL).setStreamHandler(
|
||||
object : EventChannel.StreamHandler {
|
||||
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
|
||||
eventSink = events
|
||||
val filter = IntentFilter().apply {
|
||||
addAction(Intent.ACTION_POWER_CONNECTED)
|
||||
addAction(Intent.ACTION_POWER_DISCONNECTED)
|
||||
}
|
||||
registerReceiver(chargeReceiver, filter)
|
||||
}
|
||||
|
||||
override fun onCancel(arguments: Any?) {
|
||||
eventSink = null
|
||||
unregisterReceiver(chargeReceiver)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun getBatteryStatus(): Boolean {
|
||||
val batteryIntent = registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
|
||||
val status = batteryIntent?.getIntExtra(BatteryManager.EXTRA_STATUS, -1) ?: -1
|
||||
return status == BatteryManager.BATTERY_STATUS_CHARGING ||
|
||||
status == BatteryManager.BATTERY_STATUS_FULL
|
||||
}
|
||||
|
||||
// ---------- SAF 方法 ----------
|
||||
private fun openFolderPicker() {
|
||||
try {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
try {
|
||||
val downloadsUri = DocumentsContract.buildDocumentUri(
|
||||
"com.android.externalstorage.documents",
|
||||
"primary:Downloads"
|
||||
)
|
||||
putExtra(DocumentsContract.EXTRA_INITIAL_URI, downloadsUri)
|
||||
} catch (e: Exception) {
|
||||
// 忽略
|
||||
}
|
||||
}
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
}
|
||||
startActivityForResult(intent, FOLDER_PICKER_REQUEST)
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("BatteryHealth", "打开文件夹选择器失败: ${e.message}")
|
||||
safResult?.error("SELECT_FOLDER_FAILED", e.message, null)
|
||||
safResult = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if (requestCode == FOLDER_PICKER_REQUEST && resultCode == ComponentActivity.RESULT_OK) {
|
||||
val uri = data?.data
|
||||
if (uri != null) {
|
||||
try {
|
||||
val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
contentResolver.takePersistableUriPermission(uri, takeFlags)
|
||||
safUri = uri
|
||||
android.util.Log.d("BatteryHealth", "SAF URI: $uri")
|
||||
safResult?.success(uri.toString())
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("BatteryHealth", "SAF 授权失败: ${e.message}")
|
||||
safResult?.error("SAF_FAILED", e.message, null)
|
||||
}
|
||||
} else {
|
||||
safResult?.success(null)
|
||||
}
|
||||
safResult = null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTreeDocId(): String? {
|
||||
return safUri?.let { DocumentsContract.getTreeDocumentId(it) }
|
||||
}
|
||||
|
||||
private fun listFiles(): List<String> {
|
||||
val result = mutableListOf<String>()
|
||||
val docId = getTreeDocId()
|
||||
if (docId == null || docId.isEmpty()) return result
|
||||
|
||||
try {
|
||||
val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(safUri!!, docId)
|
||||
contentResolver.query(childrenUri, null, null, null, null)?.use { cursor ->
|
||||
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||
while (cursor.moveToNext()) {
|
||||
val name = cursor.getString(nameIndex)
|
||||
if (name != null) {
|
||||
result.add(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("BatteryHealth", "列出文件失败: ${e.message}")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun getFileUri(fileName: String): Uri? {
|
||||
val docId = getTreeDocId() ?: return null
|
||||
try {
|
||||
val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(safUri!!, docId)
|
||||
contentResolver.query(childrenUri, null, null, null, null)?.use { cursor ->
|
||||
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||
val idIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DOCUMENT_ID)
|
||||
while (cursor.moveToNext()) {
|
||||
val name = cursor.getString(nameIndex)
|
||||
if (name == fileName) {
|
||||
val fileDocId = cursor.getString(idIndex)
|
||||
return DocumentsContract.buildDocumentUriUsingTree(safUri!!, fileDocId)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("BatteryHealth", "获取文件URI失败: ${e.message}")
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun readFile(uri: Uri): String? {
|
||||
return try {
|
||||
contentResolver.openInputStream(uri)?.use { inputStream ->
|
||||
BufferedReader(InputStreamReader(inputStream)).use { reader ->
|
||||
reader.readText()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("BatteryHealth", "读取文件失败: ${e.message}")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun readLogFile(fileName: String): String? {
|
||||
val fileUri = getFileUri(fileName)
|
||||
return fileUri?.let { readFile(it) }
|
||||
}
|
||||
|
||||
// ---------- Flutter 通道配置 ----------
|
||||
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
|
||||
// 充电状态通道
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHARGE_CHANNEL)
|
||||
.setMethodCallHandler { call, result ->
|
||||
if (call.method == "getChargeState") {
|
||||
val isCharging = getBatteryStatus()
|
||||
val chargeType = if (isCharging) {
|
||||
val batteryIntent = registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
|
||||
val plug = batteryIntent?.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) ?: -1
|
||||
when (plug) {
|
||||
BatteryManager.BATTERY_PLUGGED_AC -> "AC"
|
||||
BatteryManager.BATTERY_PLUGGED_USB -> "USB"
|
||||
BatteryManager.BATTERY_PLUGGED_WIRELESS -> "WIRELESS"
|
||||
else -> "UNKNOWN"
|
||||
}
|
||||
} else {
|
||||
"NONE"
|
||||
}
|
||||
result.success(mapOf("isCharging" to isCharging, "chargeType" to chargeType))
|
||||
} else {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
// SAF 通道
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, SAF_CHANNEL)
|
||||
.setMethodCallHandler { call, result ->
|
||||
when (call.method) {
|
||||
"selectFolder" -> {
|
||||
safResult = result
|
||||
openFolderPicker()
|
||||
}
|
||||
"readLogFile" -> {
|
||||
val fileName = call.argument<String>("fileName")
|
||||
if (fileName != null) {
|
||||
val content = readLogFile(fileName)
|
||||
result.success(content)
|
||||
} else {
|
||||
result.error("INVALID_ARGUMENT", "fileName is required", null)
|
||||
}
|
||||
}
|
||||
"listFiles" -> {
|
||||
val files = listFiles()
|
||||
result.success(files)
|
||||
}
|
||||
"getUri" -> {
|
||||
result.success(safUri?.toString())
|
||||
}
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
// 充电状态事件通道
|
||||
EventChannel(flutterEngine.dartExecutor.binaryMessenger, EVENT_CHANNEL)
|
||||
.setStreamHandler(
|
||||
object : EventChannel.StreamHandler {
|
||||
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
|
||||
eventSink = events
|
||||
val filter = IntentFilter().apply {
|
||||
addAction(Intent.ACTION_POWER_CONNECTED)
|
||||
addAction(Intent.ACTION_POWER_DISCONNECTED)
|
||||
}
|
||||
registerReceiver(chargeReceiver, filter)
|
||||
}
|
||||
|
||||
override fun onCancel(arguments: Any?) {
|
||||
eventSink = null
|
||||
unregisterReceiver(chargeReceiver)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user