google_module模块从业务代码仓库移出

This commit is contained in:
zu
2023-11-28 02:29:02 +08:00
parent 6cde3bd66d
commit 615e0d7427
16 changed files with 0 additions and 605 deletions

View File

@@ -1 +0,0 @@
/build

View File

@@ -1,32 +0,0 @@
apply from: "./module_standard.gradle"
android {
namespace 'com.example.module_google'
def gak = project.hasProperty("GOOGLE_APP_KEY") ? GOOGLE_APP_KEY :
"Define GOOGLE_APP_KEY in gradle.properties. Or './gradlew -PGOOGLE_APP_KEY=gak_value ... taskName'"
def gsci = project.hasProperty("GOOGLE_SERVER_CLIENT_ID") ? GOOGLE_SERVER_CLIENT_ID :
"Define GOOGLE_SERVER_CLIENT_ID in gradle.properties. Or 'gradle -PGOOGLE_SERVER_CLIENT_ID=gak_value ... taskName'"
defaultConfig {
buildConfigField "String", "GOOGLE_APP_KEY", "\"$gak\""
buildConfigField "String", "GOOGLE_SERVER_CLIENT_ID", "\"$gsci\""
}
}
kapt {
arguments {
arg("AROUTER_MODULE_NAME", project.getName())
}
}
dependencies {
// google登录
implementation 'com.google.android.gms:play-services-auth:20.7.0'
// googleplay内购
implementation 'com.google.android.gms:play-services-wallet:19.2.1'
implementation 'com.android.billingclient:billing:6.0.1'
// fastjson
implementation "com.alibaba:fastjson:1.2.41"
}

View File

@@ -1,10 +0,0 @@
/*
* 文件说明业务module的标准配置非base模块外的业务模块
*/
apply from: "../module_base.gradle"
dependencies {
// Base
implementation project(path: ":modules:module_base")
}

View File

@@ -1,21 +0,0 @@
# 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

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@@ -1,32 +0,0 @@
package com.example.module_google
import android.app.Activity
import android.content.Context
import com.alibaba.android.arouter.facade.annotation.Route
import com.example.module_base.config.RouterPath
import com.example.module_base.support.billing.IBillingService
import com.example.module_base.support.google.IGoogleService
import com.example.module_base.support.login.ILoginService
import com.example.module_google.billing.BillingService
import com.example.module_google.login.GoogleLoginService
/**
* Created by Max on 2023/11/22 15:56
* Desc:
**/
@Route(path = RouterPath.GOOGLE_SERVICE)
class GoogleServiceImpl : IGoogleService {
override fun newLoginService(): ILoginService {
return GoogleLoginService()
}
override fun newBillingService(
activity: Activity,
listener: IBillingService.Listener
): IBillingService {
return BillingService(activity, listener)
}
override fun init(context: Context?) {
}
}

View File

@@ -1,18 +0,0 @@
package com.example.module_google.billing
import com.android.billingclient.api.AccountIdentifiers
import com.example.module_base.support.billing.IAccountIdentifiers
/**
* Created by Max on 2023/11/22 20:57
* Desc:
**/
data class AccountIdentifiersImpl(val data: AccountIdentifiers) : IAccountIdentifiers {
override fun getObfuscatedAccountId(): String? {
return data.obfuscatedAccountId
}
override fun getObfuscatedProfileId(): String? {
return data.obfuscatedProfileId
}
}

View File

@@ -1,20 +0,0 @@
package com.example.module_google.billing
import com.android.billingclient.api.BillingClient
import com.android.billingclient.api.BillingResult
import com.example.module_base.support.billing.IBillingResult
/**
* Created by Max on 2023/11/22 20:20
* Desc:
**/
data class BillingResultImpl(val data: BillingResult) : IBillingResult {
override fun getResponseCode(): Int {
return data.responseCode
}
override fun isResponseOk(): Boolean {
return getResponseCode() == BillingClient.BillingResponseCode.OK
}
}

View File

