Skip to content

Commit

Permalink
Updated IAPAutoLogger to utilize v5+ billing wrapper and log results
Browse files Browse the repository at this point in the history
Summary:
This diff contains two important additions:

1. The queryPurchasesAsync and queryPurchaseHistoryAsync methods in the GPBL v5+ wrapper now automatically log the results of the queries after querying product details. This functions the exact same way as the v2-v4 implementation.

2. In the IAPAutoLogger, the billing client wrapper is chosen based on the GPBL version passed by the IAP Manager.

Reviewed By: jjiang10

Differential Revision: D60989903

fbshipit-source-id: 8ee91d9dd2c61e76dd07e039679a3093fb9996e0
  • Loading branch information
maxalbrightmeta authored and facebook-github-bot committed Aug 30, 2024
1 parent 8575b3b commit d2a656c
Show file tree
Hide file tree
Showing 6 changed files with 260 additions and 96 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,66 @@

package com.facebook.appevents.iap

import com.facebook.appevents.iap.InAppPurchaseUtils.BillingClientVersion.V2_V4
import com.facebook.appevents.iap.InAppPurchaseUtils.BillingClientVersion.V5_Plus
import com.facebook.appevents.iap.InAppPurchaseUtils.IAPProductType.INAPP
import android.content.Context
import androidx.annotation.RestrictTo
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import java.util.Collections

