Android Open Source - Divide_And_Conquer_Card_Shuffler Billing Service






From Project

Back to project page Divide_And_Conquer_Card_Shuffler.

License

The source code is released under:

MIT License

If you think the Android project Divide_And_Conquer_Card_Shuffler 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) 2010 The Android Open Source Project
 */*  w  w w.  j  a  va2  s  .  c om*/
 * 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 com.andrewkeeton.divide.and.conquer.card.shuffler.billing;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;

import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

import com.andrewkeeton.divide.and.conquer.card.shuffler.billing.Consts.PurchaseState;
import com.andrewkeeton.divide.and.conquer.card.shuffler.billing.Consts.ResponseCode;
import com.andrewkeeton.divide.and.conquer.card.shuffler.billing.Security.VerifiedPurchase;
import com.android.vending.billing.IMarketBillingService;

/**
 * This class sends messages to Android Market on behalf of the application by
 * connecting (binding) to the MarketBillingService. The application creates an
 * instance of this class and invokes billing requests through this service.
 * 
 * The {@link BillingReceiver} class starts this service to process commands
 * that it receives from Android Market.
 * 
 * You should modify and obfuscate this code before using it.
 */
public class BillingService extends Service implements ServiceConnection {
  private static final String TAG = "BillingService";

  /** The service connection to the remote MarketBillingService. */
  private static IMarketBillingService mService;

  /**
   * The list of requests that are pending while we are waiting for the
   * connection to the MarketBillingService to be established.
   */
  private static LinkedList<BillingRequest> mPendingRequests = new LinkedList<BillingRequest>();

  /**
   * The list of requests that we have sent to Android Market but for which we
   * have not yet received a response code. The HashMap is indexed by the
   * request Id that each request receives when it executes.
   */
  private static HashMap<Long, BillingRequest> mSentRequests = new HashMap<Long, BillingRequest>();

  /**
   * The base class for all requests that use the MarketBillingService. Each
   * derived class overrides the run() method to call the appropriate service
   * interface. If we are already connected to the MarketBillingService, then
   * we call the run() method directly. Otherwise, we bind to the service and
   * save the request on a queue to be run later when the service is
   * connected.
   */
  abstract class BillingRequest {
    private final int mStartId;
    protected long mRequestId;

    public BillingRequest(int startId) {
      mStartId = startId;
    }

    public int getStartId() {
      return mStartId;
    }

    /**
     * Run the request, starting the connection if necessary.
     * 
     * @return true if the request was executed or queued; false if there
     *         was an error starting the connection
     */
    public boolean runRequest() {
      if (runIfConnected()) {
        return true;
      }

      if (bindToMarketBillingService()) {
        // Add a pending request to run when the service is connected.
        mPendingRequests.add(this);
        return true;
      }
      return false;
    }

    /**
     * Try running the request directly if the service is already connected.
     * 
     * @return true if the request ran successfully; false if the service is
     *         not connected or there was an error when trying to use it
     */
    public boolean runIfConnected() {
      if (Consts.DEBUG) {
        Log.d(TAG, getClass().getSimpleName());
      }
      if (mService != null) {
        try {
          mRequestId = run();
          if (Consts.DEBUG) {
            Log.d(TAG, "request id: " + mRequestId);
          }
          if (mRequestId >= 0) {
            mSentRequests.put(mRequestId, this);
          }
          return true;
        } catch (RemoteException e) {
          onRemoteException(e);
        }
      }
      return false;
    }

    /**
     * Called when a remote exception occurs while trying to execute the
     * {@link #run()} method. The derived class can override this to execute
     * exception-handling code.
     * 
     * @param e
     *            the exception
     */
    protected void onRemoteException(RemoteException e) {
      Log.w(TAG, "remote billing service crashed");
      mService = null;
    }

    /**
     * The derived class must implement this method.
     * 
     * @throws RemoteException
     */
    abstract protected long run() throws RemoteException;

    /**
     * This is called when Android Market sends a response code for this
     * request.
     * 
     * @param responseCode
     *            the response code
     */
    protected void responseCodeReceived(ResponseCode responseCode) {
    }

