diff --git a/android/app/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl b/android/app/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl index 2a492f7..6d36e89 100644 --- a/android/app/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl +++ b/android/app/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl @@ -141,4 +141,45 @@ interface IInAppBillingService { * @return 0 if consumption succeeded. Appropriate error values for failures. */ int consumePurchase(int apiVersion, String packageName, String purchaseToken); -} + + /** + * Returns an intent to launch the purchase flow for an in-app item by providing a SKU, + * the type, a unique purchase token and an optional developer payload. + * @param apiVersion billing API version that the app is using + * @param packageName package name of the calling app + * @param sku the SKU of the in-app item as published in the developer console + * @param type the type of the in-app item ("inapp" for one-time purchases + * and "subs" for subscription). + * @param developerPayload optional argument to be sent back with the purchase information + * @return Bundle containing the following key-value pairs + * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on + * failure as listed above. + * "BUY_INTENT" - Intent to start the purchase flow + * + * The intent should be launched with startActivityForResult. When purchase flow + * has completed, the onActivityResult() will give a resultCode of OK or CANCELED. + * If the purchase is successful, the result data will contain the following key-value pairs + * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on + * failure as listed above. + * "INAPP_PURCHASE_DATA" - String in JSON format similar to + * '{"orderId":"12999763169054705758.1371079406387615", + * "packageName":"com.example.app", + * "productId":"exampleSku", + * "purchaseTime":1345678900000, + * "purchaseToken" : "122333444455555", + * "developerPayload":"example developer payload" }' + * "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that + * was signed with the private key of the developer + * TODO: change this to app-specific keys. + */ + Bundle getBuyIntentV2(int apiVersion, String packageName, String sku, String type, + String developerPayload); + + /** + * Returns the config of purchase. + * + * @return Bundle containing the following key-value pair + * "INTENT_V2_SUPPORT" with boolean value + */ + Bundle getPurchaseConfig(int apiVersion); +} \ No newline at end of file diff --git a/android/app/src/main/java/com/contoriel/cafebazaar/util/IabHelper.java b/android/app/src/main/java/com/contoriel/cafebazaar/util/IabHelper.java index aab96b5..9dca154 100644 --- a/android/app/src/main/java/com/contoriel/cafebazaar/util/IabHelper.java +++ b/android/app/src/main/java/com/contoriel/cafebazaar/util/IabHelper.java @@ -20,8 +20,10 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.IntentSender.SendIntentException; +import android.content.IntentSender; import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -141,6 +143,9 @@ public class IabHelper { public static final String RESPONSE_INAPP_SIGNATURE_LIST = "INAPP_DATA_SIGNATURE_LIST"; public static final String INAPP_CONTINUATION_TOKEN = "INAPP_CONTINUATION_TOKEN"; + // Keys for the response from getPurchaseConfig + private static final String INTENT_V2_SUPPORT = "INTENT_V2_SUPPORT"; + // Item types public static final String ITEM_TYPE_INAPP = "inapp"; public static final String ITEM_TYPE_SUBS = "subs"; @@ -264,7 +269,10 @@ public void onServiceConnected(ComponentName name, IBinder service) { Intent serviceIntent = new Intent("ir.cafebazaar.pardakht.InAppBillingService.BIND"); serviceIntent.setPackage("com.farsitel.bazaar"); - if (!mContext.getPackageManager().queryIntentServices(serviceIntent, 0).isEmpty()) { + + PackageManager pm=mContext.getPackageManager(); + List intentServices = pm.queryIntentServices(serviceIntent, 0); + if (intentServices != null && !intentServices.isEmpty()) { // service available to handle that Intent mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE); } @@ -289,7 +297,9 @@ public void dispose() { mSetupDone = false; if (mServiceConn != null) { logDebug("Unbinding from service."); - if (mContext != null && mService != null) mContext.unbindService(mServiceConn); + if (mContext != null && mService != null) { + mContext.unbindService(mServiceConn); + } } mDisposed = true; mContext = null; @@ -383,35 +393,26 @@ public void launchPurchaseFlow(Activity act, String sku, String itemType, int re try { logDebug("Constructing buy intent for " + sku + ", item type: " + itemType); - Bundle buyIntentBundle = mService.getBuyIntent(3, mContext.getPackageName(), sku, itemType, extraData); - int response = getResponseCodeFromBundle(buyIntentBundle); - if (response != BILLING_RESPONSE_RESULT_OK) { - logError("Unable to buy item, Error response: " + getResponseDesc(response)); - flagEndAsync(); - result = new IabResult(response, "Unable to buy item"); - if (listener != null) listener.onIabPurchaseFinished(result, null); - return; - } - PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT); - logDebug("Launching buy intent for " + sku + ". Request code: " + requestCode); - mRequestCode = requestCode; - mPurchaseListener = listener; - mPurchasingItemType = itemType; - act.startIntentSenderForResult(pendingIntent.getIntentSender(), - requestCode, new Intent(), - Integer.valueOf(0), Integer.valueOf(0), - Integer.valueOf(0)); - } - catch (SendIntentException e) { + int apiVersion = 3; + String packageName = mContext.getPackageName(); + + Bundle configBundle = mService.getPurchaseConfig(apiVersion); + if (configBundle != null && configBundle.getBoolean(INTENT_V2_SUPPORT)) { + logDebug("launchBuyIntentV2 for " + sku + ", item type: " + itemType); + launchBuyIntentV2(act, sku, itemType, requestCode, listener, extraData); + } else { + logDebug("launchBuyIntent for " + sku + ", item type: " + itemType); + launchBuyIntent(act, sku, itemType, requestCode, listener, extraData); + } + } catch (IntentSender.SendIntentException e) { logError("SendIntentException while launching purchase flow for sku " + sku); e.printStackTrace(); flagEndAsync(); result = new IabResult(IABHELPER_SEND_INTENT_FAILED, "Failed to send intent."); if (listener != null) listener.onIabPurchaseFinished(result, null); - } - catch (RemoteException e) { + } catch (RemoteException e) { logError("RemoteException while launching purchase flow for sku " + sku); e.printStackTrace(); flagEndAsync(); @@ -421,6 +422,69 @@ requestCode, new Intent(), } } + private void launchBuyIntentV2( + Activity act, + String sku, + String itemType, + int requestCode, + OnIabPurchaseFinishedListener listener, + String extraData + ) throws RemoteException { + int apiVersion = 3; + String packageName = mContext.getPackageName(); + + Bundle buyIntentBundle = mService.getBuyIntentV2(apiVersion, packageName, sku, itemType, extraData); + int response = getResponseCodeFromBundle(buyIntentBundle); + if (response != BILLING_RESPONSE_RESULT_OK) { + logError("Unable to buy item, Error response: " + getResponseDesc(response)); + flagEndAsync(); + IabResult result = new IabResult(response, "Unable to buy item"); + if (listener != null) listener.onIabPurchaseFinished(result, null); + return; + } + + Intent intent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT); + logDebug("Launching buy intent for " + sku + ". Request code: " + requestCode); + mRequestCode = requestCode; + mPurchaseListener = listener; + mPurchasingItemType = itemType; + act.startActivityForResult(intent, requestCode); + } + + private void launchBuyIntent( + Activity act, + String sku, + String itemType, + int requestCode, + OnIabPurchaseFinishedListener listener, + String extraData + ) throws RemoteException, IntentSender.SendIntentException { + + int apiVersion = 3; + String packageName = mContext.getPackageName(); + + Bundle buyIntentBundle = mService.getBuyIntent(apiVersion, packageName, sku, itemType, extraData); + int response = getResponseCodeFromBundle(buyIntentBundle); + if (response != BILLING_RESPONSE_RESULT_OK) { + logError("Unable to buy item, Error response: " + getResponseDesc(response)); + flagEndAsync(); + IabResult result = new IabResult(response, "Unable to buy item"); + if (listener != null) listener.onIabPurchaseFinished(result, null); + return; + } + + + PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT); + logDebug("Launching buy intent for " + sku + ". Request code: " + requestCode); + mRequestCode = requestCode; + mPurchaseListener = listener; + mPurchasingItemType = itemType; + act.startIntentSenderForResult(pendingIntent.getIntentSender(), + requestCode, new Intent(), + Integer.valueOf(0), Integer.valueOf(0), + Integer.valueOf(0)); + } + /** * Handles an activity result that's part of the purchase flow in in-app billing. If you * are calling {@link #launchPurchaseFlow}, then you must call this method from your @@ -525,7 +589,7 @@ public Inventory queryInventory(boolean querySkuDetails, List moreSkus) /** * Queries the inventory. This will query all owned items from the server, as well as * information on additional skus, if specified. This method may block or take long to execute. - * Do not call from a UI thread. For that, use the non-blocking version {@link #refreshInventoryAsync}. + * Do not call from a UI thread. * * @param querySkuDetails if true, SKU details (price, description, etc) will be queried as well * as purchase information. @@ -730,7 +794,8 @@ public void consumeAsync(Purchase purchase, OnConsumeFinishedListener listener) } /** - * Same as {@link consumeAsync}, but for multiple items at once. + * Same as {@link #consumeAsync(Purchase, OnConsumeFinishedListener)}, but for multiple items at once. + * * @param purchases The list of PurchaseInfo objects representing the purchases to consume. * @param listener The listener to notify when the consumption operation finishes. */ diff --git a/android/app/src/main/java/com/contoriel/cafebazaar/util/Inventory.java b/android/app/src/main/java/com/contoriel/cafebazaar/util/Inventory.java index 753e256..ea34cc3 100644 --- a/android/app/src/main/java/com/contoriel/cafebazaar/util/Inventory.java +++ b/android/app/src/main/java/com/contoriel/cafebazaar/util/Inventory.java @@ -19,7 +19,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.lang.String; /** * Represents a block of information about in-app items. @@ -78,7 +77,7 @@ List getAllOwnedSkus(String itemType) { } /** Returns a list of all purchases. */ - public List getAllPurchases() { + List getAllPurchases() { return new ArrayList(mPurchaseMap.values()); }