Android Open Source - Facebook-Contact-Sync Iab Helper






From Project

Back to project page Facebook-Contact-Sync.

License

The source code is released under:

GNU General Public License

If you think the Android project Facebook-Contact-Sync listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/* Copyright (c) 2012 Google Inc.
 */*from   ww  w.  j ava  2s  .  co m*/
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ro.weednet.contactssync.iap;

import android.app.Activity;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender.SendIntentException;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;

import com.android.vending.billing.IInAppBillingService;

import org.json.JSONException;

import java.util.ArrayList;
import java.util.List;

/**
 * Provides convenience methods for in-app billing. You can create one instance
 * of this class for your application and use it to process in-app billing
 * operations. It provides synchronous (blocking) and asynchronous
 * (non-blocking) methods for many common in-app billing operations, as well as
 * automatic signature verification.
 * 
 * After instantiating, you must perform setup in order to start using the
 * object. To perform setup, call the {@link #startSetup} method and provide a
 * listener; that listener will be notified when setup is complete, after which
 * (and not before) you may call other methods.
 * 
 * After setup is complete, you will typically want to request an inventory of
 * owned items and subscriptions. See {@link #queryInventory},
 * {@link #queryInventoryAsync} and related methods.
 * 
 * When you are done with this object, don't forget to call {@link #dispose} to
 * ensure proper cleanup. This object holds a binding to the in-app billing
 * service, which will leak unless you dispose of it correctly. If you created
 * the object on an Activity's onCreate method, then the recommended place to
 * dispose of it is the Activity's onDestroy method.
 * 
 * A note about threading: When using this object from a background thread, you
 * may call the blocking versions of methods; when using from a UI thread, call
 * only the asynchronous versions and handle the results via callbacks. Also,
 * notice that you can only call one asynchronous operation at a time;
 * attempting to start a second asynchronous operation while the first one has
 * not yet completed will result in an exception being thrown.
 * 
 * @author Bruno Oliveira (Google)
 * 
 */
public class IabHelper {
  // Is debug logging enabled?
  boolean mDebugLog = false;
  String mDebugTag = "IabHelper";
  
  // Is setup done?
  boolean mSetupDone = false;
  
  // Has this object been disposed of? (If so, we should ignore callbacks,
  // etc)
  boolean mDisposed = false;
  
  // Are subscriptions supported?
  boolean mSubscriptionsSupported = false;
  
  // Is an asynchronous operation in progress?
  // (only one at a time can be in progress)
  boolean mAsyncInProgress = false;
  
  // (for logging/debugging)
  // if mAsyncInProgress == true, what asynchronous operation is in progress?
  String mAsyncOperation = "";
  
  // Context we were passed during initialization
  Context mContext;
  
  // Connection to the service
  IInAppBillingService mService;
  ServiceConnection mServiceConn;
  
  // The request code used to launch purchase flow
  int mRequestCode;
  
  // The item type of the current purchase flow
  String mPurchasingItemType;
  
  // Public key for verifying signature, in base64 encoding
  String mSignatureBase64 = null;
  
  // Billing response codes
  public static final int BILLING_RESPONSE_RESULT_OK = 0;
  public static final int BILLING_RESPONSE_RESULT_USER_CANCELED = 1;
  public static final int BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE = 3;
  public static final int BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE = 4;
  public static final int BILLING_RESPONSE_RESULT_DEVELOPER_ERROR = 5;
  public static final int BILLING_RESPONSE_RESULT_ERROR = 6;
  public static final int BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED = 7;
  public static final int BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED = 8;
  
  // IAB Helper error codes
  public static final int IABHELPER_ERROR_BASE = -1000;
  public static final int IABHELPER_REMOTE_EXCEPTION = -1001;
  public static final int IABHELPER_BAD_RESPONSE = -1002;
  public static final int IABHELPER_VERIFICATION_FAILED = -1003;
  public static final int IABHELPER_SEND_INTENT_FAILED = -1004;
  public static final int IABHELPER_USER_CANCELLED = -1005;
  public static final int IABHELPER_UNKNOWN_PURCHASE_RESPONSE = -1006;
  public static final int IABHELPER_MISSING_TOKEN = -1007;
  public static final int IABHELPER_UNKNOWN_ERROR = -1008;
  public static final int IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE = -1009;
  public static final int IABHELPER_INVALID_CONSUMPTION = -1010;
  
  // Keys for the responses from InAppBillingService
  public static final String RESPONSE_CODE = "RESPONSE_CODE";
  public static final String RESPONSE_GET_SKU_DETAILS_LIST = "DETAILS_LIST";
  public static final String RESPONSE_BUY_INTENT = "BUY_INTENT";
  public static final String RESPONSE_INAPP_PURCHASE_DATA = "INAPP_PURCHASE_DATA";
  public static final String RESPONSE_INAPP_SIGNATURE = "INAPP_DATA_SIGNATURE";
  public static final String RESPONSE_INAPP_ITEM_LIST = "INAPP_PURCHASE_ITEM_LIST";
  public static final String RESPONSE_INAPP_PURCHASE_DATA_LIST = "INAPP_PURCHASE_DATA_LIST";
  public static final String RESPONSE_INAPP_SIGNATURE_LIST = "INAPP_DATA_SIGNATURE_LIST";
  public static final String INAPP_CONTINUATION_TOKEN = "INAPP_CONTINUATION_TOKEN";
  
  // Item types
  public static final String ITEM_TYPE_INAPP = "inapp";
  public static final String ITEM_TYPE_SUBS = "subs";
  
  // some fields on the getSkuDetails response bundle
  public static final String GET_SKU_DETAILS_ITEM_LIST = "ITEM_ID_LIST";
  public static final String GET_SKU_DETAILS_ITEM_TYPE_LIST = "ITEM_TYPE_LIST";
  
  /**
   * Creates an instance. After creation, it will not yet be ready to use. You
   * must perform setup by calling {@link #startSetup} and wait for setup to
   * complete. This constructor does not block and is safe to call from a UI
   * thread.
   * 
   * @param ctx
   *            Your application or Activity context. Needed to bind to the
   *            in-app billing service.
   * @param base64PublicKey
   *            Your application's public key, encoded in base64. This is used
   *            for verification of purchase signatures. You can find your
   *            app's base64-encoded public key in your application's page on
   *            Google Play Developer Console. Note that this is NOT your
   *            "developer public key".
   */
  public IabHelper(Context ctx, String base64PublicKey) {
    mContext = ctx.getApplicationContext();
    mSignatureBase64 = base64PublicKey;
    logDebug("IAB helper created.");
  }
  
  /**
   * Enables or disable debug logging through LogCat.
   */
  public void enableDebugLogging(boolean enable, String tag) {
    checkNotDisposed();
    mDebugLog = enable;
    mDebugTag = tag;
  }
  
  public void enableDebugLogging(boolean enable) {
    checkNotDisposed();
    mDebugLog = enable;
  }
  
  /**
   * Callback for setup process. This listener's {@link #onIabSetupFinished}
   * method is called when the setup process is complete.
   */
  public interface OnIabSetupFinishedListener {
    /**
     * Called to notify that setup is complete.
     * 
     * @param result
     *            The result of the setup process.
     */
    public void onIabSetupFinished(IabResult result);
  }
  
  /**
   * Starts the setup process. This will start up the setup process
   * asynchronously. You will be notified through the listener when the setup
   * process is complete. This method is safe to call from a UI thread.
   * 
   * @param listener
   *            The listener to notify when the setup process is complete.
   */
  public void startSetup(final OnIabSetupFinishedListener listener) {
    // If already set up, can't do it again.
    checkNotDisposed();
    if (mSetupDone)
      throw new IllegalStateException("IAB helper is already set up.");
    
    // Connection to IAB service
    logDebug("Starting in-app billing setup.");
    mServiceConn = new ServiceConnection() {
      @Override
      public void onServiceDisconnected(ComponentName name) {
        logDebug("Billing service disconnected.");
        mService = null;
      }
      
      @Override
      public void onServiceConnected(ComponentName name, IBinder service) {
        if (mDisposed)
          return;
        logDebug("Billing service connected.");
        mService = IInAppBillingService.Stub.asInterface(service);
        String packageName = mContext.getPackageName();
        try {
          logDebug("Checking for in-app billing 3 support.");
          
          // check for in-app billing v3 support
          int response = mService.isBillingSupported(3, packageName,
              ITEM_TYPE_INAPP);
          if (response != BILLING_RESPONSE_RESULT_OK) {
            if (listener != null)
              listener.onIabSetupFinished(new IabResult(response,
                  "Error checking for billing v3 support."));
          
            // if in-app purchases aren't supported, neither are
            // subscriptions.
            mSubscriptionsSupported = false;
            return;
          }
          logDebug("In-app billing version 3 supported for "
              + packageName);
          
          // check for v3 subscriptions support
          response = mService.isBillingSupported(3, packageName,
              ITEM_TYPE_SUBS);
          if (response == BILLING_RESPONSE_RESULT_OK) {
            logDebug("Subscriptions AVAILABLE.");
            mSubscriptionsSupported = true;
          } else {
            logDebug("Subscriptions NOT AVAILABLE. Response: "
                + response);
          }
          
          mSetupDone = true;
        } catch (RemoteException e) {
          if (listener != null) {
            listener.onIabSetupFinished(new IabResult(
                IABHELPER_REMOTE_EXCEPTION,
                "RemoteException while setting up in-app billing."));
          }
          e.printStackTrace();
          return;
        }
        
        if (listener != null) {
          listener.onIabSetupFinished(new IabResult(
              BILLING_RESPONSE_RESULT_OK, "Setup successful."));
        }
      }
    };
    
    Intent serviceIntent = new Intent(
        "com.android.vending.billing.InAppBillingService.BIND");
    serviceIntent.setPackage("com.android.vending");
    if (!mContext.getPackageManager().queryIntentServices(serviceIntent, 0)
        .isEmpty()) {
      // service available to handle that Intent
      mContext.bindService(serviceIntent, mServiceConn,
          Context.BIND_AUTO_CREATE);
    } else {
      // no service available to handle that Intent
      if (listener != null) {
        listener.onIabSetupFinished(new IabResult(
            BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE,
            "Billing service unavailable on device."));
      }
    }
  }
  