    protected Bundle makeRequestBundle(String method) {
      Bundle request = new Bundle();
      request.putString(Consts.BILLING_REQUEST_METHOD, method);
      request.putInt(Consts.BILLING_REQUEST_API_VERSION, 2);
      request.putString(Consts.BILLING_REQUEST_PACKAGE_NAME,
          getPackageName());
      return request;
    }

    protected void logResponseCode(String method, Bundle response) {
      ResponseCode responseCode = ResponseCode.valueOf(response
          .getInt(Consts.BILLING_RESPONSE_RESPONSE_CODE));
      if (Consts.DEBUG) {
        Log.e(TAG, method + " received " + responseCode.toString());
      }
    }
  }

  /**
   * Wrapper class that checks if in-app billing is supported.
   * 
   * Note: Support for subscriptions implies support for one-time purchases.
   * However, the opposite is not true.
   * 
   * Developers may want to perform two checks if both one-time and
   * subscription products are available.
   */
  class CheckBillingSupported extends BillingRequest {
    public String mProductType = null;

    /**
     * Legacy contrustor
     * 
     * This constructor is provided for legacy purposes. Assumes the calling
     * application will not be using any features not present in API v1,
     * such as subscriptions.
     */
    @Deprecated
    public CheckBillingSupported() {
      // This object is never created as a side effect of starting this
      // service so we pass -1 as the startId to indicate that we should
      // not stop this service after executing this request.
      super(-1);
    }

    /**
     * Constructor
     * 
     * Note: Support for subscriptions implies support for one-time
     * purchases. However, the opposite is not true.
     * 
     * Developers may want to perform two checks if both one-time and
     * subscription products are available.
     * 
     * @pram itemType Either Consts.ITEM_TYPE_INAPP or
     *       Consts.ITEM_TYPE_SUBSCRIPTION, indicating the type of item
     *       support is being checked for.
     */
    public CheckBillingSupported(String itemType) {
      super(-1);
      mProductType = itemType;
    }

    @Override
    protected long run() throws RemoteException {
      Bundle request = makeRequestBundle("CHECK_BILLING_SUPPORTED");
      if (mProductType != null) {
        request.putString(Consts.BILLING_REQUEST_ITEM_TYPE,
            mProductType);
      }
      Bundle response = mService.sendBillingRequest(request);
      int responseCode = response
          .getInt(Consts.BILLING_RESPONSE_RESPONSE_CODE);
      if (Consts.DEBUG) {
        Log.i(TAG, "CheckBillingSupported response code: "
            + ResponseCode.valueOf(responseCode));
      }
      boolean billingSupported = (responseCode == ResponseCode.RESULT_OK
          .ordinal());
      ResponseHandler.checkBillingSupportedResponse(billingSupported,
          mProductType);
      return Consts.BILLING_RESPONSE_INVALID_REQUEST_ID;
    }
  }

  /**
   * Wrapper class that requests a purchase.
   */
  public class RequestPurchase extends BillingRequest {
    public final String mProductId;
    public final String mDeveloperPayload;
    public final String mProductType;

    /**
     * Legacy constructor
     * 
     * @param itemId
     *            The ID of the item to be purchased. Will be assumed to be
     *            a one-time purchase.
     */
    @Deprecated
    public RequestPurchase(String itemId) {
      this(itemId, null, null);
    }

    /**
     * Legacy constructor
     * 
     * @param itemId
     *            The ID of the item to be purchased. Will be assumed to be
     *            a one-time purchase.
     * @param developerPayload
     *            Optional data.
     */
    @Deprecated
    public RequestPurchase(String itemId, String developerPayload) {
      this(itemId, null, developerPayload);
    }

    /**
     * Constructor
     * 
     * @param itemId
     *            The ID of the item to be purchased. Will be assumed to be
     *            a one-time purchase.
     * @param itemType
     *            Either Consts.ITEM_TYPE_INAPP or
     *            Consts.ITEM_TYPE_SUBSCRIPTION, indicating the type of item
     *            type support is being checked for.
     * @param developerPayload
     *            Optional data.
     */
    public RequestPurchase(String itemId, String itemType,
        String developerPayload) {
      // This object is never created as a side effect of starting this
      // service so we pass -1 as the startId to indicate that we should
      // not stop this service after executing this request.
      super(-1);
      mProductId = itemId;
      mDeveloperPayload = developerPayload;
      mProductType = itemType;
    }

