Guest User

IabHelper.java

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