Initial Android application.

This commit is contained in:
James Sanford 2012-08-06 17:04:38 -07:00
parent a9f6a3f1b0
commit 814d298c4c
11 changed files with 1048 additions and 0 deletions

20
AndroidManifest.xml Normal file
View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.froop.app.kegs"
android:installLocation="auto">
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="16" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application android:label="KEGS" android:hardwareAccelerated="true">
<activity android:name="KegsMain" android:launchMode="singleInstance" android:configChanges="orientation|keyboardHidden|screenSize" android:windowSoftInputMode="stateAlwaysVisible|adjustPan">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

23
README.md Normal file
View File

@ -0,0 +1,23 @@
Port of Kent Dickey's KEGS Apple IIgs Emulator to Android.
Work in progress! Currently optimized for tablet. Screen may be cropped on phones.
Config file and disk images in /mnt/sdcard/KEGS/
To attach disk images, use the F4 button to enter KEGS configuration.
The touch screen acts as a big trackpad for the mouse. To click and drag,
for example to pull down GS/OS menus, you need to Long Press first)
What's not ready yet:
- No audio.
- No joystick.
- Screen scaling to fit your device.
- Native UI configuration options.
Source code:
https://github.com/jamessanford/kegs/
To build from source:
android update project -p . -s
(cd jni && ndk-build)
ant debug install

53
res/layout/main.xml Normal file
View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/mainview"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.froop.app.kegs.KegsView
android:id="@+id/kegsview"
android:layout_width="704px"
android:layout_height="462px"/>
<LinearLayout orientation="horiontal" android:layout_width="wrap_content" android:layout_height="wrap_content">
<Button android:id="@+id/key_escape" android:text="Esc" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="left|bottom" />
<Button android:id="@+id/key_return" android:text="Return" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="left|bottom" />
<Button android:id="@+id/key_tab" android:text="Tab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="left|bottom" />
<Button android:id="@+id/key_f4" android:text="F4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="left|bottom" />
<ToggleButton android:id="@+id/key_control"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left|bottom"
android:textOn="Ctrl"
android:textOff="Ctrl" />
<ToggleButton android:id="@+id/key_open_apple"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left|bottom"
android:textOn="Cmd"
android:textOff="Cmd" />
<ToggleButton android:id="@+id/key_closed_apple"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left|bottom"
android:textOn="Opt"
android:textOff="Opt" />
<Button android:id="@+id/key_left" android:text="Left" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="left|bottom" />
<Button android:id="@+id/key_right" android:text="Right" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="left|bottom" />
<Button android:id="@+id/key_up" android:text="Up" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="left|bottom" />
<Button android:id="@+id/key_down" android:text="Down" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="left|bottom" />
</LinearLayout>
</FrameLayout>

View File

@ -0,0 +1,50 @@
package com.froop.app.kegs;
import android.os.Environment;
import java.io.File;
import java.io.FileOutputStream;
// Ick.
class Config {
public static final File mPath = Environment.getExternalStorageDirectory();
public static final String mConfigFile = "KEGS/config.kegs";
public static final String mROM03 = "KEGS/ROM.03";
public static final String mROM01 = "KEGS/ROM.01";
static public String whichRomFile() {
File rom = new File(mPath, mROM03);
if (rom != null && rom.exists()) {
return rom.getPath();
}
rom = new File(mPath, mROM01);
if (rom != null && rom.exists()) {
return rom.getPath();
}
return null;
}
public static void checkConfig(String rom_path) {
File config = new File(mPath, mConfigFile);
if (config == null || !config.exists()) {
createConfig(rom_path);
}
}
public static void createConfig(String rom_path) {
try {
final byte[] data_bytes = String.format(
"g_cfg_rom_path = %s\ng_limit_speed = 3\n", rom_path).getBytes();
File config = new File(mPath, mConfigFile);
config.createNewFile();
FileOutputStream out = new FileOutputStream(config);
out.write(data_bytes, 0, data_bytes.length);
out.close();
} catch (java.io.IOException e) {
// kegs will fail and exit with no config
return;
}
}
}

