From aa33c3e9781bde0cd0d1c1fb36c1416684404f62 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Fri, 2 Aug 2013 12:28:40 -0500 Subject: [PATCH] Remove billing --- astrid/AndroidManifest.xml | 18 - .../actfm/sync/ActFmPreferenceService.java | 13 +- .../astrid/actfm/sync/ActFmSyncService.java | 44 -- .../actfm/sync/ActFmSyncV2Provider.java | 8 - .../billing/IMarketBillingService.aidl | 24 - .../billing/AstridPurchaseObserver.java | 192 ------ .../com/todoroo/astrid/billing/Base64.java | 582 ---------------- .../billing/Base64DecoderException.java | 31 - .../astrid/billing/BillingActivity.java | 323 --------- .../astrid/billing/BillingConstants.java | 106 --- .../astrid/billing/BillingReceiver.java | 94 --- .../astrid/billing/BillingService.java | 628 ------------------ .../astrid/billing/PurchaseObserver.java | 165 ----- .../astrid/billing/ResponseHandler.java | 165 ----- .../com/todoroo/astrid/billing/Security.java | 84 --- .../astrid/service/MarketStrategy.java | 16 - 16 files changed, 1 insertion(+), 2492 deletions(-) delete mode 100644 astrid/src/com/android/vending/billing/IMarketBillingService.aidl delete mode 100644 astrid/src/com/todoroo/astrid/billing/AstridPurchaseObserver.java delete mode 100644 astrid/src/com/todoroo/astrid/billing/Base64.java delete mode 100644 astrid/src/com/todoroo/astrid/billing/Base64DecoderException.java delete mode 100644 astrid/src/com/todoroo/astrid/billing/BillingActivity.java delete mode 100644 astrid/src/com/todoroo/astrid/billing/BillingConstants.java delete mode 100644 astrid/src/com/todoroo/astrid/billing/BillingReceiver.java delete mode 100644 astrid/src/com/todoroo/astrid/billing/BillingService.java delete mode 100644 astrid/src/com/todoroo/astrid/billing/PurchaseObserver.java delete mode 100644 astrid/src/com/todoroo/astrid/billing/ResponseHandler.java delete mode 100644 astrid/src/com/todoroo/astrid/billing/Security.java diff --git a/astrid/AndroidManifest.xml b/astrid/AndroidManifest.xml index 8bf032198..3599e3f82 100644 --- a/astrid/AndroidManifest.xml +++ b/astrid/AndroidManifest.xml @@ -43,9 +43,6 @@ - - - @@ -612,21 +609,6 @@ - - - - - - - - - - - - - params = new ArrayList(); - params.add("purchase_token"); - params.add(purchaseToken); - params.add("product_id"); - params.add(productId); - addAbTestEventInfo(params); - params.add("token"); - params.add(token); - - actFmInvoker.invoke("premium_update_android", params.toArray(new Object[params.size()])); - Preferences.setBoolean(BillingConstants.PREF_NEEDS_SERVER_UPDATE, false); - if (onSuccess != null) { - onSuccess.run(); - } - } catch (Exception e) { - if (e instanceof ActFmServiceException) { - ActFmServiceException ae = (ActFmServiceException) e; - if (ae.result != null && ae.result.optString("status").equals("error")) { - if (ae.result.optString("code").equals("invalid_purchase_token")) { // Not a valid purchase--expired or duolicate - Preferences.setBoolean(ActFmPreferenceService.PREF_LOCAL_PREMIUM, false); - Preferences.setBoolean(BillingConstants.PREF_NEEDS_SERVER_UPDATE, false); - if (onInvalidToken != null) { - onInvalidToken.run(); - } - return; - } - } - } - Preferences.setBoolean(BillingConstants.PREF_NEEDS_SERVER_UPDATE, true); - if (onRecoverableError != null) { - onRecoverableError.run(); - } - } - } - public void setGCMRegistration(String regId) { try { String deviceId = GCMIntentService.getDeviceID(); diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncV2Provider.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncV2Provider.java index 8a43e0ec6..51d65cf97 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncV2Provider.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncV2Provider.java @@ -14,7 +14,6 @@ import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.ContextManager; import com.todoroo.andlib.utility.DialogUtilities; import com.todoroo.andlib.utility.Preferences; -import com.todoroo.astrid.billing.BillingConstants; import com.todoroo.astrid.dao.Database; import com.todoroo.astrid.dao.RemoteModelDao; import com.todoroo.astrid.service.AstridDependencyInjector; @@ -112,10 +111,6 @@ public class ActFmSyncV2Provider extends SyncV2Provider { GCMIntentService.register(ContextManager.getContext()); } - if (Preferences.getBoolean(BillingConstants.PREF_NEEDS_SERVER_UPDATE, false)) { - actFmSyncService.updateUserSubscriptionStatus(null, null, null); - } - try { JSONObject status = actFmSyncService.invoke("user_status"); //$NON-NLS-1$ if (status.has("id")) { @@ -130,9 +125,6 @@ public class ActFmSyncV2Provider extends SyncV2Provider { if (status.has("last_name")) { Preferences.setString(ActFmPreferenceService.PREF_LAST_NAME, status.optString("last_name")); } - if (status.has("premium") && !Preferences.getBoolean(BillingConstants.PREF_NEEDS_SERVER_UPDATE, false)) { - Preferences.setBoolean(ActFmPreferenceService.PREF_PREMIUM, status.optBoolean("premium")); - } if (status.has("email")) { Preferences.setString(ActFmPreferenceService.PREF_EMAIL, status.optString("email")); } diff --git a/astrid/src/com/android/vending/billing/IMarketBillingService.aidl b/astrid/src/com/android/vending/billing/IMarketBillingService.aidl deleted file mode 100644 index 6884b41f6..000000000 --- a/astrid/src/com/android/vending/billing/IMarketBillingService.aidl +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.vending.billing; - -import android.os.Bundle; - -interface IMarketBillingService { - /** Given the arguments in bundle form, returns a bundle for results. */ - Bundle sendBillingRequest(in Bundle bundle); -} diff --git a/astrid/src/com/todoroo/astrid/billing/AstridPurchaseObserver.java b/astrid/src/com/todoroo/astrid/billing/AstridPurchaseObserver.java deleted file mode 100644 index 6a347302b..000000000 --- a/astrid/src/com/todoroo/astrid/billing/AstridPurchaseObserver.java +++ /dev/null @@ -1,192 +0,0 @@ -package com.todoroo.astrid.billing; - -import android.app.Activity; -import android.content.DialogInterface; -import android.os.Handler; -import android.util.Log; - -import com.timsu.astrid.R; -import com.todoroo.andlib.service.Autowired; -import com.todoroo.andlib.service.DependencyInjectionService; -import com.todoroo.andlib.utility.DialogUtilities; -import com.todoroo.andlib.utility.Preferences; -import com.todoroo.astrid.actfm.sync.ActFmPreferenceService; -import com.todoroo.astrid.actfm.sync.ActFmSyncService; -import com.todoroo.astrid.billing.BillingConstants.PurchaseState; -import com.todoroo.astrid.billing.BillingConstants.ResponseCode; -import com.todoroo.astrid.billing.BillingService.RequestPurchase; -import com.todoroo.astrid.billing.BillingService.RestoreTransactions; - - -public abstract class AstridPurchaseObserver extends PurchaseObserver { - - @Autowired - private ActFmSyncService actFmSyncService; - - @Autowired - private ActFmPreferenceService actFmPreferenceService; - - public interface RestoreTransactionsListener { - public void restoreTransactionsResponse(ResponseCode responseCode); - } - - private RestoreTransactionsListener restoreTransactionsListener; - - /** - * A {@link PurchaseObserver} is used to get callbacks when Android Market sends - * messages to this application so that we can update the UI. - */ - public AstridPurchaseObserver(Activity activity, Handler handler) { - super(activity, handler); - DependencyInjectionService.getInstance().inject(this); - } - - public void setRestoreTransactionsListener(RestoreTransactionsListener listener) { - this.restoreTransactionsListener = listener; - } - - @Override - public void onBillingSupported(boolean supported, String type) { - if (BillingConstants.DEBUG) { - Log.i(TAG, "supported: " + supported); - } - if (type != null && type.equals(BillingConstants.ITEM_TYPE_SUBSCRIPTION)) { - if (supported) { - billingSupportedCallback(); - } else { - billingNotSupportedCallback(); - } - } else { - subscriptionsNotSupportedCallback(); - } - } - - protected abstract void billingSupportedCallback(); - - protected abstract void billingNotSupportedCallback(); - - protected abstract void subscriptionsNotSupportedCallback(); - - - @Override - public void onPurchaseStateChange(PurchaseState purchaseState, final String itemId, - int quantity, long purchaseTime, String developerPayload, final String purchaseToken) { - if (BillingConstants.DEBUG) { - Log.i(TAG, "onPurchaseStateChange() itemId: " + itemId + " " + purchaseState); - } - - Preferences.setString(BillingConstants.PREF_PRODUCT_ID, itemId); - Preferences.setString(BillingConstants.PREF_PURCHASE_TOKEN, purchaseToken); - - if (purchaseState == PurchaseState.PURCHASED) { - new Thread() { - @Override - public void run() { - Preferences.setBoolean(ActFmPreferenceService.PREF_LOCAL_PREMIUM, true); - if (actFmPreferenceService.isLoggedIn()) { - actFmSyncService.updateUserSubscriptionStatus(new Runnable() { - @Override - public void run() { // On Success - Preferences.setBoolean(ActFmPreferenceService.PREF_PREMIUM, true); - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - DialogUtilities.okDialog(mActivity, mActivity.getString(R.string.DLG_information_title), - 0, mActivity.getString(R.string.premium_success), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - mActivity.finish(); - } - }); - } - }); - } - }, new Runnable() { // On Recoverable error - @Override - public void run() { - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - DialogUtilities.okDialog(mActivity, mActivity.getString(R.string.DLG_information_title), - 0, mActivity.getString(R.string.premium_success_with_server_error), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - mActivity.finish(); - } - }); - } - }); - } - }, new Runnable() { // On invalid token - @Override - public void run() { - DialogUtilities.okDialog(mActivity, mActivity.getString(R.string.DLG_information_title), - 0, mActivity.getString(R.string.premium_verification_error), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - mActivity.finish(); - } - }); - } - } - ); - } else { - Preferences.setBoolean(BillingConstants.PREF_NEEDS_SERVER_UPDATE, true); - } - } - }.start(); - } else if (purchaseState == PurchaseState.REFUNDED) { - new Thread() { - @Override - public void run() { - Preferences.setBoolean(ActFmPreferenceService.PREF_LOCAL_PREMIUM, false); - if (actFmPreferenceService.isLoggedIn()) { - actFmSyncService.updateUserSubscriptionStatus(null, null, null); - } - } - }.start(); - } - } - - @Override - public void onRequestPurchaseResponse(RequestPurchase request, - ResponseCode responseCode) { - if (BillingConstants.DEBUG) { - Log.d(TAG, request.mProductId + ": " + responseCode); - } - if (responseCode == ResponseCode.RESULT_OK) { - if (BillingConstants.DEBUG) { - Log.i(TAG, "purchase was successfully sent to server"); - } - } else if (responseCode == ResponseCode.RESULT_USER_CANCELED) { - if (BillingConstants.DEBUG) { - Log.i(TAG, "user canceled purchase"); - } - } else { - if (BillingConstants.DEBUG) { - Log.i(TAG, "purchase failed"); - } - } - } - - @Override - public void onRestoreTransactionsResponse(RestoreTransactions request, - ResponseCode responseCode) { - if (responseCode == ResponseCode.RESULT_OK) { - if (BillingConstants.DEBUG) { - Log.d(TAG, "completed RestoreTransactions request"); - } - // Update the shared preferences so that we don't perform - // a RestoreTransactions again. - Preferences.setBoolean(BillingConstants.PREF_TRANSACTIONS_INITIALIZED, true); - } else { - if (BillingConstants.DEBUG) { - Log.d(TAG, "RestoreTransactions error: " + responseCode); - } - } - if (restoreTransactionsListener != null) { - restoreTransactionsListener.restoreTransactionsResponse(responseCode); - } - } - -} diff --git a/astrid/src/com/todoroo/astrid/billing/Base64.java b/astrid/src/com/todoroo/astrid/billing/Base64.java deleted file mode 100644 index be87fb212..000000000 --- a/astrid/src/com/todoroo/astrid/billing/Base64.java +++ /dev/null @@ -1,582 +0,0 @@ -// Portions copyright 2002, Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.todoroo.astrid.billing; - -// This code was converted from code at http://iharder.sourceforge.net/base64/ -// Lots of extraneous features were removed. -/* The original code said: - *

- * I am placing this code in the Public Domain. Do with it as you will. - * This software comes with no guarantees or warranties but with - * plenty of well-wishing instead! - * Please visit - * http://iharder.net/xmlizable - * periodically to check for updates or to contribute improvements. - *

- * - * @author Robert Harder - * @author rharder@usa.net - * @version 1.3 - */ - -/** - * Base64 converter class. This code is not a complete MIME encoder; - * it simply converts binary data to base64 data and back. - *

- *

Note {@link CharBase64} is a GWT-compatible implementation of this - * class. - */ -public class Base64 { - /** - * Specify encoding (value is {@code true}). - */ - public final static boolean ENCODE = true; - - /** - * Specify decoding (value is {@code false}). - */ - public final static boolean DECODE = false; - - /** - * The equals sign (=) as a byte. - */ - private final static byte EQUALS_SIGN = (byte) '='; - - /** - * The new line character (\n) as a byte. - */ - private final static byte NEW_LINE = (byte) '\n'; - - /** - * The 64 valid Base64 values. - */ - private final static byte[] ALPHABET = - {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', - (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', - (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', - (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', - (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', - (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', - (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', - (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', - (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', - (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', - (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', - (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', - (byte) '9', (byte) '+', (byte) '/'}; - - /** - * The 64 valid web safe Base64 values. - */ - private final static byte[] WEBSAFE_ALPHABET = - {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', - (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', - (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', - (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', - (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', - (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', - (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', - (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', - (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', - (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', - (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', - (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', - (byte) '9', (byte) '-', (byte) '_'}; - - /** - * Translates a Base64 value to either its 6-bit reconstruction value - * or a negative number indicating some other meaning. - */ - private final static byte[] DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 - -5, -5, // Whitespace: Tab and Linefeed - -9, -9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 - -9, -9, -9, -9, -9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 - 62, // Plus sign at decimal 43 - -9, -9, -9, // Decimal 44 - 46 - 63, // Slash at decimal 47 - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine - -9, -9, -9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9, -9, -9, // Decimal 62 - 64 - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' - 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' - -9, -9, -9, -9, -9, -9, // Decimal 91 - 96 - 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' - 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' - -9, -9, -9, -9, -9 // Decimal 123 - 127 - /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ - }; - - /** - * The web safe decodabet - */ - private final static byte[] WEBSAFE_DECODABET = - {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 - -5, -5, // Whitespace: Tab and Linefeed - -9, -9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 - -9, -9, -9, -9, -9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 44 - 62, // Dash '-' sign at decimal 45 - -9, -9, // Decimal 46-47 - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine - -9, -9, -9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9, -9, -9, // Decimal 62 - 64 - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' - 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' - -9, -9, -9, -9, // Decimal 91-94 - 63, // Underscore '_' at decimal 95 - -9, // Decimal 96 - 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' - 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' - -9, -9, -9, -9, -9 // Decimal 123 - 127 - /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ - }; - - // Indicates white space in encoding - private final static byte WHITE_SPACE_ENC = -5; - // Indicates equals sign in encoding - private final static byte EQUALS_SIGN_ENC = -1; - - /** - * Defeats instantiation. - */ - private Base64() { - } - - /* ******** E N C O D I N G M E T H O D S ******** */ - - /** - * Encodes up to three bytes of the array source - * and writes the resulting four Base64 bytes to destination. - * The source and destination arrays can be manipulated - * anywhere along their length by specifying - * srcOffset and destOffset. - * This method does not check to make sure your arrays - * are large enough to accommodate srcOffset + 3 for - * the source array or destOffset + 4 for - * the destination array. - * The actual number of significant bytes in your array is - * given by numSigBytes. - * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param numSigBytes the number of significant bytes in your array - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @param alphabet is the encoding alphabet - * @return the destination array - * @since 1.3 - */ - private static byte[] encode3to4(byte[] source, int srcOffset, - int numSigBytes, byte[] destination, int destOffset, byte[] alphabet) { - // 1 2 3 - // 01234567890123456789012345678901 Bit position - // --------000000001111111122222222 Array position from threeBytes - // --------| || || || | Six bit groups to index alphabet - // >>18 >>12 >> 6 >> 0 Right shift necessary - // 0x3f 0x3f 0x3f Additional AND - - // Create buffer with zero-padding if there are only one or two - // significant bytes passed in the array. - // We have to shift left 24 in order to flush out the 1's that appear - // when Java treats a value as negative that is cast from a byte to an int. - int inBuff = - (numSigBytes > 0 ? source[srcOffset] << 24 >>> 8 : 0) - | (numSigBytes > 1 ? source[srcOffset + 1] << 24 >>> 16 : 0) - | (numSigBytes > 2 ? source[srcOffset + 2] << 24 >>> 24 : 0); - - switch (numSigBytes) { - case 3: - destination[destOffset] = alphabet[inBuff >>> 18]; - destination[destOffset + 1] = alphabet[inBuff >>> 12 & 0x3f]; - destination[destOffset + 2] = alphabet[inBuff >>> 6 & 0x3f]; - destination[destOffset + 3] = alphabet[inBuff & 0x3f]; - return destination; - case 2: - destination[destOffset] = alphabet[inBuff >>> 18]; - destination[destOffset + 1] = alphabet[inBuff >>> 12 & 0x3f]; - destination[destOffset + 2] = alphabet[inBuff >>> 6 & 0x3f]; - destination[destOffset + 3] = EQUALS_SIGN; - return destination; - case 1: - destination[destOffset] = alphabet[inBuff >>> 18]; - destination[destOffset + 1] = alphabet[inBuff >>> 12 & 0x3f]; - destination[destOffset + 2] = EQUALS_SIGN; - destination[destOffset + 3] = EQUALS_SIGN; - return destination; - default: - return destination; - } // end switch - } // end encode3to4 - - /** - * Encodes a byte array into Base64 notation. - * Equivalent to calling - * {@code encodeBytes(source, 0, source.length)} - * - * @param source The data to convert - * @since 1.4 - */ - public static String encode(byte[] source) { - return encode(source, 0, source.length, ALPHABET, true); - } - - /** - * Encodes a byte array into web safe Base64 notation. - * - * @param source The data to convert - * @param doPadding is {@code true} to pad result with '=' chars - * if it does not fall on 3 byte boundaries - */ - public static String encodeWebSafe(byte[] source, boolean doPadding) { - return encode(source, 0, source.length, WEBSAFE_ALPHABET, doPadding); - } - - /** - * Encodes a byte array into Base64 notation. - * - * @param source the data to convert - * @param off offset in array where conversion should begin - * @param len length of data to convert - * @param alphabet the encoding alphabet - * @param doPadding is {@code true} to pad result with '=' chars - * if it does not fall on 3 byte boundaries - * @since 1.4 - */ - public static String encode(byte[] source, int off, int len, byte[] alphabet, - boolean doPadding) { - byte[] outBuff = encode(source, off, len, alphabet, Integer.MAX_VALUE); - int outLen = outBuff.length; - - // If doPadding is false, set length to truncate '=' - // padding characters - while (doPadding == false && outLen > 0) { - if (outBuff[outLen - 1] != '=') { - break; - } - outLen -= 1; - } - - return new String(outBuff, 0, outLen); - } - - /** - * Encodes a byte array into Base64 notation. - * - * @param source the data to convert - * @param off offset in array where conversion should begin - * @param len length of data to convert - * @param alphabet is the encoding alphabet - * @param maxLineLength maximum length of one line. - * @return the BASE64-encoded byte array - */ - public static byte[] encode(byte[] source, int off, int len, byte[] alphabet, - int maxLineLength) { - int lenDiv3 = (len + 2) / 3; // ceil(len / 3) - int len43 = lenDiv3 * 4; - byte[] outBuff = new byte[len43 // Main 4:3 - + len43 / maxLineLength]; // New lines - - int d = 0; - int e = 0; - int len2 = len - 2; - int lineLength = 0; - for (; d < len2; d += 3, e += 4) { - - // The following block of code is the same as - // encode3to4( source, d + off, 3, outBuff, e, alphabet ); - // but inlined for faster encoding (~20% improvement) - int inBuff = - source[d + off] << 24 >>> 8 - | source[d + 1 + off] << 24 >>> 16 - | source[d + 2 + off] << 24 >>> 24; - outBuff[e] = alphabet[inBuff >>> 18]; - outBuff[e + 1] = alphabet[inBuff >>> 12 & 0x3f]; - outBuff[e + 2] = alphabet[inBuff >>> 6 & 0x3f]; - outBuff[e + 3] = alphabet[inBuff & 0x3f]; - - lineLength += 4; - if (lineLength == maxLineLength) { - outBuff[e + 4] = NEW_LINE; - e++; - lineLength = 0; - } // end if: end of line - } // end for: each piece of array - - if (d < len) { - encode3to4(source, d + off, len - d, outBuff, e, alphabet); - - lineLength += 4; - if (lineLength == maxLineLength) { - // Add a last newline - outBuff[e + 4] = NEW_LINE; - e++; - } - e += 4; - } - - assert e == outBuff.length; - return outBuff; - } - - - /* ******** D E C O D I N G M E T H O D S ******** */ - - - /** - * Decodes four bytes from array source - * and writes the resulting bytes (up to three of them) - * to destination. - * The source and destination arrays can be manipulated - * anywhere along their length by specifying - * srcOffset and destOffset. - * This method does not check to make sure your arrays - * are large enough to accommodate srcOffset + 4 for - * the source array or destOffset + 3 for - * the destination array. - * This method returns the actual number of bytes that - * were converted from the Base64 encoding. - * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @param decodabet the decodabet for decoding Base64 content - * @return the number of decoded bytes converted - * @since 1.3 - */ - private static int decode4to3(byte[] source, int srcOffset, - byte[] destination, int destOffset, byte[] decodabet) { - // Example: Dk== - if (source[srcOffset + 2] == EQUALS_SIGN) { - int outBuff = - decodabet[source[srcOffset]] << 24 >>> 6 - | decodabet[source[srcOffset + 1]] << 24 >>> 12; - - destination[destOffset] = (byte) (outBuff >>> 16); - return 1; - } else if (source[srcOffset + 3] == EQUALS_SIGN) { - // Example: DkL= - int outBuff = - decodabet[source[srcOffset]] << 24 >>> 6 - | decodabet[source[srcOffset + 1]] << 24 >>> 12 - | decodabet[source[srcOffset + 2]] << 24 >>> 18; - - destination[destOffset] = (byte) (outBuff >>> 16); - destination[destOffset + 1] = (byte) (outBuff >>> 8); - return 2; - } else { - // Example: DkLE - int outBuff = - decodabet[source[srcOffset]] << 24 >>> 6 - | decodabet[source[srcOffset + 1]] << 24 >>> 12 - | decodabet[source[srcOffset + 2]] << 24 >>> 18 - | decodabet[source[srcOffset + 3]] << 24 >>> 24; - - destination[destOffset] = (byte) (outBuff >> 16); - destination[destOffset + 1] = (byte) (outBuff >> 8); - destination[destOffset + 2] = (byte) outBuff; - return 3; - } - } // end decodeToBytes - - - /** - * Decodes data from Base64 notation. - * - * @param s the string to decode (decoded in default encoding) - * @return the decoded data - * @since 1.4 - */ - public static byte[] decode(String s) throws Base64DecoderException { - byte[] bytes = s.getBytes(); - return decode(bytes, 0, bytes.length); - } - - /** - * Decodes data from web safe Base64 notation. - * Web safe encoding uses '-' instead of '+', '_' instead of '/' - * - * @param s the string to decode (decoded in default encoding) - * @return the decoded data - */ - public static byte[] decodeWebSafe(String s) throws Base64DecoderException { - byte[] bytes = s.getBytes(); - return decodeWebSafe(bytes, 0, bytes.length); - } - - /** - * Decodes Base64 content in byte array format and returns - * the decoded byte array. - * - * @param source The Base64 encoded data - * @return decoded data - * @throws Base64DecoderException - * @since 1.3 - */ - public static byte[] decode(byte[] source) throws Base64DecoderException { - return decode(source, 0, source.length); - } - - /** - * Decodes web safe Base64 content in byte array format and returns - * the decoded data. - * Web safe encoding uses '-' instead of '+', '_' instead of '/' - * - * @param source the string to decode (decoded in default encoding) - * @return the decoded data - */ - public static byte[] decodeWebSafe(byte[] source) - throws Base64DecoderException { - return decodeWebSafe(source, 0, source.length); - } - - /** - * Decodes Base64 content in byte array format and returns - * the decoded byte array. - * - * @param source the Base64 encoded data - * @param off the offset of where to begin decoding - * @param len the length of characters to decode - * @return decoded data - * @throws Base64DecoderException - * @since 1.3 - */ - public static byte[] decode(byte[] source, int off, int len) - throws Base64DecoderException { - return decode(source, off, len, DECODABET); - } - - /** - * Decodes web safe Base64 content in byte array format and returns - * the decoded byte array. - * Web safe encoding uses '-' instead of '+', '_' instead of '/' - * - * @param source the Base64 encoded data - * @param off the offset of where to begin decoding - * @param len the length of characters to decode - * @return decoded data - */ - public static byte[] decodeWebSafe(byte[] source, int off, int len) - throws Base64DecoderException { - return decode(source, off, len, WEBSAFE_DECODABET); - } - - /** - * Decodes Base64 content using the supplied decodabet and returns - * the decoded byte array. - * - * @param source the Base64 encoded data - * @param off the offset of where to begin decoding - * @param len the length of characters to decode - * @param decodabet the decodabet for decoding Base64 content - * @return decoded data - */ - - public static byte[] decode(byte[] source, int off, int len, byte[] decodabet) - throws Base64DecoderException { - int len34 = len * 3 / 4; - byte[] outBuff = new byte[2 + len34]; // Upper limit on size of output - int outBuffPosn = 0; - - byte[] b4 = new byte[4]; - int b4Posn = 0; - int i = 0; - byte sbiCrop = 0; - byte sbiDecode = 0; - for (i = 0; i < len; i++) { - sbiCrop = (byte) (source[i + off] & 0x7f); // Only the low seven bits - sbiDecode = decodabet[sbiCrop]; - - if (sbiDecode >= WHITE_SPACE_ENC) { // White space Equals sign or better - if (sbiDecode >= EQUALS_SIGN_ENC) { - // An equals sign (for padding) must not occur at position 0 or 1 - // and must be the last byte[s] in the encoded value - if (sbiCrop == EQUALS_SIGN) { - int bytesLeft = len - i; - byte lastByte = (byte) (source[len - 1 + off] & 0x7f); - if (b4Posn == 0 || b4Posn == 1) { - throw new Base64DecoderException( - "invalid padding byte '=' at byte offset " + i); - } else if (b4Posn == 3 && bytesLeft > 2 - || b4Posn == 4 && bytesLeft > 1) { - throw new Base64DecoderException( - "padding byte '=' falsely signals end of encoded value " - + "at offset " + i); - } else if (lastByte != EQUALS_SIGN && lastByte != NEW_LINE) { - throw new Base64DecoderException( - "encoded value has invalid trailing byte"); - } - break; - } - - b4[b4Posn++] = sbiCrop; - if (b4Posn == 4) { - outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet); - b4Posn = 0; - } - } - } else { - throw new Base64DecoderException("Bad Base64 input character at " + i - + ": " + source[i + off] + "(decimal)"); - } - } - - // Because web safe encoding allows non padding base64 encodes, we - // need to pad the rest of the b4 buffer with equal signs when - // b4Posn != 0. There can be at most 2 equal signs at the end of - // four characters, so the b4 buffer must have two or three - // characters. This also catches the case where the input is - // padded with EQUALS_SIGN - if (b4Posn != 0) { - if (b4Posn == 1) { - throw new Base64DecoderException("single trailing character at offset " - + (len - 1)); - } - b4[b4Posn++] = EQUALS_SIGN; - outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet); - } - - byte[] out = new byte[outBuffPosn]; - System.arraycopy(outBuff, 0, out, 0, outBuffPosn); - return out; - } -} diff --git a/astrid/src/com/todoroo/astrid/billing/Base64DecoderException.java b/astrid/src/com/todoroo/astrid/billing/Base64DecoderException.java deleted file mode 100644 index 2f28a7d27..000000000 --- a/astrid/src/com/todoroo/astrid/billing/Base64DecoderException.java +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2002, Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.todoroo.astrid.billing; - -/** - * Exception thrown when encountering an invalid Base64 input character. - * - * @author nelson - */ -public class Base64DecoderException extends Exception { - public Base64DecoderException() { - } - - public Base64DecoderException(String s) { - super(s); - } - - private static final long serialVersionUID = 1L; -} diff --git a/astrid/src/com/todoroo/astrid/billing/BillingActivity.java b/astrid/src/com/todoroo/astrid/billing/BillingActivity.java deleted file mode 100644 index 7a79d352e..000000000 --- a/astrid/src/com/todoroo/astrid/billing/BillingActivity.java +++ /dev/null @@ -1,323 +0,0 @@ -package com.todoroo.astrid.billing; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.ProgressDialog; -import android.content.DialogInterface; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.text.Html; -import android.text.Spanned; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.View; -import android.view.View.OnClickListener; -import android.webkit.WebView; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.ImageView.ScaleType; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.actionbarsherlock.app.ActionBar; -import com.actionbarsherlock.app.SherlockFragmentActivity; -import com.actionbarsherlock.view.MenuItem; -import com.timsu.astrid.R; -import com.todoroo.andlib.service.Autowired; -import com.todoroo.andlib.service.DependencyInjectionService; -import com.todoroo.andlib.utility.DialogUtilities; -import com.todoroo.andlib.utility.Preferences; -import com.todoroo.astrid.actfm.sync.ActFmPreferenceService; -import com.todoroo.astrid.billing.BillingConstants.ResponseCode; -import com.todoroo.astrid.service.ThemeService; - -import java.util.Locale; - -public class BillingActivity extends SherlockFragmentActivity implements AstridPurchaseObserver.RestoreTransactionsListener { - - private static final int DIALOG_CANNOT_CONNECT_ID = 1; - private static final int DIALOG_BILLING_NOT_SUPPORTED_ID = 2; - private static final int DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID = 3; - - private BillingService billingService; - private AstridPurchaseObserver purchaseObserver; - private Button buyMonth; - private Button buyYear; - private TextView restorePurchases; - - private ProgressDialog restoreTransactionsDialog; - - @Autowired - private ActFmPreferenceService actFmPreferenceService; - - - @Override - protected void onCreate(Bundle savedInstanceState) { - ThemeService.applyTheme(this); - DependencyInjectionService.getInstance().inject(this); - super.onCreate(savedInstanceState); - setContentView(R.layout.billing_activity); - - setupActionBar(); - - setupButtons(); - - setupText(); - - Handler handler = new Handler(); - billingService = new BillingService(); - billingService.setContext(this); - purchaseObserver = new AstridPurchaseObserver(this, handler) { - @Override - protected void billingSupportedCallback() { - restoreTransactions(false); - buyMonth.setEnabled(true); - buyYear.setEnabled(true); - restorePurchases.setEnabled(true); - } - - @Override - protected void billingNotSupportedCallback() { - showDialog(DIALOG_BILLING_NOT_SUPPORTED_ID); - } - - @Override - protected void subscriptionsNotSupportedCallback() { - showDialog(DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID); - } - }; - purchaseObserver.setRestoreTransactionsListener(this); - } - - private void setupActionBar() { - ActionBar actionBar = getSupportActionBar(); - actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.setDisplayShowTitleEnabled(false); - - actionBar.setDisplayShowCustomEnabled(true); - actionBar.setCustomView(R.layout.header_title_view); - ((TextView) actionBar.getCustomView().findViewById(R.id.title)).setText(R.string.premium_billing_title); - } - - @Override - protected void onStart() { - super.onStart(); - ResponseHandler.register(purchaseObserver); - } - - @Override - protected void onStop() { - super.onStop(); - ResponseHandler.unregister(purchaseObserver); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - billingService.unbind(); - } - - @Override - protected void onResume() { - super.onResume(); - if (!actFmPreferenceService.isLoggedIn()) { - // Prompt to log in, but this shouldn't happen anyways since we hide the entry path to this screen when not logged in - DialogUtilities.okDialog(this, getString(R.string.premium_login_prompt), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - finish(); - } - }); - } else if (!billingService.checkBillingSupported(BillingConstants.ITEM_TYPE_SUBSCRIPTION)) { - showDialog(DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID); - } else if (ActFmPreferenceService.isPremiumUser()) { - DialogUtilities.okDialog(this, getString(R.string.premium_already_subscribed), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - finish(); - } - }); - } - } - - private void setupButtons() { - buyMonth = (Button) findViewById(R.id.premium_buy_month); - buyYear = (Button) findViewById(R.id.premium_buy_year); - restorePurchases = (TextView) findViewById(R.id.check_for_purchases); - - buyMonth.setEnabled(false); - buyYear.setEnabled(false); - restorePurchases.setEnabled(false); - - if (!Preferences.getBoolean(BillingConstants.PREF_TRANSACTIONS_INITIALIZED, false)) { - restorePurchases.setVisibility(View.GONE); - } - - buyMonth.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (!billingService.requestPurchase(BillingConstants.PRODUCT_ID_MONTHLY, - BillingConstants.ITEM_TYPE_SUBSCRIPTION, null)) { - showDialog(DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID); - } - } - }); - - buyYear.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (!billingService.requestPurchase(BillingConstants.PRODUCT_ID_YEARLY, - BillingConstants.ITEM_TYPE_SUBSCRIPTION, null)) { - showDialog(DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID); - } - } - }); - - restorePurchases.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - restoreTransactions(true); - } - }); - } - - - private void setupText() { - int[] bullets = new int[]{R.string.premium_description_1, /* R.string.premium_description_2,*/ R.string.premium_description_3, - R.string.premium_description_4, R.string.premium_description_5, R.string.premium_description_6 - }; - - StringBuilder builder = new StringBuilder("

    "); - - for (int bullet : bullets) { - String curr = getString(bullet); - if (curr.contains("\n")) { - curr = curr.replace("\n", "
    "); - } - builder.append("
  • ").append(curr); - - builder.append("
  • \n"); - } - - builder.append("
"); - - WebView list = (WebView) findViewById(R.id.premium_bullets); - list.loadDataWithBaseURL("file:///android_asset/", builder.toString(), "text/html", "utf-8", null); - list.setBackgroundColor(0); - - View speechBubbleBackground = findViewById(R.id.speech_bubble_container); - speechBubbleBackground.setBackgroundColor(0); - - DisplayMetrics metrics = getResources().getDisplayMetrics(); - ImageView icon = (ImageView) findViewById(R.id.astridIcon); - - int dim = (int) (80 * metrics.density); - icon.setLayoutParams(new LinearLayout.LayoutParams(dim, dim)); - icon.setScaleType(ScaleType.FIT_CENTER); - - TextView speechBubble = (TextView) findViewById(R.id.reminder_message); - - // Construct speech bubble text - String html = String.format("%s %s", - getString(R.string.premium_speech_bubble_1), - Integer.toHexString(getResources().getColor(R.color.red_theme_color) - 0xff000000), - getString(R.string.premium_speech_bubble_2)); - Spanned spanned = Html.fromHtml(html); - speechBubble.setText(spanned); - speechBubble.setTextSize(17); - } - - /** - * Replaces the language and/or country of the device into the given string. - * The pattern "%lang%" will be replaced by the device's language code and - * the pattern "%region%" will be replaced with the device's country code. - * - * @param str the string to replace the language/country within - * @return a string containing the local language and region codes - */ - - private String replaceLanguageAndRegion(String str) { - // Substitute language and or region if present in string - if (str.contains("%lang%") || str.contains("%region%")) { - Locale locale = Locale.getDefault(); - str = str.replace("%lang%", locale.getLanguage().toLowerCase()); - str = str.replace("%region%", locale.getCountry().toLowerCase()); - } - return str; - } - - @Override - protected Dialog onCreateDialog(int id) { - switch (id) { - case DIALOG_CANNOT_CONNECT_ID: - return createDialog(R.string.cannot_connect_title, - R.string.cannot_connect_message); - case DIALOG_BILLING_NOT_SUPPORTED_ID: - return createDialog(R.string.billing_not_supported_title, - R.string.billing_not_supported_message); - case DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID: - return createDialog(R.string.subscriptions_not_supported_title, - R.string.subscriptions_not_supported_message); - default: - return null; - } - } - - - private Dialog createDialog(int titleId, int messageId) { - String helpUrl = "http://market.android.com/support/bin/answer.py?answer=1050566&hl=%lang%&dl=%region%"; - helpUrl = replaceLanguageAndRegion(helpUrl); - if (BillingConstants.DEBUG) { - Log.i("billing-activity-url", helpUrl); //$NON-NLS-1$ - } - final Uri helpUri = Uri.parse(helpUrl); - - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(titleId) - .setIcon(android.R.drawable.stat_sys_warning) - .setMessage(messageId) - .setCancelable(false) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - finish(); - } - }) - .setNegativeButton(R.string.subscriptions_learn_more, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - Intent intent = new Intent(Intent.ACTION_VIEW, helpUri); - startActivity(intent); - } - }); - return builder.create(); - } - - private void restoreTransactions(boolean force) { - boolean initialized = Preferences.getBoolean(BillingConstants.PREF_TRANSACTIONS_INITIALIZED, false); - if (!initialized || force) { - billingService.restoreTransactions(); - } - if (force) { - restoreTransactionsDialog = DialogUtilities.progressDialog(this, getString(R.string.premium_checking_for_purchases)); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - finish(); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - public void restoreTransactionsResponse(ResponseCode responseCode) { - DialogUtilities.dismissDialog(this, restoreTransactionsDialog); - restoreTransactionsDialog = null; - } -} diff --git a/astrid/src/com/todoroo/astrid/billing/BillingConstants.java b/astrid/src/com/todoroo/astrid/billing/BillingConstants.java deleted file mode 100644 index 7a3783f77..000000000 --- a/astrid/src/com/todoroo/astrid/billing/BillingConstants.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.todoroo.astrid.billing; - -import com.todoroo.astrid.actfm.sync.ActFmPreferenceService; -import com.todoroo.astrid.utility.Constants; - - - -public class BillingConstants { - - /** - * This is the action we use to bind to the MarketBillingService. - */ - public static final String MARKET_BILLING_SERVICE_ACTION = "com.android.vending.billing.MarketBillingService.BIND"; - - // Intent actions that we send from the BillingReceiver to the - // BillingService. Defined by this application. - public static final String ACTION_CONFIRM_NOTIFICATION = "com.timsu.astrid.subscriptions.CONFIRM_NOTIFICATION"; - public static final String ACTION_GET_PURCHASE_INFORMATION = "com.timsu.astrid.subscriptions.GET_PURCHASE_INFORMATION"; - public static final String ACTION_RESTORE_TRANSACTIONS = "com.timsu.astrid.subscriptions.RESTORE_TRANSACTIONS"; - - // Intent actions that we receive in the BillingReceiver from Market. - // These are defined by Market and cannot be changed. - public static final String ACTION_NOTIFY = "com.android.vending.billing.IN_APP_NOTIFY"; - public static final String ACTION_RESPONSE_CODE = "com.android.vending.billing.RESPONSE_CODE"; - public static final String ACTION_PURCHASE_STATE_CHANGED = "com.android.vending.billing.PURCHASE_STATE_CHANGED"; - - // These are the names of the extras that are passed in an intent from - // Market to this application and cannot be changed. - public static final String NOTIFICATION_ID = "notification_id"; - public static final String INAPP_SIGNED_DATA = "inapp_signed_data"; - public static final String INAPP_SIGNATURE = "inapp_signature"; - public static final String INAPP_REQUEST_ID = "request_id"; - public static final String INAPP_RESPONSE_CODE = "response_code"; - - // These are the names of the fields in the request bundle. - public static final String BILLING_REQUEST_METHOD = "BILLING_REQUEST"; - public static final String BILLING_REQUEST_API_VERSION = "API_VERSION"; - public static final String BILLING_REQUEST_PACKAGE_NAME = "PACKAGE_NAME"; - public static final String BILLING_REQUEST_ITEM_ID = "ITEM_ID"; - public static final String BILLING_REQUEST_ITEM_TYPE = "ITEM_TYPE"; - public static final String BILLING_REQUEST_DEVELOPER_PAYLOAD = "DEVELOPER_PAYLOAD"; - public static final String BILLING_REQUEST_NOTIFY_IDS = "NOTIFY_IDS"; - public static final String BILLING_REQUEST_NONCE = "NONCE"; - - public static final String BILLING_RESPONSE_RESPONSE_CODE = "RESPONSE_CODE"; - public static final String BILLING_RESPONSE_PURCHASE_INTENT = "PURCHASE_INTENT"; - public static final String BILLING_RESPONSE_REQUEST_ID = "REQUEST_ID"; - public static final long BILLING_RESPONSE_INVALID_REQUEST_ID = -1; - - // These are the types supported in the IAB v2 - public static final String ITEM_TYPE_INAPP = "inapp"; - public static final String ITEM_TYPE_SUBSCRIPTION = "subs"; - - - public static final String PRODUCT_ID_MONTHLY = "com.timsu.astrid.premium_monthly"; - public static final String PRODUCT_ID_YEARLY = "com.timsu.astrid.premium_yearly"; - - public static final String PREF_PRODUCT_ID = ActFmPreferenceService.IDENTIFIER + "_inapp_product_id"; - public static final String PREF_PURCHASE_TOKEN = ActFmPreferenceService.IDENTIFIER + "_inapp_purchase_token"; - public static final String PREF_NEEDS_SERVER_UPDATE = ActFmPreferenceService.IDENTIFIER + "_inapp_needs_server_update"; - public static final String PREF_TRANSACTIONS_INITIALIZED = "premium_transactions_initialized"; //$NON-NLS-1$ - - public static final char PUB_KEY_OBFUSCATION_CHAR = '!'; - public static final char PUB_KEY_REPLACE_CHAR = 'B'; - public static final String PUB_KEY_OBFUSCATED = "pubkey"; - - public static final boolean DEBUG = false || Constants.DEBUG; - - // The response codes for a request, defined by Android Market. - public enum ResponseCode { - RESULT_OK, - RESULT_USER_CANCELED, - RESULT_SERVICE_UNAVAILABLE, - RESULT_BILLING_UNAVAILABLE, - RESULT_ITEM_UNAVAILABLE, - RESULT_DEVELOPER_ERROR, - RESULT_ERROR; - - // Converts from an ordinal value to the ResponseCode - public static ResponseCode valueOf(int index) { - ResponseCode[] values = ResponseCode.values(); - if (index < 0 || index >= values.length) { - return RESULT_ERROR; - } - return values[index]; - } - } - - // The possible states of an in-app purchase, as defined by Android Market. - public enum PurchaseState { - // Responses to requestPurchase or restoreTransactions. - PURCHASED, // User was charged for the order. - CANCELED, // The charge failed on the server. (NOT THE SAME AS CANCELING A SUBSCRIPTION) - REFUNDED, // User received a refund for the order. - EXPIRED; // Subscription expired due to non-payment or cancellation - - // Converts from an ordinal value to the PurchaseState - public static PurchaseState valueOf(int index) { - PurchaseState[] values = PurchaseState.values(); - if (index < 0 || index >= values.length) { - return CANCELED; - } - return values[index]; - } - } -} diff --git a/astrid/src/com/todoroo/astrid/billing/BillingReceiver.java b/astrid/src/com/todoroo/astrid/billing/BillingReceiver.java deleted file mode 100644 index 50a493ce8..000000000 --- a/astrid/src/com/todoroo/astrid/billing/BillingReceiver.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.todoroo.astrid.billing; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.util.Log; - -import com.todoroo.astrid.billing.BillingConstants.ResponseCode; - -public class BillingReceiver extends BroadcastReceiver { - private static final String TAG = "billing-receiver"; //$NON-NLS-1$ - - /** - * This is the entry point for all asynchronous messages sent from Android Market to - * the application. This method forwards the messages on to the - * {@link BillingService}, which handles the communication back to Android Market. - * The {@link BillingService} also reports state changes back to the application through - * the {@link ResponseHandler}. - */ - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (BillingConstants.ACTION_PURCHASE_STATE_CHANGED.equals(action)) { - String signedData = intent.getStringExtra(BillingConstants.INAPP_SIGNED_DATA); - String signature = intent.getStringExtra(BillingConstants.INAPP_SIGNATURE); - purchaseStateChanged(context, signedData, signature); - } else if (BillingConstants.ACTION_NOTIFY.equals(action)) { - String notifyId = intent.getStringExtra(BillingConstants.NOTIFICATION_ID); - if (BillingConstants.DEBUG) { - Log.i(TAG, "notifyId: " + notifyId); //$NON-NLS-1$ - } - notify(context, notifyId); - } else if (BillingConstants.ACTION_RESPONSE_CODE.equals(action)) { - long requestId = intent.getLongExtra(BillingConstants.INAPP_REQUEST_ID, -1); - int responseCodeIndex = intent.getIntExtra(BillingConstants.INAPP_RESPONSE_CODE, - ResponseCode.RESULT_ERROR.ordinal()); - checkResponseCode(context, requestId, responseCodeIndex); - } else { - Log.w(TAG, "unexpected action: " + action); //$NON-NLS-1$ - } - } - - /** - * This is called when Android Market sends information about a purchase state - * change. The signedData parameter is a plaintext JSON string that is - * signed by the server with the developer's private key. The signature - * for the signed data is passed in the signature parameter. - * - * @param context the context - * @param signedData the (unencrypted) JSON string - * @param signature the signature for the signedData - */ - private void purchaseStateChanged(Context context, String signedData, String signature) { - Intent intent = new Intent(BillingConstants.ACTION_PURCHASE_STATE_CHANGED); - intent.setClass(context, BillingService.class); - intent.putExtra(BillingConstants.INAPP_SIGNED_DATA, signedData); - intent.putExtra(BillingConstants.INAPP_SIGNATURE, signature); - context.startService(intent); - } - - /** - * This is called when Android Market sends a "notify" message indicating that transaction - * information is available. The request includes a nonce (random number used once) that - * we generate and Android Market signs and sends back to us with the purchase state and - * other transaction details. This BroadcastReceiver cannot bind to the - * MarketBillingService directly so it starts the {@link BillingService}, which does the - * actual work of sending the message. - * - * @param context the context - * @param notifyId the notification ID - */ - private void notify(Context context, String notifyId) { - Intent intent = new Intent(BillingConstants.ACTION_GET_PURCHASE_INFORMATION); - intent.setClass(context, BillingService.class); - intent.putExtra(BillingConstants.NOTIFICATION_ID, notifyId); - context.startService(intent); - } - - /** - * This is called when Android Market sends a server response code. The BillingService can - * then report the status of the response if desired. - * - * @param context the context - * @param requestId the request ID that corresponds to a previous request - * @param responseCodeIndex the ResponseCode ordinal value for the request - */ - private void checkResponseCode(Context context, long requestId, int responseCodeIndex) { - Intent intent = new Intent(BillingConstants.ACTION_RESPONSE_CODE); - intent.setClass(context, BillingService.class); - intent.putExtra(BillingConstants.INAPP_REQUEST_ID, requestId); - intent.putExtra(BillingConstants.INAPP_RESPONSE_CODE, responseCodeIndex); - context.startService(intent); - } -} diff --git a/astrid/src/com/todoroo/astrid/billing/BillingService.java b/astrid/src/com/todoroo/astrid/billing/BillingService.java deleted file mode 100644 index 189e6fee5..000000000 --- a/astrid/src/com/todoroo/astrid/billing/BillingService.java +++ /dev/null @@ -1,628 +0,0 @@ -package com.todoroo.astrid.billing; - -import android.app.PendingIntent; -import android.app.Service; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.Bundle; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.android.vending.billing.IMarketBillingService; -import com.todoroo.astrid.billing.BillingConstants.PurchaseState; -import com.todoroo.astrid.billing.BillingConstants.ResponseCode; -import com.todoroo.astrid.billing.Security.VerifiedPurchase; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedList; - - -public class BillingService extends Service implements ServiceConnection { - private static final String TAG = "billing-service"; - - /** - * The service connection to the remote MarketBillingService. - */ - private static IMarketBillingService mService; - - /** - * The list of requests that are pending while we are waiting for the - * connection to the MarketBillingService to be established. - */ - private static LinkedList mPendingRequests = new LinkedList(); - - /** - * The list of requests that we have sent to Android Market but for which we have - * not yet received a response code. The HashMap is indexed by the - * request Id that each request receives when it executes. - */ - private static HashMap mSentRequests = - new HashMap(); - - /** - * The base class for all requests that use the MarketBillingService. - * Each derived class overrides the run() method to call the appropriate - * service interface. If we are already connected to the MarketBillingService, - * then we call the run() method directly. Otherwise, we bind - * to the service and save the request on a queue to be run later when - * the service is connected. - */ - abstract class BillingRequest { - private final int mStartId; - protected long mRequestId; - - public BillingRequest(int startId) { - mStartId = startId; - } - - public int getStartId() { - return mStartId; - } - - /** - * Run the request, starting the connection if necessary. - * - * @return true if the request was executed or queued; false if there - * was an error starting the connection - */ - public boolean runRequest() { - if (runIfConnected()) { - return true; - } - - if (bindToMarketBillingService()) { - // Add a pending request to run when the service is connected. - mPendingRequests.add(this); - return true; - } - return false; - } - - /** - * Try running the request directly if the service is already connected. - * - * @return true if the request ran successfully; false if the service - * is not connected or there was an error when trying to use it - */ - public boolean runIfConnected() { - if (BillingConstants.DEBUG) { - Log.d(TAG, getClass().getSimpleName()); - } - if (mService != null) { - try { - mRequestId = run(); - if (BillingConstants.DEBUG) { - Log.d(TAG, "request id: " + mRequestId); - } - if (mRequestId >= 0) { - mSentRequests.put(mRequestId, this); - } - return true; - } catch (RemoteException e) { - onRemoteException(e); - } - } - return false; - } - - /** - * Called when a remote exception occurs while trying to execute the - * {@link #run()} method. The derived class can override this to - * execute exception-handling code. - * - * @param e the exception - */ - protected void onRemoteException(RemoteException e) { - Log.w(TAG, "remote billing service crashed"); - mService = null; - } - - /** - * The derived class must implement this method. - * - * @throws RemoteException - */ - abstract protected long run() throws RemoteException; - - /** - * This is called when Android Market sends a response code for this - * request. - * - * @param responseCode the response code - */ - protected void responseCodeReceived(ResponseCode responseCode) { - // - } - - protected Bundle makeRequestBundle(String method) { - Bundle request = new Bundle(); - request.putString(BillingConstants.BILLING_REQUEST_METHOD, method); - request.putInt(BillingConstants.BILLING_REQUEST_API_VERSION, 2); - request.putString(BillingConstants.BILLING_REQUEST_PACKAGE_NAME, getPackageName()); - return request; - } - - protected void logResponseCode(String method, Bundle response) { - ResponseCode responseCode = ResponseCode.valueOf( - response.getInt(BillingConstants.BILLING_RESPONSE_RESPONSE_CODE)); - if (BillingConstants.DEBUG) { - Log.e(TAG, method + " received " + responseCode.toString()); - } - } - } - - /** - * Wrapper class that checks if in-app billing is supported. - *

- * Note: Support for subscriptions implies support for one-time purchases. However, the opposite - * is not true. - *

- * Developers may want to perform two checks if both one-time and subscription products are - * available. - */ - class CheckBillingSupported extends BillingRequest { - public String mProductType = null; - - /** - * Constructor - *

- * Note: Support for subscriptions implies support for one-time purchases. However, the - * opposite is not true. - *

- * Developers may want to perform two checks if both one-time and subscription products are - * available. - * - * @pram itemType Either BillingConstants.ITEM_TYPE_INAPP or BillingConstants.ITEM_TYPE_SUBSCRIPTION, indicating - * the type of item support is being checked for. - */ - public CheckBillingSupported(String itemType) { - super(-1); - mProductType = itemType; - } - - @Override - protected long run() throws RemoteException { - Bundle request = makeRequestBundle("CHECK_BILLING_SUPPORTED"); - if (mProductType != null) { - request.putString(BillingConstants.BILLING_REQUEST_ITEM_TYPE, mProductType); - } - Bundle response = mService.sendBillingRequest(request); - int responseCode = response.getInt(BillingConstants.BILLING_RESPONSE_RESPONSE_CODE); - if (BillingConstants.DEBUG) { - Log.i(TAG, "CheckBillingSupported response code: " + - ResponseCode.valueOf(responseCode)); - } - boolean billingSupported = responseCode == ResponseCode.RESULT_OK.ordinal(); - ResponseHandler.checkBillingSupportedResponse(billingSupported, mProductType); - return BillingConstants.BILLING_RESPONSE_INVALID_REQUEST_ID; - } - } - - /** - * Wrapper class that requests a purchase. - */ - class RequestPurchase extends BillingRequest { - public final String mProductId; - public final String mDeveloperPayload; - public final String mProductType; - - /** - * Constructor - * - * @param itemId The ID of the item to be purchased. Will be assumed to be a one-time - * purchase. - * @param itemType Either BillingConstants.ITEM_TYPE_INAPP or BillingConstants.ITEM_TYPE_SUBSCRIPTION, - * indicating the type of item type support is being checked for. - * @param developerPayload Optional data. - */ - public RequestPurchase(String itemId, String itemType, String developerPayload) { - // This object is never created as a side effect of starting this - // service so we pass -1 as the startId to indicate that we should - // not stop this service after executing this request. - super(-1); - mProductId = itemId; - mDeveloperPayload = developerPayload; - mProductType = itemType; - } - - @Override - protected long run() throws RemoteException { - Bundle request = makeRequestBundle("REQUEST_PURCHASE"); - request.putString(BillingConstants.BILLING_REQUEST_ITEM_ID, mProductId); - request.putString(BillingConstants.BILLING_REQUEST_ITEM_TYPE, mProductType); - // Note that the developer payload is optional. - if (mDeveloperPayload != null) { - request.putString(BillingConstants.BILLING_REQUEST_DEVELOPER_PAYLOAD, mDeveloperPayload); - } - Bundle response = mService.sendBillingRequest(request); - PendingIntent pendingIntent - = response.getParcelable(BillingConstants.BILLING_RESPONSE_PURCHASE_INTENT); - if (pendingIntent == null) { - Log.e(TAG, "Error with requestPurchase"); - return BillingConstants.BILLING_RESPONSE_INVALID_REQUEST_ID; - } - - Intent intent = new Intent(); - ResponseHandler.buyPageIntentResponse(pendingIntent, intent); - return response.getLong(BillingConstants.BILLING_RESPONSE_REQUEST_ID, - BillingConstants.BILLING_RESPONSE_INVALID_REQUEST_ID); - } - - @Override - protected void responseCodeReceived(ResponseCode responseCode) { - ResponseHandler.responseCodeReceived(BillingService.this, this, responseCode); - } - } - - /** - * Wrapper class that confirms a list of notifications to the server. - */ - class ConfirmNotifications extends BillingRequest { - final String[] mNotifyIds; - - public ConfirmNotifications(int startId, String[] notifyIds) { - super(startId); - mNotifyIds = notifyIds; - } - - @Override - protected long run() throws RemoteException { - Bundle request = makeRequestBundle("CONFIRM_NOTIFICATIONS"); - request.putStringArray(BillingConstants.BILLING_REQUEST_NOTIFY_IDS, mNotifyIds); - Bundle response = mService.sendBillingRequest(request); - logResponseCode("confirmNotifications", response); - return response.getLong(BillingConstants.BILLING_RESPONSE_REQUEST_ID, - BillingConstants.BILLING_RESPONSE_INVALID_REQUEST_ID); - } - } - - /** - * Wrapper class that sends a GET_PURCHASE_INFORMATION message to the server. - */ - class GetPurchaseInformation extends BillingRequest { - long mNonce; - final String[] mNotifyIds; - - public GetPurchaseInformation(int startId, String[] notifyIds) { - super(startId); - mNotifyIds = notifyIds; - } - - @Override - protected long run() throws RemoteException { - mNonce = Security.generateNonce(); - - Bundle request = makeRequestBundle("GET_PURCHASE_INFORMATION"); - request.putLong(BillingConstants.BILLING_REQUEST_NONCE, mNonce); - request.putStringArray(BillingConstants.BILLING_REQUEST_NOTIFY_IDS, mNotifyIds); - Bundle response = mService.sendBillingRequest(request); - logResponseCode("getPurchaseInformation", response); - return response.getLong(BillingConstants.BILLING_RESPONSE_REQUEST_ID, - BillingConstants.BILLING_RESPONSE_INVALID_REQUEST_ID); - } - - @Override - protected void onRemoteException(RemoteException e) { - super.onRemoteException(e); - Security.removeNonce(mNonce); - } - } - - /** - * Wrapper class that sends a RESTORE_TRANSACTIONS message to the server. - */ - class RestoreTransactions extends BillingRequest { - long mNonce; - - public RestoreTransactions() { - // This object is never created as a side effect of starting this - // service so we pass -1 as the startId to indicate that we should - // not stop this service after executing this request. - super(-1); - } - - @Override - protected long run() throws RemoteException { - mNonce = Security.generateNonce(); - - Bundle request = makeRequestBundle("RESTORE_TRANSACTIONS"); - request.putLong(BillingConstants.BILLING_REQUEST_NONCE, mNonce); - Bundle response = mService.sendBillingRequest(request); - logResponseCode("restoreTransactions", response); - return response.getLong(BillingConstants.BILLING_RESPONSE_REQUEST_ID, - BillingConstants.BILLING_RESPONSE_INVALID_REQUEST_ID); - } - - @Override - protected void onRemoteException(RemoteException e) { - super.onRemoteException(e); - Security.removeNonce(mNonce); - } - - @Override - protected void responseCodeReceived(ResponseCode responseCode) { - ResponseHandler.responseCodeReceived(BillingService.this, this, responseCode); - } - } - - public BillingService() { - } - - public void setContext(Context context) { - attachBaseContext(context); - } - - /** - * We don't support binding to this service, only starting the service. - */ - @Override - public IBinder onBind(Intent intent) { - return null; - } - - @Override - public void onStart(Intent intent, int startId) { - handleCommand(intent, startId); - } - - /** - * The {@link BillingReceiver} sends messages to this service using intents. - * Each intent has an action and some extra arguments specific to that action. - * - * @param intent the intent containing one of the supported actions - * @param startId an identifier for the invocation instance of this service - */ - public void handleCommand(Intent intent, int startId) { - if (intent == null) { - return; - } - String action = intent.getAction(); - if (BillingConstants.DEBUG) { - Log.i(TAG, "handleCommand() action: " + action); - } - if (BillingConstants.ACTION_CONFIRM_NOTIFICATION.equals(action)) { - String[] notifyIds = intent.getStringArrayExtra(BillingConstants.NOTIFICATION_ID); - confirmNotifications(startId, notifyIds); - } else if (BillingConstants.ACTION_GET_PURCHASE_INFORMATION.equals(action)) { - String notifyId = intent.getStringExtra(BillingConstants.NOTIFICATION_ID); - getPurchaseInformation(startId, new String[]{notifyId}); - } else if (BillingConstants.ACTION_PURCHASE_STATE_CHANGED.equals(action)) { - String signedData = intent.getStringExtra(BillingConstants.INAPP_SIGNED_DATA); - String signature = intent.getStringExtra(BillingConstants.INAPP_SIGNATURE); - purchaseStateChanged(startId, signedData, signature); - } else if (BillingConstants.ACTION_RESPONSE_CODE.equals(action)) { - long requestId = intent.getLongExtra(BillingConstants.INAPP_REQUEST_ID, -1); - int responseCodeIndex = intent.getIntExtra(BillingConstants.INAPP_RESPONSE_CODE, - ResponseCode.RESULT_ERROR.ordinal()); - ResponseCode responseCode = ResponseCode.valueOf(responseCodeIndex); - checkResponseCode(requestId, responseCode); - } - } - - /** - * Binds to the MarketBillingService and returns true if the bind - * succeeded. - * - * @return true if the bind succeeded; false otherwise - */ - private boolean bindToMarketBillingService() { - try { - if (BillingConstants.DEBUG) { - Log.i(TAG, "binding to Market billing service"); - } - boolean bindResult = bindService( - new Intent(BillingConstants.MARKET_BILLING_SERVICE_ACTION), - this, // ServiceConnection. - Context.BIND_AUTO_CREATE); - - if (bindResult) { - return true; - } else { - Log.e(TAG, "Could not bind to service."); - } - } catch (SecurityException e) { - Log.e(TAG, "Security exception: " + e); - } - return false; - } - - /** - * Checks if in-app billing is supported. - * - * @return true if supported; false otherwise - * @pram itemType Either BillingConstants.ITEM_TYPE_INAPP or BillingConstants.ITEM_TYPE_SUBSCRIPTION, indicating the - * type of item support is being checked for. - */ - public boolean checkBillingSupported(String itemType) { - return new CheckBillingSupported(itemType).runRequest(); - } - - /** - * Requests that the given item be offered to the user for purchase. When - * the purchase succeeds (or is canceled) the {@link BillingReceiver} - * receives an intent with the action {@link BillingConstants#ACTION_NOTIFY}. - * Returns false if there was an error trying to connect to Android Market. - * - * @param productId an identifier for the item being offered for purchase - * @param itemType Either BillingConstants.ITEM_TYPE_INAPP or BillingConstants.ITEM_TYPE_SUBSCRIPTION, indicating - * the type of item type support is being checked for. - * @param developerPayload a payload that is associated with a given - * purchase, if null, no payload is sent - * @return false if there was an error connecting to Android Market - */ - public boolean requestPurchase(String productId, String itemType, String developerPayload) { - return new RequestPurchase(productId, itemType, developerPayload).runRequest(); - } - - /** - * Requests transaction information for all managed items. Call this only when the - * application is first installed or after a database wipe. Do NOT call this - * every time the application starts up. - * - * @return false if there was an error connecting to Android Market - */ - public boolean restoreTransactions() { - return new RestoreTransactions().runRequest(); - } - - /** - * Confirms receipt of a purchase state change. Each {@code notifyId} is - * an opaque identifier that came from the server. This method sends those - * identifiers back to the MarketBillingService, which ACKs them to the - * server. Returns false if there was an error trying to connect to the - * MarketBillingService. - * - * @param startId an identifier for the invocation instance of this service - * @param notifyIds a list of opaque identifiers associated with purchase - * state changes. - * @return false if there was an error connecting to Market - */ - private boolean confirmNotifications(int startId, String[] notifyIds) { - return new ConfirmNotifications(startId, notifyIds).runRequest(); - } - - /** - * Gets the purchase information. This message includes a list of - * notification IDs sent to us by Android Market, which we include in - * our request. The server responds with the purchase information, - * encoded as a JSON string, and sends that to the {@link BillingReceiver} - * in an intent with the action {@link BillingConstants#ACTION_PURCHASE_STATE_CHANGED}. - * Returns false if there was an error trying to connect to the MarketBillingService. - * - * @param startId an identifier for the invocation instance of this service - * @param notifyIds a list of opaque identifiers associated with purchase - * state changes - * @return false if there was an error connecting to Android Market - */ - private boolean getPurchaseInformation(int startId, String[] notifyIds) { - return new GetPurchaseInformation(startId, notifyIds).runRequest(); - } - - /** - * Verifies that the data was signed with the given signature, and calls - * {@link ResponseHandler#purchaseResponse(Context, PurchaseState, String, String, long)} - * for each verified purchase. - * - * @param startId an identifier for the invocation instance of this service - * @param signedData the signed JSON string (signed, not encrypted) - * @param signature the signature for the data, signed with the private key - */ - private void purchaseStateChanged(int startId, String signedData, String signature) { - ArrayList purchases; - purchases = Security.verifyPurchase(signedData, signature); - if (purchases == null) { - return; - } - - ArrayList notifyList = new ArrayList(); - for (VerifiedPurchase vp : purchases) { - if (vp.notificationId != null) { - notifyList.add(vp.notificationId); - } - ResponseHandler.purchaseResponse(this, vp.purchaseState, vp.productId, - vp.orderId, vp.purchaseTime, vp.developerPayload, vp.purchaseToken); - } - if (!notifyList.isEmpty()) { - String[] notifyIds = notifyList.toArray(new String[notifyList.size()]); - confirmNotifications(startId, notifyIds); - } - } - - /** - * This is called when we receive a response code from Android Market for a request - * that we made. This is used for reporting various errors and for - * acknowledging that an order was sent to the server. This is NOT used - * for any purchase state changes. All purchase state changes are received - * in the {@link BillingReceiver} and passed to this service, where they are - * handled in {@link #purchaseStateChanged(int, String, String)}. - * - * @param requestId a number that identifies a request, assigned at the - * time the request was made to Android Market - * @param responseCode a response code from Android Market to indicate the state - * of the request - */ - private void checkResponseCode(long requestId, ResponseCode responseCode) { - BillingRequest request = mSentRequests.get(requestId); - if (request != null) { - if (BillingConstants.DEBUG) { - Log.d(TAG, request.getClass().getSimpleName() + ": " + responseCode); - } - request.responseCodeReceived(responseCode); - } - mSentRequests.remove(requestId); - } - - /** - * Runs any pending requests that are waiting for a connection to the - * service to be established. This runs in the main UI thread. - */ - private void runPendingRequests() { - int maxStartId = -1; - BillingRequest request; - while ((request = mPendingRequests.peek()) != null) { - if (request.runIfConnected()) { - // Remove the request - mPendingRequests.remove(); - - // Remember the largest startId, which is the most recent - // request to start this service. - if (maxStartId < request.getStartId()) { - maxStartId = request.getStartId(); - } - } else { - // The service crashed, so restart it. Note that this leaves - // the current request on the queue. - bindToMarketBillingService(); - return; - } - } - - // If we get here then all the requests ran successfully. If maxStartId - // is not -1, then one of the requests started the service, so we can - // stop it now. - if (maxStartId >= 0) { - if (BillingConstants.DEBUG) { - Log.i(TAG, "stopping service, startId: " + maxStartId); - } - stopSelf(maxStartId); - } - } - - /** - * This is called when we are connected to the MarketBillingService. - * This runs in the main UI thread. - */ - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - if (BillingConstants.DEBUG) { - Log.d(TAG, "Billing service connected"); - } - mService = IMarketBillingService.Stub.asInterface(service); - runPendingRequests(); - } - - /** - * This is called when we are disconnected from the MarketBillingService. - */ - @Override - public void onServiceDisconnected(ComponentName name) { - Log.w(TAG, "Billing service disconnected"); - mService = null; - } - - /** - * Unbinds from the MarketBillingService. Call this when the application - * terminates to avoid leaking a ServiceConnection. - */ - public void unbind() { - try { - unbindService(this); - } catch (IllegalArgumentException e) { - // This might happen if the service was disconnected - } - } -} diff --git a/astrid/src/com/todoroo/astrid/billing/PurchaseObserver.java b/astrid/src/com/todoroo/astrid/billing/PurchaseObserver.java deleted file mode 100644 index a327e8af0..000000000 --- a/astrid/src/com/todoroo/astrid/billing/PurchaseObserver.java +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright 2010 Google Inc. All Rights Reserved. - -package com.todoroo.astrid.billing; - -import android.app.Activity; -import android.app.PendingIntent; -import android.app.PendingIntent.CanceledException; -import android.content.Context; -import android.content.Intent; -import android.content.IntentSender; -import android.os.Handler; -import android.util.Log; - -import com.todoroo.astrid.billing.BillingConstants.PurchaseState; -import com.todoroo.astrid.billing.BillingConstants.ResponseCode; -import com.todoroo.astrid.billing.BillingService.RequestPurchase; -import com.todoroo.astrid.billing.BillingService.RestoreTransactions; - -import java.lang.reflect.Method; - -/** - * An interface for observing changes related to purchases. The main application - * extends this class and registers an instance of that derived class with - * {@link ResponseHandler}. The main application implements the callbacks - * {@link #onBillingSupported(boolean)} and - * {@link #onPurchaseStateChange(PurchaseState, String, int, long)}. These methods - * are used to update the UI. - */ -public abstract class PurchaseObserver { - protected static final String TAG = "purchase-observer"; //$NON-NLS-1$ - protected final Activity mActivity; - private final Handler mHandler; - private Method mStartIntentSender; - private final Object[] mStartIntentSenderArgs = new Object[5]; - private static final Class[] START_INTENT_SENDER_SIG = new Class[]{ - IntentSender.class, Intent.class, int.class, int.class, int.class - }; - - public PurchaseObserver(Activity activity, Handler handler) { - mActivity = activity; - mHandler = handler; - initCompatibilityLayer(); - } - - /** - * This is the callback that is invoked when Android Market responds to the - * {@link BillingService#checkBillingSupported()} request. - * - * @param supported true if in-app billing is supported. - */ - public abstract void onBillingSupported(boolean supported, String type); - - /** - * This is the callback that is invoked when an item is purchased, - * refunded, or canceled. It is the callback invoked in response to - * calling {@link BillingService#requestPurchase(String)}. It may also - * be invoked asynchronously when a purchase is made on another device - * (if the purchase was for a Market-managed item), or if the purchase - * was refunded, or the charge was canceled. This handles the UI - * update. The database update is handled in - * {@link ResponseHandler#purchaseResponse(Context, PurchaseState, - * String, String, long)}. - * - * @param purchaseState the purchase state of the item - * @param itemId a string identifying the item (the "SKU") - * @param quantity the current quantity of this item after the purchase - * @param purchaseTime the time the product was purchased, in - * milliseconds since the epoch (Jan 1, 1970) - */ - public abstract void onPurchaseStateChange(PurchaseState purchaseState, - String itemId, int quantity, long purchaseTime, String developerPayload, String purchaseToken); - - /** - * This is called when we receive a response code from Market for a - * RequestPurchase request that we made. This is NOT used for any - * purchase state changes. All purchase state changes are received in - * {@link #onPurchaseStateChange(PurchaseState, String, int, long)}. - * This is used for reporting various errors, or if the user backed out - * and didn't purchase the item. The possible response codes are: - * RESULT_OK means that the order was sent successfully to the server. - * The onPurchaseStateChange() will be invoked later (with a - * purchase state of PURCHASED or CANCELED) when the order is - * charged or canceled. This response code can also happen if an - * order for a Market-managed item was already sent to the server. - * RESULT_USER_CANCELED means that the user didn't buy the item. - * RESULT_SERVICE_UNAVAILABLE means that we couldn't connect to the - * Android Market server (for example if the data connection is down). - * RESULT_BILLING_UNAVAILABLE means that in-app billing is not - * supported yet. - * RESULT_ITEM_UNAVAILABLE means that the item this app offered for - * sale does not exist (or is not published) in the server-side - * catalog. - * RESULT_ERROR is used for any other errors (such as a server error). - */ - public abstract void onRequestPurchaseResponse(RequestPurchase request, - ResponseCode responseCode); - - /** - * This is called when we receive a response code from Android Market for a - * RestoreTransactions request that we made. A response code of - * RESULT_OK means that the request was successfully sent to the server. - */ - public abstract void onRestoreTransactionsResponse(RestoreTransactions request, - ResponseCode responseCode); - - private void initCompatibilityLayer() { - try { - mStartIntentSender = mActivity.getClass().getMethod("startIntentSender", //$NON-NLS-1$ - START_INTENT_SENDER_SIG); - } catch (SecurityException e) { - mStartIntentSender = null; - } catch (NoSuchMethodException e) { - mStartIntentSender = null; - } - } - - void startBuyPageActivity(PendingIntent pendingIntent, Intent intent) { - if (mStartIntentSender != null) { - // This is on Android 2.0 and beyond. The in-app buy page activity - // must be on the activity stack of the application. - try { - // This implements the method call: - // mActivity.startIntentSender(pendingIntent.getIntentSender(), - // intent, 0, 0, 0); - mStartIntentSenderArgs[0] = pendingIntent.getIntentSender(); - mStartIntentSenderArgs[1] = intent; - mStartIntentSenderArgs[2] = 0; - mStartIntentSenderArgs[3] = 0; - mStartIntentSenderArgs[4] = 0; - mStartIntentSender.invoke(mActivity, mStartIntentSenderArgs); - } catch (Exception e) { - Log.e(TAG, "error starting activity", e); //$NON-NLS-1$ - } - } else { - // This is on Android version 1.6. The in-app buy page activity must be on its - // own separate activity stack instead of on the activity stack of - // the application. - try { - pendingIntent.send(mActivity, 0 /* code */, intent); - } catch (CanceledException e) { - Log.e(TAG, "error starting activity", e); //$NON-NLS-1$ - } - } - } - - /** - * Updates the UI after the database has been updated. This method runs - * in a background thread so it has to post a Runnable to run on the UI - * thread. - * - * @param purchaseState the purchase state of the item - * @param itemId a string identifying the item - * @param quantity the quantity of items in this purchase - */ - void postPurchaseStateChange(final PurchaseState purchaseState, final String itemId, - final int quantity, final long purchaseTime, final String developerPayload, final String purchaseToken) { - mHandler.post(new Runnable() { - @Override - public void run() { - onPurchaseStateChange( - purchaseState, itemId, quantity, purchaseTime, developerPayload, purchaseToken); - } - }); - } -} diff --git a/astrid/src/com/todoroo/astrid/billing/ResponseHandler.java b/astrid/src/com/todoroo/astrid/billing/ResponseHandler.java deleted file mode 100644 index 0a78a8c14..000000000 --- a/astrid/src/com/todoroo/astrid/billing/ResponseHandler.java +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright 2010 Google Inc. All Rights Reserved. - -package com.todoroo.astrid.billing; - - -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.util.Log; - -import com.todoroo.astrid.billing.BillingConstants.PurchaseState; -import com.todoroo.astrid.billing.BillingConstants.ResponseCode; -import com.todoroo.astrid.billing.BillingService.RequestPurchase; -import com.todoroo.astrid.billing.BillingService.RestoreTransactions; - -/** - * This class contains the methods that handle responses from Android Market. The - * implementation of these methods is specific to a particular application. - * The methods in this example update the database and, if the main application - * has registered a {@llink PurchaseObserver}, will also update the UI. An - * application might also want to forward some responses on to its own server, - * and that could be done here (in a background thread) but this example does - * not do that. - *

- * You should modify and obfuscate this code before using it. - */ -public class ResponseHandler { - private static final String TAG = "response-handler"; //$NON-NLS-1$ - - /** - * This is a static instance of {@link PurchaseObserver} that the - * application creates and registers with this class. The PurchaseObserver - * is used for updating the UI if the UI is visible. - */ - private static PurchaseObserver sPurchaseObserver; - - /** - * Registers an observer that updates the UI. - * - * @param observer the observer to register - */ - public static synchronized void register(PurchaseObserver observer) { - sPurchaseObserver = observer; - } - - /** - * Unregisters a previously registered observer. - * - * @param observer the previously registered observer. - */ - public static synchronized void unregister(PurchaseObserver observer) { - sPurchaseObserver = null; - } - - /** - * Notifies the application of the availability of the MarketBillingService. - * This method is called in response to the application calling - * {@link BillingService#checkBillingSupported()}. - * - * @param supported true if in-app billing is supported. - */ - public static void checkBillingSupportedResponse(boolean supported, String type) { - if (sPurchaseObserver != null) { - sPurchaseObserver.onBillingSupported(supported, type); - } - } - - /** - * Starts a new activity for the user to buy an item for sale. This method - * forwards the intent on to the PurchaseObserver (if it exists) because - * we need to start the activity on the activity stack of the application. - * - * @param pendingIntent a PendingIntent that we received from Android Market that - * will create the new buy page activity - * @param intent an intent containing a request id in an extra field that - * will be passed to the buy page activity when it is created - */ - public static void buyPageIntentResponse(PendingIntent pendingIntent, Intent intent) { - if (sPurchaseObserver == null) { - if (BillingConstants.DEBUG) { - Log.d(TAG, "UI is not running"); //$NON-NLS-1$ - } - return; - } - sPurchaseObserver.startBuyPageActivity(pendingIntent, intent); - } - - /** - * Notifies the application of purchase state changes. The application - * can offer an item for sale to the user via - * {@link BillingService#requestPurchase(String)}. The BillingService - * calls this method after it gets the response. Another way this method - * can be called is if the user bought something on another device running - * this same app. Then Android Market notifies the other devices that - * the user has purchased an item, in which case the BillingService will - * also call this method. Finally, this method can be called if the item - * was refunded. - * - * @param purchaseState the state of the purchase request (PURCHASED, - * CANCELED, or REFUNDED) - * @param productId a string identifying a product for sale - * @param orderId a string identifying the order - * @param purchaseTime the time the product was purchased, in milliseconds - * since the epoch (Jan 1, 1970) - * @param developerPayload the developer provided "payload" associated with - * the order - */ - public static void purchaseResponse( - final Context context, final PurchaseState purchaseState, final String productId, - final String orderId, final long purchaseTime, final String developerPayload, final String purchaseToken) { - - new Thread(new Runnable() { - @Override - public void run() { - - // This needs to be synchronized because the UI thread can change the - // value of sPurchaseObserver. - synchronized (ResponseHandler.class) { - if (sPurchaseObserver != null) { - sPurchaseObserver.postPurchaseStateChange( - purchaseState, productId, 1, purchaseTime, developerPayload, purchaseToken); - } - } - } - }).start(); - } - - /** - * This is called when we receive a response code from Android Market for a - * RequestPurchase request that we made. This is used for reporting various - * errors and also for acknowledging that an order was sent successfully to - * the server. This is NOT used for any purchase state changes. All - * purchase state changes are received in the {@link BillingReceiver} and - * are handled in {@link Security#verifyPurchase(String, String)}. - * - * @param context the context - * @param request the RequestPurchase request for which we received a - * response code - * @param responseCode a response code from Market to indicate the state - * of the request - */ - public static void responseCodeReceived(Context context, RequestPurchase request, - ResponseCode responseCode) { - if (sPurchaseObserver != null) { - sPurchaseObserver.onRequestPurchaseResponse(request, responseCode); - } - } - - /** - * This is called when we receive a response code from Android Market for a - * RestoreTransactions request. - * - * @param context the context - * @param request the RestoreTransactions request for which we received a - * response code - * @param responseCode a response code from Market to indicate the state - * of the request - */ - public static void responseCodeReceived(Context context, RestoreTransactions request, - ResponseCode responseCode) { - if (sPurchaseObserver != null) { - sPurchaseObserver.onRestoreTransactionsResponse(request, responseCode); - } - } -} diff --git a/astrid/src/com/todoroo/astrid/billing/Security.java b/astrid/src/com/todoroo/astrid/billing/Security.java deleted file mode 100644 index d713d37b2..000000000 --- a/astrid/src/com/todoroo/astrid/billing/Security.java +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2010 Google Inc. All Rights Reserved. - -package com.todoroo.astrid.billing; - -import com.todoroo.astrid.billing.BillingConstants.PurchaseState; - -import java.security.PublicKey; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.HashSet; - - -/** - * This is a stub class - * - * @author Sam - */ - -public class Security { - private static final String TAG = "Security"; - - private static final String KEY_FACTORY_ALGORITHM = "RSA"; - private static final String SIGNATURE_ALGORITHM = "SHA1withRSA"; - private static final SecureRandom RANDOM = new SecureRandom(); - - private static HashSet sKnownNonces = new HashSet(); - - /** - * A class to hold the verified purchase information. - */ - public static class VerifiedPurchase { - public PurchaseState purchaseState; - public String notificationId; - public String productId; - public String orderId; - public long purchaseTime; - public String developerPayload; - public String purchaseToken; - - public VerifiedPurchase(PurchaseState purchaseState, String notificationId, - String productId, String orderId, long purchaseTime, String developerPayload, String purchaseToken) { - this.purchaseState = purchaseState; - this.notificationId = notificationId; - this.productId = productId; - this.orderId = orderId; - this.purchaseTime = purchaseTime; - this.developerPayload = developerPayload; - this.purchaseToken = purchaseToken; - } - } - - /** - * Generates a nonce (a random number used once). - */ - public static long generateNonce() { - long nonce = RANDOM.nextLong(); - sKnownNonces.add(nonce); - return nonce; - } - - public static void removeNonce(long nonce) { - sKnownNonces.remove(nonce); - } - - public static boolean isNonceKnown(long nonce) { - return sKnownNonces.contains(nonce); - } - - public static ArrayList verifyPurchase(String signedData, String signature) { - return null; - } - - private static String constructPublicKey() { - return ""; - } - - public static PublicKey generatePublicKey(String encodedPublicKey) { - return null; - } - - public static boolean verify(PublicKey publicKey, String signedData, String signature) { - return false; - } -} diff --git a/astrid/src/com/todoroo/astrid/service/MarketStrategy.java b/astrid/src/com/todoroo/astrid/service/MarketStrategy.java index e36703c5a..fb18c714f 100644 --- a/astrid/src/com/todoroo/astrid/service/MarketStrategy.java +++ b/astrid/src/com/todoroo/astrid/service/MarketStrategy.java @@ -45,16 +45,6 @@ public abstract class MarketStrategy { return true; } - /** - * Most market strategies don't support billing at this time, - * so we'll make the default false - * - * @return - */ - public boolean billingSupported() { - return false; - } - /** * Return true if the preference to use the phone layout should be * turned on by default (only true for Nook) @@ -90,12 +80,6 @@ public abstract class MarketStrategy { public String strategyId() { return "android_market"; //$NON-NLS-1$ } - - @Override - public boolean billingSupported() { - return true; - } - } public static class AmazonMarketStrategy extends MarketStrategy {