kegs-android/src/com/froop/app/kegs/KegsMain.java
2013-07-27 21:25:52 -07:00

632 lines
21 KiB
Java

package com.froop.app.kegs;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
import android.os.AsyncTask;
import android.util.Log;
import android.view.inputmethod.InputMethodManager;
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;
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.actionbarsherlock.app.SherlockDialogFragment;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuItem;
import java.util.ArrayDeque;
public class KegsMain extends SherlockFragmentActivity implements KegsKeyboard.StickyReset, AssetImages.AssetsReady, DiskLoader.ImageReady {
private static final String FRAGMENT_ROM = "rom";
private static final String FRAGMENT_DOWNLOAD = "download";
private static final String FRAGMENT_ERROR = "error";
private static final String FRAGMENT_SPEED = "speed";
private static final String FRAGMENT_DISKIMAGE = "diskimage";
private static final String FRAGMENT_SWAPDISK = "swapdisk";
private static final String FRAGMENT_ZIPDISK = "zipdisk";
private static final String FRAGMENT_LOADING = "loading";
private ConfigFile mConfigFile;
private KegsThread mKegsThread;
protected KegsViewGL mKegsView;
private KegsKeyboard mKegsKeyboard;
private TouchMouse mTouchMouse;
private TouchJoystick mJoystick;
private boolean mModeMouse = true;
private boolean mOverrideActionBar = false;
private long mScreenSizeTime = 0;
private boolean mPaused = false;
private final ArrayDeque<Runnable> mResumeQueue = new ArrayDeque<Runnable>();
private final Runnable mErrorFinish = new Runnable() { public void run() { finish(); } };
private DiskLoader mDiskLoader = null;
private final DiskImage.DriveNumber mDriveNumber = new DiskImage.DriveNumber();
private void withUIActive(final Runnable runnable) {
if(!mPaused) {
runnable.run();
} else {
mResumeQueue.add(runnable);
}
}
public void onAssetsReady(boolean result) {
// TODO: deal with error conditions from assets as a warning.
}
private View.OnClickListener mButtonClick = new View.OnClickListener() {
public void onClick(View v) {
final 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_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);
}
}
}
};
private boolean areControlsVisible() {
return findViewById(R.id.b1).getVisibility() == View.VISIBLE;
}
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 loadDiskImage(final DiskImage image) {
if (image.action == DiskImage.BOOT) {
getThread().doPowerOff();
}
loadDiskImageWhenReady(image);
}
private void loadDiskImageWhenReady(final DiskImage image) {
boolean nativeReady;
if (image.action == DiskImage.BOOT) {
nativeReady = getThread().nowWaitingForPowerOn();
} else {
nativeReady = true;
}
if (!nativeReady) {
mKegsView.postDelayed(new Runnable() {
public void run() {
loadDiskImageWhenReady(image);
}
}, 100);
return;
}
withUIActive(new Runnable() {
public void run() {
if (image.action == DiskImage.BOOT) {
mConfigFile.setConfig(image);
getThread().allowPowerOn();
} else if (image.action == DiskImage.SWAP) {
if (image.isHardDrive()) {
image.chooseDriveNumber(mDriveNumber);
}
getThread().getEventQueue().add(image.getDiskImageEvent());
}
}
});
}
public void onImageCancelled(final boolean result, final DiskImage image) {
if (getThread().nowWaitingForPowerOn()) {
// Emulator never powered on and the user aborted.
finish();
}
}
public void onImageReady(final boolean result, final DiskImage image) {
mDiskLoader = null;
withUIActive(new Runnable() {
public void run() {
dismissFragment(FRAGMENT_LOADING);
if (!result) {
final Runnable cancel;
if (getThread().nowWaitingForPowerOn()) {
// Emulator never powered on and we failed, exit when user clicks OK.
cancel = mErrorFinish;
} else {
cancel = null;
}
new ErrorDialogFragment(R.string.image_error, cancel).show(
getSupportFragmentManager(), FRAGMENT_ERROR);
} else if (image.action != DiskImage.ASK) {
loadDiskImage(image);
} else {
new SwapDiskFragment(image).show(getSupportFragmentManager(),
FRAGMENT_SWAPDISK);
}
}
});
}
public void prepareDiskImage(DiskImage image) {
dismissFragment(FRAGMENT_ROM); // Note: should probably bail if visible.
dismissFragment(FRAGMENT_SPEED);
dismissFragment(FRAGMENT_DISKIMAGE);
dismissFragment(FRAGMENT_SWAPDISK);
dismissFragment(FRAGMENT_ZIPDISK);
if (image.filename.endsWith(".zip") || image.filename.endsWith(".ZIP")) {
final ZipDiskFragment zip = new ZipDiskFragment(image);
if (zip.needsDialog()) {
zip.show(getSupportFragmentManager(), FRAGMENT_ZIPDISK);
return;
} else {
image = zip.getFirstImage();
}
}
runDiskLoader(image);
}
// So that other fragments can transition into the DiskLoader.
public void runDiskLoader(final DiskImage image) {
final Runnable cancel = new Runnable() {
public void run() {
if (mDiskLoader != null) {
mDiskLoader.cancel(true);
mDiskLoader = null;
}
}
};
final DiskLoader.ImageReady notify = this;
withUIActive(new Runnable() {
public void run() {
// In case there was already another DiskLoader.
cancel.run();
dismissFragment(FRAGMENT_LOADING);
mDiskLoader = new DiskLoader(notify, mConfigFile, image);
if (mDiskLoader.willBeSlow()) {
new SpecialProgressDialog(cancel).show(getSupportFragmentManager(),
FRAGMENT_LOADING);
}
if (android.os.Build.VERSION.SDK_INT >= 11) {
mDiskLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
mDiskLoader.execute();
}
}
});
}
private DiskImage getBootDiskImage(Intent intent) {
if (intent != null && intent.getAction() == Intent.ACTION_VIEW) {
final Uri uri = intent.getData();
if (uri != null && uri.getScheme().equals("file")) {
final String path = uri.getPath();
if (path != null) {
return DiskImage.fromPath(path);
}
}
}
return null;
}
// The first boot upon application launch.
private void boot() {
final DiskImage image = getBootDiskImage(getIntent());
if (image != null) {
prepareDiskImage(image);
} else {
mConfigFile.defaultConfig();
getThread().allowPowerOn();
mKegsView.postDelayed(new Runnable() {
public void run() {
withUIActive(new Runnable() {
public void run() {
if (findFragment(FRAGMENT_DISKIMAGE) == null) {
new DiskImageFragment(mConfigFile, DiskImage.BOOT).show(getSupportFragmentManager(), FRAGMENT_DISKIMAGE);
}
}
});
}
}, 1400);
}
}
protected void getRomFile(String romfile) {
final SherlockDialogFragment download = new DownloadDialogFragment();
download.show(getSupportFragmentManager(), FRAGMENT_DOWNLOAD);
if (android.os.Build.VERSION.SDK_INT >= 11) {
new DownloadRom().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, romfile);
} else {
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/KEGS/" + mRomfile,
mConfigFile.getConfigPath() + "/" + mRomfile);
}
protected void onPostExecute(final Boolean success) {
withUIActive(new Runnable() {
public void run() {
final SherlockDialogFragment frag = (SherlockDialogFragment)getSupportFragmentManager().findFragmentByTag(FRAGMENT_DOWNLOAD);
if (frag != null) {
frag.dismiss();
}
if (!success) {
if (!isCancelled()) {
new ErrorDialogFragment(R.string.rom_error, mErrorFinish).show(
getSupportFragmentManager(), FRAGMENT_ERROR);
}
} else {
boot();
}
}
});
}
}
class DownloadDialogFragment extends SherlockDialogFragment {
@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(getResources().getText(R.string.rom_check));
if (android.os.Build.VERSION.SDK_INT <= 10 || android.os.Build.VERSION.SDK_INT >= 14) {
// This seems to crash on some vendors Honeycomb.
dialog.setProgressNumberFormat(null);
}
if (android.os.Build.VERSION.SDK_INT >= 14) {
dialog.setProgressPercentFormat(null);
}
dialog.setIndeterminate(true);
dialog.setCancelable(false);
dialog.setCanceledOnTouchOutside(false);
return dialog;
}
@Override
public void onCancel(DialogInterface dialog) {
super.onCancel(dialog);
finish();
}
}
public KegsThread getThread() {
return mKegsThread;
}
// Designate the upper right corner as a special zone that
// toggles the action bar.
private Rect getSpecialActionBarRect(BitmapSize bitmapSize) {
final int left = (int)(bitmapSize.getScreenWidth() * 0.80);
final int top = 0;
final int right = bitmapSize.getScreenWidth();
final int bottom = (int)(bitmapSize.getScreenHeight() * 0.10);
// Only have a special zone if the action bar is not supposed to be visible.
if (!bitmapSize.showActionBar() && left != 0 && bottom != 0) {
return new Rect(left, top, right, bottom);
} else {
return null;
}
}
private void updateActionBar(boolean showActionBar) {
showActionBar = mOverrideActionBar || showActionBar;
final ActionBar actionBar = getSupportActionBar();
if (actionBar != null && showActionBar) {
actionBar.show();
} else if (actionBar != null && !showActionBar) {
actionBar.hide();
}
}
private void updateScreenSize(int width, int height, long sent) {
if (mScreenSizeTime != sent) {
// There's a newer event coming soon, wait for it.
//
// This is a bug workaround. We need to wait for the size to settle
// before acting on it. If we update *immediately* it seems to
// confuse Android. If we update multiple times, Android creates
// completely screwed up animations during the rotation.
//
// So, we mark mScreenSize with a message time and tell it to update
// 250ms later. We only want to act on the last update that is sent,
// but we can't easily cancel the fact that the message is being sent,
// so bail out here if the time doesn't match the last message.
return;
}
final BitmapSize bitmapSize = new BitmapSize(width, height, getResources().getDisplayMetrics());
updateActionBar(bitmapSize.showActionBar());
mKegsView.updateScreenSize(bitmapSize);
// Update scale of mouse movements.
mTouchMouse.updateScale(bitmapSize.getScaleX(), bitmapSize.getScaleY());
// Update special click zone that toggles the ActionBar.
final TouchSpecialZone zone = new TouchSpecialZone(getSpecialActionBarRect(bitmapSize)) {
public void activate() {
mOverrideActionBar = !mOverrideActionBar;
updateActionBar(mOverrideActionBar);
}
};
mTouchMouse.setSpecialZone(zone);
mJoystick.setSpecialZone(zone);
// Force another redraw of the bitmap into the canvas. Bug workaround.
getThread().updateScreen();
}
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Fire off a guess at a new size during this request,
// it makes the animation transition look better.
final BitmapSize quickSize = BitmapSize.quick(this);
updateActionBar(quickSize.showActionBar());
mKegsView.updateScreenSize(quickSize);
getThread().updateScreen();
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
return mKegsKeyboard.keyEvent(event) || super.dispatchKeyEvent(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
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
// BUG: no overflow menu on devices with menu button
// BUG: when action bar is hidden, menu bar only shows overflow items
getSupportMenuInflater().inflate(R.menu.actions, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
// remember to call supportInvalidateOptionsMenu() for this to be run
MenuItem item;
item = menu.findItem(R.id.action_joystick);
if (item != null) {
item.setIcon(mModeMouse ? R.drawable.ic_bt_misc_hid : R.drawable.ic_bt_pointing_hid);
item.setTitle(mModeMouse ? R.string.input_joystick : R.string.input_mouse);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Action bar was clicked.
final int item_id = item.getItemId();
if (item_id == R.id.action_keyboard) {
InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
imm.toggleSoftInput(0, 0);
}
return true;
} else if (item_id == R.id.action_speed) {
new SpeedFragment().show(getSupportFragmentManager(), FRAGMENT_SPEED);
return true;
} else if (item_id == R.id.action_joystick) {
mModeMouse = !mModeMouse;
supportInvalidateOptionsMenu(); // update icon
return true;
} else if (item_id == R.id.action_diskimage) {
new DiskImageFragment(mConfigFile, DiskImage.ASK).show(getSupportFragmentManager(), FRAGMENT_DISKIMAGE);
return true;
} else if (item_id == R.id.action_more_keys) {
final int vis = areControlsVisible() ? View.GONE : View.VISIBLE;
findViewById(R.id.b1).setVisibility(vis);
findViewById(R.id.b2).setVisibility(vis);
findViewById(R.id.b3).setVisibility(vis);
return true;
} else if (item_id == R.id.action_power_off) {
getThread().doPowerOff();
finish();
}
return false;
}
private SherlockDialogFragment findFragment(final String tag) {
return (SherlockDialogFragment)getSupportFragmentManager().findFragmentByTag(tag);
}
private boolean dismissFragment(final String tag) {
final SherlockDialogFragment frag = findFragment(tag);
if (frag == null) {
return false;
} else {
frag.dismiss();
return true;
}
}
private void handleNewIntent(Intent intent) {
final DiskImage image = getBootDiskImage(intent);
if (image == null) {
return; // Nothing to do.
}
image.action = DiskImage.ASK;
prepareDiskImage(image);
}
@Override
protected void onNewIntent(final Intent intent) {
// "An activity will always be paused before receiving a new intent."
withUIActive(new Runnable() {
public void run() {
handleNewIntent(intent);
}
});
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
updateActionBar(BitmapSize.quick(this).showActionBar());
mKegsView = (KegsViewGL)findViewById(R.id.kegsview);
mConfigFile = new ConfigFile(this);
mKegsThread = new KegsThread(mConfigFile.getConfigFile(),
mKegsView.getBitmap());
mKegsThread.registerUpdateScreenInterface(mKegsView);
mTouchMouse = new TouchMouse(this, getThread().getEventQueue());
mJoystick = new TouchJoystick(getThread().getEventQueue());
final SpecialRelativeLayout mainView = (SpecialRelativeLayout)findViewById(R.id.mainview);
mainView.setClickable(true);
mainView.setLongClickable(true);
mainView.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
// TODO: consider using two listeners and setOnTouchListener them
if (mModeMouse) {
return mTouchMouse.onTouchEvent(event);
} else {
return mJoystick.onTouchEvent(event);
}
}
});
mainView.setNotifySizeChanged(
new SpecialRelativeLayout.NotifySizeChanged() {
public void onSizeChanged(final int w, final int h, int oldw, int oldh) {
final long now = System.currentTimeMillis();
mScreenSizeTime = now;
mKegsView.postDelayed(new Runnable() { public void run() {
updateScreenSize(w, h, now);
} }, 250);
}
}
);
mKegsKeyboard = new KegsKeyboard(getThread().getEventQueue());
mKegsKeyboard.setOnStickyReset(this);
findViewById(R.id.key_escape).setOnClickListener(mButtonClick);
findViewById(R.id.key_return).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);
// Make sure local copy of internal disk images exist.
if (android.os.Build.VERSION.SDK_INT >= 11) {
new AssetImages(this, mConfigFile).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
new AssetImages(this, mConfigFile).execute();
}
final String romfile = mConfigFile.whichRomFile();
if (romfile == null) {
new RomDialogFragment().show(getSupportFragmentManager(), FRAGMENT_ROM);
} else {
boot();
}
}
@Override
protected void onPause() {
super.onPause();
getThread().onPause();
mKegsView.onPause();
mPaused = true;
}
@Override
protected void onResume() {
super.onResume();
getThread().onResume();
mKegsView.onResume();
mPaused = false;
Runnable runnable;
while ((runnable = mResumeQueue.poll()) != null) {
runnable.run();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mResumeQueue.clear();
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");
}
}