View File

@ -0,0 +1,110 @@
package com.froop.app.kegs;
import android.util.Log;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.HttpURLConnection;
import java.net.URL;
// Ick. Look elsewhere.
public class DownloadHelper {
private URL mURL;
private HttpURLConnection mConnection;
private InputStream mStream;
private boolean openInputStream() {
try {
mConnection = (HttpURLConnection)mURL.openConnection();
mConnection.connect();
if (mConnection.getResponseCode() != HttpURLConnection.HTTP_OK) {
mConnection.disconnect();
Log.e("kegs", "HTTP ERROR " + mConnection.getResponseCode());
return false;
}
} catch (IOException e) {
Log.e("kegs", "HTTP I/O EXCEPTION");
return false;
}
try {
mStream = new BufferedInputStream(mConnection.getInputStream());
} catch (IOException e) {
mConnection.disconnect();
Log.e("kegs", "INPUTSTREAM I/O EXCEPTION");
return false;
}
return true;
}
private boolean setURL(String url_string) {
try {
mURL = new URL(url_string);
} catch (MalformedURLException e) {
Log.e("kegs", "MALFORMED URL");
return false;
}
return true;
}
// Should be valid to call if open() works.
public void close() {
mConnection.disconnect();
mStream = null;
mConnection = null;
mURL = null;
}
public boolean open(String url_string) {
if (!setURL(url_string)) {
return false;
}
if (!openInputStream()) {
return false;
}
return true;
}
public boolean save(String url_string, String local_file) {
final File final_file = new File(local_file);
final File dir = new File(final_file.getParent());
dir.mkdirs();
final File output_file = new File(dir, "tmp");
output_file.delete(); // in case an earlier attempt failed
FileOutputStream out;
try {
output_file.createNewFile();
out = new FileOutputStream(output_file);
} catch (java.io.IOException e) {
Log.e("kegs", "unable to create " + local_file);
return false;
}
if (!open(url_string)) {
return false;
}
byte buf[] = new byte[4096];
try {
do {
int numread = mStream.read(buf);
if (numread <= 0) {
break;
} else {
out.write(buf, 0, numread);
}
} while (true);
out.close();
close();
} catch (java.io.IOException e) {
Log.e("kegs", "error while downloading " + url_string);
return false;
}
return output_file.renameTo(final_file);
}
}

View File

@ -0,0 +1,136 @@
package com.froop.app.kegs;
import android.util.Log;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import java.util.concurrent.ConcurrentLinkedQueue;
class KegsKeyboard {
public final static int KEY_ESCAPE = 0x35;
public final static int KEY_RETURN = 0x24;
public final static int KEY_BACKSPACE = 0x33; // 0x75 == delete
public final static int KEY_TAB = 0x30;
public final static int KEY_SPACE = 0x31;
public final static int KEY_F4 = 0x76;
public final static int KEY_LEFT = 0x3b;
public final static int KEY_RIGHT = 0x3c;
public final static int KEY_UP = 0x3e;
public final static int KEY_DOWN = 0x3d;
public final static int KEY_SHIFT = 0x38;
public final static int KEY_RESET = 0x7f;
// These keys can be set to stick down by calling keyDownSticky()
// They will release on a normal keypress.
// To indicate release on the GUI, get notified via setOnStickyReset()
public final static int KEY_CONTROL = 0x36;
public final static int KEY_OPEN_APPLE = 0x37;
public final static int KEY_CLOSED_APPLE = 0x3a;
private int mSticky = 0; // Bitmask for sticky keys.
private final static int STICKY_CONTROL = 1;
private final static int STICKY_OPEN_APPLE = 2;
private final static int STICKY_CLOSED_APPLE = 4;
interface StickyReset {
void onStickyReset();
}
private StickyReset mNotify;
// NOTE: Using event.getDeviceId() at runtime isn't working.
protected KeyCharacterMap keymap = KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD);
private ConcurrentLinkedQueue mEventQueue;
public KegsKeyboard(ConcurrentLinkedQueue q) {
mEventQueue = q;
}
public void setOnStickyReset(StickyReset notify) {
mNotify = notify;
}
// Handle 'space' through to ~.
public boolean handleAsciiKey(int key_id) {
if (key_id < 0x20 || key_id > 0x7e) {
return false;
}
key_id -= 0x20;
final KeyTable.A2Key a2key = KeyTable.ascii.get(key_id);
if (a2key.use_shift) {
mEventQueue.add(new KegsView.KeyKegsEvent(KEY_SHIFT, false));
keyDownUp(a2key.keycode);
mEventQueue.add(new KegsView.KeyKegsEvent(KEY_SHIFT, true));
} else {
keyDownUp(a2key.keycode);
}
return true;
}
public boolean keyEvent(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
int keyCode = event.getKeyCode();
if (keyCode == KeyEvent.KEYCODE_ENTER) {
keyDownUp(KEY_RETURN);
return true;
} else if (keyCode == KeyEvent.KEYCODE_SPACE) {
keyDownUp(KEY_SPACE);
return true;
} else if (keyCode == KeyEvent.KEYCODE_TAB) {
keyDownUp(KEY_TAB);
return true;
} else if (keyCode == KeyEvent.KEYCODE_DEL) {
keyDownUp(KEY_BACKSPACE);
return true;
} else if (keymap == null) {
return false;
} else if (keymap.isPrintingKey(keyCode)) {
int key_id = keymap.get(keyCode, event.getMetaState());
return handleAsciiKey(key_id);
}
}
return false;
}
private void resetStickyKeys() {
if (mSticky != 0) {
if ((mSticky & STICKY_CONTROL) != 0) {
mEventQueue.add(new KegsView.KeyKegsEvent(KEY_CONTROL, true));
}
if ((mSticky & STICKY_OPEN_APPLE) != 0) {
mEventQueue.add(new KegsView.KeyKegsEvent(KEY_OPEN_APPLE, true));
}
if ((mSticky & STICKY_CLOSED_APPLE) != 0) {
mEventQueue.add(new KegsView.KeyKegsEvent(KEY_CLOSED_APPLE, true));
}
mSticky = 0;
if (mNotify != null) {
mNotify.onStickyReset();
}
}
}
public void keyDownSticky(int key_id, boolean key_up) {
int mask = 0;
if (key_id == KEY_CONTROL) {
mEventQueue.add(new KegsView.KeyKegsEvent(KEY_CONTROL, key_up));
mask = STICKY_CONTROL;
} else if (key_id == KEY_OPEN_APPLE) {
mEventQueue.add(new KegsView.KeyKegsEvent(KEY_OPEN_APPLE, key_up));
mask = STICKY_OPEN_APPLE;
} else if (key_id == KEY_CLOSED_APPLE) {
mEventQueue.add(new KegsView.KeyKegsEvent(KEY_CLOSED_APPLE, key_up));
mask = STICKY_CLOSED_APPLE;
}
if (key_up) {
mSticky &= ~mask;
} else {
mSticky |= mask;
}
}
public void keyDownUp(int key_id) {
mEventQueue.add(new KegsView.KeyKegsEvent(key_id, false)); // key down
mEventQueue.add(new KegsView.KeyKegsEvent(key_id, true)); // key up
resetStickyKeys();
}
}

View File