@@ -1,221 +0,0 @@
package com.example.module_google.billing
import android.app.Activity
import android.util.Log
import com.alibaba.fastjson.JSONObject
import com.android.billingclient.api.BillingClient
import com.android.billingclient.api.BillingClient.BillingResponseCode
import com.android.billingclient.api.BillingClientStateListener
import com.android.billingclient.api.BillingFlowParams
import com.android.billingclient.api.BillingFlowParams.ProductDetailsParams
import com.android.billingclient.api.BillingResult
import com.android.billingclient.api.ConsumeParams
import com.android.billingclient.api.ProductDetails
import com.android.billingclient.api.Purchase
import com.android.billingclient.api.PurchasesUpdatedListener
import com.android.billingclient.api.QueryProductDetailsParams
import com.android.billingclient.api.QueryPurchasesParams
import com.example.module_base.support.billing.IBillingService
import com.example.module_base.support.billing.IProductDetails
import com.example.module_base.support.billing.IPurchase
import java.io.IOException
class BillingService(/*活动*/
private val activity: Activity, /*监听*/
private val listener: IBillingService.Listener
) : IBillingService, PurchasesUpdatedListener {
companion object {
private const val TAG = "BillingManager"
/*购买key*/
private const val BASE_64_ENCODED_PUBLIC_KEY =
""
}
/*客户端*/
private var billingClient: BillingClient?
/*是否连接成功*/
private var isServiceConnected = false
private set
/*商品列表*/
private val purchaseList: MutableList<IPurchase> = ArrayList()
init {
billingClient =
BillingClient.newBuilder(activity).enablePendingPurchases().setListener(this).build()
startServiceConnection {
listener.onBillingClientSetupFinished()
onQueryPurchases()
}
}
override fun isServiceConnected(): Boolean {
return isServiceConnected
}
/*开始连接Play*/
private fun startServiceConnection(executeOnSuccess: Runnable?) {
billingClient?.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
Log.d(
TAG,
"Setup finished. Response code: " + billingResult.debugMessage + " code = " + billingResult.responseCode
)
if (billingResult.responseCode == BillingResponseCode.OK) {
isServiceConnected = true
executeOnSuccess?.run()
} else {
isServiceConnected = true
}
}
override fun onBillingServiceDisconnected() {
isServiceConnected = false
}
})
}
/*请求商品库存*/
override fun onQueryPurchases() {
val queryToExecute = Runnable {
billingClient?.queryPurchasesAsync(
QueryPurchasesParams.newBuilder()
.setProductType(BillingClient.ProductType.INAPP)
.build()
) { billingResult: BillingResult, purchases: List<Purchase>? ->
onPurchasesUpdated(
billingResult,
purchases
)
}
}
executeServiceRequest(queryToExecute)
}
/*更新商品*/
override fun onPurchasesUpdated(billingResult: BillingResult, purchases: List<Purchase>?) {
Log.i(TAG, "billingResult.getResponseCode()==" + billingResult.responseCode)
purchaseList.clear()
if (billingResult.responseCode == BillingResponseCode.OK) {
if (purchases != null) {
for (purchase in purchases) {
handlePurchase(PurchaseImpl(purchase))
}
}
listener.onPurchasesUpdated(purchaseList)
} else {
if (billingResult.responseCode == BillingResponseCode.USER_CANCELED) {
} else {
}
listener.onFailedHandle(billingResult.responseCode)
}
}
/*商品处理*/
private fun handlePurchase(purchase: PurchaseImpl) {
//验证签名数据
if (!verifyValidSignature(purchase.data.originalJson, purchase.data.signature)) {
return
}
purchaseList.add(purchase)
}
/*验证签名*/
private fun verifyValidSignature(signedData: String, signature: String): Boolean {
return try {
Security.verifyPurchase(BASE_64_ENCODED_PUBLIC_KEY, signedData, signature)
} catch (e: IOException) {
Log.e(TAG, "Got an exception trying to validate a purchase: $e")
false
}
}
/*执行服务请求*/
private fun executeServiceRequest(runnable: Runnable) {
if (isServiceConnected) {
runnable.run()
} else {
startServiceConnection(runnable)
}
}
/*查询内购商品详情*/
override fun querySkuDetailsAsync(
productIdList: List<String>,
listener: IBillingService.ProductDetailsResponseListener
) {
val queryRequest = Runnable {
val products = ArrayList<QueryProductDetailsParams.Product>()
for (productId in productIdList) {
products.add(
QueryProductDetailsParams.Product.newBuilder()
.setProductId(productId)
.setProductType(BillingClient.ProductType.INAPP)
.build()
)
}
val queryProductDetailsParams = QueryProductDetailsParams.newBuilder()
.setProductList(products)
.build()
billingClient?.queryProductDetailsAsync(
queryProductDetailsParams,
ProductDetailsResponseListenerAdapter(listener)
)
}
executeServiceRequest(queryRequest)
}
/*启动购买,订购流程*/
override fun initiatePurchaseFlow(productDetails: IProductDetails, recordId: String) {
val details = productDetails.getData() as? ProductDetails ?: return
val purchaseFlowRequest = Runnable {
val p = ProductDetailsParams.newBuilder()
.setProductDetails(details)
.build()
val jsonObject = JSONObject()
val oneTimePurchaseOfferDetails = details.oneTimePurchaseOfferDetails
if (oneTimePurchaseOfferDetails != null) {
jsonObject["p"] = oneTimePurchaseOfferDetails.formattedPrice
jsonObject["a"] = oneTimePurchaseOfferDetails.priceAmountMicros / 10000
jsonObject["c"] = oneTimePurchaseOfferDetails.priceCurrencyCode
}
val purchaseParams = BillingFlowParams.newBuilder()
.setObfuscatedAccountId(recordId)
.setObfuscatedProfileId(jsonObject.toJSONString())
.setProductDetailsParamsList(java.util.List.of(p))
.build()
val billingResult = billingClient?.launchBillingFlow(activity, purchaseParams)
Log.i(
TAG,
" initiatePurchaseFlow billingResult=" + billingResult?.responseCode + " " + billingResult?.debugMessage
)
}
executeServiceRequest(purchaseFlowRequest)
}
override fun consumeAsync(purchaseToken: String) {
val consumeParams = ConsumeParams.newBuilder().setPurchaseToken(purchaseToken).build()
executeServiceRequest {
billingClient?.consumeAsync(consumeParams) { billingResult: BillingResult, s: String? ->
listener.onConsumeFinished(
purchaseToken,
billingResult.responseCode
)
}
}
}
/**
* 销毁结算客户端并断开连接
*/
override fun destroy() {
Log.d(TAG, "Destroying the manager.")
if (billingClient != null && billingClient?.isReady == true) {
billingClient?.endConnection()
billingClient = null
}
}
}

