--- /dev/null
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# Module name should match apk name to be installed
+LOCAL_MODULE := ICFotaClient
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := FotaBrand8521e_V1_202103081958_debug.apk
+LOCAL_MODULE_CLASS := APPS
+LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
+
+# icfota
+LOCAL_OVERRIDES_PACKAGES := icfota
+
+LOCAL_PRIVILEGED_MODULE := true
+LOCAL_CERTIFICATE := platform
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_PREBUILT)
--- /dev/null
+{
+ "version": 2,
+ "artifactType": {
+ "type": "APK",
+ "kind": "Directory"
+ },
+ "applicationId": "com.ic.icfotaclient",
+ "variantName": "processFotaBrand8521eDebugResources",
+ "elements": [
+ {
+ "type": "SINGLE",
+ "filters": [],
+ "versionCode": 1,
+ "versionName": "1.0",
+ "outputFile": "FotaBrand8521e_V1_202103081958_debug.apk"
+ }
+ ]
+}
\ No newline at end of file
<service
android:name=".presenter.OtaService"
android:exported="true">
+ <intent-filter>
+ <action android:name="com.ic.action.ACTION_CHECK_UPDATE_BY_ALARM" />
+ </intent-filter>
<intent-filter>
<action android:name="com.ic.ota.action.CHECK_OTA" />
</intent-filter>
var mNextRepository: IOtaBaseRepository? = null
- override fun setNextRepository(nextRepository: IOtaBaseRepository) {
+ override fun setNextRepository(nextRepository: IOtaBaseRepository) {
this.mNextRepository = nextRepository
}
- override fun checkHasNewVersion(): OtaInfo? {
+ override fun checkHasNewVersion(): OtaInfo? {
return mNextRepository?.checkHasNewVersion()
}
return mNextRepository?.downloadPackage(callback)
}
- override fun reportInstallResult(isSuccess: Boolean) {
- mNextRepository?.reportInstallResult(isSuccess)
+ override fun reportInstallResult(isSuccess: Boolean, isInCar: Boolean) {
+ mNextRepository?.reportInstallResult(isSuccess, isInCar)
}
- override fun reportResult(versionCode: String) {
- mNextRepository?.reportResult(versionCode)
+ override fun reportResult(versionCode: String, isInCar: Boolean) {
+ mNextRepository?.reportResult(versionCode, isInCar)
}
- override fun getPackagePath(): String? {
+ override fun getPackagePath(): String? {
return mNextRepository?.getPackagePath()
}
mNextRepository?.deletePackage()
}
- override fun pauseDownload() {
+ override fun pauseDownload() {
mNextRepository?.pauseDownload()
}
}
\ No newline at end of file
/**
* 检查是否有新的版本
*/
- fun checkHasNewVersion(): OtaInfo?
+ fun checkHasNewVersion(): OtaInfo?
/**
* 下载升级包
*/
- fun downloadPackage(callback: IOtaRepositoryCallback): DownloadInfo?
+ fun downloadPackage(callback: IOtaRepositoryCallback): DownloadInfo?
/**
* 通知升级结果
*/
- fun reportInstallResult(isSuccess: Boolean);
+ fun reportInstallResult(isSuccess: Boolean, isInCar: Boolean);
/**
* 通知更新成功,让服务器收费
*/
- fun reportResult(versionCode: String)
+ fun reportResult(versionCode: String, isInCar: Boolean)
/**
* 获取升级包的路径
*/
- fun getPackagePath(): String?
+ fun getPackagePath(): String?
/**
* 删除升级包
*/
- fun deletePackage()
+ fun deletePackage()
/**
* 设置下一级委托的Repository
*/
- fun setNextRepository(nextRepository: IOtaBaseRepository)
+ fun setNextRepository(nextRepository: IOtaBaseRepository)
/**
* 暂停下载
*/
- fun pauseDownload()
+ fun pauseDownload()
}
\ No newline at end of file
val packageSize: Long,
//发布时间
val publishTime: Long,
+ //平台
+ val serverInc: ServerInc
) : Parcelable {
+
+
constructor(parcel: Parcel) : this(
parcel.readByte() != 0.toByte(),
parcel.readString(),
parcel.readString(),
parcel.readLong(),
parcel.readLong(),
+ TODO("serverInc")
) {
}
- override fun toString(): String {
- return "OtaInfo(isForce=$isForce," +
- " updateDesc='$updateDesc'," +
- " versionCode='$versionCode'," +
- " packageSize=$packageSize," +
- " publishTime=$publishTime)"
+ enum class ServerInc {
+ IN_CAR,
+ CAR_OTA
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
package com.ic.icfotaclient.presenter
+import android.app.AlarmManager
import android.app.Service
+import android.content.Context
import android.content.Intent
import android.os.IBinder
import android.text.TextUtils
import com.ic.icfotaclient.R
import com.ic.icfotaclient.utils.*
+
/**
* 这个是专门给远程使用的,如果远程需要知道是否有更新的内容,可以通过AIDL通讯
* 绑定过来,检查是否有更新。
//更新数据
mRemoteCallback?.onCheckResult(it)
})
+
+ //添加下一次的检查,时间随机
+ AlarmUtils.setUpNextAlarm(alarmManager)
+ }
+
+ private val alarmManager by lazy {
+ getSystemService(Context.ALARM_SERVICE) as AlarmManager
}
private val otaViewModel by lazy {
OtaViewModelCreator.getOtaViewModel(null)
}
- private var mRemoteCallback: IOtaCheckCallback? = null
+ private var mRemoteCallback: IOtaCheckCallback? = null
private val mServiceForRemote = object : IOtaCheckService.Stub() {
//检查是否有新版本
//去检查更新
doCheck()
}
+ AlarmUtils.ACTION_CHECK_UPDATE_BY_ALARM -> {
+ //主要是检查远程的
+ checkRemote()
+ //设置下一次闹钟
+ AlarmUtils.setUpNextAlarm(alarmManager)
+ }
}
return super.onStartCommand(intent, flags, startId)
}
+ private fun checkRemote() {
+ App.sHandler.postDelayed({
+ checkVersion(VersionCheckFrom.BOOT_CHECK)
+ }, Constants.TIME_DELAY_BOOT)
+ }
+
private fun doCheck() {
if (hasCheck) {
Log.d(TAG, "has checked..")
return
}
hasCheck = true
- App.sHandler.postDelayed({
- checkVersion(VersionCheckFrom.BOOT_CHECK)
- }, Constants.TIME_DELAY_BOOT)
+ checkRemote()
OtaViewModelCreator.getOtaViewModel(object : OtaViewModelCreator.OnViewModelInitListener {
override fun onViewModelInit(otaViewModel: OtaViewModel?) {
//如果已经更新了,那么通知一下服务器
const val TAG = "OtaViewModel"
}
-
private val mFirstRepository by lazy {
//Repository是责任链模式,所以要根据顺序排好
val carOtaRepository = CarOtaRepository()
if (!result) {
//无网络
currentActionState.postValue(UpdateActionState.NO_NETWORK)
- } else {
+ } else if (currentActionState.value == UpdateActionState.NO_NETWORK) {
+ //没有网络的时候才去更新为默认状态
currentActionState.postValue(UpdateActionState.NONE)
}
return result
* 检查本地是否有升级包
*/
private fun hasLocalUpdateZip(): Boolean {
- val file = File(VersionInfoUtils.getLocalUpdateZip())
+ val file = File(VersionInfoUtils.getUpdatePackageFullPath())
Log.d(TAG, "file is == > ${file.path}")
+ //需要进行校验,否则可能只下载到一半,但文件存在,不可升级。
+ //科洛达的会下载完再复制过去
+ //英卡的直接下载到对应的目录下
+ //因为校验需要md5值
+ //所以,没有校验过的,名字不叫这个,叫temp即可。
+ //也就是只有.zip是校验完的,否则名字就为.temp
+ //.zip才可以去升级,表示校验过的
if (file.exists()) {
//弹窗询问用户是否进行升级
showLocalUpdateTips()
descText.text = App.sContext.getString(R.string.local_update_desc_text)
installBtn.setOnClickListener {
//进行安装
- val file = File(VersionInfoUtils.getLocalUpdateZip())
+ val file = File(VersionInfoUtils.getUpdatePackageFullPath())
if (file.exists()) {
downInfoResult.postValue(DownloadInfo(file.path, file.length()))
this.install()
//删除
viewModelScope.launch {
withContext(Dispatchers.IO) {
- val file = File(VersionInfoUtils.getLocalUpdateZip())
+ val file = File(VersionInfoUtils.getUpdatePackageFullPath())
if (file.exists()) {
file.delete()
}
val result: OtaInfo? = withContext(Dispatchers.IO) {
val checkHasNewVersion = mFirstRepository.checkHasNewVersion()
if (checkHasNewVersion != null) {
- //干掉复制文件的地方,给下载位置
- val file = File(VersionInfoUtils.getUpdatePackageFullPath())
+ //干掉复制文件的地方,给下载位置,这个是英卡临时的包路径
+ val file = File(VersionInfoUtils.getUpdateTempPath())
if (file.exists()) {
file.delete()
}
//已经是最新版本了
UpdateActionState.LAST_VERSION
} else {
+ //保存一下是哪家的平台,后面用于判断是否需要上报结果
+ SPUtil.getSharePreference().edit()
+ .putString(SPUtil.KEY_SERVER_INC, result.serverInc.toString())
+ .apply()
//如果是强制升级的,不等通知用户结果了直接升级
if (result.isForce) {
Log.d(TAG, "force download data...")
viewModelScope.launch {
try {
val downResult = withContext(Dispatchers.IO) {
+ //下载前先删除
+
mFirstRepository.downloadPackage(mDownloadCallback)
}
if (downResult == null) {
val systemVersion = VersionInfoUtils.getSystemVersion()
Log.d(TAG, "system version code is == > $systemVersion")
Log.d(TAG, "versionRecord code is == > $versionRecord")
+ val serverInc = SPUtil.getSharePreference().getString(SPUtil.KEY_SERVER_INC, "")
+ val isInCar = OtaInfo.ServerInc.IN_CAR.toString() == serverInc
if (!TextUtils.isEmpty(versionRecord)) {
if (versionRecord == systemVersion) {
try {
+ Toast.makeText(
+ App.sContext,
+ R.string.update_success_tips_text,
+ Toast.LENGTH_LONG
+ ).show()
+ //测试的时候可以打开,查看升级结果
+// createUpdateResultDialog(
+// App.sContext.getString(R.string.update_result_success_tips) +
+// systemVersion + ":" + versionRecord
+// )
viewModelScope.launch {
- Toast.makeText(App.sContext, "升级成功!", Toast.LENGTH_SHORT).show()
withContext(Dispatchers.IO) {
Log.d(TAG, "post install result..success.")
- mFirstRepository.reportInstallResult(true)
- mFirstRepository.reportResult(versionRecord!!)
+ mFirstRepository.reportInstallResult(true, isInCar)
+ mFirstRepository.reportResult(versionRecord!!, isInCar)
//删除文件
mFirstRepository.deletePackage()
}
}
} else {
try {
+ Toast.makeText(
+ App.sContext,
+ R.string.update_failed_tips_text,
+ Toast.LENGTH_LONG
+ ).show()
+ //测试的时候可以打开此代码
+// createUpdateResultDialog(
+// App.sContext.getString(R.string.update_result_failed_tips) +
+// systemVersion + ":" + versionRecord
+// )
viewModelScope.launch {
withContext(Dispatchers.IO) {
Log.d(TAG, "post install result..failed.")
- mFirstRepository.reportInstallResult(false)
- mFirstRepository.reportResult(versionRecord!!)
+ mFirstRepository.reportInstallResult(false, isInCar)
+ mFirstRepository.reportResult(versionRecord!!, isInCar)
//删除文件
mFirstRepository.deletePackage()
}
}
}
+ /**
+ * 创建一个弹窗,直接添加到窗口中.
+ */
+ private fun createUpdateResultDialog(desc: String) {
+ val manager = App.sContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+ val layoutParams = WindowManager.LayoutParams()
+ layoutParams.gravity = Gravity.CENTER
+ layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
+ layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT
+ layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT
+ layoutParams.alpha = 1.0f
+ layoutParams.packageName = App.sContext.getPackageName()
+ layoutParams.buttonBrightness = 1f
+ layoutParams.windowAnimations = android.R.style.Animation_Dialog
+ val view: View =
+ View.inflate(App.sContext, R.layout.dialog_tips, null)
+ val descText: TextView = view.findViewById(R.id.description)
+ descText.text = desc
+ val yesButton: Button = view.findViewById(R.id.button_ok) as Button
+ val noButton: Button = view.findViewById(R.id.button_cancel) as Button
+ noButton.visibility = View.GONE
+ yesButton.setOnClickListener {
+ //取消这个界面
+ manager.removeView(view)
+ }
+ noButton.setOnClickListener {
+
+ }
+ manager.addView(view, layoutParams)
+ }
+
}
\ No newline at end of file
checkResult.releaseNote,
checkResult.targetVersion,
checkResult.size,
- System.currentTimeMillis()//科洛达的没有时间这项
+ System.currentTimeMillis(),//科洛达的没有时间这项
+ OtaInfo.ServerInc.CAR_OTA
)
}
return downloadInfo
}
- override fun reportInstallResult(isSuccess: Boolean) {
+ override fun reportInstallResult(isSuccess: Boolean, isIncar: Boolean) {
CarOtaApiUtils.mClient.reportUpdateResult(isSuccess, 0)
Log.d(TAG, "report result == isSuccess == > $isSuccess")
}
- override fun reportResult(versionCode: String) {
+ override fun reportResult(versionCode: String, isIncar: Boolean) {
CarOtaApiUtils.mClient.reportUpdateResultByVersion(versionCode)
Log.d(TAG, "report == versionCode == > $versionCode")
}
return super.downloadPackage(callback)
}
- override fun reportResult(versionCode: String) {
- if (checkIcVersion != null) {
+ override fun reportResult(versionCode: String, isInCar: Boolean) {
+ if (checkIcVersion != null || isInCar) {
+ //英卡暂时不通知,后面服务器上线了,再去添加
return
}
- super.reportResult(versionCode)
+ super.reportResult(versionCode, isInCar)
}
- override fun reportInstallResult(isSuccess: Boolean) {
- if (checkIcVersion != null) {
+ override fun reportInstallResult(isSuccess: Boolean, isInCar: Boolean) {
+ if (checkIcVersion != null || isInCar) {
+ //英卡暂时不通知,后面服务器上线了,再去添加
return
}
- super.reportInstallResult(isSuccess)
+ super.reportInstallResult(isSuccess, isInCar)
}
override fun deletePackage() {
--- /dev/null
+package com.ic.icfotaclient.utils
+
+import android.app.AlarmManager
+import android.app.AlarmManager.ELAPSED_REALTIME
+import android.app.PendingIntent
+import android.content.Intent
+import android.os.SystemClock
+import android.util.Log
+import com.ic.icfotaclient.App
+import com.ic.icfotaclient.presenter.OtaService
+import java.util.*
+
+
+object AlarmUtils {
+
+ private const val TAG = "AlarmUtils"
+
+ const val ACTION_CHECK_UPDATE_BY_ALARM = "com.ic.action.ACTION_CHECK_UPDATE_BY_ALARM"
+
+ private fun getPendingIntent(): PendingIntent {
+ val intent = Intent(App.sContext, OtaService::class.java)
+ intent.action = ACTION_CHECK_UPDATE_BY_ALARM
+ return PendingIntent.getService(App.sContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
+ }
+
+ fun setUpNextAlarm(alarmManager: AlarmManager) {
+ //时间随机,取之范围[1,4]小时
+ val nextTargetTime = nextLong(Random(), 1000 * 60 * 60 * 3) + 1000 * 60 * 60
+ //val nextTargetTime = 1000 * 60 * 2L;
+ Log.d(TAG, "nextTargetTime == > $nextTargetTime")
+ alarmManager.set(
+ ELAPSED_REALTIME,
+ SystemClock.elapsedRealtime() + nextTargetTime,
+ getPendingIntent()
+ )
+ }
+
+ private fun nextLong(rng: Random, n: Long): Long {
+ // error checking and 2^x checking removed for simplicity.
+ var bits: Long
+ var value: Long
+ do {
+ bits = rng.nextLong() shl 1 ushr 1
+ value = bits % n
+ } while (bits - value + (n - 1) < 0L)
+ return value
+ }
+}
\ No newline at end of file
import com.ic.icfotaclient.repository.IOtaRepositoryCallback
import okhttp3.OkHttpClient
import okhttp3.Request
+import java.io.File
import java.io.FileOutputStream
import java.net.HttpURLConnection
import java.net.URI
x509Certificates: Array<X509Certificate>,
s: String
) {
+
}
override fun checkServerTrusted(
otaInfo!!.description,
otaInfo!!.OtaPackageVersion,
otaInfo!!.OtaPackageLength.toLong(),
- System.currentTimeMillis()
+ System.currentTimeMillis(),
+ OtaInfo.ServerInc.IN_CAR
)
}
}
if (response?.code() == HttpURLConnection.HTTP_OK) {
try {
val fis = response.body()?.byteStream()
- val fos = FileOutputStream(VersionInfoUtils.getUpdatePackageFullPath())
+ val fos = FileOutputStream(VersionInfoUtils.getUpdateTempPath())
val buf = ByteArray(1024)
var len: Int
var current = 0
}
//校验文件下载是否正确
callback?.onVerify()
- val checkMd5 = MD5.checkMd5(otaInfo!!.md5sum, VersionInfoUtils.getUpdatePackageFullPath())
+ val checkMd5 = MD5.checkMd5(otaInfo!!.md5sum, VersionInfoUtils.getUpdateTempPath())
Log.d(TAG, "checkMd5 =+++= > $checkMd5")
if (!checkMd5) {
callback?.onDownloadError(-5, "校验失败")
return null
}
+ //校验完了,复制到getUpdatePackageFullPath这个路径,也就是重命名
+ val oldFile = File(VersionInfoUtils.getUpdateTempPath())
+ val targetFile = File(VersionInfoUtils.getUpdatePackageFullPath())
+ //重命名成功.
+ oldFile.renameTo(targetFile)
callback?.onDownloadSuccess()
return DownloadInfo(VersionInfoUtils.getUpdatePackageFullPath(), total.toLong())
}
const val SP_NAME = "update_record"
const val KEY_TARGET_VERSION_CODE = "key_target_version_code"
+ const val KEY_SERVER_INC = "key_server_inc"
fun getSharePreference(): SharedPreferences {
return App.sContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
var DATA_ROOT = "/data/media/0"
- private const val updateFileName = "update_pkg"
- private const val localUpdateFileName = "update_pkg.zip"
+ private const val updateFileName = "update_pkg.zip"
+ private const val tmpFileName = "update_pkg.tmp"
- fun getLocalUpdateZip(): String {
+ //下载到这个目录下,复制到getUpdatePackageFullPath这个目录下
+ fun getUpdateTempPath(): String {
//移动到对应的位置上
// if (getMultiUserState()) {
flash_root = DATA_ROOT
// }
- return flash_root + File.separator + localUpdateFileName
+ return flash_root + File.separator + tmpFileName
}
fun getUpdatePackageFullPath(): String {
val tipsWifiConnect = OtaViewModelCreator.getOtaViewModel(null)?.isTipsWifiConnect
if (checkNetWork != true ||
currentActionState?.value == UpdateActionState.LAST_VERSION
- || tipsWifiConnect?.value == false
+ || tipsWifiConnect?.value == true
) {
super.onBackPressed()
}
object : OtaViewModelCreator.OnViewModelInitListener {
override fun onViewModelInit(otaViewModel: OtaViewModel?) {
initObserver(otaViewModel)
+ //初始化UI状态,只要进来都是显示检查更新
+ otaViewModel?.currentActionState?.postValue(UpdateActionState.NONE)
}
})
}
override fun onDestroy() {
super.onDestroy()
Log.d("SystemUpdateActivity", "onDestroy...")
+
}
}
\ No newline at end of file
<string name="local_new_version_tips_title">检查到本地更新!</string>
<string name="local_update_desc_text">发现本地更新包,确定更新,或者删除.</string>
<string name="update_package_not_in_tips_text">安装文件不存在</string>
+ <string name="update_result_success_tips">升级结果成功:</string>
+ <string name="update_result_failed_tips">升级结果失败:</string>
+ <string name="update_success_tips_text">系统升级成功!</string>
+ <string name="update_failed_tips_text">系统升级失败!</string>
</resources>
\ No newline at end of file