Java tutorial
/* * Copyright (c) 2013 Goodhustle Studios, 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. * * This file incorporates work covered by the following copyright and permission notice: * * Copyright (C) 2012 OUYA, Inc. * Copyright (C) 2012 Tagenigma LLC * Copyright (C) 2012 Hashbang Games * * 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.goodhustle.ouyaunitybridge; import tv.ouya.console.api.*; import tv.ouya.console.internal.util.Strings; import android.accounts.AccountManager; import android.app.Activity; import android.app.AlertDialog; import android.os.SystemClock; import android.content.*; import android.hardware.input.InputManager; //API 16 import android.hardware.input.InputManager.InputDeviceListener; //API 16 import android.os.Bundle; import android.os.Parcelable; import android.util.Base64; import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.InputDevice; import android.widget.FrameLayout; import android.widget.LinearLayout.LayoutParams; import android.widget.RelativeLayout; import android.widget.Toast; import org.json.JSONException; import org.json.JSONObject; import com.google.gson.Gson; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.security.*; import java.security.spec.X509EncodedKeySpec; import java.text.ParseException; import java.security.GeneralSecurityException; import com.unity3d.player.UnityPlayer; import com.unity3d.player.UnityPlayerActivity; import com.unity3d.player.UnityPlayerNativeActivity; import com.unity3d.player.UnityPlayerProxyActivity; import java.util.*; import tv.ouya.console.api.OuyaController; public class OuyaUnityActivity extends Activity implements InputDeviceListener { /** * The tag for log messages */ private static final String LOG_TAG = "OuyaUnityActivity"; /** * Value that OuyaController.getPlayerNumByDeviceId returns when * an InputDevice is not something that OuyaController considers * a controller. (ex: Keyboards, mice, etc) */ private static final int DEVICE_NOT_OUYACONTROLLER_COMPATIBLE = -1; //the Unity Player private UnityPlayer mUnityPlayer; /** * Log onto the developer website (you should have received a URL, a username and a password in email) * and get your developer ID. Plug it in here. Use your developer ID, not your developer UUID. * <p/> * The current value is the OUYA sample default so that UUID calls will work. Replace this with * your developer ID from the portal. */ public static final String DEVELOPER_ID = "310a8f51-4d6e-4ae5-bda0-b93878e5f5d0"; /** * The application key. This is used to decrypt encrypted receipt responses. This should be replaced with the * application key obtained from the OUYA developers website. */ private static final byte[] APPLICATION_KEY = { (byte) 0x00, }; /** * Before this app will run, you must define some purchasable items on the developer website. Once * you have defined those items, put their Product IDs in the List below. * <p/> * The Product IDs below are those in our developer account. You should change them. */ public static final List<Purchasable> PRODUCT_IDENTIFIER_LIST = Arrays.asList(new Purchasable("item-example")); /** * @IMPORTANT * The following line determines whether execution in the Unity application * will continue when the OUYA SDK opens up its own overlays. * If pause is not enabled, Input will be bypassed when the Unity player is paused because * the java layer will stop sending input. * If pause is enabled, then the game will not show underneath the popup, * but it will completely stop execution */ public static boolean UNITY_PAUSE_ON_OUYA_OVERLAYS = false; /** * The saved instance state key for products */ private static final String PRODUCTS_INSTANCE_STATE_KEY = "Products"; /** * The saved instance state key for receipts */ private static final String RECEIPTS_INSTANCE_STATE_KEY = "Receipts"; /** * The ID used to track the activity started by an authentication intent during a purchase. */ private static final int PURCHASE_AUTHENTICATION_ACTIVITY_ID = 1; /** * The ID used to track the activity started by an authentication intent during a request for * the gamers UUID. */ private static final int GAMER_UUID_AUTHENTICATION_ACTIVITY_ID = 2; private static ControllerState[] playerStates; /** * The outstanding purchase request UUIDs. */ private final Map<String, String> mOutstandingPurchaseRequests = new HashMap<String, String>(); private OuyaFacade ouyaFacade; private UserManager userManager; private List<Product> mProductList; private List<Receipt> mReceiptList; //indicates the Unity player has loaded private Boolean mEnableUnity = true; private Boolean mEnableLogging = true; private InputManager mInputManager = null; private InputManager.InputDeviceListener minputDeviceListener = null; private String mGamerUuid; private IntentFilter accountsChangedFilter; private boolean mPaused = false; /** * The cryptographic key for this application */ private PublicKey mPublicKey; /** * Broadcast listener to handle re-requesting the receipts when a user has re-authenticated */ private BroadcastReceiver mAuthChangeReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // Refresh receipts requestReceipts(); // Refresh gamer UUID fetchGamerUUID(); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); OuyaController.init(this); // Initialize ouyaFacade ouyaFacade = OuyaFacade.getInstance(); ouyaFacade.init(this, DEVELOPER_ID); userManager = UserManager.getInstance(this); playerStates = new ControllerState[OuyaController.MAX_CONTROLLERS]; for (int i = 0; i < OuyaController.MAX_CONTROLLERS; i++) { playerStates[i] = new ControllerState(); } // Create the UnityPlayer mUnityPlayer = new UnityPlayer(this); int glesMode = mUnityPlayer.getSettings().getInt("gles_mode", 1); boolean trueColor8888 = false; mUnityPlayer.init(glesMode, trueColor8888); setContentView(R.layout.main); // Add the Unity view FrameLayout layout = (FrameLayout) findViewById(R.id.unityLayout); LayoutParams lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); layout.addView(mUnityPlayer.getView(), 0, lp); // Set the focus RelativeLayout mainLayout = (RelativeLayout) findViewById(R.id.mainLayout); mainLayout.setFocusableInTouchMode(true); // Attempt to restore the product and receipt list from the savedInstanceState Bundle if (savedInstanceState != null) { if (savedInstanceState.containsKey(PRODUCTS_INSTANCE_STATE_KEY)) { Parcelable[] products = savedInstanceState.getParcelableArray(PRODUCTS_INSTANCE_STATE_KEY); mProductList = new ArrayList<Product>(products.length); for (Parcelable product : products) { mProductList.add((Product) product); } addProducts(); } if (savedInstanceState.containsKey(RECEIPTS_INSTANCE_STATE_KEY)) { Parcelable[] receipts = savedInstanceState.getParcelableArray(RECEIPTS_INSTANCE_STATE_KEY); mReceiptList = new ArrayList<Receipt>(receipts.length); for (Parcelable receipt : receipts) { mReceiptList.add((Receipt) receipt); } addReceipts(); } } // Request the product list if it could not be restored from the savedInstanceState Bundle if (mProductList == null) { requestProducts(); } // Create a PublicKey object from the key data downloaded from the developer portal. try { X509EncodedKeySpec keySpec = new X509EncodedKeySpec(APPLICATION_KEY); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); mPublicKey = keyFactory.generatePublic(keySpec); } catch (Exception e) { Log.e(LOG_TAG, "Unable to create encryption key", e); } } @Override protected void onStart() { super.onStart(); // Immediately request an up-to-date copy of receipts. requestReceipts(); // Register to receive notifications about account changes. This will re-query the receipt // list in order to ensure it is always up to date for whomever is logged in. accountsChangedFilter = new IntentFilter(); accountsChangedFilter.addAction(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION); registerReceiver(mAuthChangeReceiver, accountsChangedFilter); // listen for controller changes - http://developer.android.com/reference/android/hardware/input/InputManager.html#registerInputDeviceListener%28android.hardware.input.InputManager.InputDeviceListener,%20android.os.Handler%29 Context context = getBaseContext(); mInputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE); mInputManager.registerInputDeviceListener(this, null); sendDevices(); } /// In the iap sample, this calls super.onPause. Not sure why? @Override protected void onStop() { if (null != mInputManager) { try { mInputManager.unregisterInputDeviceListener(this); } catch (IllegalArgumentException e) { Log.w(LOG_TAG, "Already unregistered input listener at onPause"); } } // Unregister input listener try { unregisterReceiver(mAuthChangeReceiver); } catch (IllegalArgumentException e) { Log.w(LOG_TAG, "Already unregistered auth change receiver at onStop"); } mUnityPlayer.pause(); super.onStop(); } @Override protected void onDestroy() { ouyaFacade.shutdown(); userManager.shutdown(); super.onDestroy(); // Kill Unity player mUnityPlayer.quit(); } @Override public void onPause() { if (null != mInputManager) { try { mInputManager.unregisterInputDeviceListener(this); } catch (IllegalArgumentException e) { Log.w(LOG_TAG, "Already unregistered input listener at onPause"); } } try { unregisterReceiver(mAuthChangeReceiver); } catch (IllegalArgumentException e) { Log.w(LOG_TAG, "Already unregistered auth change receiver at onPause"); } // Clear out input for (int i = 0; i < OuyaController.MAX_CONTROLLERS; i++) { playerStates[i].Clear(); } super.onPause(); if (mEnableLogging) { Log.i(LOG_TAG, "OuyaUnityActivity.onPause called"); } if (UNITY_PAUSE_ON_OUYA_OVERLAYS) { mUnityPlayer.pause(); } // On the unity side, clear all current input and button flags. UnityPlayer.UnitySendMessage("OuyaBridge", "didPause", ""); if (isFinishing()) { // Unfortunately this is returning true when hitting the home button. if (mEnableLogging) { Log.i(LOG_TAG, " - OuyaUnityActivity.onPause isFinishing, killing unity player!"); } ouyaFacade.shutdown(); userManager.shutdown(); mUnityPlayer.quit(); } mPaused = true; } @Override public void onResume() { super.onResume(); if (null != mInputManager) { mInputManager.registerInputDeviceListener(this, null); } registerReceiver(mAuthChangeReceiver, accountsChangedFilter); UnityPlayer.UnitySendMessage("OuyaBridge", "didResume", ""); if (UNITY_PAUSE_ON_OUYA_OVERLAYS) { mUnityPlayer.resume(); } mPaused = false; } /** * Check for the result from a call through to the authentication intent. If the authentication * was successful then re-try the purchase. */ @Override protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) { if (resultCode == RESULT_OK) { switch (requestCode) { case GAMER_UUID_AUTHENTICATION_ACTIVITY_ID: fetchGamerUUID(); break; case PURCHASE_AUTHENTICATION_ACTIVITY_ID: restartInterruptedPurchase(); break; } } } /** * Restart an interrupted purchase */ private void restartInterruptedPurchase() { final String suspendedPurchaseId = OuyaPurchaseHelper.getSuspendedPurchase(this); if (suspendedPurchaseId == null) { return; } try { for (Product thisProduct : mProductList) { if (suspendedPurchaseId.equals(thisProduct.getIdentifier())) { requestPurchase(thisProduct.getIdentifier()); break; } } } catch (Exception ex) { Log.e(LOG_TAG, "Error during purchase restart request", ex); showError(ex.getMessage()); } } /** * Save the products and receipts if we're going for a restart. */ @Override protected void onSaveInstanceState(final Bundle outState) { if (mProductList != null) { outState.putParcelableArray(PRODUCTS_INSTANCE_STATE_KEY, mProductList.toArray(new Product[mProductList.size()])); } if (mReceiptList != null) { outState.putParcelableArray(RECEIPTS_INSTANCE_STATE_KEY, mReceiptList.toArray(new Receipt[mReceiptList.size()])); } } /** * Get the shared preferences object which is used to store the productId when the user * is being sent for authentication */ private SharedPreferences getProductIdSharedPreferences() { return getSharedPreferences("OuyaUnityActivity", MODE_PRIVATE); } /// Implements InputDeviceListener public @Override void onInputDeviceAdded(int deviceId) { if (mEnableLogging) { Log.i(LOG_TAG, "void onInputDeviceAdded(int deviceId) " + deviceId); } sendDevices(); } public @Override void onInputDeviceChanged(int deviceId) { if (mEnableLogging) { Log.i(LOG_TAG, "void onInputDeviceChanged(int deviceId) " + deviceId); } sendDevices(); } public @Override void onInputDeviceRemoved(int deviceId) { if (mEnableLogging) { Log.i(LOG_TAG, "void onInputDeviceRemoved(int deviceId) " + deviceId); } sendDevices(); } void sendDevices() { // reinitialize controllers OuyaController.init(this); //Get a list of all device id's and assign them to players. ArrayList<Device> devices = checkDevices(); Gson gson = new Gson(); String jsonData = gson.toJson(devices); UnityPlayer.UnitySendMessage("OuyaBridge", "didChangeDevices", jsonData); } private void requestProducts() { ouyaFacade.requestProductList(PRODUCT_IDENTIFIER_LIST, new CancelIgnoringOuyaResponseListener<ArrayList<Product>>() { @Override public void onSuccess(final ArrayList<Product> products) { mProductList = products; addProducts(); } @Override public void onFailure(int errorCode, String errorMessage, Bundle optionalData) { showError("Could not fetch product information (error " + errorCode + ":" + errorMessage); } }); } public boolean isRunningOnOuyaHardware() { boolean rc = ouyaFacade.isRunningOnOUYAHardware(); // The log message is partly here for debugging, partly to remind you not to call this each frame! Log.i(LOG_TAG, "ouyaFacade.isRunningOnOuyaHardware returned " + ("" + rc)); return rc; } public int getOdkVersionNumber() { int rc = ouyaFacade.getOdkVersionNumber(); Log.i(LOG_TAG, "ouyaFacade.getOdkVersionNumber returned " + ("" + rc)); return rc; } public void fetchGamerUUID() { ouyaFacade.requestGamerUuid(new CancelIgnoringOuyaResponseListener<String>() { @Override public void onSuccess(String result) { mGamerUuid = result; // Send back to unity UnityPlayer.UnitySendMessage("OuyaBridge", "didFetchGamerUuid", mGamerUuid); } @Override public void onFailure(int errorCode, String errorMessage, Bundle optionalData) { Log.w(LOG_TAG, "Fetch gamer UUID error (code " + errorCode + ": " + errorMessage + ")"); boolean wasHandledByAuthHelper = OuyaAuthenticationHelper.handleError(OuyaUnityActivity.this, errorCode, errorMessage, optionalData, GAMER_UUID_AUTHENTICATION_ACTIVITY_ID, new OuyaResponseListener<Void>() { @Override public void onSuccess(Void result) { // Retry the fetch if the error was handled fetchGamerUUID(); } @Override public void onFailure(int errorCode, String errorMessage, Bundle optionalData) { showError("Unable to fetch gamer UUID (error " + errorCode + ": " + errorMessage + ")"); } @Override public void onCancel() { showError("Unable to fetch gamer UUID (Attempt to get account cancelled)"); } }); if (!wasHandledByAuthHelper) { showError("Unable to fetch gamer UUID" + errorCode + ": " + errorMessage + ")"); } } }); } private void requestReceipts() { ouyaFacade.requestReceipts(new ReceiptListener()); } private void addProducts() { // Send product information over to Unity. Gson gson = new Gson(); String json = gson.toJson(mProductList); UnityPlayer.UnitySendMessage("OuyaBridge", "didFetchProducts", json); } private void addReceipts() { // Send receipt information over to Unity. Gson gson = new Gson(); String json = gson.toJson(mReceiptList); UnityPlayer.UnitySendMessage("OuyaBridge", "didFetchReceipts", json); } public void requestPurchase(final String productId) throws GeneralSecurityException, UnsupportedEncodingException, JSONException { SecureRandom sr = SecureRandom.getInstance("SHA1PRNG"); // This is an ID that allows you to associate a successful purchase with // it's original request. The server does nothing with this string except // pass it back to you, so it only needs to be unique within this instance // of your app to allow you to pair responses with requests. String uniqueId = Long.toHexString(sr.nextLong()); JSONObject purchaseRequest = new JSONObject(); purchaseRequest.put("uuid", uniqueId); purchaseRequest.put("identifier", productId); purchaseRequest.put("testing", "true"); // This value is only needed for testing, not setting it results in a live purchase String purchaseRequestJson = purchaseRequest.toString(); byte[] keyBytes = new byte[16]; sr.nextBytes(keyBytes); SecretKey key = new SecretKeySpec(keyBytes, "AES"); byte[] ivBytes = new byte[16]; sr.nextBytes(ivBytes); IvParameterSpec iv = new IvParameterSpec(ivBytes); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "BC"); cipher.init(Cipher.ENCRYPT_MODE, key, iv); byte[] payload = cipher.doFinal(purchaseRequestJson.getBytes("UTF-8")); cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "BC"); cipher.init(Cipher.ENCRYPT_MODE, mPublicKey); byte[] encryptedKey = cipher.doFinal(keyBytes); Purchasable purchasable = new Purchasable(productId, Base64.encodeToString(encryptedKey, Base64.NO_WRAP), Base64.encodeToString(ivBytes, Base64.NO_WRAP), Base64.encodeToString(payload, Base64.NO_WRAP)); synchronized (mOutstandingPurchaseRequests) { mOutstandingPurchaseRequests.put(uniqueId, productId); } ouyaFacade.requestPurchase(purchasable, new PurchaseListener(productId)); } private ArrayList<Device> checkDevices() { //Get a list of all device id's and assign them to players. ArrayList<Device> devices = new ArrayList<Device>(); int[] deviceIds = InputDevice.getDeviceIds(); for (int count = 0; count < deviceIds.length; count++) { InputDevice d = InputDevice.getDevice(deviceIds[count]); if (!d.isVirtual()) { Device device = new Device(); device.id = d.getId(); device.player = OuyaController.getPlayerNumByDeviceId(device.id); if (device.player != DEVICE_NOT_OUYACONTROLLER_COMPATIBLE) { device.name = d.getName(); devices.add(device); } } } return devices; } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // Pass to OuyaController first, then process. boolean handled = false; int playerNum = OuyaController.getPlayerNumByDeviceId(event.getDeviceId()); if (playerNum != DEVICE_NOT_OUYACONTROLLER_COMPATIBLE) { handled = OuyaController.onKeyDown(keyCode, event); if (mPaused) return handled || super.onKeyDown(keyCode, event); try { ControllerState data = playerStates[playerNum]; OuyaController c = OuyaController.getControllerByPlayer(playerNum); if (data != null) { data.ButtonO = c.getButton(OuyaController.BUTTON_O); data.ButtonU = c.getButton(OuyaController.BUTTON_U); data.ButtonY = c.getButton(OuyaController.BUTTON_Y); data.ButtonA = c.getButton(OuyaController.BUTTON_A); data.ButtonDPD = c.getButton(OuyaController.BUTTON_DPAD_DOWN); data.ButtonDPU = c.getButton(OuyaController.BUTTON_DPAD_UP); data.ButtonDPL = c.getButton(OuyaController.BUTTON_DPAD_LEFT); data.ButtonDPR = c.getButton(OuyaController.BUTTON_DPAD_RIGHT); data.ButtonL1 = c.getButton(OuyaController.BUTTON_L1); data.ButtonL2 = c.getButton(OuyaController.BUTTON_L2); data.ButtonL3 = c.getButton(OuyaController.BUTTON_L3); data.ButtonR1 = c.getButton(OuyaController.BUTTON_R1); data.ButtonR2 = c.getButton(OuyaController.BUTTON_R2); data.ButtonR3 = c.getButton(OuyaController.BUTTON_R3); } } catch (Exception e) { Log.w(LOG_TAG, "Exception occurred getting controller state for player " + playerNum + ": " + e.toString()); } } return handled || mUnityPlayer.onKeyDown(keyCode, event); } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { boolean handled = false; int playerNum = OuyaController.getPlayerNumByDeviceId(event.getDeviceId()); if (playerNum != DEVICE_NOT_OUYACONTROLLER_COMPATIBLE) { // A special MENU KeyUp event is triggered at the same time as its KeyDown event // in the OUYA SDK. We tell the Unity layer to handle this specially and emulate // a 1-frame menu button press. if (keyCode == OuyaController.BUTTON_MENU) { UnityPlayer.UnitySendMessage("OuyaBridge", "MenuButtonPressed", "" + playerNum); return handled || super.onKeyDown(keyCode, event); } // Pass to OuyaController first, then process. handled = OuyaController.onKeyDown(keyCode, event); if (mPaused) return handled || super.onKeyDown(keyCode, event); try { playerNum = OuyaController.getPlayerNumByDeviceId(event.getDeviceId()); ControllerState data = playerStates[playerNum]; OuyaController c = OuyaController.getControllerByPlayer(playerNum); if (data != null) { data.ButtonO = c.getButton(OuyaController.BUTTON_O); data.ButtonU = c.getButton(OuyaController.BUTTON_U); data.ButtonY = c.getButton(OuyaController.BUTTON_Y); data.ButtonA = c.getButton(OuyaController.BUTTON_A); data.ButtonDPD = c.getButton(OuyaController.BUTTON_DPAD_DOWN); data.ButtonDPU = c.getButton(OuyaController.BUTTON_DPAD_UP); data.ButtonDPL = c.getButton(OuyaController.BUTTON_DPAD_LEFT); data.ButtonDPR = c.getButton(OuyaController.BUTTON_DPAD_RIGHT); data.ButtonL1 = c.getButton(OuyaController.BUTTON_L1); data.ButtonL2 = c.getButton(OuyaController.BUTTON_L2); data.ButtonL3 = c.getButton(OuyaController.BUTTON_L3); data.ButtonR1 = c.getButton(OuyaController.BUTTON_R1); data.ButtonR2 = c.getButton(OuyaController.BUTTON_R2); data.ButtonR3 = c.getButton(OuyaController.BUTTON_R3); } } catch (Exception e) { Log.w(LOG_TAG, "Exception occurred getting controller state for player " + playerNum + ": " + e.toString()); } } return handled || mUnityPlayer.onKeyDown(keyCode, event); } @Override public boolean onGenericMotionEvent(MotionEvent event) { // Pass to OuyaController first, then process. boolean handled = false; int playerNum = OuyaController.getPlayerNumByDeviceId(event.getDeviceId()); // Add the additional conditional that this must be a joystick event (not a pointer event). if (playerNum != DEVICE_NOT_OUYACONTROLLER_COMPATIBLE && ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0)) { handled = OuyaController.onGenericMotionEvent(event); if (mPaused) return handled || super.onGenericMotionEvent(event); // Check if this was a joystick or touch hover event try { ControllerState data = playerStates[playerNum]; OuyaController c = OuyaController.getControllerByPlayer(playerNum); if (data != null) { data.AxisLSX = c.getAxisValue(OuyaController.AXIS_LS_X); data.AxisLSY = c.getAxisValue(OuyaController.AXIS_LS_Y); data.AxisRSX = c.getAxisValue(OuyaController.AXIS_RS_X); data.AxisRSY = c.getAxisValue(OuyaController.AXIS_RS_Y); data.AxisLT = c.getAxisValue(OuyaController.AXIS_L2); data.AxisRT = c.getAxisValue(OuyaController.AXIS_R2); } } catch (Exception e) { Log.i(LOG_TAG, "Exception occurred getting controller state for player " + playerNum + ": " + e.toString()); } } return handled || super.onGenericMotionEvent(event); } /*** /* Unity Interface through JNI /*/ public static ControllerState GetControllerState(int playerNum) { return playerStates[playerNum]; } public class Device { public int id; public int player; public String name; } public static class ControllerState { public float AxisLSX = 0; public float AxisLSY = 0; public float AxisRSX = 0; public float AxisRSY = 0; public float AxisLT = 0; public float AxisRT = 0; public boolean ButtonO = false; public boolean ButtonU = false; public boolean ButtonY = false; public boolean ButtonA = false; public boolean ButtonDPD = false; public boolean ButtonDPU = false; public boolean ButtonDPL = false; public boolean ButtonDPR = false; public boolean ButtonL1 = false; public boolean ButtonL2 = false; public boolean ButtonL3 = false; public boolean ButtonR1 = false; public boolean ButtonR2 = false; public boolean ButtonR3 = false; public boolean ButtonSystem = false; public void Clear() { AxisLSX = 0; AxisLSY = 0; AxisRSX = 0; AxisRSY = 0; AxisLT = 0; AxisRT = 0; ButtonO = false; ButtonU = false; ButtonY = false; ButtonA = false; ButtonDPD = false; ButtonDPU = false; ButtonDPL = false; ButtonDPR = false; ButtonL1 = false; ButtonL2 = false; ButtonL3 = false; ButtonR1 = false; ButtonR2 = false; ButtonR3 = false; ButtonSystem = false; } } /** * Display an error to the user. We're using a toast for simplicity. */ private void showError(final String errorMessage) { Toast.makeText(OuyaUnityActivity.this, errorMessage, Toast.LENGTH_LONG).show(); } /** * The callback for list of user receipts */ private class ReceiptListener extends CancelIgnoringOuyaResponseListener<String> { /** * Handle successful receipts fetch * * @param receiptResponse Server response. */ @Override public void onSuccess(String receiptResponse) { OuyaEncryptionHelper helper = new OuyaEncryptionHelper(); List<Receipt> receipts; try { JSONObject response = new JSONObject(receiptResponse); if (response.has("key") && response.has("iv")) { receipts = helper.decryptReceiptResponse(response, mPublicKey); } else { receipts = helper.parseJSONReceiptResponse(receiptResponse); } } catch (GeneralSecurityException e) { throw new RuntimeException(e); } catch (JSONException e) { if (e.getMessage().contains("ENCRYPTED")) { // This is a hack for some testing code which will be removed // before the consumer release try { receipts = helper.parseJSONReceiptResponse(receiptResponse); } catch (IOException ioe) { throw new RuntimeException(ioe); } } else { throw new RuntimeException(e); } } catch (IOException e) { throw new RuntimeException(e); } catch (Exception e) { Log.w(LOG_TAG, "Receipt Listener received invalid response error (" + e.getMessage() + ")"); return; } Collections.sort(receipts, new Comparator<Receipt>() { @Override public int compare(Receipt lhs, Receipt rhs) { return rhs.getPurchaseDate().compareTo(lhs.getPurchaseDate()); } }); mReceiptList = receipts; // Report receipt list back to Unity. Gson gson = new Gson(); String receiptJson = gson.toJson(mReceiptList); UnityPlayer.UnitySendMessage("OuyaBridge", "didFetchReceipts", receiptJson); } /** * Handle a failure. Because displaying the receipts is not critical to the application we just show an error * message rather than asking the user to authenticate themselves just to start the application up. * * @param errorCode An HTTP error code between 0 and 999, if there was one. Otherwise, an internal error code from the * Ouya server, documented in the {@link OuyaErrorCodes} class. * * @param errorMessage Empty for HTTP error codes. Otherwise, a brief, non-localized, explanation of the error. * * @param optionalData A Map of optional key/value pairs which provide additional information. */ @Override public void onFailure(int errorCode, String errorMessage, Bundle optionalData) { Log.w(LOG_TAG, "Request Receipts error (code " + errorCode + ": " + errorMessage + ")"); showError("Could not fetch receipts (error " + errorCode + ": " + errorMessage + ")"); } } /** * Callback for purchases */ private class PurchaseListener extends CancelIgnoringOuyaResponseListener<String> { /** * The ID of the product the user is trying to purchase. This is used in onFailure to start a re-purchase * if the user wishes to do so. */ private String mProductId; PurchaseListener(final String productId) { mProductId = productId; } @Override public void onSuccess(String result) { Product product; String id; try { OuyaEncryptionHelper helper = new OuyaEncryptionHelper(); JSONObject response = new JSONObject(result); if (response.has("key") && response.has("iv")) { id = helper.decryptPurchaseResponse(response, mPublicKey); String storedProductId; synchronized (mOutstandingPurchaseRequests) { storedProductId = mOutstandingPurchaseRequests.remove(id); } if (storedProductId == null || !storedProductId.equals(mProductId)) { onFailure(OuyaErrorCodes.THROW_DURING_ON_SUCCESS, "Purchased product is not the same as purchase request product", Bundle.EMPTY); return; } } else { product = new Product(new JSONObject(result)); if (!mProductId.equals(product.getIdentifier())) { onFailure(OuyaErrorCodes.THROW_DURING_ON_SUCCESS, "Purchased product is not the same as purchase request product", Bundle.EMPTY); return; } } } catch (ParseException e) { onFailure(OuyaErrorCodes.THROW_DURING_ON_SUCCESS, e.getMessage(), Bundle.EMPTY); return; } catch (JSONException e) { if (e.getMessage().contains("ENCRYPTED")) { // This is a hack for some testing code which will be removed // before the consumer release try { product = new Product(new JSONObject(result)); if (!mProductId.equals(product.getIdentifier())) { onFailure(OuyaErrorCodes.THROW_DURING_ON_SUCCESS, "Purchased product is not the same as purchase request product", Bundle.EMPTY); return; } } catch (JSONException jse) { onFailure(OuyaErrorCodes.THROW_DURING_ON_SUCCESS, e.getMessage(), Bundle.EMPTY); return; } } else { onFailure(OuyaErrorCodes.THROW_DURING_ON_SUCCESS, e.getMessage(), Bundle.EMPTY); return; } } catch (IOException e) { onFailure(OuyaErrorCodes.THROW_DURING_ON_SUCCESS, e.getMessage(), Bundle.EMPTY); return; } catch (GeneralSecurityException e) { onFailure(OuyaErrorCodes.THROW_DURING_ON_SUCCESS, e.getMessage(), Bundle.EMPTY); return; } // Report success back to Unity UnityPlayer.UnitySendMessage("OuyaBridge", "didPurchaseProductId", mProductId); // Re-request receipts to keep receipt data up to date requestReceipts(); } /** * Handle an error. If the OUYA framework supplies an intent this means that the user needs to * either authenticate or re-authenticate themselves, so we start the supplied intent. * * @param errorCode An HTTP error code between 0 and 999, if there was one. Otherwise, an internal error code from the * Ouya server, documented in the {@link OuyaErrorCodes} class. * * @param errorMessage Empty for HTTP error codes. Otherwise, a brief, non-localized, explanation of the error. * * @param optionalData A Map of optional key/value pairs which provide additional information. */ @Override public void onFailure(int errorCode, String errorMessage, Bundle optionalData) { // Suspend failure purchases OuyaPurchaseHelper.suspendPurchase(OuyaUnityActivity.this, mProductId); boolean wasHandledByHelper = OuyaAuthenticationHelper.handleError(OuyaUnityActivity.this, errorCode, errorMessage, optionalData, PURCHASE_AUTHENTICATION_ACTIVITY_ID, new OuyaResponseListener<Void>() { @Override public void onSuccess(Void result) { restartInterruptedPurchase(); // Retry the purchase } @Override public void onFailure(int errorCode, String errorMessage, Bundle optionalData) { showError("Unable to make purchase (error " + errorCode + ": " + errorMessage + ")"); } @Override public void onCancel() { showError("Unable to make purchase"); } }); if (!wasHandledByHelper) { // Show the user the error and offer them ability to repurchase if they think the error is not permanent. // Show the user the error and offer them the ability to re-purchase if they // decide the error is not permanent. new AlertDialog.Builder(OuyaUnityActivity.this).setTitle(getString(R.string.alert_title)) .setMessage("Unfortunately, your purchase failed [error code " + errorCode + " (" + errorMessage + ")]. Would you like to try again?") .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { dialogInterface.dismiss(); try { requestPurchase(mProductId); } catch (Exception e) { Log.e(LOG_TAG, "Error during purchase", e); showError(e.getMessage()); } } }).setNegativeButton(R.string.cancel, null).show(); } } } }