View File

@@ -1,23 +0,0 @@
package com.example.module_google.billing
import com.android.billingclient.api.ProductDetails.OneTimePurchaseOfferDetails
import com.example.module_base.support.billing.IOneTimePurchaseOfferDetails
/**
* Created by Max on 2023/11/22 21:05
* Desc:
**/
class OneTimePurchaseOfferDetailsImpl(val data: OneTimePurchaseOfferDetails) :
IOneTimePurchaseOfferDetails {
override fun getPriceAmountMicros(): Long {
return data.priceAmountMicros
}
override fun getFormattedPrice(): String {
return data.formattedPrice
}
override fun getPriceCurrencyCode(): String {
return data.priceCurrencyCode
}
}

View File

@@ -1,24 +0,0 @@
package com.example.module_google.billing
import com.android.billingclient.api.ProductDetails
import com.example.module_base.support.billing.IOneTimePurchaseOfferDetails
import com.example.module_base.support.billing.IProductDetails
/**
* Created by Max on 2023/11/22 20:12
* Desc:
**/
data class ProductDetailsImpl(val data: ProductDetails) : IProductDetails {
override fun getData(): Any {
return data
}
override fun getProductId(): String {
return data.productId
}
override fun getOneTimePurchaseOfferDetails(): IOneTimePurchaseOfferDetails? {
val info = data.oneTimePurchaseOfferDetails ?: return null
return OneTimePurchaseOfferDetailsImpl(info)
}
}

View File

@@ -1,19 +0,0 @@
package com.example.module_google.billing
import com.android.billingclient.api.BillingResult
import com.android.billingclient.api.ProductDetails
import com.android.billingclient.api.ProductDetailsResponseListener
import com.example.module_base.support.billing.IBillingService
/**
* Created by Max on 2023/11/22 20:23
* Desc:
**/
class ProductDetailsResponseListenerAdapter(private val listener: IBillingService.ProductDetailsResponseListener) :
ProductDetailsResponseListener {
override fun onProductDetailsResponse(p0: BillingResult, p1: MutableList<ProductDetails>) {
listener.onProductDetailsResponse(BillingResultImpl(p0), p1.map {
ProductDetailsImpl(it)
})
}
}

View File

