From 709312f90eb894cfa3dbb91f5f90be48a9807fe3 Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Sun, 29 Jul 2012 11:39:05 +0200 Subject: [PATCH 01/28] Fix Android project properties --- AndroidBillingLibrary/.settings/org.eclipse.jdt.core.prefs | 4 ++++ .../.settings/org.eclipse.jdt.core.prefs | 4 ++++ DungeonsRedux/.settings/org.eclipse.jdt.core.prefs | 4 ++++ 3 files changed, 12 insertions(+) create mode 100644 AndroidBillingLibrary/.settings/org.eclipse.jdt.core.prefs create mode 100644 AndroidBillingLibraryTest/.settings/org.eclipse.jdt.core.prefs create mode 100644 DungeonsRedux/.settings/org.eclipse.jdt.core.prefs 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/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/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 From 17a48458e8d1b1bb1b82cdb79b28a607160211c4 Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Sun, 29 Jul 2012 11:39:25 +0200 Subject: [PATCH 02/28] Remove interface @Override --- .../src/net/robotmedia/billing/BillingService.java | 2 -- .../net/robotmedia/billing/helper/AbstractBillingActivity.java | 3 --- .../net/robotmedia/billing/helper/AbstractBillingObserver.java | 2 -- .../robotmedia/billing/security/DefaultSignatureValidator.java | 1 - 4 files changed, 8 deletions(-) diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java index 348e429..3c56c62 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java @@ -141,13 +141,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; } diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/helper/AbstractBillingActivity.java b/AndroidBillingLibrary/src/net/robotmedia/billing/helper/AbstractBillingActivity.java index c8e978c..1115b2e 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/helper/AbstractBillingActivity.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/helper/AbstractBillingActivity.java @@ -46,17 +46,14 @@ protected void onCreate(android.os.Bundle savedInstanceState) { mBillingObserver = new AbstractBillingObserver(this) { - @Override public void onBillingChecked(boolean supported) { AbstractBillingActivity.this.onBillingChecked(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); } 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/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())) { From 7b84efadc020ba0ec6885e77c8e36ea8dbbd6c42 Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Sun, 29 Jul 2012 11:39:37 +0200 Subject: [PATCH 03/28] Ignore .metadata --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 From 6714c0d52508a869c543097bf9f640cad8d5896c Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Sun, 29 Jul 2012 11:43:00 +0200 Subject: [PATCH 04/28] Increase minSdkVersion to avoid Lint error --- AndroidBillingLibraryTest/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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"> - + From 2d47ca5f4c9c5c263d28aa3dc4717e025c6549c1 Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Sun, 29 Jul 2012 11:44:57 +0200 Subject: [PATCH 05/28] Put uses-sdk before application and add targetSdkVersion --- DungeonsRedux/AndroidManifest.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DungeonsRedux/AndroidManifest.xml b/DungeonsRedux/AndroidManifest.xml index fc86a0d..84a1291 100644 --- a/DungeonsRedux/AndroidManifest.xml +++ b/DungeonsRedux/AndroidManifest.xml @@ -2,10 +2,11 @@ - + + - + @@ -25,6 +26,5 @@ - From 7df20c81cf699a5db54709d7bfe17c95117ea5fe Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Sun, 29 Jul 2012 11:46:37 +0200 Subject: [PATCH 06/28] Use keepclasseswithmembers in proguard.cfg --- AndroidBillingLibraryTest/proguard.cfg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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); } From 91f597a85ef151f461110b3afdce5e5b298a9bff Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Sun, 29 Jul 2012 11:52:20 +0200 Subject: [PATCH 07/28] Remove unused strings --- AndroidBillingLibrary/res/values/strings.xml | 1 - AndroidBillingLibraryTest/res/values/strings.xml | 1 - DungeonsRedux/res/values/strings.xml | 2 -- 3 files changed, 4 deletions(-) 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/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/DungeonsRedux/res/values/strings.xml b/DungeonsRedux/res/values/strings.xml index f158e0d..bb69610 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,7 +28,6 @@ Select an item Items for sale Items you own - Recent activity Two-handed sword Potions From 22086ddde121c0f2c076abffa891e19eb8966da2 Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Sun, 29 Jul 2012 12:08:22 +0200 Subject: [PATCH 08/28] Remove interface @Override --- .../src/net/robotmedia/billing/BillingControllerTest.java | 1 - .../net/robotmedia/billing/helper/MockBillingActivity.java | 2 -- .../src/net/robotmedia/billing/example/Application.java | 2 -- .../src/net/robotmedia/billing/example/Dungeons.java | 4 ---- 4 files changed, 9 deletions(-) diff --git a/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingControllerTest.java b/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingControllerTest.java index 8f7c2a6..fc82563 100644 --- a/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingControllerTest.java +++ b/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingControllerTest.java @@ -128,7 +128,6 @@ public void onRequestPurchaseResponse(String itemId, ResponseCode response) { assertEquals(testItemId, itemId); assertEquals(testResponse, response); } - @Override public void onPurchaseStateChanged(String itemId, PurchaseState state) {} }; BillingController.registerObserver(observer); diff --git a/AndroidBillingLibraryTest/src/net/robotmedia/billing/helper/MockBillingActivity.java b/AndroidBillingLibraryTest/src/net/robotmedia/billing/helper/MockBillingActivity.java index 5e87764..b32820f 100644 --- a/AndroidBillingLibraryTest/src/net/robotmedia/billing/helper/MockBillingActivity.java +++ b/AndroidBillingLibraryTest/src/net/robotmedia/billing/helper/MockBillingActivity.java @@ -26,13 +26,11 @@ public void onBillingChecked(boolean supported) { } - @Override public byte[] getObfuscationSalt() { // TODO Auto-generated method stub return null; } - @Override public String getPublicKey() { // TODO Auto-generated method stub return null; 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..640650e 100644 --- a/DungeonsRedux/src/net/robotmedia/billing/example/Dungeons.java +++ b/DungeonsRedux/src/net/robotmedia/billing/example/Dungeons.java @@ -68,17 +68,14 @@ 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); } @@ -134,7 +131,6 @@ private void setupWidgets() { mBuyButton.setEnabled(false); mBuyButton.setOnClickListener(new OnClickListener() { - @Override public void onClick(View v) { BillingController.requestPurchase(Dungeons.this, mSku, true /* confirm */); } From 48f4135d4dde781dadcd9ad70ec3c13b6ba72435 Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Sun, 29 Jul 2012 13:53:15 +0200 Subject: [PATCH 09/28] checkSubscriptionSupported --- .../robotmedia/billing/BillingController.java | 75 +++++++++++++++++-- .../robotmedia/billing/BillingRequest.java | 34 ++++++++- .../robotmedia/billing/BillingService.java | 16 +++- .../robotmedia/billing/IBillingObserver.java | 16 +++- .../helper/AbstractBillingActivity.java | 49 ++++++++++-- .../billing/BillingControllerTest.java | 7 ++ .../billing/BillingServiceTest.java | 5 ++ .../helper/AbstractBillingActivityTest.java | 5 ++ .../billing/helper/MockBillingActivity.java | 6 +- .../robotmedia/billing/example/Dungeons.java | 10 +++ 10 files changed, 205 insertions(+), 18 deletions(-) diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java index 5bb18ff..c2c3ff5 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 = billingStatus == BillingStatus.SUPPORTED; + onSubscriptionChecked(supported); + } + return billingStatus; } /** @@ -291,7 +330,10 @@ 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); } @@ -430,6 +472,23 @@ protected static void onResponseCode(Context context, long requestId, int respon request.onResponseCode(response); } } + + /** + * 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) { diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingRequest.java b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingRequest.java index de69c2d..ee0f9ab 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingRequest.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingRequest.java @@ -24,6 +24,9 @@ public abstract class BillingRequest { + private static final String ITEM_TYPE_SUBSCRIPTION = "subs"; + private static final String REQUEST_TYPE_CHECK_BILLING_SUPPORTED = "CHECK_BILLING_SUPPORTED"; + public static class CheckBillingSupported extends BillingRequest { public CheckBillingSupported(String packageName, int startId) { @@ -32,7 +35,7 @@ public CheckBillingSupported(String packageName, int startId) { @Override public String getRequestType() { - return "CHECK_BILLING_SUPPORTED"; + return REQUEST_TYPE_CHECK_BILLING_SUPPORTED; } @Override @@ -42,6 +45,35 @@ protected void processOkResponse(Bundle response) { } } + + public static class CheckSubscriptionSupported extends BillingRequest { + + private static final String KEY_API_VERSION = "API_VERSION"; + private static final String KEY_ITEM_TYPE = "ITEM_TYPE"; + + public CheckSubscriptionSupported(String packageName, int startId) { + super(packageName, startId); + } + + @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.putInt(KEY_API_VERSION, 2); + request.putString(KEY_ITEM_TYPE, ITEM_TYPE_SUBSCRIPTION); + } + + } + public static class ConfirmNotifications extends BillingRequest { private String[] notifyIds; diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java index 3c56c62..dbacb05 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java @@ -35,7 +35,7 @@ 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, RESTORE_TRANSACTIONS } private static final String ACTION_MARKET_BILLING_SERVICE = "com.android.vending.billing.MarketBillingService.BIND"; @@ -53,6 +53,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); @@ -107,6 +112,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(); @@ -173,6 +184,9 @@ 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; 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 1115b2e..17bac58 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) { @@ -49,6 +84,10 @@ protected void onCreate(android.os.Bundle savedInstanceState) { public void onBillingChecked(boolean supported) { AbstractBillingActivity.this.onBillingChecked(supported); } + + public void onSubscriptionChecked(boolean supported) { + AbstractBillingActivity.this.onSubscriptionChecked(supported); + } public void onPurchaseStateChanged(String itemId, PurchaseState state) { AbstractBillingActivity.this.onPurchaseStateChanged(itemId, state); diff --git a/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingControllerTest.java b/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingControllerTest.java index fc82563..55c606d 100644 --- a/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingControllerTest.java +++ b/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingControllerTest.java @@ -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)); @@ -106,6 +111,7 @@ 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) {} + public void onSubscriptionChecked(boolean supported) {}; }; BillingController.registerObserver(observer); BillingController.onTransactionsRestored(); @@ -129,6 +135,7 @@ public void onRequestPurchaseResponse(String itemId, ResponseCode response) { assertEquals(testResponse, response); } public void onPurchaseStateChanged(String itemId, PurchaseState state) {} + public void onSubscriptionChecked(boolean supported) {}; }; BillingController.registerObserver(observer); BillingController.onRequestPurchaseResponse(testItemId, testResponse); diff --git a/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingServiceTest.java b/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingServiceTest.java index fe0e672..3fb9197 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 { 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 b32820f..4b9599a 100644 --- a/AndroidBillingLibraryTest/src/net/robotmedia/billing/helper/MockBillingActivity.java +++ b/AndroidBillingLibraryTest/src/net/robotmedia/billing/helper/MockBillingActivity.java @@ -23,7 +23,11 @@ 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() { diff --git a/DungeonsRedux/src/net/robotmedia/billing/example/Dungeons.java b/DungeonsRedux/src/net/robotmedia/billing/example/Dungeons.java index 640650e..50e0496 100644 --- a/DungeonsRedux/src/net/robotmedia/billing/example/Dungeons.java +++ b/DungeonsRedux/src/net/robotmedia/billing/example/Dungeons.java @@ -79,6 +79,11 @@ public void onPurchaseStateChanged(String itemId, PurchaseState state) { 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); @@ -86,6 +91,7 @@ public void onRequestPurchaseResponse(String itemId, ResponseCode response) { setupWidgets(); BillingController.registerObserver(mBillingObserver); BillingController.checkBillingSupported(this); + BillingController.checkSubscriptionSupported(this); updateOwnedItems(); } @@ -112,6 +118,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 From 874d30ced7a29802f44ba71f8c8faa5cf5d5901c Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Sun, 29 Jul 2012 18:22:18 +0200 Subject: [PATCH 10/28] onBillingChecked and onSubscriptionChecked unit tests + bug fix --- .../robotmedia/billing/BillingController.java | 4 +- .../billing/BillingControllerTest.java | 87 +++++++++++++++---- .../billing/helper/MockBillingObserver.java | 28 ++++++ 3 files changed, 102 insertions(+), 17 deletions(-) create mode 100644 AndroidBillingLibraryTest/src/net/robotmedia/billing/helper/MockBillingObserver.java diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java index c2c3ff5..e376141 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java @@ -155,10 +155,10 @@ public static BillingStatus checkSubscriptionSupported(Context context) { if (subscriptionStatus == BillingStatus.UNKNOWN) { BillingService.checkSubscriptionSupported(context); } else { - boolean supported = billingStatus == BillingStatus.SUPPORTED; + boolean supported = subscriptionStatus == BillingStatus.SUPPORTED; onSubscriptionChecked(supported); } - return billingStatus; + return subscriptionStatus; } /** diff --git a/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingControllerTest.java b/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingControllerTest.java index 55c606d..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; @@ -103,15 +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) {} - public void onSubscriptionChecked(boolean supported) {}; }; BillingController.registerObserver(observer); BillingController.onTransactionsRestored(); @@ -124,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); } - public void onPurchaseStateChanged(String itemId, PurchaseState state) {} - public void onSubscriptionChecked(boolean supported) {}; }; 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/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() { + } + +} From 65d08e9babb5b8df3dc11d894f30971b468f7ec8 Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Sun, 29 Jul 2012 18:46:30 +0200 Subject: [PATCH 11/28] requestSubscription --- .../robotmedia/billing/BillingController.java | 47 ++++++++++++++++++- .../robotmedia/billing/BillingRequest.java | 12 +++-- .../robotmedia/billing/BillingService.java | 14 +++++- .../helper/AbstractBillingActivity.java | 15 ++++++ .../billing/BillingServiceTest.java | 5 ++ 5 files changed, 86 insertions(+), 7 deletions(-) diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java index e376141..b1c0533 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java @@ -537,6 +537,10 @@ 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 @@ -548,8 +552,14 @@ public static void requestPurchase(Context context, String itemId) { } /** + *

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

+ *

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

* * @param context * @param itemId @@ -560,13 +570,48 @@ public static void requestPurchase(Context context, String itemId) { * to {@link #confirmNotifications(Context, String)}. * @see IBillingObserver#onPurchaseIntent(String, PendingIntent) */ - public static void requestPurchase(Context context, String itemId, boolean confirm) { + public static void requestPurchase(Context context, String itemId, + boolean confirm) { if (confirm) { automaticConfirmations.add(itemId); } BillingService.requestPurchase(context, itemId, null); } + /** + * 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) + */ + public static void requestSubscription(Context context, String itemId) { + requestSubscription(context, itemId, false); + } + + /** + * Requests the purchase of the specified subscription item with optional + * automatic confirmation. + * + * @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)}. + * @see IBillingObserver#onPurchaseIntent(String, PendingIntent) + */ + public static void requestSubscription(Context context, String itemId, + boolean confirm) { + if (confirm) { + automaticConfirmations.add(itemId); + } + BillingService.requestSubscription(context, itemId, null); + } + /** * Requests to restore all transactions. * diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingRequest.java b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingRequest.java index ee0f9ab..d9feda9 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingRequest.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingRequest.java @@ -24,8 +24,10 @@ public abstract class BillingRequest { - private static final String ITEM_TYPE_SUBSCRIPTION = "subs"; - private static final String REQUEST_TYPE_CHECK_BILLING_SUPPORTED = "CHECK_BILLING_SUPPORTED"; + public static final String ITEM_TYPE_INAPP = "inapp"; + public static final String ITEM_TYPE_SUBSCRIPTION = "subs"; + private static final String KEY_ITEM_TYPE = "ITEM_TYPE"; + private static final String REQUEST_TYPE_CHECK_BILLING_SUPPORTED = "CHECK_BILLING_SUPPORTED"; public static class CheckBillingSupported extends BillingRequest { @@ -49,7 +51,6 @@ protected void processOkResponse(Bundle response) { public static class CheckSubscriptionSupported extends BillingRequest { private static final String KEY_API_VERSION = "API_VERSION"; - private static final String KEY_ITEM_TYPE = "ITEM_TYPE"; public CheckSubscriptionSupported(String packageName, int startId) { super(packageName, startId); @@ -123,21 +124,24 @@ public String getRequestType() { public static class RequestPurchase extends BillingRequest { private String itemId; + private String itemType; private String developerPayload; private static final String KEY_ITEM_ID = "ITEM_ID"; private static final String KEY_DEVELOPER_PAYLOAD = "DEVELOPER_PAYLOAD"; private static final String KEY_PURCHASE_INTENT = "PURCHASE_INTENT"; - public RequestPurchase(String packageName, int startId, String itemId, String developerPayload) { + public RequestPurchase(String packageName, int startId, String itemId, String itemType, String developerPayload) { super(packageName, startId); this.itemId = itemId; + this.itemType = itemType; this.developerPayload = developerPayload; } @Override protected void addParams(Bundle request) { request.putString(KEY_ITEM_ID, itemId); + request.putString(KEY_ITEM_TYPE, itemType); if (developerPayload != null) { request.putString(KEY_DEVELOPER_PAYLOAD, developerPayload); } diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java index dbacb05..81cf230 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java @@ -40,8 +40,8 @@ private static enum Action { 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_ITEM_TYPE = "ITEM_TYPE"; private static final String EXTRA_NONCE = "EXTRA_NONCE"; private static final String EXTRA_NOTIFY_IDS = "NOTIFY_IDS"; private static LinkedList mPendingRequests = new LinkedList(); @@ -85,6 +85,15 @@ public static void getPurchaseInformation(Context context, String[] notifyIds, l public static void requestPurchase(Context context, String itemId, String developerPayload) { final Intent intent = createIntent(context, Action.REQUEST_PURCHASE); intent.putExtra(EXTRA_ITEM_ID, itemId); + intent.putExtra(EXTRA_ITEM_TYPE, BillingRequest.ITEM_TYPE_INAPP); + 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_PURCHASE); + intent.putExtra(EXTRA_ITEM_ID, itemId); + intent.putExtra(EXTRA_ITEM_TYPE, BillingRequest.ITEM_TYPE_SUBSCRIPTION); intent.putExtra(EXTRA_DEVELOPER_PAYLOAD, developerPayload); context.startService(intent); } @@ -204,8 +213,9 @@ private void handleCommand(Intent intent, int startId) { private void requestPurchase(Intent intent, int startId) { final String packageName = getPackageName(); final String itemId = intent.getStringExtra(EXTRA_ITEM_ID); + final String itemType = intent.getStringExtra(EXTRA_ITEM_TYPE); final String developerPayload = intent.getStringExtra(EXTRA_DEVELOPER_PAYLOAD); - final RequestPurchase request = new RequestPurchase(packageName, startId, itemId, developerPayload); + final RequestPurchase request = new RequestPurchase(packageName, startId, itemId, itemType, developerPayload); runRequestOrQueue(request); } diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/helper/AbstractBillingActivity.java b/AndroidBillingLibrary/src/net/robotmedia/billing/helper/AbstractBillingActivity.java index 17bac58..4415c05 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/helper/AbstractBillingActivity.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/helper/AbstractBillingActivity.java @@ -135,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/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingServiceTest.java b/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingServiceTest.java index 3fb9197..375bd4d 100644 --- a/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingServiceTest.java +++ b/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingServiceTest.java @@ -62,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); From 4e5e6057d01720e04e0bd6172937c15244ba69ab Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Sun, 29 Jul 2012 18:52:08 +0200 Subject: [PATCH 12/28] PurchaseState.EXPIRED --- .../net/robotmedia/billing/model/Transaction.java | 12 ++++++++---- .../robotmedia/billing/model/TransactionTest.java | 1 + 2 files changed, 9 insertions(+), 4 deletions(-) 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/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 From 1f9a196619feb066b038585dab5d16012cbf69da Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Sun, 29 Jul 2012 19:03:02 +0200 Subject: [PATCH 13/28] Add subscription items --- DungeonsRedux/res/values/strings.xml | 2 ++ .../example/auxiliary/CatalogEntry.java | 20 +++++++++++-------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/DungeonsRedux/res/values/strings.xml b/DungeonsRedux/res/values/strings.xml index bb69610..901e105 100644 --- a/DungeonsRedux/res/values/strings.xml +++ b/DungeonsRedux/res/values/strings.xml @@ -31,6 +31,8 @@ Two-handed sword Potions + Subscription monthly + Subscription yearly android.test.canceled diff --git a/DungeonsRedux/src/net/robotmedia/billing/example/auxiliary/CatalogEntry.java b/DungeonsRedux/src/net/robotmedia/billing/example/auxiliary/CatalogEntry.java index 66c04b2..32600bb 100644 --- a/DungeonsRedux/src/net/robotmedia/billing/example/auxiliary/CatalogEntry.java +++ b/DungeonsRedux/src/net/robotmedia/billing/example/auxiliary/CatalogEntry.java @@ -5,16 +5,18 @@ 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.UNMANAGED), + new CatalogEntry("subscription_yearly", R.string.subscription_yearly, Managed.UNMANAGED), 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), From b21fd2322b50747294ab9b5bcae9c1da8a6de081 Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Sun, 29 Jul 2012 19:03:20 +0200 Subject: [PATCH 14/28] Update to new Dungeons look --- DungeonsRedux/res/layout/item_row.xml | 6 ++---- DungeonsRedux/res/layout/main.xml | 5 +---- DungeonsRedux/res/values/colors.xml | 1 - 3 files changed, 3 insertions(+), 9 deletions(-) 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 From f1a4105352cf20c37026fd9752f3b5e19a6e3662 Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Mon, 30 Jul 2012 09:31:01 +0200 Subject: [PATCH 15/28] Use requestSubscription for subscriptions --- .../src/net/robotmedia/billing/example/Dungeons.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/DungeonsRedux/src/net/robotmedia/billing/example/Dungeons.java b/DungeonsRedux/src/net/robotmedia/billing/example/Dungeons.java index 50e0496..1e9746a 100644 --- a/DungeonsRedux/src/net/robotmedia/billing/example/Dungeons.java +++ b/DungeonsRedux/src/net/robotmedia/billing/example/Dungeons.java @@ -23,6 +23,7 @@ import net.robotmedia.billing.example.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; @@ -142,7 +143,11 @@ private void setupWidgets() { mBuyButton.setOnClickListener(new OnClickListener() { 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 */); + } else { + BillingController.requestSubscription(Dungeons.this, mSelectedItem.sku, true /* confirm */); + } } }); @@ -152,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) { From 681f020b45fdd943b4c185cdd7d4a00465deeec1 Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Mon, 30 Jul 2012 11:14:14 +0200 Subject: [PATCH 16/28] Fixed isPurchased javadoc. Closes #68. --- .../src/net/robotmedia/billing/BillingController.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java index b1c0533..39441e6 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java @@ -274,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 From 40269a1b7a12a15ae77dfa0c343b68c87153465c Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Mon, 30 Jul 2012 11:44:39 +0200 Subject: [PATCH 17/28] Add developerPayload to requestPurchase and requestSubscription. Closes #59. --- .../robotmedia/billing/BillingController.java | 45 ++++++++++--------- .../robotmedia/billing/example/Dungeons.java | 4 +- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java index 39441e6..bce19e1 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java @@ -206,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); @@ -330,7 +330,9 @@ static void obfuscate(Context context, Transaction purchase) { */ protected static void onBillingChecked(boolean supported) { billingStatus = supported ? BillingStatus.SUPPORTED : BillingStatus.UNSUPPORTED; - if (billingStatus == BillingStatus.UNSUPPORTED) { // Save us the subscription check + if (billingStatus == BillingStatus.UNSUPPORTED) { // Save us the + // subscription + // check subscriptionStatus = BillingStatus.UNSUPPORTED; } for (IBillingObserver o : observers) { @@ -432,8 +434,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. @@ -451,8 +452,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 @@ -471,17 +471,18 @@ protected static void onResponseCode(Context context, long requestId, int respon request.onResponseCode(response); } } - + /** * Called after the response to a - * {@link net.robotmedia.billing.request.CheckSubscriptionSupported} request is - * received. + * {@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 + if (subscriptionStatus == BillingStatus.SUPPORTED) { // Save us the + // billing check billingStatus = BillingStatus.SUPPORTED; } for (IBillingObserver o : observers) { @@ -547,7 +548,7 @@ public static boolean registerObserver(IBillingObserver observer) { * @see #requestPurchase(Context, String, boolean) */ public static void requestPurchase(Context context, String itemId) { - requestPurchase(context, itemId, false); + requestPurchase(context, itemId, false, null); } /** @@ -557,7 +558,7 @@ public static void requestPurchase(Context context, String itemId) { *

*

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

* * @param context @@ -567,14 +568,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 requestPurchase(Context context, String itemId, boolean confirm, String developerPayload) { if (confirm) { automaticConfirmations.add(itemId); } - BillingService.requestPurchase(context, itemId, null); + BillingService.requestPurchase(context, itemId, developerPayload); } /** @@ -584,10 +587,10 @@ public static void requestPurchase(Context context, String itemId, * @param context * @param itemId * id of the item to be purchased. - * @see #requestSubscription(Context, String, boolean) + * @see #requestSubscription(Context, String, boolean, String) */ public static void requestSubscription(Context context, String itemId) { - requestSubscription(context, itemId, false); + requestSubscription(context, itemId, false, null); } /** @@ -601,14 +604,16 @@ public static void requestSubscription(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 requestSubscription(Context context, String itemId, - boolean confirm) { + public static void requestSubscription(Context context, String itemId, boolean confirm, String developerPayload) { if (confirm) { automaticConfirmations.add(itemId); } - BillingService.requestSubscription(context, itemId, null); + BillingService.requestSubscription(context, itemId, developerPayload); } /** diff --git a/DungeonsRedux/src/net/robotmedia/billing/example/Dungeons.java b/DungeonsRedux/src/net/robotmedia/billing/example/Dungeons.java index 1e9746a..82720a0 100644 --- a/DungeonsRedux/src/net/robotmedia/billing/example/Dungeons.java +++ b/DungeonsRedux/src/net/robotmedia/billing/example/Dungeons.java @@ -144,9 +144,9 @@ private void setupWidgets() { public void onClick(View v) { if (mSelectedItem.managed != Managed.SUBSCRIPTION) { - BillingController.requestPurchase(Dungeons.this, mSelectedItem.sku, true /* confirm */); + BillingController.requestPurchase(Dungeons.this, mSelectedItem.sku, true /* confirm */, null); } else { - BillingController.requestSubscription(Dungeons.this, mSelectedItem.sku, true /* confirm */); + BillingController.requestSubscription(Dungeons.this, mSelectedItem.sku, true /* confirm */, null); } } }); From 3ca95844c560a4711bdf140050d631f9fd99c739 Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Mon, 30 Jul 2012 11:51:04 +0200 Subject: [PATCH 18/28] Log signed data on debug mode. Closes #47. --- .../src/net/robotmedia/billing/BillingController.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java index bce19e1..5b9d107 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java @@ -383,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) { From 3f09a6c5c7b58f02754a69dcc060d3d72c991d98 Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Mon, 30 Jul 2012 12:07:11 +0200 Subject: [PATCH 19/28] AbstractBillingFragment --- .../helper/AbstractBillingFragment.java | 147 ++++++++++++++++++ README.mdown | 4 +- 2 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 AndroidBillingLibrary/src/net/robotmedia/billing/helper/AbstractBillingFragment.java 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/README.mdown b/README.mdown index b2fde13..34b7bbd 100644 --- a/README.mdown +++ b/README.mdown @@ -41,7 +41,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 +63,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. From 192f429b1d81e14cfbc791ab5d8fd0aa2c142272 Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Mon, 30 Jul 2012 12:26:20 +0200 Subject: [PATCH 20/28] Tag subscription_monthly and subscription_yearly as subscriptions --- .../robotmedia/billing/example/auxiliary/CatalogEntry.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DungeonsRedux/src/net/robotmedia/billing/example/auxiliary/CatalogEntry.java b/DungeonsRedux/src/net/robotmedia/billing/example/auxiliary/CatalogEntry.java index 32600bb..733c905 100644 --- a/DungeonsRedux/src/net/robotmedia/billing/example/auxiliary/CatalogEntry.java +++ b/DungeonsRedux/src/net/robotmedia/billing/example/auxiliary/CatalogEntry.java @@ -33,8 +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.UNMANAGED), - new CatalogEntry("subscription_yearly", R.string.subscription_yearly, 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), From ae7253061a8ef46e1287ca1ecfac90e127f4a38f Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Mon, 30 Jul 2012 14:11:37 +0200 Subject: [PATCH 21/28] Fully qualify activity and application to simplify APK generation --- DungeonsRedux/AndroidManifest.xml | 6 +++--- .../src/net/robotmedia/billing/example/Dungeons.java | 2 +- .../robotmedia/billing/example/auxiliary/CatalogEntry.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/DungeonsRedux/AndroidManifest.xml b/DungeonsRedux/AndroidManifest.xml index 84a1291..c9488c2 100644 --- a/DungeonsRedux/AndroidManifest.xml +++ b/DungeonsRedux/AndroidManifest.xml @@ -1,14 +1,14 @@ + package="net.robotmedia.billing.dungeons.redux"> - - + + diff --git a/DungeonsRedux/src/net/robotmedia/billing/example/Dungeons.java b/DungeonsRedux/src/net/robotmedia/billing/example/Dungeons.java index 82720a0..99e0bf6 100644 --- a/DungeonsRedux/src/net/robotmedia/billing/example/Dungeons.java +++ b/DungeonsRedux/src/net/robotmedia/billing/example/Dungeons.java @@ -20,7 +20,7 @@ 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; diff --git a/DungeonsRedux/src/net/robotmedia/billing/example/auxiliary/CatalogEntry.java b/DungeonsRedux/src/net/robotmedia/billing/example/auxiliary/CatalogEntry.java index 733c905..08c2a14 100644 --- a/DungeonsRedux/src/net/robotmedia/billing/example/auxiliary/CatalogEntry.java +++ b/DungeonsRedux/src/net/robotmedia/billing/example/auxiliary/CatalogEntry.java @@ -1,6 +1,6 @@ package net.robotmedia.billing.example.auxiliary; -import net.robotmedia.billing.example.R; +import net.robotmedia.billing.dungeons.redux.R; public class CatalogEntry { From 957253a7142b4996195b7b5338d68cf35dbd03bf Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Mon, 30 Jul 2012 14:12:27 +0200 Subject: [PATCH 22/28] getAPIVersion and RequestSubscription --- .../robotmedia/billing/BillingRequest.java | 58 ++++++++++++------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingRequest.java b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingRequest.java index d9feda9..8362559 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingRequest.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingRequest.java @@ -23,11 +23,6 @@ import com.android.vending.billing.IMarketBillingService; public abstract class BillingRequest { - - public static final String ITEM_TYPE_INAPP = "inapp"; - public static final String ITEM_TYPE_SUBSCRIPTION = "subs"; - private static final String KEY_ITEM_TYPE = "ITEM_TYPE"; - private static final String REQUEST_TYPE_CHECK_BILLING_SUPPORTED = "CHECK_BILLING_SUPPORTED"; public static class CheckBillingSupported extends BillingRequest { @@ -49,13 +44,16 @@ protected void processOkResponse(Bundle response) { } public static class CheckSubscriptionSupported extends BillingRequest { - - private static final String KEY_API_VERSION = "API_VERSION"; 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; @@ -69,7 +67,6 @@ protected void processOkResponse(Bundle response) { @Override protected void addParams(Bundle request) { - request.putInt(KEY_API_VERSION, 2); request.putString(KEY_ITEM_TYPE, ITEM_TYPE_SUBSCRIPTION); } @@ -121,27 +118,25 @@ public String getRequestType() { @Override public boolean hasNonce() { return true; } } + public static class RequestPurchase extends BillingRequest { private String itemId; - private String itemType; private String developerPayload; private static final String KEY_ITEM_ID = "ITEM_ID"; private static final String KEY_DEVELOPER_PAYLOAD = "DEVELOPER_PAYLOAD"; private static final String KEY_PURCHASE_INTENT = "PURCHASE_INTENT"; - public RequestPurchase(String packageName, int startId, String itemId, String itemType, String developerPayload) { + public RequestPurchase(String packageName, int startId, String itemId, String developerPayload) { super(packageName, startId); this.itemId = itemId; - this.itemType = itemType; this.developerPayload = developerPayload; } @Override protected void addParams(Bundle request) { request.putString(KEY_ITEM_ID, itemId); - request.putString(KEY_ITEM_TYPE, itemType); if (developerPayload != null) { request.putString(KEY_DEVELOPER_PAYLOAD, developerPayload); } @@ -163,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 @@ -210,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; @@ -234,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; @@ -252,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); From d0345d13e549ac367e46d3331e151dc04b2d4ba5 Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Mon, 30 Jul 2012 14:12:47 +0200 Subject: [PATCH 23/28] Use RequestSubscription --- .../robotmedia/billing/BillingService.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java index 81cf230..eaa48cf 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java @@ -35,13 +35,12 @@ public class BillingService extends Service implements ServiceConnection { private static enum Action { - CHECK_BILLING_SUPPORTED, CHECK_SUBSCRIPTION_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_ITEM_TYPE = "ITEM_TYPE"; private static final String EXTRA_NONCE = "EXTRA_NONCE"; private static final String EXTRA_NOTIFY_IDS = "NOTIFY_IDS"; private static LinkedList mPendingRequests = new LinkedList(); @@ -85,15 +84,13 @@ public static void getPurchaseInformation(Context context, String[] notifyIds, l public static void requestPurchase(Context context, String itemId, String developerPayload) { final Intent intent = createIntent(context, Action.REQUEST_PURCHASE); intent.putExtra(EXTRA_ITEM_ID, itemId); - intent.putExtra(EXTRA_ITEM_TYPE, BillingRequest.ITEM_TYPE_INAPP); 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_PURCHASE); + final Intent intent = createIntent(context, Action.REQUEST_SUBSCRIPTION); intent.putExtra(EXTRA_ITEM_ID, itemId); - intent.putExtra(EXTRA_ITEM_TYPE, BillingRequest.ITEM_TYPE_SUBSCRIPTION); intent.putExtra(EXTRA_DEVELOPER_PAYLOAD, developerPayload); context.startService(intent); } @@ -198,6 +195,8 @@ private void handleCommand(Intent intent, int startId) { break; case REQUEST_PURCHASE: requestPurchase(intent, startId); + case REQUEST_SUBSCRIPTION: + requestSubscription(intent, startId); break; case GET_PURCHASE_INFORMATION: getPurchaseInformation(intent, startId); @@ -213,9 +212,16 @@ private void handleCommand(Intent intent, int startId) { private void requestPurchase(Intent intent, int startId) { final String packageName = getPackageName(); final String itemId = intent.getStringExtra(EXTRA_ITEM_ID); - final String itemType = intent.getStringExtra(EXTRA_ITEM_TYPE); final String developerPayload = intent.getStringExtra(EXTRA_DEVELOPER_PAYLOAD); - final RequestPurchase request = new RequestPurchase(packageName, startId, itemId, itemType, developerPayload); + 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); } From 8e0b9436f6f48e7580b40423b83127e5368d3d19 Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Mon, 30 Jul 2012 15:26:34 +0200 Subject: [PATCH 24/28] Missing break! --- .../src/net/robotmedia/billing/BillingService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java index eaa48cf..a37a1ae 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java @@ -195,6 +195,7 @@ private void handleCommand(Intent intent, int startId) { break; case REQUEST_PURCHASE: requestPurchase(intent, startId); + break; case REQUEST_SUBSCRIPTION: requestSubscription(intent, startId); break; From 3ecc7cad9cb6ca55d0de607e03b717876212178d Mon Sep 17 00:00:00 2001 From: hpique Date: Tue, 11 Dec 2012 16:39:28 +0100 Subject: [PATCH 25/28] Update README.mdown --- README.mdown | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.mdown b/README.mdown index 34b7bbd..a33d241 100644 --- a/README.mdown +++ b/README.mdown @@ -1,3 +1,9 @@ +Update +====== + +The In-app Billing API has been recently updated to [version 3](https://developer.android.com/google/play/billing/api.html). This library is currently for version 2 only, and will be updated in early 2013. + + Android Billing Library ======================= From 799aff4078973c06ce903be5f39176cdcbbca8ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Brais=20Gab=C3=ADn?= Date: Tue, 2 Apr 2013 19:56:35 +0300 Subject: [PATCH 26/28] Fixed a minor error in javadoc --- .../src/net/robotmedia/billing/BillingController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java index 5b9d107..cbf179e 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java @@ -547,7 +547,7 @@ public static boolean registerObserver(IBillingObserver observer) { * @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, null); From 4bbe429d7811d531ca6d478745b4eeebb17f1783 Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Mon, 12 Aug 2013 15:13:22 +0200 Subject: [PATCH 27/28] Update README.mdown --- README.mdown | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.mdown b/README.mdown index a33d241..ed03c3a 100644 --- a/README.mdown +++ b/README.mdown @@ -1,8 +1,9 @@ Update ====== -The In-app Billing API has been recently updated to [version 3](https://developer.android.com/google/play/billing/api.html). This library is currently for version 2 only, and will be updated in early 2013. +The In-app Billing API was updated to [version 3](https://developer.android.com/google/play/billing/api.html) in early 2013. This library is currently for version 2 and version 1 only. +If you're interested in helping to update it to version 3, please fork and send a pull request. Android Billing Library ======================= @@ -99,4 +100,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 From fc637bb2ed9d0c864e1b918f9b604fb140104f6d Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Sat, 18 Oct 2014 06:13:00 +0100 Subject: [PATCH 28/28] Mention v2 shut down --- README.mdown | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.mdown b/README.mdown index ed03c3a..c0db052 100644 --- a/README.mdown +++ b/README.mdown @@ -1,9 +1,10 @@ Update ====== -The In-app Billing API was updated to [version 3](https://developer.android.com/google/play/billing/api.html) in early 2013. This library is currently for version 2 and version 1 only. +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. -If you're interested in helping to update it to version 3, please fork and send a pull request. Android Billing Library =======================