@ -0,0 +1,220 @@
package com.froop.app.kegs;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.AsyncTask;
import android.util.Log;
import android.view.GestureDetector;
import android.view.View;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View.OnTouchListener;
import android.widget.Button;
import android.widget.ToggleButton;
public class KegsMain extends Activity implements KegsKeyboard.StickyReset {
private static final String FRAGMENT_ROM = "rom";
private static final String FRAGMENT_DOWNLOAD = "download";
private static final String FRAGMENT_ERROR = "error";
private KegsView mKegsView;
private KegsTouch mKegsTouch;
private KegsKeyboard mKegsKeyboard;
private View.OnClickListener mButtonClick = new View.OnClickListener() {
public void onClick(View v) {
// Log.e("kegs", "button clicked");
int click_id = v.getId();
int key_id = -1;
boolean sticky = false;
if (click_id == R.id.key_escape) {
key_id = KegsKeyboard.KEY_ESCAPE;
} else if (click_id == R.id.key_return) {
key_id = KegsKeyboard.KEY_RETURN;
} else if (click_id == R.id.key_f4) {
key_id = KegsKeyboard.KEY_F4;
} else if (click_id == R.id.key_tab) {
key_id = KegsKeyboard.KEY_TAB;
} else if (click_id == R.id.key_control) {
key_id = KegsKeyboard.KEY_CONTROL;
sticky = true;
} else if (click_id == R.id.key_open_apple) {
key_id = KegsKeyboard.KEY_OPEN_APPLE;
sticky = true;
} else if (click_id == R.id.key_closed_apple) {
key_id = KegsKeyboard.KEY_CLOSED_APPLE;
sticky = true;
} else if (click_id == R.id.key_left) {
key_id = KegsKeyboard.KEY_LEFT;
} else if (click_id == R.id.key_right) {
key_id = KegsKeyboard.KEY_RIGHT;
} else if (click_id == R.id.key_up) {
key_id = KegsKeyboard.KEY_UP;
} else if (click_id == R.id.key_down) {
key_id = KegsKeyboard.KEY_DOWN;
} else {
Log.e("kegs", "UNKNOWN BUTTON " + click_id + " CLICKED");
}
if (key_id != -1) {
if (sticky) {
mKegsKeyboard.keyDownSticky(key_id, !((ToggleButton)v).isChecked());
} else {
mKegsKeyboard.keyDownUp(key_id);
}
}
}
};
public void onStickyReset() {
((ToggleButton)findViewById(R.id.key_control)).setChecked(false);
((ToggleButton)findViewById(R.id.key_open_apple)).setChecked(false);
((ToggleButton)findViewById(R.id.key_closed_apple)).setChecked(false);
}
protected void getRomFile(String romfile) {
final DialogFragment download = new DownloadDialogFragment();
download.show(getFragmentManager(), FRAGMENT_DOWNLOAD);
new DownloadRom().execute(romfile);
}
class DownloadRom extends AsyncTask<String, Void, Boolean> {
private String mRomfile;
protected Boolean doInBackground(String ... raw_romfile) {
mRomfile = raw_romfile[0];
return new DownloadHelper().save(
"http://jsan.co/" + mRomfile, Config.mPath.getPath() + "/" + mRomfile);
}
protected void onPostExecute(Boolean success) {
final DialogFragment frag = (DialogFragment)getFragmentManager().findFragmentByTag(FRAGMENT_DOWNLOAD);
if (frag != null) {
frag.dismiss();
}
if (!success) {
if (!isCancelled()) {
final DialogFragment dialog = new ErrorDialogFragment();
dialog.show(getFragmentManager(), FRAGMENT_ERROR);
}
} else {
Config.checkConfig(mRomfile);
mKegsView.setReady(true);
}
}
}
class DownloadDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
ProgressDialog dialog = new ProgressDialog(getActivity());
// TODO: should probably use an XML layout for this.
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setMessage("Checking for ROM...");
dialog.setProgressNumberFormat(null);
dialog.setProgressPercentFormat(null);
dialog.setIndeterminate(true);
dialog.setCancelable(false);
dialog.setCanceledOnTouchOutside(false); // lame
return dialog;
}
}
class ErrorDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage("Unable to obtain ROM. Find ROM.01 or ROM.03 and put it in the /KEGS/ directory on your SD Card.");
// TODO do getActivity().finish() on button clicks
return builder.create();
}
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
// TODO: verify that this is all OK, and even Return works immediately.
return executeKeyEvent(event) || super.dispatchKeyEvent(event);
// return super.dispatchKeyEvent(event) || executeKeyEvent(event);
}
public boolean executeKeyEvent(KeyEvent event) {
return mKegsKeyboard.keyEvent(event);
}
// @Override
// public boolean dispatchGenericMotionEvent(MotionEvent event) {
// // Joystick! if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 && event.getAction() == MotionEvent.ACTION_MOVE) {}
// // See also GameControllerInput.java from ApiDemos
// return super.dispatchGenericMotionEvent(event);
// }
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mKegsView = (KegsView)findViewById(R.id.kegsview);
mKegsTouch = new KegsTouch(mKegsView.getEventQueue());
final GestureDetector inputDetect = new GestureDetector(this, mKegsTouch);
final View mainView = findViewById(R.id.mainview);
mainView.setClickable(true);
mainView.setLongClickable(true);
mainView.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
return inputDetect.onTouchEvent(event);
}
});
mKegsKeyboard = new KegsKeyboard(mKegsView.getEventQueue());
mKegsKeyboard.setOnStickyReset(this);
findViewById(R.id.key_escape).setOnClickListener(mButtonClick);
findViewById(R.id.key_return).setOnClickListener(mButtonClick);
findViewById(R.id.key_f4).setOnClickListener(mButtonClick);
findViewById(R.id.key_control).setOnClickListener(mButtonClick);
findViewById(R.id.key_open_apple).setOnClickListener(mButtonClick);
findViewById(R.id.key_closed_apple).setOnClickListener(mButtonClick);
findViewById(R.id.key_left).setOnClickListener(mButtonClick);
findViewById(R.id.key_right).setOnClickListener(mButtonClick);
findViewById(R.id.key_up).setOnClickListener(mButtonClick);
findViewById(R.id.key_down).setOnClickListener(mButtonClick);
final String romfile = Config.whichRomFile();
if (romfile == null) {
final DialogFragment chooseRom = new RomDialogFragment();
chooseRom.show(getFragmentManager(), FRAGMENT_ROM);
} else {
Config.checkConfig(romfile);
mKegsView.setReady(true);
}
}
@Override
protected void onPause() {
super.onPause();
mKegsView.getThread().onPause();
}
@Override
protected void onResume() {
super.onResume();
mKegsView.getThread().onResume();
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.w("kegs", "onDestroy called, halting");
// Force process to exit. We cannot handle another onCreate
// once a KegsView has been active. (JNI kegsmain has already run)
java.lang.Runtime.getRuntime().halt(0);
}
static {
System.loadLibrary("kegs");
}
}

