From 95e24dd7a54256e8df56e347c0f43133087a1df2 Mon Sep 17 00:00:00 2001 From: Thomas Martitz Date: Wed, 23 Feb 2011 01:10:54 +0000 Subject: [PATCH] Android: * Re-create RockboxFramebuffer instance with every time there's a new Activity. * Also, allow Rockbox to be started via multimedia buttons, immediately starting playback if wanted. We don't need to keep the fb instance around when it backround, and it makes us less depending on it and the activity (less race conditions). And this is how you usually do it in Android apps. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@29384 a1c6a512-1295-4272-9138-f99709370657 --- .../rockbox/Helper/MediaButtonReceiver.java | 17 ++- android/src/org/rockbox/RockboxActivity.java | 31 +---- .../src/org/rockbox/RockboxFramebuffer.java | 2 +- android/src/org/rockbox/RockboxService.java | 115 ++++++++---------- apps/main.c | 1 - .../target/hosted/android/button-android.c | 8 +- firmware/target/hosted/android/lcd-android.c | 97 +++++++-------- 7 files changed, 123 insertions(+), 148 deletions(-) diff --git a/android/src/org/rockbox/Helper/MediaButtonReceiver.java b/android/src/org/rockbox/Helper/MediaButtonReceiver.java index 3749cec32a..a57bdd4831 100644 --- a/android/src/org/rockbox/Helper/MediaButtonReceiver.java +++ b/android/src/org/rockbox/Helper/MediaButtonReceiver.java @@ -22,9 +22,8 @@ package org.rockbox.Helper; import java.lang.reflect.Method; - +import org.rockbox.RockboxFramebuffer; import org.rockbox.RockboxService; - import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -73,7 +72,12 @@ public class MediaButtonReceiver /* helper class for the manifest */ public static class MediaReceiver extends BroadcastReceiver - { + { + private void startService(Context c, Intent baseIntent) + { + baseIntent.setClass(c, RockboxService.class); + c.startService(baseIntent); + } @Override public void onReceive(Context context, Intent intent) { @@ -81,8 +85,11 @@ public class MediaButtonReceiver { KeyEvent key = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); if (key.getAction() == KeyEvent.ACTION_UP) - { /* pass the pressed key to Rockbox */ - if (RockboxService.get_instance().get_fb().dispatchKeyEvent(key)) + { /* pass the pressed key to Rockbox, starting it if needed */ + RockboxService s = RockboxService.get_instance(); + if (s == null || !s.isRockboxRunning()) + startService(context, intent); + else if (RockboxFramebuffer.buttonHandler(key.getKeyCode(), false)) abortBroadcast(); } } diff --git a/android/src/org/rockbox/RockboxActivity.java b/android/src/org/rockbox/RockboxActivity.java index 90c687f35a..65c7f92bbe 100644 --- a/android/src/org/rockbox/RockboxActivity.java +++ b/android/src/org/rockbox/RockboxActivity.java @@ -29,16 +29,12 @@ import android.os.Bundle; import android.os.Handler; import android.os.ResultReceiver; import android.util.Log; -import android.view.View; -import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.widget.Toast; public class RockboxActivity extends Activity { - private RockboxService rbservice; - /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) @@ -79,9 +75,7 @@ public class RockboxActivity extends Activity loadingdialog.setProgress(resultData.getInt("value", 0)); break; case RockboxService.RESULT_SERVICE_RUNNING: - rbservice = RockboxService.get_instance(); setServiceActivity(true); - attachFramebuffer(); break; case RockboxService.RESULT_ERROR_OCCURED: Toast.makeText(RockboxActivity.this, resultData.getString("error"), Toast.LENGTH_LONG); @@ -89,38 +83,21 @@ public class RockboxActivity extends Activity } } }); + setContentView(new RockboxFramebuffer(this)); startService(intent); } private void setServiceActivity(boolean set) { - if (rbservice != null) - rbservice.set_activity(set ? this : null); - } - - private void attachFramebuffer() - { - View rbFramebuffer = null; - try { - rbFramebuffer = rbservice.get_fb(); - setContentView(rbFramebuffer); - } catch (IllegalStateException e) { - /* we are already using the View, - * need to remove it and re-attach it */ - ViewGroup g = (ViewGroup) rbFramebuffer.getParent(); - g.removeView(rbFramebuffer); - setContentView(rbFramebuffer); - } catch (NullPointerException e) { - return; - } - rbFramebuffer.requestFocus(); + RockboxService s = RockboxService.get_instance(); + if (s != null) + s.set_activity(set ? this : null); } public void onResume() { super.onResume(); setVisible(true); - attachFramebuffer(); } /* this is also called when the backlight goes off, diff --git a/android/src/org/rockbox/RockboxFramebuffer.java b/android/src/org/rockbox/RockboxFramebuffer.java index 037fd2db85..a17bc90ab6 100644 --- a/android/src/org/rockbox/RockboxFramebuffer.java +++ b/android/src/org/rockbox/RockboxFramebuffer.java @@ -148,7 +148,7 @@ public class RockboxFramebuffer extends SurfaceView } private native void touchHandler(boolean down, int x, int y); - private native static boolean buttonHandler(int keycode, boolean state); + public native static boolean buttonHandler(int keycode, boolean state); public native void surfaceCreated(SurfaceHolder holder); public native void surfaceDestroyed(SurfaceHolder holder); diff --git a/android/src/org/rockbox/RockboxService.java b/android/src/org/rockbox/RockboxService.java index 43d122a8cc..4f0caa7704 100644 --- a/android/src/org/rockbox/RockboxService.java +++ b/android/src/org/rockbox/RockboxService.java @@ -59,8 +59,7 @@ public class RockboxService extends Service private static RockboxService instance = null; /* locals needed for the c code and rockbox state */ - private RockboxFramebuffer fb = null; - private volatile boolean rockbox_running; + private static volatile boolean rockbox_running; private Activity current_activity = null; private IntentFilter itf; private BroadcastReceiver batt_monitor; @@ -69,11 +68,9 @@ public class RockboxService extends Service @SuppressWarnings("unused") private int battery_level; private ResultReceiver resultReceiver; - final private Object lock = new Object(); public static final int RESULT_INVOKING_MAIN = 0; public static final int RESULT_LIB_LOAD_PROGRESS = 1; - public static final int RESULT_FB_INITIALIZED = 2; public static final int RESULT_SERVICE_RUNNING = 3; public static final int RESULT_ERROR_OCCURED = 4; public static final int RESULT_LIB_LOADED = 5; @@ -88,14 +85,15 @@ public class RockboxService extends Service public static RockboxService get_instance() { + /* don't call the construtor here, the instances are managed by + * android, so we can't just create a new one */ return instance; } - public RockboxFramebuffer get_fb() + public boolean isRockboxRunning() { - return fb; + return rockbox_running; } - public Activity get_activity() { return current_activity; @@ -108,71 +106,48 @@ public class RockboxService extends Service private void do_start(Intent intent) { LOG("Start Service"); - if (intent != null && intent.hasExtra("callback")) resultReceiver = (ResultReceiver) intent.getParcelableExtra("callback"); - /* Display a notification about us starting. - * We put an icon in the status bar. */ - if (fg_runner == null) - { /* needs to be initialized before main() runs */ - try { - } catch (Exception e) { - e.printStackTrace(); - } - } - if (!rockbox_running) - { - synchronized(lock) - { - startservice(); - while(true) { - try { - lock.wait(); - } catch (InterruptedException e) { - continue; - } - break; - } - fb = new RockboxFramebuffer(this); - if (resultReceiver != null) - resultReceiver.send(RESULT_FB_INITIALIZED, null); - } - } + startservice(); if (resultReceiver != null) resultReceiver.send(RESULT_LIB_LOADED, null); + if (intent != null && intent.getAction() != null) { - Log.d("RockboxService", intent.getAction()); - if (intent.getAction().equals("org.rockbox.PlayPause")) - { - if (fb != null) - fb.onKeyUp(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, null); + if (!rockbox_running) + { /* give it a bit of time so we can register button presses + * sleeping longer doesn't work here, apparently Android + * surpresses long sleeps during intent handling */ + try { + Thread.sleep(50); + } + catch (InterruptedException e) { } } + + if (intent.getAction().equals(Intent.ACTION_MEDIA_BUTTON)) + { + KeyEvent kev = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); + RockboxFramebuffer.buttonHandler(kev.getKeyCode(), kev.getAction() == KeyEvent.ACTION_DOWN); + } + else if (intent.getAction().equals("org.rockbox.PlayPause")) + RockboxFramebuffer.buttonHandler(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, false); else if (intent.getAction().equals("org.rockbox.Prev")) - { - if (fb != null) - fb.onKeyUp(KeyEvent.KEYCODE_MEDIA_PREVIOUS, null); - } + RockboxFramebuffer.buttonHandler(KeyEvent.KEYCODE_MEDIA_PREVIOUS, false); else if (intent.getAction().equals("org.rockbox.Next")) - { - if (fb != null) - fb.onKeyUp(KeyEvent.KEYCODE_MEDIA_NEXT, null); - } + RockboxFramebuffer.buttonHandler(KeyEvent.KEYCODE_MEDIA_NEXT, false); else if (intent.getAction().equals("org.rockbox.Stop")) - { - if (fb != null) - fb.onKeyUp(KeyEvent.KEYCODE_MEDIA_STOP, null); - } + RockboxFramebuffer.buttonHandler(KeyEvent.KEYCODE_MEDIA_STOP, false); } /* (Re-)attach the media button receiver, in case it has been lost */ mMediaButtonReceiver.register(); - if (resultReceiver != null) resultReceiver.send(RESULT_SERVICE_RUNNING, null); + + rockbox_running = true; } private void LOG(CharSequence text) @@ -195,16 +170,23 @@ public class RockboxService extends Service return 1; /* old API compatibility: 1 == START_STICKY */ } - private void startservice() + private void startservice() { - final int BUFFER = 8*1024; + final Object lock = new Object(); Thread rb = new Thread(new Runnable() { public void run() { + final int BUFFER = 8*1024; String rockboxDirPath = "/data/data/org.rockbox/app_rockbox/rockbox"; File rockboxDir = new File(rockboxDirPath); + /* load library before unzipping which may take a while */ + synchronized (lock) { + System.loadLibrary("rockbox"); + lock.notify(); + } + /* the following block unzips libmisc.so, which contains the files * we ship, such as themes. It's needed to put it into a .so file * because there's no other way to ship files and have access @@ -268,13 +250,7 @@ public class RockboxService extends Service } } } - - synchronized (lock) { - System.loadLibrary("rockbox"); - lock.notify(); - } - rockbox_running = true; if (resultReceiver != null) resultReceiver.send(RESULT_INVOKING_MAIN, null); @@ -283,7 +259,20 @@ public class RockboxService extends Service } }, "Rockbox thread"); rb.setDaemon(false); - rb.start(); + /* wait at least until the library is loaded */ + synchronized (lock) + { + rb.start(); + while(true) + { + try { + lock.wait(); + } catch (InterruptedException e) { + continue; + } + break; + } + } } private native void main(); @@ -352,5 +341,7 @@ public class RockboxService extends Service mMediaButtonReceiver = null; /* Make sure our notification is gone. */ stopForeground(); + instance = null; + rockbox_running = false; } } diff --git a/apps/main.c b/apps/main.c index 3fc48be183..bd04223f97 100644 --- a/apps/main.c +++ b/apps/main.c @@ -410,7 +410,6 @@ static void init(void) #endif /* CONFIG_CODEC == SWCODEC */ audio_init(); - button_clear_queue(); /* Empty the keyboard buffer */ settings_apply_skins(); } diff --git a/firmware/target/hosted/android/button-android.c b/firmware/target/hosted/android/button-android.c index 832eef54f3..ed1e125223 100644 --- a/firmware/target/hosted/android/button-android.c +++ b/firmware/target/hosted/android/button-android.c @@ -29,6 +29,7 @@ #include "system.h" #include "touchscreen.h" +extern JNIEnv *env_ptr; static int last_y, last_x; static int last_btns; @@ -61,11 +62,11 @@ Java_org_rockbox_RockboxFramebuffer_touchHandler(JNIEnv*env, jobject this, * this writes in an interrupt-like fashion the button events that the user * generated by pressing/releasing them to a variable */ JNIEXPORT bool JNICALL -Java_org_rockbox_RockboxFramebuffer_buttonHandler(JNIEnv*env, jclass this, +Java_org_rockbox_RockboxFramebuffer_buttonHandler(JNIEnv*env, jclass class, jint keycode, jboolean state) { (void)env; - (void)this; + (void)class; unsigned button = 0; @@ -75,7 +76,10 @@ Java_org_rockbox_RockboxFramebuffer_buttonHandler(JNIEnv*env, jclass this, if (!button) button = dpad_to_button((int)keycode); if (button) + { queue_post(&button_queue, button, 0); + return true; + } } if (!button) diff --git a/firmware/target/hosted/android/lcd-android.c b/firmware/target/hosted/android/lcd-android.c index f719329819..c9bd0a1254 100644 --- a/firmware/target/hosted/android/lcd-android.c +++ b/firmware/target/hosted/android/lcd-android.c @@ -29,68 +29,63 @@ #include "button.h" extern JNIEnv *env_ptr; -extern jclass RockboxService_class; extern jobject RockboxService_instance; -static jclass RockboxFramebuffer_class; static jobject RockboxFramebuffer_instance; static jmethodID java_lcd_update; static jmethodID java_lcd_update_rect; +static jmethodID java_lcd_init; +static jobject native_buffer; static int dpi; static int scroll_threshold; static bool display_on; -void lcd_init_device(void) +/* this might actually be called before lcd_init_device() or even main(), so + * be sure to only access static storage initalized at library loading, + * and not more */ +void connect_with_java(JNIEnv* env, jobject fb_instance) { - JNIEnv e = *env_ptr; - /* get existing instance from the Service */ - jmethodID get_fb = e->GetMethodID(env_ptr, RockboxService_class, "get_fb", - "()Lorg/rockbox/RockboxFramebuffer;"); - RockboxFramebuffer_instance = e->CallObjectMethod(env_ptr, - RockboxService_instance, - get_fb); - RockboxFramebuffer_class = (*env_ptr)->GetObjectClass(env_ptr, - RockboxFramebuffer_instance); + JNIEnv e = *env; + static bool have_class; + RockboxFramebuffer_instance = fb_instance; + if (!have_class) + { + jclass fb_class = e->GetObjectClass(env, fb_instance); + /* cache update functions */ + java_lcd_update = e->GetMethodID(env, fb_class, + "java_lcd_update", + "()V"); + java_lcd_update_rect = e->GetMethodID(env, fb_class, + "java_lcd_update_rect", + "(IIII)V"); + jmethodID get_dpi = e->GetMethodID(env, fb_class, + "getDpi", "()I"); + jmethodID thresh = e->GetMethodID(env, fb_class, + "getScrollThreshold", "()I"); + /* these don't change with new instances so call them now */ + dpi = e->CallIntMethod(env, fb_instance, get_dpi); + scroll_threshold = e->CallIntMethod(env, fb_instance, thresh); - /* Get init function and set up what's left from the constructor */ - jmethodID java_lcd_init = (*env_ptr)->GetMethodID(env_ptr, - RockboxFramebuffer_class, - "java_lcd_init", - "(IILjava/nio/ByteBuffer;)V"); - - jobject buf = e->NewDirectByteBuffer(env_ptr, + java_lcd_init = e->GetMethodID(env, fb_class, + "java_lcd_init", + "(IILjava/nio/ByteBuffer;)V"); + + native_buffer = e->NewDirectByteBuffer(env, lcd_framebuffer, (jlong)sizeof(lcd_framebuffer)); + have_class = true; + } + /* we need to setup parts for the java object every time */ + (*env)->CallVoidMethod(env, fb_instance, java_lcd_init, + (jint)LCD_WIDTH, (jint)LCD_HEIGHT, native_buffer); +} - e->CallVoidMethod(env_ptr, RockboxFramebuffer_instance, java_lcd_init, - (jint)LCD_WIDTH, - (jint)LCD_HEIGHT, - buf); - - /* cache update functions */ - java_lcd_update = (*env_ptr)->GetMethodID(env_ptr, - RockboxFramebuffer_class, - "java_lcd_update", - "()V"); - java_lcd_update_rect = (*env_ptr)->GetMethodID(env_ptr, - RockboxFramebuffer_class, - "java_lcd_update_rect", - "(IIII)V"); - - jmethodID get_dpi = e->GetMethodID(env_ptr, - RockboxFramebuffer_class, - "getDpi", "()I"); - - jmethodID get_scroll_threshold - = e->GetMethodID(env_ptr, - RockboxFramebuffer_class, - "getScrollThreshold", "()I"); - - dpi = e->CallIntMethod(env_ptr, RockboxFramebuffer_instance, - get_dpi); - scroll_threshold = e->CallIntMethod(env_ptr, RockboxFramebuffer_instance, - get_scroll_threshold); +/* + * Do nothing here and connect with the java object later (if it isn't already) + */ +void lcd_init_device(void) +{ /* must not draw until surface is created */ display_on = false; } @@ -116,12 +111,14 @@ void lcd_update_rect(int x, int y, int width, int height) * Note this is considered interrupt context */ JNIEXPORT void JNICALL -Java_org_rockbox_RockboxFramebuffer_surfaceCreated(JNIEnv *e, jobject this, +Java_org_rockbox_RockboxFramebuffer_surfaceCreated(JNIEnv *env, jobject this, jobject surfaceholder) { - (void)e; (void)this; (void)surfaceholder; - + (void)surfaceholder; + /* possibly a new instance - reconnect */ + connect_with_java(env, this); display_on = true; + send_event(LCD_EVENT_ACTIVATION, NULL); /* Force an update, since the newly created surface is initially black * waiting for the next normal update results in a longish black screen */