diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java index 430ea83..61e898f 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java @@ -29,6 +29,7 @@ import net.robotmedia.billing.model.TransactionManager; import net.robotmedia.billing.request.BillingRequest; import net.robotmedia.billing.request.ResponseCode; +import net.robotmedia.billing.request.RestoreTransactions; import net.robotmedia.billing.utils.Compatibility; import net.robotmedia.billing.utils.Security; @@ -427,6 +428,8 @@ protected static void onPurchaseStateChanged(Context context, String signedData, } } + private static HashMap pendingRequests = new HashMap(); + /** * Called after a {@link net.robotmedia.billing.request.BillingRequest} is * sent. @@ -440,14 +443,16 @@ protected static void onRequestSent(long requestId, BillingRequest request) { if (debug) { Log.d(BillingController.class.getSimpleName(), "Request " + requestId + " of type " + request.getRequestType() + " sent"); } - if (!request.isSuccess() && request.hasNonce()) { + if (request.isSuccess()) { + pendingRequests.put(requestId, request); + } else if (request.hasNonce()) { Security.removeNonce(request.getNonce()); } } /** * Called after a {@link net.robotmedia.billing.request.BillingRequest} is - * sent. Mostly used for debugging purposes. + * sent. * * @param context * @param requestId @@ -460,6 +465,11 @@ protected static void onResponseCode(Context context, long requestId, int respon if (debug) { Log.d(BillingController.class.getSimpleName(), "Request " + requestId + " received response " + ResponseCode.valueOf(responseCode)); } + final BillingRequest request = pendingRequests.get(requestId); + if (request != null) { + pendingRequests.remove(requestId); + request.onResponseCode(responseCode); + } } /** @@ -639,4 +649,14 @@ private static boolean verifyNonce(JSONObject data) { } } + /** + * + * @param restoreTransactions + */ + public static void onTransactionsRestored(RestoreTransactions restoreTransactions) { + for (IBillingObserver o : observers) { + o.onTransactionsRestored(); + } + } + } diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java index 6438520..a217cea 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingService.java @@ -23,6 +23,7 @@ import net.robotmedia.billing.request.RequestPurchase; import net.robotmedia.billing.request.RestoreTransactions; import net.robotmedia.billing.request.BillingRequest; +import net.robotmedia.billing.utils.Compatibility; import com.android.vending.billing.IMarketBillingService; @@ -163,10 +164,10 @@ public void onStart(Intent intent, int startId) { handleCommand(intent); } - @Override + // @Override // Avoid compile errors on pre-2.0 public int onStartCommand(Intent intent, int flags, int startId) { handleCommand(intent); - return START_NOT_STICKY; + return Compatibility.START_NOT_STICKY; } private void handleCommand(Intent intent) { diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/IBillingObserver.java b/AndroidBillingLibrary/src/net/robotmedia/billing/IBillingObserver.java index 2dbadc9..f8b8380 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/IBillingObserver.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/IBillingObserver.java @@ -55,4 +55,9 @@ public interface IBillingObserver { */ public void onPurchaseRefunded(String itemId); + /** + * Called after a restore transactions request has been successfully received by the server. + */ + public void onTransactionsRestored(); + } diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/model/Transaction.java b/AndroidBillingLibrary/src/net/robotmedia/billing/model/Transaction.java index f81e173..54f2daa 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/model/Transaction.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/model/Transaction.java @@ -22,8 +22,8 @@ public class Transaction { public enum PurchaseState { // Responses to requestPurchase or restoreTransactions. - PURCHASED, // 0: The charge failed on the server. - CANCELLED, // 1: User was charged for the order. + 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. // Converts from an ordinal value to the PurchaseState diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/request/BillingRequest.java b/AndroidBillingLibrary/src/net/robotmedia/billing/request/BillingRequest.java index b7d5b65..45a254c 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/request/BillingRequest.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/request/BillingRequest.java @@ -27,33 +27,53 @@ public abstract class BillingRequest { 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"; - private static final String KEY_REQUEST_ID = "REQUEST_ID"; + protected static final String KEY_REQUEST_ID = "REQUEST_ID"; private static final String KEY_NONCE = "NONCE"; public static final long IGNORE_REQUEST_ID = -1; - public abstract String getRequestType(); - protected abstract void addParams(Bundle request); - protected abstract void processOkResponse(Bundle response); + private String packageName; + private boolean success; + private long nonce; - public boolean hasNonce() { - return false; + public BillingRequest(String packageName) { + this.packageName = packageName; } - private String packageName; - private boolean success; - private long nonce; + protected void addParams(Bundle request) { + // Do nothing by default + } public long getNonce() { return nonce; } - public void setNonce(long nonce) { - this.nonce = nonce; - } - public BillingRequest(String packageName) { - this.packageName = packageName; + public abstract String getRequestType(); + + public boolean hasNonce() { + return false; + } + public boolean isSuccess() { + return success; + } + protected Bundle makeRequestBundle() { + final Bundle request = new Bundle(); + request.putString(KEY_BILLING_REQUEST, getRequestType()); + request.putInt(KEY_API_VERSION, 1); + request.putString(KEY_PACKAGE_NAME, packageName); + if (hasNonce()) { + request.putLong(KEY_NONCE, nonce); + } + return request; } - public long run(IMarketBillingService mService) throws RemoteException { + public void onResponseCode(int responseCode) { + // Do nothing by default + } + + protected void processOkResponse(Bundle response) { + // Do nothing by default + } + + public long run(IMarketBillingService mService) throws RemoteException { final Bundle request = makeRequestBundle(); addParams(request); final Bundle response = mService.sendBillingRequest(request); @@ -64,18 +84,11 @@ public long run(IMarketBillingService mService) throws RemoteException { return IGNORE_REQUEST_ID; } } - - protected Bundle makeRequestBundle() { - final Bundle request = new Bundle(); - request.putString(KEY_BILLING_REQUEST, getRequestType()); - request.putInt(KEY_API_VERSION, 1); - request.putString(KEY_PACKAGE_NAME, packageName); - if (hasNonce()) { - request.putLong(KEY_NONCE, nonce); - } - return request; - } - + + public void setNonce(long nonce) { + this.nonce = nonce; + } + protected boolean validateResponse(Bundle response) { final int responseCode = response.getInt(KEY_RESPONSE_CODE); success = ResponseCode.isResponseOk(responseCode); @@ -85,8 +98,4 @@ protected boolean validateResponse(Bundle response) { return success; } - public boolean isSuccess() { - return success; - } - } \ No newline at end of file diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/request/CheckBillingSupported.java b/AndroidBillingLibrary/src/net/robotmedia/billing/request/CheckBillingSupported.java index c4b773b..05fb7ed 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/request/CheckBillingSupported.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/request/CheckBillingSupported.java @@ -29,9 +29,6 @@ public String getRequestType() { return "CHECK_BILLING_SUPPORTED"; } - @Override - protected void addParams(Bundle request) {} - @Override protected void processOkResponse(Bundle response) { final boolean supported = this.isSuccess(); diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/request/ConfirmNotifications.java b/AndroidBillingLibrary/src/net/robotmedia/billing/request/ConfirmNotifications.java index c10eaed..0034ece 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/request/ConfirmNotifications.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/request/ConfirmNotifications.java @@ -37,8 +37,5 @@ public String getRequestType() { protected void addParams(Bundle request) { request.putStringArray(KEY_NOTIFY_IDS, notifyIds); } - - @Override - protected void processOkResponse(Bundle response) {} } diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/request/GetPurchaseInformation.java b/AndroidBillingLibrary/src/net/robotmedia/billing/request/GetPurchaseInformation.java index 1feab1b..b602c72 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/request/GetPurchaseInformation.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/request/GetPurchaseInformation.java @@ -39,9 +39,5 @@ public String getRequestType() { protected void addParams(Bundle request) { request.putStringArray(KEY_NOTIFY_IDS, notifyIds); } - - @Override - protected void processOkResponse(Bundle response) { - } } diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/request/RestoreTransactions.java b/AndroidBillingLibrary/src/net/robotmedia/billing/request/RestoreTransactions.java index 36a6238..12cd522 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/request/RestoreTransactions.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/request/RestoreTransactions.java @@ -15,7 +15,7 @@ package net.robotmedia.billing.request; -import android.os.Bundle; +import net.robotmedia.billing.BillingController; public class RestoreTransactions extends BillingRequest { @@ -29,11 +29,13 @@ public RestoreTransactions(String packageName) { public String getRequestType() { return "RESTORE_TRANSACTIONS"; } - - @Override - protected void addParams(Bundle request) {} - + @Override - protected void processOkResponse(Bundle response) {} + public void onResponseCode(int responseCode) { + super.onResponseCode(responseCode); + if (ResponseCode.isResponseOk(responseCode)) { + BillingController.onTransactionsRestored(this); + } + } } diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/utils/Compatibility.java b/AndroidBillingLibrary/src/net/robotmedia/billing/utils/Compatibility.java index 40f1f10..9942636 100644 --- a/AndroidBillingLibrary/src/net/robotmedia/billing/utils/Compatibility.java +++ b/AndroidBillingLibrary/src/net/robotmedia/billing/utils/Compatibility.java @@ -15,15 +15,18 @@ package net.robotmedia.billing.utils; +import java.lang.reflect.Field; import java.lang.reflect.Method; import android.app.Activity; +import android.app.Service; import android.content.Intent; import android.content.IntentSender; import android.util.Log; public class Compatibility { private static Method startIntentSender; + public static int START_NOT_STICKY; @SuppressWarnings("rawtypes") private static final Class[] START_INTENT_SENDER_SIG = new Class[] { IntentSender.class, Intent.class, int.class, int.class, int.class @@ -34,7 +37,13 @@ public class Compatibility { }; private static void initCompatibility() { - try { + try { + final Field field = Service.class.getField("START_NOT_STICKY"); + START_NOT_STICKY = field.getInt(null); + } catch (Exception e) { + START_NOT_STICKY = 2; + } + try { startIntentSender = Activity.class.getMethod("startIntentSender", START_INTENT_SENDER_SIG); } catch (SecurityException e) { diff --git a/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingControllerTest.java b/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingControllerTest.java index 570656c..87ff194 100644 --- a/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingControllerTest.java +++ b/AndroidBillingLibraryTest/src/net/robotmedia/billing/BillingControllerTest.java @@ -15,14 +15,19 @@ package net.robotmedia.billing; +import java.util.HashSet; import java.util.List; +import java.util.Set; 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.request.RestoreTransactions; +import android.app.PendingIntent; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; public class BillingControllerTest extends AndroidTestCase { @@ -99,4 +104,27 @@ public void testGetTransactionsString() throws Exception { final List transactions2 = BillingController.getTransactions(getContext(), TransactionTest.TRANSACTION_1.productId); assertEquals(transactions2.size(), 1); } + + @SmallTest + public void testOnTransactionRestored() throws Exception { + final Set flags = new HashSet(); + final IBillingObserver observer = new IBillingObserver() { + + @Override + public void onTransactionsRestored() { + flags.add(true); + } + + public void onPurchaseRefunded(String itemId) {} + public void onPurchaseIntent(String itemId, PendingIntent purchaseIntent) {} + public void onPurchaseExecuted(String itemId) {} + public void onPurchaseCancelled(String itemId) {} + public void onBillingChecked(boolean supported) {} + }; + BillingController.registerObserver(observer); + final RestoreTransactions request = new RestoreTransactions(getContext().getPackageName()); + BillingController.onTransactionsRestored(request); + assertEquals(flags.size(), 1); + BillingController.unregisterObserver(observer); + } } diff --git a/AndroidBillingLibraryTest/src/net/robotmedia/billing/MockBillingActivity.java b/AndroidBillingLibraryTest/src/net/robotmedia/billing/MockBillingActivity.java index 0bf1388..7069753 100644 --- a/AndroidBillingLibraryTest/src/net/robotmedia/billing/MockBillingActivity.java +++ b/AndroidBillingLibraryTest/src/net/robotmedia/billing/MockBillingActivity.java @@ -53,4 +53,9 @@ public String getPublicKey() { return null; } + @Override + public void onTransactionsRestored() { + // TODO Auto-generated method stub + } + } diff --git a/AndroidBillingLibraryTest/src/net/robotmedia/billing/utils/CompatibilityTest.java b/AndroidBillingLibraryTest/src/net/robotmedia/billing/utils/CompatibilityTest.java new file mode 100644 index 0000000..24448c8 --- /dev/null +++ b/AndroidBillingLibraryTest/src/net/robotmedia/billing/utils/CompatibilityTest.java @@ -0,0 +1,14 @@ +package net.robotmedia.billing.utils; + +import android.app.Service; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +public class CompatibilityTest extends AndroidTestCase { + + @SmallTest + public void testStartNotSticky() throws Exception { + assertEquals(Compatibility.START_NOT_STICKY, Service.START_NOT_STICKY); + } + +} diff --git a/DungeonsRedux/.classpath b/DungeonsRedux/.classpath new file mode 100644 index 0000000..430cfd8 --- /dev/null +++ b/DungeonsRedux/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/DungeonsRedux/.gitignore b/DungeonsRedux/.gitignore new file mode 100644 index 0000000..703f4e4 --- /dev/null +++ b/DungeonsRedux/.gitignore @@ -0,0 +1,2 @@ +bin +gen \ No newline at end of file diff --git a/DungeonsRedux/.project b/DungeonsRedux/.project new file mode 100644 index 0000000..d02b7a2 --- /dev/null +++ b/DungeonsRedux/.project @@ -0,0 +1,40 @@ + + + DungeonsRedux + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + + + AndroidBillingLibrary_src + 2 + _android_AndroidBillingLibrary_70afcdef/src + + + diff --git a/DungeonsRedux/AndroidManifest.xml b/DungeonsRedux/AndroidManifest.xml new file mode 100644 index 0000000..014a946 --- /dev/null +++ b/DungeonsRedux/AndroidManifest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DungeonsRedux/README b/DungeonsRedux/README new file mode 100755 index 0000000..7bd7bab --- /dev/null +++ b/DungeonsRedux/README @@ -0,0 +1,56 @@ +This package contains a sample app -Dungeons Redux- that shows how to use the Android Billing +Library (https://github.com/robotmedia/AndroidBillingLibrary). It does not intend to be an +example of how to use in-app billing in general. + +Dungeons Redux is a simplified version of the Dungeons sample app provided by Google. + +------------------------------------- +Configuring the sample application +------------------------------------- + +Before you can run the sample application, you need to add your Android Market public key to the +sample application code. This enables the application to verify the signature of the transaction +information that's returned from Android Market. To add your public key to the sample application +code, do the following: + +1. Log in to your Android Market publisher account (http://market.android.com/publish). +2. On the upper left part of the page, under your name, click Edit Profile. +3. On the Edit Profile page, scroll down to the Licensing & In-app Billing panel. +4. Copy your public key to the clipboard. +5. Open src/net/robotmedia/billing/example/Dungeons.java in the editor of your choice. +6. Search for the following line: + return "your public key here"; +7. Replace the string with your public key. +8. Save the file. + +After you add your public key to the Dungeons.java file, you can compile the application as you +normally would. + +------------------------------------- +Running the sample application +------------------------------------- + +You cannot run the sample application in the emulator. You must load the application onto a device +to run it. + +In-app billing requires version 2.3.0 of the Android Market application. To run the sample +application you must have this version (or a newer version) installed on your device. You can check +the version of the Android Market application by doing the following: + +1. Open Settings on your device and touch Applications. +2. In Application Settings, touch Manage applications. +3. Touch All to list all applications. +4. Scroll down and touch the Market application. +5. The version number appears under Market at the top of the screen. + +------------------------------------- +Additional information and resources +------------------------------------- + +To learn more about the in-app billing service, see the online documentation: + + http://developer.android.com/guide/market/billing/index.html + +To learn more about Android Billing Library, see: + + https://github.com/robotmedia/AndroidBillingLibrary \ No newline at end of file diff --git a/DungeonsRedux/default.properties b/DungeonsRedux/default.properties new file mode 100644 index 0000000..4271070 --- /dev/null +++ b/DungeonsRedux/default.properties @@ -0,0 +1,12 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "build.properties", and override values to adapt the script to your +# project structure. + +# Project target. +target=android-4 +android.library.reference.1=../AndroidBillingLibrary diff --git a/DungeonsRedux/res/drawable-hdpi/icon.png b/DungeonsRedux/res/drawable-hdpi/icon.png new file mode 100644 index 0000000..2fe7c01 Binary files /dev/null and b/DungeonsRedux/res/drawable-hdpi/icon.png differ diff --git a/DungeonsRedux/res/drawable-ldpi/icon.png b/DungeonsRedux/res/drawable-ldpi/icon.png new file mode 100644 index 0000000..68b4656 Binary files /dev/null and b/DungeonsRedux/res/drawable-ldpi/icon.png differ diff --git a/DungeonsRedux/res/drawable-mdpi/icon.png b/DungeonsRedux/res/drawable-mdpi/icon.png new file mode 100644 index 0000000..f69aa51 Binary files /dev/null and b/DungeonsRedux/res/drawable-mdpi/icon.png differ diff --git a/DungeonsRedux/res/layout/item_row.xml b/DungeonsRedux/res/layout/item_row.xml new file mode 100644 index 0000000..9accc5f --- /dev/null +++ b/DungeonsRedux/res/layout/item_row.xml @@ -0,0 +1,32 @@ + + + + + + diff --git a/DungeonsRedux/res/layout/main.xml b/DungeonsRedux/res/layout/main.xml new file mode 100644 index 0000000..40a2b9a --- /dev/null +++ b/DungeonsRedux/res/layout/main.xml @@ -0,0 +1,74 @@ + + + + + + + + + + +