diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index c52d83fd2c..06a13ddb40 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -16,10 +16,21 @@ + + + + + + + + + diff --git a/android/src/org/rockbox/Helper/MediaButtonReceiver.java b/android/src/org/rockbox/Helper/MediaButtonReceiver.java new file mode 100644 index 0000000000..3749cec32a --- /dev/null +++ b/android/src/org/rockbox/Helper/MediaButtonReceiver.java @@ -0,0 +1,164 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2010 Thomas Martitz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +package org.rockbox.Helper; + +import java.lang.reflect.Method; + +import org.rockbox.RockboxService; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.AudioManager; +import android.view.KeyEvent; + +public class MediaButtonReceiver +{ + /* A note on the API being used. 2.2 introduces a new and sane API + * for handling multimedia button presses + * http://android-developers.blogspot.com/2010/06/allowing-applications-to-play-nicer.html + * + * the old API is flawed. It doesn't have management for + * concurrent media apps + * + * if multiple media apps are running + * probably all of them want to respond to media keys + * + * it's not clear which app wins, it depends on the + * priority set for the IntentFilter (see below) + * + * so this all might or might not work on < 2.2 */ + + IMultiMediaReceiver api; + + public MediaButtonReceiver(Context c) + { + try { + api = new NewApi(c); + } catch (Exception e) { + api = new OldApi(c); + } + } + + public void register() + { + api.register(); + } + + public void unregister() + { + api.register(); + } + + /* helper class for the manifest */ + public static class MediaReceiver extends BroadcastReceiver + { + @Override + public void onReceive(Context context, Intent intent) + { + if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) + { + 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)) + abortBroadcast(); + } + } + } + } + + private interface IMultiMediaReceiver + { + void register(); + void unregister(); + } + + private static class NewApi implements IMultiMediaReceiver + { + private Method register_method; + private Method unregister_method; + private AudioManager audio_manager; + private ComponentName receiver_name; + /* the constructor gets the methods through reflection so that + * this compiles on pre-2.2 devices */ + NewApi(Context c) throws SecurityException, NoSuchMethodException + { + register_method = AudioManager.class.getMethod( + "registerMediaButtonEventReceiver", + new Class[] { ComponentName.class } ); + unregister_method = AudioManager.class.getMethod( + "unregisterMediaButtonEventReceiver", + new Class[] { ComponentName.class } ); + + audio_manager = (AudioManager)c.getSystemService(Context.AUDIO_SERVICE); + receiver_name = new ComponentName(c, MediaReceiver.class); + } + public void register() + { + try { + register_method.invoke(audio_manager, receiver_name); + } catch (Exception e) { + // Nothing + e.printStackTrace(); + } + } + + public void unregister() + { + try + { + unregister_method.invoke(audio_manager, receiver_name); + } catch (Exception e) { + // Nothing + e.printStackTrace(); + } + } + + } + + private static class OldApi implements IMultiMediaReceiver + { + private static final IntentFilter filter = new IntentFilter(Intent.ACTION_MEDIA_BUTTON); + private MediaReceiver receiver; + private Context context; + OldApi(Context c) + { + filter.setPriority(1); /* 1 higher than the built-in media player */ + receiver = new MediaReceiver(); + context = c; + } + + public void register() + { + context.registerReceiver(receiver, filter); + } + + public void unregister() + { + context.unregisterReceiver(receiver); + } + + } +} diff --git a/android/src/org/rockbox/RockboxFramebuffer.java b/android/src/org/rockbox/RockboxFramebuffer.java index 0daeffe265..84974d627a 100644 --- a/android/src/org/rockbox/RockboxFramebuffer.java +++ b/android/src/org/rockbox/RockboxFramebuffer.java @@ -23,6 +23,8 @@ package org.rockbox; import java.nio.ByteBuffer; +import org.rockbox.Helper.MediaButtonReceiver; + import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -35,6 +37,7 @@ public class RockboxFramebuffer extends View { private Bitmap btm; private ByteBuffer native_buf; + private MediaButtonReceiver media_monitor; public RockboxFramebuffer(Context c, int lcd_width, int lcd_height, ByteBuffer native_fb) @@ -47,6 +50,8 @@ public class RockboxFramebuffer extends View btm = Bitmap.createBitmap(lcd_width, lcd_height, Bitmap.Config.RGB_565); native_buf = native_fb; requestFocus(); + media_monitor = new MediaButtonReceiver(c); + media_monitor.register(); /* the service needs to know the about us */ ((RockboxService)c).set_fb(this); } @@ -122,6 +127,12 @@ public class RockboxFramebuffer extends View set_lcd_active(1); } + public void destroy() + { + suspend(); + media_monitor.unregister(); + } + public native void set_lcd_active(int active); public native void touchHandler(boolean down, int x, int y); public native boolean buttonHandler(int keycode, boolean state); diff --git a/android/src/org/rockbox/RockboxService.java b/android/src/org/rockbox/RockboxService.java index b841bc5d7f..8989271b9f 100644 --- a/android/src/org/rockbox/RockboxService.java +++ b/android/src/org/rockbox/RockboxService.java @@ -139,6 +139,7 @@ public class RockboxService extends Service { public void run() { + LOG("main"); /* 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 @@ -276,6 +277,7 @@ public class RockboxService extends Service public void onDestroy() { super.onDestroy(); + fb.destroy(); /* Make sure our notification is gone. */ stopForeground(); } diff --git a/apps/action.c b/apps/action.c index 8f427c8d68..d61930a08c 100644 --- a/apps/action.c +++ b/apps/action.c @@ -183,8 +183,9 @@ static int get_action_worker(int context, int timeout, else button = button_get_w_tmo(timeout); - /* Data from sys events can be pulled with button_get_data */ - if (button == BUTTON_NONE || button & SYS_EVENT) + /* Data from sys events can be pulled with button_get_data + * multimedia button presses don't go through the action system */ + if (button == BUTTON_NONE || button & (SYS_EVENT|BUTTON_MULTIMEDIA)) return button; /* Don't send any buttons through untill we see the release event */ if (wait_for_release) diff --git a/apps/gui/wps.c b/apps/gui/wps.c index a5fe304d21..7d633ad4e8 100644 --- a/apps/gui/wps.c +++ b/apps/gui/wps.c @@ -1045,18 +1045,18 @@ long gui_wps_show(void) exit = true; break; #endif - case SYS_POWEROFF: - default_event_handler(SYS_POWEROFF); - break; case ACTION_WPS_VIEW_PLAYLIST: gwps_leave_wps(); return GO_TO_PLAYLIST_VIEWER; break; default: - if(default_event_handler(button) == SYS_USB_CONNECTED) - { - gwps_leave_wps(); - return GO_TO_ROOT; + switch(default_event_handler(button)) + { /* music has been stopped by the default handler */ + case SYS_USB_CONNECTED: + case SYS_CALL_INCOMING: + case BUTTON_MULTIMEDIA_STOP: + gwps_leave_wps(); + return GO_TO_ROOT; } update = true; break; diff --git a/apps/menu.c b/apps/menu.c index 9d67c7b03e..5839a51c21 100644 --- a/apps/menu.c +++ b/apps/menu.c @@ -650,10 +650,20 @@ int do_menu(const struct menu_item_ex *start_menu, int *start_selected, } #endif } - else if(default_event_handler(action) == SYS_USB_CONNECTED) + else { - ret = MENU_ATTACHED_USB; - done = true; + switch(default_event_handler(action)) + { + case SYS_USB_CONNECTED: + ret = MENU_ATTACHED_USB; + done = true; + break; + case SYS_CALL_HUNG_UP: + case BUTTON_MULTIMEDIA_PLAYPAUSE: + /* remove splash from playlist_resume() */ + redraw_lists = true; + break; + } } if (redraw_lists && !done) diff --git a/apps/misc.c b/apps/misc.c index 8d0ca7922f..c41f63456c 100644 --- a/apps/misc.c +++ b/apps/misc.c @@ -529,6 +529,7 @@ long default_event_handler_ex(long event, void (*callback)(void *), void *parame #if CONFIG_PLATFORM & PLATFORM_ANDROID static bool resume = false; #endif + switch(event) { case SYS_BATTERY_UPDATE: @@ -629,10 +630,44 @@ long default_event_handler_ex(long event, void (*callback)(void *), void *parame if (resume && playlist_resume() != -1) { playlist_start(global_status.resume_index, - global_status.resume_offset); + global_status.resume_offset); } resume = false; return SYS_CALL_HUNG_UP; +#endif +#ifdef HAVE_MULTIMEDIA_KEYS + /* multimedia keys on keyboards, headsets */ + case BUTTON_MULTIMEDIA_PLAYPAUSE: + { + int status = audio_status(); + if (status & AUDIO_STATUS_PLAY) + { + if (status & AUDIO_STATUS_PAUSE) + audio_resume(); + else + audio_pause(); + } + else + if (playlist_resume() != -1) + { + playlist_start(global_status.resume_index, + global_status.resume_offset); + } + return event; + } + case BUTTON_MULTIMEDIA_NEXT: + audio_next(); + return event; + case BUTTON_MULTIMEDIA_PREV: + audio_prev(); + return event; + case BUTTON_MULTIMEDIA_STOP: + list_stop_handler(); + return event; + case BUTTON_MULTIMEDIA_REW: + case BUTTON_MULTIMEDIA_FFWD: + /* not supported yet, needs to be done in the WPS */ + return 0; #endif } return 0; diff --git a/firmware/export/button.h b/firmware/export/button.h index 097f50a9d5..3847d2ac9f 100644 --- a/firmware/export/button.h +++ b/firmware/export/button.h @@ -69,6 +69,21 @@ int button_apply_acceleration(const unsigned int data); #define BUTTON_REL 0x02000000 #define BUTTON_REPEAT 0x04000000 #define BUTTON_TOUCHSCREEN 0x08000000 +#define BUTTON_MULTIMEDIA 0x10000000 + +#define BUTTON_MULTIMEDIA_PLAYPAUSE (BUTTON_MULTIMEDIA|0x01) +#define BUTTON_MULTIMEDIA_STOP (BUTTON_MULTIMEDIA|0x02) +#define BUTTON_MULTIMEDIA_PREV (BUTTON_MULTIMEDIA|0x04) +#define BUTTON_MULTIMEDIA_NEXT (BUTTON_MULTIMEDIA|0x08) +#define BUTTON_MULTIMEDIA_REW (BUTTON_MULTIMEDIA|0x10) +#define BUTTON_MULTIMEDIA_FFWD (BUTTON_MULTIMEDIA|0x20) + +#define BUTTON_MULTIMEDIA_ALL (BUTTON_MULTIMEDIA_PLAYPAUSE| \ + BUTTON_MULTIMEDIA_STOP| \ + BUTTON_MULTIMEDIA_PREV| \ + BUTTON_MULTIMEDIA_NEXT| \ + BUTTON_MULTIMEDIA_REW | \ + BUTTON_MULTIMEDIA_FFWD) #ifdef HAVE_TOUCHSCREEN int touchscreen_last_touch(void); diff --git a/firmware/export/config/application.h b/firmware/export/config/application.h index 4dc34c14fa..b731f0cf76 100644 --- a/firmware/export/config/application.h +++ b/firmware/export/config/application.h @@ -77,6 +77,7 @@ #if (CONFIG_PLATFORM & PLATFORM_ANDROID) #define CONFIG_KEYPAD ANDROID_PAD +#define HAVE_MULTIMEDIA_KEYS #elif (CONFIG_PLATFORM & PLATFORM_SDL) #define HAVE_SCROLLWHEEL #define CONFIG_KEYPAD SDL_PAD diff --git a/firmware/target/hosted/android/app/button-application.c b/firmware/target/hosted/android/app/button-application.c index 47798a6096..a7d75ef172 100644 --- a/firmware/target/hosted/android/app/button-application.c +++ b/firmware/target/hosted/android/app/button-application.c @@ -45,3 +45,24 @@ int key_to_button(int keyboard_key) return BUTTON_MENU; } } + +unsigned multimedia_to_button(int keyboard_key) +{ + switch (keyboard_key) + { + case KEYCODE_MEDIA_PLAY_PAUSE: + return BUTTON_MULTIMEDIA_PLAYPAUSE; + case KEYCODE_MEDIA_STOP: + return BUTTON_MULTIMEDIA_STOP; + case KEYCODE_MEDIA_NEXT: + return BUTTON_MULTIMEDIA_NEXT; + case KEYCODE_MEDIA_PREVIOUS: + return BUTTON_MULTIMEDIA_PREV; + case KEYCODE_MEDIA_REWIND: + return BUTTON_MULTIMEDIA_REW; + case KEYCODE_MEDIA_FAST_FORWARD: + return BUTTON_MULTIMEDIA_FFWD; + default: + return 0; + } +} diff --git a/firmware/target/hosted/android/app/button-target.h b/firmware/target/hosted/android/app/button-target.h index 6c7bd271e9..ca306d4fef 100644 --- a/firmware/target/hosted/android/app/button-target.h +++ b/firmware/target/hosted/android/app/button-target.h @@ -28,6 +28,7 @@ #undef button_init_device void button_init_device(void); int button_read_device(int *data); +unsigned multimedia_to_button(int keyboard_key); /* Main unit's buttons */ #define BUTTON_MENU 0x00000001 diff --git a/firmware/target/hosted/android/button-android.c b/firmware/target/hosted/android/button-android.c index c072e3e38b..9bf15c25a2 100644 --- a/firmware/target/hosted/android/button-android.c +++ b/firmware/target/hosted/android/button-android.c @@ -28,7 +28,7 @@ #include "kernel.h" #include "system.h" #include "touchscreen.h" - +#include "debug.h" static int last_y, last_x; static int last_btns; static long last_button_tick; @@ -44,7 +44,7 @@ static enum { * began or stopped the touch action + where (pixel coordinates) */ JNIEXPORT void JNICALL Java_org_rockbox_RockboxFramebuffer_touchHandler(JNIEnv*env, jobject this, - bool down, int x, int y) + jboolean down, jint x, jint y) { (void)env; (void)this; @@ -63,12 +63,23 @@ Java_org_rockbox_RockboxFramebuffer_touchHandler(JNIEnv*env, jobject this, * generated by pressing/releasing them to a variable */ JNIEXPORT bool JNICALL Java_org_rockbox_RockboxFramebuffer_buttonHandler(JNIEnv*env, jobject this, - int keycode, bool state) + jint keycode, jboolean state) { (void)env; (void)this; - int button = key_to_button(keycode); + unsigned button = 0; + + if (!state) + button = multimedia_to_button((int)keycode); + + if (button) + { /* multimeida buttons are handled differently */ + queue_post(&button_queue, button, 0); + return true; + } + + button = key_to_button(keycode); if (button == BUTTON_NONE) return false;