@@ -1,44 +0,0 @@
package com.example.module_google.billing
import com.android.billingclient.api.Purchase
import com.example.module_base.support.billing.IAccountIdentifiers
import com.example.module_base.support.billing.IPurchase
/**
* Created by Max on 2023/11/22 20:06
* Desc:
**/
data class PurchaseImpl(val data: Purchase) : IPurchase {
override fun getData(): Any {
return data
}
override fun getPurchaseState(): Int {
return data.purchaseState
}
override fun isPurchasedState(): Boolean {
return getPurchaseState() == Purchase.PurchaseState.PURCHASED
}
override fun getAccountIdentifiers(): IAccountIdentifiers? {
val info = data.accountIdentifiers ?: return null
return AccountIdentifiersImpl(info)
}
override fun getProducts(): List<String> {
return data.products
}
override fun getPackageName(): String {
return data.packageName
}
override fun getPurchaseToken(): String {
return data.purchaseToken
}
override fun getOrderId(): String? {
return data.orderId
}
}

View File

@@ -1,68 +0,0 @@
package com.example.module_google.billing;
import android.text.TextUtils;
import android.util.Base64;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
public class Security {
private static final String TAG = "GoogleIap/Security";
private static final String KEY_FACTORY_ALGORITHM = "RSA";
private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
public static boolean verifyPurchase(String base64PublicKey, String signedData,
String signature) throws IOException {
if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey)
|| TextUtils.isEmpty(signature)) {
return false;
}
PublicKey key = generatePublicKey(base64PublicKey);
return verify(key, signedData, signature);
}
public static PublicKey generatePublicKey(String encodedPublicKey) throws IOException {
try {
byte[] decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
} catch (NoSuchAlgorithmException e) {
// "RSA" is guaranteed to be available.
throw new RuntimeException(e);
} catch (InvalidKeySpecException e) {
String msg = "Invalid key specification: " + e;
throw new IOException(msg);
}
}
public static boolean verify(PublicKey publicKey, String signedData, String signature) {
byte[] signatureBytes;
try {
signatureBytes = Base64.decode(signature, Base64.DEFAULT);
} catch (IllegalArgumentException e) {
return false;
}
try {
Signature signatureAlgorithm = Signature.getInstance(SIGNATURE_ALGORITHM);
signatureAlgorithm.initVerify(publicKey);
signatureAlgorithm.update(signedData.getBytes());
if (!signatureAlgorithm.verify(signatureBytes)) {
return false;
}
return true;
} catch (NoSuchAlgorithmException e) {
// "RSA" is guaranteed to be available.
throw new RuntimeException(e);
} catch (InvalidKeyException e) {
} catch (SignatureException e) {
}
return false;
}
}

View File

@@ -1,68 +0,0 @@
package com.example.module_google.login
import android.app.Activity
import android.content.Intent
import com.example.module_base.support.login.ILoginService
import com.example.module_base.support.login.LoginSDKException
import com.example.module_base.support.login.PlatformInfo
import com.example.module_google.BuildConfig
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInClient
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.google.android.gms.common.api.ApiException
import kotlin.random.Random
/**
* Created by Max on 2023/11/22 15:59
* Desc:Google登录
**/
class GoogleLoginService : ILoginService {
private val requestCode = Random.nextInt(10000, 20000)
private var listener: ILoginService.Listener? = null
private var googleSignInOptions: GoogleSignInOptions =
GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.requestProfile()
.requestIdToken(BuildConfig.GOOGLE_SERVER_CLIENT_ID)
.build()
private var googleSignInClient: GoogleSignInClient? = null
override fun login(activity: Activity, listener: ILoginService.Listener) {
this.listener = listener
if (this.googleSignInClient == null) {
this.googleSignInClient =
GoogleSignIn.getClient(activity.applicationContext, googleSignInOptions)
}
activity.startActivityForResult(
googleSignInClient!!.signInIntent,
requestCode
)
}
override fun logout() {
googleSignInClient?.signOut()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == this.requestCode) {
val task = GoogleSignIn.getSignedInAccountFromIntent(data)
try {
val account = task.getResult(ApiException::class.java)
val info = PlatformInfo(
id = account!!.id!!,
name = account.displayName,
gender = null,
avatar = account.photoUrl?.toString()
)
listener?.onSuccess(info)
} catch (e: ApiException) {
listener?.onFailure(LoginSDKException(e.statusCode, e))
} catch (e: Exception) {
listener?.onFailure(LoginSDKException(-100))
}
}
}
}