@AutoHandleExceptions
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
object InAppPurchaseAutoLogger {
private const val BILLING_CLIENT_PURCHASE_NAME = "com.android.billingclient.api.Purchase"

@JvmStatic
fun startIapLogging(context: Context) {
fun startIapLogging(
context: Context,
billingClientVersion: InAppPurchaseUtils.BillingClientVersion
) {
// check if the app has IAP with Billing Lib
if (InAppPurchaseUtils.getClass(BILLING_CLIENT_PURCHASE_NAME) == null) {
return
}
val billingClientWrapper =
InAppPurchaseBillingClientWrapper.getOrCreateInstance(context) ?: return
if (InAppPurchaseBillingClientWrapper.initialized.get()) {

if (billingClientVersion == V2_V4) {
val billingClientWrapper =
InAppPurchaseBillingClientWrapper.getOrCreateInstance(context) ?: return
if (InAppPurchaseBillingClientWrapper.initialized.get()) {
if (InAppPurchaseLoggerManager.eligibleQueryPurchaseHistory()) {
billingClientWrapper.queryPurchaseHistory(INAPP) {
logPurchase(V2_V4)
}
} else {
billingClientWrapper.queryPurchase(INAPP) {
logPurchase(V2_V4)
}
}
}
} else if (billingClientVersion == V5_Plus) {
val billingClientWrapper =
InAppPurchaseBillingClientWrapperV5Plus.getOrCreateInstance(context) ?: return
if (InAppPurchaseLoggerManager.eligibleQueryPurchaseHistory()) {
billingClientWrapper.queryPurchaseHistory(InAppPurchaseUtils.IAPProductType.INAPP) { logPurchase() }
billingClientWrapper.queryPurchaseHistoryAsync(INAPP) { logPurchase(V5_Plus) }
} else {
billingClientWrapper.queryPurchase(InAppPurchaseUtils.IAPProductType.INAPP) { logPurchase() }
billingClientWrapper.queryPurchasesAsync(INAPP) { logPurchase(V5_Plus) }
}
}
}

private fun logPurchase() {
InAppPurchaseLoggerManager.filterPurchaseLogging(
InAppPurchaseBillingClientWrapper.purchaseDetailsMap,
InAppPurchaseBillingClientWrapper.skuDetailsMap
)
InAppPurchaseBillingClientWrapper.purchaseDetailsMap.clear()
private fun logPurchase(billingClientVersion: InAppPurchaseUtils.BillingClientVersion) {
if (billingClientVersion == V2_V4) {
InAppPurchaseLoggerManager.filterPurchaseLogging(
InAppPurchaseBillingClientWrapper.purchaseDetailsMap,
InAppPurchaseBillingClientWrapper.skuDetailsMap
)
InAppPurchaseBillingClientWrapper.purchaseDetailsMap.clear()
} else {
InAppPurchaseLoggerManager.filterPurchaseLogging(
InAppPurchaseBillingClientWrapperV5Plus.purchaseDetailsMap,
InAppPurchaseBillingClientWrapperV5Plus.productDetailsMap
)
InAppPurchaseBillingClientWrapperV5Plus.purchaseDetailsMap.clear()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -265,12 +265,15 @@ private constructor(
)
}

fun queryPurchasesAsync(productType: InAppPurchaseUtils.IAPProductType) {
fun queryPurchasesAsync(
productType: InAppPurchaseUtils.IAPProductType,
loggingRunnable: Runnable
) {
val runnableQuery = Runnable {
val listenerObj = Proxy.newProxyInstance(
purchasesResponseListenerClazz.classLoader,
arrayOf(purchasesResponseListenerClazz),
ListenerWrapper(arrayOf(productType))
ListenerWrapper(arrayOf(productType, loggingRunnable))
)
invokeMethod(
billingClientClazz,
Expand All @@ -283,12 +286,14 @@ private constructor(
executeServiceRequest(runnableQuery)
}

fun queryPurchaseHistoryAsync(productType: InAppPurchaseUtils.IAPProductType) {
fun queryPurchaseHistoryAsync(
productType: InAppPurchaseUtils.IAPProductType, loggingRunnable: Runnable
) {
val runnableQuery = Runnable {
val listenerObj = Proxy.newProxyInstance(
purchaseHistoryResponseListenerClazz.classLoader,
arrayOf(purchaseHistoryResponseListenerClazz),
ListenerWrapper(arrayOf(productType))
ListenerWrapper(arrayOf(productType, loggingRunnable))
)
invokeMethod(
billingClientClazz,
Expand All @@ -303,13 +308,14 @@ private constructor(

private fun queryProductDetailsAsync(
productType: InAppPurchaseUtils.IAPProductType,
productIds: List<String>
productIds: List<String>,
loggingRunnable: Runnable
) {
val runnableQuery = Runnable {
val listenerObj = Proxy.newProxyInstance(
productDetailsResponseListenerClazz.classLoader,
arrayOf(productDetailsResponseListenerClazz),
ListenerWrapper(null)
ListenerWrapper(arrayOf(loggingRunnable))
)
val queryProductDetailsParams = getQueryProductDetailsParams(productType, productIds)
if (queryProductDetailsParams != null) {
Expand Down Expand Up @@ -353,17 +359,23 @@ private constructor(
return matchResult?.groupValues?.get(1)
}


@AutoHandleExceptions
private fun onQueryPurchasesResponse(wrapperArgs: Array<Any>?, listenerArgs: Array<Any>?) {
val productType = wrapperArgs?.get(0)
if (productType == null || productType !is InAppPurchaseUtils.IAPProductType) {
return
}
val loggingRunnable = wrapperArgs.get(1)
if (loggingRunnable !is Runnable) {
return
}
val purchaseList = listenerArgs?.get(1)
if (purchaseList == null || purchaseList !is List<*>) {
return
}
val productIds = mutableListOf<String>()
var newPurchasesToBeLogged = false
for (purchase in purchaseList) {
val purchaseJsonStr =
invokeMethod(
Expand All @@ -378,10 +390,15 @@ private constructor(
productIds.add(productId)
}
purchaseDetailsMap[productId] = purchaseJson
newPurchasesToBeLogged = true
}
}
if (productIds.isNotEmpty()) {
queryProductDetailsAsync(productType, productIds)
queryProductDetailsAsync(productType, productIds, loggingRunnable)
} else if (newPurchasesToBeLogged) {
// If there is at least one purchase to be logged, but productIds is empty,
// it means we already have the productDetails and can log immediately.
loggingRunnable.run()
}
}

Expand All @@ -391,11 +408,17 @@ private constructor(
if (productType == null || productType !is InAppPurchaseUtils.IAPProductType) {
return
}
val loggingRunnable = wrapperArgs.get(1)
if (loggingRunnable !is Runnable) {
return
}
val purchaseHistoryRecordList = listenerArgs?.get(1)

if (purchaseHistoryRecordList == null || purchaseHistoryRecordList !is List<*>) {
return
}
val productIds = mutableListOf<String>()
var newPurchasesToBeLogged = false
for (purchaseHistoryRecord in purchaseHistoryRecordList) {
try {
val purchaseHistoryRecordJsonStr = invokeMethod(
Expand All @@ -412,18 +435,24 @@ private constructor(
productIds.add(productId)
}
purchaseDetailsMap[productId] = purchaseHistoryRecordJson
newPurchasesToBeLogged = true
}
} catch (e: Exception) {
/* swallow */
}
}
if (productIds.isNotEmpty()) {
queryProductDetailsAsync(productType, productIds)
queryProductDetailsAsync(productType, productIds, loggingRunnable)
} else if (newPurchasesToBeLogged) {
// If there is at least one purchase to be logged, but productIds is empty,
// it means we already have the productDetails and can log immediately.
loggingRunnable.run()
}
}

@AutoHandleExceptions
private fun onProductDetailsResponse(wrapperArgs: Array<Any>?, listenerArgs: Array<Any>?) {
val loggingRunnable = wrapperArgs?.get(0)
val productDetailsList = listenerArgs?.get(1)

if (productDetailsList == null || productDetailsList !is List<*>) {
Expand All @@ -445,6 +474,9 @@ private constructor(
} catch (e: Exception) {
/* swallow */
}
if (loggingRunnable != null && loggingRunnable is Runnable) {
loggingRunnable.run()
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,26 +34,33 @@ object InAppPurchaseManager {

@JvmStatic
fun startTracking() {

if (!enabled.get()) {
return
}
// Delegate IAP logic to separate handler based on Google Play Billing Library version
when (getIAPHandler()) {
when (val billingClientVersion = getBillingClientVersion()) {
NONE -> return
V1 -> InAppPurchaseActivityLifecycleTracker.startIapLogging()
V2_V4 -> {
if (isEnabled(FeatureManager.Feature.IapLoggingLib2)) {
InAppPurchaseAutoLogger.startIapLogging(getApplicationContext())
InAppPurchaseAutoLogger.startIapLogging(
getApplicationContext(),
billingClientVersion
)
} else {
InAppPurchaseActivityLifecycleTracker.startIapLogging()
}
}

V5_Plus -> return
V5_Plus -> InAppPurchaseAutoLogger.startIapLogging(
getApplicationContext(),
billingClientVersion
)
}
}

private fun getIAPHandler(): InAppPurchaseUtils.BillingClientVersion {
private fun getBillingClientVersion(): InAppPurchaseUtils.BillingClientVersion {
try {
val context = getApplicationContext()
val info =
Expand Down
Loading

0 comments on commit d2a656c

Please sign in to comment.