  /**
   * Dispose of object, releasing resources. It's very important to call this
   * method when you are done with this object. It will release any resources
   * used by it such as service connections. Naturally, once the object is
   * disposed of, it can't be used again.
   */
  public void dispose() {
    logDebug("Disposing.");
    mSetupDone = false;
    if (mServiceConn != null) {
      logDebug("Unbinding from service.");
      if (mContext != null)
        mContext.unbindService(mServiceConn);
    }
    mDisposed = true;
    mContext = null;
    mServiceConn = null;
    mService = null;
    mPurchaseListener = null;
  }
  
  private void checkNotDisposed() {
    if (mDisposed)
      throw new IllegalStateException(
          "IabHelper was disposed of, so it cannot be used.");
  }
  
  /** Returns whether subscriptions are supported. */
  public boolean subscriptionsSupported() {
    checkNotDisposed();
    return mSubscriptionsSupported;
  }
  
  /**
   * Callback that notifies when a purchase is finished.
   */
  public interface OnIabPurchaseFinishedListener {
    /**
     * Called to notify that an in-app purchase finished. If the purchase
     * was successful, then the sku parameter specifies which item was
     * purchased. If the purchase failed, the sku and extraData parameters
     * may or may not be null, depending on how far the purchase process
     * went.
     * 
     * @param result
     *            The result of the purchase.
     * @param info
     *            The purchase information (null if purchase failed)
     */
    public void onIabPurchaseFinished(IabResult result, Purchase info);
  }
  
  // The listener registered on launchPurchaseFlow, which we have to call back
  // when
  // the purchase finishes
  OnIabPurchaseFinishedListener mPurchaseListener;
  
  public void launchPurchaseFlow(Activity act, String sku, int requestCode,
      OnIabPurchaseFinishedListener listener) {
    launchPurchaseFlow(act, sku, requestCode, listener, "");
  }
  
  public void launchPurchaseFlow(Activity act, String sku, int requestCode,
      OnIabPurchaseFinishedListener listener, String extraData) {
    launchPurchaseFlow(act, sku, ITEM_TYPE_INAPP, requestCode, listener,
        extraData);
  }
  
  public void launchSubscriptionPurchaseFlow(Activity act, String sku,
      int requestCode, OnIabPurchaseFinishedListener listener) {
    launchSubscriptionPurchaseFlow(act, sku, requestCode, listener, "");
  }
  
  public void launchSubscriptionPurchaseFlow(Activity act, String sku,
      int requestCode, OnIabPurchaseFinishedListener listener,
      String extraData) {
    launchPurchaseFlow(act, sku, ITEM_TYPE_SUBS, requestCode, listener,
        extraData);
  }
  
  /**
   * Initiate the UI flow for an in-app purchase. Call this method to initiate
   * an in-app purchase, which will involve bringing up the Google Play
   * screen. The calling activity will be paused while the user interacts with
   * Google Play, and the result will be delivered via the activity's
   * {@link android.app.Activity#onActivityResult} method, at which point you
   * must call this object's {@link #handleActivityResult} method to continue
   * the purchase flow. This method MUST be called from the UI thread of the
   * Activity.
   * 
   * @param act
   *            The calling activity.
   * @param sku
   *            The sku of the item to purchase.
   * @param itemType
   *            indicates if it's a product or a subscription (ITEM_TYPE_INAPP
   *            or ITEM_TYPE_SUBS)
   * @param requestCode
   *            A request code (to differentiate from other responses -- as in
   *            {@link android.app.Activity#startActivityForResult}).
   * @param listener
   *            The listener to notify when the purchase process finishes
   * @param extraData
   *            Extra data (developer payload), which will be returned with
   *            the purchase data when the purchase completes. This extra data
   *            will be permanently bound to that purchase and will always be
   *            returned when the purchase is queried.
   */
  public void launchPurchaseFlow(Activity act, String sku, String itemType,
      int requestCode, OnIabPurchaseFinishedListener listener,
      String extraData) {
    checkNotDisposed();
    checkSetupDone("launchPurchaseFlow");
    flagStartAsync("launchPurchaseFlow");
    IabResult result;
    
    if (itemType.equals(ITEM_TYPE_SUBS) && !mSubscriptionsSupported) {
      IabResult r = new IabResult(IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE,
          "Subscriptions are not available.");
      flagEndAsync();
      if (listener != null)
        listener.onIabPurchaseFinished(r, null);
      return;
    }
    
    try {
      logDebug("Constructing buy intent for " + sku + ", item type: "
          + itemType);
      Bundle buyIntentBundle = mService.getBuyIntent(3,
          mContext.getPackageName(), sku, itemType, extraData);
      int response = getResponseCodeFromBundle(buyIntentBundle);
      if (response != BILLING_RESPONSE_RESULT_OK) {
        logError("Unable to buy item, Error response: "
            + getResponseDesc(response));
        flagEndAsync();
        result = new IabResult(response, "Unable to buy item");
        if (listener != null)
          listener.onIabPurchaseFinished(result, null);
        return;
      }
      
      PendingIntent pendingIntent = buyIntentBundle
          .getParcelable(RESPONSE_BUY_INTENT);
      logDebug("Launching buy intent for " + sku + ". Request code: "
          + requestCode);
      mRequestCode = requestCode;
      mPurchaseListener = listener;
      mPurchasingItemType = itemType;
      act.startIntentSenderForResult(pendingIntent.getIntentSender(),
          requestCode, new Intent(), Integer.valueOf(0),
          Integer.valueOf(0), Integer.valueOf(0));
    } catch (SendIntentException e) {
      logError("SendIntentException while launching purchase flow for sku "
          + sku);
      e.printStackTrace();
      flagEndAsync();
      
      result = new IabResult(IABHELPER_SEND_INTENT_FAILED,
          "Failed to send intent.");
      if (listener != null)
        listener.onIabPurchaseFinished(result, null);
    } catch (RemoteException e) {
      logError("RemoteException while launching purchase flow for sku "
          + sku);
      e.printStackTrace();
      flagEndAsync();
      
      result = new IabResult(IABHELPER_REMOTE_EXCEPTION,
          "Remote exception while starting purchase flow");
      if (listener != null)
        listener.onIabPurchaseFinished(result, null);
    }
  }
  
  /**
   * Handles an activity result that's part of the purchase flow in in-app
   * billing. If you are calling {@link #launchPurchaseFlow}, then you must
   * call this method from your Activity's {@link android.app.Activity
   * @onActivityResult} method. This method MUST be called from the UI thread
   * of the Activity.
   * 
   * @param requestCode
   *            The requestCode as you received it.
   * @param resultCode
   *            The resultCode as you received it.
   * @param data
   *            The data (Intent) as you received it.
   * @return Returns true if the result was related to a purchase flow and was
   *         handled; false if the result was not related to a purchase, in
   *         which case you should handle it normally.
   */
  public boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
    IabResult result;
    if (requestCode != mRequestCode)
      return false;
    
    checkNotDisposed();
    checkSetupDone("handleActivityResult");
    
    // end of async purchase operation that started on launchPurchaseFlow
    flagEndAsync();
    
    if (data == null) {
      logError("Null data in IAB activity result.");
      result = new IabResult(IABHELPER_BAD_RESPONSE,
          "Null data in IAB result");
      if (mPurchaseListener != null)
        mPurchaseListener.onIabPurchaseFinished(result, null);
      return true;
    }
    
    int responseCode = getResponseCodeFromIntent(data);
    String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA);
    String dataSignature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE);
    
    if (resultCode == Activity.RESULT_OK
        && responseCode == BILLING_RESPONSE_RESULT_OK) {
      logDebug("Successful resultcode from purchase activity.");
      logDebug("Purchase data: " + purchaseData);
      logDebug("Data signature: " + dataSignature);
      logDebug("Extras: " + data.getExtras());
      logDebug("Expected item type: " + mPurchasingItemType);
      
      if (purchaseData == null || dataSignature == null) {
        logError("BUG: either purchaseData or dataSignature is null.");
        logDebug("Extras: " + data.getExtras().toString());
        result = new IabResult(IABHELPER_UNKNOWN_ERROR,
            "IAB returned null purchaseData or dataSignature");
        if (mPurchaseListener != null)
          mPurchaseListener.onIabPurchaseFinished(result, null);
        return true;
      }
      
      Purchase purchase = null;
      try {
        purchase = new Purchase(mPurchasingItemType, purchaseData,
            dataSignature);
        String sku = purchase.getSku();
        
        // Verify signature
        if (!Security.verifyPurchase(mSignatureBase64, purchaseData,
            dataSignature)) {
          logError("Purchase signature verification FAILED for sku "
              + sku);
          result = new IabResult(IABHELPER_VERIFICATION_FAILED,
              "Signature verification failed for sku " + sku);
          if (mPurchaseListener != null)
            mPurchaseListener.onIabPurchaseFinished(result,
                purchase);
          return true;
        }
        logDebug("Purchase signature successfully verified.");
      } catch (JSONException e) {
        logError("Failed to parse purchase data.");
        e.printStackTrace();
        result = new IabResult(IABHELPER_BAD_RESPONSE,
            "Failed to parse purchase data.");
        if (mPurchaseListener != null)
          mPurchaseListener.onIabPurchaseFinished(result, null);
        return true;
      }
      
      if (mPurchaseListener != null) {
        mPurchaseListener.onIabPurchaseFinished(new IabResult(
            BILLING_RESPONSE_RESULT_OK, "Success"), purchase);
      }
    } else if (resultCode == Activity.RESULT_OK) {
      // result code was OK, but in-app billing response was not OK.
      logDebug("Result code was OK but in-app billing response was not OK: "
          + getResponseDesc(responseCode));
      if (mPurchaseListener != null) {
        result = new IabResult(responseCode,
            "Problem purchashing item.");
        mPurchaseListener.onIabPurchaseFinished(result, null);
      }
    } else if (resultCode == Activity.RESULT_CANCELED) {
      logDebug("Purchase canceled - Response: "
          + getResponseDesc(responseCode));
      result = new IabResult(IABHELPER_USER_CANCELLED, "User canceled.");
      if (mPurchaseListener != null)
        mPurchaseListener.onIabPurchaseFinished(result, null);
    } else {
      logError("Purchase failed. Result code: "
          + Integer.toString(resultCode) + ". Response: "
          + getResponseDesc(responseCode));
      result = new IabResult(IABHELPER_UNKNOWN_PURCHASE_RESPONSE,
          "Unknown purchase response.");
      if (mPurchaseListener != null)
        mPurchaseListener.onIabPurchaseFinished(result, null);
    }
    return true;
  }
  
  public Inventory queryInventory(boolean querySkuDetails,
      List<String> moreSkus) throws IabException {
    return queryInventory(querySkuDetails, moreSkus, null);
  }
  
  /**
   * Queries the inventory. This will query all owned items from the server,
   * as well as information on additional skus, if specified. This method may
   * block or take long to execute. Do not call from a UI thread. For that,
   * use the non-blocking version {@link #refreshInventoryAsync}.
   * 
   * @param querySkuDetails
   *            if true, SKU details (price, description, etc) will be queried
   *            as well as purchase information.
   * @param moreItemSkus
   *            additional PRODUCT skus to query information on, regardless of
   *            ownership. Ignored if null or if querySkuDetails is false.
   * @param moreSubsSkus
   *            additional SUBSCRIPTIONS skus to query information on,
   *            regardless of ownership. Ignored if null or if querySkuDetails
   *            is false.
   * @throws IabException
   *             if a problem occurs while refreshing the inventory.
   */
  public Inventory queryInventory(boolean querySkuDetails,
      List<String> moreItemSkus, List<String> moreSubsSkus)
      throws IabException {
    checkNotDisposed();
    checkSetupDone("queryInventory");
    try {
      Inventory inv = new Inventory();
      int r = queryPurchases(inv, ITEM_TYPE_INAPP);
      if (r != BILLING_RESPONSE_RESULT_OK) {
        throw new IabException(r,
            "Error refreshing inventory (querying owned items).");
      }
      
      if (querySkuDetails) {
        r = querySkuDetails(ITEM_TYPE_INAPP, inv, moreItemSkus);
        if (r != BILLING_RESPONSE_RESULT_OK) {
          throw new IabException(r,
              "Error refreshing inventory (querying prices of items).");
        }
      }
      
      // if subscriptions are supported, then also query for subscriptions
      if (mSubscriptionsSupported) {
        r = queryPurchases(inv, ITEM_TYPE_SUBS);
        if (r != BILLING_RESPONSE_RESULT_OK) {
          throw new IabException(r,
              "Error refreshing inventory (querying owned subscriptions).");
        }
        
        if (querySkuDetails) {
          r = querySkuDetails(ITEM_TYPE_SUBS, inv, moreItemSkus);
          if (r != BILLING_RESPONSE_RESULT_OK) {
            throw new IabException(r,
                "Error refreshing inventory (querying prices of subscriptions).");
          }
        }
      }
      
      return inv;
    } catch (RemoteException e) {
      throw new IabException(IABHELPER_REMOTE_EXCEPTION,
          "Remote exception while refreshing inventory.", e);
    } catch (JSONException e) {
      throw new IabException(IABHELPER_BAD_RESPONSE,
          "Error parsing JSON response while refreshing inventory.",
          e);
    }
  }
  
  /**
   * Listener that notifies when an inventory query operation completes.
   */
  public interface QueryInventoryFinishedListener {
    /**
     * Called to notify that an inventory query operation completed.
     * 
     * @param result
     *            The result of the operation.
     * @param inv
     *            The inventory.
     */
    public void onQueryInventoryFinished(IabResult result, Inventory inv);
  }
  
  /**
   * Asynchronous wrapper for inventory query. This will perform an inventory
   * query as described in {@link #queryInventory}, but will do so
   * asynchronously and call back the specified listener upon completion. This
   * method is safe to call from a UI thread.
   * 
   * @param querySkuDetails
   *            as in {@link #queryInventory}
   * @param moreSkus
   *            as in {@link #queryInventory}
   * @param listener
   *            The listener to notify when the refresh operation completes.
   */
  public void queryInventoryAsync(final boolean querySkuDetails,
      final List<String> moreSkus,
      final QueryInventoryFinishedListener listener) {
    final Handler handler = new Handler();
    checkNotDisposed();
    checkSetupDone("queryInventory");
    flagStartAsync("refresh inventory");
    (new Thread(new Runnable() {
      public void run() {
        IabResult result = new IabResult(BILLING_RESPONSE_RESULT_OK,
            "Inventory refresh successful.");
        Inventory inv = null;
        try {
          inv = queryInventory(querySkuDetails, moreSkus);
        } catch (IabException ex) {
          result = ex.getResult();
        }
        
        flagEndAsync();
        
        final IabResult result_f = result;
        final Inventory inv_f = inv;
        if (!mDisposed && listener != null) {
          handler.post(new Runnable() {
            public void run() {
              listener.onQueryInventoryFinished(result_f, inv_f);
            }
          });
        }
      }
    })).start();
  }
  
  public void queryInventoryAsync(QueryInventoryFinishedListener listener) {
    queryInventoryAsync(true, null, listener);
  }
  
  public void queryInventoryAsync(boolean querySkuDetails,
      QueryInventoryFinishedListener listener) {
    queryInventoryAsync(querySkuDetails, null, listener);
  }
  
  /**
   * Consumes a given in-app product. Consuming can only be done on an item
   * that's owned, and as a result of consumption, the user will no longer own
   * it. This method may block or take long to return. Do not call from the UI
   * thread. For that, see {@link #consumeAsync}.
   * 
   * @param itemInfo
   *            The PurchaseInfo that represents the item to consume.
   * @throws IabException
   *             if there is a problem during consumption.
   */
  void consume(Purchase itemInfo) throws IabException {
    checkNotDisposed();
    checkSetupDone("consume");
    
    if (!itemInfo.mItemType.equals(ITEM_TYPE_INAPP)) {
      throw new IabException(IABHELPER_INVALID_CONSUMPTION,
          "Items of type '" + itemInfo.mItemType
              + "' can't be consumed.");
    }
    
    try {
      String token = itemInfo.getToken();
      String sku = itemInfo.getSku();
      if (token == null || token.equals("")) {
        logError("Can't consume " + sku + ". No token.");
        throw new IabException(IABHELPER_MISSING_TOKEN,
            "PurchaseInfo is missing token for sku: " + sku + " "
                + itemInfo);
      }
      
      logDebug("Consuming sku: " + sku + ", token: " + token);
      int response = mService.consumePurchase(3,
          mContext.getPackageName(), token);
      if (response == BILLING_RESPONSE_RESULT_OK) {
        logDebug("Successfully consumed sku: " + sku);
      } else {
        logDebug("Error consuming consuming sku " + sku + ". "
            + getResponseDesc(response));
        throw new IabException(response, "Error consuming sku " + sku);
      }
    } catch (RemoteException e) {
      throw new IabException(IABHELPER_REMOTE_EXCEPTION,
          "Remote exception while consuming. PurchaseInfo: "
              + itemInfo, e);
    }
  }
  
  /**
   * Callback that notifies when a consumption operation finishes.
   */
  public interface OnConsumeFinishedListener {
    /**
     * Called to notify that a consumption has finished.
     * 
     * @param purchase
     *            The purchase that was (or was to be) consumed.
     * @param result
     *            The result of the consumption operation.
     */
    public void onConsumeFinished(Purchase purchase, IabResult result);
  }
  
  /**
   * Callback that notifies when a multi-item consumption operation finishes.
   */
  public interface OnConsumeMultiFinishedListener {
    /**
     * Called to notify that a consumption of multiple items has finished.
     * 
     * @param purchases
     *            The purchases that were (or were to be) consumed.
     * @param results
     *            The results of each consumption operation, corresponding
     *            to each sku.
     */
    public void onConsumeMultiFinished(List<Purchase> purchases,
        List<IabResult> results);
  }
  
  /**
   * Asynchronous wrapper to item consumption. Works like {@link #consume},
   * but performs the consumption in the background and notifies completion
   * through the provided listener. This method is safe to call from a UI
   * thread.
   * 
   * @param purchase
   *            The purchase to be consumed.
   * @param listener
   *            The listener to notify when the consumption operation
   *            finishes.
   */
  public void consumeAsync(Purchase purchase,
      OnConsumeFinishedListener listener) {
    checkNotDisposed();
    checkSetupDone("consume");
    List<Purchase> purchases = new ArrayList<Purchase>();
    purchases.add(purchase);
    consumeAsyncInternal(purchases, listener, null);
  }
  
  /**
   * Same as {@link consumeAsync}, but for multiple items at once.
   * 
   * @param purchases
   *            The list of PurchaseInfo objects representing the purchases to
   *            consume.
   * @param listener
   *            The listener to notify when the consumption operation
   *            finishes.
   */
  public void consumeAsync(List<Purchase> purchases,
      OnConsumeMultiFinishedListener listener) {
    checkNotDisposed();
    checkSetupDone("consume");
    consumeAsyncInternal(purchases, null, listener);
  }
  
  /**
   * Returns a human-readable description for the given response code.
   * 
   * @param code
   *            The response code
   * @return A human-readable string explaining the result code. It also
   *         includes the result code numerically.
   */
  public static String getResponseDesc(int code) {
    String[] iab_msgs = ("0:OK/1:User Canceled/2:Unknown/"
        + "3:Billing Unavailable/4:Item unavailable/"
        + "5:Developer Error/6:Error/7:Item Already Owned/"
        + "8:Item not owned").split("/");
    String[] iabhelper_msgs = ("0:OK/-1001:Remote exception during initialization/"
        + "-1002:Bad response received/"
        + "-1003:Purchase signature verification failed/"
        + "-1004:Send intent failed/"
        + "-1005:User cancelled/"
        + "-1006:Unknown purchase response/"
        + "-1007:Missing token/"
        + "-1008:Unknown error/"
        + "-1009:Subscriptions not available/"
        + "-1010:Invalid consumption attempt").split("/");
    
    if (code <= IABHELPER_ERROR_BASE) {
      int index = IABHELPER_ERROR_BASE - code;
      if (index >= 0 && index < iabhelper_msgs.length)
        return iabhelper_msgs[index];
      else
        return String.valueOf(code) + ":Unknown IAB Helper Error";
    } else if (code < 0 || code >= iab_msgs.length)
      return String.valueOf(code) + ":Unknown";
    else
      return iab_msgs[code];
  }
  
  // Checks that setup was done; if not, throws an exception.
  void checkSetupDone(String operation) {
    if (!mSetupDone) {
      logError("Illegal state for operation (" + operation
          + "): IAB helper is not set up.");
      throw new IllegalStateException(
          "IAB helper is not set up. Can't perform operation: "
              + operation);
    }
  }
  
  // Workaround to bug where sometimes response codes come as Long instead of
  // Integer
  int getResponseCodeFromBundle(Bundle b) {
    Object o = b.get(RESPONSE_CODE);
    if (o == null) {
      logDebug("Bundle with null response code, assuming OK (known issue)");
      return BILLING_RESPONSE_RESULT_OK;
    } else if (o instanceof Integer)
      return ((Integer) o).intValue();
    else if (o instanceof Long)
      return (int) ((Long) o).longValue();
    else {
      logError("Unexpected type for bundle response code.");
      logError(o.getClass().getName());
      throw new RuntimeException(
          "Unexpected type for bundle response code: "
              + o.getClass().getName());
    }
  }
  
  // Workaround to bug where sometimes response codes come as Long instead of
  // Integer
  int getResponseCodeFromIntent(Intent i) {
    Object o = i.getExtras().get(RESPONSE_CODE);
    if (o == null) {
      logError("Intent with no response code, assuming OK (known issue)");
      return BILLING_RESPONSE_RESULT_OK;
    } else if (o instanceof Integer)
      return ((Integer) o).intValue();
    else if (o instanceof Long)
      return (int) ((Long) o).longValue();
    else {
      logError("Unexpected type for intent response code.");
      logError(o.getClass().getName());
      throw new RuntimeException(
          "Unexpected type for intent response code: "
              + o.getClass().getName());
    }
  }
  
  void flagStartAsync(String operation) {
    if (mAsyncInProgress)
      throw new IllegalStateException("Can't start async operation ("
          + operation + ") because another async operation("
          + mAsyncOperation + ") is in progress.");
    mAsyncOperation = operation;
    mAsyncInProgress = true;
    logDebug("Starting async operation: " + operation);
  }
  
  void flagEndAsync() {
    logDebug("Ending async operation: " + mAsyncOperation);
    mAsyncOperation = "";
    mAsyncInProgress = false;
  }
  
  int queryPurchases(Inventory inv, String itemType) throws JSONException,
      RemoteException {
    // Query purchases
    logDebug("Querying owned items, item type: " + itemType);
    logDebug("Package name: " + mContext.getPackageName());
    boolean verificationFailed = false;
    String continueToken = null;
    
    do {
      logDebug("Calling getPurchases with continuation token: "
          + continueToken);
      Bundle ownedItems = mService.getPurchases(3,
          mContext.getPackageName(), itemType, continueToken);
      
      int response = getResponseCodeFromBundle(ownedItems);
      logDebug("Owned items response: " + String.valueOf(response));
      if (response != BILLING_RESPONSE_RESULT_OK) {
        logDebug("getPurchases() failed: " + getResponseDesc(response));
        return response;
      }
      if (!ownedItems.containsKey(RESPONSE_INAPP_ITEM_LIST)
          || !ownedItems
              .containsKey(RESPONSE_INAPP_PURCHASE_DATA_LIST)
          || !ownedItems.containsKey(RESPONSE_INAPP_SIGNATURE_LIST)) {
        logError("Bundle returned from getPurchases() doesn't contain required fields.");
        return IABHELPER_BAD_RESPONSE;
      }
      
      ArrayList<String> ownedSkus = ownedItems
          .getStringArrayList(RESPONSE_INAPP_ITEM_LIST);
      ArrayList<String> purchaseDataList = ownedItems
          .getStringArrayList(RESPONSE_INAPP_PURCHASE_DATA_LIST);
      ArrayList<String> signatureList = ownedItems
          .getStringArrayList(RESPONSE_INAPP_SIGNATURE_LIST);
      
      for (int i = 0; i < purchaseDataList.size(); ++i) {
        String purchaseData = purchaseDataList.get(i);
        String signature = signatureList.get(i);
        String sku = ownedSkus.get(i);
        if (Security.verifyPurchase(mSignatureBase64, purchaseData,
            signature)) {
          logDebug("Sku is owned: " + sku);
          Purchase purchase = new Purchase(itemType, purchaseData,
              signature);
          
          if (TextUtils.isEmpty(purchase.getToken())) {
            logWarn("BUG: empty/null token!");
            logDebug("Purchase data: " + purchaseData);
          }
          
          // Record ownership and token
          inv.addPurchase(purchase);
        } else {
          logWarn("Purchase signature verification **FAILED**. Not adding item.");
          logDebug("   Purchase data: " + purchaseData);
          logDebug("   Signature: " + signature);
          verificationFailed = true;
        }
      }
      
      continueToken = ownedItems.getString(INAPP_CONTINUATION_TOKEN);
      logDebug("Continuation token: " + continueToken);
    } while (!TextUtils.isEmpty(continueToken));
    
    return verificationFailed ? IABHELPER_VERIFICATION_FAILED
        : BILLING_RESPONSE_RESULT_OK;
  }
  
  int querySkuDetails(String itemType, Inventory inv, List<String> moreSkus)
      throws RemoteException, JSONException {
    logDebug("Querying SKU details.");
    ArrayList<String> skuList = new ArrayList<String>();
    skuList.addAll(inv.getAllOwnedSkus(itemType));
    if (moreSkus != null) {
      for (String sku : moreSkus) {
        if (!skuList.contains(sku)) {
          skuList.add(sku);
        }
      }
    }
    
    if (skuList.size() == 0) {
      logDebug("queryPrices: nothing to do because there are no SKUs.");
      return BILLING_RESPONSE_RESULT_OK;
    }
    
    Bundle querySkus = new Bundle();
    querySkus.putStringArrayList(GET_SKU_DETAILS_ITEM_LIST, skuList);
    Bundle skuDetails = mService.getSkuDetails(3,
        mContext.getPackageName(), itemType, querySkus);
    
    if (!skuDetails.containsKey(RESPONSE_GET_SKU_DETAILS_LIST)) {
      int response = getResponseCodeFromBundle(skuDetails);
      if (response != BILLING_RESPONSE_RESULT_OK) {
        logDebug("getSkuDetails() failed: " + getResponseDesc(response));
        return response;
      } else {
        logError("getSkuDetails() returned a bundle with neither an error nor a detail list.");
        return IABHELPER_BAD_RESPONSE;
      }
    }
    
    ArrayList<String> responseList = skuDetails
        .getStringArrayList(RESPONSE_GET_SKU_DETAILS_LIST);
    
    for (String thisResponse : responseList) {
      SkuDetails d = new SkuDetails(itemType, thisResponse);
      logDebug("Got sku details: " + d);
      inv.addSkuDetails(d);
    }
    return BILLING_RESPONSE_RESULT_OK;
  }
  
  void consumeAsyncInternal(final List<Purchase> purchases,
      final OnConsumeFinishedListener singleListener,
      final OnConsumeMultiFinishedListener multiListener) {
    final Handler handler = new Handler();
    flagStartAsync("consume");
    (new Thread(new Runnable() {
      public void run() {
        final List<IabResult> results = new ArrayList<IabResult>();
        for (Purchase purchase : purchases) {
          try {
            consume(purchase);
            results.add(new IabResult(BILLING_RESPONSE_RESULT_OK,
                "Successful consume of sku "
                    + purchase.getSku()));
          } catch (IabException ex) {
            results.add(ex.getResult());
          }
        }
        
        flagEndAsync();
        if (!mDisposed && singleListener != null) {
          handler.post(new Runnable() {
            public void run() {
              singleListener.onConsumeFinished(purchases.get(0),
                  results.get(0));
            }
          });
        }
        if (!mDisposed && multiListener != null) {
          handler.post(new Runnable() {
            public void run() {
              multiListener.onConsumeMultiFinished(purchases,
                  results);
            }
          });
        }
      }
    })).start();
  }
  
  void logDebug(String msg) {
    if (mDebugLog)
      Log.d(mDebugTag, msg);
  }
  
  void logError(String msg) {
    Log.e(mDebugTag, "In-app billing error: " + msg);
  }
  
  void logWarn(String msg) {
    Log.w(mDebugTag, "In-app billing warning: " + msg);
  }
}




