Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for Amazon purchase dialog not showing up #552

Merged
merged 42 commits into from
Jul 1, 2022
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
3de0b41
adds ProxyAmazonBillingActivity
vegaro Jun 24, 2022
a11a295
remove finish()
vegaro Jun 24, 2022
619967e
implementation using broadcast receiver
vegaro Jun 27, 2022
4b6faea
fix for status bar
vegaro Jun 27, 2022
0e93706
fix AmazonBillingTest
vegaro Jun 29, 2022
e8c5a3e
fixes more tests
vegaro Jun 29, 2022
5aa5306
savedInstanceState
vegaro Jun 29, 2022
7d9a3fa
refactor to pass purchasingserviceprovider so we can test
vegaro Jun 29, 2022
51c425b
more refactor
vegaro Jun 30, 2022
94d85a6
more tests
vegaro Jun 30, 2022
e1d7863
fix PurchaseHandlerTest
vegaro Jun 30, 2022
7792135
missing dependencies
vegaro Jun 30, 2022
bb5cfeb
Merge branch 'main' into amazon-proxy-billing-activity
vegaro Jun 30, 2022
6bef4de
adds missing dependency
vegaro Jun 30, 2022
03cbd27
adds VisibleForTesting
vegaro Jun 30, 2022
72659e3
removes explicit exported and theme in AndroidManifest
vegaro Jun 30, 2022
4cde5a3
Move createRequestIdResultReceiver to startProxyActivity
vegaro Jun 30, 2022
8a32ed4
remove nullability of context and intent in onreceive
vegaro Jun 30, 2022
dfdc9a4
fix test
vegaro Jun 30, 2022
90f9631
replace other instances of verifyActivityIsStartedAndFakeRequestId an…
vegaro Jun 30, 2022
885b343
extracts creation of intent
vegaro Jun 30, 2022
4fc2c9c
downgrades annotation to 1.3.0 because it's failing compilation
vegaro Jun 30, 2022
177c964
extracts EXTRAS_REQUEST_ID as constant
vegaro Jun 30, 2022
bafef41
moves intent filter creation to ProxyAmazonBillingActivityBroadcastRe…
vegaro Jun 30, 2022
2516c89
remove onReceiveCalled test
vegaro Jun 30, 2022
9bd8e0f
create ProxyAmazonBillingHelper
vegaro Jun 30, 2022
72672ac
move newPurchaseFinishedIntent to ProxyAmazonBillingActivityBroadcast…
vegaro Jun 30, 2022
2cbd49c
creates WeakReference of Activity
vegaro Jun 30, 2022
28ee907
rename to ProxyAmazonBillingDelegate and moves logic there
vegaro Jun 30, 2022
0b147e4
add error logging
vegaro Jun 30, 2022
3cf3048
makes packagename an argument
vegaro Jul 1, 2022
3d03404
companion object moved to top
vegaro Jul 1, 2022
3ed4185
makes proxyAmazonBillingDelegate visiblefortesting
vegaro Jul 1, 2022
22a4aa3
changes activity test to only test creation of delegate
vegaro Jul 1, 2022
9a17cb2
ProxyAmazonBillingActivityTest fixed
vegaro Jul 1, 2022
11dad87
ProxyAmazonBillingDelegateTest
vegaro Jul 1, 2022
bfed36f
fixes test
vegaro Jul 1, 2022
a9f6872
registers broadcast in activity context instead of activity
vegaro Jul 1, 2022
f8bb051
Remove clearAllMocks
vegaro Jul 1, 2022
252d825
move to package and another clearAllMocks
vegaro Jul 1, 2022
0872ebb
adds theme back because it broke the transparency
vegaro Jul 1, 2022
f641be4
Merge branch 'main' into amazon-proxy-billing-activity
vegaro Jul 1, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api-tester/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ android {
dependencies {
implementation project(path: ':public')
implementation project(path: ':purchases')
implementation 'androidx.annotation:annotation:1.3.0'
implementation "androidx.annotation:annotation:$annotationVersion"
}
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ buildscript {
ext.robolectricVersion = "4.6.1"
ext.mockkVersion = "1.10.0"
ext.assertJVersion = "3.13.2"
ext.annotationVersion = "1.3.0"

repositories {
mavenCentral()
google()
Expand Down
3 changes: 3 additions & 0 deletions feature/amazon/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,19 @@ dependencies {
implementation project(":utils")

implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
implementation "androidx.annotation:annotation:$annotationVersion"

latestDependenciesImplementation "com.amazon.device:amazon-appstore-sdk:$amazonVersion"
unityIAPCompileOnly files("libs/in-app-purchasing-${amazon2Version}.jar")

testImplementation "com.amazon.device:amazon-appstore-sdk:$amazonVersion"
testImplementation project(":test-utils")
testImplementation "androidx.test:core:$testLibrariesVersion"
testImplementation "androidx.test:core-ktx:$testLibrariesVersion"
testImplementation "androidx.test:runner:$testLibrariesVersion"
testImplementation "androidx.test:rules:$testLibrariesVersion"
testImplementation "androidx.test.ext:junit:$testJUnitVersion"
testImplementation "androidx.test.ext:junit-ktx:$testJUnitVersion"
testImplementation "org.robolectric:robolectric:$robolectricVersion"
testImplementation "io.mockk:mockk:$mockkVersion"
testImplementation "org.assertj:assertj-core:$assertJVersion"
Expand Down
12 changes: 8 additions & 4 deletions feature/amazon/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
package="com.revenuecat.purchases.amazon">

<application>
<receiver android:name = "com.amazon.device.iap.ResponseReceiver"
android:permission = "com.amazon.inapp.purchasing.Permission.NOTIFY"
android:exported = "true">
<receiver
android:name="com.amazon.device.iap.ResponseReceiver"
android:exported="true"
android:permission="com.amazon.inapp.purchasing.Permission.NOTIFY">
<intent-filter>
<action android:name = "com.amazon.inapp.purchasing.NOTIFY" />
<action android:name="com.amazon.inapp.purchasing.NOTIFY" />
</intent-filter>
</receiver>
<activity
android:name=".ProxyAmazonBillingActivity"
android:configChanges="keyboard|keyboardHidden|screenLayout|screenSize|orientation" />
</application>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ internal class AmazonBilling constructor(
private val mainHandler: Handler,
private val purchasingServiceProvider: PurchasingServiceProvider = DefaultPurchasingServiceProvider(),
private val productDataHandler: ProductDataResponseListener = ProductDataHandler(purchasingServiceProvider),
private val purchaseHandler: PurchaseResponseListener = PurchaseHandler(purchasingServiceProvider),
private val purchaseHandler: PurchaseResponseListener =
PurchaseHandler(purchasingServiceProvider, applicationContext),
private val purchaseUpdatesHandler: PurchaseUpdatesResponseListener = PurchaseUpdatesHandler(
purchasingServiceProvider
),
Expand Down Expand Up @@ -232,13 +233,17 @@ internal class AmazonBilling constructor(
executeRequestOnUIThread { connectionError ->
if (connectionError == null) {
purchaseHandler.purchase(
mainHandler,
activity,
appUserID,
storeProduct,
presentedOfferingIdentifier,
onSuccess = { receipt, userData ->
handleReceipt(receipt, userData, storeProduct, presentedOfferingIdentifier)
},
onError = ::onPurchaseError
onError = {
onPurchaseError(it)
}
)
} else {
onPurchaseError(connectionError)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ object AmazonStrings {
const val ERROR_PURCHASE_NOT_SUPPORTED = "Failed to make purchase. Call is not supported"
const val ERROR_PURCHASE_ALREADY_OWNED = "Failed to make purchase. User already owns SKU."
const val ERROR_PURCHASE_INVALID_SKU = "Failed to make purchase. SKU is invalid"
const val ERROR_PURCHASE_INVALID_PROXY_ACTIVITY_ARGUMENTS = "Failed to make purchase. Arguments are invalid. \n " +
"Intent: %s"
const val ERROR_PURCHASE_FAILED = "Failed to make purchase. " +
"This error normally means that the purchase was cancelled"
const val ERROR_FETCHING_RECEIPT_INFO = "There was an error fetching receipt information: %s"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import com.amazon.device.iap.PurchasingListener
import com.amazon.device.iap.PurchasingService
import com.amazon.device.iap.model.FulfillmentResult
import com.amazon.device.iap.model.RequestId
import kotlinx.parcelize.Parcelize

@Parcelize
class DefaultPurchasingServiceProvider : PurchasingServiceProvider {

override fun registerListener(
Expand All @@ -19,9 +21,7 @@ class DefaultPurchasingServiceProvider : PurchasingServiceProvider {
return PurchasingService.getUserData()
}

override fun purchase(
sku: String
): RequestId {
override fun purchase(sku: String): RequestId {
return PurchasingService.purchase(sku)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.revenuecat.purchases.amazon

import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.ResultReceiver
import com.revenuecat.purchases.amazon.purchasing.ProxyAmazonBillingDelegate

internal class ProxyAmazonBillingActivity : Activity() {

companion object {
const val EXTRAS_RESULT_RECEIVER = "result_receiver"
const val EXTRAS_SKU = "sku"
const val EXTRAS_SERVICE_PROVIDER = "service_provider"
const val EXTRAS_REQUEST_ID = "request_id"

fun newStartIntent(
context: Context,
resultReceiver: ResultReceiver,
sku: String,
purchasingServiceProvider: PurchasingServiceProvider
): Intent {
val intent = Intent(context, ProxyAmazonBillingActivity::class.java)
intent.putExtra(EXTRAS_RESULT_RECEIVER, resultReceiver)
intent.putExtra(EXTRAS_SKU, sku)
intent.putExtra(EXTRAS_SERVICE_PROVIDER, purchasingServiceProvider)
return intent
}
}

private var proxyAmazonBillingDelegate: ProxyAmazonBillingDelegate? = null

override fun onCreate(savedInstanceState: Bundle?) {
// Applying theme programmatically because when applying via AndroidManifest, theme is not being
// applied correctly.
// What happens is that applying @android:style/Theme.Translucent.NoTitleBar in the manifest works
// but applying a theme that has that theme as parent, the Activity is not translucent
setTheme(R.style.ProxyAmazonBillingActivityTheme)
super.onCreate(savedInstanceState)

proxyAmazonBillingDelegate = ProxyAmazonBillingDelegate()
proxyAmazonBillingDelegate?.onCreate(this, savedInstanceState)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tonidero any ideas on how to test that this is called? I could do some little DI framework that injects a mocked ProxyAmazonBillingDelegate but I think that's too much lol

}

override fun onDestroy() {
super.onDestroy()
proxyAmazonBillingDelegate?.onDestroy(this)
proxyAmazonBillingDelegate = null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.revenuecat.purchases.amazon

import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.annotation.VisibleForTesting
import java.lang.ref.WeakReference

internal class ProxyAmazonBillingActivityBroadcastReceiver(activity: Activity) : BroadcastReceiver() {

private val activity: WeakReference<Activity>

init {
this.activity = WeakReference(activity)
}

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
var onReceiveCalled = false
vegaro marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is only added for a test... I'm not sure but is there a way of testing whether the activity was finished in the test scenario? If so we could remove this property entirely. If not, I guess we can keep it for better tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried but I couldn't find anything 😢 I thought about checking if state is ON_DESTROY but it wasn't passing because sendBroadcast is asynchronous. Maybe I can mock that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will probably be hard to mock, since it's created by the activity. I think it's ok, we can leave as is 👍


override fun onReceive(context: Context, intent: Intent) {
onReceiveCalled = true
activity.get()?.finish()
}

companion object {
vegaro marked this conversation as resolved.
Show resolved Hide resolved
const val PURCHASE_FINISHED_ACTION = "com.revenuecat.purchases.purchase_finished"

fun newPurchaseFinishedIntentFilter(): IntentFilter = IntentFilter(PURCHASE_FINISHED_ACTION)

fun newPurchaseFinishedIntent(applicationContext: Context): Intent {
return Intent(PURCHASE_FINISHED_ACTION).also { intent ->
intent.setPackage(applicationContext.packageName)
vegaro marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package com.revenuecat.purchases.amazon

import android.content.Context
import android.os.Parcelable
import com.amazon.device.iap.PurchasingListener
import com.amazon.device.iap.model.FulfillmentResult
import com.amazon.device.iap.model.RequestId

interface PurchasingServiceProvider {
interface PurchasingServiceProvider : Parcelable {

fun registerListener(context: Context, listener: PurchasingListener)
fun getProductData(skus: Set<String>): RequestId
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package com.revenuecat.purchases.amazon.handler

import android.app.Activity
import android.content.Context
import android.os.Bundle
import android.os.Handler
import android.os.ResultReceiver
import com.amazon.device.iap.model.PurchaseResponse
import com.amazon.device.iap.model.Receipt
import com.amazon.device.iap.model.RequestId
Expand All @@ -8,6 +13,8 @@ import com.revenuecat.purchases.ProductType
import com.revenuecat.purchases.PurchasesError
import com.revenuecat.purchases.PurchasesErrorCode
import com.revenuecat.purchases.amazon.AmazonStrings
import com.revenuecat.purchases.amazon.ProxyAmazonBillingActivity
import com.revenuecat.purchases.amazon.ProxyAmazonBillingActivityBroadcastReceiver
import com.revenuecat.purchases.amazon.PurchasingServiceProvider
import com.revenuecat.purchases.amazon.listener.PurchaseResponseListener
import com.revenuecat.purchases.common.LogIntent
Expand All @@ -17,7 +24,8 @@ import com.revenuecat.purchases.models.StoreProduct
import com.revenuecat.purchases.strings.PurchaseStrings

class PurchaseHandler(
private val purchasingServiceProvider: PurchasingServiceProvider
private val purchasingServiceProvider: PurchasingServiceProvider,
private val applicationContext: Context
) : PurchaseResponseListener {

private val productTypes = mutableMapOf<String, ProductType>()
Expand All @@ -26,6 +34,8 @@ class PurchaseHandler(
mutableMapOf<RequestId, Pair<(Receipt, UserData) -> Unit, (PurchasesError) -> Unit>>()

override fun purchase(
mainHandler: Handler,
activity: Activity,
appUserID: String,
storeProduct: StoreProduct,
presentedOfferingIdentifier: String?,
Expand All @@ -34,11 +44,48 @@ class PurchaseHandler(
) {
log(LogIntent.PURCHASE, PurchaseStrings.PURCHASING_PRODUCT.format(storeProduct.sku))

val requestId = purchasingServiceProvider.purchase(storeProduct.sku)
synchronized(this@PurchaseHandler) {
purchaseCallbacks[requestId] = onSuccess to onError
productTypes[storeProduct.sku] = storeProduct.type
presentedOfferingsByProductIdentifier[storeProduct.sku] = presentedOfferingIdentifier
startProxyActivity(mainHandler, activity, storeProduct, presentedOfferingIdentifier, onSuccess, onError)
}

@SuppressWarnings("LongParameterList")
private fun startProxyActivity(
mainHandler: Handler,
activity: Activity,
storeProduct: StoreProduct,
presentedOfferingIdentifier: String?,
onSuccess: (Receipt, UserData) -> Unit,
onError: (PurchasesError) -> Unit
) {
val resultReceiver =
createRequestIdResultReceiver(mainHandler, storeProduct, presentedOfferingIdentifier, onSuccess, onError)
val intent = ProxyAmazonBillingActivity.newStartIntent(
activity,
resultReceiver,
storeProduct.sku,
purchasingServiceProvider
)
// ProxyAmazonBillingActivity will initiate the purchase
activity.startActivity(intent)
}

private fun createRequestIdResultReceiver(
mainHandler: Handler,
storeProduct: StoreProduct,
presentedOfferingIdentifier: String?,
onSuccess: (Receipt, UserData) -> Unit,
onError: (PurchasesError) -> Unit
) = object : ResultReceiver(mainHandler) {
override fun onReceiveResult(resultCode: Int, resultData: Bundle?) {
synchronized(this@PurchaseHandler) {
val requestId = resultData?.get(ProxyAmazonBillingActivity.EXTRAS_REQUEST_ID) as? RequestId
if (requestId != null) {
purchaseCallbacks[requestId] = onSuccess to onError
productTypes[storeProduct.sku] = storeProduct.type
presentedOfferingsByProductIdentifier[storeProduct.sku] = presentedOfferingIdentifier
} else {
errorLog("No RequestId coming from ProxyAmazonBillingActivity")
}
}
}
}

Expand All @@ -47,7 +94,11 @@ class PurchaseHandler(
try {
log(LogIntent.DEBUG, AmazonStrings.PURCHASE_REQUEST_FINISHED.format(response.toJSON().toString(1)))

val intent = ProxyAmazonBillingActivityBroadcastReceiver.newPurchaseFinishedIntent(applicationContext)
applicationContext.sendBroadcast(intent)

val requestId = response.requestId

val callbacks = synchronized(this) { purchaseCallbacks.remove(requestId) }

callbacks?.let { (onSuccess, onError) ->
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.revenuecat.purchases.amazon.listener

import android.app.Activity
import android.os.Handler
import com.amazon.device.iap.PurchasingListener
import com.amazon.device.iap.model.ProductDataResponse
import com.amazon.device.iap.model.PurchaseUpdatesResponse
Expand All @@ -23,7 +25,10 @@ interface PurchaseResponseListener : PurchasingListener {
/* intentionally ignored. Use PurchaseUpdatesResponseListener instead */
}

@SuppressWarnings("LongParameterList")
fun purchase(
mainHandler: Handler,
activity: Activity,
appUserID: String,
storeProduct: StoreProduct,
presentedOfferingIdentifier: String?,
Expand Down
Loading