    @Override
    protected long run() throws RemoteException {
      Bundle request = makeRequestBundle("REQUEST_PURCHASE");
      request.putString(Consts.BILLING_REQUEST_ITEM_ID, mProductId);
      request.putString(Consts.BILLING_REQUEST_ITEM_TYPE, mProductType);
      // Note that the developer payload is optional.
      if (mDeveloperPayload != null) {
        request.putString(Consts.BILLING_REQUEST_DEVELOPER_PAYLOAD,
            mDeveloperPayload);
      }
      Bundle response = mService.sendBillingRequest(request);
      PendingIntent pendingIntent = response
          .getParcelable(Consts.BILLING_RESPONSE_PURCHASE_INTENT);
      if (pendingIntent == null) {
        Log.e(TAG, "Error with requestPurchase");
        return Consts.BILLING_RESPONSE_INVALID_REQUEST_ID;
      }

      Intent intent = new Intent();
      ResponseHandler.buyPageIntentResponse(pendingIntent, intent);
      return response.getLong(Consts.BILLING_RESPONSE_REQUEST_ID,
          Consts.BILLING_RESPONSE_INVALID_REQUEST_ID);
    }

    @Override
    protected void responseCodeReceived(ResponseCode responseCode) {
      ResponseHandler.responseCodeReceived(BillingService.this, this,
          responseCode);
    }
  }

  /**
   * Wrapper class that confirms a list of notifications to the server.
   */
  class ConfirmNotifications extends BillingRequest {
    final String[] mNotifyIds;

    public ConfirmNotifications(int startId, String[] notifyIds) {
      super(startId);
      mNotifyIds = notifyIds;
    }

    @Override
    protected long run() throws RemoteException {
      Bundle request = makeRequestBundle("CONFIRM_NOTIFICATIONS");
      request.putStringArray(Consts.BILLING_REQUEST_NOTIFY_IDS,
          mNotifyIds);
      Bundle response = mService.sendBillingRequest(request);
      logResponseCode("confirmNotifications", response);
      return response.getLong(Consts.BILLING_RESPONSE_REQUEST_ID,
          Consts.BILLING_RESPONSE_INVALID_REQUEST_ID);
    }
  }

  /**
   * Wrapper class that sends a GET_PURCHASE_INFORMATION message to the
   * server.
   */
  class GetPurchaseInformation extends BillingRequest {
    long mNonce;
    final String[] mNotifyIds;

    public GetPurchaseInformation(int startId, String[] notifyIds) {
      super(startId);
      mNotifyIds = notifyIds;
    }

    @Override
    protected long run() throws RemoteException {
      mNonce = Security.generateNonce();

      Bundle request = makeRequestBundle("GET_PURCHASE_INFORMATION");
      request.putLong(Consts.BILLING_REQUEST_NONCE, mNonce);
      request.putStringArray(Consts.BILLING_REQUEST_NOTIFY_IDS,
          mNotifyIds);
      Bundle response = mService.sendBillingRequest(request);
      logResponseCode("getPurchaseInformation", response);
      return response.getLong(Consts.BILLING_RESPONSE_REQUEST_ID,
          Consts.BILLING_RESPONSE_INVALID_REQUEST_ID);
    }

    @Override
    protected void onRemoteException(RemoteException e) {
      super.onRemoteException(e);
      Security.removeNonce(mNonce);
    }
  }

  /**
   * Wrapper class that sends a RESTORE_TRANSACTIONS message to the server.
   */
  public class RestoreTransactions extends BillingRequest {
    long mNonce;

    public RestoreTransactions() {
      // This object is never created as a side effect of starting this
      // service so we pass -1 as the startId to indicate that we should
      // not stop this service after executing this request.
      super(-1);
    }

