Move purchases to google billing library
This commit is contained in:
parent
cc382f5f74
commit
94fdabe624
12 changed files with 701 additions and 1509 deletions
|
@ -395,6 +395,7 @@ dependencies {
|
||||||
implementation 'org.immutables:gson:2.5.0'
|
implementation 'org.immutables:gson:2.5.0'
|
||||||
implementation 'com.vividsolutions:jts-core:1.14.0'
|
implementation 'com.vividsolutions:jts-core:1.14.0'
|
||||||
implementation 'com.google.openlocationcode:openlocationcode:1.0.4'
|
implementation 'com.google.openlocationcode:openlocationcode:1.0.4'
|
||||||
|
implementation 'com.android.billingclient:billing:2.0.3'
|
||||||
// turn off for now
|
// turn off for now
|
||||||
//implementation 'com.atilika.kuromoji:kuromoji-ipadic:0.9.0'
|
//implementation 'com.atilika.kuromoji:kuromoji-ipadic:0.9.0'
|
||||||
implementation 'com.squareup.picasso:picasso:2.71828'
|
implementation 'com.squareup.picasso:picasso:2.71828'
|
||||||
|
|
|
@ -54,17 +54,6 @@ public class OsmandInAppPurchaseActivity extends AppCompatActivity implements In
|
||||||
deinitInAppPurchaseHelper();
|
deinitInAppPurchaseHelper();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
||||||
// Pass on the activity result to the helper for handling
|
|
||||||
if (purchaseHelper == null || !purchaseHelper.onActivityResultHandled(requestCode, resultCode, data)) {
|
|
||||||
// not handled, so handle it ourselves (here's where you'd
|
|
||||||
// perform any handling of activity results not related to in-app
|
|
||||||
// billing...
|
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initInAppPurchaseHelper() {
|
private void initInAppPurchaseHelper() {
|
||||||
deinitInAppPurchaseHelper();
|
deinitInAppPurchaseHelper();
|
||||||
|
|
||||||
|
|
|
@ -2,15 +2,22 @@ package net.osmand.plus.inapp;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.android.billingclient.api.BillingClient.BillingResponseCode;
|
||||||
|
import com.android.billingclient.api.BillingClient.SkuType;
|
||||||
|
import com.android.billingclient.api.BillingResult;
|
||||||
|
import com.android.billingclient.api.Purchase;
|
||||||
|
import com.android.billingclient.api.SkuDetails;
|
||||||
|
import com.android.billingclient.api.SkuDetailsResponseListener;
|
||||||
|
|
||||||
import net.osmand.AndroidNetworkUtils;
|
import net.osmand.AndroidNetworkUtils;
|
||||||
import net.osmand.AndroidNetworkUtils.OnRequestResultListener;
|
import net.osmand.AndroidNetworkUtils.OnRequestResultListener;
|
||||||
|
import net.osmand.PlatformUtil;
|
||||||
import net.osmand.plus.OsmandApplication;
|
import net.osmand.plus.OsmandApplication;
|
||||||
import net.osmand.plus.OsmandSettings;
|
import net.osmand.plus.OsmandSettings;
|
||||||
import net.osmand.plus.OsmandSettings.OsmandPreference;
|
import net.osmand.plus.OsmandSettings.OsmandPreference;
|
||||||
|
@ -21,13 +28,8 @@ import net.osmand.plus.inapp.InAppPurchases.InAppPurchase.PurchaseState;
|
||||||
import net.osmand.plus.inapp.InAppPurchases.InAppPurchaseLiveUpdatesOldSubscription;
|
import net.osmand.plus.inapp.InAppPurchases.InAppPurchaseLiveUpdatesOldSubscription;
|
||||||
import net.osmand.plus.inapp.InAppPurchases.InAppSubscription;
|
import net.osmand.plus.inapp.InAppPurchases.InAppSubscription;
|
||||||
import net.osmand.plus.inapp.InAppPurchases.InAppSubscriptionList;
|
import net.osmand.plus.inapp.InAppPurchases.InAppSubscriptionList;
|
||||||
import net.osmand.plus.inapp.util.IabHelper;
|
import net.osmand.plus.inapp.util.BillingManager;
|
||||||
import net.osmand.plus.inapp.util.IabHelper.OnIabPurchaseFinishedListener;
|
import net.osmand.plus.inapp.util.BillingManager.BillingUpdatesListener;
|
||||||
import net.osmand.plus.inapp.util.IabHelper.QueryInventoryFinishedListener;
|
|
||||||
import net.osmand.plus.inapp.util.IabResult;
|
|
||||||
import net.osmand.plus.inapp.util.Inventory;
|
|
||||||
import net.osmand.plus.inapp.util.Purchase;
|
|
||||||
import net.osmand.plus.inapp.util.SkuDetails;
|
|
||||||
import net.osmand.plus.liveupdates.CountrySelectionFragment;
|
import net.osmand.plus.liveupdates.CountrySelectionFragment;
|
||||||
import net.osmand.plus.liveupdates.CountrySelectionFragment.CountryItem;
|
import net.osmand.plus.liveupdates.CountrySelectionFragment.CountryItem;
|
||||||
import net.osmand.util.Algorithms;
|
import net.osmand.util.Algorithms;
|
||||||
|
@ -46,11 +48,9 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static net.osmand.plus.inapp.util.IabHelper.IABHELPER_USER_CANCELLED;
|
|
||||||
import static net.osmand.plus.inapp.util.IabHelper.ITEM_TYPE_SUBS;
|
|
||||||
|
|
||||||
public class InAppPurchaseHelper {
|
public class InAppPurchaseHelper {
|
||||||
// Debug tag, for logging
|
// Debug tag, for logging
|
||||||
|
private static final org.apache.commons.logging.Log LOG = PlatformUtil.getLog(InAppPurchaseHelper.class);
|
||||||
private static final String TAG = InAppPurchaseHelper.class.getSimpleName();
|
private static final String TAG = InAppPurchaseHelper.class.getSimpleName();
|
||||||
private boolean mDebugLog = true;
|
private boolean mDebugLog = true;
|
||||||
|
|
||||||
|
@ -64,7 +64,9 @@ public class InAppPurchaseHelper {
|
||||||
private static final int RC_REQUEST = 10001;
|
private static final int RC_REQUEST = 10001;
|
||||||
|
|
||||||
// The helper object
|
// The helper object
|
||||||
private IabHelper mHelper;
|
private BillingManager billingManager;
|
||||||
|
private List<SkuDetails> skuDetailsList;
|
||||||
|
|
||||||
private boolean isDeveloperVersion;
|
private boolean isDeveloperVersion;
|
||||||
private String token = "";
|
private String token = "";
|
||||||
private InAppPurchaseTaskType activeTask;
|
private InAppPurchaseTaskType activeTask;
|
||||||
|
@ -201,10 +203,6 @@ public class InAppPurchaseHelper {
|
||||||
|
|
||||||
// Create the helper, passing it our context and the public key to verify signatures with
|
// Create the helper, passing it our context and the public key to verify signatures with
|
||||||
logDebug("Creating InAppPurchaseHelper.");
|
logDebug("Creating InAppPurchaseHelper.");
|
||||||
mHelper = new IabHelper(ctx, BASE64_ENCODED_PUBLIC_KEY);
|
|
||||||
|
|
||||||
// enable debug logging (for a production application, you should set this to false).
|
|
||||||
mHelper.enableDebugLogging(false);
|
|
||||||
|
|
||||||
// Start setup. This is asynchronous and the specified listener
|
// Start setup. This is asynchronous and the specified listener
|
||||||
// will be called once setup completes.
|
// will be called once setup completes.
|
||||||
|
@ -212,26 +210,101 @@ public class InAppPurchaseHelper {
|
||||||
try {
|
try {
|
||||||
processingTask = true;
|
processingTask = true;
|
||||||
activeTask = taskType;
|
activeTask = taskType;
|
||||||
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
|
billingManager = new BillingManager(ctx, BASE64_ENCODED_PUBLIC_KEY, new BillingUpdatesListener() {
|
||||||
public void onIabSetupFinished(IabResult result) {
|
|
||||||
|
@Override
|
||||||
|
public void onBillingClientSetupFinished() {
|
||||||
logDebug("Setup finished.");
|
logDebug("Setup finished.");
|
||||||
|
|
||||||
if (!result.isSuccess()) {
|
if (!billingManager.isIsServiceConnected()) {
|
||||||
// Oh noes, there was a problem.
|
// Oh noes, there was a problem.
|
||||||
//complain("Problem setting up in-app billing: " + result);
|
//complain("Problem setting up in-app billing: " + result);
|
||||||
notifyError(taskType, result.getMessage());
|
notifyError(taskType, billingManager.getBillingClientResponseMessage());
|
||||||
stop(true);
|
stop(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Have we been disposed of in the meantime? If so, quit.
|
// Have we been disposed of in the meantime? If so, quit.
|
||||||
if (mHelper == null) {
|
if (billingManager == null) {
|
||||||
stop(true);
|
stop(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
processingTask = !runnable.run(InAppPurchaseHelper.this);
|
processingTask = !runnable.run(InAppPurchaseHelper.this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConsumeFinished(String token, BillingResult billingResult) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPurchasesUpdated(List<Purchase> purchases) {
|
||||||
|
|
||||||
|
// Have we been disposed of in the meantime? If so, quit.
|
||||||
|
if (billingManager == null) {
|
||||||
|
stop(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeTask == InAppPurchaseTaskType.REQUEST_INVENTORY) {
|
||||||
|
List<String> skuInApps = new ArrayList<>();
|
||||||
|
for (InAppPurchase purchase : getInAppPurchases().getAllInAppPurchases(false)) {
|
||||||
|
skuInApps.add(purchase.getSku());
|
||||||
|
}
|
||||||
|
billingManager.querySkuDetailsAsync(SkuType.INAPP, skuInApps, new SkuDetailsResponseListener() {
|
||||||
|
@Override
|
||||||
|
public void onSkuDetailsResponse(BillingResult billingResult, final List<SkuDetails> skuDetailsListInApps) {
|
||||||
|
// Is it a failure?
|
||||||
|
if (billingResult.getResponseCode() != BillingResponseCode.OK) {
|
||||||
|
logError("Failed to query inapps sku details: " + billingResult.getResponseCode());
|
||||||
|
notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage());
|
||||||
|
stop(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> skuSubscriptions = new ArrayList<>();
|
||||||
|
for (InAppSubscription subscription : getInAppPurchases().getAllInAppSubscriptions()) {
|
||||||
|
skuSubscriptions.add(subscription.getSku());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Have we been disposed of in the meantime? If so, quit.
|
||||||
|
if (billingManager == null) {
|
||||||
|
stop(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
billingManager.querySkuDetailsAsync(SkuType.SUBS, skuSubscriptions, new SkuDetailsResponseListener() {
|
||||||
|
@Override
|
||||||
|
public void onSkuDetailsResponse(BillingResult billingResult, final List<SkuDetails> skuDetailsListSubscriptions) {
|
||||||
|
// Is it a failure?
|
||||||
|
if (billingResult.getResponseCode() != BillingResponseCode.OK) {
|
||||||
|
logError("Failed to query subscriptipons sku details: " + billingResult.getResponseCode());
|
||||||
|
notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage());
|
||||||
|
stop(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<SkuDetails> skuDetailsList = new ArrayList<>(skuDetailsListInApps);
|
||||||
|
skuDetailsList.addAll(skuDetailsListSubscriptions);
|
||||||
|
InAppPurchaseHelper.this.skuDetailsList = skuDetailsList;
|
||||||
|
|
||||||
|
mSkuDetailsResponseListener.onSkuDetailsResponse(billingResult, skuDetailsList);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (Purchase purchase : purchases) {
|
||||||
|
if (!purchase.isAcknowledged()) {
|
||||||
|
onPurchaseFinished(purchase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPurchaseCanceled() {
|
||||||
|
stop(true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logError("exec Error", e);
|
logError("exec Error", e);
|
||||||
|
@ -255,8 +328,11 @@ public class InAppPurchaseHelper {
|
||||||
@Override
|
@Override
|
||||||
public boolean run(InAppPurchaseHelper helper) {
|
public boolean run(InAppPurchaseHelper helper) {
|
||||||
try {
|
try {
|
||||||
mHelper.launchPurchaseFlow(activity,
|
SkuDetails skuDetails = getSkuDetails(getFullVersion().getSku());
|
||||||
getFullVersion().getSku(), RC_REQUEST, mPurchaseFinishedListener);
|
if (skuDetails == null) {
|
||||||
|
throw new IllegalArgumentException("Cannot find sku details");
|
||||||
|
}
|
||||||
|
billingManager.initiatePurchaseFlow(activity, skuDetails);
|
||||||
return false;
|
return false;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
complain("Cannot launch full version purchase!");
|
complain("Cannot launch full version purchase!");
|
||||||
|
@ -281,8 +357,11 @@ public class InAppPurchaseHelper {
|
||||||
@Override
|
@Override
|
||||||
public boolean run(InAppPurchaseHelper helper) {
|
public boolean run(InAppPurchaseHelper helper) {
|
||||||
try {
|
try {
|
||||||
mHelper.launchPurchaseFlow(activity,
|
SkuDetails skuDetails = getSkuDetails(getDepthContours().getSku());
|
||||||
getDepthContours().getSku(), RC_REQUEST, mPurchaseFinishedListener);
|
if (skuDetails == null) {
|
||||||
|
throw new IllegalArgumentException("Cannot find sku details");
|
||||||
|
}
|
||||||
|
billingManager.initiatePurchaseFlow(activity, skuDetails);
|
||||||
return false;
|
return false;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
complain("Cannot launch depth contours purchase!");
|
complain("Cannot launch depth contours purchase!");
|
||||||
|
@ -294,26 +373,74 @@ public class InAppPurchaseHelper {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private SkuDetails getSkuDetails(@NonNull String sku) {
|
||||||
|
List<SkuDetails> skuDetailsList = this.skuDetailsList;
|
||||||
|
if (skuDetailsList != null) {
|
||||||
|
for (SkuDetails details : skuDetailsList) {
|
||||||
|
if (details.getSku().equals(sku)) {
|
||||||
|
return details;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasDetails(@NonNull String sku) {
|
||||||
|
return getSkuDetails(sku) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Purchase getPurchase(@NonNull String sku) {
|
||||||
|
BillingManager billingManager = this.billingManager;
|
||||||
|
if (billingManager != null) {
|
||||||
|
List<Purchase> purchases = billingManager.getPurchases();
|
||||||
|
if (purchases != null) {
|
||||||
|
for (Purchase p : purchases) {
|
||||||
|
if (p.getSku().equals(sku)) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Listener that's called when we finish querying the items and subscriptions we own
|
// Listener that's called when we finish querying the items and subscriptions we own
|
||||||
private QueryInventoryFinishedListener mGotInventoryListener = new QueryInventoryFinishedListener() {
|
private SkuDetailsResponseListener mSkuDetailsResponseListener = new SkuDetailsResponseListener() {
|
||||||
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
|
|
||||||
logDebug("Query inventory finished.");
|
@NonNull
|
||||||
|
private List<String> getAllOwnedSubscriptionSkus() {
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
for (Purchase p : billingManager.getPurchases()) {
|
||||||
|
InAppPurchase inAppPurchase = getInAppPurchases().getInAppPurchaseBySku(p.getSku());
|
||||||
|
if (inAppPurchase instanceof InAppSubscription) {
|
||||||
|
result.add(p.getSku());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
|
||||||
|
|
||||||
|
logDebug("Query sku details finished.");
|
||||||
|
|
||||||
// Have we been disposed of in the meantime? If so, quit.
|
// Have we been disposed of in the meantime? If so, quit.
|
||||||
if (mHelper == null) {
|
if (billingManager == null) {
|
||||||
stop(true);
|
stop(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is it a failure?
|
// Is it a failure?
|
||||||
if (result.isFailure()) {
|
if (billingResult.getResponseCode() != BillingResponseCode.OK) {
|
||||||
logError("Failed to query inventory: " + result);
|
logError("Failed to query inventory: " + billingResult.getResponseCode());
|
||||||
notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, result.getMessage());
|
notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage());
|
||||||
stop(true);
|
stop(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logDebug("Query inventory was successful.");
|
logDebug("Query sku details was successful.");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check for items we own. Notice that for each purchase, we check
|
* Check for items we own. Notice that for each purchase, we check
|
||||||
|
@ -321,54 +448,64 @@ public class InAppPurchaseHelper {
|
||||||
* verifyDeveloperPayload().
|
* verifyDeveloperPayload().
|
||||||
*/
|
*/
|
||||||
|
|
||||||
List<String> allOwnedSubscriptionSkus = inventory.getAllOwnedSkus(ITEM_TYPE_SUBS);
|
List<String> allOwnedSubscriptionSkus = getAllOwnedSubscriptionSkus();
|
||||||
for (InAppPurchase p : getLiveUpdates().getAllSubscriptions()) {
|
for (InAppPurchase p : getLiveUpdates().getAllSubscriptions()) {
|
||||||
if (inventory.hasDetails(p.getSku())) {
|
if (hasDetails(p.getSku())) {
|
||||||
Purchase purchase = inventory.getPurchase(p.getSku());
|
Purchase purchase = getPurchase(p.getSku());
|
||||||
SkuDetails liveUpdatesDetails = inventory.getSkuDetails(p.getSku());
|
SkuDetails liveUpdatesDetails = getSkuDetails(p.getSku());
|
||||||
fetchInAppPurchase(p, liveUpdatesDetails, purchase);
|
if (liveUpdatesDetails != null) {
|
||||||
|
fetchInAppPurchase(p, liveUpdatesDetails, purchase);
|
||||||
|
}
|
||||||
allOwnedSubscriptionSkus.remove(p.getSku());
|
allOwnedSubscriptionSkus.remove(p.getSku());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (String sku : allOwnedSubscriptionSkus) {
|
for (String sku : allOwnedSubscriptionSkus) {
|
||||||
Purchase purchase = inventory.getPurchase(sku);
|
Purchase purchase = getPurchase(sku);
|
||||||
SkuDetails liveUpdatesDetails = inventory.getSkuDetails(sku);
|
SkuDetails liveUpdatesDetails = getSkuDetails(sku);
|
||||||
InAppSubscription s = getLiveUpdates().upgradeSubscription(sku);
|
if (liveUpdatesDetails != null) {
|
||||||
if (s == null) {
|
InAppSubscription s = getLiveUpdates().upgradeSubscription(sku);
|
||||||
s = new InAppPurchaseLiveUpdatesOldSubscription(liveUpdatesDetails);
|
if (s == null) {
|
||||||
|
s = new InAppPurchaseLiveUpdatesOldSubscription(liveUpdatesDetails);
|
||||||
|
}
|
||||||
|
fetchInAppPurchase(s, liveUpdatesDetails, purchase);
|
||||||
}
|
}
|
||||||
fetchInAppPurchase(s, liveUpdatesDetails, purchase);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InAppPurchase fullVersion = getFullVersion();
|
InAppPurchase fullVersion = getFullVersion();
|
||||||
if (inventory.hasDetails(fullVersion.getSku())) {
|
if (hasDetails(fullVersion.getSku())) {
|
||||||
Purchase purchase = inventory.getPurchase(fullVersion.getSku());
|
Purchase purchase = getPurchase(fullVersion.getSku());
|
||||||
SkuDetails fullPriceDetails = inventory.getSkuDetails(fullVersion.getSku());
|
SkuDetails fullPriceDetails = getSkuDetails(fullVersion.getSku());
|
||||||
fetchInAppPurchase(fullVersion, fullPriceDetails, purchase);
|
if (fullPriceDetails != null) {
|
||||||
|
fetchInAppPurchase(fullVersion, fullPriceDetails, purchase);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InAppPurchase depthContours = getDepthContours();
|
InAppPurchase depthContours = getDepthContours();
|
||||||
if (inventory.hasDetails(depthContours.getSku())) {
|
if (hasDetails(depthContours.getSku())) {
|
||||||
Purchase purchase = inventory.getPurchase(depthContours.getSku());
|
Purchase purchase = getPurchase(depthContours.getSku());
|
||||||
SkuDetails depthContoursDetails = inventory.getSkuDetails(depthContours.getSku());
|
SkuDetails depthContoursDetails = getSkuDetails(depthContours.getSku());
|
||||||
fetchInAppPurchase(depthContours, depthContoursDetails, purchase);
|
if (depthContoursDetails != null) {
|
||||||
|
fetchInAppPurchase(depthContours, depthContoursDetails, purchase);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InAppPurchase contourLines = getContourLines();
|
InAppPurchase contourLines = getContourLines();
|
||||||
if (inventory.hasDetails(contourLines.getSku())) {
|
if (hasDetails(contourLines.getSku())) {
|
||||||
Purchase purchase = inventory.getPurchase(contourLines.getSku());
|
Purchase purchase = getPurchase(contourLines.getSku());
|
||||||
SkuDetails contourLinesDetails = inventory.getSkuDetails(contourLines.getSku());
|
SkuDetails contourLinesDetails = getSkuDetails(contourLines.getSku());
|
||||||
fetchInAppPurchase(contourLines, contourLinesDetails, purchase);
|
if (contourLinesDetails != null) {
|
||||||
|
fetchInAppPurchase(contourLines, contourLinesDetails, purchase);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Purchase fullVersionPurchase = inventory.getPurchase(fullVersion.getSku());
|
Purchase fullVersionPurchase = getPurchase(fullVersion.getSku());
|
||||||
boolean fullVersionPurchased = (fullVersionPurchase != null && fullVersionPurchase.getPurchaseState() == 0);
|
boolean fullVersionPurchased = fullVersionPurchase != null;
|
||||||
if (fullVersionPurchased) {
|
if (fullVersionPurchased) {
|
||||||
ctx.getSettings().FULL_VERSION_PURCHASED.set(true);
|
ctx.getSettings().FULL_VERSION_PURCHASED.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Purchase depthContoursPurchase = inventory.getPurchase(depthContours.getSku());
|
Purchase depthContoursPurchase = getPurchase(depthContours.getSku());
|
||||||
boolean depthContoursPurchased = (depthContoursPurchase != null && depthContoursPurchase.getPurchaseState() == 0);
|
boolean depthContoursPurchased = depthContoursPurchase != null;
|
||||||
if (depthContoursPurchased) {
|
if (depthContoursPurchased) {
|
||||||
ctx.getSettings().DEPTH_CONTOURS_PURCHASED.set(true);
|
ctx.getSettings().DEPTH_CONTOURS_PURCHASED.set(true);
|
||||||
}
|
}
|
||||||
|
@ -377,10 +514,10 @@ public class InAppPurchaseHelper {
|
||||||
boolean subscribedToLiveUpdates = false;
|
boolean subscribedToLiveUpdates = false;
|
||||||
List<Purchase> liveUpdatesPurchases = new ArrayList<>();
|
List<Purchase> liveUpdatesPurchases = new ArrayList<>();
|
||||||
for (InAppPurchase p : getLiveUpdates().getAllSubscriptions()) {
|
for (InAppPurchase p : getLiveUpdates().getAllSubscriptions()) {
|
||||||
Purchase purchase = inventory.getPurchase(p.getSku());
|
Purchase purchase = getPurchase(p.getSku());
|
||||||
if (purchase != null) {
|
if (purchase != null) {
|
||||||
liveUpdatesPurchases.add(purchase);
|
liveUpdatesPurchases.add(purchase);
|
||||||
if (!subscribedToLiveUpdates && purchase.getPurchaseState() == 0) {
|
if (!subscribedToLiveUpdates) {
|
||||||
subscribedToLiveUpdates = true;
|
subscribedToLiveUpdates = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -453,8 +590,7 @@ public class InAppPurchaseHelper {
|
||||||
|
|
||||||
private void fetchInAppPurchase(@NonNull InAppPurchase inAppPurchase, @NonNull SkuDetails skuDetails, @Nullable Purchase purchase) {
|
private void fetchInAppPurchase(@NonNull InAppPurchase inAppPurchase, @NonNull SkuDetails skuDetails, @Nullable Purchase purchase) {
|
||||||
if (purchase != null) {
|
if (purchase != null) {
|
||||||
inAppPurchase.setPurchaseState(purchase.getPurchaseState() == 0
|
inAppPurchase.setPurchaseState(PurchaseState.PURCHASED);
|
||||||
? PurchaseState.PURCHASED : PurchaseState.NOT_PURCHASED);
|
|
||||||
inAppPurchase.setPurchaseTime(purchase.getPurchaseTime());
|
inAppPurchase.setPurchaseTime(purchase.getPurchaseTime());
|
||||||
} else {
|
} else {
|
||||||
inAppPurchase.setPurchaseState(PurchaseState.NOT_PURCHASED);
|
inAppPurchase.setPurchaseState(PurchaseState.NOT_PURCHASED);
|
||||||
|
@ -563,10 +699,10 @@ public class InAppPurchaseHelper {
|
||||||
public boolean run(InAppPurchaseHelper helper) {
|
public boolean run(InAppPurchaseHelper helper) {
|
||||||
try {
|
try {
|
||||||
Activity a = activity.get();
|
Activity a = activity.get();
|
||||||
if (a != null) {
|
SkuDetails skuDetails = getSkuDetails(sku);
|
||||||
mHelper.launchPurchaseFlow(a,
|
if (a != null && skuDetails != null) {
|
||||||
sku, ITEM_TYPE_SUBS,
|
billingManager.setPayload(payload);
|
||||||
RC_REQUEST, mPurchaseFinishedListener, payload);
|
billingManager.initiatePurchaseFlow(a, skuDetails);
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
stop(true);
|
stop(true);
|
||||||
|
@ -585,28 +721,6 @@ public class InAppPurchaseHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean onActivityResultHandled(int requestCode, int resultCode, Intent data) {
|
|
||||||
logDebug("onActivityResult(" + requestCode + "," + resultCode + "," + data);
|
|
||||||
if (mHelper == null) return false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Pass on the activity result to the helper for handling
|
|
||||||
if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
|
|
||||||
// not handled, so handle it ourselves (here's where you'd
|
|
||||||
// perform any handling of activity results not related to in-app
|
|
||||||
// billing...
|
|
||||||
//super.onActivityResult(requestCode, resultCode, data);
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
logDebug("onActivityResult handled by IABUtil.");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
logError("onActivityResultHandled", e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
private class RequestInventoryTask extends AsyncTask<Void, Void, String> {
|
private class RequestInventoryTask extends AsyncTask<Void, Void, String> {
|
||||||
|
|
||||||
|
@ -652,12 +766,8 @@ public class InAppPurchaseHelper {
|
||||||
@Override
|
@Override
|
||||||
public boolean run(InAppPurchaseHelper helper) {
|
public boolean run(InAppPurchaseHelper helper) {
|
||||||
logDebug("Setup successful. Querying inventory.");
|
logDebug("Setup successful. Querying inventory.");
|
||||||
Set<String> skus = new HashSet<>();
|
|
||||||
for (InAppPurchase purchase : purchases.getAllInAppPurchases()) {
|
|
||||||
skus.add(purchase.getSku());
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
mHelper.queryInventoryAsync(true, new ArrayList<>(skus), mGotInventoryListener);
|
billingManager.queryPurchases();
|
||||||
return false;
|
return false;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logError("queryInventoryAsync Error", e);
|
logError("queryInventoryAsync Error", e);
|
||||||
|
@ -679,81 +789,69 @@ public class InAppPurchaseHelper {
|
||||||
parameters.put("aid", ctx.getUserAndroidId());
|
parameters.put("aid", ctx.getUserAndroidId());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback for when a purchase is finished
|
// Call when a purchase is finished
|
||||||
private OnIabPurchaseFinishedListener mPurchaseFinishedListener = new OnIabPurchaseFinishedListener() {
|
private void onPurchaseFinished(Purchase purchase) {
|
||||||
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
|
logDebug("Purchase finished: " + purchase);
|
||||||
logDebug("Purchase finished: " + result + ", purchase: " + purchase);
|
|
||||||
|
|
||||||
// if we were disposed of in the meantime, quit.
|
// if we were disposed of in the meantime, quit.
|
||||||
if (mHelper == null) {
|
if (billingManager == null) {
|
||||||
stop(true);
|
stop(true);
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (result.isFailure()) {
|
|
||||||
if (result.getResponse() != IABHELPER_USER_CANCELLED) {
|
|
||||||
complain("Error purchasing: " + result);
|
|
||||||
}
|
|
||||||
notifyDismissProgress(activeTask);
|
|
||||||
notifyError(activeTask, "Error purchasing: " + result);
|
|
||||||
stop(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logDebug("Purchase successful.");
|
|
||||||
|
|
||||||
InAppPurchase liveUpdatesPurchase = getLiveUpdates().getSubscriptionBySku(purchase.getSku());
|
|
||||||
if (liveUpdatesPurchase != null) {
|
|
||||||
// bought live updates
|
|
||||||
logDebug("Live updates subscription purchased.");
|
|
||||||
final String sku = liveUpdatesPurchase.getSku();
|
|
||||||
liveUpdatesPurchase.setPurchaseState(purchase.getPurchaseState() == 0 ? PurchaseState.PURCHASED : PurchaseState.NOT_PURCHASED);
|
|
||||||
sendTokens(Collections.singletonList(purchase), new OnRequestResultListener() {
|
|
||||||
@Override
|
|
||||||
public void onResult(String result) {
|
|
||||||
boolean active = ctx.getSettings().LIVE_UPDATES_PURCHASED.get();
|
|
||||||
ctx.getSettings().LIVE_UPDATES_PURCHASED.set(true);
|
|
||||||
ctx.getSettings().getCustomRenderBooleanProperty("depthContours").set(true);
|
|
||||||
|
|
||||||
ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_TIME.set(0L);
|
|
||||||
ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN.set(false);
|
|
||||||
ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN.set(false);
|
|
||||||
|
|
||||||
notifyDismissProgress(InAppPurchaseTaskType.PURCHASE_LIVE_UPDATES);
|
|
||||||
notifyItemPurchased(sku, active);
|
|
||||||
stop(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
} else if (purchase.getSku().equals(getFullVersion().getSku())) {
|
|
||||||
// bought full version
|
|
||||||
getFullVersion().setPurchaseState(purchase.getPurchaseState() == 0 ? PurchaseState.PURCHASED : PurchaseState.NOT_PURCHASED);
|
|
||||||
logDebug("Full version purchased.");
|
|
||||||
showToast(ctx.getString(R.string.full_version_thanks));
|
|
||||||
ctx.getSettings().FULL_VERSION_PURCHASED.set(true);
|
|
||||||
|
|
||||||
notifyDismissProgress(InAppPurchaseTaskType.PURCHASE_FULL_VERSION);
|
|
||||||
notifyItemPurchased(getFullVersion().getSku(), false);
|
|
||||||
stop(true);
|
|
||||||
|
|
||||||
} else if (purchase.getSku().equals(getDepthContours().getSku())) {
|
|
||||||
// bought sea depth contours
|
|
||||||
getDepthContours().setPurchaseState(purchase.getPurchaseState() == 0 ? PurchaseState.PURCHASED : PurchaseState.NOT_PURCHASED);
|
|
||||||
logDebug("Sea depth contours purchased.");
|
|
||||||
showToast(ctx.getString(R.string.sea_depth_thanks));
|
|
||||||
ctx.getSettings().DEPTH_CONTOURS_PURCHASED.set(true);
|
|
||||||
ctx.getSettings().getCustomRenderBooleanProperty("depthContours").set(true);
|
|
||||||
|
|
||||||
notifyDismissProgress(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS);
|
|
||||||
notifyItemPurchased(getDepthContours().getSku(), false);
|
|
||||||
stop(true);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
notifyDismissProgress(activeTask);
|
|
||||||
stop(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
logDebug("Purchase successful.");
|
||||||
|
|
||||||
|
InAppPurchase liveUpdatesPurchase = getLiveUpdates().getSubscriptionBySku(purchase.getSku());
|
||||||
|
if (liveUpdatesPurchase != null) {
|
||||||
|
// bought live updates
|
||||||
|
logDebug("Live updates subscription purchased.");
|
||||||
|
final String sku = liveUpdatesPurchase.getSku();
|
||||||
|
liveUpdatesPurchase.setPurchaseState(PurchaseState.PURCHASED);
|
||||||
|
sendTokens(Collections.singletonList(purchase), new OnRequestResultListener() {
|
||||||
|
@Override
|
||||||
|
public void onResult(String result) {
|
||||||
|
boolean active = ctx.getSettings().LIVE_UPDATES_PURCHASED.get();
|
||||||
|
ctx.getSettings().LIVE_UPDATES_PURCHASED.set(true);
|
||||||
|
ctx.getSettings().getCustomRenderBooleanProperty("depthContours").set(true);
|
||||||
|
|
||||||
|
ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_TIME.set(0L);
|
||||||
|
ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN.set(false);
|
||||||
|
ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN.set(false);
|
||||||
|
|
||||||
|
notifyDismissProgress(InAppPurchaseTaskType.PURCHASE_LIVE_UPDATES);
|
||||||
|
notifyItemPurchased(sku, active);
|
||||||
|
stop(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if (purchase.getSku().equals(getFullVersion().getSku())) {
|
||||||
|
// bought full version
|
||||||
|
getFullVersion().setPurchaseState(PurchaseState.PURCHASED);
|
||||||
|
logDebug("Full version purchased.");
|
||||||
|
showToast(ctx.getString(R.string.full_version_thanks));
|
||||||
|
ctx.getSettings().FULL_VERSION_PURCHASED.set(true);
|
||||||
|
|
||||||
|
notifyDismissProgress(InAppPurchaseTaskType.PURCHASE_FULL_VERSION);
|
||||||
|
notifyItemPurchased(getFullVersion().getSku(), false);
|
||||||
|
stop(true);
|
||||||
|
|
||||||
|
} else if (purchase.getSku().equals(getDepthContours().getSku())) {
|
||||||
|
// bought sea depth contours
|
||||||
|
getDepthContours().setPurchaseState(PurchaseState.PURCHASED);
|
||||||
|
logDebug("Sea depth contours purchased.");
|
||||||
|
showToast(ctx.getString(R.string.sea_depth_thanks));
|
||||||
|
ctx.getSettings().DEPTH_CONTOURS_PURCHASED.set(true);
|
||||||
|
ctx.getSettings().getCustomRenderBooleanProperty("depthContours").set(true);
|
||||||
|
|
||||||
|
notifyDismissProgress(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS);
|
||||||
|
notifyItemPurchased(getDepthContours().getSku(), false);
|
||||||
|
stop(true);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
notifyDismissProgress(activeTask);
|
||||||
|
stop(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Do not forget call stop() when helper is not needed anymore
|
// Do not forget call stop() when helper is not needed anymore
|
||||||
public void stop() {
|
public void stop() {
|
||||||
|
@ -762,14 +860,14 @@ public class InAppPurchaseHelper {
|
||||||
|
|
||||||
private void stop(boolean taskDone) {
|
private void stop(boolean taskDone) {
|
||||||
logDebug("Destroying helper.");
|
logDebug("Destroying helper.");
|
||||||
if (mHelper != null) {
|
if (billingManager != null) {
|
||||||
if (taskDone) {
|
if (taskDone) {
|
||||||
processingTask = false;
|
processingTask = false;
|
||||||
}
|
}
|
||||||
if (!processingTask) {
|
if (!processingTask) {
|
||||||
activeTask = null;
|
activeTask = null;
|
||||||
mHelper.dispose();
|
billingManager.destroy();
|
||||||
mHelper = null;
|
billingManager = null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
processingTask = false;
|
processingTask = false;
|
||||||
|
@ -793,7 +891,7 @@ public class InAppPurchaseHelper {
|
||||||
Map<String, String> parameters = new HashMap<>();
|
Map<String, String> parameters = new HashMap<>();
|
||||||
parameters.put("userid", userId);
|
parameters.put("userid", userId);
|
||||||
parameters.put("sku", purchase.getSku());
|
parameters.put("sku", purchase.getSku());
|
||||||
parameters.put("purchaseToken", purchase.getToken());
|
parameters.put("purchaseToken", purchase.getPurchaseToken());
|
||||||
parameters.put("email", email);
|
parameters.put("email", email);
|
||||||
parameters.put("token", token);
|
parameters.put("token", token);
|
||||||
addUserInfo(parameters);
|
addUserInfo(parameters);
|
||||||
|
|
|
@ -7,10 +7,11 @@ import android.text.Spannable;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.style.StyleSpan;
|
import android.text.style.StyleSpan;
|
||||||
|
|
||||||
|
import com.android.billingclient.api.SkuDetails;
|
||||||
|
|
||||||
import net.osmand.plus.OsmandApplication;
|
import net.osmand.plus.OsmandApplication;
|
||||||
import net.osmand.plus.R;
|
import net.osmand.plus.R;
|
||||||
import net.osmand.plus.Version;
|
import net.osmand.plus.Version;
|
||||||
import net.osmand.plus.inapp.util.SkuDetails;
|
|
||||||
import net.osmand.util.Algorithms;
|
import net.osmand.util.Algorithms;
|
||||||
|
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
|
@ -122,15 +123,21 @@ public class InAppPurchases {
|
||||||
return liveUpdates;
|
return liveUpdates;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<InAppPurchase> getAllInAppPurchases() {
|
public List<InAppPurchase> getAllInAppPurchases(boolean includeSubscriptions) {
|
||||||
List<InAppPurchase> purchases = new ArrayList<>();
|
List<InAppPurchase> purchases = new ArrayList<>();
|
||||||
purchases.add(fullVersion);
|
purchases.add(fullVersion);
|
||||||
purchases.add(depthContours);
|
purchases.add(depthContours);
|
||||||
purchases.add(contourLines);
|
purchases.add(contourLines);
|
||||||
purchases.addAll(liveUpdates.getAllSubscriptions());
|
if (includeSubscriptions) {
|
||||||
|
purchases.addAll(liveUpdates.getAllSubscriptions());
|
||||||
|
}
|
||||||
return purchases;
|
return purchases;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<InAppSubscription> getAllInAppSubscriptions() {
|
||||||
|
return liveUpdates.getAllSubscriptions();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isFullVersion(String sku) {
|
public boolean isFullVersion(String sku) {
|
||||||
return FULL_VERSION.getSku().equals(sku);
|
return FULL_VERSION.getSku().equals(sku);
|
||||||
}
|
}
|
||||||
|
|
420
OsmAnd/src/net/osmand/plus/inapp/util/BillingManager.java
Normal file
420
OsmAnd/src/net/osmand/plus/inapp/util/BillingManager.java
Normal file
|
@ -0,0 +1,420 @@
|
||||||
|
package net.osmand.plus.inapp.util;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.android.billingclient.api.AcknowledgePurchaseParams;
|
||||||
|
import com.android.billingclient.api.AcknowledgePurchaseResponseListener;
|
||||||
|
import com.android.billingclient.api.BillingClient;
|
||||||
|
import com.android.billingclient.api.BillingClient.BillingResponseCode;
|
||||||
|
import com.android.billingclient.api.BillingClient.FeatureType;
|
||||||
|
import com.android.billingclient.api.BillingClient.SkuType;
|
||||||
|
import com.android.billingclient.api.BillingClientStateListener;
|
||||||
|
import com.android.billingclient.api.BillingFlowParams;
|
||||||
|
import com.android.billingclient.api.BillingResult;
|
||||||
|
import com.android.billingclient.api.ConsumeParams;
|
||||||
|
import com.android.billingclient.api.ConsumeResponseListener;
|
||||||
|
import com.android.billingclient.api.Purchase;
|
||||||
|
import com.android.billingclient.api.Purchase.PurchasesResult;
|
||||||
|
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 net.osmand.PlatformUtil;
|
||||||
|
import net.osmand.util.Algorithms;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles all the interactions with Play Store (via Billing library), maintains connection to
|
||||||
|
* it through BillingClient and caches temporary states/data if needed
|
||||||
|
*/
|
||||||
|
public class BillingManager implements PurchasesUpdatedListener {
|
||||||
|
// Default value of mBillingClientResponseCode until BillingManager was not yeat initialized
|
||||||
|
public static final int BILLING_MANAGER_NOT_INITIALIZED = -1;
|
||||||
|
|
||||||
|
private static final org.apache.commons.logging.Log LOG = PlatformUtil.getLog(BillingManager.class);
|
||||||
|
private static final String TAG = "BillingManager";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reference to BillingClient
|
||||||
|
**/
|
||||||
|
private BillingClient mBillingClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if billing service is connected now.
|
||||||
|
*/
|
||||||
|
private boolean mIsServiceConnected;
|
||||||
|
|
||||||
|
private final Context mContext;
|
||||||
|
|
||||||
|
// Public key for verifying signature, in base64 encoding
|
||||||
|
private String mSignatureBase64;
|
||||||
|
private String mPayload;
|
||||||
|
|
||||||
|
private final BillingUpdatesListener mBillingUpdatesListener;
|
||||||
|
private final List<Purchase> mPurchases = new ArrayList<>();
|
||||||
|
private Set<String> mTokensToBeConsumed;
|
||||||
|
|
||||||
|
private int mBillingClientResponseCode = BILLING_MANAGER_NOT_INITIALIZED;
|
||||||
|
private String mBillingClientResponseMessage;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener to the updates that happen when purchases list was updated or consumption of the
|
||||||
|
* item was finished
|
||||||
|
*/
|
||||||
|
public interface BillingUpdatesListener {
|
||||||
|
void onBillingClientSetupFinished();
|
||||||
|
|
||||||
|
void onConsumeFinished(String token, BillingResult billingResult);
|
||||||
|
|
||||||
|
void onPurchasesUpdated(List<Purchase> purchases);
|
||||||
|
|
||||||
|
void onPurchaseCanceled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener for the Billing client state to become connected
|
||||||
|
*/
|
||||||
|
public interface ServiceConnectedListener {
|
||||||
|
void onServiceConnected(BillingResult billingResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BillingManager(@NonNull final Context context, @NonNull final String base64PublicKey,
|
||||||
|
@NonNull final BillingUpdatesListener updatesListener) {
|
||||||
|
LOG.debug("Creating Billing client.");
|
||||||
|
mContext = context;
|
||||||
|
mSignatureBase64 = base64PublicKey;
|
||||||
|
mBillingUpdatesListener = updatesListener;
|
||||||
|
mBillingClient = BillingClient.newBuilder(mContext)
|
||||||
|
.enablePendingPurchases()
|
||||||
|
.setListener(this)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
LOG.debug("Starting setup.");
|
||||||
|
|
||||||
|
// Start setup. This is asynchronous and the specified listener will be called
|
||||||
|
// once setup completes.
|
||||||
|
// It also starts to report all the new purchases through onPurchasesUpdated() callback.
|
||||||
|
startServiceConnection(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a callback that purchases were updated from the Billing library
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> purchases) {
|
||||||
|
int responseCode = billingResult.getResponseCode();
|
||||||
|
if (responseCode == BillingResponseCode.OK) {
|
||||||
|
if (purchases != null) {
|
||||||
|
for (Purchase purchase : purchases) {
|
||||||
|
handlePurchase(purchase);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG.info("onPurchasesUpdated() - no purchases");
|
||||||
|
}
|
||||||
|
mBillingUpdatesListener.onPurchasesUpdated(mPurchases);
|
||||||
|
} else if (responseCode == BillingResponseCode.USER_CANCELED) {
|
||||||
|
LOG.info("onPurchasesUpdated() - user cancelled the purchase flow - skipping");
|
||||||
|
mBillingUpdatesListener.onPurchaseCanceled();
|
||||||
|
} else {
|
||||||
|
LOG.warn("onPurchasesUpdated() got unknown responseCode: " + responseCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a purchase flow
|
||||||
|
*/
|
||||||
|
public void initiatePurchaseFlow(final Activity activity, final SkuDetails skuDetails) {
|
||||||
|
initiatePurchaseFlow(activity, skuDetails, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a purchase or subscription replace flow
|
||||||
|
*/
|
||||||
|
public void initiatePurchaseFlow(final Activity activity, final SkuDetails skuDetails, final String oldSku) {
|
||||||
|
Runnable purchaseFlowRequest = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
LOG.debug("Launching in-app purchase flow. Replace old SKU? " + (oldSku != null));
|
||||||
|
BillingFlowParams purchaseParams = BillingFlowParams.newBuilder().setSkuDetails(skuDetails).setOldSku(oldSku).build();
|
||||||
|
mBillingClient.launchBillingFlow(activity, purchaseParams);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
executeServiceRequest(purchaseFlowRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Context getContext() {
|
||||||
|
return mContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the resources
|
||||||
|
*/
|
||||||
|
public void destroy() {
|
||||||
|
LOG.debug("Destroying the manager.");
|
||||||
|
|
||||||
|
if (mBillingClient != null && mBillingClient.isReady()) {
|
||||||
|
mBillingClient.endConnection();
|
||||||
|
mBillingClient = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void querySkuDetailsAsync(@SkuType final String itemType, final List<String> skuList,
|
||||||
|
final SkuDetailsResponseListener listener) {
|
||||||
|
// Creating a runnable from the request to use it inside our connection retry policy below
|
||||||
|
Runnable queryRequest = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// Query the purchase async
|
||||||
|
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
|
||||||
|
params.setSkusList(skuList).setType(itemType);
|
||||||
|
mBillingClient.querySkuDetailsAsync(params.build(),
|
||||||
|
new SkuDetailsResponseListener() {
|
||||||
|
@Override
|
||||||
|
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
|
||||||
|
listener.onSkuDetailsResponse(billingResult, skuDetailsList);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
executeServiceRequest(queryRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void consumeAsync(final ConsumeParams consumeParams) {
|
||||||
|
// If we've already scheduled to consume this token - no action is needed (this could happen
|
||||||
|
// if you received the token when querying purchases inside onReceive() and later from
|
||||||
|
// onActivityResult()
|
||||||
|
final String purchaseToken = consumeParams.getPurchaseToken();
|
||||||
|
if (mTokensToBeConsumed == null) {
|
||||||
|
mTokensToBeConsumed = new HashSet<>();
|
||||||
|
} else if (mTokensToBeConsumed.contains(purchaseToken)) {
|
||||||
|
LOG.info("Token was already scheduled to be consumed - skipping...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mTokensToBeConsumed.add(purchaseToken);
|
||||||
|
|
||||||
|
// Generating Consume Response listener
|
||||||
|
final ConsumeResponseListener onConsumeListener = new ConsumeResponseListener() {
|
||||||
|
@Override
|
||||||
|
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
|
||||||
|
// If billing service was disconnected, we try to reconnect 1 time
|
||||||
|
// (feel free to introduce your retry policy here).
|
||||||
|
mBillingUpdatesListener.onConsumeFinished(purchaseToken, billingResult);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Creating a runnable from the request to use it inside our connection retry policy below
|
||||||
|
Runnable consumeRequest = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// Consume the purchase async
|
||||||
|
mBillingClient.consumeAsync(consumeParams, onConsumeListener);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
executeServiceRequest(consumeRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isIsServiceConnected() {
|
||||||
|
return mIsServiceConnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value Billing client response code or BILLING_MANAGER_NOT_INITIALIZED if the
|
||||||
|
* client connection response was not received yet.
|
||||||
|
*/
|
||||||
|
public int getBillingClientResponseCode() {
|
||||||
|
return mBillingClientResponseCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBillingClientResponseMessage() {
|
||||||
|
return mBillingClientResponseMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Purchase> getPurchases() {
|
||||||
|
return Collections.unmodifiableList(mPurchases);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getPayload() {
|
||||||
|
return mPayload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPayload(String payload) {
|
||||||
|
this.mPayload = payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the purchase
|
||||||
|
* <p>Note: Notice that for each purchase, we check if signature is valid on the client.
|
||||||
|
* It's recommended to move this check into your backend.
|
||||||
|
* See {@link Security#verifyPurchase(String, String, String)}
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param purchase Purchase to be handled
|
||||||
|
*/
|
||||||
|
private void handlePurchase(final Purchase purchase) {
|
||||||
|
if (!verifyValidSignature(purchase.getOriginalJson(), purchase.getSignature())) {
|
||||||
|
LOG.info("Got a purchase: " + purchase + ", but signature is bad. Skipping...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
|
||||||
|
// Acknowledge the purchase if it hasn't already been acknowledged.
|
||||||
|
if (!purchase.isAcknowledged()) {
|
||||||
|
AcknowledgePurchaseParams.Builder builder =
|
||||||
|
AcknowledgePurchaseParams.newBuilder()
|
||||||
|
.setPurchaseToken(purchase.getPurchaseToken());
|
||||||
|
if (!Algorithms.isEmpty(mPayload)) {
|
||||||
|
builder.setDeveloperPayload(mPayload);
|
||||||
|
}
|
||||||
|
AcknowledgePurchaseParams acknowledgePurchaseParams = builder.build();
|
||||||
|
mBillingClient.acknowledgePurchase(acknowledgePurchaseParams, new AcknowledgePurchaseResponseListener() {
|
||||||
|
@Override
|
||||||
|
public void onAcknowledgePurchaseResponse(BillingResult billingResult) {
|
||||||
|
if (billingResult.getResponseCode() != BillingResponseCode.OK) {
|
||||||
|
LOG.info("Acknowledge a purchase: " + purchase + " failed (" + billingResult.getResponseCode() + "). " + billingResult.getDebugMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (purchase.getPurchaseState() == Purchase.PurchaseState.PENDING) {
|
||||||
|
LOG.info("Got a purchase: " + purchase + ", but purchase state is pending. Skipping...");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
LOG.info("Got a purchase: " + purchase + ", but purchase state is " + purchase.getPurchaseState() + ". Skipping...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("Got a verified purchase: " + purchase);
|
||||||
|
|
||||||
|
mPurchases.add(purchase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a result from querying of purchases and report an updated list to the listener
|
||||||
|
*/
|
||||||
|
private void onQueryPurchasesFinished(PurchasesResult result) {
|
||||||
|
// Have we been disposed of in the meantime? If so, or bad result code, then quit
|
||||||
|
if (mBillingClient == null || result.getResponseCode() != BillingResponseCode.OK) {
|
||||||
|
LOG.warn("Billing client was null or result code (" + result.getResponseCode()
|
||||||
|
+ ") was bad - quitting");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("Query inventory was successful.");
|
||||||
|
|
||||||
|
// Update the UI and purchases inventory with new list of purchases
|
||||||
|
mPurchases.clear();
|
||||||
|
onPurchasesUpdated(result.getBillingResult(), result.getPurchasesList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if subscriptions are supported for current client
|
||||||
|
* <p>Note: This method does not automatically retry for RESULT_SERVICE_DISCONNECTED.
|
||||||
|
* It is only used in unit tests and after queryPurchases execution, which already has
|
||||||
|
* a retry-mechanism implemented.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public boolean areSubscriptionsSupported() {
|
||||||
|
int responseCode = mBillingClient.isFeatureSupported(FeatureType.SUBSCRIPTIONS).getResponseCode();
|
||||||
|
if (responseCode != BillingResponseCode.OK) {
|
||||||
|
LOG.warn("areSubscriptionsSupported() got an error response: " + responseCode);
|
||||||
|
}
|
||||||
|
return responseCode == BillingResponseCode.OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query purchases across various use cases and deliver the result in a formalized way through
|
||||||
|
* a listener
|
||||||
|
*/
|
||||||
|
public void queryPurchases() {
|
||||||
|
Runnable queryToExecute = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
long time = System.currentTimeMillis();
|
||||||
|
PurchasesResult purchasesResult = mBillingClient.queryPurchases(SkuType.INAPP);
|
||||||
|
LOG.info("Querying purchases elapsed time: " + (System.currentTimeMillis() - time)
|
||||||
|
+ "ms");
|
||||||
|
// If there are subscriptions supported, we add subscription rows as well
|
||||||
|
if (areSubscriptionsSupported()) {
|
||||||
|
PurchasesResult subscriptionResult = mBillingClient.queryPurchases(SkuType.SUBS);
|
||||||
|
LOG.info("Querying purchases and subscriptions elapsed time: "
|
||||||
|
+ (System.currentTimeMillis() - time) + "ms");
|
||||||
|
LOG.info("Querying subscriptions result code: "
|
||||||
|
+ subscriptionResult.getResponseCode()
|
||||||
|
+ " res: " + subscriptionResult.getPurchasesList().size());
|
||||||
|
|
||||||
|
if (subscriptionResult.getResponseCode() == BillingResponseCode.OK) {
|
||||||
|
purchasesResult.getPurchasesList().addAll(
|
||||||
|
subscriptionResult.getPurchasesList());
|
||||||
|
} else {
|
||||||
|
LOG.error("Got an error response trying to query subscription purchases");
|
||||||
|
}
|
||||||
|
} else if (purchasesResult.getResponseCode() == BillingResponseCode.OK) {
|
||||||
|
LOG.info("Skipped subscription purchases query since they are not supported");
|
||||||
|
} else {
|
||||||
|
LOG.warn("queryPurchases() got an error response code: "
|
||||||
|
+ purchasesResult.getResponseCode());
|
||||||
|
}
|
||||||
|
onQueryPurchasesFinished(purchasesResult);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
executeServiceRequest(queryToExecute);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startServiceConnection(final Runnable executeOnSuccess) {
|
||||||
|
mBillingClient.startConnection(new BillingClientStateListener() {
|
||||||
|
@Override
|
||||||
|
public void onBillingSetupFinished(BillingResult billingResult) {
|
||||||
|
|
||||||
|
int billingResponseCode = billingResult.getResponseCode();
|
||||||
|
LOG.debug("Setup finished. Response code: " + billingResponseCode);
|
||||||
|
|
||||||
|
mIsServiceConnected = billingResponseCode == BillingResponseCode.OK;
|
||||||
|
mBillingClientResponseCode = billingResponseCode;
|
||||||
|
mBillingClientResponseMessage = billingResult.getDebugMessage();
|
||||||
|
mBillingUpdatesListener.onBillingClientSetupFinished();
|
||||||
|
|
||||||
|
if (mIsServiceConnected) {
|
||||||
|
if (executeOnSuccess != null) {
|
||||||
|
executeOnSuccess.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBillingServiceDisconnected() {
|
||||||
|
mIsServiceConnected = false;
|
||||||
|
mBillingUpdatesListener.onBillingClientSetupFinished();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeServiceRequest(Runnable runnable) {
|
||||||
|
if (mIsServiceConnected) {
|
||||||
|
runnable.run();
|
||||||
|
} else {
|
||||||
|
// If billing service was disconnected, we try to reconnect 1 time.
|
||||||
|
startServiceConnection(runnable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean verifyValidSignature(String signedData, String signature) {
|
||||||
|
return Security.verifyPurchase(mSignatureBase64, signedData, signature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
/* Copyright (c) 2012 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.osmand.plus.inapp.util;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exception thrown when something went wrong with in-app billing.
|
|
||||||
* An IabException has an associated IabResult (an error).
|
|
||||||
* To get the IAB result that caused this exception to be thrown,
|
|
||||||
* call {@link #getResult()}.
|
|
||||||
*/
|
|
||||||
public class IabException extends Exception {
|
|
||||||
IabResult mResult;
|
|
||||||
|
|
||||||
public IabException(IabResult r) {
|
|
||||||
this(r, null);
|
|
||||||
}
|
|
||||||
public IabException(int response, String message) {
|
|
||||||
this(new IabResult(response, message));
|
|
||||||
}
|
|
||||||
public IabException(IabResult r, Exception cause) {
|
|
||||||
super(r.getMessage(), cause);
|
|
||||||
mResult = r;
|
|
||||||
}
|
|
||||||
public IabException(int response, String message, Exception cause) {
|
|
||||||
this(new IabResult(response, message), cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns the IAB result (error) that this exception signals. */
|
|
||||||
public IabResult getResult() { return mResult; }
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,45 +0,0 @@
|
||||||
/* Copyright (c) 2012 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.osmand.plus.inapp.util;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the result of an in-app billing operation.
|
|
||||||
* A result is composed of a response code (an integer) and possibly a
|
|
||||||
* message (String). You can get those by calling
|
|
||||||
* {@link #getResponse} and {@link #getMessage()}, respectively. You
|
|
||||||
* can also inquire whether a result is a success or a failure by
|
|
||||||
* calling {@link #isSuccess()} and {@link #isFailure()}.
|
|
||||||
*/
|
|
||||||
public class IabResult {
|
|
||||||
int mResponse;
|
|
||||||
String mMessage;
|
|
||||||
|
|
||||||
public IabResult(int response, String message) {
|
|
||||||
mResponse = response;
|
|
||||||
if (message == null || message.trim().length() == 0) {
|
|
||||||
mMessage = IabHelper.getResponseDesc(response);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
mMessage = message + " (response: " + IabHelper.getResponseDesc(response) + ")";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public int getResponse() { return mResponse; }
|
|
||||||
public String getMessage() { return mMessage; }
|
|
||||||
public boolean isSuccess() { return mResponse == IabHelper.BILLING_RESPONSE_RESULT_OK; }
|
|
||||||
public boolean isFailure() { return !isSuccess(); }
|
|
||||||
public String toString() { return "IabResult: " + getMessage(); }
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
/* Copyright (c) 2012 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.osmand.plus.inapp.util;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a block of information about in-app items.
|
|
||||||
* An Inventory is returned by such methods as {@link IabHelper#queryInventory}.
|
|
||||||
*/
|
|
||||||
public class Inventory {
|
|
||||||
Map<String,SkuDetails> mSkuMap = new HashMap<String,SkuDetails>();
|
|
||||||
Map<String,Purchase> mPurchaseMap = new HashMap<String,Purchase>();
|
|
||||||
|
|
||||||
Inventory() { }
|
|
||||||
|
|
||||||
/** Returns the listing details for an in-app product. */
|
|
||||||
public SkuDetails getSkuDetails(String sku) {
|
|
||||||
return mSkuMap.get(sku);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns purchase information for a given product, or null if there is no purchase. */
|
|
||||||
public Purchase getPurchase(String sku) {
|
|
||||||
return mPurchaseMap.get(sku);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns whether or not there exists a purchase of the given product. */
|
|
||||||
public boolean hasPurchase(String sku) {
|
|
||||||
return mPurchaseMap.containsKey(sku);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Return whether or not details about the given product are available. */
|
|
||||||
public boolean hasDetails(String sku) {
|
|
||||||
return mSkuMap.containsKey(sku);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erase a purchase (locally) from the inventory, given its product ID. This just
|
|
||||||
* modifies the Inventory object locally and has no effect on the server! This is
|
|
||||||
* useful when you have an existing Inventory object which you know to be up to date,
|
|
||||||
* and you have just consumed an item successfully, which means that erasing its
|
|
||||||
* purchase data from the Inventory you already have is quicker than querying for
|
|
||||||
* a new Inventory.
|
|
||||||
*/
|
|
||||||
public void erasePurchase(String sku) {
|
|
||||||
if (mPurchaseMap.containsKey(sku)) mPurchaseMap.remove(sku);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns a list of all owned product IDs. */
|
|
||||||
public List<String> getAllOwnedSkus() {
|
|
||||||
return new ArrayList<String>(mPurchaseMap.keySet());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns a list of all owned product IDs of a given type */
|
|
||||||
public List<String> getAllOwnedSkus(String itemType) {
|
|
||||||
List<String> result = new ArrayList<String>();
|
|
||||||
for (Purchase p : mPurchaseMap.values()) {
|
|
||||||
if (p.getItemType().equals(itemType)) result.add(p.getSku());
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns a list of all purchases. */
|
|
||||||
List<Purchase> getAllPurchases() {
|
|
||||||
return new ArrayList<Purchase>(mPurchaseMap.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
void addSkuDetails(SkuDetails d) {
|
|
||||||
mSkuMap.put(d.getSku(), d);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addPurchase(Purchase p) {
|
|
||||||
mPurchaseMap.put(p.getSku(), p);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
/* Copyright (c) 2012 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.osmand.plus.inapp.util;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an in-app billing purchase.
|
|
||||||
*/
|
|
||||||
public class Purchase {
|
|
||||||
String mItemType; // ITEM_TYPE_INAPP or ITEM_TYPE_SUBS
|
|
||||||
String mOrderId;
|
|
||||||
String mPackageName;
|
|
||||||
String mSku;
|
|
||||||
long mPurchaseTime;
|
|
||||||
int mPurchaseState;
|
|
||||||
String mDeveloperPayload;
|
|
||||||
String mToken;
|
|
||||||
String mOriginalJson;
|
|
||||||
String mSignature;
|
|
||||||
|
|
||||||
public Purchase(String itemType, String jsonPurchaseInfo, String signature) throws JSONException {
|
|
||||||
mItemType = itemType;
|
|
||||||
mOriginalJson = jsonPurchaseInfo;
|
|
||||||
JSONObject o = new JSONObject(mOriginalJson);
|
|
||||||
mOrderId = o.optString("orderId");
|
|
||||||
mPackageName = o.optString("packageName");
|
|
||||||
mSku = o.optString("productId");
|
|
||||||
mPurchaseTime = o.optLong("purchaseTime");
|
|
||||||
mPurchaseState = o.optInt("purchaseState");
|
|
||||||
mDeveloperPayload = o.optString("developerPayload");
|
|
||||||
mToken = o.optString("token", o.optString("purchaseToken"));
|
|
||||||
mSignature = signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getItemType() { return mItemType; }
|
|
||||||
public String getOrderId() { return mOrderId; }
|
|
||||||
public String getPackageName() { return mPackageName; }
|
|
||||||
public String getSku() { return mSku; }
|
|
||||||
public long getPurchaseTime() { return mPurchaseTime; }
|
|
||||||
public int getPurchaseState() { return mPurchaseState; }
|
|
||||||
public String getDeveloperPayload() { return mDeveloperPayload; }
|
|
||||||
public String getToken() { return mToken; }
|
|
||||||
public String getOriginalJson() { return mOriginalJson; }
|
|
||||||
public String getSignature() { return mSignature; }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() { return "PurchaseInfo(type:" + mItemType + "):" + mOriginalJson; }
|
|
||||||
}
|
|
|
@ -18,10 +18,6 @@ package net.osmand.plus.inapp.util;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
|
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.KeyFactory;
|
import java.security.KeyFactory;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
|
@ -1,73 +0,0 @@
|
||||||
/* Copyright (c) 2012 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.osmand.plus.inapp.util;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an in-app product's listing details.
|
|
||||||
*/
|
|
||||||
public class SkuDetails {
|
|
||||||
String mItemType;
|
|
||||||
String mSku;
|
|
||||||
String mType;
|
|
||||||
String mPrice;
|
|
||||||
long mPriceAmountMicros;
|
|
||||||
String mPriceCurrencyCode;
|
|
||||||
String mSubscriptionPeriod;
|
|
||||||
String mTitle;
|
|
||||||
String mDescription;
|
|
||||||
String mJson;
|
|
||||||
|
|
||||||
public SkuDetails(String jsonSkuDetails) throws JSONException {
|
|
||||||
this(IabHelper.ITEM_TYPE_INAPP, jsonSkuDetails);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SkuDetails(String itemType, String jsonSkuDetails) throws JSONException {
|
|
||||||
mItemType = itemType;
|
|
||||||
mJson = jsonSkuDetails;
|
|
||||||
JSONObject o = new JSONObject(mJson);
|
|
||||||
mSku = o.optString("productId");
|
|
||||||
mType = o.optString("type");
|
|
||||||
mPrice = o.optString("price");
|
|
||||||
mPriceAmountMicros = o.optLong("price_amount_micros");
|
|
||||||
mPriceCurrencyCode = o.optString("price_currency_code");
|
|
||||||
mSubscriptionPeriod = o.optString("subscriptionPeriod");
|
|
||||||
mTitle = o.optString("title");
|
|
||||||
mDescription = o.optString("description");
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSku() { return mSku; }
|
|
||||||
public String getType() { return mType; }
|
|
||||||
public String getPrice() { return mPrice; }
|
|
||||||
public long getPriceAmountMicros() {
|
|
||||||
return mPriceAmountMicros;
|
|
||||||
}
|
|
||||||
public String getPriceCurrencyCode() {
|
|
||||||
return mPriceCurrencyCode;
|
|
||||||
}
|
|
||||||
public String getSubscriptionPeriod() {
|
|
||||||
return mSubscriptionPeriod;
|
|
||||||
}
|
|
||||||
public String getTitle() { return mTitle; }
|
|
||||||
public String getDescription() { return mDescription; }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "SkuDetails:" + mJson;
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue