From b016840ad473ec6594df8f1e391960f210d56176 Mon Sep 17 00:00:00 2001 From: Jaroslav Meloun Date: Fri, 26 Jul 2013 11:26:02 +0200 Subject: [PATCH] cleanup --- ouya_iap.hxproj | 12 +- src/IAPHandler.hx | 131 +++++++++++++++++++++ src/Main.hx | 136 +++------------------- src/MyIAPHandler.hx | 8 ++ src/java/com/jarnik/iaptest/OUYA_IAP.java | 63 +--------- 5 files changed, 168 insertions(+), 182 deletions(-) create mode 100644 src/IAPHandler.hx create mode 100644 src/MyIAPHandler.hx diff --git a/ouya_iap.hxproj b/ouya_iap.hxproj index 88f74b6..94fd70f 100644 --- a/ouya_iap.hxproj +++ b/ouya_iap.hxproj @@ -16,19 +16,23 @@ - + + + + - + diff --git a/src/IAPHandler.hx b/src/IAPHandler.hx new file mode 100644 index 0000000..f27f49a --- /dev/null +++ b/src/IAPHandler.hx @@ -0,0 +1,131 @@ +import flash.display.Bitmap; +import flash.display.Sprite; +import flash.utils.ByteArray; +import haxe.io.Bytes; +import haxe.crypto.BaseCode; + +import openfl.Assets; + +#if android +import openfl.utils.JNI; +import tv.ouya.console.api.OuyaController; +import tv.ouya.console.api.OuyaFacade; +import openfl.events.JoystickEvent; +#end + +class IAPHandler +{ + + public var initCall:Dynamic; + public var requestProductListCall:Dynamic; + public var getProductListIDsCall:Dynamic; + public var requestReceiptsCall:Dynamic; + public var getReceiptProductIDsCall:Dynamic; + public var requestPurchaseCall:Dynamic; + public var ouyaFacadeObject:Dynamic; + + public function new( ouyaFacadeObject:Dynamic, DERKeyPath:String ) + { + this.ouyaFacadeObject = ouyaFacadeObject; + + #if android + + // bind methods to JNI + trace("=================== JNI linking methods..."); + initCall = openfl.utils.JNI.createStaticMethod + ("com.jarnik.iaptest.OUYA_IAP", "init", "(Lorg/haxe/nme/HaxeObject;Ltv/ouya/console/api/OuyaFacade;Ljava/lang/String;)V", true); + requestProductListCall = openfl.utils.JNI.createStaticMethod + ("com.jarnik.iaptest.OUYA_IAP", "requestProductList", "([Ljava/lang/String;)V", true); + getProductListIDsCall = openfl.utils.JNI.createStaticMethod + ("com.jarnik.iaptest.OUYA_IAP", "getProductListIDs", "()Ljava/lang/String;", true); + requestReceiptsCall = openfl.utils.JNI.createStaticMethod + ("com.jarnik.iaptest.OUYA_IAP", "requestReceipts", "()V", true); + getReceiptProductIDsCall = openfl.utils.JNI.createStaticMethod + ("com.jarnik.iaptest.OUYA_IAP", "getReceiptProductIDs", "()Ljava/lang/String;", true); + requestPurchaseCall = openfl.utils.JNI.createStaticMethod + ("com.jarnik.iaptest.OUYA_IAP", "requestPurchase", "(Ljava/lang/String;)V", true); + trace("=================== JNI methods linked!"); + var appKey:ByteArray = Assets.getBytes( DERKeyPath ); + + // I don't know how to pass ByteArray to JNI, let's use Base64 encoding + var BASE:String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var base64:BaseCode = new BaseCode( Bytes.ofString( BASE ) ); + var appKey64:String = base64.encodeBytes( appKey ).toString(); + + // init the IAP + var params:Array = [this, ouyaFacadeObject, appKey64 ]; + initCall( params ); + #end + } + + public function requestProductList( products:Array ):Void { + trace("requesting " + products.join(" ")); + requestProductListCall( [products] ); + } + + public function requestPurchase( product:String ):Void { + trace("purchasing " + product ); + requestPurchaseCall( [product] ); + } + + public function requestReceipts():Void { + trace("requestReceipts"); + requestReceiptsCall( [] ); + } + + public function getProductListIDs():String { + // will return list of received product identifiers, delimited by space character + return getProductListIDsCall(); + } + + public function getReceiptProductIDs():String { + // will return list of purchased products identifiers, delimited by space character + return getReceiptProductIDsCall(); + } + + // ==================================== CALLBACKS - OVERRIDE THESE! ======================= + + // ==== Product List + public function onProductListReceived() + { + var p:Array = getProductListIDs().split(" "); + trace("=== onProductListReceived! " + p.join(" ")); + + requestReceipts(); + } + public function onProductListFailed(error:String) + { + trace("=== onProductListFailed! "+error); + } + + // ==== Purchasing + public function onPurchaseSuccess(productID:String) + { + trace("=== onPurchaseSuccess! "+productID); + } + public function onPurchaseFailed(error:String) + { + trace("=== onPurchaseFailed! "+error); + } + public function onPurchaseCancelled() + { + trace("=== onPurchaseCancelled! "); + } + + // ==== Receipt List + public function onReceiptsReceived() + { + var p:Array = getReceiptProductIDs().split(" "); + trace("=== onReceiptsReceived! "+p.join("x")); + } + public function onReceiptsFailed( error:String ) + { + trace("=== onReceiptsFailed "+error); + } + public function onReceiptsCancelled() + { + trace("=== onReceiptsCancelled "); + } +} + + diff --git a/src/Main.hx b/src/Main.hx index b921e5e..d2a9c1c 100644 --- a/src/Main.hx +++ b/src/Main.hx @@ -16,17 +16,23 @@ import openfl.events.JoystickEvent; class Main extends Sprite { public static inline var OUYA_DEVELOPER_ID:String = "a589aa6a-cf50-4f72-9313-0a515e4dab95"; + public static inline var PRODUCT_IDENTIFIER:String = "test_sss_full"; + public static inline var DER_KEY_PATH:String = "assets/key.der"; + #if android public static var ouyaFacade:OuyaFacade; #end - private var p:IAP_Handler; + private var handler:MyIAPHandler; public function new () { - super (); + // Help: + // How do you call Haxe from Java (Android) http://www.openfl.org/forums/general-discussion/how-do-you-call-haxe-java-android/ + // writing JNI bindings http://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html#zz-4.3 + // JNI elements https://nekonme.googlecode.com/svn/trunk/project/android/JNI.cpp - addChild( new Bitmap( Assets.getBitmapData("assets/OUYA_O.png" ) ) ); + super (); #if android var getContext = JNI.createStaticMethod ("org.haxe.nme.GameActivity", "getContext", "()Landroid/content/Context;", true); @@ -34,132 +40,20 @@ class Main extends Sprite { ouyaFacade = OuyaFacade.getInstance(); ouyaFacade.init( getContext(), OUYA_DEVELOPER_ID ); trace("OUYA controller & facade inited!"); - //ShopOUYA.init(); - - // How do you call Haxe from Java (Android) http://www.openfl.org/forums/general-discussion/how-do-you-call-haxe-java-android/ - p = new IAP_Handler( ouyaFacade.__jobject ); - - p.requestProductList(["test_sss_full", "__DECLINED__THIS_PURCHASE"]); + handler = new MyIAPHandler( ouyaFacade.__jobject, DER_KEY_PATH ); + handler.requestProductList(["test_sss_full", "__DECLINED__THIS_PURCHASE"]); + addChild( new Bitmap( Assets.getBitmapData("assets/OUYA_O.png" ) ) ); stage.addEventListener (JoystickEvent.BUTTON_DOWN, stage_onJoystickButtonDown); - #end - } #if android private function stage_onJoystickButtonDown( e:JoystickEvent ):Void { - trace("pressed button, will purchase"); - p.requestPurchase( "test_sss_full" ); + trace("OUYA button pressed, starting purchase"); + handler.requestPurchase( PRODUCT_IDENTIFIER ); } #end -} - -class IAP_Handler -{ - - public var initCall:Dynamic; - public var requestProductListCall:Dynamic; - public var getProductListIDsCall:Dynamic; - public var requestReceiptsCall:Dynamic; - public var getReceiptProductIDsCall:Dynamic; - public var requestPurchaseCall:Dynamic; - public var ouyaFacadeObject:Dynamic; - - public function new( ouyaFacadeObject:Dynamic ) - { - this.ouyaFacadeObject = ouyaFacadeObject; - #if android - //var fn = openfl.utils.JNI.createStaticMethod("com.jarnik.iaptest.OUYA_IAP", "purchaseSomething", "(Lorg/haxe/nme/HaxeObject;)V", true); - //fn([this]); - - // writing JNI bindings http://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html#zz-4.3 - // JNI elements https://nekonme.googlecode.com/svn/trunk/project/android/JNI.cpp - trace("=================== JNI linking"); - initCall = openfl.utils.JNI.createStaticMethod - ("com.jarnik.iaptest.OUYA_IAP", "init", "(Lorg/haxe/nme/HaxeObject;Ltv/ouya/console/api/OuyaFacade;Ljava/lang/String;)V", true); - // (Lorg/haxe/nme/HaxeObject;Ltv/ouya/console/api/OuyaFacade;[B)V - requestProductListCall = openfl.utils.JNI.createStaticMethod - ("com.jarnik.iaptest.OUYA_IAP", "requestProductList", "([Ljava/lang/String;)V", true); - getProductListIDsCall = openfl.utils.JNI.createStaticMethod - ("com.jarnik.iaptest.OUYA_IAP", "getProductListIDs", "()Ljava/lang/String;", true); - requestPurchaseCall = openfl.utils.JNI.createStaticMethod - ("com.jarnik.iaptest.OUYA_IAP", "requestPurchase", "(Ljava/lang/String;)V", true); - requestReceiptsCall = openfl.utils.JNI.createStaticMethod - ("com.jarnik.iaptest.OUYA_IAP", "requestReceipts", "()V", true); - getReceiptProductIDsCall = openfl.utils.JNI.createStaticMethod - ("com.jarnik.iaptest.OUYA_IAP", "getReceiptProductIDs", "()Ljava/lang/String;", true); - trace("=================== JNI linked!"); - var appKey:ByteArray = Assets.getBytes("assets/key.der"); - - // I don't know how to pass ByteArray to JNI, let's use Base64 - var BASE64:String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - var base64:BaseCode = new BaseCode( Bytes.ofString( BASE64 ) ); - var appKey64:String = base64.encodeBytes( appKey ).toString(); - - var params:Array = [this, ouyaFacadeObject, appKey64 ]; - trace("gonna run initCall from Haxe"); - initCall( params ); - #end - } - - public function requestProductList( products:Array ):Void { - trace("requesting " + products.join(" ")); - requestProductListCall( [products] ); - } - - public function requestPurchase( product:String ):Void { - trace("purchasing " + product ); - requestPurchaseCall( [product] ); - } - - public function requestReceipts():Void { - trace("requestReceipts"); - requestReceiptsCall( [] ); - } - - // ==================================== CALLBACKS ======================= - - public function onProductListReceived() - { - var p:Array = getProductListIDsCall().split(" "); - trace("=== onProductListReceived! >> " + p.join("x")); - - requestReceipts(); - } - public function onProductListFailed(error:String) - { - trace("=== onProductListFailed! "+error); - } - - public function onPurchaseSuccess(productID:String) - { - trace("=== onPurchaseSuccess! "+productID); - } - public function onPurchaseFailed(error:String) - { - trace("=== onPurchaseFailed! "+error); - } - public function onPurchaseCancelled() - { - trace("=== onPurchaseCancelled! "); - } - - public function onReceiptsReceived() - { - var p:Array = getReceiptProductIDsCall().split(" "); - trace("=== onReceiptsReceived! >> "+p.join("x")); - } - public function onReceiptsFailed( error:String ) - { - trace("=== onReceiptsFailed "+error); - } - public function onReceiptsCancelled() - { - trace("=== onReceiptsCancelled "); - } -} - - +} \ No newline at end of file diff --git a/src/MyIAPHandler.hx b/src/MyIAPHandler.hx new file mode 100644 index 0000000..691f729 --- /dev/null +++ b/src/MyIAPHandler.hx @@ -0,0 +1,8 @@ + +class MyIAPHandler extends IAPHandler +{ + + +} + + diff --git a/src/java/com/jarnik/iaptest/OUYA_IAP.java b/src/java/com/jarnik/iaptest/OUYA_IAP.java index be66a4d..e6413cf 100644 --- a/src/java/com/jarnik/iaptest/OUYA_IAP.java +++ b/src/java/com/jarnik/iaptest/OUYA_IAP.java @@ -36,26 +36,8 @@ public class OUYA_IAP { - /* - public static void requestProduct(final HaxeObject callback) - { - GameActivity.getInstance().runOnUiThread - ( - new Runnable() - { - public void run() - { - callback.call("onPurchase", new Object[] {"junk"}); - } - } - ); - //callback.call("onPurchase", new Object[] {"junk"}); - }*/ + // This is mostly a copy-paste of ODK's iap-sample-app --Jaroslav Meloun - /** - * The outstanding purchase request UUIDs. - */ - private static final Map mOutstandingPurchaseRequests = new HashMap(); public static List PRODUCT_IDENTIFIER_LIST; @@ -71,13 +53,7 @@ public static void init(final HaxeObject callback, OuyaFacade ouyaFacade, String mOuyaFacade = ouyaFacade; mCallback = callback; - Log.d("IAP", "Java here, running init!"); - - Log.d("IAP", "got APPLICATION_KEY_64 "+APPLICATION_KEY_64); - byte[] APPLICATION_KEY = Base64.decode( APPLICATION_KEY_64, Base64.NO_WRAP ); - Log.d("IAP", "received APP KEY bytes "+APPLICATION_KEY[0]+" "+APPLICATION_KEY[1]); - // Create a PublicKey object from the key data downloaded from the developer portal. try { X509EncodedKeySpec keySpec = new X509EncodedKeySpec(APPLICATION_KEY); @@ -92,14 +68,10 @@ public static void init(final HaxeObject callback, OuyaFacade ouyaFacade, String public static void requestProductList(String[] products) { - Log.d("IAP", " will request "+products[0]+" etc " ); - PRODUCT_IDENTIFIER_LIST = new ArrayList(); for ( String s : products ) PRODUCT_IDENTIFIER_LIST.add( new Purchasable( s ) ); - Log.d("IAP", "========== created product list of "+PRODUCT_IDENTIFIER_LIST.size() ); - mOuyaFacade.requestProductList(PRODUCT_IDENTIFIER_LIST, new CancelIgnoringOuyaResponseListener>() { @Override public void onSuccess(final ArrayList products) { @@ -107,7 +79,7 @@ public void onSuccess(final ArrayList products) { for(Product p : products) { Log.d("IAP", p.getName() + " costs " + p.getPriceInCents()); } - Log.d("IAP", "========== requestProductList SUCCESS "+products.size() ); + Log.d("IAP", "Received "+products.size()+" products." ); mCallback.call("onProductListReceived", new Object[] {} ); } @@ -117,16 +89,13 @@ public void onFailure(int errorCode, String errorMessage, Bundle optionalData) { // here to tell you that your app needs to handle this case: if your app doesn't display // something, the user won't know of the failure. //Toast.makeText(IapSampleActivity.this, "Could not fetch product information (error " + errorCode + ": " + errorMessage + ")", Toast.LENGTH_LONG).show(); - Log.d("IAP", "========== requestProductList FAIL " ); mCallback.call("onProductListFailed", new Object[] { errorCode + ": " + errorMessage } ); } }); - - Log.d("IAP", "========== requested product list " ); - //callback.call("onPurchase", new Object[] {"junk"}); } public static String getProductListIDs() { + // will return list of received product identifiers, delimited by space character String product_ids = ""; for ( int i = 0; i < mProductList.size(); i++ ) product_ids += ( i > 0 ? " ": "" ) + mProductList.get( i ).getIdentifier(); @@ -139,20 +108,15 @@ public static void requestPurchase( String productName ) throws GeneralSecurityException, UnsupportedEncodingException, JSONException { Product product = null; - for ( Product p: mProductList ) { - Log.d("IAP", "testing "+p.getIdentifier()+" against "+productName); + for ( Product p: mProductList ) if ( p.getIdentifier().equals( productName ) ) { - Log.d("IAP", "FOUND IT!"); product = p; break; } - } if ( product == null ) { - Log.w("IAP", "Requested product ID "+productName+" not found in requested products!" ); + Log.w("IAP", "Requested product ID "+productName+" not found in products!" ); return; } - - Log.d("IAP", "========== requesting purchase of "+product.getName() ); SecureRandom sr = SecureRandom.getInstance("SHA1PRNG"); @@ -166,7 +130,6 @@ public static void requestPurchase( String productName ) purchaseRequest.put("uuid", uniqueId); purchaseRequest.put("identifier", product.getIdentifier()); String purchaseRequestJson = purchaseRequest.toString(); - Log.w("IAP", "HEYYA requesting "+product.getIdentifier()+" uuid "+uniqueId); byte[] keyBytes = new byte[16]; sr.nextBytes(keyBytes); @@ -194,7 +157,6 @@ public static void requestPurchase( String productName ) synchronized (mOutstandingPurchaseRequests) { mOutstandingPurchaseRequests.put(uniqueId, product); } - Log.w("IAP", "HEYYA ouyaFacade.requestPurchase"); mOuyaFacade.requestPurchase(purchasable, new PurchaseListener(product)); } @@ -232,7 +194,6 @@ public static class PurchaseListener implements OuyaResponseListener { public void onSuccess(String result) { Product product; String id; - Log.w("IAP", "HEYYA PurchaseListener.onSuccess"); try { OuyaEncryptionHelper helper = new OuyaEncryptionHelper(); @@ -282,14 +243,11 @@ public void onSuccess(String result) { return; } - //TODO request recipes - //requestReceipts(); mCallback.call("onPurchaseSuccess", new Object[] { mProduct.getIdentifier() } ); } @Override public void onFailure(int errorCode, String errorMessage, Bundle optionalData) { - Log.w("IAP", "onPurchaseFailed "+errorCode+": "+errorMessage); mCallback.call("onPurchaseFailed", new Object[] { errorCode+": "+errorMessage } ); } @@ -298,9 +256,7 @@ public void onFailure(int errorCode, String errorMessage, Bundle optionalData) { */ @Override public void onCancel() { - Log.w("IAP", "HEYYA PurchaseListener.onCancel"); mCallback.call("onPurchaseCancelled", new Object[] {} ); - //showError("User cancelled purchase"); } } @@ -309,15 +265,9 @@ public void onCancel() { public static void requestReceipts() { mOuyaFacade.requestReceipts(new ReceiptListener()); } - - /** - * Display an error to the user. We're using a toast for simplicity. - */ private static void showError(final String errorMessage) { - Log.w("IAP", "requestReceipts error "+errorMessage); mCallback.call("onReceiptsFailed", new Object[] { errorMessage } ); - //Toast.makeText(IapSampleActivity.this, errorMessage, Toast.LENGTH_LONG).show(); } /** @@ -388,7 +338,6 @@ public int compare(Receipt lhs, Receipt rhs) { @Override public void onFailure(int errorCode, String errorMessage, Bundle optionalData) { - Log.w("IAP", "Request Receipts error (code " + errorCode + ": " + errorMessage + ")"); showError("Could not fetch receipts (error " + errorCode + ": " + errorMessage + ")"); } @@ -398,12 +347,12 @@ public void onFailure(int errorCode, String errorMessage, Bundle optionalData) { @Override public void onCancel() { - //showError("User cancelled getting receipts"); mCallback.call("onReceiptsCancelled", new Object[] {} ); } } public static String getReceiptProductIDs() { + // will return list of purchased products identifiers, delimited by space character String product_ids = ""; for ( int i = 0; i < mReceiptList.size(); i++ ) product_ids += ( i > 0 ? " ": "" ) + mReceiptList.get( i ).getIdentifier();