Java Source Code List

ro.weednet.ContactsSync.java
ro.weednet.contactssync.Constants.java
ro.weednet.contactssync.activities.Preferences.java
ro.weednet.contactssync.activities.Profile.java
ro.weednet.contactssync.activities.TestFacebookApi.java
ro.weednet.contactssync.authenticator.AuthenticationService.java
ro.weednet.contactssync.authenticator.AuthenticatorActivity.java
ro.weednet.contactssync.authenticator.Authenticator.java
ro.weednet.contactssync.client.ContactPhoto.java
ro.weednet.contactssync.client.ContactStreamItem.java
ro.weednet.contactssync.client.NetworkUtilities.java
ro.weednet.contactssync.client.RawContact.java
ro.weednet.contactssync.iap.Base64DecoderException.java
ro.weednet.contactssync.iap.Base64.java
ro.weednet.contactssync.iap.IabException.java
ro.weednet.contactssync.iap.IabHelper.java
ro.weednet.contactssync.iap.IabResult.java
ro.weednet.contactssync.iap.Inventory.java
ro.weednet.contactssync.iap.Purchase.java
ro.weednet.contactssync.iap.Security.java
ro.weednet.contactssync.iap.SkuDetails.java
ro.weednet.contactssync.notifier.NotifierService.java
ro.weednet.contactssync.platform.BatchOperation.java
ro.weednet.contactssync.platform.ContactManager.java
ro.weednet.contactssync.platform.ContactOperations.java
ro.weednet.contactssync.platform.SyncAdapterColumns.java
ro.weednet.contactssync.preferences.GlobalFragment.java
ro.weednet.contactssync.syncadapter.SyncAdapter.java
ro.weednet.contactssync.syncadapter.SyncService.java