    @Override
    protected long run() throws RemoteException {
      mNonce = Security.generateNonce();

      Bundle request = makeRequestBundle("RESTORE_TRANSACTIONS");
      request.putLong(Consts.BILLING_REQUEST_NONCE, mNonce);
      Bundle response = mService.sendBillingRequest(request);
      logResponseCode("restoreTransactions", response);
      return response.getLong(Consts.BILLING_RESPONSE_REQUEST_ID,
          Consts.BILLING_RESPONSE_INVALID_REQUEST_ID);
    }

    @Override
    protected void onRemoteException(RemoteException e) {
      super.onRemoteException(e);
      Security.removeNonce(mNonce);
    }

    @Override
    protected void responseCodeReceived(ResponseCode responseCode) {
      ResponseHandler.responseCodeReceived(BillingService.this, this,
          responseCode);
    }
  }

  public BillingService() {
    super();
  }

  public void setContext(Context context) {
    attachBaseContext(context);
  }

  /**
   * We don't support binding to this service, only starting the service.
   */
  @Override
  public IBinder onBind(Intent intent) {
    return null;
  }

  @Override
  public void onStart(Intent intent, int startId) {
    handleCommand(intent, startId);
  }

  /**
   * The {@link BillingReceiver} sends messages to this service using intents.
   * Each intent has an action and some extra arguments specific to that
   * action.
   * 
   * @param intent
   *            the intent containing one of the supported actions
   * @param startId
   *            an identifier for the invocation instance of this service
   */
  public void handleCommand(Intent intent, int startId) {
    String action = intent.getAction();
    if (Consts.DEBUG) {
      Log.i(TAG, "handleCommand() action: " + action);
    }
    if (Consts.ACTION_CONFIRM_NOTIFICATION.equals(action)) {
      String[] notifyIds = intent
          .getStringArrayExtra(Consts.NOTIFICATION_ID);
      confirmNotifications(startId, notifyIds);
    } else if (Consts.ACTION_GET_PURCHASE_INFORMATION.equals(action)) {
      String notifyId = intent.getStringExtra(Consts.NOTIFICATION_ID);
      getPurchaseInformation(startId, new String[] { notifyId });
    } else if (Consts.ACTION_PURCHASE_STATE_CHANGED.equals(action)) {
      String signedData = intent.getStringExtra(Consts.INAPP_SIGNED_DATA);
      String signature = intent.getStringExtra(Consts.INAPP_SIGNATURE);
      purchaseStateChanged(startId, signedData, signature);
    } else if (Consts.ACTION_RESPONSE_CODE.equals(action)) {
      long requestId = intent.getLongExtra(Consts.INAPP_REQUEST_ID, -1);
      int responseCodeIndex = intent.getIntExtra(
          Consts.INAPP_RESPONSE_CODE,
          ResponseCode.RESULT_ERROR.ordinal());
      ResponseCode responseCode = ResponseCode.valueOf(responseCodeIndex);
      checkResponseCode(requestId, responseCode);
    }
  }

  /**
   * Binds to the MarketBillingService and returns true if the bind succeeded.
   * 
   * @return true if the bind succeeded; false otherwise
   */
  private boolean bindToMarketBillingService() {
    try {
      if (Consts.DEBUG) {
        Log.i(TAG, "binding to Market billing service");
      }
      boolean bindResult = bindService(new Intent(
          Consts.MARKET_BILLING_SERVICE_ACTION), this, // ServiceConnection.
          Context.BIND_AUTO_CREATE);

      if (bindResult) {
        return true;
      } else {
        Log.e(TAG, "Could not bind to service.");
      }
    } catch (SecurityException e) {
      Log.e(TAG, "Security exception: " + e);
    }
    return false;
  }

  /**
   * Checks if in-app billing is supported. Assumes this is a one-time
   * purchase.
   * 
   * @return true if supported; false otherwise
   */
  @Deprecated
  public boolean checkBillingSupported() {
    return new CheckBillingSupported().runRequest();
  }

  /**
   * Checks if in-app billing is supported.
   * 
   * @pram itemType Either Consts.ITEM_TYPE_INAPP or
   *       Consts.ITEM_TYPE_SUBSCRIPTION, indicating the type of item support
   *       is being checked for.
   * @return true if supported; false otherwise
   */
  public boolean checkBillingSupported(String itemType) {
    return new CheckBillingSupported(itemType).runRequest();
  }

