浏览代码

Refactor PaymentsManager according to Google's guidelines

Xavier Sellier 7 年之前
父节点
当前提交
72d81d5903

+ 3 - 1
platform/android/java/src/org/godotengine/godot/Godot.java

@@ -388,7 +388,9 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC
 
 		result_callback = null;
 
-		mPaymentsManager = PaymentsManager.createManager(this).initService();
+		mPaymentsManager = PaymentsManager.createManager(this);
+
+		mPaymentsManager.initService();
 		godot_initialized = true;
 	}
 

+ 3 - 3
platform/android/java/src/org/godotengine/godot/payments/HandlePurchaseTask.java

@@ -59,8 +59,8 @@ abstract public class HandlePurchaseTask {
 		if (resultCode == Activity.RESULT_OK) {
 			PaymentsCache pc = new PaymentsCache(context);
 
-			String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");
-			String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE");
+			String purchaseData = data.getStringExtra(PaymentsManager.RESPONSE_INAPP_PURCHASE_DATA);
+			String dataSignature = data.getStringExtra(PaymentsManager.RESPONSE_INAPP_SIGNATURE);
 
 			try {
 				JSONObject jo = new JSONObject(purchaseData);
@@ -73,7 +73,7 @@ abstract public class HandlePurchaseTask {
 					return;
 				}
 
-				pc.setConsumableValue("ticket_signautre", productId, dataSignature);
+				pc.setConsumableValue("ticket_signature", productId, dataSignature);
 				pc.setConsumableValue("ticket", productId, purchaseData);
 				pc.setConsumableFlag("block", productId, true);
 				pc.setConsumableValue("token", productId, purchaseToken);

+ 148 - 55
platform/android/java/src/org/godotengine/godot/payments/PaymentsManager.java

@@ -33,6 +33,7 @@ import android.app.Activity;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ResolveInfo;
 import android.content.ServiceConnection;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -49,19 +50,73 @@ import org.json.JSONObject;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 
 public class PaymentsManager {
 
 	private static String TAG = "PaymentsManager";
-	public static final int BILLING_RESPONSE_RESULT_OK = 0;
-	public static final int REQUEST_CODE_FOR_PURCHASE = 0x1001;
 	private static boolean auto_consume = true;
 
-	private Activity activity;
+	private GodotPaymentV3 godotPaymentV3;
+
+	// Is setup done?
+	private boolean mSetupDone = false;
+
+	// Has this object been disposed of? (If so, we should ignore callbacks, etc)
+	private boolean mDisposed = false;
+
+	// Connection to the service
 	IInAppBillingService mService;
+	ServiceConnection mServiceConn;
+
+	private Activity activity;
+	private Context context;
+
+	// Billing response codes
+	public static final int BILLING_RESPONSE_RESULT_OK = 0;
+	public static final int BILLING_RESPONSE_RESULT_USER_CANCELED = 1;
+	public static final int BILLING_RESPONSE_RESULT_SERVICE_UNAVAILABLE = 2;
+	public static final int BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE = 3;
+	public static final int BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE = 4;
+	public static final int BILLING_RESPONSE_RESULT_DEVELOPER_ERROR = 5;
+	public static final int BILLING_RESPONSE_RESULT_ERROR = 6;
+	public static final int BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED = 7;
+	public static final int BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED = 8;
+
+	// IAB Helper error codes
+	public static final int IABHELPER_ERROR_BASE = -1000;
+	public static final int IABHELPER_REMOTE_EXCEPTION = -1001;
+	public static final int IABHELPER_BAD_RESPONSE = -1002;
+	public static final int IABHELPER_VERIFICATION_FAILED = -1003;
+	public static final int IABHELPER_SEND_INTENT_FAILED = -1004;
+	public static final int IABHELPER_USER_CANCELLED = -1005;
+	public static final int IABHELPER_UNKNOWN_PURCHASE_RESPONSE = -1006;
+	public static final int IABHELPER_MISSING_TOKEN = -1007;
+	public static final int IABHELPER_UNKNOWN_ERROR = -1008;
+	public static final int IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE = -1009;
+	public static final int IABHELPER_INVALID_CONSUMPTION = -1010;
+	public static final int IABHELPER_SUBSCRIPTION_UPDATE_NOT_AVAILABLE = -1011;
+
+	// Keys for the responses from InAppBillingService
+	public static final String RESPONSE_CODE = "RESPONSE_CODE";
+	public static final String RESPONSE_GET_SKU_DETAILS_LIST = "DETAILS_LIST";
+	public static final String RESPONSE_BUY_INTENT = "BUY_INTENT";
+	public static final String RESPONSE_INAPP_PURCHASE_DATA = "INAPP_PURCHASE_DATA";
+	public static final String RESPONSE_INAPP_SIGNATURE = "INAPP_DATA_SIGNATURE";
+	public static final String RESPONSE_INAPP_ITEM_LIST = "INAPP_PURCHASE_ITEM_LIST";
+	public static final String RESPONSE_INAPP_PURCHASE_DATA_LIST = "INAPP_PURCHASE_DATA_LIST";
+	public static final String RESPONSE_INAPP_SIGNATURE_LIST = "INAPP_DATA_SIGNATURE_LIST";
+	public static final String INAPP_CONTINUATION_TOKEN = "INAPP_CONTINUATION_TOKEN";
+
+	// Item types
+	public static final String ITEM_TYPE_INAPP = "inapp";
+	public static final String ITEM_TYPE_SUBS = "subs";
+
+	public static final int REQUEST_CODE_FOR_PURCHASE = 10001;
 
 	public void setActivity(Activity activity) {
 		this.activity = activity;
+		this.context = activity.getApplicationContext();
 	}
 
 	public static PaymentsManager createManager(Activity activity) {
@@ -71,49 +126,88 @@ public class PaymentsManager {
 
 	private PaymentsManager(Activity activity) {
 		this.activity = activity;
+		this.context = activity.getApplicationContext();
 	}
 
-	public PaymentsManager initService() {
-		Intent intent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
-		intent.setPackage("com.android.vending");
-		activity.bindService(
-				intent,
-				mServiceConn,
-				Context.BIND_AUTO_CREATE);
-		return this;
-	}
-
-	public void destroy() {
-		if (mService != null) {
-			activity.unbindService(mServiceConn);
+	public void initService() {
+		// Cancel the service creation if it has already been created or it creating
+		if (mDisposed || mSetupDone) {
+			return;
 		}
-	}
 
-	ServiceConnection mServiceConn = new ServiceConnection() {
-		@Override
-		public void onServiceDisconnected(ComponentName name) {
-			mService = null;
+		mServiceConn = new ServiceConnection() {
+			@Override
+			public void onServiceDisconnected(ComponentName name) {
+				mService = null;
 
-			// At this stage, godotPaymentV3 might not have been initialized yet.
-			if (godotPaymentV3 != null) {
-				godotPaymentV3.callbackDisconnected();
+				// At this stage, godotPaymentV3 might not have been initialized yet.
+				if (godotPaymentV3 != null) {
+					godotPaymentV3.callbackDisconnected();
+				}
 			}
-		}
 
-		@Override
-		public void onServiceConnected(ComponentName name, IBinder service) {
-			mService = IInAppBillingService.Stub.asInterface(service);
+			@Override
+			public void onServiceConnected(ComponentName name, IBinder service) {
+				if (mDisposed) return;
+
+				mService = IInAppBillingService.Stub.asInterface(service);
+
+				String packageName = context.getPackageName();
+
+				try {
+					// check for in-app billing v3 support
+					int response = mService.isBillingSupported(3, packageName, ITEM_TYPE_INAPP);
+
+					if (response != BILLING_RESPONSE_RESULT_OK) {
+						Log.i(TAG, "Device does not support billing 3.");
+
+						return;
+					}
 
-			// At this stage, godotPaymentV3 might not have been initialized yet.
-			if (godotPaymentV3 != null) {
-				godotPaymentV3.callbackConnected();
+					mSetupDone = true;
+				} catch (RemoteException e) {
+					Log.d(TAG, "Error binding ServiceConnection:" + e.getMessage());
+					return;
+				}
+
+				// At this stage, godotPaymentV3 might not have been initialized yet.
+				if (godotPaymentV3 != null) {
+					godotPaymentV3.callbackConnected();
+				}
 			}
+		};
+
+		Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
+		serviceIntent.setPackage("com.android.vending");
+
+		List<ResolveInfo> intentServices = context.getPackageManager().queryIntentServices(serviceIntent, 0);
+		if (intentServices != null && !intentServices.isEmpty()) {
+			// service available to handle that Intent
+			context.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
+		} else {
+			Log.i(TAG, "Billing service unavailable on device.");
 		}
-	};
+
+		return;
+	}
+
+	public void destroy() {
+		if (mService != null) {
+			activity.unbindService(mServiceConn);
+		}
+
+		mSetupDone = false;
+		mDisposed = true;
+		mServiceConn = null;
+		mService = null;
+		activity = null;
+		context = null;
+	}
 
 	public void requestPurchase(final String sku, String transactionId) {
-		new PurchaseTask(mService, Godot.getInstance()) {
+		if (!mSetupDone) return;
 
+		PurchaseTask purchaseTask = new PurchaseTask(mService, Godot.getInstance()) {
 			@Override
 			protected void error(String message) {
 				godotPaymentV3.callbackFail();
@@ -128,18 +222,17 @@ public class PaymentsManager {
 			protected void alreadyOwned() {
 				godotPaymentV3.callbackAlreadyOwned(sku);
 			}
+		};
 
-		}
-				.purchase(sku, transactionId);
+		purchaseTask.purchase(sku, transactionId);
 	}
 
 	public boolean isConnected() {
-		return mService != null;
+		return mSetupDone && mService != null;
 	}
 
 	public void consumeUnconsumedPurchases() {
-		new ReleaseAllConsumablesTask(mService, activity) {
-
+		ReleaseAllConsumablesTask releaseAllConsumablesTask = new ReleaseAllConsumablesTask(mService, activity) {
 			@Override
 			protected void success(String sku, String receipt, String signature, String token) {
 				godotPaymentV3.callbackSuccessProductMassConsumed(receipt, signature, sku);
@@ -156,8 +249,9 @@ public class PaymentsManager {
 				Log.d(TAG, "callbackSuccessNoUnconsumedPurchases :");
 				godotPaymentV3.callbackSuccessNoUnconsumedPurchases();
 			}
-		}
-				.consumeItAll();
+		};
+
+		releaseAllConsumablesTask.consumeItAll();
 	}
 
 	public void requestPurchased() {
@@ -167,12 +261,12 @@ public class PaymentsManager {
 			String continueToken = null;
 
 			do {
-				Bundle bundle = mService.getPurchases(3, activity.getPackageName(), "inapp", continueToken);
+				Bundle bundle = mService.getPurchases(3, activity.getPackageName(), ITEM_TYPE_INAPP, continueToken);
 
-				if (bundle.getInt("RESPONSE_CODE") == 0) {
+				if (bundle.getInt(RESPONSE_CODE) == 0) {
 
-					final ArrayList<String> myPurchases = bundle.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
-					final ArrayList<String> mySignatures = bundle.getStringArrayList("INAPP_DATA_SIGNATURE_LIST");
+					final ArrayList<String> myPurchases = bundle.getStringArrayList(RESPONSE_INAPP_PURCHASE_DATA_LIST);
+					final ArrayList<String> mySignatures = bundle.getStringArrayList(RESPONSE_INAPP_SIGNATURE_LIST);
 
 					if (myPurchases == null || myPurchases.size() == 0) {
 						godotPaymentV3.callbackPurchased("", "", "");
@@ -188,7 +282,7 @@ public class PaymentsManager {
 							String token = inappPurchaseData.getString("purchaseToken");
 							String signature = mySignatures.get(i);
 
-							pc.setConsumableValue("ticket_signautre", sku, signature);
+							pc.setConsumableValue("ticket_signature", sku, signature);
 							pc.setConsumableValue("ticket", sku, receipt);
 							pc.setConsumableFlag("block", sku, true);
 							pc.setConsumableValue("token", sku, token);
@@ -198,7 +292,7 @@ public class PaymentsManager {
 						}
 					}
 				}
-				continueToken = bundle.getString("INAPP_CONTINUATION_TOKEN");
+				continueToken = bundle.getString(INAPP_CONTINUATION_TOKEN);
 				Log.d(TAG, "continue token = " + continueToken);
 			} while (!TextUtils.isEmpty(continueToken));
 		} catch (Exception e) {
@@ -207,7 +301,7 @@ public class PaymentsManager {
 	}
 
 	public void processPurchaseResponse(int resultCode, Intent data) {
-		new HandlePurchaseTask(activity) {
+		HandlePurchaseTask handlePurchaseTask = new HandlePurchaseTask(activity) {
 
 			@Override
 			protected void success(final String sku, final String signature, final String ticket) {
@@ -238,8 +332,9 @@ public class PaymentsManager {
 			protected void canceled() {
 				godotPaymentV3.callbackCancel();
 			}
-		}
-				.handlePurchaseRequest(resultCode, data);
+		};
+
+		handlePurchaseTask.handlePurchaseRequest(resultCode, data);
 	}
 
 	public void validatePurchase(String purchaseToken, final String sku) {
@@ -299,7 +394,7 @@ public class PaymentsManager {
 
 	// Workaround to bug where sometimes response codes come as Long instead of Integer
 	int getResponseCodeFromBundle(Bundle b) {
-		Object o = b.get("RESPONSE_CODE");
+		Object o = b.get(RESPONSE_CODE);
 		if (o == null) {
 			//logDebug("Bundle with null response code, assuming OK (known issue)");
 			return BILLING_RESPONSE_RESULT_OK;
@@ -395,8 +490,8 @@ public class PaymentsManager {
 					querySkus.putStringArrayList("ITEM_ID_LIST", skuPartList);
 					Bundle skuDetails = null;
 					try {
-						skuDetails = mService.getSkuDetails(3, activity.getPackageName(), "inapp", querySkus);
-						if (!skuDetails.containsKey("DETAILS_LIST")) {
+						skuDetails = mService.getSkuDetails(3, activity.getPackageName(), ITEM_TYPE_INAPP, querySkus);
+						if (!skuDetails.containsKey(RESPONSE_GET_SKU_DETAILS_LIST)) {
 							int response = getResponseCodeFromBundle(skuDetails);
 							if (response != BILLING_RESPONSE_RESULT_OK) {
 								godotPaymentV3.errorSkuDetail(getResponseDesc(response));
@@ -406,7 +501,7 @@ public class PaymentsManager {
 							return;
 						}
 
-						ArrayList<String> responseList = skuDetails.getStringArrayList("DETAILS_LIST");
+						ArrayList<String> responseList = skuDetails.getStringArrayList(RESPONSE_GET_SKU_DETAILS_LIST);
 
 						for (String thisResponse : responseList) {
 							Log.d(TAG, "response = " + thisResponse);
@@ -423,8 +518,6 @@ public class PaymentsManager {
 				.start();
 	}
 
-	private GodotPaymentV3 godotPaymentV3;
-
 	public void setBaseSingleton(GodotPaymentV3 godotPaymentV3) {
 		this.godotPaymentV3 = godotPaymentV3;
 	}

+ 5 - 5
platform/android/java/src/org/godotengine/godot/payments/PurchaseTask.java

@@ -70,12 +70,12 @@ abstract public class PurchaseTask {
 
 		Bundle buyIntentBundle;
 		try {
-			buyIntentBundle = mService.getBuyIntent(3, context.getApplicationContext().getPackageName(), sku, "inapp", hash);
+			buyIntentBundle = mService.getBuyIntent(3, context.getApplicationContext().getPackageName(), sku, PaymentsManager.ITEM_TYPE_INAPP, hash);
 		} catch (RemoteException e) {
 			error(e.getMessage());
 			return;
 		}
-		Object rc = buyIntentBundle.get("RESPONSE_CODE");
+		Object rc = buyIntentBundle.get(PaymentsManager.RESPONSE_CODE);
 		int responseCode = 0;
 		if (rc == null) {
 			responseCode = PaymentsManager.BILLING_RESPONSE_RESULT_OK;
@@ -85,16 +85,16 @@ abstract public class PurchaseTask {
 			responseCode = (int)((Long)rc).longValue();
 		}
 
-		if (responseCode == 1 || responseCode == 3 || responseCode == 4) {
+		if (responseCode == PaymentsManager.BILLING_RESPONSE_RESULT_USER_CANCELED || responseCode == PaymentsManager.BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE || responseCode == PaymentsManager.BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE) {
 			canceled();
 			return;
 		}
-		if (responseCode == 7) {
+		if (responseCode == PaymentsManager.BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED) {
 			alreadyOwned();
 			return;
 		}
 
-		PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT");
+		PendingIntent pendingIntent = buyIntentBundle.getParcelable(PaymentsManager.RESPONSE_BUY_INTENT);
 		pc.setConsumableValue("validation_hash", sku, hash);
 		try {
 			context.startIntentSenderForResult(

+ 3 - 3
platform/android/java/src/org/godotengine/godot/payments/ReleaseAllConsumablesTask.java

@@ -57,7 +57,7 @@ abstract public class ReleaseAllConsumablesTask {
 
 	public void consumeItAll() {
 		try {
-			Bundle bundle = mService.getPurchases(3, context.getPackageName(), "inapp", null);
+			Bundle bundle = mService.getPurchases(3, context.getPackageName(), PaymentsManager.ITEM_TYPE_INAPP, null);
 
 			// TODO:
 			// Check if this loop is useful and remove it if not
@@ -67,8 +67,8 @@ abstract public class ReleaseAllConsumablesTask {
 
 			if (bundle.getInt("RESPONSE_CODE") == 0) {
 
-				final ArrayList<String> myPurchases = bundle.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
-				final ArrayList<String> mySignatures = bundle.getStringArrayList("INAPP_DATA_SIGNATURE_LIST");
+				final ArrayList<String> myPurchases = bundle.getStringArrayList(PaymentsManager.RESPONSE_INAPP_PURCHASE_DATA_LIST);
+				final ArrayList<String> mySignatures = bundle.getStringArrayList(PaymentsManager.RESPONSE_INAPP_SIGNATURE_LIST);
 
 				if (myPurchases == null || myPurchases.size() == 0) {
 					notRequired();