From: TrillGates Date: Tue, 26 Jan 2021 07:33:38 +0000 (+0800) Subject: init project. X-Git-Url: http://git.snicker.com.cn/Users/apple/Desktop/static/gitweb.js?a=commitdiff_plain;h=6e32fea65aa5f85df460b6d3e1d4ebc3a5ebbb8c;p=icfotaclient.git init project. --- 6e32fea65aa5f85df460b6d3e1d4ebc3a5ebbb8c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/$PROJECT_FILE$ b/.idea/$PROJECT_FILE$ new file mode 100644 index 0000000..58b7e3e --- /dev/null +++ b/.idea/$PROJECT_FILE$ @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..208123b --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,130 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..61a9130 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/dictionaries/Administrator.xml b/.idea/dictionaries/Administrator.xml new file mode 100644 index 0000000..1295ce2 --- /dev/null +++ b/.idea/dictionaries/Administrator.xml @@ -0,0 +1,7 @@ + + + + imei + + + \ No newline at end of file diff --git a/.idea/dictionaries/HDC.xml b/.idea/dictionaries/HDC.xml new file mode 100644 index 0000000..5b14ef4 --- /dev/null +++ b/.idea/dictionaries/HDC.xml @@ -0,0 +1,7 @@ + + + + gson + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..b617266 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,23 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..54ee638 --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..9f83622 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/qaplug_profiles.xml b/.idea/qaplug_profiles.xml new file mode 100644 index 0000000..3dfd21f --- /dev/null +++ b/.idea/qaplug_profiles.xml @@ -0,0 +1,465 @@ + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/EQC_G5.jks b/app/EQC_G5.jks new file mode 100644 index 0000000..2229d46 Binary files /dev/null and b/app/EQC_G5.jks differ diff --git a/app/FotaBrand8541e/debug/FotaBrand8541e_V1_202101251756_debug.apk b/app/FotaBrand8541e/debug/FotaBrand8541e_V1_202101251756_debug.apk new file mode 100644 index 0000000..1c87ee8 Binary files /dev/null and b/app/FotaBrand8541e/debug/FotaBrand8541e_V1_202101251756_debug.apk differ diff --git a/app/FotaBrand8541e/debug/output-metadata.json b/app/FotaBrand8541e/debug/output-metadata.json new file mode 100644 index 0000000..25d61d3 --- /dev/null +++ b/app/FotaBrand8541e/debug/output-metadata.json @@ -0,0 +1,18 @@ +{ + "version": 2, + "artifactType": { + "type": "APK", + "kind": "Directory" + }, + "applicationId": "com.ic.icfotaclient", + "variantName": "processFotaBrand8541eDebugResources", + "elements": [ + { + "type": "SINGLE", + "filters": [], + "versionCode": 1, + "versionName": "1.0", + "outputFile": "FotaBrand8541e_V1_202101252023_debug.apk" + } + ] +} \ No newline at end of file diff --git a/app/FotaBrand8541e/release/FotaBrand8541e_V1_202101260957_release.apk b/app/FotaBrand8541e/release/FotaBrand8541e_V1_202101260957_release.apk new file mode 100644 index 0000000..8bc451b Binary files /dev/null and b/app/FotaBrand8541e/release/FotaBrand8541e_V1_202101260957_release.apk differ diff --git a/app/FotaBrand8541e/release/output-metadata.json b/app/FotaBrand8541e/release/output-metadata.json new file mode 100644 index 0000000..d1b542f --- /dev/null +++ b/app/FotaBrand8541e/release/output-metadata.json @@ -0,0 +1,18 @@ +{ + "version": 2, + "artifactType": { + "type": "APK", + "kind": "Directory" + }, + "applicationId": "com.ic.icfotaclient", + "variantName": "processFotaBrand8541eReleaseResources", + "elements": [ + { + "type": "SINGLE", + "filters": [], + "versionCode": 1, + "versionName": "1.0", + "outputFile": "FotaBrand8541e_V1_202101260957_release.apk" + } + ] +} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..af1a36b --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,120 @@ +plugins { + id 'com.android.application' + id 'kotlin-android' + id 'kotlin-android-extensions' +} + +android { + compileSdkVersion 29 + + + def currentVersionCode = getVersionCode() + + + defaultConfig { + applicationId "com.ic.icfotaclient" + minSdkVersion 19 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + signingConfigs { + key { + storeFile file("EQC_G5.jks") + storePassword "123456" + keyAlias "watch_g5" + keyPassword "123456" + } + } + + productFlavors { + + FotaBrand8521e { + flavorDimensions "FotaBrand21e" + buildConfigField "String", "brandName", "\"FotaBrand21e\"" + } + FotaBrand8541e { + flavorDimensions "FotaBrand41e" + buildConfigField "String", "brandName", "\"FotaBrand41e\"" + } + } + + buildTypes { + release { + zipAlignEnabled true // zip 对齐优化 + shrinkResources true // 移除未使用的资源文件 + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + renderscriptDebuggable = false + debuggable = false + manifestPlaceholders = [:] + signingConfig signingConfigs.key + buildConfigField "String", "token", "\"fYldTgHDa7\"" + } + debug { + signingConfig signingConfigs.key + buildConfigField "String", "token", "\"fYldTgHDa7\"" + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + + android.applicationVariants.all { variant -> + variant.outputs.all { + if (variant.buildType.name == "release") { + outputFileName = "${variant.getFlavorName()}_V${currentVersionCode}_${releaseTime()}_release.apk" + } else { + outputFileName = "${variant.getFlavorName()}_V${currentVersionCode}_${releaseTime()}_debug.apk" + } + } + } +} + +static def releaseTime() { + return new Date().format("yyyyMMddHHmm", TimeZone.getTimeZone("GMT+08:00")) +} + +def getVersionCode() { + def versionCodeFile = file('version_code.properties') + if (versionCodeFile.canRead()) { + Properties properties = new Properties() + properties.load(new FileInputStream(versionCodeFile)) + def versionCode = properties['VERSION_CODE'].toInteger()//读取version_code.properties文件存放的版本号。 + def runTasks = gradle.startParameter.taskNames + def task = ':app:assembleRelease' + if (task in runTasks) { + properties['VERSION_CODE'] = (++versionCode).toString() + properties.store(versionCodeFile.newWriter(), null) + } + return versionCode + } else { + throw new GradleException("无法读取version_code.properties文件!") + } +} + +dependencies { + def lifecycle_version = "2.2.0" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" + implementation files('libs/ota.da-sdk-v3.2.1.aar') + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'androidx.core:core-ktx:1.3.2' + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'com.google.android.material:material:1.2.1' + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + testImplementation 'junit:junit:4.+' + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + implementation("com.squareup.okhttp3:okhttp:3.12.0") + implementation files('libs/android-logging-log4j-1.0.3.jar', + 'libs/ftp4j-1.7.2.jar', + 'libs/log4j-1.2.17.jar') + implementation 'com.google.code.gson:gson:2.8.6' +} \ No newline at end of file diff --git a/app/libs/android-logging-log4j-1.0.3.jar b/app/libs/android-logging-log4j-1.0.3.jar new file mode 100644 index 0000000..7d4146f Binary files /dev/null and b/app/libs/android-logging-log4j-1.0.3.jar differ diff --git a/app/libs/ftp4j-1.7.2.jar b/app/libs/ftp4j-1.7.2.jar new file mode 100644 index 0000000..fca26ec Binary files /dev/null and b/app/libs/ftp4j-1.7.2.jar differ diff --git a/app/libs/log4j-1.2.17.jar b/app/libs/log4j-1.2.17.jar new file mode 100644 index 0000000..068867e Binary files /dev/null and b/app/libs/log4j-1.2.17.jar differ diff --git a/app/libs/okio-2.4.3.jar b/app/libs/okio-2.4.3.jar new file mode 100644 index 0000000..6289cf6 Binary files /dev/null and b/app/libs/okio-2.4.3.jar differ diff --git a/app/libs/ota.da-sdk-v3.2.1.aar b/app/libs/ota.da-sdk-v3.2.1.aar new file mode 100644 index 0000000..ccca184 Binary files /dev/null and b/app/libs/ota.da-sdk-v3.2.1.aar differ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..41f2508 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,88 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + +# 保留四大组件,自定义的Application等等这些类不被混淆 +-keep public class * extends android.app.Activity +-keep public class * extends android.app.Application +-keep public class * extends android.app.Service +-keep public class * extends android.content.BroadcastReceiver +-keep public class * extends android.content.ContentProvider +-keep public class * extends android.app.backup.BackupAgentHelper +-keep public class * extends android.preference.Preference +-keep public class * extends android.view.View + +-keep class android.support.** { *; } +-keep public class * extends android.support.v4.** +-keep public class * extends android.support.v7.** +-keep public class * extends android.support.annotation.** +-keep public class * extends android.app.Fragment +-keep class com.google.** { *; } + + +# 保留R下面的资源 +-keep class **.R$* {*;} +-keepclassmembers class **.R$* { + public static ; +} + +# 保留在Activity中的方法参数是view的方法, +# 这样以来我们在layout中写的onClick就不会被影响 +-keepclassmembers class * extends android.app.Activity{ + public void *(android.view.View); +} + +# 保留我们自定义控件(继承自View)不被混淆 +-keep public class * extends android.view.View{ + *** get*(); + void set*(***); + public (android.content.Context); + public (android.content.Context, android.util.AttributeSet); + public (android.content.Context, android.util.AttributeSet, int); +} + +################################## 项目类 ################################## + +-keep class com.facebook.** { *; } +-keep class com.mediatek.** { *; } +-keep class org.apache.** { *; } +-keep class okio.Okio.** { *; } +-keep class java.nio.** { *; } +-keep class com.android.** { *; } +-keep class android.** { *; } +-keep class android.ddm.** { *; } +-keep class com.alibaba.fastjson.** { *; } +-keep class com.eqc.watch.launcher.WatchApplication { *; } +#-keep class com.enqualcomm.support.EqcAlgorithmNative { *; } +-dontwarn com.facebook.** +-dontwarn com.mediatek.** +-dontwarn java.nio.** +-dontwarn com.alibaba.fastjson.** +-dontwarn android.** +-dontwarn com.android.** +-dontwarn rx.internal.** + +-keep class com.ic.** { *; } +-keep class com.enqualcomm.** { *; } +-keep class com.sina.util.dnscache.** { *; } +-keep class rx.internal.** { *; } +-keep class java.util.concurrent.** { *; } +-keep class se.wetcat.qatia.** { *; } \ No newline at end of file diff --git a/app/src/androidTest/java/com/ic/icfotaclient/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/ic/icfotaclient/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..1472a7c --- /dev/null +++ b/app/src/androidTest/java/com/ic/icfotaclient/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.ic.icfotaclient + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.ic.icfotaclient", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..bfc13b6 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/aidl/com/ic/icfotaclient/IOtaCheckCallback.aidl b/app/src/main/aidl/com/ic/icfotaclient/IOtaCheckCallback.aidl new file mode 100644 index 0000000..03d0d90 --- /dev/null +++ b/app/src/main/aidl/com/ic/icfotaclient/IOtaCheckCallback.aidl @@ -0,0 +1,12 @@ +// IOtaCheckCallback.aidl +package com.ic.icfotaclient; + +import com.ic.icfotaclient.bean.OtaInfo; + +interface IOtaCheckCallback { + void onCheckResult(in OtaInfo otaInfo); + + void onImeiCodeGet(in String imeiCode); + + void onVersionCodeGet(in String versionCode); +} \ No newline at end of file diff --git a/app/src/main/aidl/com/ic/icfotaclient/IOtaCheckService.aidl b/app/src/main/aidl/com/ic/icfotaclient/IOtaCheckService.aidl new file mode 100644 index 0000000..ecef6de --- /dev/null +++ b/app/src/main/aidl/com/ic/icfotaclient/IOtaCheckService.aidl @@ -0,0 +1,16 @@ +// IOtaCheckService.aidl +package com.ic.icfotaclient; + +import com.ic.icfotaclient.IOtaCheckCallback; + +interface IOtaCheckService { + + oneway void checkHasNewVersion(); + + void setCallback(in IOtaCheckCallback callback); + + oneway void getImeiCode(); + + oneway void getVersionCode(); + +} \ No newline at end of file diff --git a/app/src/main/aidl/com/ic/icfotaclient/bean/OtaInfo.aidl b/app/src/main/aidl/com/ic/icfotaclient/bean/OtaInfo.aidl new file mode 100644 index 0000000..31d503d --- /dev/null +++ b/app/src/main/aidl/com/ic/icfotaclient/bean/OtaInfo.aidl @@ -0,0 +1,6 @@ +// OtaInfo.aidl +package com.ic.icfotaclient.bean; + +// Declare any non-default types here with import statements + +parcelable OtaInfo; \ No newline at end of file diff --git a/app/src/main/java/com/ic/icfotaclient/App.kt b/app/src/main/java/com/ic/icfotaclient/App.kt new file mode 100644 index 0000000..c48846b --- /dev/null +++ b/app/src/main/java/com/ic/icfotaclient/App.kt @@ -0,0 +1,23 @@ +package com.ic.icfotaclient + +import android.app.Application +import android.content.Context +import android.os.Handler +import android.os.HandlerThread + +class App : Application() { + + companion object { + lateinit var sContext: Context + lateinit var sHandler: Handler + } + + override fun onCreate() { + super.onCreate() + sContext = applicationContext + val otaHandlerThread = HandlerThread("ic:ota-handler-thread") + otaHandlerThread.start() + sHandler = Handler(otaHandlerThread.looper) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/ic/icfotaclient/base/AbstractOtaRepository.kt b/app/src/main/java/com/ic/icfotaclient/base/AbstractOtaRepository.kt new file mode 100644 index 0000000..a943fc3 --- /dev/null +++ b/app/src/main/java/com/ic/icfotaclient/base/AbstractOtaRepository.kt @@ -0,0 +1,43 @@ +package com.ic.icfotaclient.base + +import com.ic.icfotaclient.bean.DownloadInfo +import com.ic.icfotaclient.bean.OtaInfo +import com.ic.icfotaclient.repository.IOtaRepositoryCallback + +abstract class AbstractOtaRepository : IOtaBaseRepository { + + + var mNextRepository: IOtaBaseRepository? = null + + override suspend fun setNextRepository(nextRepository: IOtaBaseRepository) { + this.mNextRepository = nextRepository + } + + override suspend fun checkHasNewVersion(): OtaInfo? { + return mNextRepository?.checkHasNewVersion() + } + + override suspend fun downloadPackage(callback: IOtaRepositoryCallback): DownloadInfo? { + return mNextRepository?.downloadPackage(callback) + } + + override suspend fun reportInstallResult(isSuccess: Boolean) { + mNextRepository?.reportInstallResult(isSuccess) + } + + override suspend fun reportResult(versionCode: String) { + mNextRepository?.reportResult(versionCode) + } + + override suspend fun getPackagePath(): String? { + return mNextRepository?.getPackagePath() + } + + override suspend fun deletePackage() { + mNextRepository?.deletePackage() + } + + override suspend fun pauseDownload() { + mNextRepository?.pauseDownload() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ic/icfotaclient/base/IOtaBaseRepository.kt b/app/src/main/java/com/ic/icfotaclient/base/IOtaBaseRepository.kt new file mode 100644 index 0000000..26a6ab9 --- /dev/null +++ b/app/src/main/java/com/ic/icfotaclient/base/IOtaBaseRepository.kt @@ -0,0 +1,50 @@ +package com.ic.icfotaclient.base + +import com.ic.icfotaclient.bean.DownloadInfo +import com.ic.icfotaclient.bean.OtaInfo +import com.ic.icfotaclient.repository.IOtaRepositoryCallback + + +interface IOtaBaseRepository { + /** + * 检查是否有新的版本 + */ + suspend fun checkHasNewVersion(): OtaInfo? + + /** + * 下载升级包 + */ + suspend fun downloadPackage(callback: IOtaRepositoryCallback): DownloadInfo? + + /** + * 通知升级结果 + */ + suspend fun reportInstallResult(isSuccess: Boolean); + + /** + * 通知更新成功,让服务器收费 + */ + suspend fun reportResult(versionCode: String) + + /** + * 获取升级包的路径 + */ + suspend fun getPackagePath(): String? + + /** + * 删除升级包 + */ + suspend fun deletePackage() + + /** + * 设置下一级委托的Repository + */ + suspend fun setNextRepository(nextRepository: IOtaBaseRepository) + + + /** + * 暂停下载 + */ + suspend fun pauseDownload() + +} \ No newline at end of file diff --git a/app/src/main/java/com/ic/icfotaclient/bean/DownloadInfo.kt b/app/src/main/java/com/ic/icfotaclient/bean/DownloadInfo.kt new file mode 100644 index 0000000..bd39c93 --- /dev/null +++ b/app/src/main/java/com/ic/icfotaclient/bean/DownloadInfo.kt @@ -0,0 +1,7 @@ +package com.ic.icfotaclient.bean + +data class DownloadInfo(var path: String, val size: Long) { + override fun toString(): String { + return "DownloadInfo(path='$path', size=$size)" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ic/icfotaclient/bean/ICOtaInfo.kt b/app/src/main/java/com/ic/icfotaclient/bean/ICOtaInfo.kt new file mode 100644 index 0000000..2ab0aab --- /dev/null +++ b/app/src/main/java/com/ic/icfotaclient/bean/ICOtaInfo.kt @@ -0,0 +1,13 @@ +package com.ic.icfotaclient.bean + +data class ICOtaInfo( + val AutoUpdate: Boolean, + val OtaPackageLength: Int, + val OtaPackageName: String, + val OtaPackageVersion: String, + val SystemVersion: String, + val UptoDate: Boolean, + val description: String, + val md5sum: String, + val url: String +) \ No newline at end of file diff --git a/app/src/main/java/com/ic/icfotaclient/bean/OtaInfo.kt b/app/src/main/java/com/ic/icfotaclient/bean/OtaInfo.kt new file mode 100644 index 0000000..ce544df --- /dev/null +++ b/app/src/main/java/com/ic/icfotaclient/bean/OtaInfo.kt @@ -0,0 +1,59 @@ +package com.ic.icfotaclient.bean + +import android.os.Parcel +import android.os.Parcelable + +/** + * 升级信息 + */ +data class OtaInfo( + //是否强制升级 + val isForce: Boolean, + //更新信息 + val updateDesc: String?, + //版本号 + val versionCode: String?, + //包的大小 + val packageSize: Long, + //发布时间 + val publishTime: Long, +) : Parcelable { + constructor(parcel: Parcel) : this( + parcel.readByte() != 0.toByte(), + parcel.readString(), + parcel.readString(), + parcel.readLong(), + parcel.readLong(), + ) { + } + + override fun toString(): String { + return "OtaInfo(isForce=$isForce," + + " updateDesc='$updateDesc'," + + " versionCode='$versionCode'," + + " packageSize=$packageSize," + + " publishTime=$publishTime)" + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeByte(if (isForce) 1 else 0) + parcel.writeString(updateDesc) + parcel.writeString(versionCode) + parcel.writeLong(packageSize) + parcel.writeLong(publishTime) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): OtaInfo { + return OtaInfo(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} diff --git a/app/src/main/java/com/ic/icfotaclient/presenter/OtaReceiver.kt b/app/src/main/java/com/ic/icfotaclient/presenter/OtaReceiver.kt new file mode 100644 index 0000000..9a77823 --- /dev/null +++ b/app/src/main/java/com/ic/icfotaclient/presenter/OtaReceiver.kt @@ -0,0 +1,35 @@ +package com.ic.icfotaclient.presenter + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.util.Log +import com.ic.icfotaclient.utils.Constants + +/** + * 广播监听者: + * 监听开机广播,去检查是否有升级 + * 其他广播//TODO: + */ +class OtaReceiver : BroadcastReceiver() { + companion object { + const val TAG = "OtaReceiver" + } + + override fun onReceive(context: Context?, intent: Intent?) { + Log.d(TAG, "OtaReceiver...") + if (intent == null) { + return + } + val action = intent.action + Log.d(TAG, "action is == > $action") + when (action) { + Intent.ACTION_BOOT_COMPLETED -> { + //开机广播,拉起服务 + val serviceIntent = Intent(context, OtaService::class.java) + serviceIntent.action = Constants.CMD_ACTION_BOOT_COMPLETED + context?.startService(serviceIntent) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ic/icfotaclient/presenter/OtaService.kt b/app/src/main/java/com/ic/icfotaclient/presenter/OtaService.kt new file mode 100644 index 0000000..d60a162 --- /dev/null +++ b/app/src/main/java/com/ic/icfotaclient/presenter/OtaService.kt @@ -0,0 +1,128 @@ +package com.ic.icfotaclient.presenter + +import android.app.Service +import android.content.Intent +import android.os.IBinder +import android.text.TextUtils +import android.util.Log +import androidx.lifecycle.* +import com.ic.icfotaclient.App +import com.ic.icfotaclient.IOtaCheckCallback +import com.ic.icfotaclient.IOtaCheckService +import com.ic.icfotaclient.utils.Constants +import com.ic.icfotaclient.utils.OtaViewModelCreator +import com.ic.icfotaclient.utils.VersionCheckFrom +import com.ic.icfotaclient.utils.VersionInfoUtils + +/** + * 这个是专门给远程使用的,如果远程需要知道是否有更新的内容,可以通过AIDL通讯 + * 绑定过来,检查是否有更新。 + */ +class OtaService : Service(), ViewModelStoreOwner, LifecycleOwner { + + companion object{ + const val TAG = "OtaService" + } + + override fun onBind(intent: Intent?): IBinder? { + return mServiceForRemote + } + + override fun onCreate() { + super.onCreate() + Log.d(TAG,"on service created...") + OtaViewModelCreator.initOtaViewModelStoreOwner(this) + //观察数据变化 + otaViewModel?.otaInfo?.observe(this, Observer { + Log.d(TAG,"on ota info change...$it") + //更新数据 + mRemoteCallback?.onCheckResult(it) + }) + } + + private val otaViewModel by lazy { + OtaViewModelCreator.getOtaViewModel(null) + } + + private var mRemoteCallback: IOtaCheckCallback? = null + + + private val mServiceForRemote = object : IOtaCheckService.Stub() { + //检查是否有新版本 + override fun checkHasNewVersion() { + checkVersion(VersionCheckFrom.REMOTE_CHECK) + } + + override fun setCallback(callback: IOtaCheckCallback?) { + //远程回调接口 + mRemoteCallback = callback + } + + override fun getImeiCode() { + val imei = VersionInfoUtils.getIMEI() + Log.d(TAG,"imei == > $imei") + mRemoteCallback?.onImeiCodeGet(imei) + } + + override fun getVersionCode() { + val versionCode = VersionInfoUtils.getSystemVersion() + Log.d(TAG,"versionCode == > $versionCode") + mRemoteCallback?.onVersionCodeGet(versionCode) + } + } + + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + //判断指令是啥 + val action = intent?.action + Log.d(TAG,"onStartCommand action -- > $action") + if (TextUtils.isEmpty(action)) { + return START_NOT_STICKY + } + //根据action做出处理 + when (action) { + Constants.CMD_ACTION_BOOT_COMPLETED -> { + //开机完,创建一个ViewModel,生命周期与此服务生命周期挂钩 + //以下动作,需要延时执行,因为IMEI号,网络这些还没准备好 + //检查网络,如果网络okay了,就去检查版本更新 + //如果网络不okay,那么就等待网络连接的广播,在连接的广播里去检查更新 + App.sHandler.postDelayed({ + checkVersion(VersionCheckFrom.BOOT_CHECK) + }, Constants.TIME_DELAY_BOOT) + //如果已经更新了,那么通知一下服务器 + otaViewModel?.checkHasUpdated() + } + } + + return super.onStartCommand(intent, flags, startId) + } + + private fun checkVersion(from: VersionCheckFrom) { + val checkNetWork = otaViewModel?.checkNetWork() + Log.d(TAG,"checkNetWork == > $checkNetWork") + if (checkNetWork == true) { + //连接上了,那么就去检查更新 + otaViewModel?.checkUpdate(from) + } + } + + /** + * 释放资源 + */ + override fun onDestroy() { + super.onDestroy() + viewModel.clear() + } + + private val viewModel by lazy { + ViewModelStore() + } + + override fun getViewModelStore(): ViewModelStore { + return viewModel + } + + override fun getLifecycle(): Lifecycle { + return LifecycleRegistry(this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ic/icfotaclient/presenter/OtaViewModel.kt b/app/src/main/java/com/ic/icfotaclient/presenter/OtaViewModel.kt new file mode 100644 index 0000000..164561f --- /dev/null +++ b/app/src/main/java/com/ic/icfotaclient/presenter/OtaViewModel.kt @@ -0,0 +1,386 @@ +package com.ic.icfotaclient.presenter + +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.BatteryManager +import android.os.RecoverySystem +import android.text.TextUtils +import android.util.Log +import android.view.Gravity +import android.view.View +import android.view.WindowManager +import android.widget.Button +import android.widget.TextView +import android.widget.Toast +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.ic.icfotaclient.App +import com.ic.icfotaclient.R +import com.ic.icfotaclient.bean.DownloadInfo +import com.ic.icfotaclient.bean.OtaInfo +import com.ic.icfotaclient.repository.CarOtaRepository +import com.ic.icfotaclient.repository.ICOtaRepository +import com.ic.icfotaclient.repository.IOtaRepositoryCallback +import com.ic.icfotaclient.utils.* +import com.ic.icfotaclient.view.SystemUpdateActivity +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.File + + +/** + * 逻辑层,全局只有一个,生命周期与服务挂钩吧,检查完了就可以停掉了,不需要一直跑着。 + * 如果有升级就去做事情,如果没有升级就自己退出,释放所有资源。 + */ +class OtaViewModel : ViewModel() { + + companion object { + const val TAG = "OtaViewModel" + } + + + private val mFirstRepository by lazy { + //Repository是责任链模式,所以要根据顺序排好 + val carOtaRepository = CarOtaRepository() + val icOtaRepository = ICOtaRepository() + icOtaRepository.mNextRepository = carOtaRepository + icOtaRepository + } + + //关心的数据 + //升级数据 + val otaInfo = MutableLiveData() + + //下载进度,百分比 + val downloadProgress = MutableLiveData() + + //是否下载完成 + val currentActionState by lazy { + val mutableLiveData = MutableLiveData() + mutableLiveData.postValue(UpdateActionState.NONE) + mutableLiveData + } + + private var wifiConnected = false + + //删除包的状态 + fun checkNetWork(): Boolean { + var result: Boolean + wifiConnected = NetworkUtils.isWifiConnected(App.sContext) + Log.d(TAG, "WIFI CONNECTED == > $wifiConnected") + result = if (wifiConnected) { + isTipsWifiConnect.postValue(false) + wifiConnected + } else { + val isNetConnected = NetworkUtils.isNetConnected(App.sContext) + Log.d(TAG, "mobile net is connected == > $isNetConnected") + isNetConnected + } + if (!result) { + //无网络 + currentActionState.postValue(UpdateActionState.NO_NETWORK) + } + return result + } + + //检查更新,需要在子线程种完成 + fun checkUpdate(from: VersionCheckFrom) { + if (currentActionState.value == UpdateActionState.CHECKING) { + Log.d(TAG, "checking....") + return + } + if (!checkNetWork()) { + currentActionState.value = UpdateActionState.NO_NETWORK + return + } + currentActionState.postValue(UpdateActionState.CHECKING) + viewModelScope.launch { + try { + //检查更新 + val result: OtaInfo? = withContext(Dispatchers.IO) { + mFirstRepository.checkHasNewVersion() + } + //结果 + Log.d(TAG, "check result ==> $result") + otaInfo.postValue(result) + //如果不为null,弹出一个系统级的dialog,也就是在任何地方都可以弹出的 + currentActionState.postValue( + if (result == null) { + //已经是最新版本了 + UpdateActionState.LAST_VERSION + } else { + //如果是强制升级的,不等通知用户结果了直接升级 + if (result.isForce) { + Log.d(TAG, "force download data...") + //直接调用下载 + downloadPackage() + } else { + //不是UI来的,可以创建一个弹窗,否则不弹窗,非强制升级 + if (from != VersionCheckFrom.UI_CHECK) { + createNewVersionDialog(result.updateDesc ?: "无描述") + } + } + //有新版本 + UpdateActionState.NEW_VERSION + } + ) + } catch (e: Exception) { + Log.e(TAG, e.toString()) + } + } + } + + val downInfoResult = MutableLiveData() + + + /** + * 暂停下载 + */ + fun pauseDownload() { + if (currentActionState.value != UpdateActionState.DOWNLOADING) { + Log.d(TAG, "current is not download...") + return + } + currentActionState.postValue(UpdateActionState.DOWNLOAD_PAUSE) + viewModelScope.launch { + withContext(Dispatchers.IO) { + Log.d(TAG, "download pause...") + mFirstRepository.pauseDownload() + } + } + } + + val isTipsWifiConnect = MutableLiveData() + + + fun downloadPackage() { + if (currentActionState.value == UpdateActionState.DOWNLOADING) { + Log.d(TAG, "downloading...") + return + } + //判断size是否大于100M,如果大于100M的话那么就提示连接wifi进行下载 + try { + if (otaInfo.value?.packageSize!! > 100000000L && !wifiConnected) { + isTipsWifiConnect.postValue(true) + //提示用户连接wifi下载 + Log.d(TAG, "size == > ${otaInfo.value?.packageSize} please in wifi download...") + return + } + } catch (e: Exception) { + Log.d(TAG, "e", e) + } + currentActionState.postValue(UpdateActionState.DOWNLOADING) + viewModelScope.launch { + try { + val downResult = withContext(Dispatchers.IO) { + mFirstRepository.downloadPackage(mDownloadCallback) + } + if (downResult == null) { + currentActionState.postValue(UpdateActionState.ERROR) + } else { + downInfoResult.value = downResult + //下载成功以后,如果是强制升级的,直接走升级流程 + if (otaInfo.value?.isForce == true) { + Log.d(TAG, "download completed and force to update....") + //直接调用升级 + install() + } else { + currentActionState.postValue(UpdateActionState.DOWNLOADED) + } + } + } catch (e: java.lang.Exception) { + Log.e("e", e.toString()) + } + } + } + + /** + * 下载的回调 + */ + private val mDownloadCallback = object : IOtaRepositoryCallback { + + override fun onDownloadProgressChange(currentProgress: Long, totalProgress: Long) { + //处理一下进度 + val progress: Int = (currentProgress * 100.0f / totalProgress + 0.5f).toInt() + Log.d(TAG, "progress == > $progress") + downloadProgress.postValue(progress) + } + + override fun onDownloadError(errorCode: Int, msg: String) { + Toast.makeText(App.sContext, "errorCode:$errorCode", Toast.LENGTH_LONG).show() + Log.d(TAG, "onDownloadError -- > $errorCode == > $msg") + if (errorCode == 0) { + //停止下载了,修改UI为继续下载 + currentActionState.postValue(UpdateActionState.DOWNLOAD_PAUSE) + } else { + //其他错误,都提示用户重启 + currentActionState.postValue(UpdateActionState.ERROR) + } + } + + override fun onDownloadSuccess() { + Log.d(TAG, "onDownloadSuccess...") + //下载完成了 + currentActionState.postValue(UpdateActionState.DOWNLOADED) + } + + override fun onDeletePackageSuccess() { + // + } + + override fun onDeletePackageFailed(errorCode: Int, msg: String) { + // + } + } + + /** + * 创建一个弹窗,直接添加到窗口中. + */ + private fun createNewVersionDialog(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) + if (TextUtils.isEmpty(desc)) { + descText.text = "没有更新说明..." + } else { + 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 + yesButton.setOnClickListener { + //下载安装包 + downloadPackage() + //跳转到下载界面 + val targetIntent = Intent(view.context, SystemUpdateActivity::class.java) + targetIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + view.context.startActivity(targetIntent) + manager.removeView(view) + } + noButton.setOnClickListener { manager.removeView(view) } + manager.addView(view, layoutParams) + } + + /** + * 获取当前的版本号 + */ + fun getCurrentVersionCode(): String? { + return VersionInfoUtils.getSystemVersion() + } + + fun getImeiCode(): String? { + return VersionInfoUtils.getIMEI() + } + + /** + * 更新版本 + */ + fun install() { + viewModelScope.launch { + try { + //判断电量多少,如果低于50%不进行安装 + val batteryStatus: Intent? = + IntentFilter(Intent.ACTION_BATTERY_CHANGED).let { filter -> + App.sContext.registerReceiver(null, filter) + } + val batteryPct: Float? = batteryStatus?.let { intent -> + val level: Int = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) + val scale: Int = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1) + level * 100f / scale.toFloat() + } + Log.d(TAG, "batteryPct == > $batteryPct") + if (batteryPct!! >= 50.0f) { + Log.d(TAG, "otaInfo.value!!.versionCode == > " + otaInfo.value!!.versionCode) + Log.d(TAG, "save target update version.") + //保存一个SP值,升级完了检查,用于通知服务器更新成功了. + val sharePreference = SPUtil.getSharePreference() + sharePreference.edit() + .putString(SPUtil.KEY_TARGET_VERSION_CODE, otaInfo.value!!.versionCode) + .apply() + //这里后面要改成我们自己的升级,而不是apk的升级方式 + withContext(Dispatchers.IO) { + //进行电量检查 + val path = downInfoResult.value!!.path; + //val path = "/data/media/0/sl8541e_1h10_oversea-ota-04210.zip" + val tempFile = File(path) + Log.d(TAG, "tempFile.exists() == > ${tempFile.exists()}") + Log.d(TAG, "install path is == > $path") + //先验证,再安装 + try { + RecoverySystem.verifyPackage(File(path), null, null) + Log.d(TAG, "verifyPackageFinished...") + RecoverySystem.installPackage( + App.sContext, + File(path) + ) + } catch (e: Exception) { + e.printStackTrace() + } + } + } else { + Log.d(TAG, "battery level is not enough...") + //电量不够,不进行升级 + currentActionState.postValue(UpdateActionState.BATTERY_LEVEL_NOT_ENOUGH) + } + } catch (e: Exception) { + Log.e("e", e.toString()) + } + } + } + + /** + * 检查有没有更新成了,通过检查SP即可 + */ + fun checkHasUpdated() { + val sharePreference = SPUtil.getSharePreference() + val versionRecord = sharePreference.getString(SPUtil.KEY_TARGET_VERSION_CODE, "") + //获取当前的系统版本号 + val systemVersion = VersionInfoUtils.getSystemVersion() + Log.d(TAG, "system version code is == > $systemVersion") + Log.d(TAG, "versionRecord code is == > $versionRecord") + if (TextUtils.isEmpty(versionRecord)) { + if (versionRecord == systemVersion) { + try { + viewModelScope.launch { + withContext(Dispatchers.IO) { + Log.d(TAG, "post install result..success.") + mFirstRepository.reportInstallResult(true) + mFirstRepository.reportResult(versionRecord!!) + //删除文件 + mFirstRepository.deletePackage() + } + } + } catch (e: Exception) { + e.printStackTrace() + } + } else { + try { + viewModelScope.launch { + withContext(Dispatchers.IO) { + Log.d(TAG, "post install result..failed.") + mFirstRepository.reportInstallResult(false) + mFirstRepository.reportResult(versionRecord!!) + } + } + } catch (e: Exception) { + e.printStackTrace() + } + } + sharePreference.edit().putString(SPUtil.KEY_TARGET_VERSION_CODE, "").apply() + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/ic/icfotaclient/repository/CarOtaRepository.kt b/app/src/main/java/com/ic/icfotaclient/repository/CarOtaRepository.kt new file mode 100644 index 0000000..a391552 --- /dev/null +++ b/app/src/main/java/com/ic/icfotaclient/repository/CarOtaRepository.kt @@ -0,0 +1,118 @@ +package com.ic.icfotaclient.repository + +import android.os.Bundle +import android.util.Log +import com.ic.icfotaclient.BuildConfig +import com.ic.icfotaclient.base.IOtaBaseRepository +import com.ic.icfotaclient.bean.DownloadInfo +import com.ic.icfotaclient.bean.OtaInfo +import com.ic.icfotaclient.presenter.OtaViewModel +import com.ic.icfotaclient.utils.CarOtaApiUtils +import com.ic.icfotaclient.utils.VersionInfoUtils +import com.ota.fw.IDownloadEvent +import com.ota.fw.SysInfo +import java.io.BufferedInputStream +import java.io.BufferedOutputStream +import java.io.FileInputStream +import java.io.FileOutputStream + +/** + * 科洛达的Repository + */ +class CarOtaRepository : IOtaBaseRepository { + + companion object { + const val TAG = "CarOtaRepository" + } + + private var mCallback: IOtaRepositoryCallback? = null + + override suspend fun checkHasNewVersion(): OtaInfo? { + val systemInfo = SysInfo( + BuildConfig.token, + VersionInfoUtils.getIMEI(), + VersionInfoUtils.getSystemVersion(), + BuildConfig.brandName, + VersionInfoUtils.getOtaProductName() + ) + val checkResult = CarOtaApiUtils.mClient.check(systemInfo) ?: return null + Log.d(TAG, "checkResult == > $checkResult") + //转成我们的bean类 + return OtaInfo( + checkResult.mode != 0,//0表示用户执行,1/2表示强制下载/强制更新 + checkResult.releaseNote, + checkResult.targetVersion, + checkResult.size, + System.currentTimeMillis()//科洛达的没有时间这项 + ) + } + + override suspend fun downloadPackage(callback: IOtaRepositoryCallback): DownloadInfo? { + mCallback = callback + val download = CarOtaApiUtils.mClient.download(object : IDownloadEvent { + override fun onDownloading(downloaded: Long, total: Long, extra: Bundle?) { + Log.d(TAG, "onDownloading...$downloaded -- > $total") + mCallback?.onDownloadProgressChange(downloaded, total) + } + + override fun onDownloaded(extra: Bundle?) { + Log.d(TAG, "onDownloaded...$extra") + mCallback?.onDownloadSuccess() + } + + override fun onError(errCode: Int, extra: Bundle?) { + Log.d(TAG, "onError...$errCode") + mCallback?.onDownloadError(errCode, "下载出错") + } + + override fun onRetry(code: Int) { + Log.d(TAG, "onRetry...$code") + } + + }) ?: return null + //转一下 + Log.d(TAG, "download == > ${download.file}") + val targetFile = VersionInfoUtils.getFlashPath() + Log.d(OtaViewModel.TAG, "copy finished to target path == > $targetFile") + val fis = FileInputStream(targetFile); + val fos = FileOutputStream(targetFile) + //复制到对应的目录下进行 + val bis = BufferedInputStream(fis) + val bos = BufferedOutputStream(fos) + val buf = ByteArray(1024) + var len = 0; + while (true) { + len = bis.read(buf) + if (len == -1) break; + bos.write(buf, 0, len) + } + fis.close() + fos.close() + val downloadInfo = DownloadInfo(download.file.path, 0L) + downloadInfo.path = targetFile + return downloadInfo + } + + override suspend fun reportInstallResult(isSuccess: Boolean) { + CarOtaApiUtils.mClient.reportUpdateResult(isSuccess, 0) + Log.d(TAG, "report result == isSuccess == > $isSuccess") + } + + override suspend fun reportResult(versionCode: String) { + CarOtaApiUtils.mClient.reportUpdateResultByVersion(versionCode) + Log.d(TAG, "report == versionCode == > $versionCode") + } + + override suspend fun getPackagePath(): String? { + return null + } + + override suspend fun deletePackage() { + //传说他们自己会删除的,通报了成功以后 + } + + override suspend fun pauseDownload() { + Log.d(TAG, "pauseDownload...") + CarOtaApiUtils.mClient.stopDownload() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ic/icfotaclient/repository/ICOtaRepository.kt b/app/src/main/java/com/ic/icfotaclient/repository/ICOtaRepository.kt new file mode 100644 index 0000000..ea4a896 --- /dev/null +++ b/app/src/main/java/com/ic/icfotaclient/repository/ICOtaRepository.kt @@ -0,0 +1,87 @@ +package com.ic.icfotaclient.repository + +import android.util.Log +import android.widget.Toast +import com.ic.icfotaclient.App +import com.ic.icfotaclient.R +import com.ic.icfotaclient.base.AbstractOtaRepository +import com.ic.icfotaclient.bean.DownloadInfo +import com.ic.icfotaclient.bean.OtaInfo +import com.ic.icfotaclient.utils.ICOtaApiUtils +import com.ic.icfotaclient.utils.VersionInfoUtils +import java.io.File + +/** + * 有不服气,可以自己复写,默认是交给下一级Repository处理。 + * 比如说英卡的OTA,目前需要去统计一下数据,去调用一下检查更新即可。不需要做其他的工作。 + */ +class ICOtaRepository : AbstractOtaRepository() { + + + private var checkIcVersion: OtaInfo? = null + + /** + * 检查是否有更新,英卡的服务器 + */ + override suspend fun checkHasNewVersion(): OtaInfo? { + try { + //调用一下英卡的接口 + checkIcVersion = ICOtaApiUtils.checkIcVersion() + if (checkIcVersion != null) { + return checkIcVersion + } + } catch (e: Exception) { + Log.e("e==>", e.toString()) + } + //如果没有则走下一级服务器 + return super.checkHasNewVersion() + } + + + override suspend fun pauseDownload() { + if (checkIcVersion != null) { + Toast.makeText( + App.sContext, + App.sContext.getString(R.string.unsupport_pause_download), + Toast.LENGTH_LONG + ).show() + } + super.pauseDownload() + } + + + override suspend fun downloadPackage(callback: IOtaRepositoryCallback): DownloadInfo? { + //下载安装包,直接当前线程调用即可 + //如果这个有安装包就不需要往下去下载了。 + if (checkIcVersion != null) { + //说明是走英卡的服务器更新的,从英卡服务器下载更新包 + return ICOtaApiUtils.downloadPackage(callback) + } + return super.downloadPackage(callback) + } + + override suspend fun reportResult(versionCode: String) { + if (checkIcVersion != null) { + return + } + super.reportResult(versionCode) + } + + + override suspend fun reportInstallResult(isSuccess: Boolean) { + if (checkIcVersion != null) { + return + } + super.reportInstallResult(isSuccess) + } + + override suspend fun deletePackage() { + //删除flash_root下的 + val file = File(VersionInfoUtils.getFlashPath()) + if (file.exists()) { + file.delete() + } + super.deletePackage() + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/ic/icfotaclient/repository/IOtaRepositoryCallback.kt b/app/src/main/java/com/ic/icfotaclient/repository/IOtaRepositoryCallback.kt new file mode 100644 index 0000000..0eab794 --- /dev/null +++ b/app/src/main/java/com/ic/icfotaclient/repository/IOtaRepositoryCallback.kt @@ -0,0 +1,29 @@ +package com.ic.icfotaclient.repository + +interface IOtaRepositoryCallback { + + /** + * 下载进度 + */ + fun onDownloadProgressChange(currentProgress: Long, totalProgress: Long) + + /** + * 0-停止下载 -1-连接错误 -2-下载中断 -3-下载文件校验失败 -4-其他错误 -5-文件校验失败 + */ + fun onDownloadError(errorCode: Int, msg: String) + + /** + * 下载成功 + */ + fun onDownloadSuccess() + + /** + * 删除升级包成功 + */ + fun onDeletePackageSuccess() + + /** + * 删除升级包失败 + */ + fun onDeletePackageFailed(errorCode: Int, msg: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/ic/icfotaclient/utils/CarOtaApiUtils.kt b/app/src/main/java/com/ic/icfotaclient/utils/CarOtaApiUtils.kt new file mode 100644 index 0000000..21ae589 --- /dev/null +++ b/app/src/main/java/com/ic/icfotaclient/utils/CarOtaApiUtils.kt @@ -0,0 +1,19 @@ +package com.ic.icfotaclient.utils + +import android.util.Log +import com.ic.icfotaclient.App +import com.ota.fw.FotaClient +import com.ota.fw.IHookInstall + +object CarOtaApiUtils { + + val mClient by lazy { + FotaClient.buildBasic(App.sContext, Constants.OTA_URL, null, IHookInstall { context, file -> + Log.d( + "CarOtaApiUtils", "install success == > " + file.path + ) + 1//这个值是自定义值,在install的时候会返回 + }) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/ic/icfotaclient/utils/Constants.kt b/app/src/main/java/com/ic/icfotaclient/utils/Constants.kt new file mode 100644 index 0000000..2230469 --- /dev/null +++ b/app/src/main/java/com/ic/icfotaclient/utils/Constants.kt @@ -0,0 +1,12 @@ +package com.ic.icfotaclient.utils + +object Constants { + const val CMD_ACTION_BOOT_COMPLETED = "cmd_action_boot_completed" + const val CMD_ACTION_ACTIVITY_START = "cmd_action_activity_start" + const val OTA_URL = "https://api.ota.limios.net/conn/api/v1/" + + //收到开机广播后,10秒钟以后,再去检查系统更新 + const val TIME_DELAY_BOOT = 10 * 1000L + + +} \ No newline at end of file diff --git a/app/src/main/java/com/ic/icfotaclient/utils/ICOtaApiUtils.kt b/app/src/main/java/com/ic/icfotaclient/utils/ICOtaApiUtils.kt new file mode 100644 index 0000000..551cb1c --- /dev/null +++ b/app/src/main/java/com/ic/icfotaclient/utils/ICOtaApiUtils.kt @@ -0,0 +1,187 @@ +package com.ic.icfotaclient.utils + +import android.util.Log +import com.google.gson.Gson +import com.ic.icfotaclient.App +import com.ic.icfotaclient.R +import com.ic.icfotaclient.bean.DownloadInfo +import com.ic.icfotaclient.bean.ICOtaInfo +import com.ic.icfotaclient.bean.OtaInfo +import com.ic.icfotaclient.repository.IOtaRepositoryCallback +import okhttp3.OkHttpClient +import okhttp3.Request +import java.io.BufferedInputStream +import java.io.BufferedOutputStream +import java.io.FileOutputStream +import java.net.HttpURLConnection +import java.net.URI +import java.security.SecureRandom +import java.security.cert.X509Certificate +import java.util.concurrent.TimeUnit +import javax.net.ssl.SSLContext +import javax.net.ssl.TrustManager +import javax.net.ssl.X509TrustManager + +object ICOtaApiUtils { + + val TAG = "ICOtaApiUtils" + + fun checkIcVersion(): OtaInfo? { + val uri = URI(getRemoteUri()) + val url = uri.toString() + return getUpdateInfo(url) + } + + private fun initOkHttp() { + //首先创建OkHttpClient.build进行信任所有证书配置 + //首先创建OkHttpClient.build进行信任所有证书配置 + val okhttpClient = OkHttpClient().newBuilder() + //信任所有服务器地址 + //信任所有服务器地址 + okhttpClient.hostnameVerifier { s, sslSession -> //设置为true + true + } + //创建管理器 + //创建管理器 + val trustAllCerts = + arrayOf(object : X509TrustManager { + override fun checkClientTrusted( + x509Certificates: Array, + s: String + ) { + } + + override fun checkServerTrusted( + x509Certificates: Array, + s: String + ) { + } + + override fun getAcceptedIssuers(): Array { + return arrayOf() + } + }) + try { + val sslContext = SSLContext.getInstance("TLS") + sslContext.init(null, trustAllCerts, SecureRandom()) + + //为OkHttpClient设置sslSocketFactory + okhttpClient.sslSocketFactory(sslContext.socketFactory) + } catch (e: Exception) { + e.printStackTrace() + } + okHttpClient = okhttpClient + .connectTimeout(100000, TimeUnit.SECONDS) + .writeTimeout(100000, TimeUnit.SECONDS) + .readTimeout(100000, TimeUnit.SECONDS) + .build() + } + + private var okHttpClient: OkHttpClient? = null + + private var otaInfo: ICOtaInfo? = null + + private fun getUpdateInfo( + url: String? + ): OtaInfo? { + if (okHttpClient == null) { + initOkHttp() + } + val request: Request = Request.Builder().url(url).build() + + val execute = okHttpClient?.newCall(request)?.execute() + //解析数据 + if (execute != null && execute.code() == HttpURLConnection.HTTP_OK) { + val body = execute.body() ?: return null + val json = body.string() + Log.d(TAG, "result == > $json") + val gson = Gson() + otaInfo = gson.fromJson(json, ICOtaInfo::class.java) + if (otaInfo != null) { + return OtaInfo( + otaInfo!!.AutoUpdate, + otaInfo!!.description, + otaInfo!!.OtaPackageVersion, + otaInfo!!.OtaPackageLength.toLong(), + System.currentTimeMillis() + ) + } + } + return null + } + + private fun getRemoteUri(): String? { + var server: String = getRemoteHost() + val productname: String? = VersionInfoUtils.getOtaProductName() + val pkgversion: String? = VersionInfoUtils.getSystemVersion() + val imei: String? = VersionInfoUtils.getIMEI() + if (server.startsWith("http://")) { + server = server.replace("http://", "") + } + //为了兼容之前的只设置ip和端口的设置方式 + var actionUrl = + "http://$server/ota.jsp?product=$productname&pkgver=$pkgversion&imei=$imei" + if (server.contains("/")) { //此时说明不是ip地址,而是个url + actionUrl = + "http://$server?product=$productname&pkgver=$pkgversion&imei=$imei" + } + Log.d(TAG, "actionUrl == > $actionUrl") + return actionUrl + } + + + private fun getRemoteHost(): String { + var remoteHost: String? = + SysPropUtils.get("persist.sys.ota.host", null) + if (remoteHost == null || remoteHost.isEmpty() || remoteHost == "unknown") { + remoteHost = "icfota.snicker.com.cn:8089" + } + Log.d(TAG, "remoteHost == > $remoteHost") + return remoteHost + } + + /** + * 下载更新包 + */ + fun downloadPackage( + callback: IOtaRepositoryCallback? + ): DownloadInfo? { + if (okHttpClient == null) { + initOkHttp() + } + if (otaInfo == null) { + return null + } + val request: Request = Request.Builder().url( + otaInfo!!.url + ).build() + try { + val total = otaInfo!!.OtaPackageLength + val response = okHttpClient?.newCall(request)?.execute() + if (response?.code() == HttpURLConnection.HTTP_OK) { + val inputStream = response.body()?.byteStream() + val outPutStream = FileOutputStream(VersionInfoUtils.getFlashPath()) + val bis = BufferedInputStream(inputStream) + val bos = BufferedOutputStream(outPutStream) + val buf = ByteArray(1024) + var len = 0; + var current = 0; + while (true) { + len = bis.read(buf) + if (len == -1) break; + current += len; + bos.write(buf, 0, len) + callback?.onDownloadProgressChange(current.toLong(), total.toLong()) + } + inputStream?.close() + outPutStream.close() + } + callback?.onDownloadSuccess() + return DownloadInfo(VersionInfoUtils.getFlashPath(), total.toLong()) + } catch (e: Exception) { + e.printStackTrace() + callback?.onDownloadError(0, App.sContext.getString(R.string.error_tips_text)) + } + return null + } +} diff --git a/app/src/main/java/com/ic/icfotaclient/utils/NetworkUtils.kt b/app/src/main/java/com/ic/icfotaclient/utils/NetworkUtils.kt new file mode 100644 index 0000000..cad9b16 --- /dev/null +++ b/app/src/main/java/com/ic/icfotaclient/utils/NetworkUtils.kt @@ -0,0 +1,76 @@ +package com.ic.icfotaclient.utils + +import android.Manifest.permission.ACCESS_NETWORK_STATE +import android.content.Context +import android.net.ConnectivityManager +import android.net.NetworkCapabilities +import android.os.Build +import androidx.annotation.RequiresPermission + + +/** + * 网络状态工具类,用于获取网络状态 + */ +object NetworkUtils { + + /** + * 网络是否已连接 + * + * @return true:已连接 false:未连接 + */ + fun isNetConnected(context: Context): Boolean { + val manager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val networkCapabilities = manager.getNetworkCapabilities(manager.activeNetwork) + if (networkCapabilities != null) { + return (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) + || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) + || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) + } + } else { + val networkInfo = manager.activeNetworkInfo + return networkInfo != null && networkInfo.isConnected + } + return false + } + + /** + * Wifi是否已连接 + * + * @return true:已连接 false:未连接 + */ + fun isWifiConnected(context: Context): Boolean { + val manager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + if (manager != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val networkCapabilities = manager.getNetworkCapabilities(manager.activeNetwork) + if (networkCapabilities != null) { + return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) + } + } else { + val networkInfo = manager.activeNetworkInfo + return networkInfo != null && networkInfo.isConnected && networkInfo.type == ConnectivityManager.TYPE_WIFI + } + } + return false + } + + /** + * 是否为流量 + */ + fun isMobileData(context: Context): Boolean { + val manager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + if (manager != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val networkCapabilities = manager.getNetworkCapabilities(manager.activeNetwork) + if (networkCapabilities != null) { + return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) + } + } else { + val networkInfo = manager.activeNetworkInfo + return networkInfo != null && networkInfo.isConnected && networkInfo.type == ConnectivityManager.TYPE_MOBILE + } + } + return false + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ic/icfotaclient/utils/OtaViewModelCreator.kt b/app/src/main/java/com/ic/icfotaclient/utils/OtaViewModelCreator.kt new file mode 100644 index 0000000..6b544b6 --- /dev/null +++ b/app/src/main/java/com/ic/icfotaclient/utils/OtaViewModelCreator.kt @@ -0,0 +1,62 @@ +package com.ic.icfotaclient.utils + +import android.util.Log +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelStoreOwner +import com.ic.icfotaclient.presenter.OtaViewModel + +/** + * 获取OtaViewModel,生命周期与OtaService挂钩 + * 可以理解为一个单例 + */ +object OtaViewModelCreator { + + const val TAG = "OtaViewModelCreator" + + lateinit var mVmStore: ViewModelStoreOwner + + private var viewModel: OtaViewModel? = null + + + /** + * 首先要把owner设置进来 + */ + fun initOtaViewModelStoreOwner(vmStore: ViewModelStoreOwner) { + mVmStore = vmStore + viewModel = ViewModelProvider(mVmStore).get(OtaViewModel::class.java) + for (onViewModelInitListener in callbackList) { + onViewModelInitListener.onViewModelInit(viewModel) + } + callbackList.clear() + } + + private val callbackList = arrayListOf() + + /** + * 获取OtaViewModel,有可能为null,如果OtaService退出的话,会清掉的 + * callback可以为null,如果为null,则不通知结果,但是会返回。 + * 建议界面调用的时候,通过接口获取viewModel + */ + fun getOtaViewModel(callback: OnViewModelInitListener?): OtaViewModel? { + //可以传null + if (!this::mVmStore.isInitialized) { + Log.d( + TAG, + "you must call initOtaViewModelStoreOwner() first to setup ViewModelStoreOwner" + ) + //没有初始化,如果callback不为null,添加到集合里 + if (callback != null) { + callbackList.add(callback) + } + } else { + //如果有接口设置过来,那就接口也返回一下 + callback?.onViewModelInit(viewModel) + } + return viewModel + } + + interface OnViewModelInitListener { + fun onViewModelInit(otaViewModel: OtaViewModel?); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/ic/icfotaclient/utils/SPUtil.kt b/app/src/main/java/com/ic/icfotaclient/utils/SPUtil.kt new file mode 100644 index 0000000..a35e391 --- /dev/null +++ b/app/src/main/java/com/ic/icfotaclient/utils/SPUtil.kt @@ -0,0 +1,17 @@ +package com.ic.icfotaclient.utils + +import android.content.Context +import android.content.SharedPreferences +import com.ic.icfotaclient.App + +object SPUtil { + + + const val SP_NAME = "update_record" + const val KEY_TARGET_VERSION_CODE = "key_target_version_code" + + fun getSharePreference(): SharedPreferences { + return App.sContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/ic/icfotaclient/utils/SysPropUtils.kt b/app/src/main/java/com/ic/icfotaclient/utils/SysPropUtils.kt new file mode 100644 index 0000000..92e2d41 --- /dev/null +++ b/app/src/main/java/com/ic/icfotaclient/utils/SysPropUtils.kt @@ -0,0 +1,54 @@ +package com.ic.icfotaclient.utils + +import java.lang.reflect.InvocationTargetException +import java.lang.reflect.Method + +/** + * 系统属性获取的工具 + */ +object SysPropUtils { + + private var sysPropGet: Method? = null + private var sysPropSet: Method? = null + + init { + try { + val forName = Class.forName("android.os.SystemProperties") + val methods = forName.methods + for (method in methods) { + if ("get" == method.name) { + sysPropGet = method + } else { + sysPropSet = method + } + } + } catch (e: ClassNotFoundException) { + e.printStackTrace() + } + } + + operator fun get(name: String?, default_value: String?): String? { + try { + return sysPropGet!!.invoke(null, name, default_value) as String + } catch (e: IllegalArgumentException) { + e.printStackTrace() + } catch (e: IllegalAccessException) { + e.printStackTrace() + } catch (e: InvocationTargetException) { + e.printStackTrace() + } + return default_value + } + + operator fun set(name: String?, value: String?) { + try { + sysPropSet!!.invoke(null, name, value) + } catch (e: IllegalArgumentException) { + e.printStackTrace() + } catch (e: IllegalAccessException) { + e.printStackTrace() + } catch (e: InvocationTargetException) { + e.printStackTrace() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ic/icfotaclient/utils/UpdateActionState.kt b/app/src/main/java/com/ic/icfotaclient/utils/UpdateActionState.kt new file mode 100644 index 0000000..cafcb33 --- /dev/null +++ b/app/src/main/java/com/ic/icfotaclient/utils/UpdateActionState.kt @@ -0,0 +1,14 @@ +package com.ic.icfotaclient.utils + +enum class UpdateActionState { + NONE,/*初始状态,显示:检查更新,isEnable = true*/ + CHECKING,/*正在检查更新,显示:正在检查更新 isEnable = false*/ + NEW_VERSION,/*显示:下载新版本 isEnable = true*/ + LAST_VERSION,/*显示:已是最新版本,isEnable = false*/ + DOWNLOADING,/*显示暂停下载 isEnable = true*/ + DOWNLOADED,/*显示:安装,isEnable = true*/ + DOWNLOAD_PAUSE,/*显示继续下载,isEnable=true*/ + NO_NETWORK,/*显示无网络,isEnable=false*/ + ERROR,/*恢复默认,显示检查更新 isEnable=false*/ + BATTERY_LEVEL_NOT_ENOUGH,/*电池电量不足50%,isEnable=false*/ +} \ No newline at end of file diff --git a/app/src/main/java/com/ic/icfotaclient/utils/VersionCheckFrom.kt b/app/src/main/java/com/ic/icfotaclient/utils/VersionCheckFrom.kt new file mode 100644 index 0000000..78544c4 --- /dev/null +++ b/app/src/main/java/com/ic/icfotaclient/utils/VersionCheckFrom.kt @@ -0,0 +1,12 @@ +package com.ic.icfotaclient.utils + +enum class VersionCheckFrom { + /*远程调用检查*/ + REMOTE_CHECK, + + /*UI检查更新时的check*/ + UI_CHECK, + + /*开机检查的check*/ + BOOT_CHECK +} \ No newline at end of file diff --git a/app/src/main/java/com/ic/icfotaclient/utils/VersionInfoUtils.kt b/app/src/main/java/com/ic/icfotaclient/utils/VersionInfoUtils.kt new file mode 100644 index 0000000..e7b21b4 --- /dev/null +++ b/app/src/main/java/com/ic/icfotaclient/utils/VersionInfoUtils.kt @@ -0,0 +1,98 @@ +package com.ic.icfotaclient.utils + +import android.annotation.SuppressLint +import android.content.Context +import android.os.Build +import android.telephony.TelephonyManager +import android.text.TextUtils +import android.util.Log +import com.ic.icfotaclient.App +import java.io.File + +/** + * 工具类 + * 获取IMEI号 + * 获取版本号 + * 获取版本名称 + */ +object VersionInfoUtils { + + const val TAG = "VersionInfoUtils" + + private const val KeyIMEI = "persist.sys.protocol_IMEI" + + /** + * 获取IMEI号 + */ + //获取手机IMEI号 + @SuppressLint("MissingPermission", "HardwareIds") + fun getIMEI(): String? { + //优先从Pro中获取,有可能通过短信修改了IMEI号 + var imei: String? = SysPropUtils[KeyIMEI, ""] + if (TextUtils.isEmpty(imei)) { + imei = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + (App.sContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager).imei + } else { + (App.sContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager).deviceId + } + } + Log.d("imei", "imei = > $imei") + return imei + } + + + fun getOtaProductName(): String? { + var productName: String = + //C41S-D-EU-P2 + SysPropUtils.get("ro.product.model", "") as String + if (productName.contains(" ")) { + productName = productName.replace(" ".toRegex(), "") + } + Log.d(TAG, "product name -= > $productName") + return productName + } + + fun getSystemVersion(): String? { + var version: String = + //C41S-D-EU-P2-V0.3.37.20210112.184146 + SysPropUtils.get("ro.build.display.id", "") as String + if (version.isEmpty()) { + version = "1.0.0" + } + Log.d(TAG, "system version is == > $version") + return version + } + + + private fun getMultiUserState(): Boolean { + var multiUser: String? = + SysPropUtils.get("ro.factory.hasUMS", "") + if (multiUser != null && multiUser.isNotEmpty()) { + return multiUser != "true" + } + multiUser = SysPropUtils.get("ro.factory.storage_policy", "") + if (multiUser != null && multiUser.isNotEmpty()) { + return multiUser == "1" + } + val property = SysPropUtils.get("ro.boot.hardware", "") + return property != null && property.contains("sl8541e") + } + + + var flash_root = + "/data" //Environment.getExternalStorageDirectory().getAbsolutePath(); + + var DATA_ROOT = "/data/media/0" + + private const val updateFileName = "update.zip" + + + fun getFlashPath(): String { + //移动到对应的位置上 + if (getMultiUserState()) { + flash_root = DATA_ROOT + } + + return flash_root + File.separator + updateFileName + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ic/icfotaclient/view/ApiTestActivity.kt b/app/src/main/java/com/ic/icfotaclient/view/ApiTestActivity.kt new file mode 100644 index 0000000..a297703 --- /dev/null +++ b/app/src/main/java/com/ic/icfotaclient/view/ApiTestActivity.kt @@ -0,0 +1,109 @@ +package com.ic.icfotaclient.view + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.ic.icfotaclient.BuildConfig +import com.ic.icfotaclient.R + +import com.ic.icfotaclient.utils.VersionInfoUtils +import com.ota.fw.FotaClient +import com.ota.fw.IDownloadEvent +import com.ota.fw.IUpdateInfo +import com.ota.fw.SysInfo + +class ApiTestActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_api_test) + //测试api + val client = + FotaClient.buildBasic(this, "http://api.ota.limios.net/conn/api/v1/", { + //网络是否可用? + true + }, { context, file -> + //context-上下文 + //path-安装包路径 + //调用 FotaClient. install 后,经过包二次校验,状态回报等操作后,就会调用到此接口来进行升级操 + //作。返回值可以自定义,最终通过 FotaClient. install 返回 + println("context == > $context") + println("file == > $file") + 0//这个值会返回到install里 + }) + + //SysInfo(String projectToken, String serialNum, String srcVer, String brand, String model, Bundle extra) + //serialNum-设备唯一识别码(IMEI 或 sn 号或其他参数) + //srcVer-设备当前版本号 + //brand-设备 brand + //model-设备 model + //extra-设备额外信息,不设置可传 null + //projectToken-项目 token,后端生成,由科络达分配给客户 + //检查更新 + val sysInfo = SysInfo( + BuildConfig.token, VersionInfoUtils.getIMEI(), + VersionInfoUtils.getSystemVersion(), "JX_Dev", "JX_Default", null + ) + //如果这里面不为null + /** + * public interface IUpdateInfo { + * String getReleaseNote(); //获取更新包升级信息 + * String getTargetVersion(); //获取目标版本号 + * long getSize(); //获取升级包大小 + * } + */ + val check: IUpdateInfo = client.check(sysInfo) + + + //下载 + client.download(object : IDownloadEvent { + override fun onDownloading(downloaded: Long, total: Long, extra: Bundle?) { + //下载中 + } + + override fun onDownloaded(extra: Bundle?) { + //下载完成 + } + + /** + * 0-停止下载 + * 1-连接错误 + * 2-下载中断 + * 3-下载文件校验失败 + * 4-其他错误 + * 5-文件校验失败 + */ + override fun onError(errCode: Int, extra: Bundle?) { + //下载出错 + } + + override fun onRetry(p0: Int) { + + } + + }) + + //停止下载 + client.stopDownload() + + //安装 + //返回值-根据升级方法的实现,该方法可能无法返回。默认返回值如下: + //-101-设备调用重启时发生异常 + //-1001-一般错误 + //-1004-升级包 MD5 校验失败 + //其他值-IHookInstall.installPackage 返回值 + //启动升级包安装流程,同步接口,需要在非主线程执行 + val install = client.install() + + //通知服务器安装结果 + //TODO: + + //success-升级是否成功 + //code-升级失败时候的错误码,默认传 0 + client.reportUpdateResult(true, 0) + + //curVer-当前版本号 + client.reportUpdateResultByVersion(VersionInfoUtils.getSystemVersion()) + + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ic/icfotaclient/view/SystemUpdateActivity.kt b/app/src/main/java/com/ic/icfotaclient/view/SystemUpdateActivity.kt new file mode 100644 index 0000000..4c8ea59 --- /dev/null +++ b/app/src/main/java/com/ic/icfotaclient/view/SystemUpdateActivity.kt @@ -0,0 +1,170 @@ +package com.ic.icfotaclient.view + +import android.content.Intent +import android.os.Bundle +import android.text.TextUtils +import android.util.Log +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Observer +import com.ic.icfotaclient.R +import com.ic.icfotaclient.presenter.OtaService +import com.ic.icfotaclient.presenter.OtaViewModel +import com.ic.icfotaclient.utils.Constants +import com.ic.icfotaclient.utils.OtaViewModelCreator +import com.ic.icfotaclient.utils.UpdateActionState +import com.ic.icfotaclient.utils.VersionCheckFrom +import kotlinx.android.synthetic.main.activity_main.* + +class SystemUpdateActivity : AppCompatActivity() { + + override fun onBackPressed() { + //不让退出了哇 + val checkNetWork = OtaViewModelCreator.getOtaViewModel(null)?.checkNetWork() + val currentActionState = OtaViewModelCreator.getOtaViewModel(null)?.currentActionState + val tipsWifiConnect = OtaViewModelCreator.getOtaViewModel(null)?.isTipsWifiConnect + if (checkNetWork != true || + currentActionState?.value == UpdateActionState.LAST_VERSION + || tipsWifiConnect?.value == false + ) { + super.onBackPressed() + } + } + + override fun onResume() { + super.onResume() + //检查一下网络更新 + val otaViewModel = OtaViewModelCreator.getOtaViewModel(null) + otaViewModel?.checkNetWork() + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + val intent = Intent(this, OtaService::class.java) + intent.action = Constants.CMD_ACTION_ACTIVITY_START + startService(intent) + checkUpdate.setOnClickListener { + val otaViewModel = OtaViewModelCreator.getOtaViewModel(null) + if (otaViewModel != null) { + //判断状态,执行不同的动作 + when (otaViewModel.currentActionState.value) { + UpdateActionState.NONE -> { + //显示检查更新,isEnable=true + //检查更新 + otaViewModel.checkUpdate(VersionCheckFrom.UI_CHECK) + } + UpdateActionState.NEW_VERSION -> { + //去下载 + otaViewModel.downloadPackage() + } + UpdateActionState.DOWNLOADED -> { + //去安装 + otaViewModel.install() + } + UpdateActionState.DOWNLOADING -> { + //暂停下载 + otaViewModel.pauseDownload() + } + UpdateActionState.DOWNLOAD_PAUSE -> { + //去下载 + otaViewModel.downloadPackage() + } + else -> { + //NONE + } + } + } + } + + OtaViewModelCreator.getOtaViewModel( + object : OtaViewModelCreator.OnViewModelInitListener { + override fun onViewModelInit(otaViewModel: OtaViewModel?) { + initObserver(otaViewModel) + } + }) + } + + private fun initObserver(otaViewModel: OtaViewModel?) { + otaViewModel?.apply { + val that = this@SystemUpdateActivity + otaInfo.observe(that, Observer { + if (it != null) { + updateText.text = getString(R.string.update_tips_text) + it.updateDesc + + } + }) + isTipsWifiConnect.observe(that, Observer { + if (it) { + checkUpdate.text = getString(R.string.tips_wifi_connect) + checkUpdate.isEnabled = false + } + }) + currentActionState.observe(that, Observer { it -> + Log.d("SystemUpdateActivity", "current state == > $it") + when (it) { + UpdateActionState.NONE -> { + //显示检查更新,isEnable=true + checkUpdate.text = getString(R.string.check_has_update_text) + checkUpdate.isEnabled = true + } + UpdateActionState.CHECKING -> { + checkUpdate.text = getString(R.string.try_to_checking) + checkUpdate.isEnabled = false + } + UpdateActionState.NEW_VERSION -> { + checkUpdate.text = getString(R.string.download_package) + checkUpdate.isEnabled = true + } + UpdateActionState.LAST_VERSION -> { + checkUpdate.text = getString(R.string.last_version_text) + checkUpdate.isEnabled = false + } + UpdateActionState.DOWNLOADING -> { + checkUpdate.text = getString(R.string.pause_download_text) + checkUpdate.isEnabled = true + } + UpdateActionState.DOWNLOAD_PAUSE -> { + checkUpdate.text = getString(R.string.continue_download_text) + checkUpdate.isEnabled = true + } + UpdateActionState.DOWNLOADED -> { + checkUpdate.text = getString(R.string.update_text) + checkUpdate.isEnabled = true + } + UpdateActionState.NO_NETWORK -> { + checkUpdate.text = getString(R.string.no_network_text) + checkUpdate.isEnabled = false + } + UpdateActionState.BATTERY_LEVEL_NOT_ENOUGH -> { + checkUpdate.text = getString(R.string.battery_level_tips_text) + checkUpdate.isEnabled = false + } + UpdateActionState.ERROR -> { + checkUpdate.text = getString(R.string.error_tips_text) + checkUpdate.isEnabled = false + } + else -> { + //NONE + } + } + }) + downloadProgress.observe(that, Observer { + downloadProgressBar.visibility = View.VISIBLE + //更新进度条,此为百分比,转成int,max=100 + downloadProgressBar.progress = it + }) + } + val currentVersionCode = otaViewModel?.getCurrentVersionCode() + if (!TextUtils.isEmpty(currentVersionCode)) { + versionTitle.visibility = View.VISIBLE + versionTitle.text = getString(R.string.version_tips) + currentVersionCode + } + val imeiCode = otaViewModel?.getImeiCode() + if (!TextUtils.isEmpty(imeiCode)) { + imeiCodeTv.visibility = View.VISIBLE + imeiCodeTv.text = getString(R.string.imei_text) + imeiCode + } + + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/selector_btn_bg.xml b/app/src/main/res/drawable/selector_btn_bg.xml new file mode 100644 index 0000000..b7e9829 --- /dev/null +++ b/app/src/main/res/drawable/selector_btn_bg.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_btn_disable.xml b/app/src/main/res/drawable/shape_btn_disable.xml new file mode 100644 index 0000000..54db3c6 --- /dev/null +++ b/app/src/main/res/drawable/shape_btn_disable.xml @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_btn_normal.xml b/app/src/main/res/drawable/shape_btn_normal.xml new file mode 100644 index 0000000..a6853b5 --- /dev/null +++ b/app/src/main/res/drawable/shape_btn_normal.xml @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_btn_press.xml b/app/src/main/res/drawable/shape_btn_press.xml new file mode 100644 index 0000000..b4e7579 --- /dev/null +++ b/app/src/main/res/drawable/shape_btn_press.xml @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_api_test.xml b/app/src/main/res/layout/activity_api_test.xml new file mode 100644 index 0000000..61a4490 --- /dev/null +++ b/app/src/main/res/layout/activity_api_test.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..4095cf0 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_tips.xml b/app/src/main/res/layout/dialog_tips.xml new file mode 100644 index 0000000..0039973 --- /dev/null +++ b/app/src/main/res/layout/dialog_tips.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + +