peko-android-gms init
This commit is contained in:
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
/build
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
*.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.idea
|
||||
.settings
|
||||
*.apk
|
||||
|
32
build.gradle
Normal file
32
build.gradle
Normal file
@@ -0,0 +1,32 @@
|
||||
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"
|
||||
}
|
0
consumer-rules.pro
Normal file
0
consumer-rules.pro
Normal file
53
module_base.gradle
Normal file
53
module_base.gradle
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 文件说明:module的基础配置
|
||||
*/
|
||||
apply plugin: "com.android.library"
|
||||
apply plugin: "org.jetbrains.kotlin.android"
|
||||
apply plugin: "kotlin-android"
|
||||
apply plugin: "kotlin-kapt"
|
||||
apply plugin: "com.alibaba.arouter"
|
||||
|
||||
android {
|
||||
compileSdkVersion COMPILE_SDK_VERSION.toInteger()
|
||||
defaultConfig {
|
||||
minSdkVersion MIN_SDK_VERSION.toInteger()
|
||||
targetSdkVersion TARGET_SDK_VERSION.toInteger()
|
||||
versionCode 1
|
||||
versionName "1.0.0"
|
||||
consumerProguardFiles 'proguard-rules.pro'
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
zipAlignEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
debuggable true
|
||||
minifyEnabled false
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '11'
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.material:material:1.8.0'
|
||||
implementation 'androidx.core:core-ktx:1.9.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
|
||||
// aRouter
|
||||
api 'com.alibaba:arouter-api:1.4.0'
|
||||
api 'com.alibaba:arouter-annotation:1.0.6'
|
||||
kapt 'com.alibaba:arouter-compiler:1.5.2'
|
||||
}
|
||||
|
10
module_standard.gradle
Normal file
10
module_standard.gradle
Normal file
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* 文件说明:业务module的标准配置(非base模块外的业务模块)
|
||||
*/
|
||||
apply from: "./module_base.gradle"
|
||||
|
||||
dependencies {
|
||||
// Base
|
||||
implementation project(path: ":modules:module_base")
|
||||
}
|
||||
|
21
proguard-rules.pro
vendored
Normal file
21
proguard-rules.pro
vendored
Normal file
@@ -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
|
4
src/main/AndroidManifest.xml
Normal file
4
src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
32
src/main/java/com/example/module_google/GoogleServiceImpl.kt
Normal file
32
src/main/java/com/example/module_google/GoogleServiceImpl.kt
Normal file
@@ -0,0 +1,32 @@
|
||||
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?) {
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
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
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
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
|
||||
}
|
||||
}
|
@@ -0,0 +1,221 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
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
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
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)
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
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
|
||||
}
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user