More code and refactoring in the billing activity

pull/14/head
Sam Bosley 14 years ago
parent b8d5fedf84
commit a8f74da42e

@ -46,9 +46,21 @@
<string name="file_err_show">Sorry, the system does not yet support this type of file</string> <string name="file_err_show">Sorry, the system does not yet support this type of file</string>
<!-- in app billing --> <!-- in app billing -->
<string name="subscriptions_not_supported">Subscriptions not supported</string> <string name="billing_not_supported_title">Can\'t make purchases</string>
<string name="subscriptions_not_supported_message">Sorry! The Market billing <string name="billing_not_supported_message">The Market billing
service is not available at this time. You can continue to use this app but you
won\'t be able to make purchases.</string>
<string name="subscriptions_not_supported_title">Can\'t purchase subscriptions</string>
<string name="subscriptions_not_supported_message">The Market billing
service on this device does not support subscriptions at this time.</string> service on this device does not support subscriptions at this time.</string>
<string name="cannot_connect_title">Can\'t connect to Market</string>
<string name="cannot_connect_message">This app cannot connect to Market.
Your version of Market may be out of date.
You can continue to use this app but you
won\'t be able to make purchases.</string>
<string name="restoring_transactions">Restoring transactions</string>
<string name="subscriptions_learn_more">Learn more</string> <string name="subscriptions_learn_more">Learn more</string>
<string name="subscriptions_help_url" formatted="false">http://market.android.com/support/bin/answer.py?answer=1050566&amp;hl=%lang%&amp;dl=%region%</string> <string name="subscriptions_help_url" formatted="false">http://market.android.com/support/bin/answer.py?answer=1050566&amp;hl=%lang%&amp;dl=%region%</string>

