Browse Source

Android IAP: Ensure that IAP requests will still succeed even if the IAP service did not yet bind to our app

tags/2021-05-28
hogliux 8 years ago
parent
commit
0413331cc3
1 changed files with 48 additions and 51 deletions
  1. +48
    -51
      modules/juce_product_unlocking/native/juce_android_InAppPurchases.cpp

+ 48
- 51
modules/juce_product_unlocking/native/juce_android_InAppPurchases.cpp View File

@@ -139,15 +139,10 @@ struct InAppPurchases::Pimpl : private AsyncUpdater,
serviceConnection = GlobalRef (CreateJavaInterface (this, "android/content/ServiceConnection").get()); serviceConnection = GlobalRef (CreateJavaInterface (this, "android/content/ServiceConnection").get());
android.activity.callBooleanMethod (JuceAppActivity.bindService, intent, android.activity.callBooleanMethod (JuceAppActivity.bindService, intent,
serviceConnection.get(), 1 /*BIND_AUTO_CREATE*/); serviceConnection.get(), 1 /*BIND_AUTO_CREATE*/);
if (threadPool == nullptr)
threadPool = new ThreadPool (1);
} }
~Pimpl() ~Pimpl()
{ {
threadPool = nullptr;
if (serviceConnection != nullptr) if (serviceConnection != nullptr)
{ {
android.activity.callVoidMethod (JuceAppActivity.unbindService, serviceConnection.get()); android.activity.callVoidMethod (JuceAppActivity.unbindService, serviceConnection.get());
@@ -162,6 +157,9 @@ struct InAppPurchases::Pimpl : private AsyncUpdater,
void getProductsInformation (const StringArray& productIdentifiers) void getProductsInformation (const StringArray& productIdentifiers)
{ {
if (! checkIsReady())
return;
auto callback = [this](const Array<InAppPurchases::Product>& products) auto callback = [this](const Array<InAppPurchases::Product>& products)
{ {
const ScopedLock lock (getProductsInformationJobResultsLock); const ScopedLock lock (getProductsInformationJobResultsLock);
@@ -169,13 +167,16 @@ struct InAppPurchases::Pimpl : private AsyncUpdater,
triggerAsyncUpdate(); triggerAsyncUpdate();
}; };
threadPool->addJob (new GetProductsInformationJob (*this, getPackageName(),
threadPool->addJob (new GetProductsInformationJob (inAppBillingService, getPackageName(),
productIdentifiers, callback), true); productIdentifiers, callback), true);
} }
void purchaseProduct (const String& productIdentifier, bool isSubscription, void purchaseProduct (const String& productIdentifier, bool isSubscription,
const StringArray& subscriptionIdentifiers, bool creditForUnusedSubscription) const StringArray& subscriptionIdentifiers, bool creditForUnusedSubscription)
{ {
if (! checkIsReady())
return;
// Upgrading/downgrading only makes sense for subscriptions! // Upgrading/downgrading only makes sense for subscriptions!
jassert (subscriptionIdentifiers.isEmpty() || isSubscription); jassert (subscriptionIdentifiers.isEmpty() || isSubscription);
@@ -205,6 +206,9 @@ struct InAppPurchases::Pimpl : private AsyncUpdater,
void restoreProductsBoughtList (bool, const juce::String&) void restoreProductsBoughtList (bool, const juce::String&)
{ {
if (! checkIsReady())
return;
auto callback = [this](const Array<InAppPurchases::Listener::PurchaseInfo>& purchases) auto callback = [this](const Array<InAppPurchases::Listener::PurchaseInfo>& purchases)
{ {
const ScopedLock lock (getProductsBoughtJobResultsLock); const ScopedLock lock (getProductsBoughtJobResultsLock);
@@ -212,12 +216,15 @@ struct InAppPurchases::Pimpl : private AsyncUpdater,
triggerAsyncUpdate(); triggerAsyncUpdate();
}; };
threadPool->addJob (new GetProductsBoughtJob (*this,
threadPool->addJob (new GetProductsBoughtJob (inAppBillingService,
getPackageName(), callback), true); getPackageName(), callback), true);
} }
void consumePurchase (const String& productIdentifier, const String& purchaseToken) void consumePurchase (const String& productIdentifier, const String& purchaseToken)
{ {
if (! checkIsReady())
return;
auto callback = [this](const ConsumePurchaseJob::Result& r) auto callback = [this](const ConsumePurchaseJob::Result& r)
{ {
const ScopedLock lock (consumePurchaseJobResultsLock); const ScopedLock lock (consumePurchaseJobResultsLock);
@@ -225,7 +232,7 @@ struct InAppPurchases::Pimpl : private AsyncUpdater,
triggerAsyncUpdate(); triggerAsyncUpdate();
}; };
threadPool->addJob (new ConsumePurchaseJob (*this, getPackageName(), productIdentifier,
threadPool->addJob (new ConsumePurchaseJob (inAppBillingService, getPackageName(), productIdentifier,
purchaseToken, callback), true); purchaseToken, callback), true);
} }
@@ -314,10 +321,6 @@ struct InAppPurchases::Pimpl : private AsyncUpdater,
//============================================================================== //==============================================================================
bool checkIsReady() 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); 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: // Connecting to the in-app purchase server failed! This could have multiple reasons:
// 1) Your phone/emulator must support the google play store // 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 // 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; return false;
} }
@@ -359,7 +360,12 @@ struct InAppPurchases::Pimpl : private AsyncUpdater,
iBinder)); iBinder));
if (isInAppPurchasesSupported (iapService)) if (isInAppPurchasesSupported (iapService))
{
if (threadPool == nullptr)
threadPool = new ThreadPool (1);
inAppBillingService = GlobalRef (iapService); inAppBillingService = GlobalRef (iapService);
}
// If you hit this assert, then in-app purchases is not available on your device, // 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). // 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 void onServiceDisconnected (jobject) override
{ {
threadPool = nullptr;
inAppBillingService.clear(); inAppBillingService.clear();
} }
@@ -382,12 +389,12 @@ struct InAppPurchases::Pimpl : private AsyncUpdater,
{ {
using Callback = std::function<void(const Array<InAppPurchases::Product>&)>; using Callback = std::function<void(const Array<InAppPurchases::Product>&)>;
GetProductsInformationJob (Pimpl& parent,
GetProductsInformationJob (const GlobalRef& inAppBillingServiceToUse,
const LocalRef<jstring>& packageNameToUse, const LocalRef<jstring>& packageNameToUse,
const StringArray& productIdentifiersToUse, const StringArray& productIdentifiersToUse,
const Callback& callbackToUse) const Callback& callbackToUse)
: ThreadPoolJob ("GetProductsInformationJob"), : ThreadPoolJob ("GetProductsInformationJob"),
owner (parent),
inAppBillingService (inAppBillingServiceToUse),
packageName (packageNameToUse.get()), packageName (packageNameToUse.get()),
productIdentifiers (productIdentifiersToUse), productIdentifiers (productIdentifiersToUse),
callback (callbackToUse) callback (callbackToUse)
@@ -397,7 +404,7 @@ struct InAppPurchases::Pimpl : private AsyncUpdater,
{ {
jassert (callback); jassert (callback);
if (owner.checkIsReady())
if (inAppBillingService.get() != 0)
{ {
// Google's Billing API limitation // Google's Billing API limitation
auto maxQuerySize = 20; auto maxQuerySize = 20;
@@ -459,7 +466,7 @@ struct InAppPurchases::Pimpl : private AsyncUpdater,
auto productTypeString = javaString (productType); auto productTypeString = javaString (productType);
auto productDetails = LocalRef<jobject> (owner.inAppBillingService.callObjectMethod (IInAppBillingService.getSkuDetails,
auto productDetails = LocalRef<jobject> (inAppBillingService.callObjectMethod (IInAppBillingService.getSkuDetails,
3, (jstring) packageName.get(), 3, (jstring) packageName.get(),
productTypeString.get(), querySkus.get())); productTypeString.get(), querySkus.get()));
@@ -470,7 +477,7 @@ struct InAppPurchases::Pimpl : private AsyncUpdater,
{ {
Array<InAppPurchases::Product> products; Array<InAppPurchases::Product> products;
if (owner.checkIsReady())
if (retrievedProducts.get() != 0)
{ {
auto* env = getEnv(); auto* env = getEnv();
@@ -535,8 +542,8 @@ struct InAppPurchases::Pimpl : private AsyncUpdater,
return products; return products;
} }
Pimpl& owner;
GlobalRef packageName;
GlobalRef inAppBillingService, packageName;
const StringArray productIdentifiers; const StringArray productIdentifiers;
Callback callback; Callback callback;
}; };
@@ -546,11 +553,11 @@ struct InAppPurchases::Pimpl : private AsyncUpdater,
{ {
using Callback = std::function<void(const Array<InAppPurchases::Listener::PurchaseInfo>&)>; using Callback = std::function<void(const Array<InAppPurchases::Listener::PurchaseInfo>&)>;
GetProductsBoughtJob (Pimpl& parent,
GetProductsBoughtJob (const GlobalRef& inAppBillingServiceToUse,
const LocalRef<jstring>& packageNameToUse, const LocalRef<jstring>& packageNameToUse,
const Callback& callbackToUse) const Callback& callbackToUse)
: ThreadPoolJob ("GetProductsBoughtJob"), : ThreadPoolJob ("GetProductsBoughtJob"),
owner (parent),
inAppBillingService (inAppBillingServiceToUse),
packageName (packageNameToUse.get()), packageName (packageNameToUse.get()),
callback (callbackToUse) callback (callbackToUse)
{} {}
@@ -559,7 +566,7 @@ struct InAppPurchases::Pimpl : private AsyncUpdater,
{ {
jassert (callback); jassert (callback);
if (owner.checkIsReady())
if (inAppBillingService.get() != 0)
{ {
auto inAppPurchases = getProductsBought ("inapp", 0); auto inAppPurchases = getProductsBought ("inapp", 0);
auto subsPurchases = getProductsBought ("subs", 0); auto subsPurchases = getProductsBought ("subs", 0);
@@ -590,7 +597,7 @@ struct InAppPurchases::Pimpl : private AsyncUpdater,
auto* env = getEnv(); auto* env = getEnv();
auto productTypeString = javaString (productType); auto productTypeString = javaString (productType);
auto ownedItems = LocalRef<jobject> (owner.inAppBillingService.callObjectMethod (IInAppBillingService.getPurchases, 3,
auto ownedItems = LocalRef<jobject> (inAppBillingService.callObjectMethod (IInAppBillingService.getPurchases, 3,
(jstring) packageName.get(), productTypeString.get(), (jstring) packageName.get(), productTypeString.get(),
continuationToken)); continuationToken));
@@ -648,8 +655,7 @@ struct InAppPurchases::Pimpl : private AsyncUpdater,
return purchases; return purchases;
} }
Pimpl& owner;
GlobalRef packageName;
GlobalRef inAppBillingService, packageName;
Callback callback; Callback callback;
}; };
@@ -666,13 +672,13 @@ struct InAppPurchases::Pimpl : private AsyncUpdater,
using Callback = std::function<void(const Result&)>; using Callback = std::function<void(const Result&)>;
ConsumePurchaseJob (Pimpl& parent,
ConsumePurchaseJob (const GlobalRef& inAppBillingServiceToUse,
const LocalRef<jstring>& packageNameToUse, const LocalRef<jstring>& packageNameToUse,
const String& productIdentifierToUse, const String& productIdentifierToUse,
const String& purchaseTokenToUse, const String& purchaseTokenToUse,
const Callback& callbackToUse) const Callback& callbackToUse)
: ThreadPoolJob ("ConsumePurchaseJob"), : ThreadPoolJob ("ConsumePurchaseJob"),
owner (parent),
inAppBillingService (inAppBillingServiceToUse),
packageName (packageNameToUse.get()), packageName (packageNameToUse.get()),
productIdentifier (productIdentifierToUse), productIdentifier (productIdentifierToUse),
purchaseToken (purchaseTokenToUse), purchaseToken (purchaseTokenToUse),
@@ -683,30 +689,22 @@ struct InAppPurchases::Pimpl : private AsyncUpdater,
{ {
jassert (callback); 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) 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; return jobHasFinished;
} }
@@ -714,7 +712,7 @@ struct InAppPurchases::Pimpl : private AsyncUpdater,
String getPurchaseTokenForProductId (const String productIdToLookFor, bool isSubscription, jstring continuationToken) String getPurchaseTokenForProductId (const String productIdToLookFor, bool isSubscription, jstring continuationToken)
{ {
auto productTypeString = javaString (isSubscription ? "subs" : "inapp"); auto productTypeString = javaString (isSubscription ? "subs" : "inapp");
auto ownedItems = LocalRef<jobject> (owner.inAppBillingService.callObjectMethod (IInAppBillingService.getPurchases, 3,
auto ownedItems = LocalRef<jobject> (inAppBillingService.callObjectMethod (IInAppBillingService.getPurchases, 3,
(jstring) packageName.get(), productTypeString.get(), (jstring) packageName.get(), productTypeString.get(),
continuationToken)); continuationToken));
@@ -760,8 +758,7 @@ struct InAppPurchases::Pimpl : private AsyncUpdater,
return {}; return {};
} }
Pimpl& owner;
GlobalRef packageName;
GlobalRef inAppBillingService, packageName;
const String productIdentifier, purchaseToken; const String productIdentifier, purchaseToken;
Callback callback; Callback callback;
}; };


Loading…
Cancel
Save