Advertisement
semtiko

Fixed IabHelper.java

Mar 13th, 2013
86
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 36.97 KB | None | 0 0
  1. import java.util.ArrayList;
  2. import java.util.List;
  3.  
  4. import org.json.JSONException;
  5.  
  6. import android.app.Activity;
  7. import android.app.PendingIntent;
  8. import android.content.ComponentName;
  9. import android.content.Context;
  10. import android.content.Intent;
  11. import android.content.IntentSender.SendIntentException;
  12. import android.content.ServiceConnection;
  13. import android.os.Bundle;
  14. import android.os.Handler;
  15. import android.os.IBinder;
  16. import android.os.RemoteException;
  17. import android.text.TextUtils;
  18. import android.util.Log;
  19.  
  20. import com.android.vending.billing.IInAppBillingService;
  21.  
  22. /**
  23.  * Provides convenience methods for in-app billing. You can create one instance of this
  24.  * class for your application and use it to process in-app billing operations.
  25.  * It provides synchronous (blocking) and asynchronous (non-blocking) methods for
  26.  * many common in-app billing operations, as well as automatic signature
  27.  * verification.
  28.  *
  29.  * After instantiating, you must perform setup in order to start using the object.
  30.  * To perform setup, call the {@link #startSetup} method and provide a listener;
  31.  * that listener will be notified when setup is complete, after which (and not before)
  32.  * you may call other methods.
  33.  *
  34.  * After setup is complete, you will typically want to request an inventory of owned
  35.  * items and subscriptions. See {@link #queryInventory}, {@link #queryInventoryAsync}
  36.  * and related methods.
  37.  *
  38.  * When you are done with this object, don't forget to call {@link #dispose}
  39.  * to ensure proper cleanup. This object holds a binding to the in-app billing
  40.  * service, which will leak unless you dispose of it correctly. If you created
  41.  * the object on an Activity's onCreate method, then the recommended
  42.  * place to dispose of it is the Activity's onDestroy method.
  43.  *
  44.  * A note about threading: When using this object from a background thread, you may
  45.  * call the blocking versions of methods; when using from a UI thread, call
  46.  * only the asynchronous versions and handle the results via callbacks.
  47.  * Also, notice that you can only call one asynchronous operation at a time;
  48.  * attempting to start a second asynchronous operation while the first one
  49.  * has not yet completed will result in an exception being thrown.
  50.  *
  51.  * @author Bruno Oliveira (Google)
  52.  *
  53.  */
  54. public class IabHelper {
  55.     // Is debug logging enabled?
  56.     boolean mDebugLog = false;
  57.     String mDebugTag = "IabHelper";
  58.  
  59.     // Is setup done?
  60.     boolean mSetupDone = false;
  61.  
  62.     // Are subscriptions supported?
  63.     boolean mSubscriptionsSupported = false;
  64.    
  65.     // Is an asynchronous operation in progress?
  66.     // (only one at a time can be in progress)
  67.     boolean mAsyncInProgress = false;
  68.  
  69.     // (for logging/debugging)
  70.     // if mAsyncInProgress == true, what asynchronous operation is in progress?
  71.     String mAsyncOperation = "";
  72.  
  73.     // Context we were passed during initialization
  74.     Context mContext;
  75.  
  76.     // Connection to the service
  77.     IInAppBillingService mService;
  78.     ServiceConnection mServiceConn;
  79.  
  80.     // The request code used to launch purchase flow
  81.     int mRequestCode;
  82.    
  83.     // The item type of the current purchase flow
  84.     String mPurchasingItemType;
  85.  
  86.     // Public key for verifying signature, in base64 encoding
  87.     String mSignatureBase64 = null;
  88.  
  89.     // Billing response codes
  90.     public static final int BILLING_RESPONSE_RESULT_OK = 0;
  91.     public static final int BILLING_RESPONSE_RESULT_USER_CANCELED = 1;
  92.     public static final int BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE = 3;
  93.     public static final int BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE = 4;
  94.     public static final int BILLING_RESPONSE_RESULT_DEVELOPER_ERROR = 5;
  95.     public static final int BILLING_RESPONSE_RESULT_ERROR = 6;
  96.     public static final int BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED = 7;
  97.     public static final int BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED = 8;
  98.  
  99.     // IAB Helper error codes
  100.     public static final int IABHELPER_ERROR_BASE = -1000;
  101.     public static final int IABHELPER_REMOTE_EXCEPTION = -1001;
  102.     public static final int IABHELPER_BAD_RESPONSE = -1002;
  103.     public static final int IABHELPER_VERIFICATION_FAILED = -1003;
  104.     public static final int IABHELPER_SEND_INTENT_FAILED = -1004;
  105.     public static final int IABHELPER_USER_CANCELLED = -1005;
  106.     public static final int IABHELPER_UNKNOWN_PURCHASE_RESPONSE = -1006;
  107.     public static final int IABHELPER_MISSING_TOKEN = -1007;
  108.     public static final int IABHELPER_UNKNOWN_ERROR = -1008;
  109.     public static final int IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE = -1009;
  110.     public static final int IABHELPER_INVALID_CONSUMPTION = -1010;
  111.  
  112.     // Keys for the responses from InAppBillingService
  113.     public static final String RESPONSE_CODE = "RESPONSE_CODE";
  114.     public static final String RESPONSE_GET_SKU_DETAILS_LIST = "DETAILS_LIST";
  115.     public static final String RESPONSE_BUY_INTENT = "BUY_INTENT";
  116.     public static final String RESPONSE_INAPP_PURCHASE_DATA = "INAPP_PURCHASE_DATA";
  117.     public static final String RESPONSE_INAPP_SIGNATURE = "INAPP_DATA_SIGNATURE";
  118.     public static final String RESPONSE_INAPP_ITEM_LIST = "INAPP_PURCHASE_ITEM_LIST";
  119.     public static final String RESPONSE_INAPP_PURCHASE_DATA_LIST = "INAPP_PURCHASE_DATA_LIST";
  120.     public static final String RESPONSE_INAPP_SIGNATURE_LIST = "INAPP_DATA_SIGNATURE_LIST";
  121.     public static final String INAPP_CONTINUATION_TOKEN = "INAPP_CONTINUATION_TOKEN";
  122.  
  123.     // Item types
  124.     public static final String ITEM_TYPE_INAPP = "inapp";
  125.     public static final String ITEM_TYPE_SUBS = "subs";
  126.  
  127.     // some fields on the getSkuDetails response bundle
  128.     public static final String GET_SKU_DETAILS_ITEM_LIST = "ITEM_ID_LIST";
  129.     public static final String GET_SKU_DETAILS_ITEM_TYPE_LIST = "ITEM_TYPE_LIST";
  130.  
  131.     /**
  132.      * Creates an instance. After creation, it will not yet be ready to use. You must perform
  133.      * setup by calling {@link #startSetup} and wait for setup to complete. This constructor does not
  134.      * block and is safe to call from a UI thread.
  135.      *
  136.      * @param ctx Your application or Activity context. Needed to bind to the in-app billing service.
  137.      * @param base64PublicKey Your application's public key, encoded in base64.
  138.      *   This is used for verification of purchase signatures. You can find your app's base64-encoded
  139.      *   public key in your application's page on Google Play Developer Console. Note that this
  140.      *   is NOT your "developer public key".
  141.      */
  142.     public IabHelper(Context ctx, String base64PublicKey) {
  143.         mContext = ctx.getApplicationContext();
  144.         mSignatureBase64 = base64PublicKey;
  145.         logDebug("IAB helper created.");
  146.     }
  147.    
  148.     /**
  149.      * Enables or disable debug logging through LogCat.
  150.      */
  151.     public void enableDebugLogging(boolean enable, String tag) {
  152.         mDebugLog = enable;
  153.         mDebugTag = tag;
  154.     }
  155.    
  156.     public void enableDebugLogging(boolean enable) {
  157.         mDebugLog = enable;
  158.     }
  159.  
  160.     /**
  161.      * Callback for setup process. This listener's {@link #onIabSetupFinished} method is called
  162.      * when the setup process is complete.
  163.      */
  164.     public interface OnIabSetupFinishedListener {
  165.         /**
  166.          * Called to notify that setup is complete.
  167.          *
  168.          * @param result The result of the setup process.
  169.          */
  170.         public void onIabSetupFinished(IabResult result);
  171.     }
  172.  
  173.     /**
  174.      * Starts the setup process. This will start up the setup process asynchronously.
  175.      * You will be notified through the listener when the setup process is complete.
  176.      * This method is safe to call from a UI thread.
  177.      *
  178.      * @param listener The listener to notify when the setup process is complete.
  179.      */
  180.     public void startSetup(final OnIabSetupFinishedListener listener) {
  181.         // If already set up, can't do it again.
  182.         if (mSetupDone) throw new IllegalStateException("IAB helper is already set up.");
  183.  
  184.         // Connection to IAB service
  185.         logDebug("Starting in-app billing setup.");
  186.         mServiceConn = new ServiceConnection() {
  187.             @Override
  188.             public void onServiceDisconnected(ComponentName name) {
  189.                 logDebug("Billing service disconnected.");
  190.                 mService = null;
  191.             }
  192.  
  193.             @Override
  194.             public void onServiceConnected(ComponentName name, IBinder service) {
  195.                 logDebug("Billing service connected.");
  196.                 mService = IInAppBillingService.Stub.asInterface(service);
  197.                 String packageName = mContext.getPackageName();
  198.                 try {
  199.                     logDebug("Checking for in-app billing 3 support.");
  200.                    
  201.                     // check for in-app billing v3 support
  202.                     int response = mService.isBillingSupported(3, packageName, ITEM_TYPE_INAPP);
  203.                     if (response != BILLING_RESPONSE_RESULT_OK) {
  204.                         if (listener != null) listener.onIabSetupFinished(new IabResult(response,
  205.                                 "Error checking for billing v3 support."));
  206.                        
  207.                         // if in-app purchases aren't supported, neither are subscriptions.
  208.                         mSubscriptionsSupported = false;
  209.                         return;
  210.                     }
  211.                     logDebug("In-app billing version 3 supported for " + packageName);
  212.                    
  213.                     // check for v3 subscriptions support
  214.                     response = mService.isBillingSupported(3, packageName, ITEM_TYPE_SUBS);
  215.                     if (response == BILLING_RESPONSE_RESULT_OK) {
  216.                         logDebug("Subscriptions AVAILABLE.");
  217.                         mSubscriptionsSupported = true;
  218.                     }
  219.                     else {
  220.                         logDebug("Subscriptions NOT AVAILABLE. Response: " + response);
  221.                     }
  222.                    
  223.                     mSetupDone = true;
  224.                 }
  225.                 catch (RemoteException e) {
  226.                     if (listener != null) {
  227.                         listener.onIabSetupFinished(new IabResult(IABHELPER_REMOTE_EXCEPTION,
  228.                                                     "RemoteException while setting up in-app billing."));
  229.                     }
  230.                     e.printStackTrace();
  231.                     return;
  232.                 }
  233.  
  234.                 if (listener != null) {
  235.                     listener.onIabSetupFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Setup successful."));
  236.                 }
  237.             }
  238.         };
  239.        
  240.         Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
  241.         if (!mContext.getPackageManager().queryIntentServices(serviceIntent, 0).isEmpty()) {
  242.             // service available to handle that Intent
  243.             mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
  244.         }
  245.         else {
  246.             // no service available to handle that Intent
  247.             if (listener != null) {
  248.                 listener.onIabSetupFinished(
  249.                         new IabResult(BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE,
  250.                         "Billing service unavailable on device."));
  251.             }
  252.         }
  253.     }
  254.  
  255.     /**
  256.      * Dispose of object, releasing resources. It's very important to call this
  257.      * method when you are done with this object. It will release any resources
  258.      * used by it such as service connections. Naturally, once the object is
  259.      * disposed of, it can't be used again.
  260.      */
  261.     public void dispose() {
  262.         logDebug("Disposing.");
  263.         mSetupDone = false;
  264.         if (mServiceConn != null) {
  265.             logDebug("Unbinding from service.");
  266.             if (mContext != null) mContext.unbindService(mServiceConn);
  267.             mServiceConn = null;
  268.             mService = null;
  269.             mPurchaseListener = null;
  270.         }
  271.     }
  272.    
  273.     /** Returns whether subscriptions are supported. */
  274.     public boolean subscriptionsSupported() {
  275.         return mSubscriptionsSupported;
  276.     }
  277.  
  278.  
  279.     /**
  280.      * Callback that notifies when a purchase is finished.
  281.      */
  282.     public interface OnIabPurchaseFinishedListener {
  283.         /**
  284.          * Called to notify that an in-app purchase finished. If the purchase was successful,
  285.          * then the sku parameter specifies which item was purchased. If the purchase failed,
  286.          * the sku and extraData parameters may or may not be null, depending on how far the purchase
  287.          * process went.
  288.          *
  289.          * @param result The result of the purchase.
  290.          * @param info The purchase information (null if purchase failed)
  291.          */
  292.         public void onIabPurchaseFinished(IabResult result, Purchase info);
  293.     }
  294.  
  295.     // The listener registered on launchPurchaseFlow, which we have to call back when
  296.     // the purchase finishes
  297.     OnIabPurchaseFinishedListener mPurchaseListener;
  298.  
  299.     public void launchPurchaseFlow(Activity act, String sku, int requestCode, OnIabPurchaseFinishedListener listener) {
  300.         launchPurchaseFlow(act, sku, requestCode, listener, "");
  301.     }
  302.    
  303.     public void launchPurchaseFlow(Activity act, String sku, int requestCode,
  304.             OnIabPurchaseFinishedListener listener, String extraData) {
  305.         launchPurchaseFlow(act, sku, ITEM_TYPE_INAPP, requestCode, listener, extraData);
  306.     }
  307.    
  308.     public void launchSubscriptionPurchaseFlow(Activity act, String sku, int requestCode,
  309.             OnIabPurchaseFinishedListener listener) {
  310.         launchSubscriptionPurchaseFlow(act, sku, requestCode, listener, "");
  311.     }
  312.    
  313.     public void launchSubscriptionPurchaseFlow(Activity act, String sku, int requestCode,
  314.             OnIabPurchaseFinishedListener listener, String extraData) {
  315.         launchPurchaseFlow(act, sku, ITEM_TYPE_SUBS, requestCode, listener, extraData);
  316.     }
  317.  
  318.     /**
  319.      * Initiate the UI flow for an in-app purchase. Call this method to initiate an in-app purchase,
  320.      * which will involve bringing up the Google Play screen. The calling activity will be paused while
  321.      * the user interacts with Google Play, and the result will be delivered via the activity's
  322.      * {@link android.app.Activity#onActivityResult} method, at which point you must call
  323.      * this object's {@link #handleActivityResult} method to continue the purchase flow. This method
  324.      * MUST be called from the UI thread of the Activity.
  325.      *
  326.      * @param act The calling activity.
  327.      * @param sku The sku of the item to purchase.
  328.      * @param itemType indicates if it's a product or a subscription (ITEM_TYPE_INAPP or ITEM_TYPE_SUBS)
  329.      * @param requestCode A request code (to differentiate from other responses --
  330.      *   as in {@link android.app.Activity#startActivityForResult}).
  331.      * @param listener The listener to notify when the purchase process finishes
  332.      * @param extraData Extra data (developer payload), which will be returned with the purchase data
  333.      *   when the purchase completes. This extra data will be permanently bound to that purchase
  334.      *   and will always be returned when the purchase is queried.
  335.      */
  336.     public void launchPurchaseFlow(Activity act, String sku, String itemType, int requestCode, OnIabPurchaseFinishedListener listener, String extraData) {
  337.         checkSetupDone("launchPurchaseFlow");
  338.         flagStartAsync("launchPurchaseFlow");
  339.         IabResult result;
  340.  
  341.         if (itemType.equals(ITEM_TYPE_SUBS) && !mSubscriptionsSupported) {
  342.             IabResult r = new IabResult(IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE, "Subscriptions are not available.");
  343.             if (listener != null) listener.onIabPurchaseFinished(r, null);
  344.             flagEndAsync();
  345.             return;
  346.         }
  347.  
  348.         try {
  349.             logDebug("Constructing buy intent for " + sku + ", item type: " + itemType);
  350.             Bundle buyIntentBundle = mService.getBuyIntent(3, mContext.getPackageName(), sku, itemType, extraData);
  351.  
  352.             int response = getResponseCodeFromBundle(buyIntentBundle);
  353.  
  354.             if (response != BILLING_RESPONSE_RESULT_OK) {
  355.                 logError("Unable to buy item, Error response: " + getResponseDesc(response));
  356.  
  357.                 flagEndAsync();
  358.                 result = new IabResult(response, "Unable to buy item");
  359.  
  360.                 if (listener != null) listener.onIabPurchaseFinished(result, null);
  361.                 return;
  362.             }
  363.  
  364.             PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT);
  365.             logDebug("Launching buy intent for " + sku + ". Request code: " + requestCode);
  366.             mRequestCode = requestCode;
  367.             mPurchaseListener = listener;
  368.             mPurchasingItemType = itemType;
  369.             act.startIntentSenderForResult(pendingIntent.getIntentSender(), requestCode, new Intent(), Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0));
  370.             flagEndAsync();
  371.         } catch (SendIntentException e) {
  372.             logError("SendIntentException while launching purchase flow for sku " + sku);
  373.             e.printStackTrace();
  374.             flagEndAsync();
  375.             result = new IabResult(IABHELPER_SEND_INTENT_FAILED, "Failed to send intent.");
  376.  
  377.             if (listener != null)
  378.                 listener.onIabPurchaseFinished(result, null);
  379.         } catch (RemoteException e) {
  380.             logError("RemoteException while launching purchase flow for sku " + sku);
  381.             e.printStackTrace();
  382.             flagEndAsync();
  383.             result = new IabResult(IABHELPER_REMOTE_EXCEPTION, "Remote exception while starting purchase flow");
  384.  
  385.             if (listener != null)
  386.                 listener.onIabPurchaseFinished(result, null);
  387.         } catch (NullPointerException e) {
  388.             logError("Null pointer exception while launching purchase flow for sku " + sku);
  389.             e.printStackTrace();
  390.             flagEndAsync();
  391.             result = new IabResult(IABHELPER_UNKNOWN_ERROR, "Null pointer exception while starting purchase flow");
  392.  
  393.             if (listener != null)
  394.                 listener.onIabPurchaseFinished(result, null);
  395.         }
  396.     }
  397.  
  398.     /**
  399.      * Handles an activity result that's part of the purchase flow in in-app billing. If you
  400.      * are calling {@link #launchPurchaseFlow}, then you must call this method from your
  401.      * Activity's {@link android.app.Activity@onActivityResult} method. This method
  402.      * MUST be called from the UI thread of the Activity.
  403.      *
  404.      * @param requestCode The requestCode as you received it.
  405.      * @param resultCode The resultCode as you received it.
  406.      * @param data The data (Intent) as you received it.
  407.      * @return Returns true if the result was related to a purchase flow and was handled;
  408.      *   false if the result was not related to a purchase, in which case you should
  409.      *   handle it normally.
  410.      */
  411.     public boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
  412.         IabResult result;
  413.  
  414.         if (requestCode != mRequestCode)
  415.             return false;
  416.  
  417.         checkSetupDone("handleActivityResult");
  418.  
  419.         // end of async purchase operation
  420.         flagEndAsync();
  421.  
  422.         if (data == null) {
  423.             logError("Null data in IAB activity result.");
  424.             result = new IabResult(IABHELPER_BAD_RESPONSE, "Null data in IAB result");
  425.             if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
  426.             return true;
  427.         }
  428.  
  429.         int responseCode = getResponseCodeFromIntent(data);
  430.         String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA);
  431.         String dataSignature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE);
  432.  
  433.         if (resultCode == Activity.RESULT_OK && responseCode == BILLING_RESPONSE_RESULT_OK) {
  434.             logDebug("Successful resultcode from purchase activity.");
  435.             logDebug("Purchase data: " + purchaseData);
  436.             logDebug("Data signature: " + dataSignature);
  437.             logDebug("Extras: " + data.getExtras());
  438.             logDebug("Expected item type: " + mPurchasingItemType);
  439.  
  440.             if (purchaseData == null || dataSignature == null) {
  441.                 logError("BUG: either purchaseData or dataSignature is null.");
  442.                 logDebug("Extras: " + data.getExtras().toString());
  443.                 result = new IabResult(IABHELPER_UNKNOWN_ERROR, "IAB returned null purchaseData or dataSignature");
  444.                 if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
  445.                 return true;
  446.             }
  447.  
  448.             Purchase purchase = null;
  449.  
  450.             try {
  451.                 purchase = new Purchase(mPurchasingItemType, purchaseData, dataSignature);
  452.                 String sku = purchase.getSku();
  453.  
  454.                 // Verify signature
  455.                 if (!Security.verifyPurchase(mSignatureBase64, purchaseData, dataSignature)) {
  456.                     logError("Purchase signature verification FAILED for sku " + sku);
  457.                     result = new IabResult(IABHELPER_VERIFICATION_FAILED, "Signature verification failed for sku " + sku);
  458.  
  459.                     if (mPurchaseListener != null)
  460.                         mPurchaseListener.onIabPurchaseFinished(result, purchase);
  461.  
  462.                     return true;
  463.                 }
  464.  
  465.                 logDebug("Purchase signature successfully verified.");
  466.             } catch (JSONException e) {
  467.                 logError("Failed to parse purchase data.");
  468.                 e.printStackTrace();
  469.                 result = new IabResult(IABHELPER_BAD_RESPONSE, "Failed to parse purchase data.");
  470.  
  471.                 if (mPurchaseListener != null)
  472.                     mPurchaseListener.onIabPurchaseFinished(result, null);
  473.  
  474.                 return true;
  475.             }
  476.  
  477.             if (mPurchaseListener != null)
  478.                 mPurchaseListener.onIabPurchaseFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Success"), purchase);
  479.         } else if (resultCode == Activity.RESULT_OK) {
  480.             // result code was OK, but in-app billing response was not OK.
  481.             logDebug("Result code was OK but in-app billing response was not OK: " + getResponseDesc(responseCode));
  482.  
  483.             if (mPurchaseListener != null) {
  484.                 result = new IabResult(responseCode, "Problem purchashing item.");
  485.                 mPurchaseListener.onIabPurchaseFinished(result, null);
  486.             }
  487.         } else if (resultCode == Activity.RESULT_CANCELED) {
  488.             logDebug("Purchase canceled - Response: " + getResponseDesc(responseCode));
  489.             result = new IabResult(IABHELPER_USER_CANCELLED, "User canceled.");
  490.  
  491.             if (mPurchaseListener != null)
  492.                 mPurchaseListener.onIabPurchaseFinished(result, null);
  493.         } else {
  494.             logError("Purchase failed. Result code: " + Integer.toString(resultCode) + ". Response: " + getResponseDesc(responseCode));
  495.             result = new IabResult(IABHELPER_UNKNOWN_PURCHASE_RESPONSE, "Unknown purchase response.");
  496.  
  497.             if (mPurchaseListener != null)
  498.                 mPurchaseListener.onIabPurchaseFinished(result, null);
  499.         }
  500.  
  501.         return true;
  502.     }
  503.  
  504.     public Inventory queryInventory(boolean querySkuDetails, List<String> moreSkus) throws IabException {
  505.         return queryInventory(querySkuDetails, moreSkus, null);
  506.     }
  507.    
  508.     /**
  509.      * Queries the inventory. This will query all owned items from the server, as well as
  510.      * information on additional skus, if specified. This method may block or take long to execute.
  511.      * Do not call from a UI thread. For that, use the non-blocking version {@link #refreshInventoryAsync}.
  512.      *
  513.      * @param querySkuDetails if true, SKU details (price, description, etc) will be queried as well
  514.      *   as purchase information.
  515.      * @param moreItemSkus additional PRODUCT skus to query information on, regardless of ownership.
  516.      *   Ignored if null or if querySkuDetails is false.
  517.      * @param moreSubsSkus additional SUBSCRIPTIONS skus to query information on, regardless of ownership.
  518.      *   Ignored if null or if querySkuDetails is false.
  519.      * @throws IabException if a problem occurs while refreshing the inventory.
  520.      */
  521.     public Inventory queryInventory(boolean querySkuDetails, List<String> moreItemSkus, List<String> moreSubsSkus) throws IabException {
  522.         checkSetupDone("queryInventory");
  523.  
  524.         try {
  525.             Inventory inv = new Inventory();
  526.             int r = queryPurchases(inv, ITEM_TYPE_INAPP);
  527.  
  528.             if (r != BILLING_RESPONSE_RESULT_OK)
  529.                 throw new IabException(r, "Error refreshing inventory (querying owned items).");
  530.  
  531.             if (querySkuDetails) {
  532.                 r = querySkuDetails(ITEM_TYPE_INAPP, inv, moreItemSkus);
  533.  
  534.                 if (r != BILLING_RESPONSE_RESULT_OK)
  535.                     throw new IabException(r, "Error refreshing inventory (querying prices of items).");
  536.             }
  537.  
  538.             // if subscriptions are supported, then also query for subscriptions
  539.             if (mSubscriptionsSupported) {
  540.                 r = queryPurchases(inv, ITEM_TYPE_SUBS);
  541.  
  542.                 if (r != BILLING_RESPONSE_RESULT_OK)
  543.                     throw new IabException(r, "Error refreshing inventory (querying owned subscriptions).");
  544.  
  545.                 if (querySkuDetails) {
  546.                     r = querySkuDetails(ITEM_TYPE_SUBS, inv, moreItemSkus);
  547.  
  548.                     if (r != BILLING_RESPONSE_RESULT_OK)
  549.                         throw new IabException(r, "Error refreshing inventory (querying prices of subscriptions).");
  550.                 }
  551.             }
  552.            
  553.             return inv;
  554.         } catch (RemoteException e) {
  555.             throw new IabException(IABHELPER_REMOTE_EXCEPTION, "Remote exception while refreshing inventory.", e);
  556.         } catch (JSONException e) {
  557.             throw new IabException(IABHELPER_BAD_RESPONSE, "Error parsing JSON response while refreshing inventory.", e);
  558.         }
  559.     }
  560.  
  561.     /**
  562.      * Listener that notifies when an inventory query operation completes.
  563.      */
  564.     public interface QueryInventoryFinishedListener {
  565.         /**
  566.          * Called to notify that an inventory query operation completed.
  567.          *
  568.          * @param result The result of the operation.
  569.          * @param inv The inventory.
  570.          */
  571.         public void onQueryInventoryFinished(IabResult result, Inventory inv);
  572.     }
  573.  
  574.     /**
  575.      * Asynchronous wrapper for inventory query. This will perform an inventory
  576.      * query as described in {@link #queryInventory}, but will do so asynchronously
  577.      * and call back the specified listener upon completion. This method is safe to
  578.      * call from a UI thread.
  579.      *
  580.      * @param querySkuDetails as in {@link #queryInventory}
  581.      * @param moreSkus as in {@link #queryInventory}
  582.      * @param listener The listener to notify when the refresh operation completes.
  583.      */
  584.     public void queryInventoryAsync(final boolean querySkuDetails, final List<String> moreSkus, final QueryInventoryFinishedListener listener) {
  585.         final Handler handler = new Handler();
  586.  
  587.         //if (mAsyncInProgress)
  588.         //  flagEndAsync();
  589.  
  590.         checkSetupDone("queryInventory");
  591.         flagStartAsync("refresh inventory");
  592.  
  593.         new Thread(new Runnable() {
  594.             public void run() {
  595.                 IabResult result = new IabResult(BILLING_RESPONSE_RESULT_OK, "Inventory refresh successful.");
  596.                 Inventory inv = null;
  597.  
  598.                 try {
  599.                     inv = queryInventory(querySkuDetails, moreSkus);
  600.                 } catch (IabException ex) {
  601.                     result = ex.getResult();
  602.                 }
  603.  
  604.                 flagEndAsync();
  605.  
  606.                 final IabResult result_f = result;
  607.                 final Inventory inv_f = inv;
  608.  
  609.                 handler.post(new Runnable() {
  610.                     public void run() {
  611.                         listener.onQueryInventoryFinished(result_f, inv_f);
  612.                     }
  613.                 });
  614.             }
  615.         }).start();
  616.     }
  617.  
  618.     public void queryInventoryAsync(QueryInventoryFinishedListener listener) {
  619.         queryInventoryAsync(true, null, listener);
  620.     }
  621.  
  622.     public void queryInventoryAsync(boolean querySkuDetails, QueryInventoryFinishedListener listener) {
  623.         queryInventoryAsync(querySkuDetails, null, listener);
  624.     }
  625.  
  626.  
  627.     /**
  628.      * Consumes a given in-app product. Consuming can only be done on an item
  629.      * that's owned, and as a result of consumption, the user will no longer own it.
  630.      * This method may block or take long to return. Do not call from the UI thread.
  631.      * For that, see {@link #consumeAsync}.
  632.      *
  633.      * @param itemInfo The PurchaseInfo that represents the item to consume.
  634.      * @throws IabException if there is a problem during consumption.
  635.      */
  636.     void consume(Purchase itemInfo) throws IabException {
  637.         checkSetupDone("consume");
  638.  
  639.         if (!itemInfo.mItemType.equals(ITEM_TYPE_INAPP))
  640.             throw new IabException(IABHELPER_INVALID_CONSUMPTION, "Items of type '" + itemInfo.mItemType + "' can't be consumed.");
  641.  
  642.         try {
  643.             String token = itemInfo.getToken();
  644.             String sku = itemInfo.getSku();
  645.  
  646.             if (token == null || token.equals("")) {
  647.                 logError("Can't consume "+ sku + ". No token.");
  648.                 throw new IabException(IABHELPER_MISSING_TOKEN, "PurchaseInfo is missing token for sku: " + sku + " " + itemInfo);
  649.             }
  650.  
  651.             logDebug("Consuming sku: " + sku + ", token: " + token);
  652.             int response = mService.consumePurchase(3, mContext.getPackageName(), token);
  653.  
  654.             if (response == BILLING_RESPONSE_RESULT_OK) {
  655.                 logDebug("Successfully consumed sku: " + sku);
  656.             } else {
  657.                 logDebug("Error consuming consuming sku " + sku + ". " + getResponseDesc(response));
  658.                 throw new IabException(response, "Error consuming sku " + sku);
  659.             }
  660.         } catch (RemoteException e) {
  661.             throw new IabException(IABHELPER_REMOTE_EXCEPTION, "Remote exception while consuming. PurchaseInfo: " + itemInfo, e);
  662.         }
  663.     }
  664.  
  665.     /**
  666.      * Callback that notifies when a consumption operation finishes.
  667.      */
  668.     public interface OnConsumeFinishedListener {
  669.         /**
  670.          * Called to notify that a consumption has finished.
  671.          *
  672.          * @param purchase The purchase that was (or was to be) consumed.
  673.          * @param result The result of the consumption operation.
  674.          */
  675.         public void onConsumeFinished(Purchase purchase, IabResult result);
  676.     }
  677.  
  678.     /**
  679.      * Callback that notifies when a multi-item consumption operation finishes.
  680.      */
  681.     public interface OnConsumeMultiFinishedListener {
  682.         /**
  683.          * Called to notify that a consumption of multiple items has finished.
  684.          *
  685.          * @param purchases The purchases that were (or were to be) consumed.
  686.          * @param results The results of each consumption operation, corresponding to each
  687.          *   sku.
  688.          */
  689.         public void onConsumeMultiFinished(List<Purchase> purchases, List<IabResult> results);
  690.     }
  691.  
  692.     /**
  693.      * Asynchronous wrapper to item consumption. Works like {@link #consume}, but
  694.      * performs the consumption in the background and notifies completion through
  695.      * the provided listener. This method is safe to call from a UI thread.
  696.      *
  697.      * @param purchase The purchase to be consumed.
  698.      * @param listener The listener to notify when the consumption operation finishes.
  699.      */
  700.     public void consumeAsync(Purchase purchase, OnConsumeFinishedListener listener) {
  701.         checkSetupDone("consume");
  702.         List<Purchase> purchases = new ArrayList<Purchase>();
  703.         purchases.add(purchase);
  704.         consumeAsyncInternal(purchases, listener, null);
  705.     }
  706.  
  707.     /**
  708.      * Same as {@link consumeAsync}, but for multiple items at once.
  709.      * @param purchases The list of PurchaseInfo objects representing the purchases to consume.
  710.      * @param listener The listener to notify when the consumption operation finishes.
  711.      */
  712.     public void consumeAsync(List<Purchase> purchases, OnConsumeMultiFinishedListener listener) {
  713.         checkSetupDone("consume");
  714.         consumeAsyncInternal(purchases, null, listener);
  715.     }
  716.  
  717.     /**
  718.      * Returns a human-readable description for the given response code.
  719.      *
  720.      * @param code The response code
  721.      * @return A human-readable string explaining the result code.
  722.      *   It also includes the result code numerically.
  723.      */
  724.     public static String getResponseDesc(int code) {
  725.         String[] iab_msgs = ("0:OK/1:User Canceled/2:Unknown/" +
  726.                 "3:Billing Unavailable/4:Item unavailable/" +
  727.                 "5:Developer Error/6:Error/7:Item Already Owned/" +
  728.                 "8:Item not owned").split("/");
  729.         String[] iabhelper_msgs = ("0:OK/-1001:Remote exception during initialization/" +
  730.                                    "-1002:Bad response received/" +
  731.                                    "-1003:Purchase signature verification failed/" +
  732.                                    "-1004:Send intent failed/" +
  733.                                    "-1005:User cancelled/" +
  734.                                    "-1006:Unknown purchase response/" +
  735.                                    "-1007:Missing token/" +
  736.                                    "-1008:Unknown error/" +
  737.                                    "-1009:Subscriptions not available/" +
  738.                                    "-1010:Invalid consumption attempt").split("/");
  739.  
  740.         if (code <= IABHELPER_ERROR_BASE) {
  741.             int index = IABHELPER_ERROR_BASE - code;
  742.             if (index >= 0 && index < iabhelper_msgs.length) return iabhelper_msgs[index];
  743.             else return String.valueOf(code) + ":Unknown IAB Helper Error";
  744.         } else if (code < 0 || code >= iab_msgs.length) {
  745.             return String.valueOf(code) + ":Unknown";
  746.         } else {
  747.             return iab_msgs[code];
  748.         }
  749.     }
  750.  
  751.  
  752.     // Checks that setup was done; if not, throws an exception.
  753.     void checkSetupDone(String operation) {
  754.         if (!mSetupDone) {
  755.             logError("Illegal state for operation (" + operation + "): IAB helper is not set up.");
  756.             throw new IllegalStateException("IAB helper is not set up. Can't perform operation: " + operation);
  757.         }
  758.     }
  759.  
  760.     // Workaround to bug where sometimes response codes come as Long instead of Integer
  761.     int getResponseCodeFromBundle(Bundle b) {
  762.         Object o = b.get(RESPONSE_CODE);
  763.         if (o == null) {
  764.             logDebug("Bundle with null response code, assuming OK (known issue)");
  765.             return BILLING_RESPONSE_RESULT_OK;
  766.         }
  767.         else if (o instanceof Integer) return ((Integer)o).intValue();
  768.         else if (o instanceof Long) return (int)((Long)o).longValue();
  769.         else {
  770.             logError("Unexpected type for bundle response code.");
  771.             logError(o.getClass().getName());
  772.             throw new RuntimeException("Unexpected type for bundle response code: " + o.getClass().getName());
  773.         }
  774.     }
  775.  
  776.     // Workaround to bug where sometimes response codes come as Long instead of Integer
  777.     int getResponseCodeFromIntent(Intent i) {
  778.         Object o = i.getExtras().get(RESPONSE_CODE);
  779.         if (o == null) {
  780.             logError("Intent with no response code, assuming OK (known issue)");
  781.             return BILLING_RESPONSE_RESULT_OK;
  782.         }
  783.         else if (o instanceof Integer) return ((Integer)o).intValue();
  784.         else if (o instanceof Long) return (int)((Long)o).longValue();
  785.         else {
  786.             logError("Unexpected type for intent response code.");
  787.             logError(o.getClass().getName());
  788.             throw new RuntimeException("Unexpected type for intent response code: " + o.getClass().getName());
  789.         }
  790.     }
  791.  
  792.     void flagStartAsync(String operation) {
  793.         if (mAsyncInProgress)
  794.             throw new IllegalStateException("Can't start async operation (" + operation + ") because another async operation(" + mAsyncOperation + ") is in progress.");
  795.  
  796.         mAsyncOperation = operation;
  797.         mAsyncInProgress = true;
  798.  
  799.         logDebug("Starting async operation: " + operation);
  800.     }
  801.  
  802.     void flagEndAsync() {
  803.         logDebug("Ending async operation: " + mAsyncOperation);
  804.         mAsyncOperation = "";
  805.         mAsyncInProgress = false;
  806.     }
  807.  
  808.  
  809.     int queryPurchases(Inventory inv, String itemType) throws JSONException, RemoteException {
  810.         // Query purchases
  811.         logDebug("Querying owned items, item type: " + itemType);
  812.         logDebug("Package name: " + mContext.getPackageName());
  813.         boolean verificationFailed = false;
  814.         String continueToken = null;
  815.  
  816.         do {
  817.             logDebug("Calling getPurchases with continuation token: " + continueToken);
  818.             Bundle ownedItems = mService.getPurchases(3, mContext.getPackageName(),
  819.                     itemType, continueToken);
  820.  
  821.             int response = getResponseCodeFromBundle(ownedItems);
  822.             logDebug("Owned items response: " + String.valueOf(response));
  823.             if (response != BILLING_RESPONSE_RESULT_OK) {
  824.                 logDebug("getPurchases() failed: " + getResponseDesc(response));
  825.                 return response;
  826.             }
  827.             if (!ownedItems.containsKey(RESPONSE_INAPP_ITEM_LIST)
  828.                     || !ownedItems.containsKey(RESPONSE_INAPP_PURCHASE_DATA_LIST)
  829.                     || !ownedItems.containsKey(RESPONSE_INAPP_SIGNATURE_LIST)) {
  830.                 logError("Bundle returned from getPurchases() doesn't contain required fields.");
  831.                 return IABHELPER_BAD_RESPONSE;
  832.             }
  833.  
  834.             ArrayList<String> ownedSkus = ownedItems.getStringArrayList(
  835.                         RESPONSE_INAPP_ITEM_LIST);
  836.             ArrayList<String> purchaseDataList = ownedItems.getStringArrayList(
  837.                         RESPONSE_INAPP_PURCHASE_DATA_LIST);
  838.             ArrayList<String> signatureList = ownedItems.getStringArrayList(
  839.                         RESPONSE_INAPP_SIGNATURE_LIST);
  840.  
  841.             for (int i = 0; i < purchaseDataList.size(); ++i) {
  842.                 String purchaseData = purchaseDataList.get(i);
  843.                 String signature = signatureList.get(i);
  844.                 String sku = ownedSkus.get(i);
  845.                 if (Security.verifyPurchase(mSignatureBase64, purchaseData, signature)) {
  846.                     logDebug("Sku is owned: " + sku);
  847.                     Purchase purchase = new Purchase(itemType, purchaseData, signature);
  848.  
  849.                     if (TextUtils.isEmpty(purchase.getToken())) {
  850.                         logWarn("BUG: empty/null token!");
  851.                         logDebug("Purchase data: " + purchaseData);
  852.                     }
  853.  
  854.                     // Record ownership and token
  855.                     inv.addPurchase(purchase);
  856.                 }
  857.                 else {
  858.                     logWarn("Purchase signature verification **FAILED**. Not adding item.");
  859.                     logDebug("   Purchase data: " + purchaseData);
  860.                     logDebug("   Signature: " + signature);
  861.                     verificationFailed = true;
  862.                 }
  863.             }
  864.  
  865.             continueToken = ownedItems.getString(INAPP_CONTINUATION_TOKEN);
  866.             logDebug("Continuation token: " + continueToken);
  867.         } while (!TextUtils.isEmpty(continueToken));
  868.  
  869.         return verificationFailed ? IABHELPER_VERIFICATION_FAILED : BILLING_RESPONSE_RESULT_OK;
  870.     }
  871.  
  872.     int querySkuDetails(String itemType, Inventory inv, List<String> moreSkus) throws RemoteException, JSONException {
  873.         logDebug("Querying SKU details.");
  874.         ArrayList<String> skuList = new ArrayList<String>();
  875.         skuList.addAll(inv.getAllOwnedSkus(itemType));
  876.  
  877.         if (moreSkus != null)
  878.             skuList.addAll(moreSkus);
  879.  
  880.         if (skuList.size() == 0) {
  881.             logDebug("queryPrices: nothing to do because there are no SKUs.");
  882.             return BILLING_RESPONSE_RESULT_OK;
  883.         }
  884.  
  885.         Bundle querySkus = new Bundle();
  886.         querySkus.putStringArrayList(GET_SKU_DETAILS_ITEM_LIST, skuList);
  887.         Bundle skuDetails = mService.getSkuDetails(3, mContext.getPackageName(), itemType, querySkus);
  888.  
  889.         if (!skuDetails.containsKey(RESPONSE_GET_SKU_DETAILS_LIST)) {
  890.             int response = getResponseCodeFromBundle(skuDetails);
  891.  
  892.             if (response != BILLING_RESPONSE_RESULT_OK) {
  893.                 logDebug("getSkuDetails() failed: " + getResponseDesc(response));
  894.                 return response;
  895.             } else {
  896.                 logError("getSkuDetails() returned a bundle with neither an error nor a detail list.");
  897.                 return IABHELPER_BAD_RESPONSE;
  898.             }
  899.         }
  900.  
  901.         ArrayList<String> responseList = skuDetails.getStringArrayList(
  902.                 RESPONSE_GET_SKU_DETAILS_LIST);
  903.  
  904.         for (String thisResponse : responseList) {
  905.             SkuDetails d = new SkuDetails(itemType, thisResponse);
  906.             logDebug("Got sku details: " + d);
  907.             inv.addSkuDetails(d);
  908.         }
  909.  
  910.         return BILLING_RESPONSE_RESULT_OK;
  911.     }
  912.  
  913.  
  914.     void consumeAsyncInternal(final List<Purchase> purchases, final OnConsumeFinishedListener singleListener, final OnConsumeMultiFinishedListener multiListener) {
  915.         final Handler handler = new Handler();
  916.  
  917.         flagStartAsync("consume");
  918.  
  919.         new Thread(new Runnable() {
  920.             public void run() {
  921.                 final List<IabResult> results = new ArrayList<IabResult>();
  922.  
  923.                 for (Purchase purchase : purchases) {
  924.                     try {
  925.                         consume(purchase);
  926.                         results.add(new IabResult(BILLING_RESPONSE_RESULT_OK, "Successful consume of sku " + purchase.getSku()));
  927.                     } catch (IabException ex) {
  928.                         results.add(ex.getResult());
  929.                     }
  930.                 }
  931.  
  932.                 flagEndAsync();
  933.  
  934.                 if (singleListener != null) {
  935.                     handler.post(new Runnable() {
  936.                         public void run() {
  937.                             singleListener.onConsumeFinished(purchases.get(0), results.get(0));
  938.                         }
  939.                     });
  940.                 }
  941.  
  942.                 if (multiListener != null) {
  943.                     handler.post(new Runnable() {
  944.                         public void run() {
  945.                             multiListener.onConsumeMultiFinished(purchases, results);
  946.                         }
  947.                     });
  948.                 }
  949.             }
  950.         }).start();
  951.     }
  952.    
  953.     void logDebug(String msg) {
  954.         if (mDebugLog) Log.d(mDebugTag, msg);
  955.     }
  956.    
  957.     void logError(String msg) {
  958.         Log.e(mDebugTag, "In-app billing error: " + msg);
  959.     }
  960.    
  961.     void logWarn(String msg) {
  962.         Log.w(mDebugTag, "In-app billing warning: " + msg);
  963.     }
  964. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement