From 0413331cc3ea4ea217146e831ed6e7446dd88921 Mon Sep 17 00:00:00 2001 From: hogliux Date: Thu, 10 Aug 2017 11:43:40 +0100 Subject: [PATCH] Android IAP: Ensure that IAP requests will still succeed even if the IAP service did not yet bind to our app --- .../native/juce_android_InAppPurchases.cpp | 99 +++++++++---------- 1 file changed, 48 insertions(+), 51 deletions(-) diff --git a/modules/juce_product_unlocking/native/juce_android_InAppPurchases.cpp b/modules/juce_product_unlocking/native/juce_android_InAppPurchases.cpp index 3611c9c3f3..b9f03a8736 100644 --- a/modules/juce_product_unlocking/native/juce_android_InAppPurchases.cpp +++ b/modules/juce_product_unlocking/native/juce_android_InAppPurchases.cpp @@ -139,15 +139,10 @@ struct InAppPurchases::Pimpl : private AsyncUpdater, serviceConnection = GlobalRef (CreateJavaInterface (this, "android/content/ServiceConnection").get()); android.activity.callBooleanMethod (JuceAppActivity.bindService, intent, serviceConnection.get(), 1 /*BIND_AUTO_CREATE*/); - - if (threadPool == nullptr) - threadPool = new ThreadPool (1); } ~Pimpl() { - threadPool = nullptr; - if (serviceConnection != nullptr) { android.activity.callVoidMethod (JuceAppActivity.unbindService, serviceConnection.get()); @@ -162,6 +157,9 @@ struct InAppPurchases::Pimpl : private AsyncUpdater, void getProductsInformation (const StringArray& productIdentifiers) { + if (! checkIsReady()) + return; + auto callback = [this](const Array& products) { const ScopedLock lock (getProductsInformationJobResultsLock); @@ -169,13 +167,16 @@ struct InAppPurchases::Pimpl : private AsyncUpdater, triggerAsyncUpdate(); }; - threadPool->addJob (new GetProductsInformationJob (*this, getPackageName(), + threadPool->addJob (new GetProductsInformationJob (inAppBillingService, getPackageName(), productIdentifiers, callback), true); } void purchaseProduct (const String& productIdentifier, bool isSubscription, const StringArray& subscriptionIdentifiers, bool creditForUnusedSubscription) { + if (! checkIsReady()) + return; + // Upgrading/downgrading only makes sense for subscriptions! jassert (subscriptionIdentifiers.isEmpty() || isSubscription); @@ -205,6 +206,9 @@ struct InAppPurchases::Pimpl : private AsyncUpdater, void restoreProductsBoughtList (bool, const juce::String&) { + if (! checkIsReady()) + return; + auto callback = [this](const Array& purchases) { const ScopedLock lock (getProductsBoughtJobResultsLock); @@ -212,12 +216,15 @@ struct InAppPurchases::Pimpl : private AsyncUpdater, triggerAsyncUpdate(); }; - threadPool->addJob (new GetProductsBoughtJob (*this, + threadPool->addJob (new GetProductsBoughtJob (inAppBillingService, getPackageName(), callback), true); } void consumePurchase (const String& productIdentifier, const String& purchaseToken) { + if (! checkIsReady()) + return; + auto callback = [this](const ConsumePurchaseJob::Result& r) { const ScopedLock lock (consumePurchaseJobResultsLock); @@ -225,7 +232,7 @@ struct InAppPurchases::Pimpl : private AsyncUpdater, triggerAsyncUpdate(); }; - threadPool->addJob (new ConsumePurchaseJob (*this, getPackageName(), productIdentifier, + threadPool->addJob (new ConsumePurchaseJob (inAppBillingService, getPackageName(), productIdentifier, purchaseToken, callback), true); } @@ -314,10 +321,6 @@ struct InAppPurchases::Pimpl : private AsyncUpdater, //============================================================================== bool checkIsReady() { - // It may take a few seconds for the in-app purchase service to connect - for (auto retries = 0; retries < 10 && inAppBillingService.get() == 0; ++retries) - Thread::sleep (500); - return (inAppBillingService.get() != 0); } @@ -344,8 +347,6 @@ struct InAppPurchases::Pimpl : private AsyncUpdater, // Connecting to the in-app purchase server failed! This could have multiple reasons: // 1) Your phone/emulator must support the google play store // 2) Your phone must be logged into the google play store and be able to receive updates - // 3) It can take a few seconds after instantiation of the InAppPurchase class for - // in-app purchases to be avaialable on Android. return false; } @@ -359,7 +360,12 @@ struct InAppPurchases::Pimpl : private AsyncUpdater, iBinder)); if (isInAppPurchasesSupported (iapService)) + { + if (threadPool == nullptr) + threadPool = new ThreadPool (1); + inAppBillingService = GlobalRef (iapService); + } // If you hit this assert, then in-app purchases is not available on your device, // most likely due to too old version of Google Play API (hint: update Google Play on the device). @@ -368,6 +374,7 @@ struct InAppPurchases::Pimpl : private AsyncUpdater, void onServiceDisconnected (jobject) override { + threadPool = nullptr; inAppBillingService.clear(); } @@ -382,12 +389,12 @@ struct InAppPurchases::Pimpl : private AsyncUpdater, { using Callback = std::function&)>; - GetProductsInformationJob (Pimpl& parent, + GetProductsInformationJob (const GlobalRef& inAppBillingServiceToUse, const LocalRef& packageNameToUse, const StringArray& productIdentifiersToUse, const Callback& callbackToUse) : ThreadPoolJob ("GetProductsInformationJob"), - owner (parent), + inAppBillingService (inAppBillingServiceToUse), packageName (packageNameToUse.get()), productIdentifiers (productIdentifiersToUse), callback (callbackToUse) @@ -397,7 +404,7 @@ struct InAppPurchases::Pimpl : private AsyncUpdater, { jassert (callback); - if (owner.checkIsReady()) + if (inAppBillingService.get() != 0) { // Google's Billing API limitation auto maxQuerySize = 20; @@ -459,7 +466,7 @@ struct InAppPurchases::Pimpl : private AsyncUpdater, auto productTypeString = javaString (productType); - auto productDetails = LocalRef (owner.inAppBillingService.callObjectMethod (IInAppBillingService.getSkuDetails, + auto productDetails = LocalRef (inAppBillingService.callObjectMethod (IInAppBillingService.getSkuDetails, 3, (jstring) packageName.get(), productTypeString.get(), querySkus.get())); @@ -470,7 +477,7 @@ struct InAppPurchases::Pimpl : private AsyncUpdater, { Array products; - if (owner.checkIsReady()) + if (retrievedProducts.get() != 0) { auto* env = getEnv(); @@ -535,8 +542,8 @@ struct InAppPurchases::Pimpl : private AsyncUpdater, return products; } - Pimpl& owner; - GlobalRef packageName; + + GlobalRef inAppBillingService, packageName; const StringArray productIdentifiers; Callback callback; }; @@ -546,11 +553,11 @@ struct InAppPurchases::Pimpl : private AsyncUpdater, { using Callback = std::function&)>; - GetProductsBoughtJob (Pimpl& parent, + GetProductsBoughtJob (const GlobalRef& inAppBillingServiceToUse, const LocalRef& packageNameToUse, const Callback& callbackToUse) : ThreadPoolJob ("GetProductsBoughtJob"), - owner (parent), + inAppBillingService (inAppBillingServiceToUse), packageName (packageNameToUse.get()), callback (callbackToUse) {} @@ -559,7 +566,7 @@ struct InAppPurchases::Pimpl : private AsyncUpdater, { jassert (callback); - if (owner.checkIsReady()) + if (inAppBillingService.get() != 0) { auto inAppPurchases = getProductsBought ("inapp", 0); auto subsPurchases = getProductsBought ("subs", 0); @@ -590,7 +597,7 @@ struct InAppPurchases::Pimpl : private AsyncUpdater, auto* env = getEnv(); auto productTypeString = javaString (productType); - auto ownedItems = LocalRef (owner.inAppBillingService.callObjectMethod (IInAppBillingService.getPurchases, 3, + auto ownedItems = LocalRef (inAppBillingService.callObjectMethod (IInAppBillingService.getPurchases, 3, (jstring) packageName.get(), productTypeString.get(), continuationToken)); @@ -648,8 +655,7 @@ struct InAppPurchases::Pimpl : private AsyncUpdater, return purchases; } - Pimpl& owner; - GlobalRef packageName; + GlobalRef inAppBillingService, packageName; Callback callback; }; @@ -666,13 +672,13 @@ struct InAppPurchases::Pimpl : private AsyncUpdater, using Callback = std::function; - ConsumePurchaseJob (Pimpl& parent, + ConsumePurchaseJob (const GlobalRef& inAppBillingServiceToUse, const LocalRef& packageNameToUse, const String& productIdentifierToUse, const String& purchaseTokenToUse, const Callback& callbackToUse) : ThreadPoolJob ("ConsumePurchaseJob"), - owner (parent), + inAppBillingService (inAppBillingServiceToUse), packageName (packageNameToUse.get()), productIdentifier (productIdentifierToUse), purchaseToken (purchaseTokenToUse), @@ -683,30 +689,22 @@ struct InAppPurchases::Pimpl : private AsyncUpdater, { jassert (callback); - if (owner.checkIsReady()) - { - auto token = (! purchaseToken.isEmpty() ? purchaseToken : getPurchaseTokenForProductId (productIdentifier, false, 0)); - - if (token.isEmpty()) - { - if (callback) - callback ({ productIdentifier, false, NEEDS_TRANS ("Item not owned") }); - - return jobHasFinished; - } - - auto responseCode = owner.inAppBillingService.callIntMethod (IInAppBillingService.consumePurchase, 3, - (jstring)packageName.get(), javaString (token).get()); + auto token = (! purchaseToken.isEmpty() ? purchaseToken : getPurchaseTokenForProductId (productIdentifier, false, 0)); - if (callback) - callback ({ productIdentifier, responseCode == 0, statusCodeToUserString (responseCode) }); - } - else + if (token.isEmpty()) { if (callback) - callback ({{}, false, "In-App purchases unavailable"}); + callback ({ productIdentifier, false, NEEDS_TRANS ("Item not owned") }); + + return jobHasFinished; } + auto responseCode = inAppBillingService.callIntMethod (IInAppBillingService.consumePurchase, 3, + (jstring)packageName.get(), javaString (token).get()); + + if (callback) + callback ({ productIdentifier, responseCode == 0, statusCodeToUserString (responseCode) }); + return jobHasFinished; } @@ -714,7 +712,7 @@ struct InAppPurchases::Pimpl : private AsyncUpdater, String getPurchaseTokenForProductId (const String productIdToLookFor, bool isSubscription, jstring continuationToken) { auto productTypeString = javaString (isSubscription ? "subs" : "inapp"); - auto ownedItems = LocalRef (owner.inAppBillingService.callObjectMethod (IInAppBillingService.getPurchases, 3, + auto ownedItems = LocalRef (inAppBillingService.callObjectMethod (IInAppBillingService.getPurchases, 3, (jstring) packageName.get(), productTypeString.get(), continuationToken)); @@ -760,8 +758,7 @@ struct InAppPurchases::Pimpl : private AsyncUpdater, return {}; } - Pimpl& owner; - GlobalRef packageName; + GlobalRef inAppBillingService, packageName; const String productIdentifier, purchaseToken; Callback callback; };