com.badlogic.gdx.pay.android.googleplay.AndroidGooglePlayPurchaseManager Maven / Gradle / Ivy
/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* 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 com.badlogic.gdx.pay.android.googleplay;
import android.app.Activity;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.util.Log;
import com.badlogic.gdx.backends.android.AndroidApplication;
import com.badlogic.gdx.backends.android.AndroidFragmentApplication;
import com.badlogic.gdx.pay.GdxPayException;
import com.badlogic.gdx.pay.Information;
import com.badlogic.gdx.pay.Offer;
import com.badlogic.gdx.pay.OfferType;
import com.badlogic.gdx.pay.PurchaseManager;
import com.badlogic.gdx.pay.PurchaseManagerConfig;
import com.badlogic.gdx.pay.PurchaseManagerTestSupport;
import com.badlogic.gdx.pay.PurchaseObserver;
import com.badlogic.gdx.pay.PurchaseSystem;
import com.badlogic.gdx.pay.Transaction;
import com.badlogic.gdx.pay.android.googleplay.billing.ApplicationProxy;
import com.badlogic.gdx.pay.android.googleplay.billing.AsyncExecutor;
import com.badlogic.gdx.pay.android.googleplay.billing.GoogleInAppBillingService;
import com.badlogic.gdx.pay.android.googleplay.billing.GoogleInAppBillingService.ConnectionListener;
import com.badlogic.gdx.pay.android.googleplay.billing.GoogleInAppBillingService.PurchaseRequestCallback;
import com.badlogic.gdx.pay.android.googleplay.billing.NewThreadSleepAsyncExecutor;
import com.badlogic.gdx.pay.android.googleplay.billing.V3GoogleInAppBillingService;
import com.badlogic.gdx.pay.android.googleplay.billing.converter.OfferToInAppPurchaseConverter;
import com.badlogic.gdx.pay.android.googleplay.billing.converter.PurchaseResponseActivityResultConverter;
import com.badlogic.gdx.utils.Array;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static com.badlogic.gdx.pay.android.googleplay.billing.V3GoogleInAppBillingService.PURCHASE_TYPE_IN_APP;
import static com.badlogic.gdx.pay.android.googleplay.billing.V3GoogleInAppBillingService.PURCHASE_TYPE_SUBSCRIPTION;
/**
* The purchase manager implementation for Google Play (Android).
*
* @author noblemaster
* @deprecated Use gdx-pay-android-googlebilling
*/
public class AndroidGooglePlayPurchaseManager implements PurchaseManager, PurchaseManagerTestSupport {
public static final String LOG_TAG = "GdxPay/AndroidPlay";
public static final String GOOGLE_MARKET_NAME = "com.google.market";
public static final String GOOGLE_PLAY_STORE_NAME = "com.android.vending";
private final GoogleInAppBillingService googleInAppBillingService;
private final Map informationMap = new ConcurrentHashMap<>();
private PurchaseObserver observer;
private PurchaseManagerConfig purchaseManagerConfig;
public AndroidGooglePlayPurchaseManager(GoogleInAppBillingService googleInAppBillingService) {
this.googleInAppBillingService = googleInAppBillingService;
}
@SuppressWarnings("unused") // Unit tested with reflection. (as in IAP.java)
public AndroidGooglePlayPurchaseManager(Activity activity, int activityRequestCode) {
if (!(activity instanceof AndroidApplication)) {
throw new IllegalArgumentException("Bootstrapping gdx-pay only supported with AndroidApplication activity.");
}
AndroidApplication application = (AndroidApplication) activity;
PurchaseResponseActivityResultConverter converter = new PurchaseResponseActivityResultConverter(this);
AsyncExecutor executor = new NewThreadSleepAsyncExecutor();
googleInAppBillingService = new V3GoogleInAppBillingService(application, activityRequestCode, converter, executor);
}
@SuppressWarnings("unused") // Unit tested with reflection. (as in IAP.java)
public AndroidGooglePlayPurchaseManager(Activity activity,
AndroidFragmentApplication application,
int activityRequestCode) {
PurchaseResponseActivityResultConverter converter = new PurchaseResponseActivityResultConverter(this);
AsyncExecutor executor = new NewThreadSleepAsyncExecutor();
ApplicationProxy.FragmentProxy proxy = new ApplicationProxy.FragmentProxy(activity, application);
googleInAppBillingService = new V3GoogleInAppBillingService(proxy, activityRequestCode, converter, executor);
PurchaseSystem.setManager(this);
}
@Override
public void install(final PurchaseObserver observer, final PurchaseManagerConfig purchaseManagerConfig, final boolean autoFetchInformation) {
this.observer = observer;
this.purchaseManagerConfig = purchaseManagerConfig;
if (googleInAppBillingService.isListeningForConnections()) {
// Supports calling me multiple times.
// TODO: scenario not unit tested, test this!
googleInAppBillingService.disconnect();
}
googleInAppBillingService.requestConnect(new ConnectionListener() {
@Override
public void connected() {
onServiceConnected(observer, autoFetchInformation);
}
@Override
public void disconnected(GdxPayException exception) {
observer.handleInstallError(new GdxPayException("Failed to bind to service", exception));
}
});
}
/**
* Detect if running on Phone which has Google Play installed.
*
* Used when purchase system is installed via IAP class.
* If Google changes the package identifier of Google Play, this method will not return the
* new name.
*/
public static boolean isRunningViaGooglePlay(Activity activity) {
PackageManager packageManager = activity.getPackageManager();
List packages = packageManager
.getInstalledPackages(0);
for (PackageInfo packageInfo : packages) {
String packageName = packageInfo.packageName;
if (packageName.equals(GOOGLE_MARKET_NAME) || packageName.equals(GOOGLE_PLAY_STORE_NAME)) {
return true;
}
}
return false;
}
protected void runAsync(Runnable runnable) {
new Thread(runnable).start();
}
private void loadSkusAndFillPurchaseInformation() {
List inAppPurchasesIds = new ArrayList<>();
List subsPurchasesIds = new ArrayList<>();
getPurchaseIdsByType(inAppPurchasesIds, subsPurchasesIds);
informationMap.clear();
if (!inAppPurchasesIds.isEmpty()) {
Map inappSkuDetails = googleInAppBillingService.getProductsDetails(inAppPurchasesIds, PURCHASE_TYPE_IN_APP);
informationMap.putAll(inappSkuDetails);
}
if (!subsPurchasesIds.isEmpty()) {
Map subsSkuDetails = googleInAppBillingService.getProductsDetails(subsPurchasesIds, PURCHASE_TYPE_SUBSCRIPTION);
informationMap.putAll(subsSkuDetails);
}
}
private void getPurchaseIdsByType(List inAppPurchasesIds, List subsPurchasesIds) {
for (int i = 0; i < purchaseManagerConfig.getOfferCount(); i++) {
Offer offer = purchaseManagerConfig.getOffer(i);
if (offer.getType().equals(OfferType.SUBSCRIPTION))
subsPurchasesIds.add(offer.getIdentifier());
else
inAppPurchasesIds.add(offer.getIdentifier());
}
}
@Override
public boolean installed() {
return googleInAppBillingService.isListeningForConnections();
}
@Override
public void dispose() {
googleInAppBillingService.dispose();
clearCaches();
observer = null;
}
@Override
public void purchase(final String identifier) {
assertInstalled();
final OfferType offerType = getOfferType(identifier);
googleInAppBillingService.startPurchaseRequest(identifier, OfferToInAppPurchaseConverter.convertOfferType(offerType), new PurchaseRequestCallback() {
@Override
public void purchaseSuccess(Transaction transaction) {
if (observer != null) {
switch (offerType) {
case CONSUMABLE:
// Warning: observer.handlePurchase is called in googleInAppBillingService.consumePurchase.
// That is not clean, I would prefer to keep it on one place.
// Should be refactored later.
googleInAppBillingService.consumePurchase(transaction, observer);
break;
case ENTITLEMENT:
case SUBSCRIPTION:
observer.handlePurchase(transaction);
break;
default:
String error = "Unsupported OfferType=" + getOfferType(identifier)
+ " for identifier=" + identifier;
throw new GdxPayException(error);
}
}
}
@Override
public void purchaseError(GdxPayException exception) {
if (observer != null) {
observer.handlePurchaseError(exception);
}
}
@Override
public void purchaseCanceled() {
if (observer != null) {
observer.handlePurchaseCanceled();
}
}
});
}
private OfferType getOfferType(String identifier) {
Offer offer = purchaseManagerConfig.getOffer(identifier);
if (offer == null || offer.getType() == null) {
throw new IllegalStateException("No offer or offerType configured for identifier: " + identifier + ", offer: " + offer);
}
return offer.getType();
}
// TODO: call in new thread if called from UI thread (check if this is necessary).
@Override
public void purchaseRestore() {
try {
List transactions = googleInAppBillingService.getPurchases();
Array entitlements = new Array<>(Transaction.class);
for (int i = 0; i < transactions.size(); i++) {
Transaction transaction = transactions.get(i);
if (OfferType.CONSUMABLE == getOfferType(transaction.getIdentifier())) {
googleInAppBillingService.consumePurchase(transaction, observer);
} else {
entitlements.add(transaction);
}
}
if (observer != null) {
observer.handleRestore(entitlements.toArray());
}
} catch (GdxPayException e) {
if (observer != null) {
observer.handleRestoreError(e);
}
}
}
@Override
public Information getInformation(String identifier) {
Information information = informationMap.get(identifier);
if (information == null) {
return Information.UNAVAILABLE;
}
return information;
}
@Override
public String storeName() {
return PurchaseManagerConfig.STORE_NAME_ANDROID_GOOGLE;
}
private void clearCaches() {
informationMap.clear();
}
private void onServiceConnected(final PurchaseObserver observer, final boolean autofetchInformation) {
runAsync(new Runnable() {
@Override
public void run() {
try {
if (autofetchInformation) {
loadSkusAndFillPurchaseInformation();
}
} catch (Exception e) {
// TODO: this situation not yet unit-tested.
Log.e(LOG_TAG, "Failed to load skus in onServiceConnected()", e);
}
observer.handleInstall();
}
});
}
private void assertInstalled() {
if (!installed()) {
throw new GdxPayException("Payment system must be installed to perform this action.");
}
}
@Override
public void cancelTestPurchases() {
googleInAppBillingService.cancelTestPurchases();
}
}