package com.fteqw; import javax.microedition.khronos.egl.EGLContext; import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.opengles.GL10; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.view.Window; import android.view.WindowManager; import android.opengl.GLSurfaceView; import android.view.KeyEvent; import android.view.MotionEvent; import android.app.AlertDialog; import android.content.DialogInterface; import android.hardware.SensorManager; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioTrack; import android.os.Environment; import android.view.inputmethod.InputMethodManager; import android.os.Vibrator; public class FTEDroidActivity extends Activity { private static final int USE_GLES_VERSION = 1; //valid values: 1 or 2. If set to 2, it'll still fall back to 1 if gles2 isn't supported on this device. private SensorManager sensorman; private Sensor sensoracc; private FTEView view; float acc_x, acc_y, acc_z; /*might be some minor race condition on these*/ private String basedir, userdir; private class FTEEGLConfig implements GLSurfaceView.EGLConfigChooser { int version; public void setversion(FTEView view, int version) { this.version = version; view.setEGLContextClientVersion(version); } public boolean CheckGLES2Support() { EGL10 egl = (EGL10) EGLContext.getEGL(); EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); EGLConfig cfg; int oldver = this.version; int[] version = new int[2]; egl.eglInitialize(display, version); this.version = 2; cfg = chooseConfig(egl, display); this.version = oldver; int[] value = {0}; egl.eglGetConfigAttrib(display, cfg, EGL10.EGL_RENDERABLE_TYPE, value); egl.eglTerminate(display); return ((value[0] & 4) == 4); } @Override public EGLConfig chooseConfig (EGL10 egl, EGLDisplay display) { int EGL_CONTEXT_CLIENT_VERSION = 0x3098; EGLConfig[] cfg = new EGLConfig[64]; int[] num_configs = {0}; int[] value = {0}; int i; int[] attribs = { egl.EGL_RENDERABLE_TYPE, (version>=2?4:1)/*egl.EGL_OPENGL_ES2_BIT*/, // egl.EGL_SURFACE_TYPE, egl.EGL_WINDOW_BIT, egl.EGL_BLUE_SIZE, 5, egl.EGL_GREEN_SIZE, 6, egl.EGL_RED_SIZE, 5, egl.EGL_DEPTH_SIZE, 16, // egl.EGL_STENCIL_SIZE, 8, egl.EGL_NONE, egl.EGL_NONE }; if (!egl.eglChooseConfig(display, attribs, cfg, 64, num_configs)) throw new IllegalArgumentException("eglChooseConfig failed"); if (num_configs[0] == 0) { attribs[1] = 1; //egl.EGL_RENDERABLE_TYPE, 1/*egl.EGL_OPENGL_ES_BIT*/, if (!egl.eglChooseConfig(display, attribs, cfg, 64, num_configs)) throw new IllegalArgumentException("eglChooseConfig failed"); if (num_configs[0] == 0) { throw new IllegalArgumentException("eglChooseConfig didn't report any valid configs"); } } android.util.Log.i("FTEDroid", "Found " + num_configs[0] + " EGL configs."); //try to find a gles2 context instead. for (i = 0; i < num_configs[0]; i++) { android.util.Log.i("FTEDroid", "Config " + i + ":"); egl.eglGetConfigAttrib(display, cfg[i], egl.EGL_RED_SIZE, value); android.util.Log.i("FTEDroid", "EGL_RED_SIZE " + value[0]); egl.eglGetConfigAttrib(display, cfg[i], egl.EGL_GREEN_SIZE, value); android.util.Log.i("FTEDroid", "EGL_GREEN_SIZE " + value[0]); egl.eglGetConfigAttrib(display, cfg[i], egl.EGL_BLUE_SIZE, value); android.util.Log.i("FTEDroid", "EGL_BLUE_SIZE " + value[0]); egl.eglGetConfigAttrib(display, cfg[i], egl.EGL_DEPTH_SIZE, value); android.util.Log.i("FTEDroid", "EGL_DEPTH_SIZE " + value[0]); egl.eglGetConfigAttrib(display, cfg[i], egl.EGL_STENCIL_SIZE, value); android.util.Log.i("FTEDroid", "EGL_STENCIL_SIZE " + value[0]); egl.eglGetConfigAttrib(display, cfg[i], egl.EGL_RENDERABLE_TYPE, value); android.util.Log.i("FTEDroid", "EGL_RENDERABLE_TYPE " + value[0]); if ((value[0] & 4) == 4) { android.util.Log.i("FTEDroid", "Found a GLES2 context!"); return cfg[i]; } } return cfg[0]; } } private class FTERenderer implements GLSurfaceView.Renderer { private boolean inited; public int glesversion; FTEDroidActivity act; FTEView theview; FTEEGLConfig cfgchooser; int notifiedflags; void updateGLESVersion() { if (FTEDroidEngine.getpreferedglesversion() < 2) { android.util.Log.i("FTEDroid", "Using GLES1"); this.glesversion = 1; } else if (android.os.Build.VERSION.SDK_INT >= 8) //could be 5 with setEGLContextFactory instead of setEGLContextClientVersion { if (cfgchooser.CheckGLES2Support()) { android.util.Log.i("FTEDroid", "Support for GLES2 detected"); this.glesversion = 2; cfgchooser.setversion(theview, this.glesversion); } else { android.util.Log.i("FTEDroid", "GLES2 not supported. Using GLES1."); this.glesversion = 1; } } else { android.util.Log.i("FTEDroid", "GLES2 requires android 2.2+"); this.glesversion = 1; } } FTERenderer(FTEView view, FTEDroidActivity parent) { act = parent; theview = view; FTEDroidEngine.init(0, 0, 0, basedir, userdir); inited = true; cfgchooser = new FTEEGLConfig(); // theview.setEGLConfigChooser(cfgchooser); updateGLESVersion(); } @Override public void onDrawFrame(GL10 gl) { if (inited == true) { int flags; flags = FTEDroidEngine.frame(act.acc_x, act.acc_y, act.acc_z); if (flags != notifiedflags) { if (((flags ^ notifiedflags) & 1) != 0) { final int fl = flags; Runnable r = new Runnable() { //doing this on the ui thread because android sucks. public void run() { InputMethodManager im = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE); if (im != null) { if ((fl & 1) != 0) { // getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); im.showSoftInput(theview, 0);//InputMethodManager.SHOW_FORCED); } else { // getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); im.hideSoftInputFromWindow(theview.getWindowToken(), 0); } } else android.util.Log.i("FTEDroid", "IMM failed"); } }; act.runOnUiThread(r); } if (((flags ^ notifiedflags) & 2) != 0) { int dur = FTEDroidEngine.getvibrateduration(); flags &= ~2; Vibrator vib = (Vibrator) act.getSystemService(Context.VIBRATOR_SERVICE); if (vib != null) { android.util.Log.i("FTEDroid", "Vibrate " + dur + "ms"); vib.vibrate(dur); } else android.util.Log.i("FTEDroid", "No vibrator device"); } if (((flags ^ notifiedflags) & 4) != 0) { final int fl = flags; Runnable r = new Runnable() { public void run() { if ((fl & 4) != 0) act.getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); else act.getWindow().setFlags(0, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } }; act.runOnUiThread(r); } if (((flags ^ notifiedflags) & 8) != 0) { final String errormsg = FTEDroidEngine.geterrormessage(); inited = false; if (errormsg == "") { finish(); System.exit(0); } //8 means sys error Runnable r = new Runnable() { public void run() { theview.setVisibility(theview.GONE); AlertDialog ad = new AlertDialog.Builder(act).create(); ad.setTitle("FTE ERROR"); ad.setMessage(errormsg); ad.setCancelable(false); ad.setButton("Ok", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { finish(); System.exit(0); } } ); ad.show(); } }; act.runOnUiThread(r); } if (((flags ^ notifiedflags) & 16) != 0) { //16 means orientation cvar change Runnable r = new Runnable() { public void run() { String ors = FTEDroidEngine.getpreferedorientation(); int ori = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR; if (ors.equalsIgnoreCase("unspecified")) ori = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; else if (ors.equalsIgnoreCase("landscape")) ori = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; else if (ors.equalsIgnoreCase("portrait")) ori = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; else if (ors.equalsIgnoreCase("user")) ori = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER; else if (ors.equalsIgnoreCase("behind")) ori = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; else if (ors.equalsIgnoreCase("sensor")) ori = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR; else if (ors.equalsIgnoreCase("nosensor")) ori = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; //the following are api level 9+ else if (ors.equalsIgnoreCase("sensorlandscape")) ori = 6;//android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; else if (ors.equalsIgnoreCase("sensorportrait")) ori = 7;//android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; else if (ors.equalsIgnoreCase("reverselandscape")) ori = 8;//android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; else if (ors.equalsIgnoreCase("reverseportrait")) ori = 9;//android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; else if (ors.equalsIgnoreCase("fullsensor")) ori = 10;//android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR; android.util.Log.i("FTEDroid", "Orientation changed to " + ori + " (" + ors + ")."); act.setRequestedOrientation(ori); } }; act.runOnUiThread(r); } if (((flags ^ notifiedflags) & 32) != 0) { if ((flags & 32) != 0) view.audioInit(FTEDroidEngine.audioinfo(0), FTEDroidEngine.audioinfo(1), FTEDroidEngine.audioinfo(2)); else view.audioStop(); } //clear anything which is an impulse notifiedflags = flags; } } } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { android.util.Log.i("FTEDroid", "Surface changed, now " + width + " by " + height + "."); FTEDroidEngine.init(width, height, glesversion, basedir, userdir); inited = true; } @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { FTEDroidEngine.newglcontext(); } } private class FTEView extends GLSurfaceView implements SensorEventListener { private final FTERenderer rndr; private audiothreadclass audiothread; private class audiothreadclass extends Thread { boolean timetodie; int schannels; int sspeed; int sbits; @Override public void run() { byte[] audbuf = new byte[2048]; int avail; AudioTrack at; int chans; try { if (schannels >= 8) //the OUT enumeration allows specific speaker control. but also api level 5+ chans = AudioFormat.CHANNEL_OUT_7POINT1; else if (schannels >= 6) chans = AudioFormat.CHANNEL_OUT_5POINT1; else if (schannels >= 4) chans = AudioFormat.CHANNEL_OUT_QUAD; else if (schannels >= 2) chans = AudioFormat.CHANNEL_CONFIGURATION_STEREO; else chans = AudioFormat.CHANNEL_CONFIGURATION_MONO; int enc = (sbits == 8)?AudioFormat.ENCODING_PCM_8BIT:AudioFormat.ENCODING_PCM_16BIT; int sz = 2*AudioTrack.getMinBufferSize(sspeed, chans, enc); // if (sz < sspeed * 0.05) // sz = sspeed * 0.05; at = new AudioTrack(AudioManager.STREAM_MUSIC, sspeed, chans, enc, sz, AudioTrack.MODE_STREAM); } catch(IllegalArgumentException e) { //fixme: tell the engine that its bad and that it should configure some different audio attributes, instead of simply muting. return; } at.setStereoVolume(1, 1); at.play(); while(!timetodie) { avail = FTEDroidEngine.paintaudio(audbuf, audbuf.length); at.write(audbuf, 0, avail); } at.stop(); } public void killoff() { timetodie = true; try { join(); } catch(InterruptedException e) { } timetodie = false; } }; private void audioInit(int sspeed, int schannels, int sbits) { if (audiothread == null) { audiothread = new audiothreadclass(); audiothread.schannels = schannels; audiothread.sspeed = sspeed; audiothread.sbits = sbits; audiothread.start(); } } public void audioStop() { if (audiothread != null) { audiothread.killoff(); audiothread = null; } } public void audioResume() { if (audiothread != null) { audiothread.killoff(); audiothread.start(); } } private FTELegacyInputEvent inputevent; class FTEMultiTouchInputEvent extends FTELegacyInputEvent { /*Requires API level 5+ (android 2.0+)*/ private void domove(MotionEvent event) { final int pointerCount = event.getPointerCount(); int i; for (i = 0; i < pointerCount; i++) FTEDroidEngine.motion(0, event.getPointerId(i), event.getX(i), event.getY(i), event.getSize(i)); } public boolean go(MotionEvent event) { int id; float x, y, size; final int act = event.getAction(); domove(event); switch(act & event.ACTION_MASK) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_POINTER_DOWN: id = ((act&event.ACTION_POINTER_ID_MASK) >> event.ACTION_POINTER_ID_SHIFT); x = event.getX(id); y = event.getY(id); size = event.getSize(id); id = event.getPointerId(id); FTEDroidEngine.motion(1, id, x, y, size); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_POINTER_UP: id = ((act&event.ACTION_POINTER_ID_MASK) >> event.ACTION_POINTER_ID_SHIFT); x = event.getX(id); y = event.getY(id); size = event.getSize(id); id = event.getPointerId(id); FTEDroidEngine.motion(2, id, x, y, size); break; case MotionEvent.ACTION_MOVE: break; default: return false; } return true; } } class FTELegacyInputEvent { public boolean go(MotionEvent event) { final int act = event.getAction(); final float x = event.getX(); final float y = event.getY(); final float size = event.getSize(); FTEDroidEngine.motion(0, 0, x, y, size); switch(act) { case MotionEvent.ACTION_DOWN: FTEDroidEngine.motion(1, 0, x, y, size); break; case MotionEvent.ACTION_UP: FTEDroidEngine.motion(2, 0, x, y, size); break; case MotionEvent.ACTION_MOVE: break; default: return false; } return true; } } public FTEView(FTEDroidActivity context) { super(context); if (android.os.Build.VERSION.SDK_INT >= 5) inputevent = new FTEMultiTouchInputEvent(); else inputevent = new FTELegacyInputEvent(); rndr = new FTERenderer(this, context); setRenderer(rndr); setFocusable(true); setFocusableInTouchMode(true); } private void sendKey(final boolean presseddown, final int qcode, final int unicode) { FTEDroidEngine.keypress(presseddown?1:0, qcode, unicode); } @Override public boolean onTouchEvent(MotionEvent event) { return inputevent.go(event); } /* @Override public boolean onTrackballEvent(MotionEvent event) { int act = event.getAction(); float x = event.getX(); float y = event.getY(); } */ private static final int K_ENTER = 13; private static final int K_ESCAPE = 27; private static final int K_DEL = 127; private static final int K_POWER = 130; private static final int K_UPARROW = 132; private static final int K_DOWNARROW = 133; private static final int K_LEFTARROW = 134; private static final int K_RIGHTARROW = 135; private static final int K_APP = 241; private static final int K_SEARCH = 242; private static final int K_VOLUP = 243; private static final int K_VOLDOWN = 244; private int mapKey(int acode, int unicode) { switch(acode) { case KeyEvent.KEYCODE_DPAD_UP: return K_UPARROW; case KeyEvent.KEYCODE_DPAD_DOWN: return K_DOWNARROW; case KeyEvent.KEYCODE_DPAD_LEFT: return K_LEFTARROW; case KeyEvent.KEYCODE_DPAD_RIGHT: return K_RIGHTARROW; case KeyEvent.KEYCODE_DPAD_CENTER: case KeyEvent.KEYCODE_ENTER: return K_ENTER; case KeyEvent.KEYCODE_BACK: return K_ESCAPE; //escape, cannot be rebound case KeyEvent.KEYCODE_MENU: return K_APP; //"app" case KeyEvent.KEYCODE_DEL: return K_DEL; //"del" case KeyEvent.KEYCODE_SEARCH: return K_SEARCH; //"search" case KeyEvent.KEYCODE_POWER: return K_POWER; //"power" case KeyEvent.KEYCODE_VOLUME_DOWN: return K_VOLDOWN; //"voldown" case KeyEvent.KEYCODE_VOLUME_UP: return K_VOLUP; //"volup" default: if (unicode < 128) return Character.toLowerCase(unicode); } return 0; } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { int uc = event.getUnicodeChar(); sendKey(true, mapKey(keyCode, uc), uc); return true; } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { int uc = event.getUnicodeChar(); sendKey(false, mapKey(keyCode, uc), uc); return true; } public void onAccuracyChanged(Sensor sensor, int accuracy) { } public void onSensorChanged(final SensorEvent event) { acc_x = event.values[0]; acc_y = event.values[1]; acc_z = event.values[2]; } } private boolean runningintheemulator() { android.util.Log.i("FTEDroid", "model: " + android.os.Build.MODEL + " product: " + android.os.Build.PRODUCT + " device: " + android.os.Build.DEVICE); return android.os.Build.MODEL.equals("sdk") && android.os.Build.PRODUCT.equals("sdk") && android.os.Build.DEVICE.equals("generic"); } @Override public void onCreate(Bundle savedInstanceState) { android.util.Log.i("FTEDroid", "onCreate"); try { String packagename = this.getComponentName().getPackageName(); //"com.fteqw", but not hardcoded. android.util.Log.i("FTEDroid", "Installed in package \"" + packagename + "\"."); android.content.pm.PackageInfo info = this.getPackageManager().getPackageInfo(packagename, 0); basedir = info.applicationInfo.sourceDir; } catch(android.content.pm.PackageManager.NameNotFoundException e) { /*oh well, can just use the homedir instead*/ } // try // { userdir = Environment.getExternalStorageDirectory().getPath() + "/fte"; // } // catch(foo) // { // } android.util.Log.i("FTEDroid", "Base dir is \"" + basedir + "\"."); android.util.Log.i("FTEDroid", "User dir is \"" + userdir + "\"."); //go full-screen getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); requestWindowFeature(Window.FEATURE_NO_TITLE); super.onCreate(savedInstanceState); android.util.Log.i("FTEDroid", "create view"); view = new FTEView(this); setContentView(view); if (runningintheemulator()) { android.util.Log.i("FTEDroid", "emulator detected - skipping sensors to avoid emulator bugs"); sensorman = null; } else { android.util.Log.i("FTEDroid", "init sensor manager"); sensorman = (SensorManager)getSystemService(SENSOR_SERVICE); } if (sensorman != null) { android.util.Log.i("FTEDroid", "init accelerometer"); sensoracc = sensorman.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); } android.util.Log.i("FTEDroid", "done"); } @Override protected void onResume() { super.onResume(); if (sensorman != null && sensoracc != null) sensorman.registerListener((SensorEventListener)view, sensoracc, SensorManager.SENSOR_DELAY_GAME); view.audioResume(); } @Override protected void onStop() { if (sensorman != null && sensoracc != null) sensorman.unregisterListener(view); view.audioStop(); super.onStop(); } @Override protected void onPause() { if (sensorman != null && sensoracc != null) sensorman.unregisterListener(view); view.audioStop(); super.onPause(); } }