diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Activity.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Activity.java index ab2a1e5a..a5570b1e 100644 --- a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Activity.java +++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Activity.java @@ -41,6 +41,7 @@ public class Apple2Activity extends Activity { private final static int MAX_FINGERS = 32;// HACK ... private String mDataDir = null; + private boolean mSetUncaughtExceptionHandler = false; private Apple2View mView = null; private ArrayList mMenuStack = new ArrayList(); @@ -76,7 +77,7 @@ public class Apple2Activity extends Activity { public native void nativeOnResume(boolean isSystemResume); - public native void nativeOnPause(); + public native void nativeOnPause(boolean isSystemPause); public native void nativeOnQuit(); @@ -92,7 +93,7 @@ public class Apple2Activity extends Activity { // HACK NOTE 2015/02/22 : Apparently native code cannot easily access stuff in the APK ... so copy various resources // out of the APK and into the /data/data/... for ease of access. Because this is FOSS software we don't care about // security or DRM for these assets =) - private String firstTimeInitialization() { + private String _firstTimeInitialization() { String dataDir = null; try { @@ -153,25 +154,12 @@ public class Apple2Activity extends Activity { os.close(); } - @Override - public void onCreate(Bundle savedInstanceState) { - if (BuildConfig.DEBUG) { - StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() - .detectDiskReads() - .detectDiskWrites() - .detectAll() - .penaltyLog() - .build()); - StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() - .detectLeakedSqlLiteObjects() - /*.detectLeakedClosableObjects()*/ - .penaltyLog() - .penaltyDeath() - .build()); + private void _setCustomExceptionHandler() { + if (mSetUncaughtExceptionHandler) { + return; } - super.onCreate(savedInstanceState); + mSetUncaughtExceptionHandler = true; - // Immediately set up exception handler ... final String homeDir = "/data/data/" + this.getPackageName(); final Thread.UncaughtExceptionHandler defaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @@ -221,10 +209,30 @@ public class Apple2Activity extends Activity { defaultExceptionHandler.uncaughtException(thread, t); } }); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + if (BuildConfig.DEBUG) { + StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() + .detectDiskReads() + .detectDiskWrites() + .detectAll() + .penaltyLog() + .build()); + StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() + .detectLeakedSqlLiteObjects() + /*.detectLeakedClosableObjects()*/ + .penaltyLog() + .penaltyDeath() + .build()); + } + super.onCreate(savedInstanceState); Log.e(TAG, "onCreate()"); - mDataDir = firstTimeInitialization(); + _setCustomExceptionHandler(); + mDataDir = _firstTimeInitialization(); // get device audio parameters for native OpenSLES mSampleRate = DevicePropertyCalculator.getRecommendedSampleRate(this); @@ -297,7 +305,7 @@ public class Apple2Activity extends Activity { } } while (apple2MenuView != null); - nativeOnPause(); + nativeOnPause(true); } @Override @@ -372,17 +380,14 @@ public class Apple2Activity extends Activity { @Override public boolean onTouchEvent(MotionEvent event) { do { + Apple2MainMenu mainMenu = mView.getMainMenu(); if (mainMenu == null) { break; } - Apple2SettingsMenu settingsMenu = mainMenu.getSettingsMenu(); - Apple2DisksMenu disksMenu = mView.getDisksMenu(); - if (settingsMenu != null && settingsMenu.isShowing()) { - break; - } - if (disksMenu != null && disksMenu.isShowing()) { + Apple2MenuView apple2MenuView = peekApple2View(); + if (apple2MenuView != null) { break; } @@ -441,7 +446,7 @@ public class Apple2Activity extends Activity { public synchronized void pushApple2View(Apple2MenuView apple2MenuView) { mMenuStack.add(apple2MenuView); View menuView = apple2MenuView.getView(); - nativeOnPause(); + nativeOnPause(false); addContentView(menuView, new FrameLayout.LayoutParams(getWidth(), getHeight())); } @@ -456,6 +461,15 @@ public class Apple2Activity extends Activity { return apple2MenuView; } + public synchronized Apple2MenuView peekApple2View() { + int lastIndex = mMenuStack.size() - 1; + if (lastIndex < 0) { + return null; + } + + return mMenuStack.get(lastIndex); + } + public synchronized Apple2MenuView popApple2View(Apple2MenuView apple2MenuView) { boolean wasRemoved = mMenuStack.remove(apple2MenuView); _disposeApple2View(apple2MenuView); @@ -495,7 +509,7 @@ public class Apple2Activity extends Activity { } public void maybeQuitApp() { - nativeOnPause(); + nativeOnPause(false); if (mQuitDialog == null) { mQuitDialog = new AlertDialog.Builder(this).setIcon(R.drawable.ic_launcher).setCancelable(true).setTitle(R.string.quit_really).setMessage(R.string.quit_warning).setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { @Override @@ -527,7 +541,7 @@ public class Apple2Activity extends Activity { } public void maybeReboot() { - nativeOnPause(); + nativeOnPause(false); if (mRebootDialog == null) { mRebootDialog = new AlertDialog.Builder(this).setIcon(R.drawable.ic_launcher).setCancelable(true).setTitle(R.string.reboot_really).setMessage(R.string.reboot_warning).setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { @Override diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2MainMenu.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2MainMenu.java index 5e9ed468..ea5d7083 100644 --- a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2MainMenu.java +++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2MainMenu.java @@ -201,7 +201,7 @@ public class Apple2MainMenu { return; } - mActivity.nativeOnPause(); + mActivity.nativeOnPause(false); mMainMenuPopup.showAtLocation(mParentView, Gravity.CENTER, 0, 0); } diff --git a/Android/jni/jnihooks.c b/Android/jni/jnihooks.c index 92de9fe5..d9766037 100644 --- a/Android/jni/jnihooks.c +++ b/Android/jni/jnihooks.c @@ -30,7 +30,6 @@ enum { ANDROID_ACTION_POINTER_UP = 0x6, }; -static bool nativePaused = false; static bool nativeRequestsShowMainMenu = false; #if TESTING @@ -161,29 +160,28 @@ void Java_org_deadc0de_apple2ix_Apple2Activity_nativeGraphicsInitialized(JNIEnv } void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnResume(JNIEnv *env, jobject obj, jboolean isSystemResume) { - if (!nativePaused) { -#warning FIXME ... replace nativePaused check with cpu_isPaused() + if (!cpu_isPaused()) { return; } LOG("%s", "native onResume..."); - if (isSystemResume) { - if (video_backend->animation_showPaused) { - video_backend->animation_showPaused(); - } - } else { - nativePaused = false; + if (!isSystemResume) { cpu_resume(); } } -void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnPause(JNIEnv *env, jobject obj) { - if (nativePaused) { +void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnPause(JNIEnv *env, jobject obj, jboolean isSystemPause) { + if (cpu_isPaused()) { return; } - nativePaused = true; LOG("%s", "native onPause..."); - cpu_pause(); + if (isSystemPause) { + // going to background + cpu_pauseBackground(); + } else { + // going to menu + cpu_pause(); + } } void Java_org_deadc0de_apple2ix_Apple2Activity_nativeRender(JNIEnv *env, jobject obj) { @@ -191,7 +189,7 @@ void Java_org_deadc0de_apple2ix_Apple2Activity_nativeRender(JNIEnv *env, jobject return; } - if (!nativePaused) { + if (!cpu_isPaused()) { c_keys_handle_input(-1, 0, 0); } @@ -289,9 +287,8 @@ jboolean Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnTouch(JNIEnv *env, jo return true; } - if (nativePaused) { + if (cpu_isPaused()) { LOG("UNPAUSING NATIVE CPU THREAD"); - nativePaused = false; cpu_resume(); return true; } diff --git a/src/common.h b/src/common.h index 0e5f6227..31b7013b 100644 --- a/src/common.h +++ b/src/common.h @@ -269,6 +269,16 @@ static const char *log_end = "\n"; _LOG(__VA_ARGS__); \ } while (0) +#define RELEASE_BREAK() \ + do { \ + /* BOOM */ \ + char *ptr = (char *)0xABADF000; \ + *ptr = '\0';\ + /* or if that worked, just deref NULL */ \ + ptr = (char *)0x0; \ + *ptr = '\0'; \ + } while (0); + #define FREE(x) \ do { \ free((x)); \ diff --git a/src/timing.c b/src/timing.c index e169c158..4780bdd6 100644 --- a/src/timing.c +++ b/src/timing.c @@ -81,7 +81,10 @@ bool alt_speed_enabled = false; // misc volatile uint8_t emul_reinitialize = 1; -unsigned long emul_reinitialize_audio = 1UL; +bool emul_reinitialize_audio = 1UL; +#if MOBILE_DEVICE +static bool emul_reinitialize_background = true; +#endif pthread_t cpu_thread_id = 0; pthread_mutex_t interface_mutex = { 0 }; pthread_cond_t dbg_thread_cond = PTHREAD_COND_INITIALIZER; @@ -170,35 +173,48 @@ void timing_toggleCPUSpeed(void) { void timing_reinitializeAudio(void) { assert(cpu_isPaused() || (pthread_self() == cpu_thread_id)); - __sync_fetch_and_or(&emul_reinitialize_audio, 1UL); + emul_reinitialize_audio = true; } void cpu_pause(void) { + + assert(pthread_self() != cpu_thread_id); _LOCK_CPU_THREAD(); +#if MOBILE_DEVICE + if (emul_reinitialize_background) { + RELEASE_LOG("CPU thread already paused ..."); + _UNLOCK_CPU_THREAD(); + return; + } +#endif + #ifdef AUDIO_ENABLED audio_pause(); #endif is_paused = true; } -static void _cpu_thread_really_start(void) { - int err = 0; - if ( (err = pthread_cond_signal(&cpu_thread_cond)) ) { - ERRLOG("pthread_cond_signal : %d", err); - } +#if MOBILE_DEVICE +void cpu_pauseBackground(void) { + assert(pthread_self() != cpu_thread_id); + _LOCK_CPU_THREAD(); + emul_reinitialize_background = true; + _UNLOCK_CPU_THREAD(); } +#endif void cpu_resume(void) { + assert(pthread_self() != cpu_thread_id); assert(cpu_isPaused()); is_paused = false; - static pthread_once_t onceToken = PTHREAD_ONCE_INIT; - if (onceToken == PTHREAD_ONCE_INIT) { - int err = 0; - if ( (err = pthread_once(&onceToken, _cpu_thread_really_start)) ) { - ERRLOG("pthread_once : %d", err); - } +#if MOBILE_DEVICE + int err = pthread_cond_signal(&cpu_thread_cond); + if (err) { + RELEASE_ERRLOG("pthread_cond_signal : %d", err); + RELEASE_BREAK(); } +#endif #ifdef AUDIO_ENABLED audio_resume(); @@ -243,20 +259,46 @@ void *cpu_thread(void *dummyptr) { unsigned int dbg_cycles_executed = 0; #endif -#if !TESTING - pthread_mutex_lock(&interface_mutex); - int err = 0; - LOG("cpu_thread : waiting for splash screen completion..."); - pthread_cond_wait(&cpu_thread_cond, &interface_mutex); - pthread_mutex_unlock(&interface_mutex); - LOG("cpu_thread : starting..."); -#endif - do { +#if MOBILE_DEVICE && !TESTING + if (emul_reinitialize_background) { + + speaker_destroy(); + MB_Destroy(); + audio_shutdown(); + + int err = TEMP_FAILURE_RETRY(pthread_mutex_lock(&interface_mutex)); + if (err) { + RELEASE_LOG("Error locking CPU mutex : %d", err); + RELEASE_BREAK(); + } + + is_paused = true; + emul_reinitialize_background = false; + + LOG("cpu_thread : waiting for splash screen completion..."); + err = pthread_cond_wait(&cpu_thread_cond, &interface_mutex); + if (err) { + RELEASE_LOG("Error waiting for CPU condition : %d", err); + RELEASE_BREAK(); + } + + err = TEMP_FAILURE_RETRY(pthread_mutex_unlock(&interface_mutex)); + if (err) { + RELEASE_LOG("Error unlocking CPU mutex : %d", err); + RELEASE_BREAK(); + } + + LOG("cpu_thread : starting..."); + emul_reinitialize_audio = true; + } +#endif + #ifdef AUDIO_ENABLED - bool reinit_audio = __sync_fetch_and_and(&emul_reinitialize_audio, 0UL); - if (reinit_audio) { + if (emul_reinitialize_audio) { + emul_reinitialize_audio = false; + speaker_destroy(); MB_Destroy(); audio_shutdown(); @@ -266,6 +308,7 @@ void *cpu_thread(void *dummyptr) { MB_Initialize(); } #endif + if (emul_reinitialize) { reinitialize(); } @@ -457,6 +500,12 @@ void *cpu_thread(void *dummyptr) { break; } +#if MOBILE_DEVICE + if (UNLIKELY(emul_reinitialize_background)) { + break; + } +#endif + if (UNLIKELY(emulator_shutting_down)) { break; } diff --git a/src/timing.h b/src/timing.h index c51b578a..0964aa48 100644 --- a/src/timing.h +++ b/src/timing.h @@ -74,7 +74,7 @@ struct timespec timespec_diff(struct timespec start, struct timespec end, bool * */ void timing_toggleCPUSpeed(void); -#if !defined(MOBILE_DEVICE) +#if !MOBILE_DEVICE /* * check whether automatic adjusting of CPU speed is configured. */ @@ -97,10 +97,26 @@ void timing_reinitializeAudio(void); void *cpu_thread(void *ignored); /* - * Pause timing/CPU thread + * Pause timing/CPU thread. + * + * This may block for a short amount of time to grab the appropriate mutex. CPU thread is blocked upon function return, + * until call to cpu_resume() is made. */ void cpu_pause(void); +#if MOBILE_DEVICE +/* + * Pause timing/CPU thread because of a system backgrounding event. + * + * This may block for a short amount of time to grab the appropriate mutex, toggle a dirty bit, and release the mutex. + * NOTE: CPU thread is not likely to actually be paused upon function return, (but will be shortly thereafter). + * + * This should also destroy/free any audio resources (speaker, mockingboard) managed by the CPU thread back to system. + * Audio resources will be automatically recreated upon call to cpu_resume() + */ +void cpu_pauseBackground(void); +#endif + /* * Resume timing/CPU thread */