package de.jeisfeld.augendiagnoselib.util; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import android.app.Activity; import android.content.Intent; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; import com.android.vending.billing.IabHelper; import com.android.vending.billing.IabResult; import com.android.vending.billing.Inventory; import com.android.vending.billing.Purchase; import com.android.vending.billing.PurchasedSku; import com.android.vending.billing.SkuDetails; import de.jeisfeld.augendiagnoselib.Application; import de.jeisfeld.augendiagnoselib.R; /** * Utility class to support in-ad purchases via Google Billing. */ public final class GoogleBillingHelper { /** * The request code used for starting the payment activity - any number. */ private static final int REQUEST_CODE = 14254; /** * The logging tag for this class. */ private static final String TAG = "GoogleBillingHelper"; /** * The product ids to be offered. */ private static final String[] PRODUCT_IDS = Application.getAppContext().getResources().getStringArray(R.array.googlebilling_ids); /** * The product ids which set premium status. */ private static final String[] PREMIUM_IDS = Application.getAppContext().getResources().getStringArray(R.array.googlebilling_premium_ids); /** * The primary product id used for one-click purchase. */ public static final String PRIMARY_ID = Application.getResourceString(R.string.googlebilling_primary_id); /** * The product ids which are subscriptions. */ private static final String[] SUBSCRIPTION_IDS = Application.getAppContext().getResources().getStringArray(R.array.googlebilling_subscription_ids); /** * An instance of GoogleBillingHelper. */ @Nullable private static GoogleBillingHelper mInstance; /** * Helper class for Google Billing. */ private IabHelper mIabHelper; /** * The activity starting Google Billing. */ private Activity mActivity; /** * An onInventoryFinishedListener called after inventory has been retrieved. */ private OnInventoryFinishedListener mOnInventoryFinishedListener; /** * An onPurchaseSuccessListener called after a purchase has been completed. */ private OnPurchaseSuccessListener mOnPurchaseSuccessListener; /** * Flag indicating if there is a purchase setting premium status. */ private boolean mIsPremium = false; /** * Hide default constructor. */ private GoogleBillingHelper() { // hide default constructor. } /** * Initialize an instance of GoogleBillingHelper. * * @param activity The activity triggering Google Billing. * @param listener a listener called after the inventory has been retrieved. */ public static void initialize(final Activity activity, final OnInventoryFinishedListener listener) { synchronized (GoogleBillingHelper.class) { if (mInstance != null) { if (mInstance.mActivity == activity) { return; } else { dispose(); } } mInstance = new GoogleBillingHelper(); mInstance.mActivity = activity; mInstance.mOnInventoryFinishedListener = listener; } mInstance.initialize(); } /** * Initialize the GoogleBillingHelper. */ private void initialize() { String base64EncodedPublicKey = Application.getResourceString(R.string.private_license_key); // compute your public key and store it in base64EncodedPublicKey mIabHelper = new IabHelper(mActivity, base64EncodedPublicKey); mIabHelper.enableDebugLogging(false, TAG); mIabHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() { @Override public void onIabSetupFinished(@NonNull final IabResult result) { if (result.isSuccess()) { Log.d(TAG, "Finished IAB setup"); mIabHelper.queryInventoryAsync(true, Arrays.asList(PRODUCT_IDS), mGotInventoryListener); } else { Log.e(TAG, "Problem setting up In-app Billing: " + result); } } }); } /** * Launch the purchase flow for a product. * * @param productId The productId. * @param listener a listener called after the purchase has been completed. */ public static void launchPurchaseFlow(final String productId, final OnPurchaseSuccessListener listener) { if (mInstance == null || mInstance.mIabHelper == null) { throw new NullPointerException( "Tried to launch purchase flow without having GoogleBillingHelper initialized"); } mInstance.mOnPurchaseSuccessListener = listener; if (Arrays.asList(SUBSCRIPTION_IDS).contains(productId)) { Log.d(TAG, "Starting subscription purchase flow for " + productId); mInstance.mIabHelper.launchSubscriptionPurchaseFlow(mInstance.mActivity, productId, REQUEST_CODE, mInstance.mPurchaseFinishedListener); } else { Log.d(TAG, "Starting product purchase flow for " + productId); mInstance.mIabHelper.launchPurchaseFlow(mInstance.mActivity, productId, REQUEST_CODE, mInstance.mPurchaseFinishedListener); } } /** * To be called in the onActivityResult method of the activity launching the purchase flow. * * @param requestCode The integer request code originally supplied to startActivityForResult(), allowing you to identify who * this result came from. * @param resultCode The integer result code returned by the child activity through its setResult(). * @param data An Intent, which can return result data to the caller (various data can be attached to Intent * "extras"). */ public static void handleActivityResult(final int requestCode, final int resultCode, final Intent data) { if (requestCode == REQUEST_CODE) { if (mInstance != null && mInstance.mIabHelper != null) { mInstance.mIabHelper.handleActivityResult(requestCode, resultCode, data); } } } /** * Clean up GoogleBillingHelper instance. */ public static void dispose() { Log.d(TAG, "Disposing GoogleBillingHelper"); synchronized (GoogleBillingHelper.class) { if (mInstance != null) { if (mInstance.mIabHelper != null) { mInstance.mIabHelper.dispose(); } mInstance = null; } } } /** * The onInventoryFinishedListener started after the inventory is loaded. */ @Nullable private final IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() { @Override public void onQueryInventoryFinished(@NonNull final IabResult result, @NonNull final Inventory inventory) { Log.d(TAG, "Query inventory finished - " + inventory); // Have we been disposed of in the meantime? If so, quit. if (mIabHelper == null) { return; } // Is it a failure? if (result.isFailure()) { Log.e(TAG, "Failed to query inventory: " + result); return; } Log.d(TAG, "Query inventory was successful."); List<PurchasedSku> purchases = new ArrayList<>(); List<SkuDetails> nonPurchases = new ArrayList<>(); for (String purchaseId : Arrays.asList(PRODUCT_IDS)) { Purchase purchase = inventory.getPurchase(purchaseId); SkuDetails skuDetails = inventory.getSkuDetails(purchaseId); if (purchase == null && skuDetails == null) { Log.w(TAG, "Did not find entry for " + purchaseId); } else { synchronized (GoogleBillingHelper.class) { if (purchase == null) { nonPurchases.add(skuDetails); } else { Log.d(TAG, "Found purchase: " + purchase); purchases.add(new PurchasedSku(skuDetails, purchase)); if (Arrays.asList(PREMIUM_IDS).contains(purchase.getSku())) { mIsPremium = true; } } } } } if (mOnInventoryFinishedListener != null) { mOnInventoryFinishedListener.handleProducts(purchases, nonPurchases, mIsPremium); } } }; /** * Callback for when a purchase is finished. */ @Nullable private final IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() { @Override public void onIabPurchaseFinished(@NonNull final IabResult result, @NonNull final Purchase purchase) { Log.d(TAG, "Purchase finished: " + result + ", purchase: " + purchase); if (result.isFailure()) { Log.e(TAG, "Error purchasing: " + result); if (mOnPurchaseSuccessListener != null) { mOnPurchaseSuccessListener.handleFailure(); } return; } Log.d(TAG, "Purchase successful."); if (mOnPurchaseSuccessListener != null) { boolean isPremiumProduct = Arrays.asList(PREMIUM_IDS).contains(purchase.getSku()); mOnPurchaseSuccessListener.handlePurchase(purchase, isPremiumProduct && !mIsPremium); mIsPremium = mIsPremium || isPremiumProduct; } } }; /** * Listener to be called after inventory has been retrieved. */ public interface OnInventoryFinishedListener { /** * Handler called after inventory has been retrieved. * * @param purchases The list of bought purchases. * @param availableProducts The list of available products. * @param isPremium Flag indicating if there is a purchase setting premium status. */ void handleProducts(List<PurchasedSku> purchases, List<SkuDetails> availableProducts, boolean isPremium); } /** * Listener to be called after a purchase has been successfully completed. */ public interface OnPurchaseSuccessListener { /** * Handler called after a purchase has been successfully completed. * * @param purchase The completed purchase. * @param addedPremiumProduct Flag indicating if there was a premium upgrade. */ void handlePurchase(Purchase purchase, boolean addedPremiumProduct); /** * Handler called after the failure of a purchase. */ void handleFailure(); } }