  /**
   * Requests that the given item be offered to the user for purchase. When
   * the purchase succeeds (or is canceled) the {@link BillingReceiver}
   * receives an intent with the action {@link Consts#ACTION_NOTIFY}. Returns
   * false if there was an error trying to connect to Android Market.
   * 
   * @param productId
   *            an identifier for the item being offered for purchase
   * @param itemType
   *            Either Consts.ITEM_TYPE_INAPP or
   *            Consts.ITEM_TYPE_SUBSCRIPTION, indicating the type of item
   *            type support is being checked for.
   * @param developerPayload
   *            a payload that is associated with a given purchase, if null,
   *            no payload is sent
   * @return false if there was an error connecting to Android Market
   */
  public boolean requestPurchase(String productId, String itemType,
      String developerPayload) {
    return new RequestPurchase(productId, itemType, developerPayload)
        .runRequest();
  }

  /**
   * Requests transaction information for all managed items. Call this only
   * when the application is first installed or after a database wipe. Do NOT
   * call this every time the application starts up.
   * 
   * @return false if there was an error connecting to Android Market
   */
  public boolean restoreTransactions() {
    return new RestoreTransactions().runRequest();
  }

  /**
   * Confirms receipt of a purchase state change. Each {@code notifyId} is an
   * opaque identifier that came from the server. This method sends those
   * identifiers back to the MarketBillingService, which ACKs them to the
   * server. Returns false if there was an error trying to connect to the
   * MarketBillingService.
   * 
   * @param startId
   *            an identifier for the invocation instance of this service
   * @param notifyIds
   *            a list of opaque identifiers associated with purchase state
   *            changes.
   * @return false if there was an error connecting to Market
   */
  private boolean confirmNotifications(int startId, String[] notifyIds) {
    return new ConfirmNotifications(startId, notifyIds).runRequest();
  }

  /**
   * Gets the purchase information. This message includes a list of
   * notification IDs sent to us by Android Market, which we include in our
   * request. The server responds with the purchase information, encoded as a
   * JSON string, and sends that to the {@link BillingReceiver} in an intent
   * with the action {@link Consts#ACTION_PURCHASE_STATE_CHANGED}. Returns
   * false if there was an error trying to connect to the
   * MarketBillingService.
   * 
   * @param startId
   *            an identifier for the invocation instance of this service
   * @param notifyIds
   *            a list of opaque identifiers associated with purchase state
   *            changes
   * @return false if there was an error connecting to Android Market
   */
  private boolean getPurchaseInformation(int startId, String[] notifyIds) {
    return new GetPurchaseInformation(startId, notifyIds).runRequest();
  }

  /**
   * Verifies that the data was signed with the given signature, and calls
   * {@link ResponseHandler#purchaseResponse(Context, PurchaseState, String, String, long)}
   * for each verified purchase.
   * 
   * @param startId
   *            an identifier for the invocation instance of this service
   * @param signedData
   *            the signed JSON string (signed, not encrypted)
   * @param signature
   *            the signature for the data, signed with the private key
   */
  private void purchaseStateChanged(int startId, String signedData,
      String signature) {
    ArrayList<Security.VerifiedPurchase> purchases;
    purchases = Security.verifyPurchase(signedData, signature);
    if (purchases == null) {
      return;
    }

    ArrayList<String> notifyList = new ArrayList<String>();
    for (VerifiedPurchase vp : purchases) {
      if (vp.notificationId != null) {
        notifyList.add(vp.notificationId);
      }
      ResponseHandler.purchaseResponse(this, vp.purchaseState,
          vp.productId, vp.orderId, vp.purchaseTime,
          vp.developerPayload);
    }
    if (!notifyList.isEmpty()) {
      String[] notifyIds = notifyList.toArray(new String[notifyList
          .size()]);
      confirmNotifications(startId, notifyIds);
    }
  }

