diff --git a/app/src/androidTest/assets/HelloActivity.apk b/app/src/androidTest/assets/HelloActivity.apk new file mode 100644 index 0000000000..a299fa2514 Binary files /dev/null and b/app/src/androidTest/assets/HelloActivity.apk differ diff --git a/app/src/androidTest/java/com/mogo/functions/test/ApkInstallerTest.kt b/app/src/androidTest/java/com/mogo/functions/test/ApkInstallerTest.kt new file mode 100644 index 0000000000..5c7bd770a2 --- /dev/null +++ b/app/src/androidTest/java/com/mogo/functions/test/ApkInstallerTest.kt @@ -0,0 +1,88 @@ +package com.mogo.functions.test + +import android.util.* +import androidx.test.core.app.* +import androidx.test.ext.junit.runners.* +import androidx.test.filters.* +import androidx.test.platform.app.InstrumentationRegistry +import com.mogo.eagle.core.function.hmi.ui.* +import com.mogo.eagle.core.function.main.* +import com.mogo.eagle.core.utilcode.util.* +import kotlinx.coroutines.* +import org.junit.* +import org.junit.runner.* +import java.util.concurrent.* +import java.util.concurrent.TimeUnit.MILLISECONDS +import kotlin.Result + +@RunWith(AndroidJUnit4::class) +@LargeTest +class ApkInstallerTest { + + + lateinit var launch: ActivityScenario + + @Before + fun before() { + launch = ActivityScenario.launch(MainLauncherActivity::class.java) + } + + @Test + fun testInstall(): Unit = runBlocking { + Log.d("RWJ", "wait fragment show ...") + val f = ensureMoGoHmiFragmentShow() + Log.d("RWJ", "fragment showed, delay 10s ...") + delay(10000) + + Log.d("RWJ", "10s end, start install ...") + val context = InstrumentationRegistry.getInstrumentation().context + ApkInstaller.installApp(f.requireContext(), context.assets.open("190000013.apk")) { code, msg -> + Log.d("RWJ", "code: $code, msg: $msg") + } + Log.d("RWJ", "开始延时10分钟....") + delay(TimeUnit.MINUTES.toMillis(10)) + Log.d("RWJ", "延时10分钟结束....") + } + + + @Test + fun testInstall2(): Unit = runBlocking { + Log.d("RWJ", "wait fragment show ...") + val f = ensureMoGoHmiFragmentShow() + Log.d("RWJ", "fragment showed, delay 10s ...") + delay(10000) + + Log.d("RWJ", "10s end, start install ...") + val context = InstrumentationRegistry.getInstrumentation().context + ApkInstaller.installApp(f.requireContext(), context.assets.open("HelloActivity.apk")) { code, msg -> + Log.d("RWJ", "code: $code, msg: $msg") + } + Log.d("RWJ", "开始延时10分钟....") + delay(TimeUnit.MINUTES.toMillis(10)) + Log.d("RWJ", "延时10分钟结束....") + } + + private suspend fun ensureMoGoHmiFragmentShow(): MoGoHmiFragment = suspendCancellableCoroutine { + launch.onActivity { itx -> + val executor = Executors.newSingleThreadScheduledExecutor() + executor.scheduleAtFixedRate({ + var find = + itx.supportFragmentManager.fragments.find { it is MoGoHmiFragment } as? MoGoHmiFragment + while (find == null) { + find = + itx.supportFragmentManager.fragments.find { it is MoGoHmiFragment } as? MoGoHmiFragment + } + while (!find.isResumed) { + Thread.sleep(500) + } + it.resumeWith(Result.success(find)) + try { + Thread.sleep(500) + executor.shutdownNow() + } catch (e: Throwable) { + e.printStackTrace() + } + }, 50, 500, MILLISECONDS) + } + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/mogo/functions/test/PatchUtilsTest.kt b/app/src/androidTest/java/com/mogo/functions/test/PatchUtilsTest.kt new file mode 100644 index 0000000000..c575038ce1 --- /dev/null +++ b/app/src/androidTest/java/com/mogo/functions/test/PatchUtilsTest.kt @@ -0,0 +1,89 @@ +package com.mogo.functions.test + +import androidx.test.core.app.* +import androidx.test.ext.junit.runners.* +import androidx.test.filters.* +import com.mogo.eagle.core.function.hmi.ui.* +import com.mogo.eagle.core.function.main.* +import com.mogo.launcher.patch.* +import com.mogo.launcher.patch.utils.* +import kotlinx.coroutines.* +import org.junit.* +import org.junit.runner.* +import java.io.* +import java.util.concurrent.* +import java.util.concurrent.TimeUnit.MILLISECONDS +import kotlin.Result + +@RunWith(AndroidJUnit4::class) +@LargeTest +class PatchUtilsTest { + + + lateinit var launch: ActivityScenario + + @Before + fun before() { + launch = ActivityScenario.launch(MainLauncherActivity::class.java) + } + + + @Test + fun testGeneratePatchAndMergeThenInstall() = runBlocking { + val f = ensureMoGoHmiFragmentShow() + withContext(Dispatchers.Default) { + + + val context = f.context ?: return@withContext + val oldApkPath = context.let { + it.packageManager.getPackageInfo(it.packageName, 0)?.applicationInfo?.sourceDir + } ?: return@withContext + + val oldApk = File(oldApkPath) + if (!oldApk.exists()) { + throw AssertionError("old apk file is not exist.") + } + val oldApkTemp = File(context.getExternalFilesDir(null), "patches/old.apk") + if (oldApkTemp.exists()) { + oldApkTemp.delete() + } + oldApkTemp.parentFile?.takeIf { !it.exists() }?.also { it.mkdirs() } + + oldApk.copyTo(oldApkTemp) + val patch = File(context.getExternalFilesDir(null), "patches/patch.zip") + if (!patch.exists()) { + throw AssertionError("patch file is not exist.") + } + val newApk = File(context.getExternalFilesDir(null), "patches/new.apk") +// PatchUtils.applyPatch(context, File(oldApkPath), patch, newApk) +// PatchUtils.install(context, newApk) + } + delay(TimeUnit.MINUTES.toMillis(1)) + } + + + private suspend fun ensureMoGoHmiFragmentShow(): MoGoHmiFragment = suspendCancellableCoroutine { + launch.onActivity { itx -> + val executor = Executors.newSingleThreadScheduledExecutor() + executor.scheduleAtFixedRate({ + var find = + itx.supportFragmentManager.fragments.find { it is MoGoHmiFragment } as? MoGoHmiFragment + while (find == null) { + find = + itx.supportFragmentManager.fragments.find { it is MoGoHmiFragment } as? MoGoHmiFragment + } + while (!find.isResumed) { + Thread.sleep(500) + } + it.resumeWith(Result.success(find)) + try { + Thread.sleep(500) + executor.shutdownNow() + } catch (e: Throwable) { + e.printStackTrace() + } + }, 50, 500, MILLISECONDS) + } + } + +} \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/DevaToolsProvider.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/DevaToolsProvider.kt index 0e4b4aa983..96419f137c 100644 --- a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/DevaToolsProvider.kt +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/DevaToolsProvider.kt @@ -16,8 +16,10 @@ import com.mogo.eagle.core.data.deva.scene.SceneTAG import com.mogo.eagle.core.data.msgbox.MsgBoxBean import com.mogo.eagle.core.function.api.devatools.IDevaToolsProvider import com.mogo.eagle.core.function.api.devatools.apm.* +import com.mogo.eagle.core.function.api.devatools.download.* import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger import com.zhjt.mogo_core_function_devatools.apm.* +import com.mogo.eagle.core.function.api.upgrade.* import com.zhjt.mogo_core_function_devatools.badcase.BadCaseManager import com.zhjt.mogo_core_function_devatools.badcase.consts.BadCaseConfig import com.zhjt.mogo_core_function_devatools.binding.BindingCarManager.Companion.bindingCarManager @@ -167,8 +169,8 @@ class DevaToolsProvider : IDevaToolsProvider { iPCReportManager.showReportListWindow(context, isShow) } - override fun downLoadPackage(downloadKey: String, downloadUrl: String) { - upgradeManager.downLoadPackage(mContext!!, downloadKey, downloadUrl) + override fun downLoadPackage(type: DownloadType, downloadKey: String, downloadUrl: String) { + upgradeManager.downLoadPackage(mContext!!,type, downloadKey, downloadUrl) } override fun updateUpgradeProgress() { @@ -249,4 +251,8 @@ class DevaToolsProvider : IDevaToolsProvider { override fun queryObuUpgrade(obuVersionName: String) { bindingCarManager.queryObuUpgrade(obuVersionName) } + + override fun upgradeProvider(): IMoGoUpgradeProvider? { + return upgradeManager.upgradeProvider() + } } \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/binding/BindingCarManager.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/binding/BindingCarManager.kt index f8827223e0..c9ba36a422 100644 --- a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/binding/BindingCarManager.kt +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/binding/BindingCarManager.kt @@ -162,8 +162,8 @@ class BindingCarManager : IMoGoAutopilotCarConfigListener { * 查询app是否需要升级 */ fun queryAppUpgrade() { - UpgradeAppNetWorkManager.getInstance() - .getAppUpgradeInfo(mContext, mAddress, role.toString() + "") + UpgradeAppNetWorkManager.instance + ?.getAppUpgradeInfo(mContext, mAddress ?: "", role.toString() + "") } diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/ObuUpgradeAppNetWorkManager.java b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/ObuUpgradeAppNetWorkManager.java index 685b74cdb8..325817effd 100644 --- a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/ObuUpgradeAppNetWorkManager.java +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/ObuUpgradeAppNetWorkManager.java @@ -7,6 +7,7 @@ import android.content.Context; import com.mogo.cloud.passport.MoGoAiCloudClientConfig; import com.mogo.commons.constants.HostConst; import com.mogo.eagle.core.data.deva.bindingcar.UpgradeAppInfo; +import com.mogo.eagle.core.function.api.devatools.download.DownloadType; import com.mogo.eagle.core.function.call.devatools.CallerDevaToolsManager; import com.mogo.eagle.core.function.call.obu.CallerObuApiManager; import com.mogo.eagle.core.network.MoGoRetrofitFactory; @@ -58,7 +59,7 @@ public class ObuUpgradeAppNetWorkManager { String sn = MoGoAiCloudClientConfig.getInstance().getSn(); CallerLogger.INSTANCE.d(M_BINDING + TAG, "getObuUpgradeInfo mac = " + mac + " ---sn = " + sn + " ---versionName = " + versionName); - UpgradeAppRequest request = new UpgradeAppRequest(sn, mac, "7"); + UpgradeAppRequest request = new UpgradeAppRequest(sn, mac, "7", null, "0"); RequestBody requestBody = RequestBody.create(MediaType.get("application/json;charset=UTF-8"), GsonUtil.jsonFromObject(request)); mUpgradeApiService.getUpgradeInfo(requestBody) .subscribeOn(Schedulers.io()) @@ -73,7 +74,7 @@ public class ObuUpgradeAppNetWorkManager { if (info != null && info.result != null) { CallerLogger.INSTANCE.d(M_BINDING + TAG, "getObuUpgradeInfo appFileName = " + info.result.getAppFileName() + " ----url = " + info.result.getAppUrl() + " ----name = " + info.result.getVersionName() + " --obuVersionName =" + versionName + " ---info.result = " + info.result); if (!String.valueOf(info.result.getVersionName()).equals(versionName)) { //判断是否下载,当文件名称不一致的时候,就下载 - CallerDevaToolsManager.INSTANCE.downLoadPackage(info.result.getAppFileName(), info.result.getAppUrl()); + CallerDevaToolsManager.INSTANCE.downLoadPackage(DownloadType.OBU, info.result.getAppFileName(), info.result.getAppUrl()); } } else { CallerLogger.INSTANCE.d(M_BINDING + TAG, "getObuUpgradeInfo onNext info == null"); diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/UpgradeAppNetWorkManager.java b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/UpgradeAppNetWorkManager.java deleted file mode 100644 index 6c42c6384b..0000000000 --- a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/UpgradeAppNetWorkManager.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.zhjt.mogo_core_function_devatools.upgrade; - -import static com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant.M_BINDING; - -import android.content.Context; - -import com.elegant.utils.UiThreadHandler; -import com.mogo.cloud.passport.MoGoAiCloudClientConfig; -import com.mogo.commons.constants.HostConst; -import com.mogo.eagle.core.data.deva.bindingcar.UpgradeAppInfo; -import com.mogo.eagle.core.function.call.hmi.CallerHmiManager; -import com.mogo.eagle.core.network.MoGoRetrofitFactory; -import com.mogo.eagle.core.network.utils.GsonUtil; -import com.mogo.eagle.core.utilcode.breakpoint.Config; -import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger; -import com.mogo.eagle.core.utilcode.util.AppUtils; -import com.mogo.eagle.core.utilcode.util.FileUtils; - -import io.reactivex.Observer; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.annotations.NonNull; -import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; -import okhttp3.MediaType; -import okhttp3.RequestBody; - -/** - * @author lixiaopeng - * @description 获取升级信息 - * @since: 3/25/22 - */ -public class UpgradeAppNetWorkManager { - private static volatile UpgradeAppNetWorkManager requestNoticeManager; - private final UpgradeApiService mUpgradeApiService; - private static final String TAG = "Upgrade"; - - private UpgradeAppNetWorkManager() { - mUpgradeApiService = MoGoRetrofitFactory.getInstance(HostConst.getHost()) - .create(UpgradeApiService.class); - } - - public static UpgradeAppNetWorkManager getInstance() { - if (requestNoticeManager == null) { - synchronized (UpgradeAppNetWorkManager.class) { - if (requestNoticeManager == null) { - requestNoticeManager = new UpgradeAppNetWorkManager(); - } - } - } - return requestNoticeManager; - } - - /** - * 获取app升级信息 - */ - public void getAppUpgradeInfo(Context context, String mac, String screenType) { -// String sn = "X20202203105S688HZ"; -// String mac = "48:b0:2d:3a:bc:78"; - String sn = MoGoAiCloudClientConfig.getInstance().getSn(); - int versionCode = AppUtils.getAppVersionCode(); - CallerLogger.INSTANCE.d(M_BINDING + TAG, "getAppUpgradeInfo mac = " + mac + "---type = " + screenType + "---sn = " + sn + "---versionCode =" + versionCode); - UpgradeAppRequest request = new UpgradeAppRequest(sn, mac, screenType); - RequestBody requestBody = RequestBody.create(MediaType.get("application/json;charset=UTF-8"), GsonUtil.jsonFromObject(request)); - mUpgradeApiService.getUpgradeInfo(requestBody) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new Observer() { - @Override - public void onSubscribe(@NonNull Disposable d) { - } - - @Override - public void onNext(@NonNull UpgradeAppInfo info) { - if (info != null && info.result != null) { - CallerLogger.INSTANCE.d(M_BINDING + TAG, "UpgradeAppInfo url = " + info.result.getAppUrl() + "----code = " + info.result.getVersionCode() + "--versionCode =" + versionCode + "--info.result = " + info.result); - if (info.result.getVersionCode() > versionCode) { - CallerHmiManager.INSTANCE.showUpgradeDialog(info.result.getAppUrl().substring(info.result.getAppUrl().lastIndexOf("/")+1), info.result.getAppUrl(), info.result.getInstallTitle(), info.result.getInstallContent(), info.result.getInstallType()); - } else { - deleteApkFile(); - } - } else { - CallerLogger.INSTANCE.d(M_BINDING + TAG, "UpgradeAppInfo onNext info == null"); - deleteApkFile(); - } - } - - @Override - public void onError(Throwable e) { - deleteApkFile(); - } - - @Override - public void onComplete() { - } - }); - - } - - /** - * 删除APK 相关的文件 - */ - private void deleteApkFile(){ - UiThreadHandler.post(new Runnable() { - @Override - public void run() { - FileUtils.delete(Config.downLoadPath); - } - }); - } - -} diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/UpgradeAppNetWorkManager.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/UpgradeAppNetWorkManager.kt new file mode 100644 index 0000000000..fcd040aac4 --- /dev/null +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/UpgradeAppNetWorkManager.kt @@ -0,0 +1,150 @@ +package com.zhjt.mogo_core_function_devatools.upgrade + +import android.content.* +import android.text.* +import android.util.* +import com.elegant.utils.UiThreadHandler +import com.mogo.cloud.passport.* +import com.mogo.commons.constants.* +import com.mogo.eagle.core.data.config.* +import com.mogo.eagle.core.data.deva.bindingcar.* +import com.mogo.eagle.core.function.call.devatools.CallerDevaToolsManager.upgradeProvider +import com.mogo.eagle.core.function.call.hmi.CallerHmiManager.showUpgradeDialog +import com.mogo.eagle.core.function.call.patch.CallerPatchManager.addPatchInfo +import com.mogo.eagle.core.function.call.patch.CallerPatchManager.isPatchAccept +import com.mogo.eagle.core.network.* +import com.mogo.eagle.core.network.utils.* +import com.mogo.eagle.core.utilcode.breakpoint.* +import com.mogo.eagle.core.utilcode.breakpoint.Config +import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger.d +import com.mogo.eagle.core.utilcode.mogo.logger.scene.* +import com.mogo.eagle.core.utilcode.util.* +import com.mogo.eagle.core.utilcode.util.FileUtils +import io.reactivex.* +import io.reactivex.android.schedulers.* +import io.reactivex.disposables.* +import io.reactivex.schedulers.* +import kotlinx.coroutines.* +import okhttp3.* + +/** + * @author lixiaopeng + * @description 获取升级信息 + * @since: 3/25/22 + */ +class UpgradeAppNetWorkManager private constructor() { + + private val mUpgradeApiService: UpgradeApiService = MoGoRetrofitFactory.getInstance(HostConst.getHost()).create(UpgradeApiService::class.java) + + private val scope by lazy { CoroutineScope(Dispatchers.IO + SupervisorJob()) } + + /** + * 获取app升级信息 + */ + fun getAppUpgradeInfo(context: Context?, mac: String, screenType: String) { // String sn = "X20202203105S688HZ"; + // String mac = "48:b0:2d:3a:bc:78"; + var mac = mac + var screenType = screenType + var sn = MoGoAiCloudClientConfig.getInstance().sn + val versionCode = AppUtils.getAppVersionCode() + val versionName = AppUtils.getAppVersionName() + d(SceneConstant.M_BINDING + TAG, "getAppUpgradeInfo mac = $mac---type = $screenType---sn = $sn---versionCode =$versionCode---versionName =$versionName") + + //TODO renwj undo start + mac = "48:b0:2d:4d:31:7f" + sn = null + screenType = "10" //TODO renwj undo end + val request = UpgradeAppRequest(sn, mac, screenType, versionName, "1") + val requestBody = RequestBody.create(MediaType.get("application/json;charset=UTF-8"), GsonUtil.jsonFromObject(request)) + mUpgradeApiService.getUpgradeInfo(requestBody).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(object : Observer { + override fun onSubscribe(d: Disposable) {} + override fun onNext(info: UpgradeAppInfo) { + doUpgrade(info) + } + + override fun onError(e: Throwable) { + deleteApkFile() + } + + override fun onComplete() {} + }) + } + + private fun doUpgrade(info: UpgradeAppInfo) { + scope.launch { + if (info.result != null) { + val versionCode = AppUtils.getAppVersionCode() + d(SceneConstant.M_BINDING + TAG, "UpgradeAppInfo url = " + info.result.appUrl + "----code = " + info.result.versionCode + "--versionCode =" + versionCode + "--info.result = " + info.result) + if (info.result.versionCode > versionCode) { + val patchInfo = info.result.patchInfo + var downloadUrl: String = info.result.appUrl + val provider = upgradeProvider() + if (patchInfo != null) { + val f1 = FunctionBuildConfig.isSupportPatchUpgrade + if (!f1) { + Log.d("ApkInstaller", "当前版本配置不支持增量升级...") + } + val f2 = provider != null + if (f1 && !f2) { + Log.d("ApkInstaller", "provider为空...") + } + var f3 = true + if (f2 && provider != null && !provider.isNeedGoPatchUpgrade()) { + Log.d("ApkInstaller", "上次patch升级失败了...") + f3 = false + } + var f4 = true + if (f3) { + provider?.recordUpgradeRecord(patchInfo.targetVersion, patchInfo.targetMd5, 1) + provider?.recordSourceMd5CheckStart() + f4 = isPatchAccept(Utils.getApp(), patchInfo.sourceMd5, patchInfo.patchSize.toLong()) + if (!f4) { + Log.d("ApkInstaller", "旧版本apk包的md5与服务端上的md5不匹配...") + provider?.recordSourceMd5CheckFailed("旧apk的md5与服务端的md5不一致:[server_md5: ${patchInfo.sourceMd5}, apk_md5: ${AppUtils.getAppApkMd5()}]") + } else { + provider?.recordSourceMd5CheckSuccess() + } + } + if (f4) { + downloadUrl = patchInfo.patchDownloadUrl + addPatchInfo(patchInfo) + } + } else { + provider?.recordUpgradeRecord(info.result.versionName, null, 0) + } + withContext(Dispatchers.Main) { + showUpgradeDialog(downloadUrl.substring(downloadUrl.lastIndexOf("/") + 1), downloadUrl, info.result.installTitle, info.result.installContent, info.result.installType) + } + } else { + deleteApkFile() + } + } else { + d(SceneConstant.M_BINDING + TAG, "UpgradeAppInfo onNext info == null") + deleteApkFile() + } + } + } + + /** + * 删除APK 相关的文件 + */ + private fun deleteApkFile() { + UiThreadHandler.post { FileUtils.delete(Config.downLoadPath) } + } + + companion object { + @Volatile private var requestNoticeManager: UpgradeAppNetWorkManager? = null + private const val TAG = "Upgrade" + val instance: UpgradeAppNetWorkManager? + get() { + if (requestNoticeManager == null) { + synchronized(UpgradeAppNetWorkManager::class.java) { + if (requestNoticeManager == null) { + requestNoticeManager = UpgradeAppNetWorkManager() + } + } + } + return requestNoticeManager + } + } +} \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/UpgradeAppRequest.java b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/UpgradeAppRequest.java index c82c3d27e4..61ff979dea 100644 --- a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/UpgradeAppRequest.java +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/UpgradeAppRequest.java @@ -1,20 +1,27 @@ package com.zhjt.mogo_core_function_devatools.upgrade; +import java.io.Serializable; + /** * @author lixiaopeng * @description 获取app升级信息 * @since: 11/15/21 */ -public class UpgradeAppRequest { +public class UpgradeAppRequest implements Serializable { private String mac; private String sn; private String screenType; + private String currentVersion; - public UpgradeAppRequest( String sn, String mac, String screenType) { + private String patchStatus; + + public UpgradeAppRequest( String sn, String mac, String screenType, String versionName, String patchStatus) { this.sn = sn; this.mac = mac; this.screenType = screenType; + this.currentVersion = versionName; + this.patchStatus = patchStatus; } public String getSn() { @@ -41,4 +48,19 @@ public class UpgradeAppRequest { this.screenType = screenType; } + public String getVersionName() { + return currentVersion; + } + + public void setVersionName(String versionName) { + this.currentVersion = versionName; + } + + public String getPatchStatus() { + return patchStatus; + } + + public void setPatchStatus(String patchStatus) { + this.patchStatus = patchStatus; + } } diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/UpgradeManager.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/UpgradeManager.kt index e0acfc917a..20c5a96716 100644 --- a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/UpgradeManager.kt +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/UpgradeManager.kt @@ -2,11 +2,18 @@ package com.zhjt.mogo_core_function_devatools.upgrade import android.app.NotificationManager import android.content.Context +import android.content.pm.PackageInstaller +import com.mogo.eagle.core.data.constants.MogoServicePaths +import com.mogo.eagle.core.function.api.upgrade.IMoGoUpgradeProvider +import com.mogo.eagle.core.function.call.base.CallerBase import androidx.core.app.NotificationCompat import com.elegant.utils.UiThreadHandler import com.mogo.eagle.core.data.obu.MogoObuConst import com.mogo.eagle.core.function.api.devatools.IMogoDevaToolsUpgradeListener -import com.mogo.eagle.core.function.call.devatools.CallerDevaToolsUpgradeListenerManager +import com.mogo.eagle.core.function.api.devatools.download.* +import com.mogo.eagle.core.function.api.devatools.download.DownloadType.* +import com.mogo.eagle.core.function.call.devatools.* +import com.mogo.eagle.core.function.call.patch.CallerPatchManager import com.mogo.eagle.core.function.call.hmi.CallerHmiManager.updateStatusBarDownloadView import com.mogo.eagle.core.function.call.obu.CallerObuApiManager import com.mogo.eagle.core.utilcode.breakpoint.Config @@ -15,10 +22,15 @@ import com.mogo.eagle.core.utilcode.breakpoint.callback.IDownload import com.mogo.eagle.core.utilcode.breakpoint.utils.DownloadUtils import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant +import com.mogo.eagle.core.utilcode.mogo.logger.Logger import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant.Companion.M_DEVA -import com.mogo.eagle.core.utilcode.util.AppUtils -import com.mogo.eagle.core.utilcode.util.ZipUtils +import com.mogo.eagle.core.utilcode.util.* +import java.io.File +import java.text.SimpleDateFormat +import java.util.* +import kotlinx.coroutines.* import java.io.IOException +import java.util.concurrent.ConcurrentHashMap class UpgradeManager : IDownload { @@ -34,16 +46,24 @@ class UpgradeManager : IDownload { private var map: Map? = null private var mDownloadFileName: String? = null - fun downLoadPackage(context: Context, downloadKey: String,downloadUrl: String) { - CallerLogger.d("${SceneConstant.M_OBU}${MogoObuConst.TAG_UPGRADE_OBU}", "UpgradeManager downLoadPackage = " + downloadUrl?.contains(".zip") + "----downloadKey = $downloadKey ---downloadUrl = $downloadUrl") - if (downloadUrl?.contains(".zip")) { + private val upgradeProvider: IMoGoUpgradeProvider? by lazy { CallerBase.getApiInstance(IMoGoUpgradeProvider::class.java, MogoServicePaths.PATH_UPGRADE_TYPE_API) } + + private val types by lazy { ConcurrentHashMap() } + + + fun upgradeProvider(): IMoGoUpgradeProvider? = upgradeProvider + + + fun downLoadPackage(context: Context, type: DownloadType, downloadKey: String, downloadUrl: String) { + CallerLogger.d("${SceneConstant.M_OBU}${MogoObuConst.TAG_UPGRADE_OBU}", "UpgradeManager downLoadPackage = " + downloadUrl.contains(".zip") + "----downloadKey = $downloadKey ---downloadUrl = $downloadUrl") + if (type == OBU) { mDownloadFileName = downloadKey } - + types[downloadUrl] = type DownloadUtils.downLoad( context, downloadUrl, - if (downloadUrl?.contains(".zip")) Config.downLoadObuPath else Config.downLoadPath, + if (type == OBU) Config.downLoadObuPath else Config.downLoadPath, downloadKey, 5, this @@ -60,6 +80,12 @@ class UpgradeManager : IDownload { CallerDevaToolsUpgradeListenerManager.invokeUpgradeStart(it) } } + val type = types[downloadUrl] + if (type == PATCH || type == APK) { + runBlocking { + CallerDevaToolsManager.upgradeProvider()?.recordDownloadStart() + } + } } } @@ -90,8 +116,118 @@ class UpgradeManager : IDownload { } override fun onFinished(downloadUrl: String?, threadBean: ThreadBean?) { - if (downloadUrl != null) { //TODO 需要判断是否是apk文件 - AppUtils.installApp(Config.downLoadPath + downloadUrl.substring(downloadUrl.lastIndexOf("/") + 1)) + if (downloadUrl != null) { + val type = types[downloadUrl] + if (type == APK || type == PATCH) { + val patchInfo = CallerPatchManager.getPatchInfoByUrl(downloadUrl) + if (patchInfo != null) { + var isPatchInstallFailed = false + var patchInstallFailedReason = "" + CallerPatchManager.removePatchInfoByUrl(downloadUrl) + val patch = File(Config.downLoadPath, downloadUrl.substring(downloadUrl.lastIndexOf("/") + 1)) + if (patch.exists()) { + try { + val newApk = File(Utils.getApp().getExternalFilesDir(null), "patch/merged/${SimpleDateFormat("yyyy_MM_dd_HH_mm_ss", Locale.ROOT).format(Date()) }.apk") + val dir = newApk.parentFile + if (dir != null && !dir.exists()) { + val ret = dir.mkdirs() + if (!ret) { + Logger.w(TAG, "create new apk path failed.") + throw AssertionError("创建目录失败") + } + } + if (dir != null){ + try { + if (newApk.exists()) { + newApk.delete() + } + } catch (t: Throwable) { + t.printStackTrace() + } + runBlocking { + upgradeProvider?.recordInstallApplyPatchStart() + } + var ret = CallerPatchManager.applyPatch(Utils.getApp(), patch, newApk) + if (!ret) { + runBlocking { + upgradeProvider?.recordInstallApplyPatchFailed("合成patch失败") + } + Logger.w(TAG, "合成patch失败...") + throw AssertionError("合成patch失败...") + } else { + runBlocking { + upgradeProvider?.recordInstallApplyPatchSuccess() + upgradeProvider?.recordTargetMd5CheckStart() + } + ret = CallerPatchManager.checkMd5ForMergedApk(Utils.getApp(), newApk, patchInfo.targetMd5) + if (!ret) { + runBlocking { + upgradeProvider?.recordTargetMd5CheckFailed("合成后的apk的md5与服务端上的目标版本的md5不一致:[server_target_md5: ${patchInfo.targetMd5}, merged_md5: ${Md5Util.getMd5FromFile(newApk)}]") + } + Logger.w(TAG, "md5校验失败...") + throw AssertionError("md5校验失败:[target:${patchInfo.targetMd5}]") + } else { + Logger.w(TAG, "md5校验成功...") + runBlocking { + upgradeProvider?.recordTargetMd5CheckSuccess() + upgradeProvider?.recordInstallStart() + } + ApkInstaller.installApp(Utils.getApp(), newApk) { code, reason -> + if (code != PackageInstaller.STATUS_SUCCESS) { + upgradeProvider?.also { + try { + newApk.delete() + } catch (t: Throwable) { + t.printStackTrace() + } + try { + patch.delete() + } catch (t: Throwable) { + t.printStackTrace() + } + runBlocking { + it.recordInstallFailed(code, reason) + } + } + } + } + } + } + } + } catch (t: Throwable) { + t.printStackTrace() + isPatchInstallFailed = true + patchInstallFailedReason = t.message ?: "安装失败: code -1" + } + } else { + patchInstallFailedReason = "patch下载后文件不存在" + isPatchInstallFailed = true + } + + if (isPatchInstallFailed) { + runBlocking { + CallerDevaToolsManager.upgradeProvider()?.recordInstallFailed(ApkInstaller.INSTALL_CODE_INVLID, patchInstallFailedReason) + } + } + } else { + val apkPath = Config.downLoadPath + downloadUrl.substring(downloadUrl.lastIndexOf("/") + 1) + val apk = File(apkPath) + ApkInstaller.installApp(Utils.getApp(), apk) { code, reason -> + if (code != PackageInstaller.STATUS_SUCCESS) { + upgradeProvider?.also { itx -> + try { + apk.delete() + } catch (t: Throwable) { + t.printStackTrace() + } + runBlocking { + itx.recordInstallFailed(code, reason) + } + } + } + } + } + } } if (downloadUrl != null) { if (map.isNullOrEmpty()) { @@ -114,6 +250,12 @@ class UpgradeManager : IDownload { CallerDevaToolsUpgradeListenerManager.invokeUpgradeError(it, errorMsg ?: "未知错误") } } + val type = types[downloadUrl] + if (type == APK || type == PATCH) { + runBlocking { + CallerDevaToolsManager.upgradeProvider()?.recordDownloadFailed(errorMsg ?: "下载失败") + } + } } } diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/provider/MoGoUpgradeProviderImpl.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/provider/MoGoUpgradeProviderImpl.kt new file mode 100644 index 0000000000..7b2c96c048 --- /dev/null +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/provider/MoGoUpgradeProviderImpl.kt @@ -0,0 +1,185 @@ +package com.zhjt.mogo_core_function_devatools.upgrade.provider + +import android.content.* +import android.content.pm.PackageInstaller +import com.alibaba.android.arouter.facade.annotation.Route +import com.mogo.eagle.core.data.constants.MogoServicePaths +import com.mogo.eagle.core.function.api.upgrade.* +import com.mogo.eagle.core.utilcode.util.* +import com.zhjt.mogo_core_function_devatools.upgrade.provider.db.UpgradeDbHelper +import com.zhjt.mogo_core_function_devatools.upgrade.provider.db.vo.* + +@Route(path = MogoServicePaths.PATH_UPGRADE_TYPE_API) +class MoGoUpgradeProviderImpl: IMoGoUpgradeProvider { + + override fun init(context: Context?) {} + + override suspend fun recordUpgradeRecord(newVersion: String, newMd5: String?, type: Int) { + UpgradeDbHelper.insertUpgradeRecord(UpgradeRecord(AppUtils.getAppVersionName(), newVersion, AppUtils.getAppApkMd5(), newMd5, if (type == 0) UpgradeType.FULL else UpgradeType.PATCH)) + } + + override suspend fun recordDownloadStart() { + UpgradeDbHelper.insertDownloadRecord(DownloadRecord(version = AppUtils.getAppVersionName(), status = DownloadStatus.DownloadStart)) + } + + override suspend fun recordDownloadFailed(error: String) { + UpgradeDbHelper.insertDownloadRecord(DownloadRecord(version = AppUtils.getAppVersionName(), status = DownloadStatus.DownloadFailed, failReason = error)) + } + + override suspend fun recordDownloadSuccess() { + UpgradeDbHelper.insertDownloadRecord(DownloadRecord(version = AppUtils.getAppVersionName(), status = DownloadStatus.DownloadComplete)) + } + + override suspend fun recordInstallStart() { + UpgradeDbHelper.insertInstallRecord(InstallRecord(version = AppUtils.getAppVersionName(), status = InstallStatus.InstallStart)) + } + + override suspend fun recordSourceMd5CheckStart() { + UpgradeDbHelper.insertInstallRecord(InstallRecord(version = AppUtils.getAppVersionName(), status = InstallStatus.SourceMd5CheckStart)) + } + + override suspend fun recordSourceMd5CheckFailed(error: String) { + UpgradeDbHelper.insertInstallRecord(InstallRecord(version = AppUtils.getAppVersionName(), status = InstallStatus.SourceMd5CheckFailed, failReason = error)) + } + + override suspend fun recordSourceMd5CheckSuccess() { + UpgradeDbHelper.insertInstallRecord(InstallRecord(version = AppUtils.getAppVersionName(), status = InstallStatus.SourceMd5CheckSuccess)) + } + + override suspend fun recordInstallApplyPatchStart() { + UpgradeDbHelper.insertInstallRecord(InstallRecord(version = AppUtils.getAppVersionName(), status = InstallStatus.ApplyPatchStart)) + } + + override suspend fun recordInstallApplyPatchFailed(error: String) { + UpgradeDbHelper.insertInstallRecord(InstallRecord(version = AppUtils.getAppVersionName(), status = InstallStatus.ApplyPatchFailed, failReason = error)) + } + + override suspend fun recordInstallApplyPatchSuccess() { + UpgradeDbHelper.insertInstallRecord(InstallRecord(version = AppUtils.getAppVersionName(), status = InstallStatus.ApplyPatchSuccess)) + } + + override suspend fun recordTargetMd5CheckStart() { + UpgradeDbHelper.insertInstallRecord(InstallRecord(version = AppUtils.getAppVersionName(), status = InstallStatus.TargetMd5CheckStart)) + } + + override suspend fun recordTargetMd5CheckFailed(error: String) { + UpgradeDbHelper.insertInstallRecord(InstallRecord(version = AppUtils.getAppVersionName(), status = InstallStatus.TargetMd5CheckFailed, failReason = error)) + } + + override suspend fun recordTargetMd5CheckSuccess() { + UpgradeDbHelper.insertInstallRecord(InstallRecord(version = AppUtils.getAppVersionName(), status = InstallStatus.TargetMd5CheckSuccess)) + } + + override suspend fun recordInstallFailed(code: Int, error: String) { + UpgradeDbHelper.insertInstallRecord(InstallRecord(version = AppUtils.getAppVersionName(), status = InstallStatus.InstallFailed, code = code, failReason = error)) + } + + override suspend fun isUpgradeSuccessByPatch(): Boolean { + val record = UpgradeDbHelper.getUpgradeRecordOnlyByTarget(AppUtils.getAppVersionName()) + return record?.toVersion == AppUtils.getAppVersionName() && record.type == UpgradeType.PATCH + } + + override suspend fun isUpgradeSuccessByFull(): Boolean { + val record = UpgradeDbHelper.getUpgradeRecordOnlyByTarget(AppUtils.getAppVersionName()) + return record?.toVersion == AppUtils.getAppVersionName() && record.type == UpgradeType.FULL + } + + override suspend fun removeRecordByTargetVersion(version: String) { + UpgradeDbHelper.deleteRecordByTargetVersion(version) + } + + override suspend fun removeRecordBySourceVersion(version: String) { + UpgradeDbHelper.deleteRecordBySourceVersion(version) + } + + override suspend fun getFullUpgradeFailedReason(): Map>>? { + if (isUpgradeSuccessByFull()) { + return null + } + if (isUpgradeSuccessByPatch()) { + return null + } + val full = UpgradeDbHelper.getUpgradeRecordFull(AppUtils.getAppVersionName()) + if (full == null || full.upgrade?.type != UpgradeType.FULL) { + return null + } + val map = HashMap>>() + full.downloads?.takeIf { + it.isNotEmpty() + }?.sortedBy { + it.status.ordinal + }?.map { + it.status.ordinal to (it.failReason ?: "") + }?.also { + map["download"] = it + } + full.installs?.takeIf { + it.isNotEmpty() + }?.sortedBy { + it.status.ordinal + }?.map { + it.status.ordinal to (it.failReason ?: "") + }?.also { + map["install"] = it + } + return map + } + + override suspend fun getPatchUpgradeFailedReason(): Map>>? { + if (isUpgradeSuccessByFull()) { + return null + } + if (isUpgradeSuccessByPatch()) { + return null + } + val full = UpgradeDbHelper.getUpgradeRecordFull(AppUtils.getAppVersionName()) + if (full == null || full.upgrade?.type != UpgradeType.PATCH) { + return null + } + val map = HashMap>>() + full.downloads?.takeIf { + it.isNotEmpty() + }?.sortedBy { + it.status.ordinal + }?.map { + it.status.ordinal to (it.failReason ?: "") + }?.also { + map["download"] = it + } + full.installs?.takeIf { + it.isNotEmpty() + }?.sortedBy { + it.status.ordinal + }?.map { + it.status.ordinal to (it.failReason ?: "") + }?.also { + map["install"] = it + } + return map + } + override suspend fun hasUpgradeRecord(): Boolean { + return UpgradeDbHelper.hasRecords() + } + + override suspend fun isNeedGoPatchUpgrade(): Boolean { + val reasons = getPatchUpgradeFailedReason() + return reasons?.let { itx -> + itx["install"]?.takeIf { + it.isNotEmpty() + }?.let { + val last = it.last() + val ordinal = last.first + val status = InstallStatus.values().find { v -> v.ordinal == ordinal } + if (status != null && status.isReallyFailed()) { + val code = UpgradeDbHelper.getUpgradeRecordFull(AppUtils.getAppVersionName())?.installs?.find { s -> s.status == status }?.code + code != PackageInstaller.STATUS_FAILURE_INVALID && + code != PackageInstaller.STATUS_FAILURE_CONFLICT && + code != PackageInstaller.STATUS_FAILURE && + code != PackageInstaller.STATUS_FAILURE_STORAGE + } else { + true + } + } ?: true + } ?: true + } +} \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/provider/db/UpgradeDb.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/provider/db/UpgradeDb.kt new file mode 100644 index 0000000000..7310fbb63c --- /dev/null +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/provider/db/UpgradeDb.kt @@ -0,0 +1,20 @@ +package com.zhjt.mogo_core_function_devatools.upgrade.provider.db + +import androidx.room.Database +import androidx.room.RoomDatabase +import com.zhjt.mogo_core_function_devatools.upgrade.provider.db.dao.IUpgradeRecordDao +import com.zhjt.mogo_core_function_devatools.upgrade.provider.db.vo.DownloadRecord +import com.zhjt.mogo_core_function_devatools.upgrade.provider.db.vo.InstallRecord +import com.zhjt.mogo_core_function_devatools.upgrade.provider.db.vo.UpgradeRecord + +@Database( + entities = [ + UpgradeRecord::class, + DownloadRecord::class, + InstallRecord::class], + version = 1, + exportSchema = false) +internal abstract class UpgradeRecordDb: RoomDatabase() { + + abstract fun dao(): IUpgradeRecordDao +} \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/provider/db/UpgradeDbHelper.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/provider/db/UpgradeDbHelper.kt new file mode 100644 index 0000000000..144389ecc5 --- /dev/null +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/provider/db/UpgradeDbHelper.kt @@ -0,0 +1,69 @@ +package com.zhjt.mogo_core_function_devatools.upgrade.provider.db + +import androidx.room.Room +import com.mogo.eagle.core.utilcode.util.Utils +import com.zhjt.mogo_core_function_devatools.upgrade.provider.db.vo.DownloadRecord +import com.zhjt.mogo_core_function_devatools.upgrade.provider.db.vo.InstallRecord +import com.zhjt.mogo_core_function_devatools.upgrade.provider.db.vo.UpgradeRecord +import com.zhjt.mogo_core_function_devatools.upgrade.provider.db.vo.UpgradeRecordFull + +internal object UpgradeDbHelper { + + private val db by lazy { + Room.databaseBuilder(Utils.getApp(), UpgradeRecordDb::class.java, "upgrade_records").build() + } + + suspend fun insertUpgradeRecord(record: UpgradeRecord): Long = try { + db.dao().insertUpgradeRecord(record) + } catch (e: Exception) { + e.printStackTrace() + -1 + } + + suspend fun insertDownloadRecord(record: DownloadRecord): Long = try { + db.dao().insertDownloadRecord(record) + } catch (e: Exception) { + e.printStackTrace() + -1 + } + + suspend fun insertInstallRecord(record: InstallRecord): Long = try { + db.dao().insertInstallRecord(record) + } catch (e: Exception) { + e.printStackTrace() + -1 + } + + suspend fun getUpgradeRecordFull(oldVersion: String): UpgradeRecordFull? = try { + db.dao().getUpgradeRecordFull(oldVersion) + } catch (e: Exception) { + e.printStackTrace() + null + } + + suspend fun getUpgradeRecordOnlyByTarget(targetVersion: String): UpgradeRecord? = try { + db.dao().getUpgradeRecordOnlyByTarget(targetVersion) + } catch (e: Exception) { + e.printStackTrace() + null + } + + suspend fun deleteRecordBySourceVersion(version: String) = try { + db.dao().deleteRecordBySourceVersion(version) + } catch (e: Exception) { + e.printStackTrace() + } + + suspend fun deleteRecordByTargetVersion(version: String) = try { + db.dao().deleteRecordByTargetVersion(version) + } catch (e: Exception) { + e.printStackTrace() + } + + suspend fun hasRecords() = try { + db.dao().getRecordCount() > 0 + } catch (e: Exception) { + e.printStackTrace() + false + } +} \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/provider/db/dao/IUpgradeRecordDao.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/provider/db/dao/IUpgradeRecordDao.kt new file mode 100644 index 0000000000..3d9dcd3ecc --- /dev/null +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/provider/db/dao/IUpgradeRecordDao.kt @@ -0,0 +1,44 @@ +package com.zhjt.mogo_core_function_devatools.upgrade.provider.db.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy.IGNORE +import androidx.room.OnConflictStrategy.REPLACE +import androidx.room.Query +import androidx.room.Transaction +import com.zhjt.mogo_core_function_devatools.upgrade.provider.db.vo.* + +@Dao +internal interface IUpgradeRecordDao { + + @Insert(onConflict = REPLACE) + @Transaction + suspend fun insertUpgradeRecord(record: UpgradeRecord): Long + + @Insert(onConflict = IGNORE) + @Transaction + suspend fun insertDownloadRecord(record: DownloadRecord): Long + + @Insert(onConflict = IGNORE) + @Transaction + suspend fun insertInstallRecord(record: InstallRecord): Long + + @Query("SELECT * FROM upgrade_record WHERE f_v = :oldVersion") + @Transaction + suspend fun getUpgradeRecordFull(oldVersion: String): UpgradeRecordFull? + + @Query("SELECT * FROM upgrade_record WHERE f_v = :oldVersion") + suspend fun getUpgradeRecordOnly(oldVersion: String): UpgradeRecord? + + @Query("SELECT * FROM upgrade_record WHERE t_v = :toVersion") + suspend fun getUpgradeRecordOnlyByTarget(toVersion: String): UpgradeRecord? + + @Query("DELETE FROM upgrade_record WHERE f_v = :version") + suspend fun deleteRecordBySourceVersion(version: String) + + @Query("DELETE FROM upgrade_record WHERE t_v = :version") + suspend fun deleteRecordByTargetVersion(version: String) + + @Query("SELECT COUNT(*) FROM upgrade_record") + suspend fun getRecordCount(): Int +} \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/provider/db/vo/Vo.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/provider/db/vo/Vo.kt new file mode 100644 index 0000000000..31c8388341 --- /dev/null +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/upgrade/provider/db/vo/Vo.kt @@ -0,0 +1,187 @@ +package com.zhjt.mogo_core_function_devatools.upgrade.provider.db.vo + +import androidx.room.* +import androidx.room.ForeignKey.CASCADE +import com.mogo.eagle.core.utilcode.util.* + +@Entity(tableName = "upgrade_record") +data class UpgradeRecord( + + @PrimaryKey + @ColumnInfo(name = "f_v") + var fromVersion: String, + + @ColumnInfo(name = "t_v") + var toVersion: String, + + @ColumnInfo(name = "f_m") + var fromMD5: String?, + + @ColumnInfo(name = "t_m") + var toMD5: String?, + + @field:TypeConverters(UpgradeType::class) + var type: UpgradeType +) + + +enum class UpgradeType { + FULL, + PATCH; + + companion object { + + @JvmStatic + @TypeConverter + fun to(value: Int?) = values().find { it.ordinal == value } + + @JvmStatic + @TypeConverter + fun from(type: UpgradeType?) = type?.ordinal + } +} + + +@Entity( + tableName = "download_record", + foreignKeys = [ + ForeignKey( + entity = UpgradeRecord::class, + parentColumns = arrayOf("f_v"), + childColumns = arrayOf("d_v"), + deferred = true, + onDelete = CASCADE + )], + indices = [ + Index(value = arrayOf("id"), unique = true), + Index(value = arrayOf("d_v")) + ] +) +data class DownloadRecord( + + @PrimaryKey(autoGenerate = true) + var id: Long = 0, + + @ColumnInfo(name = "d_v") + var version: String, + + @field:TypeConverters(DownloadStatus::class) + var status: DownloadStatus, + + @ColumnInfo(name = "reason") + var failReason: String? = null +) + +enum class DownloadStatus { + + DownloadStart, + + DownloadFailed, + + DownloadComplete; + + companion object { + + @JvmStatic + @TypeConverter + fun to(value: Int?) = values().find { it.ordinal == value } + + @JvmStatic + @TypeConverter + fun from(type: DownloadStatus?) = type?.ordinal + } +} + +@Entity( + tableName = "install_record", + foreignKeys = [ForeignKey( + entity = UpgradeRecord::class, + parentColumns = arrayOf("f_v"), + childColumns = arrayOf("i_v"), + deferred = true, + onDelete = CASCADE + )], + indices = [ + Index(value = arrayOf("id"), unique = true), + Index(value = arrayOf("i_v")) + ] +) +data class InstallRecord( + + @PrimaryKey(autoGenerate = true) + var id: Long = 0, + + @ColumnInfo(name = "i_v") + var version: String, + + @field:TypeConverters(InstallStatus::class) + var status: InstallStatus, + + @ColumnInfo(defaultValue = "${ApkInstaller.INSTALL_CODE_INVLID}") + var code: Int = 0, + + @ColumnInfo(name = "reason") + var failReason: String? = null +) + +enum class InstallStatus { + + SourceMd5CheckStart, + + SourceMd5CheckFailed, + + SourceMd5CheckSuccess, + + ApplyPatchStart, + + ApplyPatchFailed, + + ApplyPatchSuccess, + + TargetMd5CheckStart, + + TargetMd5CheckFailed, + + TargetMd5CheckSuccess, + + InstallStart, + + InstallFailed, + + InstallSuccess; + + companion object { + + @JvmStatic + @TypeConverter + fun to(value: Int?) = values().find { it.ordinal == value } + + @JvmStatic + @TypeConverter + fun from(status: InstallStatus?) = status?.ordinal + } + + fun isReallyFailed() = (this == SourceMd5CheckFailed || this == ApplyPatchFailed || this == TargetMd5CheckFailed) +} + +class UpgradeRecordFull { + + @Embedded + var upgrade: UpgradeRecord? = null + + @Relation( + parentColumn = "f_v", + entityColumn = "d_v", + entity = DownloadRecord::class + ) + var downloads: List? = null + + + @Relation( + parentColumn = "f_v", + entityColumn = "i_v", + entity = InstallRecord::class + ) + var installs: List? = null +} + diff --git a/core/function-impl/mogo-core-function-hmi/build.gradle b/core/function-impl/mogo-core-function-hmi/build.gradle index cf6ba9cbf2..1746d759e5 100644 --- a/core/function-impl/mogo-core-function-hmi/build.gradle +++ b/core/function-impl/mogo-core-function-hmi/build.gradle @@ -71,7 +71,7 @@ dependencies { implementation rootProject.ext.dependencies.cicle_indicator implementation rootProject.ext.dependencies.koomnative implementation rootProject.ext.dependencies.koomxhook - + implementation project(':core:function-impl:mogo-core-function-patch') api project(':test:crashreport-apmbyte') compileOnly project(':core:function-impl:mogo-core-function-datacenter') implementation project(':foudations:mogo-commons') diff --git a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/tools/UpgradeAppDialog.kt b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/tools/UpgradeAppDialog.kt index 74cecac037..044bf86256 100644 --- a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/tools/UpgradeAppDialog.kt +++ b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/tools/UpgradeAppDialog.kt @@ -4,10 +4,16 @@ import android.content.Context import android.view.View import android.widget.TextView import androidx.lifecycle.LifecycleObserver +import com.mogo.eagle.core.function.api.devatools.download.DownloadType.APK +import com.mogo.eagle.core.function.api.devatools.download.DownloadType.PATCH import com.mogo.eagle.core.function.call.devatools.CallerDevaToolsManager +import com.mogo.eagle.core.function.call.patch.* import com.mogo.eagle.core.function.hmi.R import com.mogo.eagle.core.utilcode.util.ToastUtils import com.mogo.eagle.core.function.hmi.dialog.BaseFloatDialog +import com.mogo.eagle.core.utilcode.kotlin.* +import kotlinx.coroutines.* +import java.lang.ref.WeakReference /** * @brief APP升级提示弹框 @@ -18,7 +24,7 @@ class UpgradeAppDialog(context: Context) : BaseFloatDialog(context), LifecycleOb companion object{ private const val TAG = "UpgradeAppDialog" - private var upgradeAppDialog: UpgradeAppDialog? = null + private var upgradeAppDialog: WeakReference? = null fun show(context: Context?, name: String, @@ -27,10 +33,12 @@ class UpgradeAppDialog(context: Context) : BaseFloatDialog(context), LifecycleOb content: String, installType: String) { context?.let { - if (upgradeAppDialog == null) { - upgradeAppDialog = UpgradeAppDialog(it) + var dialog = upgradeAppDialog?.get() + if (dialog == null) { + dialog = UpgradeAppDialog(it) + upgradeAppDialog = WeakReference(dialog) } - upgradeAppDialog?.let { dialog -> + dialog.let { d -> if (dialog.isShowing) { return } @@ -78,10 +86,19 @@ class UpgradeAppDialog(context: Context) : BaseFloatDialog(context), LifecycleOb /** * 去下载 */ - fun downloadApp() { + private fun downloadApp() { ToastUtils.showLong("开始下载APK,稍后可前往downloads文件夹查看,通知栏查看下载进度") - tag?.let { downloadUrl?.let { it1 -> CallerDevaToolsManager.downLoadPackage(it, it1) } } - + tag?.let { + downloadUrl?.let { url -> + window?.decorView?.scope?.launch { + if (CallerPatchManager.getPatchInfoByUrl(url) != null) { + CallerDevaToolsManager.downLoadPackage(PATCH, it, url) + } else { + CallerDevaToolsManager.downLoadPackage(APK, it, url) + } + } + } + } dismiss() } diff --git a/core/function-impl/mogo-core-function-patch/.gitignore b/core/function-impl/mogo-core-function-patch/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/core/function-impl/mogo-core-function-patch/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-patch/build.gradle b/core/function-impl/mogo-core-function-patch/build.gradle new file mode 100644 index 0000000000..ab82536996 --- /dev/null +++ b/core/function-impl/mogo-core-function-patch/build.gradle @@ -0,0 +1,180 @@ +import org.gradle.api.execution.* +import com.android.build.gradle.tasks.* +import com.android.build.gradle.* +import org.gradle.process.* +import org.gradle.api.invocation.* + +plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' + id 'com.alibaba.arouter' + id 'kotlin-kapt' +} + +android { + compileSdkVersion rootProject.ext.android.compileSdkVersion + defaultConfig { + minSdkVersion rootProject.ext.android.minSdkVersion + targetSdkVersion rootProject.ext.android.targetSdkVersion + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + + //ARouter apt 参数 + kapt { + useBuildCache = false + arguments { + arg("AROUTER_MODULE_NAME", project.getName()) + } + } + } + + sourceSets { + main { + jniLibs.srcDirs = ['jniLibs'] + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation rootProject.ext.dependencies.androidxccorektx + implementation rootProject.ext.dependencies.androidxappcompat + implementation rootProject.ext.dependencies.material + androidTestImplementation rootProject.ext.dependencies.androidxjunit + androidTestImplementation rootProject.ext.dependencies.junit + implementation rootProject.ext.dependencies.lancetx_runtime + implementation rootProject.ext.dependencies.arouter + kapt rootProject.ext.dependencies.aroutercompiler + if (Boolean.valueOf(USE_MAVEN_PACKAGE)) { + implementation rootProject.ext.dependencies.moduleservice + implementation rootProject.ext.dependencies.mogo_core_data + implementation rootProject.ext.dependencies.mogo_core_utils + implementation rootProject.ext.dependencies.mogo_core_function_api + implementation rootProject.ext.dependencies.mogo_core_res + } else { + implementation project(':core:mogo-core-data') + implementation project(':core:mogo-core-utils') + implementation project(':core:mogo-core-function-api') + implementation project(':core:mogo-core-res') + } +} + +rootProject.gradle.taskGraph.whenReady { TaskExecutionGraph graph -> + def tasks = graph.allTasks.findAll { t0 -> + t0 instanceof PackageApplication + } + def app = rootProject.subprojects.find { + it.plugins.hasPlugin("com.android.application") + } + if (app != null) { + def android = (AppExtension)app.extensions.findByName("android") + def signConfig = android.signingConfigs.getByName("release") + if (signConfig != null && isOnlineReleaseBuild(gradle)) { + if (tasks != null) { + tasks.forEach { t1 -> + t1.doLast { t2 -> + def packageTask = (PackageApplication) t2 + def outDir = packageTask.outputDirectory + def apkNames = packageTask.apkNames + if (apkNames != null && apkNames.size() > 0) { + apkNames.forEach { apkName -> + def apk = new File(outDir, apkName) + def lp = new File(rootProject.rootDir, "local.properties") + if (apk.exists() && lp.exists()) { + def p = new Properties() + lp.withInputStream { instr -> + p.load(instr) + } + def sdkDir = p.getProperty('sdk.dir') + def apkSigner = new File(sdkDir, isWindows() ? "build-tools${File.separator}${android.buildToolsVersion}${File.separator}apksigner.bat" : "build-tools${File.separator}${android.buildToolsVersion}${File.separator}apksigner") + + def newApk = new File(outDir, "normalized_" + apkName) + def outApk = new File(outDir, "signed_" + apkName) + def workDir = new File(projectDir, "shell") + def shell = isMac() ? ".${File.separator}macos${File.separator}ApkNormalized" : (isLinux() ? ".${File.separator}linux64${File.separator}ApkNormalized" : ".${File.separator}windows64${File.separator}ApkNormalized.exe") + println "***** apk:${apk.name} -> 开始执行apk文件格式对齐 *****" + ExecResult result = exec { + workingDir workDir + commandLine shell, "${apk.absolutePath}", "${newApk.absolutePath}" + } + def code = result.exitValue + if (code == 0) { + println "***** apk:${apk.name} -> apk文件格式对齐完成 *****" + } + + println "***** apk:${newApk.name} -> 对文件格式对齐后的apk重新签名 *****" + result = exec { + workingDir apkSigner.parentFile + commandLine ".${File.separator}${apkSigner.name}", "sign", "-v", + "--out", "${outApk.absolutePath}", + "--ks", "${signConfig.storeFile.absolutePath}", + "--ks-key-alias", "${signConfig.keyAlias}", + "--ks-pass", "pass:${signConfig.storePassword}", + "--key-pass", "pass:${signConfig.keyPassword}", + "--pass-encoding", "utf-8", + "${newApk.absolutePath}" + } + code = result.exitValue + if (code == 0) { + println "***** apk:${newApk.name} -> 签名成功 *****" + } + println "***** 正在删除临时文件 *****" + try { + newApk.delete() + } catch (Throwable t) { + t.printStackTrace() + throw t + } + try { + outApk.renameTo(apk) + } catch (Throwable t) { + t.printStackTrace() + throw t + } + } + } + } + } + } + } + } + } +} + +static boolean isWindows() { + return System.getProperty("os.name").toLowerCase().contains('windows') +} + +static boolean isLinux() { + return System.getProperty("os.name").toLowerCase().contains('linux') +} + +static boolean isMac() { + return System.getProperty("os.name").toLowerCase().contains('mac') +} + +static boolean isOnlineReleaseBuild(Gradle g) { + StringBuilder sb = new StringBuilder() + for (String s : g.startParameter.taskNames) { + sb.append(s) + } + String buildScript = sb.toString().toLowerCase() + return buildScript.endsWith("release") && buildScript.contains("online") +} + + diff --git a/core/function-impl/mogo-core-function-patch/consumer-rules.pro b/core/function-impl/mogo-core-function-patch/consumer-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/core/function-impl/mogo-core-function-patch/jniLibs/arm64-v8a/libapkpatch.so b/core/function-impl/mogo-core-function-patch/jniLibs/arm64-v8a/libapkpatch.so new file mode 100755 index 0000000000..0186fe4bcb Binary files /dev/null and b/core/function-impl/mogo-core-function-patch/jniLibs/arm64-v8a/libapkpatch.so differ diff --git a/core/function-impl/mogo-core-function-patch/jniLibs/armeabi-v7a/libapkpatch.so b/core/function-impl/mogo-core-function-patch/jniLibs/armeabi-v7a/libapkpatch.so new file mode 100755 index 0000000000..7e313c4c17 Binary files /dev/null and b/core/function-impl/mogo-core-function-patch/jniLibs/armeabi-v7a/libapkpatch.so differ diff --git a/core/function-impl/mogo-core-function-patch/jniLibs/x86/libapkpatch.so b/core/function-impl/mogo-core-function-patch/jniLibs/x86/libapkpatch.so new file mode 100755 index 0000000000..ffa122dbf1 Binary files /dev/null and b/core/function-impl/mogo-core-function-patch/jniLibs/x86/libapkpatch.so differ diff --git a/core/function-impl/mogo-core-function-patch/jniLibs/x86_64/libapkpatch.so b/core/function-impl/mogo-core-function-patch/jniLibs/x86_64/libapkpatch.so new file mode 100755 index 0000000000..2a0851f2db Binary files /dev/null and b/core/function-impl/mogo-core-function-patch/jniLibs/x86_64/libapkpatch.so differ diff --git a/core/function-impl/mogo-core-function-patch/proguard-rules.pro b/core/function-impl/mogo-core-function-patch/proguard-rules.pro new file mode 100644 index 0000000000..481bb43481 --- /dev/null +++ b/core/function-impl/mogo-core-function-patch/proguard-rules.pro @@ -0,0 +1,21 @@ +# 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 \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-patch/shell/linux64/ApkNormalized b/core/function-impl/mogo-core-function-patch/shell/linux64/ApkNormalized new file mode 100755 index 0000000000..74704b3c1c Binary files /dev/null and b/core/function-impl/mogo-core-function-patch/shell/linux64/ApkNormalized differ diff --git a/core/function-impl/mogo-core-function-patch/shell/macos/ApkNormalized b/core/function-impl/mogo-core-function-patch/shell/macos/ApkNormalized new file mode 100755 index 0000000000..a95e81ce0a Binary files /dev/null and b/core/function-impl/mogo-core-function-patch/shell/macos/ApkNormalized differ diff --git a/core/function-impl/mogo-core-function-patch/shell/windows64/ApkNormalized.exe b/core/function-impl/mogo-core-function-patch/shell/windows64/ApkNormalized.exe new file mode 100755 index 0000000000..3f7b360934 Binary files /dev/null and b/core/function-impl/mogo-core-function-patch/shell/windows64/ApkNormalized.exe differ diff --git a/core/function-impl/mogo-core-function-patch/src/androidTest/java/com/mogo/launcer/patch/ExampleInstrumentedTest.kt b/core/function-impl/mogo-core-function-patch/src/androidTest/java/com/mogo/launcer/patch/ExampleInstrumentedTest.kt new file mode 100644 index 0000000000..9390da3399 --- /dev/null +++ b/core/function-impl/mogo-core-function-patch/src/androidTest/java/com/mogo/launcer/patch/ExampleInstrumentedTest.kt @@ -0,0 +1,21 @@ +package com.mogo.launcer.patch + +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.mogo.launcer.patch.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-patch/src/main/AndroidManifest.xml b/core/function-impl/mogo-core-function-patch/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..7c2ffe86eb --- /dev/null +++ b/core/function-impl/mogo-core-function-patch/src/main/AndroidManifest.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-patch/src/main/java/com/github/sisong/ApkPatch.java b/core/function-impl/mogo-core-function-patch/src/main/java/com/github/sisong/ApkPatch.java new file mode 100755 index 0000000000..1b5452fd29 --- /dev/null +++ b/core/function-impl/mogo-core-function-patch/src/main/java/com/github/sisong/ApkPatch.java @@ -0,0 +1,12 @@ +package com.github.sisong; + +public class ApkPatch{ + + static { + System.loadLibrary("apkpatch"); + } + + // return TPatchResult, 0 is ok; patchFilePath file created by ZipDiff; + public static native int patch(String oldApkPath,String patchFilePath,String outNewApkPath, + long maxUncompressMemory,String tempUncompressFilePath,int threadNum); +} diff --git a/core/function-impl/mogo-core-function-patch/src/main/java/com/mogo/launcher/patch/PatchManager.kt b/core/function-impl/mogo-core-function-patch/src/main/java/com/mogo/launcher/patch/PatchManager.kt new file mode 100644 index 0000000000..2474c0e784 --- /dev/null +++ b/core/function-impl/mogo-core-function-patch/src/main/java/com/mogo/launcher/patch/PatchManager.kt @@ -0,0 +1,80 @@ +package com.mogo.launcher.patch + +import android.content.* +import android.text.TextUtils +import android.util.* +import com.github.sisong.* +import com.mogo.eagle.core.utilcode.util.AppUtils +import com.mogo.launcher.patch.utils.* +import java.io.File + +internal object PatchManager { + + private const val TAG = "PatchManager" + fun isPatchAccept(context: Context, expectSourceMd5: String, patchSize: Long): Boolean { + val apkMd5 = AppUtils.getAppApkMd5() + if (TextUtils.isEmpty(apkMd5)) { + return false + } + if (apkMd5 != expectSourceMd5) { + return false + } + return true + } + + fun applyPatch(ctx: Context, patch: File, newApk: File): Boolean { + val oldApkPath = ctx.packageManager.getPackageInfo(ctx.packageName, 0).applicationInfo.sourceDir + if (TextUtils.isEmpty(oldApkPath)) { + throw AssertionError("旧的apk文件的文件路径为空.") + } + val oldApk = File(oldApkPath) + Log.d(TAG, "applyPatch -- 1 --:oldApk -> ${oldApk.absolutePath}, patch: -> ${patch.absolutePath}, newApk: ${newApk.absolutePath}") + if (!oldApk.exists()) { + throw AssertionError("旧的apk文件不存在, 文件所在路径:${oldApk.absolutePath}") + } + Log.d(TAG, "applyPatch -- 2 --") + + if (!patch.exists()) { + throw AssertionError("差分包文件不存在,文件所在路径:${patch.absolutePath}") + } + Log.d(TAG, "applyPatch -- 3 --") + val newApkParent = newApk.parentFile + if (newApkParent != null && !newApkParent.exists()) { + val ret = newApkParent.mkdirs() + if (!ret) { + throw AssertionError("新包创建父目录失败") + } + } + Log.d(TAG, "applyPatch -- 4 --") + if (newApk.exists()) { + newApk.delete() + } + if (newApkParent == null) { + throw AssertionError("新包父目录为空") + } + Log.d(TAG, "applyPatch -- 5 --") + val tempFilePath = File(ctx.getExternalFilesDir(null), "temp/temp.patch") + + if (tempFilePath.exists()) { + tempFilePath.delete() + } + tempFilePath.parentFile?.takeIf { !it.exists() }?.mkdirs() + val result = ApkPatch.patch(oldApk.absolutePath, patch.absolutePath, newApk.absolutePath, 4 * 1024 * 1024, "", 4) + Log.d(TAG, "applyPatch -- end ---: result: $result") + if (result != 0) { + throw AssertionError("文件合成失败") + } + return true + } + + fun checkMd5ForMergedApk(mergedApk: File, expectNewApkMd5: String): Boolean { + if (!mergedApk.exists()) { + return false + } + val md5 = Md5Util.getMd5FromFile(mergedApk) + if (md5 != expectNewApkMd5) { + return false + } + return true + } +} \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-patch/src/main/java/com/mogo/launcher/patch/provider/MoGoPatchProviderImpl.kt b/core/function-impl/mogo-core-function-patch/src/main/java/com/mogo/launcher/patch/provider/MoGoPatchProviderImpl.kt new file mode 100644 index 0000000000..682b63e3a1 --- /dev/null +++ b/core/function-impl/mogo-core-function-patch/src/main/java/com/mogo/launcher/patch/provider/MoGoPatchProviderImpl.kt @@ -0,0 +1,33 @@ +package com.mogo.launcher.patch.provider + +import android.content.* +import com.alibaba.android.arouter.facade.annotation.Route +import com.mogo.eagle.core.data.constants.MogoServicePaths +import com.mogo.eagle.core.function.api.patch.* +import com.mogo.launcher.patch.* +import java.io.* + + +@Route(path = MogoServicePaths.PATH_PATCH_UPGRADE) +class MoGoPatchProviderImpl : IMoGoPatchProvider { + + + override fun isPatchAccept(context: Context, expectOldApkMd5: String, patchSize: Long): Boolean { + return PatchManager.isPatchAccept(context, expectOldApkMd5, patchSize) + } + + override fun applyPatch(context: Context, patch: File, newApk: File): Boolean { + return try { + PatchManager.applyPatch(context, patch, newApk) + } catch (t: Throwable) { + t.printStackTrace() + false + } + } + + override fun checkMd5ForMergedApk(context: Context, mergedApk: File, expectNewApkMd5: String): Boolean { + return PatchManager.checkMd5ForMergedApk(mergedApk, expectNewApkMd5) + } + + override fun init(context: Context?) { } +} \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-patch/src/main/java/com/mogo/launcher/patch/utils/Md5Util.kt b/core/function-impl/mogo-core-function-patch/src/main/java/com/mogo/launcher/patch/utils/Md5Util.kt new file mode 100644 index 0000000000..d0ac1c76ea --- /dev/null +++ b/core/function-impl/mogo-core-function-patch/src/main/java/com/mogo/launcher/patch/utils/Md5Util.kt @@ -0,0 +1,42 @@ +package com.mogo.launcher.patch.utils + +import java.io.* +import java.nio.channels.FileChannel.MapMode +import java.security.* + +class Md5Util { + + companion object { + + /** + * 获取单个文件的MD5值! + * @param file + * @return + * 解决首位0被省略问题 + */ + fun getMd5FromFile(file: File): String? { + var stringbuffer: StringBuffer? = null + try { + val hexDigits = charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f') + val input = FileInputStream(file) + val ch = input.channel + val byteBuffer = ch.map(MapMode.READ_ONLY, 0, file.length()) + val digest = MessageDigest.getInstance("MD5") + digest.update(byteBuffer) + val bytes = digest.digest() + val n = bytes.size + stringbuffer = StringBuffer(2 * n) + for (l in 0 until n) { + val bt = bytes[l] + val c0 = hexDigits[bt.toInt() and 0xf0 shr 4] + val c1 = hexDigits[bt.toInt() and 0xf] + stringbuffer.append(c0) + stringbuffer.append(c1) + } + } catch (e: java.lang.Exception) { + e.printStackTrace() + } + return stringbuffer?.toString() + } + } +} \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-patch/src/test/java/com/mogo/launcer/patch/ExampleUnitTest.kt b/core/function-impl/mogo-core-function-patch/src/test/java/com/mogo/launcer/patch/ExampleUnitTest.kt new file mode 100644 index 0000000000..bf47ec6404 --- /dev/null +++ b/core/function-impl/mogo-core-function-patch/src/test/java/com/mogo/launcer/patch/ExampleUnitTest.kt @@ -0,0 +1,16 @@ +package com.mogo.launcer.patch + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/config/FunctionBuildConfig.kt b/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/config/FunctionBuildConfig.kt index c02056061b..dc59d5ff33 100644 --- a/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/config/FunctionBuildConfig.kt +++ b/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/config/FunctionBuildConfig.kt @@ -212,4 +212,12 @@ object FunctionBuildConfig { @Volatile @JvmField var unableLaunchAutopilotGear: Set? = null + + /** + * 当前应用是否支持patch升级 + */ + @Volatile + @JvmField + var isSupportPatchUpgrade = true + } \ No newline at end of file diff --git a/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/constants/MogoServicePaths.java b/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/constants/MogoServicePaths.java index e7ef1a087a..2eb6a3e1bc 100644 --- a/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/constants/MogoServicePaths.java +++ b/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/constants/MogoServicePaths.java @@ -98,4 +98,10 @@ public class MogoServicePaths { @Keep public static final String PATH_VISUAL_ANGLE = "/map/angle_change"; + + @Keep + public static final String PATH_PATCH_UPGRADE = "/patch/api"; + + + public static final String PATH_UPGRADE_TYPE_API = "/upgrade/type/api"; } diff --git a/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/deva/bindingcar/AppInfo.java b/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/deva/bindingcar/AppInfo.java index eedfacae3a..6d0d96e468 100644 --- a/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/deva/bindingcar/AppInfo.java +++ b/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/deva/bindingcar/AppInfo.java @@ -1,6 +1,9 @@ package com.mogo.eagle.core.data.deva.bindingcar; +import com.google.gson.annotations.SerializedName; + import java.io.Serializable; +import java.util.Objects; /** * @author lixiaopeng @@ -19,6 +22,9 @@ public class AppInfo implements Serializable { private String endTime; private String appFileName; + @SerializedName("patchInfoView") + public PatchInfo patchInfo; + public String getAppUrl() { return appUrl; } @@ -111,7 +117,155 @@ public class AppInfo implements Serializable { ", installType='" + installType + '\'' + ", beginTime='" + beginTime + '\'' + ", endTime='" + endTime + '\'' + + ", patchInfo=" + patchInfo + ", appFileName='" + appFileName + '\'' + '}'; } + + public static class PatchInfo implements Serializable { + + private Integer sourceId; + + private String sourceMd5; + + private String sourceVersion; + + private Integer targetId; + + private String targetMd5; + + private String targetVersion; + + private String patchName; + + private String patchLocalPath; + + private String patchMd5; + + private String patchDownloadUrl; + + private Integer patchSize; + + private Integer patchStatus; + + + public Integer getSourceId() { + return sourceId; + } + + public void setSourceId(Integer sourceId) { + this.sourceId = sourceId; + } + + public String getSourceMd5() { + return sourceMd5; + } + + public void setSourceMd5(String sourceMd5) { + this.sourceMd5 = sourceMd5; + } + + public String getSourceVersion() { + return sourceVersion; + } + + public void setSourceVersion(String sourceVersion) { + this.sourceVersion = sourceVersion; + } + + public Integer getTargetId() { + return targetId; + } + + public void setTargetId(Integer targetId) { + this.targetId = targetId; + } + + public String getTargetMd5() { + return targetMd5; + } + + public void setTargetMd5(String targetMd5) { + this.targetMd5 = targetMd5; + } + + public String getTargetVersion() { + return targetVersion; + } + + public void setTargetVersion(String targetVersion) { + this.targetVersion = targetVersion; + } + + public String getPatchName() { + return patchName; + } + + public void setPatchName(String patchName) { + this.patchName = patchName; + } + + public String getPatchLocalPath() { + return patchLocalPath; + } + + public void setPatchLocalPath(String patchLocalPath) { + this.patchLocalPath = patchLocalPath; + } + + public String getPatchMd5() { + return patchMd5; + } + + public void setPatchMd5(String patchMd5) { + this.patchMd5 = patchMd5; + } + + public String getPatchDownloadUrl() { + return patchDownloadUrl; + } + + public void setPatchDownloadUrl(String patchDownloadUrl) { + this.patchDownloadUrl = patchDownloadUrl; + } + + public Integer getPatchSize() { + return patchSize; + } + + public Integer getPatchStatus() { + return patchStatus; + } + + @Override + public String toString() { + return "PatchInfo{" + + "sourceId=" + sourceId + + ", sourceMd5='" + sourceMd5 + '\'' + + ", sourceVersion='" + sourceVersion + '\'' + + ", targetId=" + targetId + + ", targetMd5='" + targetMd5 + '\'' + + ", targetVersion='" + targetVersion + '\'' + + ", patchName='" + patchName + '\'' + + ", patchLocalPath='" + patchLocalPath + '\'' + + ", patchMd5='" + patchMd5 + '\'' + + ", patchDownloadUrl='" + patchDownloadUrl + '\'' + + ", patchSize=" + patchSize + + ", patchStatus=" + patchStatus + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PatchInfo patchInfo = (PatchInfo) o; + return patchMd5.equals(patchInfo.patchMd5); + } + + @Override + public int hashCode() { + return Objects.hash(patchMd5); + } + } } diff --git a/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/devatools/IDevaToolsProvider.kt b/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/devatools/IDevaToolsProvider.kt index 3a4835bb04..f303d8f8ab 100644 --- a/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/devatools/IDevaToolsProvider.kt +++ b/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/devatools/IDevaToolsProvider.kt @@ -12,6 +12,8 @@ import com.mogo.eagle.core.data.deva.scene.SceneModule import com.mogo.eagle.core.data.deva.scene.SceneTAG import com.mogo.eagle.core.data.msgbox.MsgBoxBean import com.mogo.eagle.core.function.api.devatools.apm.* +import com.mogo.eagle.core.function.api.devatools.download.* +import com.mogo.eagle.core.function.api.upgrade.* /** * 开发套件工具接口 @@ -118,7 +120,7 @@ interface IDevaToolsProvider : IProvider { /** * 下载指定包 */ - fun downLoadPackage(downloadKey: String, downloadUrl: String) + fun downLoadPackage(type: DownloadType, downloadKey: String, downloadUrl: String) /** * 更新下载进度 @@ -195,4 +197,9 @@ interface IDevaToolsProvider : IProvider { fun dockerVersion(dockerVersion: String?) fun apmEnvProvider(): IApmEnvProvider + + /** + * 升级相关状态查询 + */ + fun upgradeProvider(): IMoGoUpgradeProvider? } \ No newline at end of file diff --git a/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/devatools/download/DownloadType.kt b/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/devatools/download/DownloadType.kt new file mode 100644 index 0000000000..438e241be1 --- /dev/null +++ b/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/devatools/download/DownloadType.kt @@ -0,0 +1,7 @@ +package com.mogo.eagle.core.function.api.devatools.download + +enum class DownloadType { + APK, + OBU, + PATCH +} \ No newline at end of file diff --git a/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/patch/IMoGoPatchProvider.kt b/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/patch/IMoGoPatchProvider.kt new file mode 100644 index 0000000000..1c086b1ad5 --- /dev/null +++ b/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/patch/IMoGoPatchProvider.kt @@ -0,0 +1,35 @@ +package com.mogo.eagle.core.function.api.patch + +import android.content.Context +import com.alibaba.android.arouter.facade.template.IProvider +import java.io.* + +interface IMoGoPatchProvider : IProvider { + + + /** + * @param context 上下文环境 + * @param expectOldApkMd5 服务端返回的旧apk文件的md5 + * @param patchSize patch文件大小 + * @return true: 本地apk与服务端旧apk版本的md5文件一致; false: 本地apk与服务端旧apk版本md5不一致 + */ + fun isPatchAccept(context: Context, expectOldApkMd5: String, patchSize: Long): Boolean + + + /** + * @param context 上下文环境 + * @param patch 已下载文件的patch路径 + * @param newApk 合成后文件的文件路径 + * @return true: patch与旧apk文件合成成功; false: patch与旧apk文件合成失败 + */ + fun applyPatch(context: Context, patch: File, newApk: File): Boolean + + + /** + * @param context 上下文环境 + * @param mergedApk patch合成后的文件路径 + * @param expectNewApkMd5 期望合成后文件的md5值 + * @return true: md5校验成功; false: md5校验失败 + */ + fun checkMd5ForMergedApk(context: Context, mergedApk: File, expectNewApkMd5: String): Boolean +} \ No newline at end of file diff --git a/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/upgrade/IMoGoUpgradeProvider.kt b/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/upgrade/IMoGoUpgradeProvider.kt new file mode 100644 index 0000000000..aee79e7410 --- /dev/null +++ b/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/upgrade/IMoGoUpgradeProvider.kt @@ -0,0 +1,52 @@ +package com.mogo.eagle.core.function.api.upgrade + +import com.alibaba.android.arouter.facade.template.IProvider + +interface IMoGoUpgradeProvider: IProvider { + + suspend fun recordUpgradeRecord(newVersion: String,newMd5: String?, type: Int) + + suspend fun recordDownloadStart() + + suspend fun recordDownloadFailed(error: String) + + suspend fun recordDownloadSuccess() + + suspend fun recordInstallStart() + + suspend fun recordSourceMd5CheckStart() + + suspend fun recordSourceMd5CheckFailed(error: String) + + suspend fun recordSourceMd5CheckSuccess() + + suspend fun recordInstallApplyPatchStart() + + suspend fun recordInstallApplyPatchFailed(error: String) + + suspend fun recordInstallApplyPatchSuccess() + + suspend fun recordTargetMd5CheckStart() + + suspend fun recordTargetMd5CheckFailed(error: String) + + suspend fun recordTargetMd5CheckSuccess() + + suspend fun recordInstallFailed(code: Int, error: String) + + suspend fun isUpgradeSuccessByPatch(): Boolean + + suspend fun isUpgradeSuccessByFull(): Boolean + + suspend fun getFullUpgradeFailedReason(): Map>>? + + suspend fun getPatchUpgradeFailedReason(): Map>>? + + suspend fun removeRecordByTargetVersion(version: String) + + suspend fun removeRecordBySourceVersion(version: String) + + suspend fun hasUpgradeRecord(): Boolean + + suspend fun isNeedGoPatchUpgrade(): Boolean +} \ No newline at end of file diff --git a/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/devatools/CallerDevaToolsManager.kt b/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/devatools/CallerDevaToolsManager.kt index 0cc3944c46..2f6c51a71f 100644 --- a/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/devatools/CallerDevaToolsManager.kt +++ b/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/devatools/CallerDevaToolsManager.kt @@ -14,6 +14,8 @@ import com.mogo.eagle.core.data.deva.scene.SceneTAG import com.mogo.eagle.core.data.msgbox.MsgBoxBean import com.mogo.eagle.core.function.api.devatools.IDevaToolsProvider import com.mogo.eagle.core.function.api.devatools.apm.* +import com.mogo.eagle.core.function.api.devatools.download.* +import com.mogo.eagle.core.function.api.upgrade.* import com.mogo.eagle.core.function.call.base.CallerBase import com.mogo.eagle.core.utilcode.mogo.AppIdentityModeUtils @@ -158,8 +160,8 @@ object CallerDevaToolsManager { /** * 下载指定包 */ - fun downLoadPackage(downloadKey: String, downloadUrl: String) { - devaToolsProviderApi?.downLoadPackage(downloadKey, downloadUrl) + fun downLoadPackage(type: DownloadType, downloadKey: String, downloadUrl: String) { + devaToolsProviderApi?.downLoadPackage(type, downloadKey, downloadUrl, ) } /** @@ -248,4 +250,6 @@ object CallerDevaToolsManager { fun queryObuUpgrade(obuVersionName: String) { devaToolsProviderApi?.queryObuUpgrade(obuVersionName) } + + fun upgradeProvider(): IMoGoUpgradeProvider? = devaToolsProviderApi?.upgradeProvider() } \ No newline at end of file diff --git a/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/patch/CallerPatchManager.kt b/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/patch/CallerPatchManager.kt new file mode 100644 index 0000000000..641f963675 --- /dev/null +++ b/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/patch/CallerPatchManager.kt @@ -0,0 +1,61 @@ +package com.mogo.eagle.core.function.call.patch + +import android.content.* +import com.mogo.eagle.core.data.constants.MogoServicePaths +import com.mogo.eagle.core.data.deva.bindingcar.AppInfo.PatchInfo +import com.mogo.eagle.core.function.api.patch.* +import com.mogo.eagle.core.function.call.base.CallerBase +import java.io.* +import java.util.concurrent.CopyOnWriteArraySet + +object CallerPatchManager { + + private val patchInfo by lazy { CopyOnWriteArraySet() } + + private val provider: IMoGoPatchProvider? by lazy { + CallerBase.getApiInstance(IMoGoPatchProvider::class.java, MogoServicePaths.PATH_PATCH_UPGRADE) + } + + /** + * @param context 上下文环境 + * @param expectOldApkMd5 服务端返回的旧apk文件的md5 + * @return true: 本地apk与服务端旧apk版本的md5文件一致; false: 本地apk与服务端旧apk版本md5不一致 + */ + fun isPatchAccept(context: Context, expectOldApkMd5: String, patchSize: Long): Boolean { + return provider?.isPatchAccept(context, expectOldApkMd5, patchSize) ?: false + } + + /** + * @param context 上下文环境 + * @param patch 已下载文件的patch路径 + * @param newApk 合成后文件的文件路径 + * @return true: patch与旧apk文件合成成功; false: patch与旧apk文件合成失败 + */ + fun applyPatch(context: Context, patch: File, newApk: File): Boolean { + return provider?.applyPatch(context, patch, newApk) ?: false + } + + /** + * @param context 上下文环境 + * @param mergedApk patch合成后的文件路径 + * @param expectNewApkMd5 期望合成后文件的md5值 + * @return true: md5校验成功; false: md5校验失败 + */ + fun checkMd5ForMergedApk(context: Context, mergedApk: File, expectNewApkMd5: String): Boolean { + return provider?.checkMd5ForMergedApk(context, mergedApk, expectNewApkMd5) ?: false + } + + fun addPatchInfo(info: PatchInfo) { + patchInfo += info + } + + fun getPatchInfoByUrl(patchUrl: String): PatchInfo? = patchInfo.find { it.patchDownloadUrl == patchUrl } + + fun removePatchInfoByUrl(patchUrl: String) { + try { + patchInfo.remove(patchInfo.find { it.patchDownloadUrl == patchUrl }) + } catch (t: Throwable) { + t.printStackTrace() + } + } +} \ No newline at end of file diff --git a/core/mogo-core-res/src/main/res/xml/provider_paths.xml b/core/mogo-core-res/src/main/res/xml/provider_paths.xml new file mode 100644 index 0000000000..f6f82da9b5 --- /dev/null +++ b/core/mogo-core-res/src/main/res/xml/provider_paths.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/core/mogo-core-utils/src/main/AndroidManifest.xml b/core/mogo-core-utils/src/main/AndroidManifest.xml index 70033d6f31..6f47c81c15 100644 --- a/core/mogo-core-utils/src/main/AndroidManifest.xml +++ b/core/mogo-core-utils/src/main/AndroidManifest.xml @@ -37,5 +37,11 @@ + + + + + \ No newline at end of file diff --git a/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/breakpoint/services/DownloadService.java b/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/breakpoint/services/DownloadService.java index 829b9b76c1..e0ac091160 100644 --- a/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/breakpoint/services/DownloadService.java +++ b/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/breakpoint/services/DownloadService.java @@ -73,6 +73,7 @@ public class DownloadService implements InitThread.InitCallBack, DownloadCallBac FileBean fileBean = (FileBean) intent.getSerializableExtra("FileBean"); if (fileBean == null) { + Log.e(DOWN_LOAD_TAG, "onStartCommand bean is null"); return; } diff --git a/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/breakpoint/thread/DownloadTask.java b/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/breakpoint/thread/DownloadTask.java index 39604be8b1..f541f5c33f 100644 --- a/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/breakpoint/thread/DownloadTask.java +++ b/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/breakpoint/thread/DownloadTask.java @@ -12,6 +12,7 @@ import com.mogo.eagle.core.utilcode.breakpoint.db.dao.ThreadDao; import com.mogo.eagle.core.utilcode.breakpoint.db.impl.ThreadDaoImpl; import com.mogo.eagle.core.utilcode.breakpoint.event.DownloadData; import com.mogo.eagle.core.utilcode.breakpoint.services.DownloadService; +import com.mogo.eagle.core.utilcode.util.UiThreadHandler; import java.util.ArrayList; import java.util.List; @@ -73,7 +74,7 @@ public class DownloadTask implements DownloadCallBack { DownloadService.executorService.execute(downloadThread); downloadThreads.add(downloadThread); } - downloadCallBack.startDownload(fileBean.getUrl()); + UiThreadHandler.post(() -> downloadCallBack.startDownload(fileBean.getUrl())); Log.d(DOWN_LOAD_TAG, " 开始下载:" + finishedProgress); } @@ -100,7 +101,7 @@ public class DownloadTask implements DownloadCallBack { @Override public void startDownload(String url) { - downloadCallBack.startDownload(url); + UiThreadHandler.post(() -> downloadCallBack.startDownload(url)); } @Override @@ -108,7 +109,7 @@ public class DownloadTask implements DownloadCallBack { //保存下载进度到数据库 Log.d(DOWN_LOAD_TAG, "保存数据:" + threadBean.toString()); dao.updateThread(threadBean.getUrl(), threadBean.getId(), threadBean.getFinished()); - downloadCallBack.pauseCallBack(url,threadBean); + UiThreadHandler.post(() -> downloadCallBack.pauseCallBack(url,threadBean)); } private long curTime = 0; @@ -117,18 +118,17 @@ public class DownloadTask implements DownloadCallBack { public void progressCallBack(String url, int length) { finishedProgress += length; //每500毫秒发送刷新进度事件 + Log.d(DOWN_LOAD_TAG, "process:" + finishedProgress); if (System.currentTimeMillis() - curTime > 500 || finishedProgress == fileBean.getLength()) { int progress = (int) (finishedProgress * 1.0 / fileBean.getLength() * 100); -// Log.d(DOWN_LOAD_TAG, "DownloadTask ----length = " + length + " ---progress = " + progress); + Log.d(DOWN_LOAD_TAG, "DownloadTask ----length = " + length + " ---progress = " + progress); fileBean.setFinished(finishedProgress); DownloadData downloadData = new DownloadData(); downloadData.setUrl(fileBean.getUrl()); downloadData.setProgress(progress); downloadData.setLength(fileBean.getLength()); downloadData.setMsg("下载进度回调"); - - downloadCallBack.progressCallBack(url,progress); - + UiThreadHandler.post(() -> downloadCallBack.progressCallBack(url,progress)); curTime = System.currentTimeMillis(); } } @@ -151,8 +151,7 @@ public class DownloadTask implements DownloadCallBack { downloadData.setUrl(fileBean.getUrl()); downloadData.setMsg("下载完成"); downloadData.setFilePath(fileBean.getSavePath() + fileBean.getFileName()); - - downloadCallBack.threadDownLoadFinished(url,threadBean); + UiThreadHandler.post(() -> downloadCallBack.threadDownLoadFinished(url,threadBean)); } } diff --git a/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/breakpoint/thread/DownloadThread.java b/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/breakpoint/thread/DownloadThread.java index 77be24236a..86f726ca98 100644 --- a/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/breakpoint/thread/DownloadThread.java +++ b/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/breakpoint/thread/DownloadThread.java @@ -10,6 +10,8 @@ import com.mogo.eagle.core.utilcode.breakpoint.bean.FileBean; import com.mogo.eagle.core.utilcode.breakpoint.bean.ThreadBean; import com.mogo.eagle.core.utilcode.breakpoint.callback.DownloadCallBack; import com.mogo.eagle.core.utilcode.breakpoint.event.DownloadData; +import com.mogo.eagle.core.utilcode.util.ThreadUtils; +import com.mogo.eagle.core.utilcode.util.UiThreadHandler; import java.io.File; import java.io.InputStream; @@ -63,21 +65,22 @@ public class DownloadThread extends Thread { while ((len = inputStream.read(bytes))!=-1){ raf.write(bytes,0,len); //将加载的进度回调出去 - callback.progressCallBack(this.fileBean.getUrl(),len); + callback.progressCallBack(fileBean.getUrl(), len); //保存进度 threadBean.setFinished(threadBean.getFinished()+len); //在下载暂停的时候将下载进度保存到数据库 if(isPause){ - callback.pauseCallBack(this.fileBean.getUrl(),threadBean); + UiThreadHandler.post(() -> callback.pauseCallBack(this.fileBean.getUrl(),threadBean)); return; } } //下载完成 - callback.threadDownLoadFinished(this.fileBean.getUrl(),threadBean); + UiThreadHandler.post(() -> callback.threadDownLoadFinished(this.fileBean.getUrl(),threadBean)); } } catch (Exception e) { e.printStackTrace(); + Log.e(DOWN_LOAD_TAG, "error: " + e.getMessage()); DownloadData downloadData = new DownloadData(); downloadData.setUrl(fileBean.getUrl()); downloadData.setMsg(e.getMessage()); @@ -90,6 +93,7 @@ public class DownloadThread extends Thread { connection.disconnect(); }catch (Exception e){ e.printStackTrace(); + Log.e(DOWN_LOAD_TAG, "error2: " + e.getMessage()); DownloadData downloadData = new DownloadData(); downloadData.setUrl(fileBean.getUrl()); downloadData.setMsg(e.getMessage()); diff --git a/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/util/ApkInstaller.kt b/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/util/ApkInstaller.kt new file mode 100644 index 0000000000..0ea6b4073f --- /dev/null +++ b/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/util/ApkInstaller.kt @@ -0,0 +1,164 @@ +package com.mogo.eagle.core.utilcode.util + +import android.annotation.* +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentSender +import android.content.pm.PackageInstaller +import android.content.pm.PackageInstaller.Session +import android.content.pm.PackageInstaller.SessionCallback +import android.content.pm.PackageInstaller.SessionParams +import android.os.* +import android.util.Log +import androidx.lifecycle.ProcessLifecycleOwner +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.* +import java.io.File +import java.io.FileInputStream +import java.io.InputStream +import java.lang.ref.WeakReference +import java.util.concurrent.ConcurrentHashMap + +class ApkInstaller { + + companion object { + + private const val TAG = "ApkInstaller" + + const val INSTALL_CODE_INVLID = -2 + + @JvmStatic + fun installApp(context: Context, apkFile: File, block: ((Int, String) -> Unit)?) { + if (!apkFile.exists()) { + val msg = "要安装的apk文件不存在" + block?.invoke(INSTALL_CODE_INVLID, msg) + return + } + val isApk = try { + context.packageManager.getPackageArchiveInfo(apkFile.absolutePath, 0)?.versionName != null + } catch (t: Throwable) { + false + } + if (!isApk) { + val msg = "文件: ${apkFile.absolutePath}, 不是一个有效的安装包" + block?.invoke(INSTALL_CODE_INVLID, msg) + return + } + installApp(context, FileInputStream(apkFile), block) + } + + @JvmStatic + fun installApp(context: Context, input: InputStream, block: ((Int, String) -> Unit)?) { + ProcessLifecycleOwner.get().lifecycleScope.launch(Dispatchers.IO) { + var session: Session? = null + try { + val installer = context.packageManager.packageInstaller + installer.registerSessionCallback(object : SessionCallback() { + override fun onCreated(sessionId: Int) { + Log.d(TAG, "onCreate: sessionId -> $sessionId") + } + override fun onBadgingChanged(sessionId: Int) { + Log.d(TAG, "onBadgingChanged: sessionId -> $sessionId") + } + + override fun onActiveChanged(sessionId: Int, active: Boolean) { + Log.d(TAG, "onActiveChanged: sessionId -> $sessionId, active: $active") + } + + override fun onProgressChanged(sessionId: Int, progress: Float) { + Log.d(TAG, "onProgressChanged: sessionId -> $sessionId, process: $progress") + } + + override fun onFinished(sessionId: Int, success: Boolean) { + Log.d(TAG, "onFinished: sessionId -> $sessionId, success: $success") + } + }, Handler(Looper.getMainLooper())) + val params = SessionParams(SessionParams.MODE_FULL_INSTALL) + val sessionId = installer.createSession(params) + session = installer.openSession(sessionId) + val output = session.openWrite("install.apk", 0, -1) + output.use { o -> + input.copyTo(o) + } + withContext(Dispatchers.Main) { + session.commit(createIntent(context, sessionId)) + } + block?.also { + AppInstallReceiver.listeners[sessionId] = WeakReference(it) + } + } catch (t: Throwable) { + block?.invoke(-2, t.message ?: "未知异常") + session?.close() + } + } + } + + @SuppressLint("UnspecifiedImmutableFlag") + private fun createIntent(context: Context, sessionId: Int): IntentSender { + val intent = Intent(context, AppInstallReceiver::class.java) + intent.action = AppInstallReceiver.INTENT_ACTION_SESSION_INSTALL + intent.putExtra(AppInstallReceiver.INTENT_EXTRA_SESSION_ID, sessionId) + return PendingIntent.getBroadcast(context, sessionId, intent, PendingIntent.FLAG_UPDATE_CURRENT).intentSender + } + } +} + +class AppInstallReceiver: BroadcastReceiver() { + + companion object { + const val TAG = "AppInstallReceiver" + const val INTENT_ACTION_SESSION_INSTALL = "com.mogo.launcher.f.action.SESSION_API_PACKAGE_INSTALLED" + const val INTENT_EXTRA_SESSION_ID = "intent.extra.session_id" + val listeners by lazy { ConcurrentHashMap Unit)>>() } + } + + override fun onReceive(context: Context?, intent: Intent?) { + val c = context ?: return + val i = intent ?: return + val a = i.action ?: return + if (a == INTENT_ACTION_SESSION_INSTALL) { + val e = i.extras ?: return + val sessionId = e.getInt(INTENT_EXTRA_SESSION_ID, 0) + Log.d(TAG, "action -> $a, sessionId: $sessionId") + when(val status = e.getInt(PackageInstaller.EXTRA_STATUS)) { + PackageInstaller.STATUS_PENDING_USER_ACTION -> { + val confirm = e.get(Intent.EXTRA_INTENT) as? Intent + if (confirm != null) { + Log.d(TAG, "confirm -> status:$status, sessionID: $sessionId") + val activityInfo = c.packageManager.resolveActivity(confirm, 0)?.activityInfo + if (activityInfo != null) { + confirm.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + c.startActivity(confirm) + } else { + notifyListeners(status, "action: ${confirm.action} 不存在") + } + } else { + notifyListeners(status, "需要用户确认的应用程序不存在") + } + } + PackageInstaller.STATUS_FAILURE_ABORTED, + PackageInstaller.STATUS_FAILURE, + PackageInstaller.STATUS_FAILURE_BLOCKED, + PackageInstaller.STATUS_FAILURE_CONFLICT, + PackageInstaller.STATUS_FAILURE_INCOMPATIBLE, + PackageInstaller.STATUS_FAILURE_INVALID, + PackageInstaller.STATUS_FAILURE_STORAGE -> { + Log.d(TAG, "failed -> status:$status, sessionID: $sessionId") + notifyListeners(status, "安装失败: $status") + } + else -> { + Log.d(TAG, "other status:$status, sessionID: $sessionId") + notifyListeners(status, "未知状态") + } + } + } + } + + private fun notifyListeners(code: Int, reason: String) { + listeners.values.forEach { + it.get()?.invoke(code, reason) + } + } +} \ No newline at end of file diff --git a/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/util/AppUtils.java b/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/util/AppUtils.java index 4ac13c5cc4..be3d0fe040 100644 --- a/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/util/AppUtils.java +++ b/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/util/AppUtils.java @@ -27,6 +27,7 @@ import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger; import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; /** *
@@ -40,6 +41,12 @@ public final class AppUtils {
 
     private static final String MOGO_MAP_SDK_VERSION = "MAP_SDK_VERSION";
 
+    private static final AtomicReference sVersionCode = new AtomicReference<>();
+
+    private static final AtomicReference sVersionName = new AtomicReference<>();
+
+    private static final AtomicReference sAppApkMd5 = new AtomicReference<>();
+
     private AppUtils() {
         throw new UnsupportedOperationException("u can't instantiate me...");
     }
@@ -180,6 +187,12 @@ public final class AppUtils {
         Utils.getApp().startActivity(installAppIntent);
     }
 
+    public static void installApp(Activity activity, File apk, int requestCode) {
+        Intent installAppIntent = UtilsBridge.getInstallAppIntent(apk);
+        if (installAppIntent == null) return;
+        activity.startActivityForResult(installAppIntent, requestCode);
+    }
+
     /**
      * Install the app.
      * 

Target APIs greater than 25 must hold @@ -556,6 +569,27 @@ public final class AppUtils { } } + public static String getAppApkMd5() { + if (sAppApkMd5.get() != null) { + return sAppApkMd5.get(); + } + try { + String apkPath = Utils.getApp().getPackageManager().getPackageInfo(Utils.getApp().getPackageName(), 0).applicationInfo.sourceDir; + File apk = new File(apkPath); + if (apk.exists()) { + String ret = Md5Util.getMd5FromFile(apk); + if (!TextUtils.isEmpty(ret)) { + sAppApkMd5.set(ret); + } + return ret; + } + } catch (Throwable t) { + t.printStackTrace(); + return ""; + } + return ""; + } + /** * Return the application's version name. * @@ -575,10 +609,17 @@ public final class AppUtils { @NonNull public static String getAppVersionName(final String packageName) { if (UtilsBridge.isSpace(packageName)) return ""; + if (sVersionName.get() != null) { + return sVersionName.get(); + } try { PackageManager pm = Utils.getApp().getPackageManager(); PackageInfo pi = pm.getPackageInfo(packageName, 0); - return pi == null ? "" : pi.versionName; + String s = pi == null ? "" : pi.versionName; + if (!TextUtils.isEmpty(s)) { + sVersionName.set(s); + } + return s; } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); return ""; @@ -602,10 +643,18 @@ public final class AppUtils { */ public static int getAppVersionCode(final String packageName) { if (UtilsBridge.isSpace(packageName)) return -1; + Integer oldVersionCode = sVersionCode.get(); + if (oldVersionCode != null && oldVersionCode > 0) { + return oldVersionCode; + } try { PackageManager pm = Utils.getApp().getPackageManager(); PackageInfo pi = pm.getPackageInfo(packageName, 0); - return pi == null ? -1 : pi.versionCode; + int ret = pi == null ? -1 : pi.versionCode; + if (ret > 0) { + sVersionCode.set(ret); + } + return ret; } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); return -1; diff --git a/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/util/Md5Util.kt b/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/util/Md5Util.kt new file mode 100644 index 0000000000..943a9819a2 --- /dev/null +++ b/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/util/Md5Util.kt @@ -0,0 +1,43 @@ +package com.mogo.eagle.core.utilcode.util + +import java.io.* +import java.nio.channels.FileChannel.MapMode +import java.security.* + +class Md5Util { + + companion object { + + /** + * 获取单个文件的MD5值! + * @param file + * @return + * 解决首位0被省略问题 + */ + @JvmStatic + fun getMd5FromFile(file: File): String? { + var stringbuffer: StringBuffer? = null + try { + val hexDigits = charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f') + val input = FileInputStream(file) + val ch = input.channel + val byteBuffer = ch.map(MapMode.READ_ONLY, 0, file.length()) + val digest = MessageDigest.getInstance("MD5") + digest.update(byteBuffer) + val bytes = digest.digest() + val n = bytes.size + stringbuffer = StringBuffer(2 * n) + for (l in 0 until n) { + val bt = bytes[l] + val c0 = hexDigits[bt.toInt() and 0xf0 shr 4] + val c1 = hexDigits[bt.toInt() and 0xf] + stringbuffer.append(c0) + stringbuffer.append(c1) + } + } catch (e: java.lang.Exception) { + e.printStackTrace() + } + return stringbuffer?.toString() + } + } +} \ No newline at end of file diff --git a/foudations/mogo-commons/src/main/java/com/mogo/commons/constants/HostConst.java b/foudations/mogo-commons/src/main/java/com/mogo/commons/constants/HostConst.java index 62e5713ece..ff72401217 100644 --- a/foudations/mogo-commons/src/main/java/com/mogo/commons/constants/HostConst.java +++ b/foudations/mogo-commons/src/main/java/com/mogo/commons/constants/HostConst.java @@ -21,10 +21,10 @@ public class HostConst { public static final String CMDB_HOST = "http://eagle-mis.zhidaozhixing.com/eagleEye-mis/cmdbapi/"; - public static final String HOST_DEV = "http://eagle-mis-a.zhidaozhixing.com"; + public static final String HOST_DEV = "http://eagle-qa.zhidaozhixing.com"; public static final String HOST_RELEASE = "http://eagle-mis.zhidaozhixing.com"; - public static final String HOST_EAGLE_QA = "http://eagle-dns-a.zhidaozhixing.com/"; + public static final String HOST_EAGLE_QA = "http://eagle-dns-qa.zhidaozhixing.com/"; public static String getHost() { String host = HOST_RELEASE; diff --git a/gradle.properties b/gradle.properties index 96971cae96..db06feefee 100644 --- a/gradle.properties +++ b/gradle.properties @@ -146,3 +146,6 @@ ADAS_DATA_LIB_CHILD_VERSION=.0 # 线程优化版本 THREAD_OPT_VERSION=4.0.0 + +# 是否支持patch升级 +PATCH_UPGRADE_SUPPORT=true \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 253517d62f..9546a88821 100644 --- a/settings.gradle +++ b/settings.gradle @@ -65,3 +65,5 @@ include ':OCH:mogo-och-taxi-passenger' include ':OCH:mogo-och-noop' include(':OCH:mogo-och-common-module') include ':OCH:mogo-och-sweeper' +include ':core:function-impl:mogo-core-function-patch' +include ':core:function-impl:mogo-core-function-patch'