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;