1.增加g40固件升级
2.优化dfu弹窗布局
This commit is contained in:
@@ -30,7 +30,7 @@ android {
|
||||
targetSdkVersion 35
|
||||
versionCode 2202
|
||||
// versionName "2.2.2"
|
||||
versionName "2.2.2-Beta1"
|
||||
versionName "2.2.2-Beta2"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
@@ -319,8 +319,11 @@ dependencies {
|
||||
//支付宝支付
|
||||
implementation 'com.alipay.sdk:alipaysdk-android:+@aar'
|
||||
|
||||
//dfu升级https://github.com/NordicSemiconductor/Android-DFU-Library
|
||||
implementation 'no.nordicsemi.android:dfu:2.9.0'
|
||||
//g30 dfu升级https://github.com/NordicSemiconductor/Android-DFU-Library
|
||||
implementation 'no.nordicsemi.android:dfu:2.10.1'
|
||||
//g40 dfu升级https://github.com/nordicsemi/Android-nRF-Connect-Device-Manager?tab=readme-ov-file#migration-from-the-original-repo
|
||||
// implementation 'no.nordicsemi.android:mcumgr-core:2.7.4'
|
||||
implementation 'no.nordicsemi.android:mcumgr-ble:2.7.4'
|
||||
|
||||
//适配Android 12以下SplashScreen启动动画,闪屏图片
|
||||
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||
|
||||
@@ -25,8 +25,8 @@ class DFUNewDialog(context: Context) :
|
||||
}
|
||||
|
||||
fun startDfuState(version: String, onClick: () -> Unit) {
|
||||
mVersion = version
|
||||
val ver = String.format(mContext.getString(R.string.txt_new_firmware_version), mVersion)
|
||||
mVersion = "\t$version"
|
||||
val ver = String.format(mContext.getString(R.string.txt_about_version), mVersion)
|
||||
mViewBinding.apply {
|
||||
rlDialogDfuNewAfterLayout.visibility = View.GONE
|
||||
llDialogDfuNewBeforeLayout.visibility = View.VISIBLE
|
||||
@@ -36,7 +36,7 @@ class DFUNewDialog(context: Context) :
|
||||
inDFUState()
|
||||
onClick()
|
||||
}
|
||||
0 }
|
||||
}
|
||||
}
|
||||
|
||||
private fun inDFUState() {
|
||||
|
||||
@@ -111,7 +111,7 @@ class SharePetActivityActivity :
|
||||
|
||||
//先设置文件fileProvider
|
||||
EasyPhotos.createCamera(this@SharePetActivityActivity, false)
|
||||
.setFileProviderAuthority("${BuildConfig.APPLICATION_ID}.fileprovider")
|
||||
.setFileProviderAuthority(FileUtil.FILE_PROVIDER)
|
||||
|
||||
mTakePhotoAndCompressViewModel.registerCropImageActivityResult(this, 3, 4)
|
||||
}
|
||||
|
||||
@@ -306,7 +306,15 @@ class HomeTrackFragment :
|
||||
LogUtil.e("固件下载完成")
|
||||
mBleTrackDeviceBean?.let { b ->
|
||||
b.bleDevice?.let { ble ->
|
||||
mDeviceDFUViewModel.startDFU(mContext!!, ble, filePath)
|
||||
getHomeV2Activity()?.let { m ->
|
||||
m.getPet(false)?.let { p ->
|
||||
if (p.deviceType == ConstantInt.Type2) {
|
||||
mDeviceDFUViewModel.startG40DFU(mContext!!, ble, filePath)
|
||||
} else {
|
||||
mDeviceDFUViewModel.startG30DFU(mContext!!, ble, filePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -943,7 +951,11 @@ class HomeTrackFragment :
|
||||
)
|
||||
)
|
||||
}
|
||||
mDeviceDFUViewModel.getFirmware()
|
||||
getHomeV2Activity()?.let { m ->
|
||||
m.getPet(false)?.let { p ->
|
||||
mDeviceDFUViewModel.getFirmware(p.deviceType)
|
||||
}
|
||||
}
|
||||
mBleTrackDeviceBean?.apply {
|
||||
SRBleUtil.instance.writeData(
|
||||
bleDevice, SRBleCmdUtil.instance.ledState(SRBleCmdUtil.CMD_READ, 0)
|
||||
@@ -1068,9 +1080,13 @@ class HomeTrackFragment :
|
||||
getHomeV2Activity()?.apply {
|
||||
//保持屏幕常亮
|
||||
window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
DfuServiceListenerHelper.registerProgressListener(
|
||||
mContext, mDeviceDFUViewModel.mDfuProgressListener
|
||||
)
|
||||
getPet(false)?.let {
|
||||
if (it.deviceType == ConstantInt.Type1) {
|
||||
DfuServiceListenerHelper.registerProgressListener(
|
||||
mContext, mDeviceDFUViewModel.mDfuProgressListener
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1078,9 +1094,13 @@ class HomeTrackFragment :
|
||||
getHomeV2Activity()?.apply {
|
||||
//清除屏幕常亮
|
||||
window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
DfuServiceListenerHelper.unregisterProgressListener(
|
||||
mContext, mDeviceDFUViewModel.mDfuProgressListener
|
||||
)
|
||||
getPet(false)?.let {
|
||||
if (it.deviceType == ConstantInt.Type1) {
|
||||
DfuServiceListenerHelper.unregisterProgressListener(
|
||||
mContext, mDeviceDFUViewModel.mDfuProgressListener
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,12 @@ package com.abbidot.tracker.util
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import androidx.core.content.FileProvider
|
||||
import com.abbidot.baselibrary.util.LogUtil
|
||||
import com.abbidot.tracker.BuildConfig
|
||||
import java.io.File
|
||||
import java.math.BigDecimal
|
||||
|
||||
@@ -46,6 +50,11 @@ class FileUtil {
|
||||
*/
|
||||
const val LOG_DIRECTORY_NAME = "log"
|
||||
|
||||
/**
|
||||
* fileProvider字段
|
||||
*/
|
||||
const val FILE_PROVIDER = "${BuildConfig.APPLICATION_ID}.fileprovider"
|
||||
|
||||
/**
|
||||
* 获取缓存目录
|
||||
* <p>
|
||||
@@ -144,10 +153,20 @@ class FileUtil {
|
||||
/**
|
||||
* 删除文件或文件夹
|
||||
*/
|
||||
fun File.clearFile() {
|
||||
private fun File.clearFile() {
|
||||
if (isFile) delete()
|
||||
if (isDirectory) listFiles()?.forEach { it.clearFile() }
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件File的Uri
|
||||
*/
|
||||
fun getUri(cxt: Context, file: File): Uri {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
FileProvider.getUriForFile(cxt, FILE_PROVIDER, file)
|
||||
} else {
|
||||
Uri.fromFile(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,6 @@ import com.abbidot.baselibrary.util.AppUtils
|
||||
import com.abbidot.baselibrary.util.LogUtil
|
||||
import com.abbidot.baselibrary.util.MMKVUtil
|
||||
import com.abbidot.baselibrary.util.Utils
|
||||
import com.abbidot.tracker.BuildConfig
|
||||
import com.abbidot.tracker.R
|
||||
import com.abbidot.tracker.adapter.GridItemDecoration
|
||||
import com.abbidot.tracker.adapter.SelectMapTypeCardShadeAdapter
|
||||
@@ -582,7 +581,7 @@ class ViewUtil private constructor() {
|
||||
if (allGranted) {
|
||||
LogUtil.e("获取READ_MEDIA_IMAGES权限成功")
|
||||
EasyPhotos.createAlbum(activity, true, false, CoilEngine.instance)
|
||||
.setFileProviderAuthority("${BuildConfig.APPLICATION_ID}.fileprovider")
|
||||
.setFileProviderAuthority(FileUtil.FILE_PROVIDER)
|
||||
.setCount(count).setPuzzleMenu(false).setCleanMenu(false)
|
||||
.start(requestCode)
|
||||
//activity.overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left)
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.abbidot.tracker.vm
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.text.TextUtils
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
@@ -11,18 +12,30 @@ import com.abbidot.baselibrary.util.LogUtil
|
||||
import com.abbidot.tracker.R
|
||||
import com.abbidot.tracker.bean.DFUStateBean
|
||||
import com.abbidot.tracker.bean.FirmwareBean
|
||||
import com.abbidot.tracker.constant.ConstantInt
|
||||
import com.abbidot.tracker.retrofit2.NetworkApi
|
||||
import com.abbidot.tracker.service.DfuService
|
||||
import com.abbidot.tracker.util.FileUtil
|
||||
import com.abbidot.tracker.util.bluetooth.SRBleUtil
|
||||
import com.clj.fastble.BleManager
|
||||
import com.clj.fastble.callback.BleScanCallback
|
||||
import com.clj.fastble.data.BleDevice
|
||||
import com.clj.fastble.data.BleScanState
|
||||
import com.clj.fastble.scan.BleScanRuleConfig
|
||||
import io.runtime.mcumgr.ble.McuMgrBleTransport
|
||||
import io.runtime.mcumgr.dfu.FirmwareUpgradeCallback
|
||||
import io.runtime.mcumgr.dfu.FirmwareUpgradeController
|
||||
import io.runtime.mcumgr.dfu.mcuboot.FirmwareUpgradeManager
|
||||
import io.runtime.mcumgr.exception.McuMgrException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.dfu.DfuProgressListener
|
||||
import no.nordicsemi.android.dfu.DfuServiceInitiator
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
/**
|
||||
*Created by .yzq on 2023/3/16/016.
|
||||
@@ -47,10 +60,12 @@ class DeviceDFUViewModel(application: Application) : AndroidViewModel(applicatio
|
||||
/**
|
||||
* 获取固件信息
|
||||
*/
|
||||
fun getFirmware() {
|
||||
fun getFirmware(deviceType: Int) {
|
||||
// activity.showLoading(isShowLoading)
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val result = NetworkApi.getFirmware("GPS_Tracker_01")
|
||||
val deviceName = if (deviceType == ConstantInt.Type2) "g40"
|
||||
else "GPS_Tracker_01"
|
||||
val result = NetworkApi.getFirmware(deviceName)
|
||||
mFirmwareInfoLiveData.postValue(result)
|
||||
}
|
||||
}
|
||||
@@ -66,7 +81,146 @@ class DeviceDFUViewModel(application: Application) : AndroidViewModel(applicatio
|
||||
return serviceVersionInt > localVersionInt
|
||||
}
|
||||
|
||||
fun startDFU(context: Context, device: BleDevice, path: String) {
|
||||
|
||||
fun startG40DFU(context: Context, bleDevice: BleDevice, path: String) {
|
||||
val transport = McuMgrBleTransport(context, bleDevice.device)
|
||||
val dfuManager = FirmwareUpgradeManager(
|
||||
transport, object : FirmwareUpgradeCallback<FirmwareUpgradeManager.State?> {
|
||||
override fun onUpgradeStarted(controller: FirmwareUpgradeController?) {
|
||||
val stateBean = DFUStateBean(context.getString(R.string.txt_upgrade_start))
|
||||
mDfuStateLiveData.value = stateBean
|
||||
LogUtil.e("DfuProgressListener -->> onDfuProcessStarting")
|
||||
}
|
||||
|
||||
override fun onUpgradeCompleted() {
|
||||
val stateBean = DFUStateBean(context.getString(R.string.txt_upgrade_success))
|
||||
mDfuStateLiveData.value = stateBean
|
||||
LogUtil.e("DfuProgressListener -->> onUpgradeCompleted")
|
||||
val state = DFUStateBean(
|
||||
context.getString(R.string.txt_reconnect_device), mDFUSuccessCode
|
||||
)
|
||||
mDfuStateLiveData.value = state
|
||||
}
|
||||
|
||||
override fun onUploadProgressChanged(
|
||||
bytesSent: Int, imageSize: Int, timestamp: Long
|
||||
) {
|
||||
val currentTimeMillis = System.currentTimeMillis()
|
||||
//500ms更新发送数据,不用频繁更新
|
||||
if (currentTimeMillis - mUpdatePostTimeMillis > 500 || bytesSent == imageSize) {
|
||||
val progress = (bytesSent / imageSize.toFloat())
|
||||
mUpdatePostTimeMillis = currentTimeMillis
|
||||
val dFUStateBean = DFUStateBean(
|
||||
context.getString(R.string.txt_upgrading) + " ",
|
||||
(progress * 100).toInt()
|
||||
)
|
||||
mDfuStateLiveData.value = dFUStateBean
|
||||
}
|
||||
}
|
||||
|
||||
override fun onUpgradeCanceled(state: FirmwareUpgradeManager.State?) {
|
||||
LogUtil.e("DfuProgressListener -->> onUpgradeCanceled")
|
||||
}
|
||||
|
||||
override fun onUpgradeFailed(
|
||||
state: FirmwareUpgradeManager.State?, error: McuMgrException?
|
||||
) {
|
||||
LogUtil.e("DfuProgressListener -->> onError")
|
||||
val stateBean =
|
||||
DFUStateBean(context.getString(R.string.txt_upgrade_fail), mDFUFailCode)
|
||||
mDfuStateLiveData.value = stateBean
|
||||
}
|
||||
|
||||
override fun onStateChanged(
|
||||
prevState: FirmwareUpgradeManager.State?,
|
||||
newState: FirmwareUpgradeManager.State?
|
||||
) {
|
||||
LogUtil.e("DfuProgressListener -->> onStateChanged")
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
* 常用设置:
|
||||
* estimatedSwapTime: 设备重启后 MCUboot 交换镜像的大致时间
|
||||
* windowCapacity: 窗口上传并发数;设备端支持时可以提速
|
||||
* eraseAppSettings: 是否擦除应用 settings
|
||||
*
|
||||
* 这里先给一个稳妥配置:
|
||||
* - estimatedSwapTime = 20s
|
||||
* - windowCapacity = 1 (最稳,兼容性最好)
|
||||
*/
|
||||
val settings = FirmwareUpgradeManager.Settings.Builder().setEstimatedSwapTime(20000)
|
||||
.setWindowCapacity(1).setMemoryAlignment(4).setEraseAppSettings(false).build()
|
||||
|
||||
dfuManager.setMode(FirmwareUpgradeManager.Mode.CONFIRM_ONLY)
|
||||
try {
|
||||
val file = File(path)
|
||||
// 读取 zip,并提取单镜像 .bin
|
||||
val imageData = extractSingleBinFromZip(context, FileUtil.getUri(context, file))
|
||||
dfuManager.start(imageData!!, settings)
|
||||
} catch (e: Exception) {
|
||||
val stateBean = DFUStateBean(context.getString(R.string.txt_upgrade_fail), mDFUFailCode)
|
||||
mDfuStateLiveData.value = stateBean
|
||||
LogUtil.e("G40DFU 失败:${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 zip 中提取“单镜像”的 .bin 文件。
|
||||
*
|
||||
* 适用于:
|
||||
* - g40_app.zip 里只有一个主要 app bin
|
||||
*
|
||||
* 如果 zip 中有多个 .bin,当前实现会优先返回:
|
||||
* 1. 文件名包含 app 的
|
||||
* 2. 否则返回第一个 .bin
|
||||
*/
|
||||
private fun extractSingleBinFromZip(context: Context, zipUri: Uri): ByteArray? {
|
||||
context.contentResolver.openInputStream(zipUri).use { input ->
|
||||
requireNotNull(input) { "无法打开 zip 文件: $zipUri" }
|
||||
|
||||
val bins = mutableListOf<Pair<String, ByteArray>>()
|
||||
|
||||
ZipInputStream(input.buffered()).use { zis ->
|
||||
var entry: ZipEntry? = zis.nextEntry
|
||||
while (entry != null) {
|
||||
if (!entry.isDirectory) {
|
||||
val name = entry.name
|
||||
if (name.endsWith(".bin", ignoreCase = true)) {
|
||||
bins.add(name to zis.readAllBytesCompat())
|
||||
}
|
||||
}
|
||||
zis.closeEntry()
|
||||
entry = zis.nextEntry
|
||||
}
|
||||
}
|
||||
|
||||
if (bins.isEmpty()) return null
|
||||
if (bins.size == 1) return bins.first().second
|
||||
|
||||
// 多个 bin 时,优先选名字里带 app 的
|
||||
bins.firstOrNull { it.first.contains("app", ignoreCase = true) }?.let {
|
||||
return it.second
|
||||
}
|
||||
|
||||
// 否则退化成第一个
|
||||
return bins.first().second
|
||||
}
|
||||
}
|
||||
|
||||
private fun InputStream.readAllBytesCompat(): ByteArray {
|
||||
val buffer = ByteArray(4096)
|
||||
val bos = ByteArrayOutputStream()
|
||||
while (true) {
|
||||
val len = this.read(buffer)
|
||||
if (len <= 0) break
|
||||
bos.write(buffer, 0, len)
|
||||
}
|
||||
return bos.toByteArray()
|
||||
}
|
||||
|
||||
|
||||
fun startG30DFU(context: Context, device: BleDevice, path: String) {
|
||||
viewModelScope.launch {
|
||||
val dFUStateBean = DFUStateBean(context.getString(R.string.txt_dfu_model))
|
||||
mDfuStateLiveData.value = dFUStateBean
|
||||
|
||||
@@ -17,9 +17,18 @@
|
||||
|
||||
<com.abbidot.tracker.widget.TypefaceTextView
|
||||
android:id="@+id/tv_dialog_dfu_new_title"
|
||||
style="@style/my_TextView_style"
|
||||
android:layout_marginTop="@dimen/dp_20"
|
||||
android:text="@string/txt_about_version"
|
||||
android:textColor="@color/block_color2"
|
||||
android:textSize="@dimen/textSize12"
|
||||
app:lineHeight="@dimen/textSize20"
|
||||
app:typeface="@string/roboto_regular_font" />
|
||||
|
||||
<com.abbidot.tracker.widget.TypefaceTextView
|
||||
style="@style/my_TextView_style"
|
||||
android:layout_marginTop="@dimen/dp_18"
|
||||
android:text="@string/txt_new_firmware_version"
|
||||
android:text="@string/txt_phone_close_device"
|
||||
android:textSize="@dimen/textSize14"
|
||||
android:textStyle="bold"
|
||||
app:lineHeight="@dimen/textSize20" />
|
||||
|
||||
@@ -1078,5 +1078,6 @@
|
||||
|
||||
<string name="map_navigate_map_baidu">Baidu Map</string>
|
||||
<string name="txt_time_line">Timeline</string>
|
||||
<string name="txt_phone_close_device">Keep phone close to device</string>
|
||||
|
||||
</resources>
|
||||
Reference in New Issue
Block a user