com.tealeaf.NativeShim.java Source code

Java tutorial

Introduction

Here is the source code for com.tealeaf.NativeShim.java

Source

/* @license
 * This file is part of the Game Closure SDK.
 *
 * The Game Closure SDK is free software: you can redistribute it and/or modify
 * it under the terms of the Mozilla Public License v. 2.0 as published by Mozilla.
    
 * The Game Closure SDK is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * Mozilla Public License v. 2.0 for more details.
    
 * You should have received a copy of the Mozilla Public License v. 2.0
 * along with the Game Closure SDK.  If not, see <http://mozilla.org/MPL/2.0/>.
 */
package com.tealeaf;

import java.util.Locale;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.concurrent.Callable;
import org.json.JSONObject;
import java.lang.StringBuilder;
import java.io.*;

import com.google.gson.Gson;
import com.tealeaf.event.DialogButtonClickedEvent;
import com.tealeaf.event.OnlineEvent;
import com.tealeaf.plugin.PluginManager;
import com.tealeaf.util.Connection;
import com.tealeaf.util.ILogger;
import com.tealeaf.util.XMLHttpRequest;

import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.graphics.Bitmap;
import android.os.RemoteException;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.view.inputmethod.EditorInfo;
import android.text.InputType;

import android.view.Window;

import android.util.DisplayMetrics;
import android.view.WindowManager;
import android.os.Build;

public class NativeShim {
    private static HashMap<String, TeaLeafCallable> callables = new HashMap<String, TeaLeafCallable>();
    private SoundQueue soundQueue;
    private TextureLoader textureLoader;
    private TextManager textManager;
    private LocalStorage localStorage;
    private ContactList contactList;
    private Haptics haptics;
    private LocationManager locationManager;
    private TeaLeaf context;
    private ResourceManager resourceManager;
    private ArrayList<TeaLeafSocket> sockets = new ArrayList<TeaLeafSocket>();
    private ArrayList<String> overlayEvents = new ArrayList<String>();
    private ILogger remoteLogger;
    private ConnectivityManager connectivityManager;
    private NetworkStateReceiver networkStateReceiver;
    private boolean onlineStatus;
    private int statusBarHeight;
    private Gson gson = new Gson();

    public NativeShim(TextManager textManager, TextureLoader textureLoader, SoundQueue soundQueue,
            LocalStorage localStorage, ContactList contactList, LocationManager locationManager,
            ResourceManager resourceManager, TeaLeaf context) {
        this.textManager = textManager;
        this.textureLoader = textureLoader;
        new Thread(this.textureLoader).start();
        this.soundQueue = soundQueue;
        this.localStorage = localStorage;
        this.contactList = contactList;
        this.haptics = new Haptics(context);
        this.locationManager = locationManager;
        this.resourceManager = resourceManager;
        this.context = context;
        this.remoteLogger = context.getRemoteLogger();
        this.connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        this.networkStateReceiver = new NetworkStateReceiver(this);
        this.onlineStatus = false;
        this.updateOnlineStatus();

        this.statusBarHeight = 0;
        int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            this.statusBarHeight = context.getResources().getDimensionPixelSize(resourceId);
            logger.log("status bar height:", this.statusBarHeight);
        }

        IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
        context.registerReceiver(this.networkStateReceiver, filter);

    }

    public void onPause() {
    }

    public void onResume() {
    }

    public String getVersionCode() {
        return context.getOptions().getBuildIdentifier();
    }

    //xhr
    public void sendXHR(int id, String method, String url, String data, boolean async, String[] requestHeaders) {
        HashMap<String, String> requestHeadersMap = new HashMap<String, String>();
        if (requestHeaders != null) {
            for (int i = 0; i < requestHeaders.length / 2; i++) {
                requestHeadersMap.put(requestHeaders[i * 2], requestHeaders[i * 2 + 1]);
            }
        }
        XMLHttpRequest xhr = new XMLHttpRequest(id, method, url, data, async, requestHeadersMap);

        if (async) {
            Thread xhrThread = new Thread(xhr);
            xhrThread.setPriority(Thread.MIN_PRIORITY + 2);
            xhrThread.start();
        } else {
            xhr.run();
        }
    }

    // Display
    public DisplayMetrics getDisplayMetrics() {
        DisplayMetrics metrics = new DisplayMetrics();
        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        windowManager.getDefaultDisplay().getMetrics(metrics);
        return metrics;
    }

    //Haptics
    public void cancel() {
        haptics.cancel();
    }

    public void vibrate(long milliseconds) {
        haptics.vibrate(milliseconds);
    }

    public void vibrate(long[] pattern, int repeat) {
        haptics.vibrate(pattern, repeat);
    }

    public boolean hasVibrator() {
        return haptics.hasVibrator();
    }

    public void showDialog(final String title, final String text, final String imageUrl, final String[] buttons,
            int[] callbacks) {
        final Runnable[] cbs = new Runnable[callbacks.length];
        for (int i = 0; i < callbacks.length; i++) {
            final int callback = i;
            cbs[i] = new Runnable() {
                public void run() {
                    EventQueue.pushEvent(new DialogButtonClickedEvent(callback));
                }
            };
        }
        Bitmap bitmap = null;
        if (imageUrl != null) {
            textureLoader.getImage(imageUrl);
        }
        final Bitmap image = bitmap;
        context.runOnUiThread(new Runnable() {
            public void run() {
                JSDialog.showDialog(context, image, title, text, buttons, cbs);
            }
        });
    }

    //Overlay
    public void loadOverlay(final String url) {
        context.runOnUiThread(new Runnable() {
            public void run() {
                context.getOverlay().load(url);
            }
        });
    }

    public void showOverlay() {
        context.runOnUiThread(new Runnable() {
            public void run() {
                context.getOverlay().show();
            }
        });
    }

    public void hideOverlay() {
        context.runOnUiThread(new Runnable() {
            public void run() {
                context.getOverlay().hide();
            }
        });
    }

    public void sendEventToOverlay(final String event) {
        context.runOnUiThread(new Runnable() {
            public void run() {
                context.getOverlay().sendEvent(event);
            }
        });
    }

    private int textInputId = 0;

    public int openPrompt(final String title, final String message, final String okText, final String cancelText,
            final String value, final boolean autoShowKeyboard, final boolean isPassword) {
        return InputPrompt.getInstance().open(context, title, message, okText, cancelText, value, autoShowKeyboard,
                isPassword);
    }

    public void showSoftKeyboard(final String text, final String hint, final boolean hasBackward,
            final boolean hasForward, final String inputType, final String inputReturnButton, final int maxLength,
            final int cursorPos) {
        context.runOnUiThread(new Runnable() {
            public void run() {
                TextEditViewHandler textEditView = context.getTextEditViewHandler();
                textEditView.activate(text, hint, hasBackward, hasForward, inputType, inputReturnButton, maxLength,
                        cursorPos);
            }
        });
    }

    public void hideSoftKeyboard() {
        context.runOnUiThread(new Runnable() {
            public void run() {
                TextEditViewHandler textEditView = context.getTextEditViewHandler();
                textEditView.closeKeyboard();
            }
        });
    }

    public int getStatusBarHeight() {
        return statusBarHeight;
    }

    public void showStatusBar() {
        context.runOnUiThread(new Runnable() {
            public void run() {
                context.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
            }
        });
    }

    public void hideStatusBar() {
        context.runOnUiThread(new Runnable() {
            public void run() {
                context.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
            }
        });
    }

    //TextInputView
    public int createTextBox() {
        return context.getTextInputView().createNew();
    }

    public int createTextBox(final int x, final int y, final int w, final int h, final String initialValue) {
        context.runOnUiThread(new Runnable() {
            public void run() {
                context.getTextInputView().createNew(x, y, w, h, initialValue);
            }
        });

        return 0;
    }

    public void destroyTextBox(int id) {
        context.getTextInputView().destroy(id);
    }

    public void showTextBox(int id) {
        context.getTextInputView().show(id);
    }

    public void hideTextBox(int id) {
        context.getTextInputView().hide(id);
    }

    public void textBoxSelectAll(int id) {
        context.getTextInputView().selectAll(id);
    }

    public boolean getTextBoxVisible(int id) {
        return context.getTextInputView().getVisible(id);
    }

    public int getTextBoxX(int id) {
        return context.getTextInputView().getX(id);
    }

    public int getTextBoxY(int id) {
        return context.getTextInputView().getY(id);
    }

    public int getTextBoxWidth(int id) {
        return context.getTextInputView().getWidth(id);
    }

    public int getTextBoxHeight(int id) {
        return context.getTextInputView().getHeight(id);
    }

    public int getTextBoxType(int id) {
        return context.getTextInputView().getType(id);
    }

    public String getTextBoxValue(int id) {
        return context.getTextInputView().getValue(id);
    }

    public float getTextBoxOpacity(int id) {
        return context.getTextInputView().getOpacity(id);
    }

    public void setTextBoxVisible(int id, boolean visible) {
        context.getTextInputView().setVisible(id, visible);
    }

    public void setTextBoxPosition(int id, int x, int y, int w, int h) {
        context.getTextInputView().setPosition(id, x, y, w, h);
    }

    public void setTextBoxDimensions(int id, int w, int h) {
        context.getTextInputView().setDimensions(id, w, h);
    }

    public void setTextBoxX(int id, int x) {
        context.getTextInputView().setX(id, x);
    }

    public void setTextBoxY(int id, int y) {
        context.getTextInputView().setY(id, y);
    }

    public void setTextBoxWidth(int id, int w) {
        context.getTextInputView().setWidth(id, w);
    }

    public void setTextBoxHeight(int id, int h) {
        context.getTextInputView().setHeight(id, h);
    }

    public void setTextBoxType(int id, int type) {
        context.getTextInputView().setType(id, type);
    }

    public void setTextBoxValue(int id, String value) {
        context.getTextInputView().setValue(id, value);
    }

    public void setTextBoxOpacity(int id, float value) {
        context.getTextInputView().setOpacity(id, value);
    }

    //Locale
    public String getCountry() {
        return Locale.getDefault().getCountry();
    }

    public String getLanguage() {
        return Locale.getDefault().getLanguage();
    }

    //Device
    public String getDeviceID() {
        return Device.getDeviceID(context, context.getSettings());
    }

    public String getDeviceInfo() {
        return Device.getDeviceInfo();
    }

    public void setStayAwake(final boolean on) {
        final Window w = context.getWindow();

        context.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (on) {
                    w.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
                } else {
                    w.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
                }
            }
        });
    }

    public void reload() {
        context.reload();
    }

    //Textures
    public int measureText(String font, int size, byte[] textBytes) {
        int textSize = 0;
        try {
            String text = new String(textBytes, "UTF-8");
            textSize = textManager.measureText(font, size, text);
        } catch (Exception e) {
            logger.log(e);
        }
        return textSize;
    }

    public void loadTexture(byte[] urlBytes) {
        try {
            String url = new String(urlBytes, "UTF-8");
            textureLoader.loadTexture(url);
        } catch (Exception e) {
            logger.log(e);
        }
    }

    public int cameraGetPhoto(int width, int height, int crop) {
        int id = textureLoader.getNextCameraId();
        textureLoader.loadCameraPicture("" + id, width, height, crop);
        return id;
    }

    public int galleryGetPhoto(int width, int height, int crop) {
        int id = textureLoader.getNextGalleryId();
        textureLoader.loadGalleryPicture("" + id, width, height, crop);
        return id;
    }

    public int getNextCameraId() {
        return textureLoader.getNextCameraId();
    }

    public int getNextGalleryId() {
        return textureLoader.getNextGalleryId();
    }

    public void clearTextureData() {
        clearTextures();
    }

    public void setHalfsizedTexturesSetting(boolean on) {
        Settings settings = context.getSettings();
        settings.setBoolean("@__use_halfsized_textures__", on);
    }

    //Sound
    private static final boolean DO_SOUND = true;

    public void loadSound(final String url) {
        if (!DO_SOUND) {
            return;
        }
        soundQueue.loadSound(url);
    }

    public void playSound(String url, float volume, boolean loop) {
        if (!DO_SOUND) {
            return;
        }
        soundQueue.playSound(url, volume, loop);
    }

    public void loadBackgroundMusic(String url) {
        if (!DO_SOUND) {
            return;
        }
        soundQueue.loadBackgroundMusic(url);
    }

    public void playBackgroundMusic(final String url, final float volume, final boolean loop) {
        if (!DO_SOUND) {
            return;
        }
        soundQueue.playBackgroundMusic(url, volume, loop);
    }

    public void stopSound(String url) {
        soundQueue.stopSound(url);
    }

    public void pauseSound(String url) {
        soundQueue.pauseSound(url);
    }

    public void setVolume(String url, float volume) {
        soundQueue.setVolume(url, volume);
    }

    public void seekTo(String url, float position) {
        soundQueue.seekTo(url, position);
    }

    public void haltSounds() {
        soundQueue.haltSounds();
    }

    // Sockets
    public void sendData(int id, String data) {
        if (sockets.size() > id) {
            TeaLeafSocket socket = sockets.get(id);
            if (socket != null) {
                socket.write(data);
            } else {
                logger.log("{socket} WARNING: Send data failed on broken socket");
            }
        }
    }

    public int openSocket(String host, int port) {
        int id = sockets.size();
        logger.log("{socket} Connecting to ", host, ":", port, " (id=", id, ")");

        TeaLeafSocket socket = new TeaLeafSocket(host, port, id);
        sockets.add(socket);
        new Thread(socket).start();
        return id;
    }

    public void closeSocket(int id) {
        TeaLeafSocket socket = sockets.get(id);
        if (socket != null) {
            socket.close();
        }
    }

    //Source Loading
    public String loadSourceFile(String url) {
        TeaLeafOptions options = context.getOptions();
        String sourceString = null;
        if (options.isDevelop() && options.get("forceURL", false)) {
            // load native.js from the file system
            // read file in
            String path = resourceManager.getStorageDirectory();
            String result = null;
            DataInputStream in = null;
            try {
                File f = new File(path + url);
                byte[] buffer = new byte[(int) f.length()];
                in = new DataInputStream(new FileInputStream(f));
                in.readFully(buffer);
                result = new String(buffer);
            } catch (FileNotFoundException e) {
                logger.log("Error loading", url, "from", path);
                logger.log("File not found!");
                throw new RuntimeException("File not found in loadSourceFile");
            } catch (IOException e) {
                throw new RuntimeException("IO problem in fileToString", e);
            } finally {
                try {
                    if (in != null) {
                        in.close();
                    }
                } catch (IOException e) {
                    logger.log(e);
                }
            }
            sourceString = result;

        } else {
            sourceString = resourceManager.getFileContents(url);
        }
        return sourceString;
    }

    //GL stuff
    public native static void initGL(int glName);

    public native static void setSingleShader(boolean on);

    public native static void setHalfsizedTextures(boolean on);

    //Contacts stuff
    public static native void dispatchContactCallback(int cb, long id, String name);

    public native static void textureManagerSetMaxMemory(int bytes);

    public native static void textureManagerMemoryWarning();

    public native static void textureManagerMemoryCritical();

    //LocalStorage
    public void setData(String key, String data) {
        localStorage.setData(key, data);
    }

    public String getData(String key) {
        return localStorage.getData(key);
    }

    public void setData(String key, byte[] data) {
        try {
            String dataString = new String(data, "UTF-8");
            localStorage.setData(key, dataString);
        } catch (Exception e) {
            logger.log(e);
        }
    }

    public byte[] getDataAsBytes(String key) {
        try {
            return localStorage.getData(key).getBytes("UTF-8");
        } catch (Exception e) {
            return null;
        }
    }

    public void removeData(String key) {
        localStorage.removeData(key);
    }

    public void clearData() {
        localStorage.clear();

    }

    //location manager
    public void setLocation(String uri) {
        locationManager.setLocation(uri);
    }

    //love me some blocks
    {
    }

    //call
    public String call(String method, byte[] byteArgs) {
        String args = "{}";
        try {
            args = new String(byteArgs, "UTF-8");
        } catch (Exception e) {
            logger.log(e);
        }
        JSONObject jsonArgs = null;
        try {
            jsonArgs = new JSONObject(args);
        } catch (Exception e) {
            jsonArgs = new JSONObject();
            logger.log("NativeShim.call", e);
        }

        JSONObject jsonRet = null;
        try {
            jsonRet = callables.get(method).call(jsonArgs);
        } catch (Exception e) {
            logger.log(e);
            jsonRet = new JSONObject();
        }

        return jsonRet.toString();
    }

    public static void RegisterCallable(String method, TeaLeafCallable callable) {
        callables.put(method, callable);
    }

    final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();

    public static String bytesToHex(byte[] bytes) {
        char[] hexChars = new char[bytes.length * 2];
        int v;
        for (int j = 0; j < bytes.length; j++) {
            v = bytes[j] & 0xFF;
            hexChars[j * 2] = hexArray[v >>> 4];
            hexChars[j * 2 + 1] = hexArray[v & 0x0F];
        }
        return new String(hexChars);
    }

    // plugins
    public String pluginsCall(final String className, final String methodName, final Object[] params) {
        //      there may be issues not running this on the ui thread however
        //      it is required for now and there doesn't seem to be any immediate
        //      concerns with it running on any thread

        for (int i = 0; i < params.length; i++) {
            if (params[i].getClass() == byte[].class) {
                try {
                    params[i] = new String((byte[]) params[i], "UTF-8");
                } catch (Exception e) {
                    logger.log(e);
                }
            }
        }
        return PluginManager.call(className, methodName, params);
    }

    public void pluginsRequest(final String className, final String methodName, final Object[] params,
            final int requestId) {
        PluginManager.request(className, methodName, params, requestId);
    }

    // native
    public String getMarketUrl() {
        return "market://details?id=" + context.getPackageName();
    }

    public void startGame(String appid) {
        logger.log("{tealeaf} Starting", appid == null ? "the default game" : appid);
        context.glView.startCrossPromo(appid);
    }

    public void applyUpdate() {
        logger.log("{tealeaf} Applying game update");
        TeaLeafOptions options = context.getOptions();
        Settings settings = context.getSettings();
        if (settings.isMarketUpdate(options.getBuildIdentifier())) {
            locationManager.setLocation(getMarketUrl());
        } else {
            Updater updater = new Updater(context, context.getOptions(),
                    settings.getUpdateUrl(options.getBuildIdentifier()));
            if (updater.apply()) {
                settings.clear("updating_now");
                options.setBuildIdentifier(settings.getUpdateBuild(options.getBuildIdentifier()));
                settings.markUnpacked(options.getBuildIdentifier());
                startGame(null);
            }
        }
    }

    public boolean sendActivityToBack() {
        boolean success = context.moveTaskToBack(true);

        if (!success) {
            logger.log("{tealeaf} WARNING: Unable to move activity to background");
        } else {
            logger.log("{tealeaf} Moved activity to background");
        }

        return success;
    }

    public String getStorageDirectory() {
        return resourceManager.getStorageDirectory();
        //return resourceManager.resolveFile(filename);
    }

    // network status
    // TODO move all of this to TeaLeaf and register the receiver there, and make it use that directly instead
    public void updateOnlineStatus() {
        this.onlineStatus = Connection.available(context);

        if (this.onlineStatus) {
            logger.log("{reachability} Online");
        } else {
            logger.log("{reachability} Offline");
        }
    }

    public boolean getOnlineStatus() {
        return this.onlineStatus;
    }

    public void sendOnlineEvent() {
        EventQueue.pushEvent(new OnlineEvent(this.onlineStatus));
    }

    public void uploadDeviceInfo() {
        remoteLogger.sendDeviceInfoEvent(context);
    }

    public void logJavascriptError(final String message, final String url, final int lineNumber) {
        if (context.getOptions().isDevelop()) {
            context.runOnUiThread(new Runnable() {
                public void run() {
                    JSDialog.showDialog(context, null, "JS Error", url + " line " + lineNumber + "\n" + message,
                            new String[] { "OK" }, new Runnable[] { new Runnable() {
                                public void run() {
                                }
                            } });
                }
            });
        } else {
            remoteLogger.sendErrorEvent(context, "JS Error at " + url + " line " + lineNumber + ": " + message);
        }
    }

    public int[] reportGlError(int errorCode) {
        Settings settings = context.getSettings();
        String glErrorStr = settings.getString("gl_errors", "NONE");
        String errorCodeStr = Integer.toString(errorCode);

        ArrayList<String> glErrorList = null;
        if (glErrorStr.equals("NONE")) {
            glErrorList = new ArrayList<String>();
        } else {
            glErrorList = new ArrayList<String>(Arrays.asList(glErrorStr.split(",")));
        }

        int[] glErrorInts = new int[glErrorList.size()];

        if (glErrorList.contains(errorCodeStr)) {
            //create the return array, do not log as this error has been seen
            for (int i = 0; i < glErrorInts.length; i++) {
                glErrorInts[i] = Integer.parseInt(glErrorList.get(i));
            }

        } else {
            //create the return array and log
            glErrorList.add(errorCodeStr);

            //build the settings string
            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 0; i < glErrorList.size(); i++) {
                stringBuilder.append(glErrorList.get(i));
                if (i != glErrorList.size() - 1) {
                    stringBuilder.append(",");
                }
            }

            //save the new errors
            settings.setString("gl_errors", stringBuilder.toString());

            //log the error
            String errorString = "GL ERROR: " + errorCodeStr;
            if (context.getOptions().isDevelop()) {
                logger.log(errorString);
            } else {
                remoteLogger.sendGLErrorEvent(context, errorString);
            }

            //create the return array
            for (int i = 0; i < glErrorInts.length; i++) {
                glErrorInts[i] = Integer.parseInt(glErrorList.get(i));
            }
        }

        return glErrorInts;
    }

    public void takeScreenshot() {
        context.takeScreenshot = true;
    }

    public int getTotalMemory() {
        return Device.getTotalMemory();
    }

    public void logNativeError() {
        Intent intent = new Intent(context, CrashRecover.class);
        context.startActivity(intent);
    }

    //build info
    public String getSDKHash() {
        return context.getOptions().getSDKHash();
    }

    public String getAndroidHash() {
        return context.getOptions().getAndroidHash();
    }

    public String getGameHash() {
        return context.getOptions().getGameHash();
    }

    public String getAndroidVersion() {
        return android.os.Build.VERSION.RELEASE;
    }

    public String getSimulateID() {
        return context.getOptions().getSimulateID();
    }

    //Install stuff
    public String getInstallReferrer() {
        return context.getSettings().getString("installReferrer.referrer", "");
    }

    //Local
    public String getLocaleCountry() {
        return LocaleInfo.getCountry(context);
    }

    public String getLocaleLanguage() {
        return LocaleInfo.getLanguage(context);
    }

    public boolean isSimulator() {
        return Build.HARDWARE.contains("goldfish");
    }

    //Initialization and Running JS
    public static native boolean initIsolate();

    public static native void init(Object shim, String codeHost, String tcpHost, int codePort, int tcpPort,
            String entryPoint, String sourceDir, int width, int height, boolean remote_loading, String splash,
            String simulateID);

    public static native boolean initJS(String uri, String androidHash);

    public static native void destroy();

    public static native void reset();

    public static native void run();

    //GL stuff
    public static native void step(int dt);

    public static native void resizeScreen(int w, int h);

    public static native void reloadTextures();

    public static native void reloadCanvases();

    public static native void clearTextures();

    public static void onTextureLoaded(String url, int name, int width, int height, int originalWidth,
            int originalHeight, int numChannels) {
        try {
            onTextureLoaded(url.getBytes("UTF-8"), name, width, height, originalWidth, originalHeight, numChannels);
        } catch (Exception e) {
            logger.log(e);
        }
    }

    public static native void onTextureLoaded(byte[] urlBytes, int name, int width, int height, int originalWidth,
            int originalHeight, int numChannels);

    public static native void onTextureFailedToLoad(String url);

    //Input stuff
    public static void dispatchEvents(String[] event) {
        byte[][] event_bytes = new byte[event.length][];
        for (int i = 0; i < event.length; i++) {
            try {
                event_bytes[i] = event[i].getBytes("UTF-8");
            } catch (Exception e) {
                event_bytes[i] = new byte[1];
            }
        }
        dispatchEvents(event_bytes);
    }

    public static native void dispatchEvents(byte[][] event);

    public static native void dispatchInputEvents(int[] ids, int[] types, int[] xs, int[] ys, int count);

    public static native void saveTextures();
}