diff --git a/.gitignore b/.gitignore index 496ee2c..a6c8a51 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -.DS_Store \ No newline at end of file +.DS_Store +.metadata \ No newline at end of file diff --git a/AndroidBillingLibrary/.settings/org.eclipse.jdt.core.prefs b/AndroidBillingLibrary/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..f77b31c --- /dev/null +++ b/AndroidBillingLibrary/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.source=1.5 diff --git a/AndroidBillingLibrary/res/values/strings.xml b/AndroidBillingLibrary/res/values/strings.xml index e1ab33c..75c75a3 100644 --- a/AndroidBillingLibrary/res/values/strings.xml +++ b/AndroidBillingLibrary/res/values/strings.xml @@ -1,5 +1,4 @@ - Hello World! AndroidBillingLibraryTest diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java index 5bb18ff..cbf179e 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java @@ -67,7 +67,8 @@ public interface IConfiguration { public String getPublicKey(); } - private static BillingStatus status = BillingStatus.UNKNOWN; + private static BillingStatus billingStatus = BillingStatus.UNKNOWN; + private static BillingStatus subscriptionStatus = BillingStatus.UNKNOWN; private static Set automaticConfirmations = new HashSet(); private static IConfiguration configuration = null; @@ -103,23 +104,61 @@ private static final void addManualConfirmation(String itemId, String notificati } /** - * Returns the billing status. If it is currently unknown, checks the - * billing status asynchronously. Observers will receive a + * Returns the in-app product billing support status, and checks it + * asynchronously if it is currently unknown. Observers will receive a * {@link IBillingObserver#onBillingChecked(boolean)} notification in either * case. + *

+ * In-app product support does not imply subscription support. To check if + * subscriptions are supported, use + * {@link BillingController#checkSubscriptionSupported(Context)}. + *

* * @param context - * @return the current billing status (unknown, supported or unsupported). + * @return the current in-app product billing support status (unknown, + * supported or unsupported). If it is unsupported, subscriptions + * are also unsupported. * @see IBillingObserver#onBillingChecked(boolean) + * @see BillingController#checkSubscriptionSupported(Context) */ public static BillingStatus checkBillingSupported(Context context) { - if (status == BillingStatus.UNKNOWN) { + if (billingStatus == BillingStatus.UNKNOWN) { BillingService.checkBillingSupported(context); } else { - boolean supported = status == BillingStatus.SUPPORTED; + boolean supported = billingStatus == BillingStatus.SUPPORTED; onBillingChecked(supported); } - return status; + return billingStatus; + } + + /** + *

+ * Returns the subscription billing support status, and checks it + * asynchronously if it is currently unknown. Observers will receive a + * {@link IBillingObserver#onSubscriptionChecked(boolean)} notification in + * either case. + *

+ *

+ * No support for subscriptions does not imply that in-app products are also + * unsupported. To check if in-app products are supported, use + * {@link BillingController#checkBillingSupported(Context)}. + *

+ * + * @param context + * @return the current subscription billing status (unknown, supported or + * unsupported). If it is supported, in-app products are also + * supported. + * @see IBillingObserver#onSubscriptionChecked(boolean) + * @see BillingController#checkBillingSupported(Context) + */ + public static BillingStatus checkSubscriptionSupported(Context context) { + if (subscriptionStatus == BillingStatus.UNKNOWN) { + BillingService.checkSubscriptionSupported(context); + } else { + boolean supported = subscriptionStatus == BillingStatus.SUPPORTED; + onSubscriptionChecked(supported); + } + return subscriptionStatus; } /** @@ -167,7 +206,7 @@ public static int countPurchases(Context context, String itemId) { itemId = salt != null ? Security.obfuscate(context, salt, itemId) : itemId; return TransactionManager.countPurchases(context, itemId); } - + protected static void debug(String message) { if (debug) { Log.d(LOG_TAG, message); @@ -235,9 +274,8 @@ public static List getTransactions(Context context, String itemId) /** * Returns true if the specified item has been registered as purchased in - * local memory. Note that if the item was later canceled or refunded this - * will still return true. Also note that the item might have been purchased - * in another installation, but not yet registered in this one. + * local memory, false otherwise. Also note that the item might have been + * purchased in another installation, but not yet registered in this one. * * @param context * @param itemId @@ -291,7 +329,12 @@ static void obfuscate(Context context, Transaction purchase) { * @param supported */ protected static void onBillingChecked(boolean supported) { - status = supported ? BillingStatus.SUPPORTED : BillingStatus.UNSUPPORTED; + billingStatus = supported ? BillingStatus.SUPPORTED : BillingStatus.UNSUPPORTED; + if (billingStatus == BillingStatus.UNSUPPORTED) { // Save us the + // subscription + // check + subscriptionStatus = BillingStatus.UNSUPPORTED; + } for (IBillingObserver o : observers) { o.onBillingChecked(supported); } @@ -340,10 +383,12 @@ protected static void onPurchaseIntent(String itemId, PendingIntent purchaseInte */ protected static void onPurchaseStateChanged(Context context, String signedData, String signature) { debug("Purchase state changed"); - + if (TextUtils.isEmpty(signedData)) { Log.w(LOG_TAG, "Signed data is empty"); return; + } else { + debug(signedData); } if (!debug) { @@ -391,8 +436,7 @@ protected static void onPurchaseStateChanged(Context context, String signedData, } /** - * Called after a {@link net.robotmedia.billing.BillingRequest} is - * sent. + * Called after a {@link net.robotmedia.billing.BillingRequest} is sent. * * @param requestId * the id the request. @@ -410,8 +454,7 @@ protected static void onRequestSent(long requestId, BillingRequest request) { } /** - * Called after a {@link net.robotmedia.billing.BillingRequest} is - * sent. + * Called after a {@link net.robotmedia.billing.BillingRequest} is sent. * * @param context * @param requestId @@ -431,6 +474,24 @@ protected static void onResponseCode(Context context, long requestId, int respon } } + /** + * Called after the response to a + * {@link net.robotmedia.billing.request.CheckSubscriptionSupported} request + * is received. + * + * @param supported + */ + protected static void onSubscriptionChecked(boolean supported) { + subscriptionStatus = supported ? BillingStatus.SUPPORTED : BillingStatus.UNSUPPORTED; + if (subscriptionStatus == BillingStatus.SUPPORTED) { // Save us the + // billing check + billingStatus = BillingStatus.SUPPORTED; + } + for (IBillingObserver o : observers) { + o.onSubscriptionChecked(supported); + } + } + protected static void onTransactionsRestored() { for (IBillingObserver o : observers) { o.onTransactionsRestored(); @@ -478,19 +539,65 @@ public static boolean registerObserver(IBillingObserver observer) { /** * Requests the purchase of the specified item. The transaction will not be * confirmed automatically. + *

+ * For subscriptions, use {@link #requestSubscription(Context, String)} + * instead. + *

* * @param context * @param itemId * id of the item to be purchased. - * @see #requestPurchase(Context, String, boolean) + * @see #requestPurchase(Context, String, boolean, String) */ public static void requestPurchase(Context context, String itemId) { - requestPurchase(context, itemId, false); + requestPurchase(context, itemId, false, null); } /** + *

* Requests the purchase of the specified item with optional automatic * confirmation. + *

+ *

+ * For subscriptions, use + * {@link #requestSubscription(Context, String, boolean, String)} instead. + *

+ * + * @param context + * @param itemId + * id of the item to be purchased. + * @param confirm + * if true, the transaction will be confirmed automatically. If + * false, the transaction will have to be confirmed with a call + * to {@link #confirmNotifications(Context, String)}. + * @param developerPayload + * a developer-specified string that contains supplemental + * information about the order. + * @see IBillingObserver#onPurchaseIntent(String, PendingIntent) + */ + public static void requestPurchase(Context context, String itemId, boolean confirm, String developerPayload) { + if (confirm) { + automaticConfirmations.add(itemId); + } + BillingService.requestPurchase(context, itemId, developerPayload); + } + + /** + * Requests the purchase of the specified subscription item. The transaction + * will not be confirmed automatically. + * + * @param context + * @param itemId + * id of the item to be purchased. + * @see #requestSubscription(Context, String, boolean, String) + */ + public static void requestSubscription(Context context, String itemId) { + requestSubscription(context, itemId, false, null); + } + + /** + * Requests the purchase of the specified subscription item with optional + * automatic confirmation. * * @param context * @param itemId @@ -499,13 +606,16 @@ public static void requestPurchase(Context context, String itemId) { * if true, the transaction will be confirmed automatically. If * false, the transaction will have to be confirmed with a call * to {@link #confirmNotifications(Context, String)}. + * @param developerPayload + * a developer-specified string that contains supplemental + * information about the order. * @see IBillingObserver#onPurchaseIntent(String, PendingIntent) */ - public static void requestPurchase(Context context, String itemId, boolean confirm) { + public static void requestSubscription(Context context, String itemId, boolean confirm, String developerPayload) { if (confirm) { automaticConfirmations.add(itemId); } - BillingService.requestPurchase(context, itemId, null); + BillingService.requestSubscription(context, itemId, developerPayload); } /** diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingRequest.java b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingRequest.java index de69c2d..8362559 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingRequest.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingRequest.java @@ -23,7 +23,7 @@ import com.android.vending.billing.IMarketBillingService; public abstract class BillingRequest { - + public static class CheckBillingSupported extends BillingRequest { public CheckBillingSupported(String packageName, int startId) { @@ -32,7 +32,7 @@ public CheckBillingSupported(String packageName, int startId) { @Override public String getRequestType() { - return "CHECK_BILLING_SUPPORTED"; + return REQUEST_TYPE_CHECK_BILLING_SUPPORTED; } @Override @@ -42,6 +42,36 @@ protected void processOkResponse(Bundle response) { } } + + public static class CheckSubscriptionSupported extends BillingRequest { + + public CheckSubscriptionSupported(String packageName, int startId) { + super(packageName, startId); + } + + @Override + protected int getAPIVersion() { + return 2; + }; + + @Override + public String getRequestType() { + return REQUEST_TYPE_CHECK_BILLING_SUPPORTED; + } + + @Override + protected void processOkResponse(Bundle response) { + final boolean supported = this.isSuccess(); + BillingController.onSubscriptionChecked(supported); + } + + @Override + protected void addParams(Bundle request) { + request.putString(KEY_ITEM_TYPE, ITEM_TYPE_SUBSCRIPTION); + } + + } + public static class ConfirmNotifications extends BillingRequest { private String[] notifyIds; @@ -88,6 +118,7 @@ public String getRequestType() { @Override public boolean hasNonce() { return true; } } + public static class RequestPurchase extends BillingRequest { private String itemId; @@ -127,9 +158,27 @@ protected void processOkResponse(Bundle response) { final PendingIntent purchaseIntent = response.getParcelable(KEY_PURCHASE_INTENT); BillingController.onPurchaseIntent(itemId, purchaseIntent); } + + } + + public static class RequestSubscription extends RequestPurchase { + public RequestSubscription(String packageName, int startId, String itemId, String developerPayload) { + super(packageName, startId, itemId, developerPayload); + } + + @Override + protected void addParams(Bundle request) { + super.addParams(request); + request.putString(KEY_ITEM_TYPE, ITEM_TYPE_SUBSCRIPTION); + } + @Override + protected int getAPIVersion() { + return 2; + } } + public static enum ResponseCode { RESULT_OK, // 0 RESULT_USER_CANCELED, // 1 @@ -174,15 +223,16 @@ public void onResponseCode(ResponseCode response) { } } + + public static final String ITEM_TYPE_SUBSCRIPTION = "subs"; + private static final String KEY_API_VERSION = "API_VERSION"; private static final String KEY_BILLING_REQUEST = "BILLING_REQUEST"; - - private static final String KEY_API_VERSION = "API_VERSION"; - private static final String KEY_PACKAGE_NAME = "PACKAGE_NAME"; - private static final String KEY_RESPONSE_CODE = "RESPONSE_CODE"; - - protected static final String KEY_REQUEST_ID = "REQUEST_ID"; - + private static final String KEY_ITEM_TYPE = "ITEM_TYPE"; private static final String KEY_NONCE = "NONCE"; + private static final String KEY_PACKAGE_NAME = "PACKAGE_NAME"; + protected static final String KEY_REQUEST_ID = "REQUEST_ID"; + private static final String KEY_RESPONSE_CODE = "RESPONSE_CODE"; + private static final String REQUEST_TYPE_CHECK_BILLING_SUPPORTED = "CHECK_BILLING_SUPPORTED"; public static final long IGNORE_REQUEST_ID = -1; private String packageName; @@ -198,6 +248,10 @@ public BillingRequest(String packageName,int startId) { protected void addParams(Bundle request) { // Do nothing by default } + + protected int getAPIVersion() { + return 1; + } public long getNonce() { return nonce; @@ -216,7 +270,7 @@ public boolean isSuccess() { protected Bundle makeRequestBundle() { final Bundle request = new Bundle(); request.putString(KEY_BILLING_REQUEST, getRequestType()); - request.putInt(KEY_API_VERSION, 1); + request.putInt(KEY_API_VERSION, getAPIVersion()); request.putString(KEY_PACKAGE_NAME, packageName); if (hasNonce()) { request.putLong(KEY_NONCE, nonce); diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java index 348e429..a37a1ae 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java @@ -35,12 +35,11 @@ public class BillingService extends Service implements ServiceConnection { private static enum Action { - CHECK_BILLING_SUPPORTED, CONFIRM_NOTIFICATIONS, GET_PURCHASE_INFORMATION, REQUEST_PURCHASE, RESTORE_TRANSACTIONS, + CHECK_BILLING_SUPPORTED, CHECK_SUBSCRIPTION_SUPPORTED, CONFIRM_NOTIFICATIONS, GET_PURCHASE_INFORMATION, REQUEST_PURCHASE, REQUEST_SUBSCRIPTION, RESTORE_TRANSACTIONS } private static final String ACTION_MARKET_BILLING_SERVICE = "com.android.vending.billing.MarketBillingService.BIND"; private static final String EXTRA_DEVELOPER_PAYLOAD = "DEVELOPER_PAYLOAD"; - private static final String EXTRA_ITEM_ID = "ITEM_ID"; private static final String EXTRA_NONCE = "EXTRA_NONCE"; private static final String EXTRA_NOTIFY_IDS = "NOTIFY_IDS"; @@ -53,6 +52,11 @@ public static void checkBillingSupported(Context context) { context.startService(intent); } + public static void checkSubscriptionSupported(Context context) { + final Intent intent = createIntent(context, Action.CHECK_SUBSCRIPTION_SUPPORTED); + context.startService(intent); + } + public static void confirmNotifications(Context context, String[] notifyIds) { final Intent intent = createIntent(context, Action.CONFIRM_NOTIFICATIONS); intent.putExtra(EXTRA_NOTIFY_IDS, notifyIds); @@ -83,6 +87,13 @@ public static void requestPurchase(Context context, String itemId, String develo intent.putExtra(EXTRA_DEVELOPER_PAYLOAD, developerPayload); context.startService(intent); } + + public static void requestSubscription(Context context, String itemId, String developerPayload) { + final Intent intent = createIntent(context, Action.REQUEST_SUBSCRIPTION); + intent.putExtra(EXTRA_ITEM_ID, itemId); + intent.putExtra(EXTRA_DEVELOPER_PAYLOAD, developerPayload); + context.startService(intent); + } public static void restoreTransations(Context context, long nonce) { final Intent intent = createIntent(context, Action.RESTORE_TRANSACTIONS); @@ -107,6 +118,12 @@ private void checkBillingSupported(int startId) { final CheckBillingSupported request = new CheckBillingSupported(packageName, startId); runRequestOrQueue(request); } + + private void checkSubscriptionSupported(int startId) { + final String packageName = getPackageName(); + final CheckSubscriptionSupported request = new CheckSubscriptionSupported(packageName, startId); + runRequestOrQueue(request); + } private void confirmNotifications(Intent intent, int startId) { final String packageName = getPackageName(); @@ -141,13 +158,11 @@ public IBinder onBind(Intent intent) { return null; } - @Override public void onServiceConnected(ComponentName name, IBinder service) { mService = IMarketBillingService.Stub.asInterface(service); runPendingRequests(); } - @Override public void onServiceDisconnected(ComponentName name) { mService = null; } @@ -175,9 +190,15 @@ private void handleCommand(Intent intent, int startId) { case CHECK_BILLING_SUPPORTED: checkBillingSupported(startId); break; + case CHECK_SUBSCRIPTION_SUPPORTED: + checkSubscriptionSupported(startId); + break; case REQUEST_PURCHASE: requestPurchase(intent, startId); break; + case REQUEST_SUBSCRIPTION: + requestSubscription(intent, startId); + break; case GET_PURCHASE_INFORMATION: getPurchaseInformation(intent, startId); break; @@ -196,6 +217,14 @@ private void requestPurchase(Intent intent, int startId) { final RequestPurchase request = new RequestPurchase(packageName, startId, itemId, developerPayload); runRequestOrQueue(request); } + + private void requestSubscription(Intent intent, int startId) { + final String packageName = getPackageName(); + final String itemId = intent.getStringExtra(EXTRA_ITEM_ID); + final String developerPayload = intent.getStringExtra(EXTRA_DEVELOPER_PAYLOAD); + final RequestPurchase request = new RequestSubscription(packageName, startId, itemId, developerPayload); + runRequestOrQueue(request); + } private void restoreTransactions(Intent intent, int startId) { final String packageName = getPackageName(); diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/IBillingObserver.java b/AndroidBillingLibrary/src/net/robotmedia/billing/IBillingObserver.java index c1c3ae9..59f3ab4 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/IBillingObserver.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/IBillingObserver.java @@ -22,14 +22,26 @@ public interface IBillingObserver { /** - * Called only once after determining if in-app billing is supported or not. + * Called after checking if in-app product billing is supported or not. * * @param supported - * if true, in-app billing is supported. Otherwise, it isn't. + * if true, in-app product billing is supported. If false, in-app + * product billing is not supported, and neither is subscription + * billing. * @see BillingController#checkBillingSupported(android.content.Context) */ public void onBillingChecked(boolean supported); + /** + * Called after checking if subscription billing is supported or not. + * + * @param supported + * if true, subscription billing is supported, and also is in-app + * product billing. Otherwise, subscription billing is not + * supported. + */ + public void onSubscriptionChecked(boolean supported); + /** * Called after requesting the purchase of the specified item. * diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/helper/AbstractBillingActivity.java b/AndroidBillingLibrary/src/net/robotmedia/billing/helper/AbstractBillingActivity.java index c8e978c..4415c05 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/helper/AbstractBillingActivity.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/helper/AbstractBillingActivity.java @@ -26,19 +26,54 @@ public abstract class AbstractBillingActivity extends Activity implements Billin protected AbstractBillingObserver mBillingObserver; /** - * Returns the billing status. If it's currently unknown, requests to check - * if billing is supported and - * {@link AbstractBillingActivity#onBillingChecked(boolean)} should be - * called later with the result. + *

+ * Returns the in-app product billing support status, and checks it + * asynchronously if it is currently unknown. + * {@link AbstractBillingActivity#onBillingChecked(boolean)} will be called + * eventually with the result. + *

+ *

+ * In-app product support does not imply subscription support. To check if + * subscriptions are supported, use + * {@link AbstractBillingActivity#checkSubscriptionSupported()}. + *

* - * @return the current billing status (unknown, supported or unsupported). + * @return the current in-app product billing support status (unknown, + * supported or unsupported). If it is unsupported, subscriptions + * are also unsupported. * @see AbstractBillingActivity#onBillingChecked(boolean) + * @see AbstractBillingActivity#checkSubscriptionSupported() */ public BillingStatus checkBillingSupported() { return BillingController.checkBillingSupported(this); } + /** + *

+ * Returns the subscription billing support status, and checks it + * asynchronously if it is currently unknown. + * {@link AbstractBillingActivity#onSubscriptionChecked(boolean)} will be + * called eventually with the result. + *

+ *

+ * No support for subscriptions does not imply that in-app products are also + * unsupported. To check if subscriptions are supported, use + * {@link AbstractBillingActivity#checkSubscriptionSupported()}. + *

+ * + * @return the current in-app product billing support status (unknown, + * supported or unsupported). If it is unsupported, subscriptions + * are also unsupported. + * @see AbstractBillingActivity#onBillingChecked(boolean) + * @see AbstractBillingActivity#checkSubscriptionSupported() + */ + public BillingStatus checkSubscriptionSupported() { + return BillingController.checkSubscriptionSupported(this); + } + public abstract void onBillingChecked(boolean supported); + + public abstract void onSubscriptionChecked(boolean supported); @Override protected void onCreate(android.os.Bundle savedInstanceState) { @@ -46,17 +81,18 @@ protected void onCreate(android.os.Bundle savedInstanceState) { mBillingObserver = new AbstractBillingObserver(this) { - @Override public void onBillingChecked(boolean supported) { AbstractBillingActivity.this.onBillingChecked(supported); } + + public void onSubscriptionChecked(boolean supported) { + AbstractBillingActivity.this.onSubscriptionChecked(supported); + } - @Override public void onPurchaseStateChanged(String itemId, PurchaseState state) { AbstractBillingActivity.this.onPurchaseStateChanged(itemId, state); } - @Override public void onRequestPurchaseResponse(String itemId, ResponseCode response) { AbstractBillingActivity.this.onRequestPurchaseResponse(itemId, response); } @@ -99,6 +135,21 @@ public void requestPurchase(String itemId) { BillingController.requestPurchase(this, itemId); } + /** + * Requests the purchase of the specified subscription item. The transaction + * will not be confirmed automatically; such confirmation could be handled + * in {@link AbstractBillingActivity#onPurchaseExecuted(String)}. If + * automatic confirmation is preferred use + * {@link BillingController#requestPurchase(android.content.Context, String, boolean)} + * instead. + * + * @param itemId + * id of the item to be purchased. + */ + public void requestSubscription(String itemId) { + BillingController.requestSubscription(this, itemId); + } + /** * Requests to restore all transactions. */ diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/helper/AbstractBillingFragment.java b/AndroidBillingLibrary/src/net/robotmedia/billing/helper/AbstractBillingFragment.java new file mode 100644 index 0000000..01644d2 --- /dev/null +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/helper/AbstractBillingFragment.java @@ -0,0 +1,147 @@ +package net.robotmedia.billing.helper; + +import net.robotmedia.billing.BillingController; +import net.robotmedia.billing.BillingController.BillingStatus; +import net.robotmedia.billing.BillingRequest.ResponseCode; +import net.robotmedia.billing.model.Transaction.PurchaseState; +import android.annotation.TargetApi; +import android.app.Fragment; + +@TargetApi(11) +public abstract class AbstractBillingFragment extends Fragment implements BillingController.IConfiguration { + + protected AbstractBillingObserver mBillingObserver; + + /** + *

+ * Returns the in-app product billing support status, and checks it + * asynchronously if it is currently unknown. + * {@link AbstractBillingActivity#onBillingChecked(boolean)} will be called + * eventually with the result. + *

+ *

+ * In-app product support does not imply subscription support. To check if + * subscriptions are supported, use + * {@link AbstractBillingActivity#checkSubscriptionSupported()}. + *

+ * + * @return the current in-app product billing support status (unknown, + * supported or unsupported). If it is unsupported, subscriptions + * are also unsupported. + * @see AbstractBillingActivity#onBillingChecked(boolean) + * @see AbstractBillingActivity#checkSubscriptionSupported() + */ + public BillingStatus checkBillingSupported() { + return BillingController.checkBillingSupported(getActivity()); + } + + /** + *

+ * Returns the subscription billing support status, and checks it + * asynchronously if it is currently unknown. + * {@link AbstractBillingActivity#onSubscriptionChecked(boolean)} will be + * called eventually with the result. + *

+ *

+ * No support for subscriptions does not imply that in-app products are also + * unsupported. To check if subscriptions are supported, use + * {@link AbstractBillingActivity#checkSubscriptionSupported()}. + *

+ * + * @return the current in-app product billing support status (unknown, + * supported or unsupported). If it is unsupported, subscriptions + * are also unsupported. + * @see AbstractBillingActivity#onBillingChecked(boolean) + * @see AbstractBillingActivity#checkSubscriptionSupported() + */ + public BillingStatus checkSubscriptionSupported() { + return BillingController.checkSubscriptionSupported(getActivity()); + } + + public abstract void onBillingChecked(boolean supported); + + public abstract void onSubscriptionChecked(boolean supported); + + @Override + public void onCreate(android.os.Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mBillingObserver = new AbstractBillingObserver(getActivity()) { + + public void onBillingChecked(boolean supported) { + AbstractBillingFragment.this.onBillingChecked(supported); + } + + public void onSubscriptionChecked(boolean supported) { + AbstractBillingFragment.this.onSubscriptionChecked(supported); + } + + public void onPurchaseStateChanged(String itemId, PurchaseState state) { + AbstractBillingFragment.this.onPurchaseStateChanged(itemId, state); + } + + public void onRequestPurchaseResponse(String itemId, ResponseCode response) { + AbstractBillingFragment.this.onRequestPurchaseResponse(itemId, response); + } + }; + BillingController.registerObserver(mBillingObserver); + BillingController.setConfiguration(this); // This fragment will provide + // the public key and salt + this.checkBillingSupported(); + if (!mBillingObserver.isTransactionsRestored()) { + BillingController.restoreTransactions(getActivity()); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + BillingController.unregisterObserver(mBillingObserver); // Avoid + // receiving + // notifications + // after destroy + BillingController.setConfiguration(null); + } + + public abstract void onPurchaseStateChanged(String itemId, PurchaseState state);; + + public abstract void onRequestPurchaseResponse(String itemId, ResponseCode response); + + /** + * Requests the purchase of the specified item. The transaction will not be + * confirmed automatically; such confirmation could be handled in + * {@link AbstractBillingActivity#onPurchaseExecuted(String)}. If automatic + * confirmation is preferred use + * {@link BillingController#requestPurchase(android.content.Context, String, boolean)} + * instead. + * + * @param itemId + * id of the item to be purchased. + */ + public void requestPurchase(String itemId) { + BillingController.requestPurchase(getActivity(), itemId); + } + + /** + * Requests the purchase of the specified subscription item. The transaction + * will not be confirmed automatically; such confirmation could be handled + * in {@link AbstractBillingActivity#onPurchaseExecuted(String)}. If + * automatic confirmation is preferred use + * {@link BillingController#requestPurchase(android.content.Context, String, boolean)} + * instead. + * + * @param itemId + * id of the item to be purchased. + */ + public void requestSubscription(String itemId) { + BillingController.requestSubscription(getActivity(), itemId); + } + + /** + * Requests to restore all transactions. + */ + public void restoreTransactions() { + BillingController.restoreTransactions(getActivity()); + } + +} diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/helper/AbstractBillingObserver.java b/AndroidBillingLibrary/src/net/robotmedia/billing/helper/AbstractBillingObserver.java index fdea04d..f982d43 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/helper/AbstractBillingObserver.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/helper/AbstractBillingObserver.java @@ -53,12 +53,10 @@ public boolean isTransactionsRestored() { * @param purchaseIntent * a purchase pending intent for the specified item. */ - @Override public void onPurchaseIntent(String itemId, PendingIntent purchaseIntent) { BillingController.startPurchaseIntent(activity, purchaseIntent, null); } - @Override public void onTransactionsRestored() { final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); final Editor editor = preferences.edit(); diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/model/Transaction.java b/AndroidBillingLibrary/src/net/robotmedia/billing/model/Transaction.java index 297baf8..b3b01e8 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/model/Transaction.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/model/Transaction.java @@ -21,10 +21,14 @@ public class Transaction { public enum PurchaseState { - // Responses to requestPurchase or restoreTransactions. - PURCHASED, // 0: User was charged for the order. - CANCELLED, // 1: The charge failed on the server. - REFUNDED; // 2: User received a refund for the order. + // Responses to requestPurchase or restoreTransactions. + PURCHASED, // 0: User was charged for the order. + CANCELLED, // 1: The charge failed on the server. + REFUNDED, // 2: User received a refund for the order. + EXPIRED; // 3: Sent at the end of a billing cycle to indicate that the + // subscription expired without renewal because of + // non-payment or user-cancellation. Your app does not need + // to grant continued access to the subscription content. // Converts from an ordinal value to the PurchaseState public static PurchaseState valueOf(int index) { diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/security/DefaultSignatureValidator.java b/AndroidBillingLibrary/src/net/robotmedia/billing/security/DefaultSignatureValidator.java index 207b44f..fb9e5db 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/security/DefaultSignatureValidator.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/security/DefaultSignatureValidator.java @@ -89,7 +89,6 @@ protected boolean validate(PublicKey publicKey, String signedData, String signat return false; } - @Override public boolean validate(String signedData, String signature) { final String publicKey; if (configuration == null || TextUtils.isEmpty(publicKey = configuration.getPublicKey())) { diff --git a/AndroidBillingLibraryTest/.settings/org.eclipse.jdt.core.prefs b/AndroidBillingLibraryTest/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..f77b31c --- /dev/null +++ b/AndroidBillingLibraryTest/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.source=1.5 diff --git a/AndroidBillingLibraryTest/AndroidManifest.xml b/AndroidBillingLibraryTest/AndroidManifest.xml index 3ced93f..76cda86 100644 --- a/AndroidBillingLibraryTest/AndroidManifest.xml +++ b/AndroidBillingLibraryTest/AndroidManifest.xml @@ -3,7 +3,7 @@ package="net.robotmedia.billing.test" android:versionCode="1" android:versionName="1.0"> - + diff --git a/AndroidBillingLibraryTest/proguard.cfg b/AndroidBillingLibraryTest/proguard.cfg index 12dd039..4dc32b1 100644 --- a/AndroidBillingLibraryTest/proguard.cfg +++ b/AndroidBillingLibraryTest/proguard.cfg @@ -14,15 +14,15 @@ -keep public class * extends android.preference.Preference -keep public class com.android.vending.licensing.ILicensingService --keepclasseswithmembernames class * { +-keepclasseswithmembers class * { native ; } --keepclasseswithmembernames class * { +-keepclasseswithmembers class * { public (android.content.Context, android.util.AttributeSet); } --keepclasseswithmembernames class * { +-keepclasseswithmembers class * { public (android.content.Context, android.util.AttributeSet, int); } diff --git a/AndroidBillingLibraryTest/res/values/strings.xml b/AndroidBillingLibraryTest/res/values/strings.xml index e1ab33c..75c75a3 100644 --- a/AndroidBillingLibraryTest/res/values/strings.xml +++ b/AndroidBillingLibraryTest/res/values/strings.xml @@ -1,5 +1,4 @@ - Hello World! AndroidBillingLibraryTest diff --git a/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingControllerTest.java b/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingControllerTest.java index 8f7c2a6..4cf7f3c 100644 --- a/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingControllerTest.java +++ b/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingControllerTest.java @@ -19,13 +19,13 @@ import java.util.List; import java.util.Set; +import net.robotmedia.billing.BillingController.BillingStatus; import net.robotmedia.billing.BillingRequest.ResponseCode; +import net.robotmedia.billing.helper.MockBillingObserver; import net.robotmedia.billing.model.BillingDB; import net.robotmedia.billing.model.BillingDBTest; import net.robotmedia.billing.model.Transaction; import net.robotmedia.billing.model.TransactionTest; -import net.robotmedia.billing.model.Transaction.PurchaseState; -import android.app.PendingIntent; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; @@ -51,6 +51,11 @@ public void testCheckBillingSupported() throws Exception { BillingController.checkBillingSupported(getContext()); } + @SmallTest + public void testCheckSubscriptionSupported() throws Exception { + BillingController.checkSubscriptionSupported(getContext()); + } + @MediumTest public void testIsPurchased() throws Exception { assertFalse(BillingController.isPurchased(getContext(), TransactionTest.TRANSACTION_1.productId)); @@ -98,14 +103,11 @@ public void testGetTransactionsString() throws Exception { @SmallTest public void testOnTransactionRestored() throws Exception { final Set flags = new HashSet(); - final IBillingObserver observer = new IBillingObserver() { + final IBillingObserver observer = new MockBillingObserver() { + @Override public void onTransactionsRestored() { flags.add(true); } - public void onPurchaseIntent(String itemId, PendingIntent purchaseIntent) {} - public void onBillingChecked(boolean supported) {} - public void onRequestPurchaseResponse(String itemId, ResponseCode response) {} - public void onPurchaseStateChanged(String itemId, PurchaseState state) {} }; BillingController.registerObserver(observer); BillingController.onTransactionsRestored(); @@ -118,22 +120,83 @@ public void testOnRequestPurchaseResponse() throws Exception { final String testItemId = TransactionTest.TRANSACTION_1.productId; final ResponseCode testResponse = ResponseCode.RESULT_OK; final Set flags = new HashSet(); - final IBillingObserver observer = new IBillingObserver() { - - public void onTransactionsRestored() {} - public void onPurchaseIntent(String itemId, PendingIntent purchaseIntent) {} - public void onBillingChecked(boolean supported) {} + final IBillingObserver observer = new MockBillingObserver() { + @Override public void onRequestPurchaseResponse(String itemId, ResponseCode response) { flags.add(true); assertEquals(testItemId, itemId); assertEquals(testResponse, response); } - @Override - public void onPurchaseStateChanged(String itemId, PurchaseState state) {} }; BillingController.registerObserver(observer); BillingController.onRequestPurchaseResponse(testItemId, testResponse); assertEquals(flags.size(), 1); BillingController.unregisterObserver(observer); } + + public void testOnBillingCheckedSupportedTrue() throws Exception { + final Set flags = new HashSet(); + final IBillingObserver observer = new MockBillingObserver() { + @Override + public void onBillingChecked(boolean supported) { + flags.add(true); + assertTrue(supported); + } + }; + BillingController.registerObserver(observer); + BillingController.onBillingChecked(true); + assertEquals(flags.size(), 1); + assertEquals(BillingController.checkBillingSupported(getContext()), BillingStatus.SUPPORTED); + BillingController.unregisterObserver(observer); + } + + public void testOnBillingCheckedSupportedFalse() throws Exception { + final Set flags = new HashSet(); + final IBillingObserver observer = new MockBillingObserver() { + @Override + public void onBillingChecked(boolean supported) { + flags.add(true); + assertFalse(supported); + } + }; + BillingController.registerObserver(observer); + BillingController.onBillingChecked(false); + assertEquals(flags.size(), 1); + assertEquals(BillingController.checkBillingSupported(getContext()), BillingStatus.UNSUPPORTED); + assertEquals(BillingController.checkSubscriptionSupported(getContext()), BillingStatus.UNSUPPORTED); + BillingController.unregisterObserver(observer); + } + + public void testOnSubscriptionCheckedSupportedTrue() throws Exception { + final Set flags = new HashSet(); + final IBillingObserver observer = new MockBillingObserver() { + @Override + public void onSubscriptionChecked(boolean supported) { + flags.add(true); + assertTrue(supported); + } + }; + BillingController.registerObserver(observer); + BillingController.onSubscriptionChecked(true); + assertEquals(flags.size(), 1); + assertEquals(BillingController.checkBillingSupported(getContext()), BillingStatus.SUPPORTED); + assertEquals(BillingController.checkSubscriptionSupported(getContext()), BillingStatus.SUPPORTED); + BillingController.unregisterObserver(observer); + } + + public void testOnSubscriptionCheckedSupportedFalse() throws Exception { + final Set flags = new HashSet(); + final IBillingObserver observer = new MockBillingObserver() { + @Override + public void onSubscriptionChecked(boolean supported) { + flags.add(true); + assertFalse(supported); + } + }; + BillingController.registerObserver(observer); + BillingController.onSubscriptionChecked(false); + assertEquals(flags.size(), 1); + assertEquals(BillingController.checkSubscriptionSupported(getContext()), BillingStatus.UNSUPPORTED); + BillingController.unregisterObserver(observer); + } } diff --git a/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingServiceTest.java b/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingServiceTest.java index fe0e672..375bd4d 100644 --- a/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingServiceTest.java +++ b/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingServiceTest.java @@ -41,6 +41,11 @@ public void testStart() throws Exception { public void testCheckBillingSupported() throws Exception { BillingService.checkBillingSupported(getContext()); } + + @SmallTest + public void testCheckSubscriptionSupported() throws Exception { + BillingService.checkSubscriptionSupported(getContext()); + } @SmallTest public void testConfirmNotifications() throws Exception { @@ -57,6 +62,11 @@ public void testRequestPurchase() throws Exception { BillingService.requestPurchase(getContext(), ITEM_ID, null); } + @SmallTest + public void testRequestSubscription() throws Exception { + BillingService.requestSubscription(getContext(), ITEM_ID, null); + } + @SmallTest public void testRestoreTransactions() throws Exception { BillingService.restoreTransations(getContext(), NONCE); diff --git a/AndroidBillingLibraryTest/src/net/robotmedia/billing/helper/AbstractBillingActivityTest.java b/AndroidBillingLibraryTest/src/net/robotmedia/billing/helper/AbstractBillingActivityTest.java index 6f984ca..7e01e8d 100644 --- a/AndroidBillingLibraryTest/src/net/robotmedia/billing/helper/AbstractBillingActivityTest.java +++ b/AndroidBillingLibraryTest/src/net/robotmedia/billing/helper/AbstractBillingActivityTest.java @@ -38,6 +38,11 @@ protected void setUp() throws Exception { public void testCheckBillingSupported() throws Exception { mActivity.checkBillingSupported(); } + + @MediumTest + public void testCheckSubscriptionSupported() throws Exception { + mActivity.checkSubscriptionSupported(); + } // TODO: Find a way to test the following without hanging the test suite // @SmallTest diff --git a/AndroidBillingLibraryTest/src/net/robotmedia/billing/helper/MockBillingActivity.java b/AndroidBillingLibraryTest/src/net/robotmedia/billing/helper/MockBillingActivity.java index 5e87764..4b9599a 100644 --- a/AndroidBillingLibraryTest/src/net/robotmedia/billing/helper/MockBillingActivity.java +++ b/AndroidBillingLibraryTest/src/net/robotmedia/billing/helper/MockBillingActivity.java @@ -23,16 +23,18 @@ public class MockBillingActivity extends AbstractBillingActivity { @Override public void onBillingChecked(boolean supported) { // TODO Auto-generated method stub - } - + @Override + public void onSubscriptionChecked(boolean supported) { + // TODO Auto-generated method stub + } + public byte[] getObfuscationSalt() { // TODO Auto-generated method stub return null; } - @Override public String getPublicKey() { // TODO Auto-generated method stub return null; diff --git a/AndroidBillingLibraryTest/src/net/robotmedia/billing/helper/MockBillingObserver.java b/AndroidBillingLibraryTest/src/net/robotmedia/billing/helper/MockBillingObserver.java new file mode 100644 index 0000000..f9bb54b --- /dev/null +++ b/AndroidBillingLibraryTest/src/net/robotmedia/billing/helper/MockBillingObserver.java @@ -0,0 +1,28 @@ +package net.robotmedia.billing.helper; + +import android.app.PendingIntent; +import net.robotmedia.billing.BillingRequest.ResponseCode; +import net.robotmedia.billing.IBillingObserver; +import net.robotmedia.billing.model.Transaction.PurchaseState; + +public class MockBillingObserver implements IBillingObserver { + + public void onBillingChecked(boolean supported) { + } + + public void onSubscriptionChecked(boolean supported) { + } + + public void onPurchaseIntent(String itemId, PendingIntent purchaseIntent) { + } + + public void onPurchaseStateChanged(String itemId, PurchaseState state) { + } + + public void onRequestPurchaseResponse(String itemId, ResponseCode response) { + } + + public void onTransactionsRestored() { + } + +} diff --git a/AndroidBillingLibraryTest/src/net/robotmedia/billing/model/TransactionTest.java b/AndroidBillingLibraryTest/src/net/robotmedia/billing/model/TransactionTest.java index 71b2789..4275333 100644 --- a/AndroidBillingLibraryTest/src/net/robotmedia/billing/model/TransactionTest.java +++ b/AndroidBillingLibraryTest/src/net/robotmedia/billing/model/TransactionTest.java @@ -69,6 +69,7 @@ public void testPurchaseStateOrdinal() throws Exception { assertEquals(Transaction.PurchaseState.PURCHASED.ordinal(), 0); assertEquals(Transaction.PurchaseState.CANCELLED.ordinal(), 1); assertEquals(Transaction.PurchaseState.REFUNDED.ordinal(), 2); + assertEquals(Transaction.PurchaseState.EXPIRED.ordinal(), 3); } @SmallTest diff --git a/DungeonsRedux/.settings/org.eclipse.jdt.core.prefs b/DungeonsRedux/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..f77b31c --- /dev/null +++ b/DungeonsRedux/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.source=1.5 diff --git a/DungeonsRedux/AndroidManifest.xml b/DungeonsRedux/AndroidManifest.xml index fc86a0d..c9488c2 100644 --- a/DungeonsRedux/AndroidManifest.xml +++ b/DungeonsRedux/AndroidManifest.xml @@ -1,13 +1,14 @@ - + package="net.robotmedia.billing.dungeons.redux"> + + - - - + + + @@ -25,6 +26,5 @@ - diff --git a/DungeonsRedux/res/layout/item_row.xml b/DungeonsRedux/res/layout/item_row.xml index 9accc5f..313262b 100644 --- a/DungeonsRedux/res/layout/item_row.xml +++ b/DungeonsRedux/res/layout/item_row.xml @@ -19,14 +19,12 @@ + android:layout_height="wrap_content"> + android:textAppearance="?android:attr/textAppearanceMedium" /> diff --git a/DungeonsRedux/res/layout/main.xml b/DungeonsRedux/res/layout/main.xml index 40a2b9a..fbf49f6 100644 --- a/DungeonsRedux/res/layout/main.xml +++ b/DungeonsRedux/res/layout/main.xml @@ -22,8 +22,7 @@ android:layout_width="fill_parent" android:layout_height="fill_parent" android:paddingLeft="4dip" - android:paddingRight="4dip" - android:background="@color/screen_background"> + android:paddingRight="4dip"> @@ -60,7 +58,6 @@ android:layout_width="fill_parent" android:layout_height="wrap_content" android:paddingTop="10dip" - android:textColor="@android:color/black" android:textAppearance="?android:attr/textAppearanceMedium" android:textStyle="bold" android:text="@string/items_you_own" /> diff --git a/DungeonsRedux/res/values/colors.xml b/DungeonsRedux/res/values/colors.xml index 1b08c48..35ddbf4 100644 --- a/DungeonsRedux/res/values/colors.xml +++ b/DungeonsRedux/res/values/colors.xml @@ -18,6 +18,5 @@ --> - #ffc0f0c0 #ffc00000 diff --git a/DungeonsRedux/res/values/strings.xml b/DungeonsRedux/res/values/strings.xml index f158e0d..901e105 100644 --- a/DungeonsRedux/res/values/strings.xml +++ b/DungeonsRedux/res/values/strings.xml @@ -18,7 +18,6 @@ --> - Welcome to the Sample store! Dungeons Redux Can\'t make purchases The Market billing @@ -29,10 +28,11 @@ Select an item Items for sale Items you own - Recent activity Two-handed sword Potions + Subscription monthly + Subscription yearly android.test.canceled diff --git a/DungeonsRedux/src/net/robotmedia/billing/example/Application.java b/DungeonsRedux/src/net/robotmedia/billing/example/Application.java index a0b46c6..e0cc47b 100644 --- a/DungeonsRedux/src/net/robotmedia/billing/example/Application.java +++ b/DungeonsRedux/src/net/robotmedia/billing/example/Application.java @@ -10,12 +10,10 @@ public void onCreate() { BillingController.setDebug(true); BillingController.setConfiguration(new BillingController.IConfiguration() { - @Override public byte[] getObfuscationSalt() { return new byte[] {41, -90, -116, -41, 66, -53, 122, -110, -127, -96, -88, 77, 127, 115, 1, 73, 57, 110, 48, -116}; } - @Override public String getPublicKey() { return "your public key here"; } diff --git a/DungeonsRedux/src/net/robotmedia/billing/example/Dungeons.java b/DungeonsRedux/src/net/robotmedia/billing/example/Dungeons.java index 9413c42..99e0bf6 100644 --- a/DungeonsRedux/src/net/robotmedia/billing/example/Dungeons.java +++ b/DungeonsRedux/src/net/robotmedia/billing/example/Dungeons.java @@ -20,9 +20,10 @@ import net.robotmedia.billing.BillingController; import net.robotmedia.billing.BillingRequest.ResponseCode; -import net.robotmedia.billing.example.R; +import net.robotmedia.billing.dungeons.redux.R; import net.robotmedia.billing.example.auxiliary.CatalogAdapter; import net.robotmedia.billing.example.auxiliary.CatalogEntry; +import net.robotmedia.billing.example.auxiliary.CatalogEntry.Managed; import net.robotmedia.billing.helper.AbstractBillingObserver; import net.robotmedia.billing.model.Transaction; import net.robotmedia.billing.model.Transaction.PurchaseState; @@ -41,7 +42,7 @@ public class Dungeons extends Activity { private static final int DIALOG_BILLING_NOT_SUPPORTED_ID = 2; - private String mSku; + private CatalogEntry mSelectedItem; private CatalogAdapter mCatalogAdapter; @@ -68,20 +69,22 @@ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mBillingObserver = new AbstractBillingObserver(this) { - @Override public void onBillingChecked(boolean supported) { Dungeons.this.onBillingChecked(supported); } - @Override public void onPurchaseStateChanged(String itemId, PurchaseState state) { Dungeons.this.onPurchaseStateChanged(itemId, state); } - @Override public void onRequestPurchaseResponse(String itemId, ResponseCode response) { Dungeons.this.onRequestPurchaseResponse(itemId, response); } + + public void onSubscriptionChecked(boolean supported) { + Dungeons.this.onSubscriptionChecked(supported); + } + }; setContentView(R.layout.main); @@ -89,6 +92,7 @@ public void onRequestPurchaseResponse(String itemId, ResponseCode response) { setupWidgets(); BillingController.registerObserver(mBillingObserver); BillingController.checkBillingSupported(this); + BillingController.checkSubscriptionSupported(this); updateOwnedItems(); } @@ -115,6 +119,10 @@ public void onPurchaseStateChanged(String itemId, PurchaseState state) { public void onRequestPurchaseResponse(String itemId, ResponseCode response) { } + + public void onSubscriptionChecked(boolean supported) { + + } /** * Restores previous transactions, if any. This happens if the application @@ -134,9 +142,12 @@ private void setupWidgets() { mBuyButton.setEnabled(false); mBuyButton.setOnClickListener(new OnClickListener() { - @Override public void onClick(View v) { - BillingController.requestPurchase(Dungeons.this, mSku, true /* confirm */); + if (mSelectedItem.managed != Managed.SUBSCRIPTION) { + BillingController.requestPurchase(Dungeons.this, mSelectedItem.sku, true /* confirm */, null); + } else { + BillingController.requestSubscription(Dungeons.this, mSelectedItem.sku, true /* confirm */, null); + } } }); @@ -146,7 +157,7 @@ public void onClick(View v) { mSelectItemSpinner.setOnItemSelectedListener(new OnItemSelectedListener() { public void onItemSelected(AdapterView parent, View view, int position, long id) { - mSku = CatalogEntry.CATALOG[position].sku; + mSelectedItem = CatalogEntry.CATALOG[position]; } public void onNothingSelected(AdapterView arg0) { diff --git a/DungeonsRedux/src/net/robotmedia/billing/example/auxiliary/CatalogEntry.java b/DungeonsRedux/src/net/robotmedia/billing/example/auxiliary/CatalogEntry.java index 66c04b2..08c2a14 100644 --- a/DungeonsRedux/src/net/robotmedia/billing/example/auxiliary/CatalogEntry.java +++ b/DungeonsRedux/src/net/robotmedia/billing/example/auxiliary/CatalogEntry.java @@ -1,20 +1,22 @@ package net.robotmedia.billing.example.auxiliary; -import net.robotmedia.billing.example.R; +import net.robotmedia.billing.dungeons.redux.R; public class CatalogEntry { /** - * Each product in the catalog is either MANAGED or UNMANAGED. MANAGED means - * that the product can be purchased only once per user (such as a new level - * in a game). The purchase is remembered by Android Market and can be - * restored if this application is uninstalled and then re-installed. - * UNMANAGED is used for products that can be used up and purchased multiple - * times (such as poker chips). It is up to the application to keep track of - * UNMANAGED products for the user. + * Each product in the catalog can be MANAGED, UNMANAGED, or SUBSCRIPTION. + * MANAGED means that the product can be purchased only once per user (such + * as a new level in a game). The purchase is remembered by Android Market + * and can be restored if this application is uninstalled and then + * re-installed. UNMANAGED is used for products that can be used up and + * purchased multiple times (such as poker chips). It is up to the + * application to keep track of UNMANAGED products for the user. + * SUBSCRIPTION is just like MANAGED except that the user gets charged + * monthly or yearly. */ public enum Managed { - MANAGED, UNMANAGED + MANAGED, UNMANAGED, SUBSCRIPTION } public String sku; @@ -31,6 +33,8 @@ public CatalogEntry(String sku, int nameId, Managed managed) { public static final CatalogEntry[] CATALOG = new CatalogEntry[] { new CatalogEntry("sword_001", R.string.two_handed_sword, Managed.MANAGED), new CatalogEntry("potion_001", R.string.potions, Managed.UNMANAGED), + new CatalogEntry("subscription_monthly", R.string.subscription_monthly, Managed.SUBSCRIPTION), + new CatalogEntry("subscription_yearly", R.string.subscription_yearly, Managed.SUBSCRIPTION), new CatalogEntry("android.test.purchased", R.string.android_test_purchased, Managed.UNMANAGED), new CatalogEntry("android.test.canceled", R.string.android_test_canceled, Managed.UNMANAGED), new CatalogEntry("android.test.refunded", R.string.android_test_refunded, Managed.UNMANAGED), diff --git a/README.mdown b/README.mdown index b2fde13..c0db052 100644 --- a/README.mdown +++ b/README.mdown @@ -1,3 +1,11 @@ +Update +====== + +In-app Billing v2 API is deprecated and will be shut down in January 2015. This library was developed for v2 a long time ago. If your app is still using this library, please migrate to the [v3 API](https://developer.android.com/google/play/billing/api.html) as soon as possible. + +The project [Android Checkout Library](https://github.com/serso/android-checkout) by @serso supports v3 and attemps to provide [data compatibility](https://github.com/serso/android-checkout#billing-version-2) with AndroidBillingLibrary. We haven't verified this so please use it at your own discretion. + + Android Billing Library ======================= @@ -41,7 +49,7 @@ Usage Subclassing AbstractBillingActivity ----------------------------------- -[AbstractBillingActivity][2] is an abstract activity that provides default integration with in-app billing. It is useful to get acquainted with the library, or for very simple applications that require in-app billing integration in only one activity. For more flexibility use [BillingController][3] directly instead. +[AbstractBillingActivity][2] is an abstract activity that provides default integration with in-app billing (an analogous class for fragments is also provided). It is useful to get acquainted with the library, or for very simple applications that require in-app billing integration in only one activity. For more flexibility use [BillingController][3] directly. When created your [AbstractBillingActivity][2] instance will check if in-app billing is supported, followed by a call to `onBillingChecked(boolean)`, which has to be implemented by the subclass. @@ -63,7 +71,7 @@ Additionally, [BillingController][3] requires a `BillingController.IConfiguratio Dungeons Redux ============== -[Dungeons Redux][5] is a sample app that shows how to use *Android Billing Library* through [BillingController][3]. It is a simplified version of the Dungeons in-app billing example provided by Google. +[Dungeons Redux][5] is a sample app that shows how to use *Android Billing Library* via [BillingController][3]. It is a simplified version of the Dungeons in-app billing example provided by Google. It should be noted that Dungeons Redux does not intend to be an example of how to use in-app billing in general. @@ -93,4 +101,4 @@ limitations under the License. [2]: https://github.com/robotmedia/AndroidBillingLibrary/blob/master/AndroidBillingLibrary/src/net/robotmedia/billing/helper/AbstractBillingActivity.java [3]: https://github.com/robotmedia/AndroidBillingLibrary/blob/master/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java [4]: https://github.com/robotmedia/AndroidBillingLibrary/blob/master/AndroidBillingLibrary/src/net/robotmedia/billing/IBillingObserver.java -[5]: https://github.com/robotmedia/AndroidBillingLibrary/tree/master/DungeonsRedux \ No newline at end of file +[5]: https://github.com/robotmedia/AndroidBillingLibrary/tree/master/DungeonsRedux