@ -4,19 +4,40 @@ import java.util.Locale;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.widget.Button; import android.widget.Button;
import android.widget.Toast;
import com.timsu.astrid.R; import com.timsu.astrid.R;
import com.todoroo.andlib.utility.Preferences;
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 com.todoroo.astrid.utility.Constants;
public class BillingActivity extends Activity { public class BillingActivity extends Activity {
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 static final String TRANSACTIONS_INITIALIZED = "premium_transactions_initialized"; //$NON-NLS-1$
private Handler handler;
private BillingService billingService; private BillingService billingService;
private AstridPurchaseObserver purchaseObserver;
private Button buyMonth;
private Button buyYear;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -25,17 +46,21 @@ public class BillingActivity extends Activity {
setupButtons(); setupButtons();
handler = new Handler();
billingService = new BillingService(); billingService = new BillingService();
billingService.setContext(this); billingService.setContext(this);
purchaseObserver = new AstridPurchaseObserver(handler);
ResponseHandler.register(purchaseObserver);
if (!billingService.checkBillingSupported(BillingConstants.ITEM_TYPE_SUBSCRIPTION)) { if (!billingService.checkBillingSupported(BillingConstants.ITEM_TYPE_SUBSCRIPTION)) {
showSubscriptionsNotSupported(); showDialog(DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID);
} }
} }
private void setupButtons() { private void setupButtons() {
Button buyMonth = (Button) findViewById(R.id.buy_month); buyMonth = (Button) findViewById(R.id.buy_month);
Button buyYear = (Button) findViewById(R.id.buy_year); buyYear = (Button) findViewById(R.id.buy_year);
//TODO: Figure out if we need a payload for any reason //TODO: Figure out if we need a payload for any reason
@ -44,7 +69,7 @@ public class BillingActivity extends Activity {
public void onClick(View v) { public void onClick(View v) {
if (!billingService.requestPurchase(BillingConstants.PRODUCT_ID_MONTHLY, if (!billingService.requestPurchase(BillingConstants.PRODUCT_ID_MONTHLY,
BillingConstants.ITEM_TYPE_SUBSCRIPTION, null)) { BillingConstants.ITEM_TYPE_SUBSCRIPTION, null)) {
showSubscriptionsNotSupported(); showDialog(DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID);
} }
} }
}); });
@ -54,30 +79,12 @@ public class BillingActivity extends Activity {
public void onClick(View v) { public void onClick(View v) {
if (!billingService.requestPurchase(BillingConstants.PRODUCT_ID_YEARLY, if (!billingService.requestPurchase(BillingConstants.PRODUCT_ID_YEARLY,
BillingConstants.ITEM_TYPE_SUBSCRIPTION, null)) { BillingConstants.ITEM_TYPE_SUBSCRIPTION, null)) {
showSubscriptionsNotSupported(); showDialog(DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID);
} }
} }
}); });
} }
private void showSubscriptionsNotSupported() {
String helpUrl = replaceLanguageAndRegion(getString(R.string.subscriptions_help_url));
final Uri helpUri = Uri.parse(helpUrl);
new AlertDialog.Builder(this)
.setTitle(R.string.subscriptions_not_supported)
.setMessage(R.string.subscriptions_not_supported_message)
.setCancelable(false)
.setPositiveButton(R.string.DLG_ok, null)
.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);
}
}).create().show();
}
/** /**
* Replaces the language and/or country of the device into the given string. * 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 "%lang%" will be replaced by the device's language code and
@ -96,4 +103,148 @@ public class BillingActivity extends Activity {
} }
return str; 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 = replaceLanguageAndRegion(getString(R.string.subscriptions_help_url));
if (Constants.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, null)
.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 initialized = Preferences.getBoolean(TRANSACTIONS_INITIALIZED, false);
if (!initialized) {
billingService.restoreTransactions();
Toast.makeText(this, R.string.restoring_transactions, Toast.LENGTH_LONG).show();
}
}
/**
* A {@link PurchaseObserver} is used to get callbacks when Android Market sends
* messages to this application so that we can update the UI.
*/
@SuppressWarnings("nls")
private class AstridPurchaseObserver extends PurchaseObserver {
public AstridPurchaseObserver(Handler handler) {
super(BillingActivity.this, handler);
}
@Override
public void onBillingSupported(boolean supported, String type) {
if (Constants.DEBUG) {
Log.i(TAG, "supported: " + supported);
}
if (type != null && type.equals(BillingConstants.ITEM_TYPE_SUBSCRIPTION)) {
if (supported) {
restoreTransactions();
buyMonth.setEnabled(true);
buyYear.setEnabled(true);
} else {
showDialog(DIALOG_BILLING_NOT_SUPPORTED_ID);
}
} else {
showDialog(DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID);
}
}
@Override
public void onPurchaseStateChange(PurchaseState purchaseState, String itemId,
int quantity, long purchaseTime, String developerPayload) {
if (Constants.DEBUG) {
Log.i(TAG, "onPurchaseStateChange() itemId: " + itemId + " " + purchaseState);
}
if (developerPayload == null) {
//
} else {
//
}
if (purchaseState == PurchaseState.PURCHASED) {
// mOwnedItems.add(itemId);
//
// // If this is a subscription, then enable the "Edit
// // Subscriptions" button.
// for (CatalogEntry e : CATALOG) {
// if (e.sku.equals(itemId) &&
// e.managed.equals(Managed.SUBSCRIPTION)) {
// mEditSubscriptionsButton.setVisibility(View.VISIBLE);
// }
// }
}
// mCatalogAdapter.setOwnedItems(mOwnedItems);
// mOwnedItemsCursor.requery();
}
@Override
public void onRequestPurchaseResponse(RequestPurchase request,
ResponseCode responseCode) {
if (Constants.DEBUG) {
Log.d(TAG, request.mProductId + ": " + responseCode);
}
if (responseCode == ResponseCode.RESULT_OK) {
if (Constants.DEBUG) {
Log.i(TAG, "purchase was successfully sent to server");
}
} else if (responseCode == ResponseCode.RESULT_USER_CANCELED) {
if (Constants.DEBUG) {
Log.i(TAG, "user canceled purchase");
}
} else {
if (Constants.DEBUG) {
Log.i(TAG, "purchase failed");
}
}
}
@Override
public void onRestoreTransactionsResponse(RestoreTransactions request,
ResponseCode responseCode) {
if (responseCode == ResponseCode.RESULT_OK) {
if (Constants.DEBUG) {
Log.d(TAG, "completed RestoreTransactions request");
}
// Update the shared preferences so that we don't perform
// a RestoreTransactions again.
Preferences.setBoolean(TRANSACTIONS_INITIALIZED, true);
} else {
if (Constants.DEBUG) {
Log.d(TAG, "RestoreTransactions error: " + responseCode);
}
}
}
}
} }

@ -3,13 +3,92 @@ package com.todoroo.astrid.billing;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.util.Log;
import com.todoroo.astrid.billing.BillingConstants.ResponseCode;
import com.todoroo.astrid.utility.Constants;
public class BillingReceiver extends BroadcastReceiver { 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 @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub 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 (Constants.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);
}
} }

@ -27,7 +27,7 @@ import com.todoroo.astrid.billing.BillingService.RestoreTransactions;
* are used to update the UI. * are used to update the UI.
*/ */
public abstract class PurchaseObserver { public abstract class PurchaseObserver {
private static final String TAG = "purchase-observer"; //$NON-NLS-1$ protected static final String TAG = "purchase-observer"; //$NON-NLS-1$
private final Activity mActivity; private final Activity mActivity;
private final Handler mHandler; private final Handler mHandler;
private Method mStartIntentSender; private Method mStartIntentSender;

Loading…
Cancel
Save