Java tutorial
/* * Copyright (C) 2012 Jimmy Theis. Licensed under the MIT License: * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.jetheis.android.grades.billing.googleplay; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.List; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentSender.SendIntentException; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.text.format.DateFormat; import android.util.Log; import com.jetheis.android.grades.Constants; import com.jetheis.android.grades.billing.BillingWrapper; import com.jetheis.android.grades.billing.Security; import com.jetheis.android.grades.billing.googleplay.GooglePlayBillingConstants.GooglePlayPurchaseState; import com.jetheis.android.grades.billing.googleplay.GooglePlayBillingConstants.GooglePlayResponseCode; import com.jetheis.android.grades.billing.googleplay.GooglePlayBillingService.OnGooglePlayBillingSupportResultListener; public class GooglePlayBillingWrapper implements BillingWrapper { private static GooglePlayBillingWrapper sInstance; private Context mContext; private ServiceConnection mConnection; private Collection<OnBillingReadyListener> mOnReadyListeners; private Collection<OnPurchaseStateChangedListener> mOnPurchaseStateChangedListeners; private GooglePlayBillingService mBoundService; public static GooglePlayBillingWrapper initializeInstance(Context context) { sInstance = new GooglePlayBillingWrapper(context); return sInstance; } public static GooglePlayBillingWrapper getInstance() { if (sInstance == null) { throw new IllegalStateException("Billing wrapper has not been initialized"); } return sInstance; } public static boolean isInstanceInitialized() { return sInstance != null; } public GooglePlayBillingWrapper(Context context) { mContext = context; mOnReadyListeners = new HashSet<OnBillingReadyListener>(); mOnPurchaseStateChangedListeners = new HashSet<OnPurchaseStateChangedListener>(); mContext.startService(new Intent(mContext, GooglePlayBillingService.class)); mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mBoundService = ((GooglePlayBillingService.GooglePlayBillingBinder) service).getService(); mBoundService.checkIsBillingSupported(new OnGooglePlayBillingSupportResultListener() { @Override public void onGooglePlayBillingSupportResultFound(boolean billingSupported) { if (billingSupported) { Log.i(Constants.TAG, "Google Play billing ready"); for (OnBillingReadyListener listener : mOnReadyListeners) { listener.onBillingReady(); } } else { Log.i(Constants.TAG, "Google Play billing is not supported"); for (OnBillingReadyListener listener : mOnReadyListeners) { listener.onBillingNotSupported(); } } } }); } @Override public void onServiceDisconnected(ComponentName name) { mBoundService = null; } }; mContext.bindService(new Intent(context, GooglePlayBillingService.class), mConnection, Context.BIND_AUTO_CREATE); } @Override public void requestPurchase(String itemId) { Log.d(Constants.TAG, "Requesting purchase of " + itemId); Bundle response; try { response = mBoundService.makeGooglePlayPurchaseRequest(itemId); } catch (RemoteException e) { Log.e(Constants.TAG, "RemoteException: " + e.getLocalizedMessage()); return; } if (response.getInt( GooglePlayBillingConstants.GOOGLE_PLAY_BUNDLE_KEY_RESPONSE_CODE) != GooglePlayResponseCode.RESULT_OK .ordinal()) { return; } PendingIntent pendingIntent = response .getParcelable(GooglePlayBillingConstants.GOOGLE_PLAY_BUNDLE_KEY_PURCHASE_INTENT); try { mContext.startIntentSender(pendingIntent.getIntentSender(), new Intent(), 0, 0, 0); } catch (SendIntentException e) { Log.e(Constants.TAG, "SendIntentException: " + e.getLocalizedMessage()); } } @Override public void restorePurchases() { try { mBoundService.makeGooglePlayRestoreTransactionsRequest(); } catch (RemoteException e) { Log.e(Constants.TAG, "RemoteException: " + e.getLocalizedMessage()); } } @Override public void registerOnBillingReadyListener(OnBillingReadyListener onBillingReadyListener) { mOnReadyListeners.add(onBillingReadyListener); if (mBoundService != null) { onBillingReadyListener.onBillingReady(); } } @Override public int clearOnBillingReadyListeners() { int result = mOnReadyListeners.size(); mOnReadyListeners.clear(); return result; } @Override public void registerOnPurchaseStateChangedListener( OnPurchaseStateChangedListener onPurchaseStateChangedListener) { mOnPurchaseStateChangedListeners.add(onPurchaseStateChangedListener); } @Override public int clearOnPurchaseStateChangedListeners() { int result = mOnPurchaseStateChangedListeners.size(); mOnPurchaseStateChangedListeners.clear(); return result; } public void requestPurchaseInfo(String[] notifyIds) { try { mBoundService.makeGooglePlayPurchaseInformationRequest(notifyIds); } catch (RemoteException e) { Log.e(Constants.TAG, "RemoteException: " + e.getLocalizedMessage()); return; } } public void sendNotificationConformation(String[] notificationIds) { Log.v(Constants.TAG, "Confirming " + notificationIds.length + " notification(s)"); try { mBoundService.makeGooglePlayConfirmNotificationsRequest(notificationIds); } catch (RemoteException e) { Log.e(Constants.TAG, "RemoteException: " + e.getLocalizedMessage()); } } public void unbind() { if (mConnection != null) { mContext.unbindService(mConnection); mConnection = null; } } public void handleJsonResponse(String response, String signature) { Log.v(Constants.TAG, "Handling JSON response: " + response); if (!Security.isCorrectSignature(response, signature)) { Log.e(Constants.TAG, "Bad Google Play signature! Possible security breach!"); return; } JSONObject responseJson; try { responseJson = new JSONObject(response); long nonce = responseJson.getLong(GooglePlayBillingConstants.GOOGLE_PLAY_JSON_KEY_NONCE); if (!Security.isNonceKnown(nonce)) { Log.e(Constants.TAG, "Bad Google Play nonce! Possible security breach!"); return; } Log.v(Constants.TAG, "Signature and nonce OK"); JSONArray orders = responseJson.getJSONArray(GooglePlayBillingConstants.GOOGLE_PLAY_JSON_KEY_ORDERS); if (orders.length() == 0) { Log.v(Constants.TAG, "No orders present in response"); return; } List<String> notificationIds = new ArrayList<String>(orders.length()); for (int i = 0; i < orders.length(); i++) { JSONObject order = orders.getJSONObject(i); String packageName = order.getString(GooglePlayBillingConstants.GOOGLE_PLAY_JSON_KEY_PACKAGE_NAME); if (!packageName.equals(mContext.getPackageName())) { Log.e(Constants.TAG, "Bad Google Play package name! Possible security breach!"); return; } Log.v(Constants.TAG, "Package name OK"); if (order.has(GooglePlayBillingConstants.GOOGLE_PLAY_JSON_KEY_NOTIFICATION_ID)) { notificationIds .add(order.getString(GooglePlayBillingConstants.GOOGLE_PLAY_JSON_KEY_NOTIFICATION_ID)); } String productId = order.getString(GooglePlayBillingConstants.GOOGLE_PLAY_JSON_KEY_PRODUCT_ID); Date purchaseDate = new Date( order.getLong(GooglePlayBillingConstants.GOOGLE_PLAY_JSON_KEY_PURCHASE_TIME)); GooglePlayPurchaseState purchaseState = GooglePlayPurchaseState .fromInt(order.getInt(GooglePlayBillingConstants.GOOGLE_PLAY_JSON_KEY_PURCHASE_STATE)); if (purchaseState == GooglePlayPurchaseState.PURCHASED) { Log.i(Constants.TAG, "Found record of purchase of " + productId + " from " + DateFormat.getLongDateFormat(mContext).format(purchaseDate)); Security.setFullVersionUnlocked(true, mContext); for (OnPurchaseStateChangedListener listener : mOnPurchaseStateChangedListeners) { listener.onPurchaseSuccessful(productId); } } else if (purchaseState == GooglePlayPurchaseState.CANCELLED) { Log.i(Constants.TAG, "User cancelled purchase"); for (OnPurchaseStateChangedListener listener : mOnPurchaseStateChangedListeners) { listener.onPurchaseCancelled(productId); } } else { Log.e(Constants.TAG, "Google Play purchase refunded"); Security.setFullVersionUnlocked(false, mContext); for (OnPurchaseStateChangedListener listener : mOnPurchaseStateChangedListeners) { listener.onPurchaseReturned(productId); } } } if (notificationIds.size() > 0) { sendNotificationConformation(notificationIds.toArray(new String[notificationIds.size()])); } } catch (JSONException e) { Log.e(Constants.TAG, "JSONException: " + e.getLocalizedMessage()); } } }