View File

@ -0,0 +1,67 @@
package com.froop.app.kegs;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import java.util.concurrent.ConcurrentLinkedQueue;
class KegsTouch extends GestureDetector.SimpleOnGestureListener {
private ConcurrentLinkedQueue mEventQueue;
private int mButton1 = 0;
public KegsTouch(ConcurrentLinkedQueue q) {
mEventQueue = q;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2,
float velocityX, float velocityY) {
// move mouse cursor
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
int changeX = (int)distanceX * -1;
int changeY = (int)distanceY * -1;
mEventQueue.add(
new KegsView.MouseKegsEvent(changeX, changeY, mButton1, 1));
// if (mButton1 == 1 && e2.getAction() == MotionEvent.ACTION_UP) {
// mButton1 = 0;
// mEventQueue.add(new KegsView.MouseKegsEvent(0, 0, mButton1, 1));
// }
return true;
}
@Override
public void onLongPress(MotionEvent e) {
// press mouse button down
// Log.e("kegs", "onlongpress");
mButton1 = 1;
mEventQueue.add(new KegsView.MouseKegsEvent(0, 0, mButton1, 1));
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
// press mouse button down, then up
mButton1 = 1;
mEventQueue.add(new KegsView.MouseKegsEvent(0, 0, mButton1, 1));
mButton1 = 0;
mEventQueue.add(new KegsView.MouseKegsEvent(0, 0, mButton1, 1));
return true;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
mButton1 = 1;
mEventQueue.add(new KegsView.MouseKegsEvent(0, 0, mButton1, 1));
mButton1 = 0;
mEventQueue.add(new KegsView.MouseKegsEvent(0, 0, mButton1, 1));
mButton1 = 1;
mEventQueue.add(new KegsView.MouseKegsEvent(0, 0, mButton1, 1));
mButton1 = 0;
mEventQueue.add(new KegsView.MouseKegsEvent(0, 0, mButton1, 1));
return true;
}
}

View File

@ -0,0 +1,218 @@
package com.froop.app.kegs;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.ConcurrentLinkedQueue;
class KegsView extends SurfaceView implements SurfaceHolder.Callback {
private static final int mA2Width = 640 + 32 + 32; // kegs defcomm.h
private static final int mA2Height = 400 + 32 + 30; // kegs defcomm.h
// Look also at mPauseLock.
private boolean mHaveSurface = false;
private boolean mPaused = false;
private boolean mReady = false;
static class KegsEvent {}
static class KeyKegsEvent extends KegsEvent {
public KeyKegsEvent(int key_id, boolean up) {
this.key_id = key_id;
this.up = up;
}
public int key_id;
public boolean up;
}
static class MouseKegsEvent extends KegsEvent {
public MouseKegsEvent(int x, int y, int buttons, int buttons_valid) {
this.x = x;
this.y = y;
this.buttons = buttons;
this.buttons_valid = buttons_valid;
}
public int x;
public int y;
public int buttons;
public int buttons_valid;
}
protected ConcurrentLinkedQueue<KegsEvent> mEventQueue = new ConcurrentLinkedQueue<KegsEvent>();
class KegsThread extends Thread {
private Bitmap mBitmap;
private Canvas mCanvas;
private SurfaceHolder mSurfaceHolder;
private Context mContext;
private final ReentrantLock mSurfaceLock = new ReentrantLock();
private final ReentrantLock mPauseLock = new ReentrantLock();
private final Rect mRect = new Rect(0, 0, mA2Width, mA2Height);
// private final Rect mDestRect = new Rect(0, 0, 800, 600);
public KegsThread(SurfaceHolder surfaceHolder, Context context) {
mSurfaceHolder = surfaceHolder;
mContext = context;
mBitmap = Bitmap.createBitmap(mA2Width, mA2Height,
Bitmap.Config.ARGB_8888);
}
// Called by surfaceCreated also!
public void updateScreen() {
mSurfaceLock.lock();
mCanvas = mSurfaceHolder.lockCanvas(); // Use mRect ?
try {
if(mCanvas != null) {
mCanvas.drawARGB(255, 0, 0, 0);
mCanvas.drawBitmap(mBitmap, 0, 0, null);
// scaling tests:
// mCanvas.drawBitmap(mBitmap, mRect, mDestRect, null);
// Doesn't work well, but consider eliminating the border instead, for phones.
mSurfaceHolder.unlockCanvasAndPost(mCanvas);
mCanvas = null;
}
} finally {
mSurfaceLock.unlock();
}
}
void checkForPause() {
if (mPaused) {
mPauseLock.lock();
// deadlock here until onResume. Maybe not efficient.
mPauseLock.unlock();
}
}
public native void mainLoop(Bitmap b, ConcurrentLinkedQueue q);
@Override
public void run() {
mainLoop(mBitmap, mEventQueue);
// For TESTING:
// while (true) {
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {}
// }
}
public void onPause() {
if (!mReady) {
return; // bail out, we haven't started doing anything yet
}
if (!mPaused) {
mPaused = true;
mPauseLock.lock();
}
}
public void onResume() {
if (!mReady) {
return; // bail out, we haven't started doing anything yet
}
if (mHaveSurface && mPaused) {
mPaused = false;
mPauseLock.unlock();
}
// otherwise, wait for the surface...
}
// Has the thread itself actually paused waiting to acquire the lock?
public boolean nowPaused() {
return mPauseLock.hasQueuedThreads();
}
public void setSurfaceSize(int width, int height) {
// consider.. mSurfaceLock.lock(); update sizes and bitmap; unlock
}
}
private Context mContext;
private KegsThread thread;
public KegsView(Context context, AttributeSet attrs) {
super(context, attrs);
SurfaceHolder holder = getHolder();
holder.addCallback(this);
thread = new KegsThread(holder, context);
setFocusable(true);
}
public KegsThread getThread() {
return thread;
}
public ConcurrentLinkedQueue getEventQueue() {
return mEventQueue;
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
// TODO: check to see if this is necessary, for example during alarms or phone calls.
// if (!hasWindowFocus) {
// thread.onPause();
// } else {
// thread.onResume();
// }
}
public void surfaceChanged(SurfaceHolder holder,
int format, int width, int height) {
thread.setSurfaceSize(width, height);
}
public native String stringFromJNI();
public void setReady(boolean ready) {
if (ready && !mReady && mHaveSurface) {
thread.start();
}
mReady = ready;
}
public void surfaceCreated(SurfaceHolder holder) {
mHaveSurface = true;
if (!mReady) {
return; // bail out, start the thread later
}
thread.updateScreen();
if (mPaused) {
thread.onResume();
} else {
thread.start();
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
Log.w("kegs", "surfaceDestroyed");
mHaveSurface = false;
if (!mReady) {
return; // bail out, we never actually started
}
if (!mPaused) {
thread.onPause();
}
while (!thread.nowPaused()) {
// We are waiting for the thread to actually pause itself,
// so that it won't be updating the canvas.
try {
Thread.sleep(18); // 18ms == just over 1/60th of a second
} catch (InterruptedException e) {}
}
}
}

View File

@ -0,0 +1,115 @@
package com.froop.app.kegs;
import java.util.ArrayList;
class KeyTable {
static class A2Key {
public A2Key(int keycode, boolean use_shift) {
this.keycode = keycode;
this.use_shift = use_shift;
}
public int keycode;
public boolean use_shift;
}
// Mapping of ASCII 0x20-0x7e inclusive to the ADB keycode.
// 0x20 is element 0.
static ArrayList<A2Key> ascii = new ArrayList<A2Key>(96);
static {
ascii.add(new A2Key(0x31, false)); // <space>
ascii.add(new A2Key(0x12, true)); // !
ascii.add(new A2Key(0x27, true)); // "
ascii.add(new A2Key(0x14, true)); // #
ascii.add(new A2Key(0x15, true)); // $
ascii.add(new A2Key(0x17, true)); // %
ascii.add(new A2Key(0x1a, true)); // &
ascii.add(new A2Key(0x27, false)); // '
ascii.add(new A2Key(0x19, true)); // (
ascii.add(new A2Key(0x1d, true)); // )
ascii.add(new A2Key(0x1c, true)); // *
ascii.add(new A2Key(0x18, true)); // +
ascii.add(new A2Key(0x2b, false)); // ,
ascii.add(new A2Key(0x1b, false)); // -
ascii.add(new A2Key(0x2f, false)); // .
ascii.add(new A2Key(0x2c, false)); // /
ascii.add(new A2Key(0x1d, false)); // 0
ascii.add(new A2Key(0x12, false)); // 1
ascii.add(new A2Key(0x13, false)); // 2
ascii.add(new A2Key(0x14, false)); // 3
ascii.add(new A2Key(0x15, false)); // 4
ascii.add(new A2Key(0x17, false)); // 5
ascii.add(new A2Key(0x16, false)); // 6
ascii.add(new A2Key(0x1a, false)); // 7
ascii.add(new A2Key(0x1c, false)); // 8
ascii.add(new A2Key(0x19, false)); // 9
ascii.add(new A2Key(0x29, true)); // :
ascii.add(new A2Key(0x29, false)); // ;
ascii.add(new A2Key(0x2b, true)); // <
ascii.add(new A2Key(0x18, false)); // =
ascii.add(new A2Key(0x2f, true)); // >
ascii.add(new A2Key(0x2c, true)); // ?
ascii.add(new A2Key(0x13, true)); // @
ascii.add(new A2Key(0x00, true)); // A
ascii.add(new A2Key(0x0b, true)); // B
ascii.add(new A2Key(0x08, true)); // C
ascii.add(new A2Key(0x02, true)); // D
ascii.add(new A2Key(0x0e, true)); // E
ascii.add(new A2Key(0x03, true)); // F
ascii.add(new A2Key(0x05, true)); // G
ascii.add(new A2Key(0x04, true)); // H
ascii.add(new A2Key(0x22, true)); // I
ascii.add(new A2Key(0x26, true)); // J
ascii.add(new A2Key(0x28, true)); // K
ascii.add(new A2Key(0x25, true)); // L
ascii.add(new A2Key(0x2e, true)); // M
ascii.add(new A2Key(0x2d, true)); // N
ascii.add(new A2Key(0x1f, true)); // O
ascii.add(new A2Key(0x23, true)); // P
ascii.add(new A2Key(0x0c, true)); // Q
ascii.add(new A2Key(0x0f, true)); // R
ascii.add(new A2Key(0x01, true)); // S
ascii.add(new A2Key(0x11, true)); // T
ascii.add(new A2Key(0x20, true)); // U
ascii.add(new A2Key(0x09, true)); // V
ascii.add(new A2Key(0x0d, true)); // W
ascii.add(new A2Key(0x07, true)); // X
ascii.add(new A2Key(0x10, true)); // Y
ascii.add(new A2Key(0x06, true)); // Z
ascii.add(new A2Key(0x21, false)); // [
ascii.add(new A2Key(0x2a, false)); // \
ascii.add(new A2Key(0x1e, false)); // ]
ascii.add(new A2Key(0x16, true)); // ^
ascii.add(new A2Key(0x1b, true)); // _
ascii.add(new A2Key(0x32, false)); // `
ascii.add(new A2Key(0x00, false)); // a
ascii.add(new A2Key(0x0b, false)); // b
ascii.add(new A2Key(0x08, false)); // c
ascii.add(new A2Key(0x02, false)); // d
ascii.add(new A2Key(0x0e, false)); // e
ascii.add(new A2Key(0x03, false)); // f
ascii.add(new A2Key(0x05, false)); // g
ascii.add(new A2Key(0x04, false)); // h
ascii.add(new A2Key(0x22, false)); // i
ascii.add(new A2Key(0x26, false)); // j
ascii.add(new A2Key(0x28, false)); // k
ascii.add(new A2Key(0x25, false)); // l
ascii.add(new A2Key(0x2e, false)); // m
ascii.add(new A2Key(0x2d, false)); // n
ascii.add(new A2Key(0x1f, false)); // o
ascii.add(new A2Key(0x23, false)); // p
ascii.add(new A2Key(0x0c, false)); // q
ascii.add(new A2Key(0x0f, false)); // r
ascii.add(new A2Key(0x01, false)); // s
ascii.add(new A2Key(0x11, false)); // t
ascii.add(new A2Key(0x20, false)); // u
ascii.add(new A2Key(0x09, false)); // v
ascii.add(new A2Key(0x0d, false)); // w
ascii.add(new A2Key(0x07, false)); // x
ascii.add(new A2Key(0x10, false)); // y
ascii.add(new A2Key(0x06, false)); // z
ascii.add(new A2Key(0x21, true)); // {
ascii.add(new A2Key(0x2a, true)); // |
ascii.add(new A2Key(0x1e, true)); // }
ascii.add(new A2Key(0x32, true)); // ~
}
}

View File

@ -0,0 +1,36 @@
package com.froop.app.kegs;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
public class RomDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final CharSequence[] items = {"I don't own a GS",
"I have a ROM 01",
"I have a ROM 03"};
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("Checking for ROM");
builder.setSingleChoiceItems(items, -1,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
dismiss();
if (item == 0) {
((KegsMain)getActivity()).finish();
} else if (item == 1) {
((KegsMain)getActivity()).getRomFile(Config.mROM01);
} else if (item == 2) {
((KegsMain)getActivity()).getRomFile(Config.mROM03);
}
}
});
final AlertDialog dialog = builder.create();
dialog.setCanceledOnTouchOutside(false); // lame
return dialog;
}
}