  /**
   * This is called when we receive a response code from Android Market for a
   * request that we made. This is used for reporting various errors and for
   * acknowledging that an order was sent to the server. This is NOT used for
   * any purchase state changes. All purchase state changes are received in
   * the {@link BillingReceiver} and passed to this service, where they are
   * handled in {@link #purchaseStateChanged(int, String, String)}.
   * 
   * @param requestId
   *            a number that identifies a request, assigned at the time the
   *            request was made to Android Market
   * @param responseCode
   *            a response code from Android Market to indicate the state of
   *            the request
   */
  private void checkResponseCode(long requestId, ResponseCode responseCode) {
    BillingRequest request = mSentRequests.get(requestId);
    if (request != null) {
      if (Consts.DEBUG) {
        Log.d(TAG, request.getClass().getSimpleName() + ": "
            + responseCode);
      }
      request.responseCodeReceived(responseCode);
    }
    mSentRequests.remove(requestId);
  }

  /**
   * Runs any pending requests that are waiting for a connection to the
   * service to be established. This runs in the main UI thread.
   */
  private void runPendingRequests() {
    int maxStartId = -1;
    BillingRequest request;
    while ((request = mPendingRequests.peek()) != null) {
      if (request.runIfConnected()) {
        // Remove the request
        mPendingRequests.remove();

        // Remember the largest startId, which is the most recent
        // request to start this service.
        if (maxStartId < request.getStartId()) {
          maxStartId = request.getStartId();
        }
      } else {
        // The service crashed, so restart it. Note that this leaves
        // the current request on the queue.
        bindToMarketBillingService();
        return;
      }
    }

    // If we get here then all the requests ran successfully. If maxStartId
    // is not -1, then one of the requests started the service, so we can
    // stop it now.
    if (maxStartId >= 0) {
      if (Consts.DEBUG) {
        Log.i(TAG, "stopping service, startId: " + maxStartId);
      }
      stopSelf(maxStartId);
    }
  }

  /**
   * This is called when we are connected to the MarketBillingService. This
   * runs in the main UI thread.
   */
  public void onServiceConnected(ComponentName name, IBinder service) {
    if (Consts.DEBUG) {
      Log.d(TAG, "Billing service connected");
    }
    mService = IMarketBillingService.Stub.asInterface(service);
    runPendingRequests();
  }

  /**
   * This is called when we are disconnected from the MarketBillingService.
   */
  public void onServiceDisconnected(ComponentName name) {
    Log.w(TAG, "Billing service disconnected");
    mService = null;
  }

  /**
   * Unbinds from the MarketBillingService. Call this when the application
   * terminates to avoid leaking a ServiceConnection.
   */
  public void unbind() {
    try {
      unbindService(this);
    } catch (IllegalArgumentException e) {
      // This might happen if the service was disconnected
    }
  }
}




Java Source Code List

com.andrewkeeton.divide.and.conquer.card.shuffler.AutoResizeTextView.java
com.andrewkeeton.divide.and.conquer.card.shuffler.Card.java
com.andrewkeeton.divide.and.conquer.card.shuffler.EstimateTime.java
com.andrewkeeton.divide.and.conquer.card.shuffler.MainActivity.java
com.andrewkeeton.divide.and.conquer.card.shuffler.Move.java
com.andrewkeeton.divide.and.conquer.card.shuffler.PileOfCards.java
com.andrewkeeton.divide.and.conquer.card.shuffler.SettingsActivity.java
com.andrewkeeton.divide.and.conquer.card.shuffler.SetupFragment.java
com.andrewkeeton.divide.and.conquer.card.shuffler.ShufflerFragment.java
com.andrewkeeton.divide.and.conquer.card.shuffler.billing.BillingReceiver.java
com.andrewkeeton.divide.and.conquer.card.shuffler.billing.BillingService.java
com.andrewkeeton.divide.and.conquer.card.shuffler.billing.Consts.java
com.andrewkeeton.divide.and.conquer.card.shuffler.billing.PurchaseDatabase.java
com.andrewkeeton.divide.and.conquer.card.shuffler.billing.PurchaseObserver.java
com.andrewkeeton.divide.and.conquer.card.shuffler.billing.ResponseHandler.java
com.andrewkeeton.divide.and.conquer.card.shuffler.billing.Security.java
com.andrewkeeton.divide.and.conquer.card.shuffler.billing.util.Base64DecoderException.java
com.andrewkeeton.divide.and.conquer.card.shuffler.billing.util.Base64.java