mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Android: Replaced deprecated AIDL in-app billing code with Google Play Billing library
This commit is contained in:
parent
bad6500424
commit
027e12e3a6
10 changed files with 651 additions and 763 deletions
|
|
@ -52,6 +52,19 @@
|
|||
|
||||
#include "../Assets/DemoUtilities.h"
|
||||
|
||||
/*
|
||||
To finish the setup of this demo, do the following in the Projucer project:
|
||||
|
||||
1. In the project settings, set the "Bundle Identifier" to com.roli.juceInAppPurchaseSample
|
||||
2. In the Android exporter settings, change the following settings:
|
||||
- "In-App Billing" - Enabled
|
||||
- "Key Signing: key.store" - path to InAppPurchase.keystore file in examples/Assets/Signing
|
||||
- "Key Signing: key.store.password" - amazingvoices
|
||||
- "Key Signing: key-alias" - InAppPurchase
|
||||
- "Key Signing: key.alias.password" - amazingvoices
|
||||
3. Re-save the project
|
||||
*/
|
||||
|
||||
//==============================================================================
|
||||
class VoicePurchases : private InAppPurchases::Listener
|
||||
{
|
||||
|
|
@ -108,7 +121,7 @@ public:
|
|||
purchaseInProgress = true;
|
||||
|
||||
product.purchaseInProgress = true;
|
||||
InAppPurchases::getInstance()->purchaseProduct (product.identifier, false);
|
||||
InAppPurchases::getInstance()->purchaseProduct (product.identifier);
|
||||
|
||||
guiUpdater.triggerAsyncUpdate();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -140,9 +140,9 @@ public:
|
|||
androidKeyStorePass (settings, Ids::androidKeyStorePass, getUndoManager(), "android"),
|
||||
androidKeyAlias (settings, Ids::androidKeyAlias, getUndoManager(), "androiddebugkey"),
|
||||
androidKeyAliasPass (settings, Ids::androidKeyAliasPass, getUndoManager(), "android"),
|
||||
gradleVersion (settings, Ids::gradleVersion, getUndoManager(), "4.10"),
|
||||
gradleVersion (settings, Ids::gradleVersion, getUndoManager(), "5.4.1"),
|
||||
gradleToolchain (settings, Ids::gradleToolchain, getUndoManager(), "clang"),
|
||||
androidPluginVersion (settings, Ids::androidPluginVersion, getUndoManager(), "3.2.1"),
|
||||
androidPluginVersion (settings, Ids::androidPluginVersion, getUndoManager(), "3.5.3"),
|
||||
AndroidExecutable (getAppSettings().getStoredPath (Ids::androidStudioExePath, TargetOS::getThisOS()).get().toString())
|
||||
{
|
||||
name = getName();
|
||||
|
|
@ -840,6 +840,9 @@ private:
|
|||
for (auto& d : StringArray::fromLines (androidJavaLibs.get().toString()))
|
||||
mo << " implementation files('libs/" << File (d).getFileName() << "')" << newLine;
|
||||
|
||||
if (isInAppBillingEnabled())
|
||||
mo << " implementation 'com.android.billingclient:billing:2.1.0'" << newLine;
|
||||
|
||||
if (areRemoteNotificationsEnabled())
|
||||
{
|
||||
mo << " implementation 'com.google.firebase:firebase-core:16.0.1'" << newLine;
|
||||
|
|
@ -1215,6 +1218,8 @@ private:
|
|||
bool arePushNotificationsEnabled() const { return androidPushNotifications.get(); }
|
||||
bool areRemoteNotificationsEnabled() const { return arePushNotificationsEnabled() && androidEnableRemoteNotifications.get(); }
|
||||
|
||||
bool isInAppBillingEnabled() const { return androidInAppBillingPermission.get(); }
|
||||
|
||||
String getJNIActivityClassName() const
|
||||
{
|
||||
return getActivityClassString().replaceCharacter ('.', '/');
|
||||
|
|
@ -1436,7 +1441,7 @@ private:
|
|||
defines.set ("JUCE_PUSH_NOTIFICATIONS_ACTIVITY", getJNIActivityClassName().quoted());
|
||||
}
|
||||
|
||||
if (androidInAppBillingPermission.get())
|
||||
if (isInAppBillingEnabled())
|
||||
defines.set ("JUCE_IN_APP_PURCHASES", "1");
|
||||
|
||||
if (supportsGLv3())
|
||||
|
|
@ -1820,7 +1825,7 @@ private:
|
|||
if (androidExternalWritePermission.get())
|
||||
s.add ("android.permission.WRITE_EXTERNAL_STORAGE");
|
||||
|
||||
if (androidInAppBillingPermission.get())
|
||||
if (isInAppBillingEnabled())
|
||||
s.add ("com.android.vending.BILLING");
|
||||
|
||||
if (androidVibratePermission.get())
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
#include <unordered_set>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <queue>
|
||||
|
||||
//==============================================================================
|
||||
#include "juce_CompilerSupport.h"
|
||||
|
|
|
|||
|
|
@ -196,10 +196,6 @@ static const uint8 javaComponentPeerView[] =
|
|||
extern void juce_firebaseRemoteMessageSendError (void*, void*);
|
||||
#endif
|
||||
|
||||
#if JUCE_IN_APP_PURCHASES && JUCE_MODULE_AVAILABLE_juce_product_unlocking
|
||||
extern void juce_inAppPurchaseCompleted (void*);
|
||||
#endif
|
||||
|
||||
extern void juce_contentSharingCompleted (int);
|
||||
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -61,13 +61,11 @@ void InAppPurchases::getProductsInformation (const StringArray& productIdentifie
|
|||
}
|
||||
|
||||
void InAppPurchases::purchaseProduct (const String& productIdentifier,
|
||||
bool isSubscription,
|
||||
const StringArray& upgradeProductIdentifiers,
|
||||
const String& upgradeProductIdentifier,
|
||||
bool creditForUnusedSubscription)
|
||||
{
|
||||
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
|
||||
pimpl->purchaseProduct (productIdentifier, isSubscription,
|
||||
upgradeProductIdentifiers, creditForUnusedSubscription);
|
||||
pimpl->purchaseProduct (productIdentifier, upgradeProductIdentifier, creditForUnusedSubscription);
|
||||
#else
|
||||
Listener::PurchaseInfo purchaseInfo { Purchase { "", productIdentifier, {}, {}, {} }, {} };
|
||||
|
||||
|
|
|
|||
|
|
@ -200,19 +200,15 @@ public:
|
|||
|
||||
@param productIdentifier The product identifier.
|
||||
|
||||
@param isSubscription (Android only) defines if a product a user wants to buy is a subscription or a one-time purchase.
|
||||
On iOS, type of the product is derived implicitly.
|
||||
|
||||
@param upgradeOrDowngradeFromSubscriptionsWithProductIdentifiers (Android only) specifies subscriptions that will be replaced by the
|
||||
one being purchased now. Used only when buying a subscription
|
||||
that is an upgrade or downgrade from other ones.
|
||||
@param upgradeOrDowngradeFromSubscriptionsWithProductIdentifier (Android only) specifies the subscription that will be replaced by
|
||||
the one being purchased now. Used only when buying a subscription
|
||||
that is an upgrade or downgrade from another.
|
||||
|
||||
@param creditForUnusedSubscription (Android only) controls whether a user should be credited for any unused subscription time on
|
||||
the products that are being upgraded or downgraded.
|
||||
the product that is being upgraded or downgraded.
|
||||
*/
|
||||
void purchaseProduct (const String& productIdentifier,
|
||||
bool isSubscription,
|
||||
const StringArray& upgradeOrDowngradeFromSubscriptionsWithProductIdentifiers = {},
|
||||
const String& upgradeOrDowngradeFromSubscriptionWithProductIdentifier = {},
|
||||
bool creditForUnusedSubscription = true);
|
||||
|
||||
/** Asynchronously asks about a list of products that a user has already bought. Upon completion, Listener::purchasesListReceived()
|
||||
|
|
@ -260,6 +256,22 @@ public:
|
|||
/** iOS only: Cancels downloads of hosted content from the store. */
|
||||
void cancelDownloads (const Array<Download*>& downloads);
|
||||
|
||||
//==============================================================================
|
||||
// On Android, it is no longer necessary to specify whether the product being purchased is a subscription
|
||||
// and only a single subscription can be upgraded/downgraded. Use the updated purchaseProduct() method
|
||||
// which takes a single String argument.
|
||||
JUCE_DEPRECATED_WITH_BODY (void purchaseProduct (const String& productIdentifier,
|
||||
bool isSubscription,
|
||||
const StringArray& upgradeOrDowngradeFromSubscriptionsWithProductIdentifiers = {},
|
||||
bool creditForUnusedSubscription = true),
|
||||
{
|
||||
|
||||
ignoreUnused (isSubscription);
|
||||
purchaseProduct (productIdentifier,
|
||||
upgradeOrDowngradeFromSubscriptionsWithProductIdentifiers[0],
|
||||
creditForUnusedSubscription);
|
||||
})
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
website: http://www.juce.com/juce
|
||||
license: GPL/Commercial
|
||||
|
||||
dependencies: juce_cryptography juce_core
|
||||
dependencies: juce_cryptography juce_core, juce_events
|
||||
|
||||
END_JUCE_MODULE_DECLARATION
|
||||
|
||||
|
|
@ -68,6 +68,7 @@
|
|||
//==============================================================================
|
||||
#include <juce_core/juce_core.h>
|
||||
#include <juce_cryptography/juce_cryptography.h>
|
||||
#include <juce_events/juce_events.h>
|
||||
|
||||
#if JUCE_MODULE_AVAILABLE_juce_data_structures
|
||||
#include <juce_data_structures/juce_data_structures.h>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,197 @@
|
|||
package com.roli.juce;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.app.Activity;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.billingclient.api.BillingClient;
|
||||
import com.android.billingclient.api.BillingFlowParams;
|
||||
import com.android.billingclient.api.BillingResult;
|
||||
import com.android.billingclient.api.BillingClientStateListener;
|
||||
import com.android.billingclient.api.ConsumeResponseListener;
|
||||
import com.android.billingclient.api.Purchase;
|
||||
import com.android.billingclient.api.PurchasesUpdatedListener;
|
||||
import com.android.billingclient.api.SkuDetails;
|
||||
import com.android.billingclient.api.SkuDetailsParams;
|
||||
import com.android.billingclient.api.SkuDetailsResponseListener;
|
||||
import com.android.billingclient.api.AcknowledgePurchaseParams;
|
||||
import com.android.billingclient.api.AcknowledgePurchaseResponseListener;
|
||||
import com.android.billingclient.api.ConsumeParams;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
public class JuceBillingClient implements PurchasesUpdatedListener {
|
||||
private native void skuDetailsQueryCallback(long host, List<SkuDetails> skuDetails);
|
||||
private native void purchasesListQueryCallback(long host, List<Purchase> purchases);
|
||||
private native void purchaseCompletedCallback(long host, Purchase purchase, int responseCode);
|
||||
private native void purchaseConsumedCallback(long host, String productIdentifier, int responseCode);
|
||||
|
||||
public JuceBillingClient(Context context, long hostToUse) {
|
||||
host = hostToUse;
|
||||
|
||||
billingClient = BillingClient.newBuilder(context)
|
||||
.enablePendingPurchases()
|
||||
.setListener(this)
|
||||
.build();
|
||||
|
||||
billingClient.startConnection(null);
|
||||
}
|
||||
|
||||
public void endConnection() {
|
||||
billingClient.endConnection();
|
||||
}
|
||||
|
||||
public boolean isReady() {
|
||||
return billingClient.isReady();
|
||||
}
|
||||
|
||||
public boolean isBillingSupported() {
|
||||
return billingClient.isFeatureSupported(BillingClient.FeatureType.SUBSCRIPTIONS).getResponseCode()
|
||||
== BillingClient.BillingResponseCode.OK;
|
||||
}
|
||||
|
||||
public void querySkuDetails(final String[] skusToQuery) {
|
||||
executeOnBillingClientConnection(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final List<String> skuList = Arrays.asList(skusToQuery);
|
||||
|
||||
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder()
|
||||
.setSkusList(skuList)
|
||||
.setType(BillingClient.SkuType.INAPP);
|
||||
|
||||
billingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() {
|
||||
@Override
|
||||
public void onSkuDetailsResponse(BillingResult billingResult, final List<SkuDetails> inAppSkuDetails) {
|
||||
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder()
|
||||
.setSkusList(skuList)
|
||||
.setType(BillingClient.SkuType.SUBS);
|
||||
|
||||
billingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() {
|
||||
@Override
|
||||
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> subsSkuDetails) {
|
||||
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||
subsSkuDetails.addAll(inAppSkuDetails);
|
||||
skuDetailsQueryCallback(host, subsSkuDetails);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void launchBillingFlow(final Activity activity, final BillingFlowParams params) {
|
||||
executeOnBillingClientConnection(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
BillingResult r = billingClient.launchBillingFlow(activity, params);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void queryPurchases() {
|
||||
executeOnBillingClientConnection(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Purchase.PurchasesResult inAppPurchases = billingClient.queryPurchases(BillingClient.SkuType.INAPP);
|
||||
Purchase.PurchasesResult subsPurchases = billingClient.queryPurchases(BillingClient.SkuType.SUBS);
|
||||
|
||||
if (inAppPurchases.getResponseCode() == BillingClient.BillingResponseCode.OK
|
||||
&& subsPurchases.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||
List<Purchase> purchaseList = inAppPurchases.getPurchasesList();
|
||||
purchaseList.addAll(subsPurchases.getPurchasesList());
|
||||
|
||||
purchasesListQueryCallback(host, purchaseList);
|
||||
return;
|
||||
}
|
||||
|
||||
purchasesListQueryCallback(host, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void consumePurchase(final String productIdentifier, final String purchaseToken) {
|
||||
executeOnBillingClientConnection(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ConsumeParams consumeParams = ConsumeParams.newBuilder()
|
||||
.setPurchaseToken(purchaseToken)
|
||||
.build();
|
||||
|
||||
billingClient.consumeAsync(consumeParams, new ConsumeResponseListener() {
|
||||
@Override
|
||||
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
|
||||
purchaseConsumedCallback(host, productIdentifier, billingResult.getResponseCode());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPurchasesUpdated(BillingResult result, List<Purchase> purchases) {
|
||||
int responseCode = result.getResponseCode();
|
||||
|
||||
if (purchases != null) {
|
||||
for (Purchase purchase : purchases) {
|
||||
handlePurchase(purchase, responseCode);
|
||||
}
|
||||
} else {
|
||||
purchaseCompletedCallback(host, null, responseCode);
|
||||
}
|
||||
}
|
||||
|
||||
private void executeOnBillingClientConnection(Runnable runnable) {
|
||||
if (billingClient.isReady()) {
|
||||
runnable.run();
|
||||
} else {
|
||||
connectAndExecute(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
private void connectAndExecute(final Runnable executeOnSuccess) {
|
||||
billingClient.startConnection(new BillingClientStateListener() {
|
||||
@Override
|
||||
public void onBillingSetupFinished(BillingResult billingResponse) {
|
||||
if (billingResponse.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||
if (executeOnSuccess != null) {
|
||||
executeOnSuccess.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBillingServiceDisconnected() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handlePurchase(final Purchase purchase, final int responseCode) {
|
||||
purchaseCompletedCallback(host, purchase, responseCode);
|
||||
|
||||
if (responseCode == BillingClient.BillingResponseCode.OK
|
||||
&& purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED
|
||||
&& !purchase.isAcknowledged()) {
|
||||
executeOnBillingClientConnection(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
AcknowledgePurchaseParams acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build();
|
||||
billingClient.acknowledgePurchase(acknowledgePurchaseParams, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private long host = 0;
|
||||
private BillingClient billingClient;
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -196,7 +196,7 @@ struct InAppPurchases::Pimpl : public SKDelegateAndPaymentObserver
|
|||
[productsRequest start];
|
||||
}
|
||||
|
||||
void purchaseProduct (const String& productIdentifier, bool, const StringArray&, bool)
|
||||
void purchaseProduct (const String& productIdentifier, const String&, bool)
|
||||
{
|
||||
if (! [SKPaymentQueue canMakePayments])
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue