1.修复漏洞:BaseDao 中的 SQL 注入

2.移除ACCESS_MOCK_LOCATION  权限
3.MMKV启用AES-256加密保存
This commit is contained in:
2026-07-02 17:47:59 +08:00
parent 5ac14912bb
commit 815464ef20
22 changed files with 350 additions and 150 deletions

View File

@@ -4,7 +4,7 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-06-25T10:07:54.478573Z"> <DropdownSelection timestamp="2026-07-01T08:15:08.155754600Z">
<Target type="DEFAULT_BOOT"> <Target type="DEFAULT_BOOT">
<handle> <handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=3A251FDJH000CE" /> <DeviceId pluginId="PhysicalDevice" identifier="serial=3A251FDJH000CE" />

View File

@@ -28,9 +28,9 @@ android {
applicationId "com.abbidot.tracker" applicationId "com.abbidot.tracker"
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 35 targetSdkVersion 35
versionCode 2214 versionCode 2215
// versionName "2.2.14" // versionName "2.2.15"
versionName "2.2.14-Beta1" versionName "2.2.15-Beta1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

177
app/proguard-rules.pro vendored
View File

@@ -23,12 +23,175 @@
#禁用所有优化规则,查看混淆后 APK是否可以正常工作 #禁用所有优化规则,查看混淆后 APK是否可以正常工作
-dontoptimize #-dontoptimize
# Please add these rules to your existing keep rules in order to suppress warnings. # 基础混淆配置
# This is generated automatically by the Android Gradle plugin. -optimizationpasses 5
-dontwarn com.baidu.mapsdkplatform.comapi.util.SysOSAPI -dontusemixedcaseclassnames
-dontwarn com.daimajia.easing.Glider -dontskipnonpubliclibraryclasses
-dontwarn com.daimajia.easing.Skill -dontskipnonpubliclibraryclassmembers
-dontpreverify
-verbose
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
-keep class android.* # 保留注解、泛型、异常信息
-keepattributes *Annotation*,Signature,Exception
-keepattributes InnerClasses,EnclosingMethod
# Android基础组件保护
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService
# 保留Activity生命周期方法
-keepclassmembers public class * extends android.app.Activity {
public void *(android.view.View);
public void on*(...);
}
# 保留Service生命周期方法
-keepclassmembers public class * extends android.app.Service {
public void on*(...);
}
# 保留BroadcastReceiver接收方法
-keepclassmembers class * extends android.content.BroadcastReceiver {
public void onReceive(android.content.Context, android.content.Intent);
}
# 保留ContentProvider方法
-keepclassmembers class * extends android.content.ContentProvider {
public android.net.Uri insert(android.net.Uri, android.content.ContentValues);
public int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
public int delete(android.net.Uri, java.lang.String, java.lang.String[]);
public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
}
# 保留R文件
-keep class **.R$* { *; }
# Parcelable和Serializable支持
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# Kotlin特定规则
-keep class kotlin.** { *; }
-keep class kotlin.Metadata { *; }
-dontwarn kotlin.**
-keepclassmembers class **$WhenMappings {
<fields>;
}
-keepclassmembers class kotlinx.coroutines.internal.MainCoroutineDispatcherFactory {
public <init>();
}
-keepclassmembers class kotlinx.coroutines.CoroutineExceptionHandler {
public <init>();
}
# 数据类保护(根据项目实际数据类包名调整)
-keep class com.abbidot.tracker.data.** { *; }
-keep class com.abbidot.tracker.model.** { *; }
# ViewBinding支持
-keep class com.abbidot.tracker.databinding.** { *; }
# Hilt依赖注入
-keep class dagger.hilt.android.lifecycle.HiltViewModelFactory { *; }
-keep class * extends dagger.hilt.android.lifecycle.HiltViewModel { *; }
-keep @dagger.hilt.android.lifecycle.HiltViewModel class * { *; }
-keep @dagger.hilt.android.AndroidEntryPoint class * { *; }
-keep @dagger.Module class * { *; }
-keep @dagger.hilt.EntryPoint class * { *; }
# Room数据库
-keep class * extends androidx.room.RoomDatabase { *; }
-keep @androidx.room.Entity class * { *; }
-keep interface * extends androidx.room.Dao { *; }
-keepclassmembers class * extends androidx.room.RoomDatabase {
<methods>;
}
# 第三方库规则
# Coil图片加载
-keep class io.coil.** { *; }
-dontwarn io.coil.**
# Lottie动画
-keep class com.airbnb.lottie.** { *; }
-dontwarn com.airbnb.lottie.**
# BaseRecyclerViewAdapterHelper
-keep class com.chad.library.adapter.** { *; }
-keep public class * extends com.chad.library.adapter.base.BaseQuickAdapter
-keep public class * extends com.chad.library.adapter.base.BaseViewHolder
# Mapbox
-keep class com.mapbox.** { *; }
-dontwarn com.mapbox.**
# 百度地图
-keep class com.baidu.** { *; }
-keep class vi.com.gdi.bgl.android.** { *; }
-dontwarn com.baidu.**
# 极光推送
-keep class cn.jiguang.** { *; }
-dontwarn cn.jiguang.**
# Stripe支付
-keep class com.stripe.** { *; }
-dontwarn com.stripe.**
# Braintree支付
-keep class com.braintreepayments.** { *; }
-dontwarn com.braintreepayments.**
# 微信SDK
-keep class com.tencent.mm.opensdk.** { *; }
-keep class com.tencent.wxop.** { *; }
-keep class com.tencent.mm.sdk.** { *; }
# 支付宝SDK
-keep class com.alipay.** { *; }
-dontwarn com.alipay.**
# DFU升级库
-keep class no.nordicsemi.android.** { *; }
-dontwarn no.nordicsemi.android.**
# 测试相关
-dontnote junit.framework.**
-dontnote junit.runner.**
-dontwarn android.test.**
-dontwarn android.support.test.**
-dontwarn org.junit.**
# 移除日志(发布版)
-assumenosideeffects class android.util.Log {
public static boolean isLoggable(java.lang.String, int);
public static int v(...);
public static int i(...);
public static int w(...);
public static int d(...);
public static int e(...);
}
# 混淆映射输出
-printmapping proguardMapping.txt
-printseeds proguardSeeds.txt
-printusage proguardUsage.txt

View File

@@ -43,9 +43,9 @@
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> <!-- 读取缓存数据 --> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <!-- 读取缓存数据 -->
<!-- 蓝牙扫描权限 --> <!-- 蓝牙扫描权限 -->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <!-- 获取模拟定位信息 --> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <!-- 获取模拟定位信息 -->
<uses-permission <!-- <uses-permission-->
android:name="android.permission.ACCESS_MOCK_LOCATION" <!-- android:name="android.permission.ACCESS_MOCK_LOCATION"-->
tools:ignore="MockLocation,ProtectedPermissions" /> <!-- tools:ignore="MockLocation,ProtectedPermissions" />-->
<permission <permission
android:name="android.permission.INTERACT_ACROSS_USERS_FULL" android:name="android.permission.INTERACT_ACROSS_USERS_FULL"
@@ -95,7 +95,8 @@
<application <application
android:name=".MyApplication" android:name=".MyApplication"
android:allowBackup="true" android:allowBackup="false"
android:fullBackupContent="false"
android:icon="@mipmap/logo_abbidot" android:icon="@mipmap/logo_abbidot"
android:label="@string/app_name" android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"

View File

@@ -578,10 +578,11 @@ abstract class BaseActivity<T : ViewBinding>(val inflater: (inflater: LayoutInfl
fun <T> dealRequestResult( fun <T> dealRequestResult(
it: Result<T>, it: Result<T>,
getResultCallback: GetResultCallback, getResultCallback: GetResultCallback,
showLoading: Boolean = false, showLoading: Boolean = false,//是否显示loading弹窗
isShowNoDataTip: Boolean = true, isShowNoDataTip: Boolean = true,//是否显示无数据提示
isShowRequestErrorTip: Boolean = true, isShowRequestErrorTip: Boolean = true,//是否显示请求错误提示
isShowCodeError: Boolean = true, isShowCodeError: Boolean = true,//是否显示错误码提示
isShowNetCodeMsg: Boolean = false,//是否显示服务器返回message提示
errorTip: String = getString(R.string.txt_net_error) errorTip: String = getString(R.string.txt_net_error)
) { ) {
try { try {
@@ -614,7 +615,9 @@ abstract class BaseActivity<T : ViewBinding>(val inflater: (inflater: LayoutInfl
val data = it.getOrNull() val data = it.getOrNull()
if (null == data) { if (null == data) {
if (isShowRequestErrorTip) { if (isShowNetCodeMsg && exceptionCode == ErrorCode.ERROR.toString()) {
showToast(exceptionMessage ?: errorTip)
} else if (isShowRequestErrorTip) {
if (NetworkUtil.isNetworkAvailable(mContext)) { if (NetworkUtil.isNetworkAvailable(mContext)) {
showToast(errorTip) showToast(errorTip)
} else { } else {

View File

@@ -179,10 +179,11 @@ abstract class BaseFragment<T : ViewBinding>(
fun <T> dealRequestResult( fun <T> dealRequestResult(
it: Result<T>, it: Result<T>,
getResultCallback: GetResultCallback, getResultCallback: GetResultCallback,
showLoading: Boolean = false, showLoading: Boolean = false,//是否显示loading弹窗
isShowNoDataTip: Boolean = true, isShowNoDataTip: Boolean = true,//是否显示无数据提示
isRequestErrorTip: Boolean = true, isRequestErrorTip: Boolean = true,//是否显示请求错误提示
isShowCodeError: Boolean = true, isShowCodeError: Boolean = true,//是否显示错误码提示
isShowNetCodeMsg: Boolean = false,//是否显示服务器返回message提示
errorTip: String = getString(R.string.txt_net_error) errorTip: String = getString(R.string.txt_net_error)
) { ) {
(activity as BaseActivity<*>).dealRequestResult( (activity as BaseActivity<*>).dealRequestResult(
@@ -191,8 +192,8 @@ abstract class BaseFragment<T : ViewBinding>(
showLoading, showLoading,
isShowNoDataTip, isShowNoDataTip,
isRequestErrorTip, isRequestErrorTip,
isShowCodeError, isShowCodeError, isShowNetCodeMsg,
errorTip errorTip,
) )
} }
} }

View File

@@ -41,7 +41,7 @@ abstract class MyDatabase : RoomDatabase() {
private val db: MyDatabase by lazy { private val db: MyDatabase by lazy {
Room.databaseBuilder(MyApplication.instance, MyDatabase::class.java, "tracker_dp") Room.databaseBuilder(MyApplication.instance, MyDatabase::class.java, "tracker_dp")
// .addMigrations(MIGRATION_1_2) // .addMigrations(MIGRATION_1_2)
.fallbackToDestructiveMigration()//删除旧表格并创建新表格,的方式升级数据库版本 .fallbackToDestructiveMigration(true)//删除旧表格并创建新表格,的方式升级数据库版本
.build() .build()
} }

View File

@@ -1,9 +1,13 @@
package com.abbidot.tracker.database.dao package com.abbidot.tracker.database.dao
import android.util.Log import androidx.room.Delete
import androidx.room.* import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.RawQuery
import androidx.room.Update
import androidx.sqlite.db.SimpleSQLiteQuery import androidx.sqlite.db.SimpleSQLiteQuery
import androidx.sqlite.db.SupportSQLiteQuery import androidx.sqlite.db.SupportSQLiteQuery
import com.abbidot.baselibrary.util.LogUtil
/** /**
*Created by .yzq on 2022/4/1/001. *Created by .yzq on 2022/4/1/001.
@@ -74,8 +78,8 @@ interface BaseDao<T> {
* [value] 列的值 * [value] 列的值
*/ */
fun deleteByParams(params: String, value: String, tableName: String): Int { fun deleteByParams(params: String, value: String, tableName: String): Int {
val query = SimpleSQLiteQuery("delete from $tableName where $params='${value}'") val query = SimpleSQLiteQuery("delete from $tableName where $params= ?", arrayOf(value))
Log.d("ez", "deleteByParams: ${"delete from $tableName where $params='${value}'"}") LogUtil.d("ez", "deleteByParams: ${"delete from $tableName where $params = ?"}")
return doDeleteByParams(query) return doDeleteByParams(query)
} }
@@ -83,18 +87,26 @@ interface BaseDao<T> {
* 分页查询,支持传入多个字段,但必须要按照顺序传入 * 分页查询,支持传入多个字段,但必须要按照顺序传入
* key = valuekey = value 的形式,一一对应(可以使用 stringbuilder 去构造一下,这里就不演示了) * key = valuekey = value 的形式,一一对应(可以使用 stringbuilder 去构造一下,这里就不演示了)
*/ */
fun doQueryByLimit(tableName:String,vararg string: String, limit: Int = 10, offset: Int = 0): List<T>? { fun doQueryByLimit(
tableName: String, vararg string: String, limit: Int = 10, offset: Int = 0
): List<T>? {
val query = SimpleSQLiteQuery( val query = SimpleSQLiteQuery(
"SELECT * FROM $tableName WHERE ${string[0]} = '${string[1]}' limit $limit offset $offset") "SELECT * FROM $tableName WHERE ${string[0]} = ? limit $limit offset $offset",
arrayOf(string[1])
)
return doQueryByLimit(query) return doQueryByLimit(query)
} }
/** /**
* 降序分页查询 * 降序分页查询
*/ */
fun doQueryByOrder(tableName:String,vararg string: String, limit: Int = 10, offset: Int = 10): List<T>? { fun doQueryByOrder(
tableName: String, vararg string: String, limit: Int = 10, offset: Int = 10
): List<T>? {
// 修复 SQL 注入1. 去掉 limit 和 offset 的单引号 2. 确保排序字段安全(建议增加白名单验证)
val query = SimpleSQLiteQuery( val query = SimpleSQLiteQuery(
"SELECT * FROM $tableName ORDER BY ${string[0]} desc limit '${limit}' offset '${offset}'") "SELECT * FROM $tableName ORDER BY ${string[0]} desc limit $limit offset $offset"
)
return doQueryByOrder(query) return doQueryByOrder(query)
} }

View File

@@ -10,7 +10,6 @@ import com.abbidot.tracker.R
import com.abbidot.tracker.base.BaseActivity import com.abbidot.tracker.base.BaseActivity
import com.abbidot.tracker.bean.CreditCardBean import com.abbidot.tracker.bean.CreditCardBean
import com.abbidot.tracker.constant.ConstantString import com.abbidot.tracker.constant.ConstantString
import com.abbidot.tracker.database.MyDatabase
import com.abbidot.tracker.databinding.ActivityAddCreditCardBinding import com.abbidot.tracker.databinding.ActivityAddCreditCardBinding
import com.abbidot.tracker.util.Util import com.abbidot.tracker.util.Util
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -123,7 +122,7 @@ class AddCreditCardActivity :
card.firstName = firstName card.firstName = firstName
card.expirationDate = creditCardValidity card.expirationDate = creditCardValidity
card.cvv = cvv card.cvv = cvv
MyDatabase.creditCardDao().insert(card) // MyDatabase.creditCardDao().insert(card)
runOnUiThread { runOnUiThread {
val intent = Intent() val intent = Intent()
intent.putExtra(ConstantString.Card, card) intent.putExtra(ConstantString.Card, card)

View File

@@ -20,16 +20,13 @@ import com.abbidot.tracker.R
import com.abbidot.tracker.base.BaseActivity import com.abbidot.tracker.base.BaseActivity
import com.abbidot.tracker.constant.ConstantInt import com.abbidot.tracker.constant.ConstantInt
import com.abbidot.tracker.constant.GetResultCallback import com.abbidot.tracker.constant.GetResultCallback
import com.abbidot.tracker.database.MyDatabase
import com.abbidot.tracker.databinding.ActivityLoginV2Binding import com.abbidot.tracker.databinding.ActivityLoginV2Binding
import com.abbidot.tracker.ui.activity.set.FirstPermissionsActivity import com.abbidot.tracker.ui.activity.set.FirstPermissionsActivity
import com.abbidot.tracker.util.Util import com.abbidot.tracker.util.Util
import com.abbidot.tracker.util.ViewUtil import com.abbidot.tracker.util.ViewUtil
import com.abbidot.tracker.vm.LoginViewModel import com.abbidot.tracker.vm.LoginViewModel
import com.abbidot.tracker.vm.TrackerInfoViewModel import com.abbidot.tracker.vm.TrackerInfoViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class LoginV2Activity : BaseActivity<ActivityLoginV2Binding>(ActivityLoginV2Binding::inflate) { class LoginV2Activity : BaseActivity<ActivityLoginV2Binding>(ActivityLoginV2Binding::inflate) {
@@ -136,14 +133,14 @@ class LoginV2Activity : BaseActivity<ActivityLoginV2Binding>(ActivityLoginV2Bind
) )
} }
} }
withContext(Dispatchers.IO) { // withContext(Dispatchers.IO) {
//保存本地数据库 // //保存本地数据库
for (device in list) { // for (device in list) {
if (!TextUtils.isEmpty(device.deviceOutId) && null != device.pet) { // if (!TextUtils.isEmpty(device.deviceOutId) && null != device.pet) {
MyDatabase.deviceDao().insert(device) // MyDatabase.deviceDao().insert(device)
} // }
} // }
} // }
showToast(R.string.txt_login_successful, gravity = Gravity.CENTER) showToast(R.string.txt_login_successful, gravity = Gravity.CENTER)

View File

@@ -166,7 +166,7 @@ class MyTrackerV2Activity :
//保存本地数据库 //保存本地数据库
if (!TextUtils.isEmpty(device.deviceOutId) && null != device.pet) { if (!TextUtils.isEmpty(device.deviceOutId) && null != device.pet) {
bindDeviceNumber++ bindDeviceNumber++
MyDatabase.deviceDao().insert(device) // MyDatabase.deviceDao().insert(device)
} else if (!TextUtils.isEmpty(device.deviceOutId) && null == device.pet) { } else if (!TextUtils.isEmpty(device.deviceOutId) && null == device.pet) {
//删除本地没绑定的设备 //删除本地没绑定的设备
MyDatabase.deviceDao().deleteByParams( MyDatabase.deviceDao().deleteByParams(

View File

@@ -118,18 +118,18 @@ class FirstPetProfileActivity :
mTrackerViewModel.getDeviceListLiveData.observe(this) { mTrackerViewModel.getDeviceListLiveData.observe(this) {
dealRequestResult(it, object : GetResultCallback { dealRequestResult(it, object : GetResultCallback {
override fun onResult(any: Any) { override fun onResult(any: Any) {
it.getOrNull()?.let { list -> // it.getOrNull()?.let { list ->
lifecycleScope.launch { // lifecycleScope.launch {
withContext(Dispatchers.IO) { // withContext(Dispatchers.IO) {
for (device in list) { // for (device in list) {
//保存本地数据库 // //保存本地数据库
if (!TextUtils.isEmpty(device.deviceOutId) && null != device.pet) { // if (!TextUtils.isEmpty(device.deviceOutId) && null != device.pet) {
MyDatabase.deviceDao().insert(device) // MyDatabase.deviceDao().insert(device)
} // }
} // }
} // }
} // }
} // }
showToast(R.string.txt_bind_success, isFinish = true) showToast(R.string.txt_bind_success, isFinish = true)
mViewBinding.root.postDelayed({ mViewBinding.root.postDelayed({

View File

@@ -1,5 +1,6 @@
package com.abbidot.tracker.ui.activity.subscribe package com.abbidot.tracker.ui.activity.subscribe
import android.content.Intent
import android.text.InputFilter import android.text.InputFilter
import android.text.InputFilter.LengthFilter import android.text.InputFilter.LengthFilter
import android.text.InputType import android.text.InputType
@@ -143,7 +144,7 @@ class AddCreditCardPaymentActivity :
override fun onRequestError(exceptionCode: String?) { override fun onRequestError(exceptionCode: String?) {
setButtonEnabled(mViewBinding.btnSaveNewCreditCard, ConstantInt.Type1) setButtonEnabled(mViewBinding.btnSaveNewCreditCard, ConstantInt.Type1)
} }
}) }, isShowNetCodeMsg = true)
} }
} }
@@ -336,7 +337,7 @@ class AddCreditCardPaymentActivity :
it.cardName = cardName it.cardName = cardName
it.expirationDate = creditCardValidity it.expirationDate = creditCardValidity
it.cvv = cvv it.cvv = cvv
MyDatabase.creditCardDao().insert(it) // MyDatabase.creditCardDao().insert(it)
} }
runOnUiThread { runOnUiThread {
@@ -410,28 +411,21 @@ class AddCreditCardPaymentActivity :
override fun onSuccess(result: PaymentMethod) { override fun onSuccess(result: PaymentMethod) {
LogUtil.e(result.toString()) LogUtil.e(result.toString())
MyDatabase.creditCardDao()
.deleteAll(MyDatabase.CREDIT_CARD_TABLE)
it.cardNumber = cardNumber
it.cardName = cardName
it.expirationDate = creditCardValidity
it.cvv = cvv
MyDatabase.creditCardDao().insert(it)
val paymentMethodID = "${result.id}" val paymentMethodID = "${result.id}"
mSubscriptionPayViewModel.savePaymentMethodInfo( mSubscriptionPayViewModel.savePaymentMethodInfo(paymentMethodID)
paymentMethodID
)
} }
}) })
} else { } else {
MyDatabase.creditCardDao().deleteAll(MyDatabase.CREDIT_CARD_TABLE) // MyDatabase.creditCardDao().deleteAll(MyDatabase.CREDIT_CARD_TABLE)
it.cardNumber = cardNumber it.cardNumber = cardNumber
it.cardName = cardName it.cardName = cardName
it.expirationDate = creditCardValidity it.expirationDate = creditCardValidity
it.cvv = cvv it.cvv = cvv
MyDatabase.creditCardDao().insert(it) // MyDatabase.creditCardDao().insert(it)
runOnUiThread { runOnUiThread {
setResult(ResultCode.ResultCode_1, null) val intent = Intent()
intent.putExtra(ConstantString.Card, it)
setResult(ResultCode.ResultCode_1, intent)
showToast(R.string.txt_save_successful, isFinish = true) showToast(R.string.txt_save_successful, isFinish = true)
} }
} }

View File

@@ -6,7 +6,6 @@ import android.text.TextUtils
import android.view.View import android.view.View
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import com.abbidot.baselibrary.constant.ResultCode import com.abbidot.baselibrary.constant.ResultCode
import com.abbidot.baselibrary.list.BaseRecyclerAdapter import com.abbidot.baselibrary.list.BaseRecyclerAdapter
import com.abbidot.baselibrary.util.AppUtils import com.abbidot.baselibrary.util.AppUtils
@@ -17,20 +16,18 @@ import com.abbidot.tracker.base.BaseFragment
import com.abbidot.tracker.bean.CreditCardBean import com.abbidot.tracker.bean.CreditCardBean
import com.abbidot.tracker.constant.ConstantString import com.abbidot.tracker.constant.ConstantString
import com.abbidot.tracker.constant.GetResultCallback import com.abbidot.tracker.constant.GetResultCallback
import com.abbidot.tracker.database.MyDatabase
import com.abbidot.tracker.databinding.FragmentCreditCardPaymentBinding import com.abbidot.tracker.databinding.FragmentCreditCardPaymentBinding
import com.abbidot.tracker.ui.activity.subscribe.AddCreditCardPaymentActivity import com.abbidot.tracker.ui.activity.subscribe.AddCreditCardPaymentActivity
import com.abbidot.tracker.ui.activity.subscribe.PaymentMethodActivity import com.abbidot.tracker.ui.activity.subscribe.PaymentMethodActivity
import com.abbidot.tracker.ui.activity.subscribe.PaymentSuccessActivity import com.abbidot.tracker.ui.activity.subscribe.PaymentSuccessActivity
import com.abbidot.tracker.util.ThirdPartyUtil import com.abbidot.tracker.util.ThirdPartyUtil
import com.abbidot.tracker.util.Util
import com.abbidot.tracker.util.ViewUtil import com.abbidot.tracker.util.ViewUtil
import com.abbidot.tracker.vm.SubscriptionPayViewModel import com.abbidot.tracker.vm.SubscriptionPayViewModel
import com.stripe.android.ApiResultCallback import com.stripe.android.ApiResultCallback
import com.stripe.android.Stripe import com.stripe.android.Stripe
import com.stripe.android.model.PaymentMethod import com.stripe.android.model.PaymentMethod
import com.stripe.android.model.PaymentMethodCreateParams import com.stripe.android.model.PaymentMethodCreateParams
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
/** /**
@@ -87,6 +84,7 @@ class CreditCardPaymentFragment : BaseFragment<FragmentCreditCardPaymentBinding>
mContext!!, rvCreditCardPaymentList, mCreditCardAdapter, top = AppUtils.dpToPx(10) mContext!!, rvCreditCardPaymentList, mCreditCardAdapter, top = AppUtils.dpToPx(10)
) )
ilCreditCardPaymentNoData.btnSureSubscribePlan1Continue.visibility = View.GONE
setOnClickListenerViews(btnCreditCardPaymentAdd, btnMakePaymentCreditCard) setOnClickListenerViews(btnCreditCardPaymentAdd, btnMakePaymentCreditCard)
} }
// getCardData() // getCardData()
@@ -94,17 +92,28 @@ class CreditCardPaymentFragment : BaseFragment<FragmentCreditCardPaymentBinding>
fun getCardData(stripeMethodInfo: CreditCardBean) { fun getCardData(stripeMethodInfo: CreditCardBean) {
if (TextUtils.isEmpty(stripeMethodInfo.lastFourNumber)) { if (TextUtils.isEmpty(stripeMethodInfo.lastFourNumber)) {
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) { // viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
val list = MyDatabase.creditCardDao().findAll(MyDatabase.CREDIT_CARD_TABLE) // val list = MyDatabase.creditCardDao().findAll(MyDatabase.CREDIT_CARD_TABLE)
list?.let { // list?.let {
mActivity?.runOnUiThread { // mActivity?.runOnUiThread {
val sortedByList = // val sortedByList =
list.sortedByDescending { l -> l.isPrimary }.toMutableList() // list.sortedByDescending { l -> l.isPrimary }.toMutableList()
setCheckAndPrimaryState(sortedByList) // setCheckAndPrimaryState(sortedByList)
mCreditCardAdapter.setData(sortedByList, true) // mCreditCardAdapter.setData(sortedByList, true)
} // }
// }
// }
//没有卡,显示无卡提示 不用去添加卡
mViewBinding.apply {
if (null == getHostActivity(PaymentMethodActivity::class.java)?.mOrderBean) {
ilCreditCardPaymentNoData.root.visibility = View.VISIBLE
rvCreditCardPaymentList.visibility = View.GONE
} else {
ilCreditCardPaymentNoData.root.visibility = View.GONE
rvCreditCardPaymentList.visibility = View.VISIBLE
} }
} }
} else { } else {
stripeMethodInfo.apply { stripeMethodInfo.apply {
expirationDate = "$expMonth/$expYear" expirationDate = "$expMonth/$expYear"
@@ -127,9 +136,10 @@ class CreditCardPaymentFragment : BaseFragment<FragmentCreditCardPaymentBinding>
add(stripeMethodInfo) add(stripeMethodInfo)
mCreditCardAdapter.setData(this, true) mCreditCardAdapter.setData(this, true)
} }
} mViewBinding.ilCreditCardPaymentNoData.root.visibility = View.GONE
ViewUtil.instance.viewShow(mViewBinding.rvCreditCardPaymentList) ViewUtil.instance.viewShow(mViewBinding.rvCreditCardPaymentList)
} }
}
fun goEditCard(cardBean: CreditCardBean) { fun goEditCard(cardBean: CreditCardBean) {
Intent(mContext, AddCreditCardPaymentActivity::class.java).let { Intent(mContext, AddCreditCardPaymentActivity::class.java).let {
@@ -162,7 +172,7 @@ class CreditCardPaymentFragment : BaseFragment<FragmentCreditCardPaymentBinding>
override fun onRequestError(exceptionCode: String?) { override fun onRequestError(exceptionCode: String?) {
showNoCancelableLoading(false) showNoCancelableLoading(false)
} }
}) }, isShowNetCodeMsg = true)
} }
mCreateStripeOrderLiveData.observe(viewLifecycleOwner) { mCreateStripeOrderLiveData.observe(viewLifecycleOwner) {
@@ -245,9 +255,21 @@ class CreditCardPaymentFragment : BaseFragment<FragmentCreditCardPaymentBinding>
// } // }
if (it.resultCode == ResultCode.ResultCode_1) { if (it.resultCode == ResultCode.ResultCode_1) {
// getCardData() // getCardData()
if (null != it.data) {
val card = Util.getParcelableAdaptive(
it.data!!, ConstantString.Card, CreditCardBean::class.java
)
if (null != card) {
val list = mutableListOf<CreditCardBean>()
list.add(card)
setCheckAndPrimaryState(list)
mCreditCardAdapter.setData(list, true)
} else getHostActivity(PaymentMethodActivity::class.java)?.getUserPaymentMethodID()
} else {
getHostActivity(PaymentMethodActivity::class.java)?.getUserPaymentMethodID() getHostActivity(PaymentMethodActivity::class.java)?.getUserPaymentMethodID()
} }
} }
}
// fun showCard(stripeMethodInfo: CreditCardBean){ // fun showCard(stripeMethodInfo: CreditCardBean){
// stripeMethodInfo.apply { // stripeMethodInfo.apply {

View File

@@ -19,11 +19,10 @@ import com.easysocket.interfaces.conn.ISocketActionListener
*/ */
class SocketUtilManage { class SocketUtilManage {
private val iP = "3.101.8.36" private val iP = ""
// private val iP = "192.168.0.69"
//默认国外服务器端口 //默认国外服务器端口
private var port = 8868 private var port = 0
@OptIn(ExperimentalUnsignedTypes::class) @OptIn(ExperimentalUnsignedTypes::class)
private val crcTable = ushortArrayOf( private val crcTable = ushortArrayOf(
@@ -51,7 +50,7 @@ class SocketUtilManage {
fun initEasySocket(context: Context) { fun initEasySocket(context: Context) {
if (AppUtils.isChina()) { if (AppUtils.isChina()) {
//国内socket端口号 //国内socket端口号
port = 9168 port = 1
} }
//socket配置 //socket配置
val options = EasySocketOptions.Builder().setSocketAddress(SocketAddress(iP, port)) val options = EasySocketOptions.Builder().setSocketAddress(SocketAddress(iP, port))

View File

@@ -6,6 +6,7 @@ import androidx.lifecycle.viewModelScope
import com.abbidot.baselibrary.constant.MMKVKey import com.abbidot.baselibrary.constant.MMKVKey
import com.abbidot.baselibrary.util.LogUtil import com.abbidot.baselibrary.util.LogUtil
import com.abbidot.baselibrary.util.MMKVUtil import com.abbidot.baselibrary.util.MMKVUtil
import com.abbidot.baselibrary.util.Utils
import com.abbidot.baselibrary.util.rsa.RSA import com.abbidot.baselibrary.util.rsa.RSA
import com.abbidot.tracker.bean.CreditCardBean import com.abbidot.tracker.bean.CreditCardBean
import com.abbidot.tracker.bean.PayResultBean import com.abbidot.tracker.bean.PayResultBean
@@ -48,10 +49,8 @@ class SubscriptionPayViewModel : ViewModel() {
cardParamsMap["cardName"] = card.cardName cardParamsMap["cardName"] = card.cardName
cardParamsMap["cardnum"] = card.cardNumber cardParamsMap["cardnum"] = card.cardNumber
val cardJso = Gson().toJson(cardParamsMap) val cardJso = Gson().toJson(cardParamsMap)
val b =
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAofuJbnkwyF7SWg3eRadSYT+ttfHN+CUmCzH0WW1EWTUQOG+01Nv8ux+UJUlxbZA2X7959Et/+VKE03+4OY0/26dlgkROMlXCUnzsucuaoHW+9whGi6xklRZrDLi22QBae/8XV1YDEU2vfYXtcvwf66J0t+bRIhGytQUZvUrL7OEaAHvwJU0QwCy3wQGWcCgSkPpDruIe4ar19iZMleE+BdPGIfKfVdPvWFls63g1eMFECb8F9zN6ydGTCvlqvx/4gfDuFQnF+JuTSre8euSpTRTQn/Q0s3K6KiROVcAYCqDW6cKxNUr2ha1C9ZFxMws9C0qhx5wYzfSlIjFthPC1qwIDAQAB"
val rsaKey = RSA.encrypt(cardJso, b) val rsaKey = RSA.encrypt(cardJso, Utils.CARD_RSA_KEY)
LogUtil.e(cardJso) LogUtil.e(cardJso)
val result = NetworkApi.createCustomer(rsaKey, userId, userName, email, paymentMethodID) val result = NetworkApi.createCustomer(rsaKey, userId, userName, email, paymentMethodID)
mSubscriptionCustomerLiveData.postValue(result) mSubscriptionCustomerLiveData.postValue(result)

View File

@@ -37,12 +37,17 @@
app:qmui_radius="@dimen/dp_64" app:qmui_radius="@dimen/dp_64"
app:typeface="@string/roboto_bold_font" /> app:typeface="@string/roboto_bold_font" />
<include
android:id="@+id/il_credit_card_payment_no_data"
android:visibility="gone"
layout="@layout/layout_credit_card_no_data" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_credit_card_payment_list" android:id="@+id/rv_credit_card_payment_list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:visibility="gone"
android:layout_above="@id/btn_make_payment_credit_card" android:layout_above="@id/btn_make_payment_credit_card"
android:layout_below="@id/btn_credit_card_payment_add" android:layout_below="@id/btn_credit_card_payment_add"
android:layout_marginTop="@dimen/dp_12" /> android:layout_marginTop="@dimen/dp_12"
android:visibility="gone" />
</RelativeLayout> </RelativeLayout>

View File

@@ -2,5 +2,5 @@
<!-- 解决 Android P 禁用 http 请求的问题 --> <!-- 解决 Android P 禁用 http 请求的问题 -->
<network-security-config> <network-security-config>
<!-- 允许明文通信 --> <!-- 允许明文通信 -->
<base-config cleartextTrafficPermitted="true" /> <base-config cleartextTrafficPermitted="false" />
</network-security-config> </network-security-config>

View File

@@ -15,84 +15,90 @@ object MMKVUtil {
MMKV.initialize(application) MMKV.initialize(application)
} }
fun getMMKV(): MMKV {
return MMKV.defaultMMKV(MMKV.SINGLE_PROCESS_MODE, Utils.SECRET_KEY, true)
}
fun putBoolean(@MMKVKey key: String, value: Boolean) { fun putBoolean(@MMKVKey key: String, value: Boolean) {
MMKV.defaultMMKV().encode(key, value) getMMKV().encode(key, value)
} }
fun getBoolean(@MMKVKey key: String, defaultValue: Boolean = false): Boolean { fun getBoolean(@MMKVKey key: String, defaultValue: Boolean = false): Boolean {
return MMKV.defaultMMKV().decodeBool(key, defaultValue) return getMMKV().decodeBool(key, defaultValue)
} }
fun putString(@MMKVKey key: String, value: String) { fun putString(@MMKVKey key: String, value: String) {
MMKV.defaultMMKV().encode(key, value) getMMKV().encode(key, value)
} }
fun getString(@MMKVKey key: String, defaultValue: String = ""): String { fun getString(@MMKVKey key: String, defaultValue: String = ""): String {
return MMKV.defaultMMKV().decodeString(key, defaultValue)!! return getMMKV().decodeString(key, defaultValue)!!
} }
fun putInt(@MMKVKey key: String, value: Int) { fun putInt(@MMKVKey key: String, value: Int) {
MMKV.defaultMMKV().encode(key, value) getMMKV().encode(key, value)
} }
fun getInt(@MMKVKey key: String, defaultValue: Int = 0): Int { fun getInt(@MMKVKey key: String, defaultValue: Int = 0): Int {
return MMKV.defaultMMKV().decodeInt(key, defaultValue) return getMMKV().decodeInt(key, defaultValue)
} }
fun putFloat(@MMKVKey key: String, value: Float) { fun putFloat(@MMKVKey key: String, value: Float) {
MMKV.defaultMMKV().encode(key, value) getMMKV().encode(key, value)
} }
fun getFloat(@MMKVKey key: String, defaultValue: Float = 0F): Float { fun getFloat(@MMKVKey key: String, defaultValue: Float = 0F): Float {
return MMKV.defaultMMKV().decodeFloat(key, defaultValue) return getMMKV().decodeFloat(key, defaultValue)
} }
fun putLong(@MMKVKey key: String, value: Long) { fun putLong(@MMKVKey key: String, value: Long) {
MMKV.defaultMMKV().encode(key, value) getMMKV().encode(key, value)
} }
fun getLong(@MMKVKey key: String, defaultValue: Long = 0L): Long { fun getLong(@MMKVKey key: String, defaultValue: Long = 0L): Long {
return MMKV.defaultMMKV().decodeLong(key, defaultValue) return getMMKV().decodeLong(key, defaultValue)
} }
fun putDouble(@MMKVKey key: String, value: Double) { fun putDouble(@MMKVKey key: String, value: Double) {
MMKV.defaultMMKV().encode(key, value) getMMKV().encode(key, value)
} }
fun getDouble(@MMKVKey key: String, defaultValue: Double = 0.0): Double { fun getDouble(@MMKVKey key: String, defaultValue: Double = 0.0): Double {
return MMKV.defaultMMKV().decodeDouble(key, defaultValue) return getMMKV().decodeDouble(key, defaultValue)
} }
fun putByteArray(@MMKVKey key: String, value: ByteArray) { fun putByteArray(@MMKVKey key: String, value: ByteArray) {
MMKV.defaultMMKV().encode(key, value) getMMKV().encode(key, value)
} }
fun getByteArray(@MMKVKey key: String, defaultValue: ByteArray = ByteArray(0)): ByteArray { fun getByteArray(@MMKVKey key: String, defaultValue: ByteArray = ByteArray(0)): ByteArray {
return MMKV.defaultMMKV().decodeBytes(key, defaultValue)!! return getMMKV().decodeBytes(key, defaultValue)!!
} }
fun putStringSet(@MMKVKey key: String, value: Set<String>) { fun putStringSet(@MMKVKey key: String, value: Set<String>) {
MMKV.defaultMMKV().encode(key, value) getMMKV().encode(key, value)
} }
fun getStringSet(@MMKVKey key: String, fun getStringSet(
defaultValue: Set<String> = mutableSetOf()): Set<String> { @MMKVKey key: String,
return MMKV.defaultMMKV().decodeStringSet(key, defaultValue)!! defaultValue: Set<String> = mutableSetOf()
): Set<String> {
return getMMKV().decodeStringSet(key, defaultValue)!!
} }
fun putParcelable(@MMKVKey key: String, value: Parcelable) { fun putParcelable(@MMKVKey key: String, value: Parcelable) {
MMKV.defaultMMKV().encode(key, value) getMMKV().encode(key, value)
} }
//清空所有sp //清空所有sp
fun clearAll() { fun clearAll() {
//正式测试标识不清空 //正式测试标识不清空
val debugIp = getBoolean(MMKVKey.DebugIp, true) val debugIp = getBoolean(MMKVKey.DebugIp, true)
MMKV.defaultMMKV().clearAll() getMMKV().clearAll()
putBoolean(MMKVKey.DebugIp, debugIp) putBoolean(MMKVKey.DebugIp, debugIp)
} }
inline fun <reified T : Parcelable> getParcelable(@MMKVKey key: String): T? { inline fun <reified T : Parcelable> getParcelable(@MMKVKey key: String): T? {
return MMKV.defaultMMKV().decodeParcelable(key, T::class.java) return getMMKV().decodeParcelable(key, T::class.java)
} }
} }

View File

@@ -30,6 +30,7 @@ class Utils {
//HTTP请求加密key //HTTP请求加密key
var SECRET_KEY = "" var SECRET_KEY = ""
val CARD_RSA_KEY="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAofuJbnkwyF7SWg3eRadSYT+ttfHN+CUmCzH0WW1EWTUQOG+01Nv8ux+UJUlxbZA2X7959Et/+VKE03+4OY0/26dlgkROMlXCUnzsucuaoHW+9whGi6xklRZrDLi22QBae/8XV1YDEU2vfYXtcvwf66J0t+bRIhGytQUZvUrL7OEaAHvwJU0QwCy3wQGWcCgSkPpDruIe4ar19iZMleE+BdPGIfKfVdPvWFls63g1eMFECb8F9zN6ydGTCvlqvx/4gfDuFQnF+JuTSre8euSpTRTQn/Q0s3K6KiROVcAYCqDW6cKxNUr2ha1C9ZFxMws9C0qhx5wYzfSlIjFthPC1qwIDAQAB"
const val DATE_FORMAT_PATTERN_CN = "yyyy-MM-dd" const val DATE_FORMAT_PATTERN_CN = "yyyy-MM-dd"
const val DATE_FORMAT_PATTERN_CN2 = "yyyy-MM-dd HH:mm:ss" const val DATE_FORMAT_PATTERN_CN2 = "yyyy-MM-dd HH:mm:ss"
const val DATE_FORMAT_PATTERN_CN3 = "yyyy/MM/dd HH:mm:ss" const val DATE_FORMAT_PATTERN_CN3 = "yyyy/MM/dd HH:mm:ss"

View File

@@ -16,7 +16,7 @@
<application <application
android:allowBackup="true" android:allowBackup="false"
android:requestLegacyExternalStorage="true" android:requestLegacyExternalStorage="true"
android:supportsRtl="true"> android:supportsRtl="true">
<activity <activity

View File

@@ -5,7 +5,6 @@ import android.content.ContentUris;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.database.DatabaseUtils;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Environment; import android.os.Environment;
@@ -37,7 +36,6 @@ public class FileUtils {
* TAG for log messages. * TAG for log messages.
*/ */
static final String TAG = "FileUtils"; static final String TAG = "FileUtils";
private static final boolean DEBUG = false; // Set to true to enable logging
/** /**
* File and folder comparator. TODO Expose sorting option method * File and folder comparator. TODO Expose sorting option method
*/ */
@@ -216,7 +214,7 @@ public class FileUtils {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs
, null); , null);
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
if (DEBUG) DatabaseUtils.dumpCursor(cursor); // if (DEBUG) DatabaseUtils.dumpCursor(cursor);
final int column_index = cursor.getColumnIndexOrThrow(column); final int column_index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(column_index); return cursor.getString(column_index);
@@ -248,12 +246,12 @@ public class FileUtils {
private static String getLocalPath(final Context context, final Uri uri) { private static String getLocalPath(final Context context, final Uri uri) {
if (DEBUG) // if (DEBUG)
Log.d(TAG + " File -", // Log.d(TAG + " File -",
"Authority: " + uri.getAuthority() + ", Fragment: " + uri.getFragment() + ", " // "Authority: " + uri.getAuthority() + ", Fragment: " + uri.getFragment() + ", "
+ "Port: " + uri.getPort() + ", Query: " + uri.getQuery() + ", Scheme" // + "Port: " + uri.getPort() + ", Query: " + uri.getQuery() + ", Scheme"
+ ": " + uri.getScheme() + ", Host: " + uri.getHost() + ", Segments: " // + ": " + uri.getScheme() + ", Host: " + uri.getHost() + ", Segments: "
+ uri.getPathSegments().toString()); // + uri.getPathSegments().toString());
// DocumentProvider // DocumentProvider
@@ -487,12 +485,12 @@ public class FileUtils {
} }
private static void logDir(File dir) { private static void logDir(File dir) {
if (!DEBUG) return; // if (!DEBUG) return;
Log.d(TAG, "Dir=" + dir); // Log.d(TAG, "Dir=" + dir);
File[] files = dir.listFiles(); // File[] files = dir.listFiles();
for (File file : files) { // for (File file : files) {
Log.d(TAG, "File=" + file.getPath()); // Log.d(TAG, "File=" + file.getPath());
} // }
} }
@Nullable @Nullable