diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 26d3352..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml deleted file mode 100644 index 4a53bee..0000000 --- a/.idea/AndroidProjectSystem.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index 1c5ab05..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml deleted file mode 100644 index 0990919..0000000 --- a/.idea/deploymentTargetSelector.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index da0415a..0000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 4396204..0000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 0822f21..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 16660f1..0000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/FastBleLib/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties b/FastBleLib/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties index 5e7c409..87267b0 100644 --- a/FastBleLib/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +++ b/FastBleLib/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties @@ -1 +1 @@ -#Thu Sep 10 18:18:21 CST 2026 +#Fri Sep 11 15:28:30 CST 2026 diff --git a/app/src/main/java/com/abbidot/tracker/base/BaseActivity.kt b/app/src/main/java/com/abbidot/tracker/base/BaseActivity.kt index 5e1ce0a..bf6e14f 100644 --- a/app/src/main/java/com/abbidot/tracker/base/BaseActivity.kt +++ b/app/src/main/java/com/abbidot/tracker/base/BaseActivity.kt @@ -502,7 +502,7 @@ abstract class BaseActivity(val inflater: (inflater: LayoutInfl } else { tip.length * (1000L / 10) } - if (showTime < 1000) showTime = 1500 + if (showTime < 2000) showTime = 2000 if (showTime > 8000) showTime = 8000 mViewBinding.root.postDelayed({ if (!isFinishing) { @@ -920,7 +920,7 @@ abstract class BaseActivity(val inflater: (inflater: LayoutInfl mTranslateView = translateView val r = Rect() translateView.getWindowVisibleDisplayFrame(r) - val heightDiff = translateView.getRootView().height - (r.bottom - r.top) + val heightDiff = translateView.rootView.height - (r.bottom - r.top) if (heightDiff > 200) { // 如果高度差大于200,说明键盘是弹出的 // 进行布局调整 if (!isKeyboardTranslate) { diff --git a/app/src/main/java/com/abbidot/tracker/retrofit2/INetworkService.kt b/app/src/main/java/com/abbidot/tracker/retrofit2/INetworkService.kt index 4c0e153..773c0e2 100644 --- a/app/src/main/java/com/abbidot/tracker/retrofit2/INetworkService.kt +++ b/app/src/main/java/com/abbidot/tracker/retrofit2/INetworkService.kt @@ -95,6 +95,17 @@ interface INetworkService { // @Url 一定要传入以“/”结尾注解会替换 baseUrl,每次调用可传入完整 URL suspend fun blePassThrough(@Url url: String, @Body body: RequestBody): Response + /** + * 检查apk包状态 + */ + @FormUrlEncoded + @POST("user/fetchPackageCheckStatus") + suspend fun checkPackageStatus( + @Field("packageName") packageName: String, + @Field("signSha256") signSha256: String, + @Field("nonce") nonce: String + ): BaseResponse + /** * 获取国家代码号 */ diff --git a/app/src/main/java/com/abbidot/tracker/retrofit2/NetworkApi.kt b/app/src/main/java/com/abbidot/tracker/retrofit2/NetworkApi.kt index e98473f..dc37b20 100644 --- a/app/src/main/java/com/abbidot/tracker/retrofit2/NetworkApi.kt +++ b/app/src/main/java/com/abbidot/tracker/retrofit2/NetworkApi.kt @@ -834,6 +834,14 @@ object NetworkApi : BaseNetworkApi(INetworkService.BASE_URL) { */ suspend fun blePassThrough(url: String, body: RequestBody) = service.blePassThrough(url, body) + /** + * 检查apk包状态 + */ + suspend fun checkPackageStatus(packageName: String, signSha256: String, nonce: String) = + getResult { + service.checkPackageStatus(packageName, signSha256, nonce) + } + /** * 取消自动续费/自动订阅 */ diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/HomeV2Activity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/HomeV2Activity.kt index a275cbe..9314c74 100644 --- a/app/src/main/java/com/abbidot/tracker/ui/activity/HomeV2Activity.kt +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/HomeV2Activity.kt @@ -52,6 +52,7 @@ import com.abbidot.tracker.ui.fragment.pet.PetV2Fragment import com.abbidot.tracker.util.Util import com.abbidot.tracker.util.ViewUtil import com.abbidot.tracker.util.bluetooth.SRBleUtil +import com.abbidot.tracker.vm.AppSetViewModel import com.abbidot.tracker.vm.ConnectionDeviceViewModel import com.abbidot.tracker.vm.CountDownTimerViewModel import com.abbidot.tracker.vm.DataViewModel @@ -75,6 +76,7 @@ class HomeV2Activity : BaseActivity(ActivityHomeV2Binding private val mAutomaticConnectionDeviceViewModel: ConnectionDeviceViewModel by viewModels() private val mLogBleReportViewModel: LogBleReportViewModel by viewModels() private val mCountDownTimerViewModel: CountDownTimerViewModel by viewModels() + private val mAppSetViewModel: AppSetViewModel by viewModels() // private val mSocketSendMessageViewModel: SocketSendMessageViewModel by viewModels() private val mFamilyViewModel: FamilyViewModel by viewModels() @@ -180,6 +182,7 @@ class HomeV2Activity : BaseActivity(ActivityHomeV2Binding } else { //清除首次绑定设备信息 MMKVUtil.putString(MMKVKey.FirstDeviceMac, "") + //监听网络变化 val connectivityManager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager val networkCallback: ConnectivityManager.NetworkCallback = object : ConnectivityManager.NetworkCallback() { @@ -187,7 +190,10 @@ class HomeV2Activity : BaseActivity(ActivityHomeV2Binding // 当网络可用时调用 super.onAvailable(network) LogUtil.e("当网络可用时,onAvailable") - if (mNetworkRequestsFailRetryCount == mNetworkRequestsFailMaxCount && mPetList.isEmpty()) { + //首次启动APP,没有网络去检验APP合法性,就在网络可用时去检验 + if (MMKVUtil.getInt(MMKVKey.isCheckLegalApp) == ConstantInt.Type2) { + mAppSetViewModel.checkPackageStatus(applicationContext) + } else if (mNetworkRequestsFailRetryCount == mNetworkRequestsFailMaxCount && mPetList.isEmpty()) { mNetworkRequestsFailRetryCount = 0 isFirst = false mDataViewModel.getHomeBindPetList(this@HomeV2Activity) @@ -295,6 +301,22 @@ class HomeV2Activity : BaseActivity(ActivityHomeV2Binding } } } + mAppSetViewModel.mCheckPackageStatusLiveData.observe(this) { + if (null == it) { + showToast(R.string.txt_no_legal_app, true) + } else { + if (null == it.getOrNull()) { + showToast(R.string.txt_no_legal_app, true, Gravity.CENTER) + } else { + MMKVUtil.putInt(MMKVKey.isCheckLegalApp, ConstantInt.Type1) + if (mNetworkRequestsFailRetryCount == mNetworkRequestsFailMaxCount && mPetList.isEmpty()) { + mNetworkRequestsFailRetryCount = 0 + isFirst = false + mDataViewModel.getHomeBindPetList(this@HomeV2Activity) + } + } + } + } //退出登录 XEventBus.observe(this, EventName.LogOut) { finish() diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/SplashActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/SplashActivity.kt index 6a2f32e..2323a28 100644 --- a/app/src/main/java/com/abbidot/tracker/ui/activity/SplashActivity.kt +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/SplashActivity.kt @@ -24,6 +24,7 @@ import com.abbidot.baselibrary.util.MMKVUtil import com.abbidot.baselibrary.util.Utils import com.abbidot.baselibrary.util.rsa.Base64 import com.abbidot.tracker.R +import com.abbidot.tracker.constant.ConstantInt import com.abbidot.tracker.constant.KeyNames import com.abbidot.tracker.databinding.ActivitySplashBinding import com.abbidot.tracker.dialog.PrivacyPolicyDialog @@ -31,10 +32,12 @@ import com.abbidot.tracker.retrofit2.INetworkService import com.abbidot.tracker.util.ThirdPartyUtil import com.abbidot.tracker.util.Util import com.abbidot.tracker.util.bluetooth.SRBleUtil +import com.abbidot.tracker.vm.AppSetViewModel import com.abbidot.tracker.vm.CountDownTimerViewModel import com.baidu.location.LocationClient import com.baidu.mapapi.CoordType import com.baidu.mapapi.SDKInitializer +import com.baidu.platform.comapi.util.NetworkUtil import com.hjq.toast.Toaster import com.qmuiteam.qmui.arch.QMUIActivity import com.stripe.android.PaymentConfiguration @@ -57,6 +60,7 @@ class SplashActivity : QMUIActivity() { private lateinit var mViewBinding: ActivitySplashBinding private val mCountDownTimerViewModel: CountDownTimerViewModel by viewModels() + private val mAppSetViewModel: AppSetViewModel by viewModels() // IWXAPI 是第三方 app 和微信通信的 openApi 接口 private lateinit var mApi: IWXAPI @@ -114,6 +118,36 @@ class SplashActivity : QMUIActivity() { mViewBinding.ivSplashDebug.visibility = View.VISIBLE } + if (NetworkUtil.isNetworkAvailable(mContext)) { + mAppSetViewModel.apply { + mCheckPackageStatusLiveData.observe(this@SplashActivity) { + if (null == it) { + showToastAndFinish() + } else { + if (null == it.getOrNull()) { + showToastAndFinish() + } else { + MMKVUtil.putInt(MMKVKey.isCheckLegalApp, ConstantInt.Type1) + legalApp() + } + } + } + } + mAppSetViewModel.checkPackageStatus(applicationContext) + } else { + MMKVUtil.putInt(MMKVKey.isCheckLegalApp, ConstantInt.Type2) + legalApp() + } + } + + private fun showToastAndFinish() { + Toaster.show(getString(R.string.txt_no_legal_app)) + mViewBinding.root.postDelayed({ + finish() + }, 2000) + } + + private fun legalApp() { mCountDownTimerViewModel.mCountDownEndLiveData.observe(this) { mViewBinding.splashLottieAnimationView.cancelAnimation() val userId = MMKVUtil.getString(MMKVKey.UserId) diff --git a/app/src/main/java/com/abbidot/tracker/vm/AppSetViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/AppSetViewModel.kt index 3879482..d73da58 100644 --- a/app/src/main/java/com/abbidot/tracker/vm/AppSetViewModel.kt +++ b/app/src/main/java/com/abbidot/tracker/vm/AppSetViewModel.kt @@ -1,13 +1,16 @@ package com.abbidot.tracker.vm +import android.content.Context import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.AppUtils import com.abbidot.baselibrary.util.MMKVUtil import com.abbidot.tracker.bean.NotificationBean import com.abbidot.tracker.retrofit2.NetworkApi import kotlinx.coroutines.launch +import java.util.UUID /** *Created by .yzq on 2024/9/26/026. @@ -18,6 +21,7 @@ class AppSetViewModel : ViewModel() { val mGetNotificationLiveData = MutableLiveData>>() val mSetNotificationLiveData = MutableLiveData>() + val mCheckPackageStatusLiveData = MutableLiveData>() /** * 获取通知设置 @@ -40,4 +44,17 @@ class AppSetViewModel : ViewModel() { mSetNotificationLiveData.value = result } } + + /** + * 检查apk包状态 + */ + fun checkPackageStatus(context: Context) { + viewModelScope.launch { + val packageName = context.packageName + val signSha256 = AppUtils.getApkSignSha256(context) + val nonce = UUID.randomUUID().toString() + val result = NetworkApi.checkPackageStatus(packageName, signSha256, nonce) + mCheckPackageStatusLiveData.value = result + } + } } \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 51e73b2..4da4d89 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -948,4 +948,5 @@ 自动续订已开启 移除 关闭并移除 + 非法APP,请从正规渠道下载安装 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bbb9a6d..2403931 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -999,4 +999,5 @@ Auto-Renewal Is On Remove Turn Off & Remove + 非法APP,请从正规渠道下载安装 \ No newline at end of file diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/constant/MMKVKey.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/constant/MMKVKey.kt index 8d45e73..87e88d7 100644 --- a/baselibrary/src/main/java/com/abbidot/baselibrary/constant/MMKVKey.kt +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/constant/MMKVKey.kt @@ -44,6 +44,7 @@ import androidx.annotation.StringDef MMKVKey.UserGpsLat, MMKVKey.UserGpsLon, MMKVKey.UserGpsSpeed, + MMKVKey.isCheckLegalApp, MMKVKey.isFirstCheckBleOpen, MMKVKey.UUID, MMKVKey.Shared @@ -66,6 +67,7 @@ annotation class MMKVKey { const val Gender = "gender" const val BirthdayDate = "birthdayDate" const val CountryCode = "countryCode" + const val isCheckLegalApp = "isCheckLegalApp" //首次绑定流程 const val FirstDeviceId = "firstDeviceId" diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/network/interceptor/CommonRequestInterceptor.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/network/interceptor/CommonRequestInterceptor.kt index ef0538d..4f8e6f6 100644 --- a/baselibrary/src/main/java/com/abbidot/baselibrary/network/interceptor/CommonRequestInterceptor.kt +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/network/interceptor/CommonRequestInterceptor.kt @@ -75,8 +75,8 @@ class CommonRequestInterceptor : Interceptor { } } - val uuid = MMKVUtil.getString(MMKVKey.UUID) - paramList.add("uuid$uuid") + val uniqueId = MMKVUtil.getString(MMKVKey.UUID) + paramList.add("uuid$uniqueId") val sign = if (paramList.isNotEmpty()) { //按key的字母排序 paramList.sort() @@ -87,7 +87,7 @@ class CommonRequestInterceptor : Interceptor { val token = MMKVUtil.getString(MMKVKey.Token) builder.addHeader("token", token) - builder.addHeader("uuid", uuid) + builder.addHeader("uuid", uniqueId) builder.addHeader("sign", sign) return chain.proceed(builder.build()) diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/util/AppUtils.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/util/AppUtils.kt index c7d6399..2bb8636 100644 --- a/baselibrary/src/main/java/com/abbidot/baselibrary/util/AppUtils.kt +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/util/AppUtils.kt @@ -6,9 +6,11 @@ import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.content.res.Configuration import android.content.res.Resources +import android.os.Build import android.provider.Settings import android.text.TextUtils import com.abbidot.baselibrary.constant.MMKVKey +import java.security.MessageDigest import java.util.Locale import java.util.UUID @@ -115,19 +117,87 @@ class AppUtils { */ @SuppressLint("HardwareIds") fun saveUniqueId(context: Context) { - var uuid = MMKVUtil.getString(MMKVKey.UUID) - if (TextUtils.isEmpty(uuid)) { + var uniqueId = MMKVUtil.getString(MMKVKey.UUID) + if (TextUtils.isEmpty(uniqueId)) { val androidId = Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID) ?: "" - uuid = if (TextUtils.isEmpty(androidId)) - UUID.randomUUID().toString() - else - androidId - MMKVUtil.putString(MMKVKey.UUID, uuid) + uniqueId = if (TextUtils.isEmpty(androidId)) UUID.randomUUID().toString() + else androidId + MMKVUtil.putString(MMKVKey.UUID, uniqueId) } } + /** + *获取apk签名Sha256 + */ + fun getApkSignSha2561(context: Context): String { + return try { + val pm = context.packageManager + val pkgName = context.packageName + val pkgInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + // API33+ Android13,直接用GET_SIGNING_CERTIFICATES,不需要if判断 + val flagLong = PackageManager.GET_SIGNING_CERTIFICATES.toLong() + pm.getPackageInfo(pkgName, PackageManager.PackageInfoFlags.of(flagLong)) + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + // API28 ~ API32 + pm.getPackageInfo(pkgName, PackageManager.GET_SIGNING_CERTIFICATES) + } else { + // API24 ~ API27 + pm.getPackageInfo(pkgName, PackageManager.GET_SIGNATURES) + } + val cert = when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && pkgInfo.signingInfo != null -> pkgInfo.signingInfo.signingCertificateHistory?.firstOrNull() + else -> pkgInfo.signatures?.firstOrNull() + } ?: return "" + val digest = MessageDigest.getInstance("SHA-256") + val hashBytes = digest.digest(cert.toByteArray()) + hashBytes.joinToString("") { "%02x".format(Locale.US, it) } + } catch (_: Exception) { + "" + } + } + + /** + * 获取apk签名Sha256 + */ + fun getApkSignSha256(context: Context): String { + return try { + val pm = context.packageManager + val pkgName = context.packageName + val pkgInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + // API33+ Android13 + val flagLong = PackageManager.GET_SIGNING_CERTIFICATES.toLong() + pm.getPackageInfo(pkgName, PackageManager.PackageInfoFlags.of(flagLong)) + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + // API28 ~ API32 + pm.getPackageInfo(pkgName, PackageManager.GET_SIGNING_CERTIFICATES) + } else { + // API24 ~ API27 + pm.getPackageInfo(pkgName, PackageManager.GET_SIGNATURES) + } + val certBytes = when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && pkgInfo.signingInfo != null -> { + // 读取【当前生效签名】apkContentsSigners,不是历史证书 + val activeCert = + pkgInfo.signingInfo.apkContentsSigners?.firstOrNull() ?: return "" + activeCert.toByteArray() + } + + else -> { + val signature = pkgInfo.signatures?.firstOrNull() ?: return "" + signature.toByteArray() + } + } + val digest = MessageDigest.getInstance("SHA-256") + val hashBytes = digest.digest(certBytes) + hashBytes.joinToString(separator = "") { "%02x".format(Locale.US, it) } + } catch (_: Exception) { + "" + } + } + + /** * 获取系统国家地区 */