Add widgets to android port.

git-svn-id: svn://svn.rockbox.org/rockbox/trunk@29170 a1c6a512-1295-4272-9138-f99709370657
diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml
index 0eab90f..41a662f 100644
--- a/android/AndroidManifest.xml
+++ b/android/AndroidManifest.xml
@@ -29,6 +29,37 @@
 			</intent-filter>
 		</receiver>
 
+    <!-- Widgets -->
+    <receiver android:name=".widgets.RockboxWidgetProvider4x1"
+              android:label="@string/appwidget_label_4x1">
+      <intent-filter>
+        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+        <action android:name="org.rockbox.TrackUpdateInfo" />
+        <action android:name="org.rockbox.TrackFinish" />
+        <action android:name="org.rockbox.UpdateState" />
+      </intent-filter>
+      <meta-data android:name="android.appwidget.provider"
+                 android:resource="@xml/appwidget_provider_4x1" />
+    </receiver>
+
+    <receiver android:name=".widgets.RockboxWidgetProvider2x2"
+              android:label="@string/appwidget_label_2x2">
+      <intent-filter>
+        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+        <action android:name="org.rockbox.TrackUpdateInfo" />
+        <action android:name="org.rockbox.TrackFinish" />
+        <action android:name="org.rockbox.UpdateState" />
+      </intent-filter>
+      <meta-data android:name="android.appwidget.provider"
+                 android:resource="@xml/appwidget_provider_2x2" />
+    </receiver>
+
+    <!-- Widget configuration -->
+    <activity android:name=".widgets.RockboxWidgetConfigure">
+      <intent-filter>
+        <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
+      </intent-filter>
+    </activity>
 	
 	</application>
 
diff --git a/android/android.make b/android/android.make
index 82a8ded..53d5d46 100644
--- a/android/android.make
+++ b/android/android.make
@@ -40,6 +40,7 @@
 
 JAVA_SRC	:= $(wildcard $(ANDROID_DIR)/src/$(PACKAGE_PATH)/Helper/*.java)
 JAVA_SRC	+= $(wildcard $(ANDROID_DIR)/src/$(PACKAGE_PATH)/*.java)
+JAVA_SRC	+= $(wildcard $(ANDROID_DIR)/src/$(PACKAGE_PATH)/widgets/*.java)
 JAVA_OBJ	:= $(call java2class,$(subst $(ANDROID)/src/$(PACKAGE_PATH),$(ANDROID)/bin/$(PACKAGE_PATH),$(JAVA_SRC)))
 
 
diff --git a/android/res/drawable/appwidget_background.xml b/android/res/drawable/appwidget_background.xml
new file mode 100644
index 0000000..a9a2349
--- /dev/null
+++ b/android/res/drawable/appwidget_background.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:drawable="@drawable/appwidget_background_normal" />
+</selector>
+
diff --git a/android/res/drawable/appwidget_background_normal.9.png b/android/res/drawable/appwidget_background_normal.9.png
new file mode 100644
index 0000000..c06f285
--- /dev/null
+++ b/android/res/drawable/appwidget_background_normal.9.png
Binary files differ
diff --git a/android/res/drawable/appwidget_ff_normal.png b/android/res/drawable/appwidget_ff_normal.png
new file mode 100644
index 0000000..82a90dd
--- /dev/null
+++ b/android/res/drawable/appwidget_ff_normal.png
Binary files differ
diff --git a/android/res/drawable/appwidget_infodisplay_background.xml b/android/res/drawable/appwidget_infodisplay_background.xml
new file mode 100644
index 0000000..e6ed34c
--- /dev/null
+++ b/android/res/drawable/appwidget_infodisplay_background.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:state_pressed="true" android:drawable="@drawable/appwidget_selection_clicked" />
+  <item android:state_focused="true" android:drawable="@drawable/appwidget_selection_over" />
+  <item android:drawable="@drawable/appwidget_selection_transparent" />
+</selector>
+
diff --git a/android/res/drawable/appwidget_next.xml b/android/res/drawable/appwidget_next.xml
new file mode 100644
index 0000000..34d6a69
--- /dev/null
+++ b/android/res/drawable/appwidget_next.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:drawable="@drawable/appwidget_ff_normal" />
+</selector>
+
diff --git a/android/res/drawable/appwidget_pause.xml b/android/res/drawable/appwidget_pause.xml
new file mode 100644
index 0000000..72df5a8
--- /dev/null
+++ b/android/res/drawable/appwidget_pause.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:drawable="@drawable/appwidget_pause_normal" />
+</selector>
+
diff --git a/android/res/drawable/appwidget_pause_normal.png b/android/res/drawable/appwidget_pause_normal.png
new file mode 100644
index 0000000..835c5e7
--- /dev/null
+++ b/android/res/drawable/appwidget_pause_normal.png
Binary files differ
diff --git a/android/res/drawable/appwidget_play.xml b/android/res/drawable/appwidget_play.xml
new file mode 100644
index 0000000..5fa0f5b
--- /dev/null
+++ b/android/res/drawable/appwidget_play.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:drawable="@drawable/appwidget_play_normal" />
+</selector>
+
diff --git a/android/res/drawable/appwidget_play_normal.png b/android/res/drawable/appwidget_play_normal.png
new file mode 100644
index 0000000..73d48c9
--- /dev/null
+++ b/android/res/drawable/appwidget_play_normal.png
Binary files differ
diff --git a/android/res/drawable/appwidget_prev.xml b/android/res/drawable/appwidget_prev.xml
new file mode 100644
index 0000000..df56f77
--- /dev/null
+++ b/android/res/drawable/appwidget_prev.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:drawable="@drawable/appwidget_rew_normal" />
+</selector>
+
diff --git a/android/res/drawable/appwidget_rew_normal.png b/android/res/drawable/appwidget_rew_normal.png
new file mode 100644
index 0000000..b5e18a7
--- /dev/null
+++ b/android/res/drawable/appwidget_rew_normal.png
Binary files differ
diff --git a/android/res/drawable/appwidget_selection_clicked.9.png b/android/res/drawable/appwidget_selection_clicked.9.png
new file mode 100644
index 0000000..7d39e88
--- /dev/null
+++ b/android/res/drawable/appwidget_selection_clicked.9.png
Binary files differ
diff --git a/android/res/drawable/appwidget_selection_over.9.png b/android/res/drawable/appwidget_selection_over.9.png
new file mode 100644
index 0000000..da96c6a
--- /dev/null
+++ b/android/res/drawable/appwidget_selection_over.9.png
Binary files differ
diff --git a/android/res/drawable/appwidget_selection_transparent.9.png b/android/res/drawable/appwidget_selection_transparent.9.png
new file mode 100644
index 0000000..535eb0b
--- /dev/null
+++ b/android/res/drawable/appwidget_selection_transparent.9.png
Binary files differ
diff --git a/android/res/drawable/appwidget_stop.xml b/android/res/drawable/appwidget_stop.xml
new file mode 100644
index 0000000..895c8b6
--- /dev/null
+++ b/android/res/drawable/appwidget_stop.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:drawable="@drawable/appwidget_stop_normal" />
+</selector>
+
diff --git a/android/res/drawable/appwidget_stop_normal.png b/android/res/drawable/appwidget_stop_normal.png
new file mode 100644
index 0000000..5693225
--- /dev/null
+++ b/android/res/drawable/appwidget_stop_normal.png
Binary files differ
diff --git a/android/res/drawable/rockbox.png b/android/res/drawable/rockbox.png
new file mode 100644
index 0000000..84d940a
--- /dev/null
+++ b/android/res/drawable/rockbox.png
Binary files differ
diff --git a/android/res/drawable/source/appwidget_background.svg b/android/res/drawable/source/appwidget_background.svg
new file mode 100644
index 0000000..1404bde
--- /dev/null
+++ b/android/res/drawable/source/appwidget_background.svg
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="46"
+   height="116"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.47 r22583"
+   sodipodi:docname="appwidget_background.svg"
+   inkscape:export-filename="/home/antoine/rockbox/rockbox-svn/android/res/drawable/appwidget_background_normal.9.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3594">
+      <stop
+         style="stop-color:#000000;stop-opacity:1;"
+         offset="0"
+         id="stop3596" />
+      <stop
+         style="stop-color:#545454;stop-opacity:1;"
+         offset="1"
+         id="stop3598" />
+    </linearGradient>
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective10" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3594"
+       id="linearGradient3600"
+       x1="149.28572"
+       y1="442.29074"
+       x2="149.28572"
+       y2="345.29074"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-1.940295e-8,-15)" />
+    <inkscape:perspective
+       id="perspective3612"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#bd0000"
+     borderopacity="0.75686275"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="14.651253"
+     inkscape:cx="0.54030499"
+     inkscape:cy="14.631532"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:window-width="1280"
+     inkscape:window-height="725"
+     inkscape:window-x="0"
+     inkscape:window-y="25"
+     inkscape:window-maximized="1"
+     inkscape:snap-page="false"
+     inkscape:snap-grids="true"
+     inkscape:snap-to-guides="false" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-133.78572,-328.29074)">
+    <rect
+       style="fill:url(#linearGradient3600);fill-opacity:1;fill-rule:evenodd;stroke:#8d8d8d;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       id="rect2816"
+       width="25"
+       height="99"
+       x="144.28572"
+       y="334.79074"
+       ry="4.8042979"
+       rx="4.3476572" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 150.28573,328.79074 13.00097,0"
+       id="path3602"
+       clip-path="none" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:0.99999994;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 134.28572,340.75091 0,87.06367"
+       id="path3602-1" />
+  </g>
+</svg>
diff --git a/android/res/layout/appwidget_2x2.xml b/android/res/layout/appwidget_2x2.xml
new file mode 100644
index 0000000..a29f416
--- /dev/null
+++ b/android/res/layout/appwidget_2x2.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="fill_parent"
+              android:layout_height="fill_parent"
+              android:orientation="vertical"
+              android:gravity="center"
+              android:background="@drawable/appwidget_background">
+              <!-- style="@style/appwidget_background"-->
+
+  <ImageView android:id="@+id/logo"
+             android:layout_width="fill_parent"
+             android:layout_height="40dp"
+             android:scaleType="centerInside"
+             android:src="@drawable/rockbox" />
+  <Button android:id="@+id/infoDisplay"
+          style="@style/appwidget_infodisplay"
+          android:layout_width="fill_parent"
+          android:text="@string/appwidget_infoDisplay" />
+  <LinearLayout android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="0"
+                android:gravity="center"
+                android:orientation="horizontal">
+    <ImageButton android:id="@+id/prev"
+                 style="@style/appwidget_button"
+                 android:layout_weight="1"
+                 android:src="@drawable/appwidget_prev" />
+    <ImageButton android:id="@+id/stop"
+                 style="@style/appwidget_button"
+                 android:layout_weight="1"
+                 android:src="@drawable/appwidget_stop" />
+    <ImageButton android:id="@+id/playPause"
+                 style="@style/appwidget_button"
+                 android:layout_weight="1"
+                 android:src="@drawable/appwidget_play" />
+    <ImageButton android:id="@+id/next"
+                 style="@style/appwidget_button"
+                 android:layout_weight="1"
+                 android:src="@drawable/appwidget_next" />
+  </LinearLayout>
+</LinearLayout>
diff --git a/android/res/layout/appwidget_4x1.xml b/android/res/layout/appwidget_4x1.xml
new file mode 100644
index 0000000..0d2d6df
--- /dev/null
+++ b/android/res/layout/appwidget_4x1.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="fill_parent"
+              android:layout_height="fill_parent"
+              android:orientation="horizontal"
+              android:gravity="center"
+              android:background="@drawable/appwidget_background">
+              <!-- style="@style/appwidget_background"-->
+
+  <Button android:id="@+id/infoDisplay"
+          style="@style/appwidget_infodisplay"
+          android:layout_height="fill_parent"
+          android:text="@string/appwidget_infoDisplay" />
+  <ImageButton android:id="@+id/prev"
+               style="@style/appwidget_button"
+               android:layout_height="fill_parent"
+               android:src="@drawable/appwidget_prev" />
+  <ImageButton android:id="@+id/stop"
+               style="@style/appwidget_button"
+               android:layout_height="fill_parent"
+               android:src="@drawable/appwidget_stop" />
+  <ImageButton android:id="@+id/playPause"
+               style="@style/appwidget_button"
+               android:layout_height="fill_parent"
+               android:src="@drawable/appwidget_play" />
+  <ImageButton android:id="@+id/next"
+               style="@style/appwidget_button"
+               android:layout_height="fill_parent"
+               android:src="@drawable/appwidget_next" />
+</LinearLayout>
diff --git a/android/res/layout/appwidget_configure.xml b/android/res/layout/appwidget_configure.xml
new file mode 100644
index 0000000..8dd0ff2
--- /dev/null
+++ b/android/res/layout/appwidget_configure.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="fill_parent"
+              android:layout_height="wrap_content"
+              android:orientation="vertical">
+
+  <ImageView android:id="@+id/logo"
+             android:layout_width="fill_parent"
+             android:layout_height="wrap_content"
+             android:scaleType="centerInside"
+             android:src="@drawable/rockbox" />
+
+  <TextView android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/appwidget_configure_instructions"/>
+
+  <CheckBox android:id="@+id/enable_prev"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/appwidget_configure_prev"/>
+  <CheckBox android:id="@+id/enable_stop"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/appwidget_configure_stop"/>
+  <CheckBox android:id="@+id/enable_playpause"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/appwidget_configure_playpause"/>
+  <CheckBox android:id="@+id/enable_next"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/appwidget_configure_next"/>
+
+  <Button android:id="@+id/confirm"
+          android:layout_width="fill_parent"
+          android:layout_height="wrap_content"
+          android:text="@string/appwidget_configure_confirm"/>
+</LinearLayout>
+
diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml
index 8034eab..5ff2a6d 100644
--- a/android/res/values/strings.xml
+++ b/android/res/values/strings.xml
@@ -10,4 +10,18 @@
 <string name="No">No</string>
 <string name="error_extraction">Error occured during extraction!</string>
 <string name="rockbox_extracting">Rockbox is loading. Please wait...</string>
-</resources>
\ No newline at end of file
+
+<!-- Widget -->
+<string name="appwidget_label_4x1">Rockbox (line)</string>
+<string name="appwidget_label_2x2">Rockbox (square)</string>
+<string name="appwidget_infoDisplay">Touch to launch app</string>
+
+<!-- Widget configuration -->
+<string name="appwidget_configure_instructions">Please chose elements to display in widget.</string>
+<string name="appwidget_configure_prev">Prev Button</string>
+<string name="appwidget_configure_stop">Stop Button</string>
+<string name="appwidget_configure_playpause">Play/Pause Button</string>
+<string name="appwidget_configure_next">Next Button</string>
+<string name="appwidget_configure_confirm">Create Widget</string>
+
+</resources>
diff --git a/android/res/values/style.xml b/android/res/values/style.xml
new file mode 100644
index 0000000..201b478
--- /dev/null
+++ b/android/res/values/style.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+  <style name="appwidget_infodisplay">
+    <item name="android:layout_width">1dp</item>
+    <item name="android:layout_height">1dp</item>
+    <item name="android:layout_weight">1</item>
+    <item name="android:textColor">#cecfce</item>
+    <item name="android:background">@drawable/appwidget_infodisplay_background</item>
+  </style>
+  <style name="appwidget_button">
+    <item name="android:layout_width">40dp</item>
+    <item name="android:layout_height">40dp</item>
+    <item name="android:scaleType">fitCenter</item>
+    <item name="android:background">@drawable/appwidget_infodisplay_background</item>
+  </style>
+</resources>
+
diff --git a/android/res/xml/appwidget_provider_2x2.xml b/android/res/xml/appwidget_provider_2x2.xml
new file mode 100644
index 0000000..981b4f1
--- /dev/null
+++ b/android/res/xml/appwidget_provider_2x2.xml
@@ -0,0 +1,8 @@
+<!-- cell size is (number of cells * 74) - 2 dp according to http://developer.android.com/guide/topics/appwidgets/index.html#MetaData -->
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+  android:minWidth="146dp"
+  android:minHeight="146dp"
+  android:updatePeriodMillis="86400000"
+  android:initialLayout="@layout/appwidget_2x2"
+  android:configure="org.rockbox.widgets.RockboxWidgetConfigure">
+</appwidget-provider>
diff --git a/android/res/xml/appwidget_provider_4x1.xml b/android/res/xml/appwidget_provider_4x1.xml
new file mode 100644
index 0000000..9a29901
--- /dev/null
+++ b/android/res/xml/appwidget_provider_4x1.xml
@@ -0,0 +1,8 @@
+<!-- cell size is (number of cells * 74) - 2 dp according to http://developer.android.com/guide/topics/appwidgets/index.html#MetaData -->
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+  android:minWidth="294dp"
+  android:minHeight="72dp"
+  android:updatePeriodMillis="86400000"
+  android:initialLayout="@layout/appwidget_4x1"
+  android:configure="org.rockbox.widgets.RockboxWidgetConfigure">
+</appwidget-provider>
diff --git a/android/src/org/rockbox/Helper/RunForegroundManager.java b/android/src/org/rockbox/Helper/RunForegroundManager.java
index 6c5f2de..99ac66b 100644
--- a/android/src/org/rockbox/Helper/RunForegroundManager.java
+++ b/android/src/org/rockbox/Helper/RunForegroundManager.java
@@ -11,6 +11,7 @@
 import android.app.PendingIntent;
 import android.app.Service;
 import android.content.Intent;
+import android.net.Uri;
 import android.util.Log;
 import android.widget.RemoteViews;
 
@@ -92,6 +93,19 @@
         else
             mNotification.tickerText = title+" - "+artist;
         mNM.notify(R.string.notification, mNotification);
+
+        Intent widgetUpdate = new Intent("org.rockbox.TrackUpdateInfo");
+        widgetUpdate.putExtra("title", title);
+        widgetUpdate.putExtra("artist", artist);
+        widgetUpdate.putExtra("album", album);
+        mCurrentService.sendBroadcast(widgetUpdate);
+    }
+
+    public void finishNotification()
+    {
+        Log.d("Rockbox", "TrackFinish");
+        Intent widgetUpdate = new Intent("org.rockbox.TrackFinish");
+        mCurrentService.sendBroadcast(widgetUpdate);
     }
 
     private interface IRunForeground 
diff --git a/android/src/org/rockbox/RockboxFramebuffer.java b/android/src/org/rockbox/RockboxFramebuffer.java
index 1841556..05d2b11 100644
--- a/android/src/org/rockbox/RockboxFramebuffer.java
+++ b/android/src/org/rockbox/RockboxFramebuffer.java
@@ -147,5 +147,5 @@
     private native void post_update_done();
     private native void set_lcd_active(int active);
     private native void touchHandler(boolean down, int x, int y);
-    private native boolean buttonHandler(int keycode, boolean state);
+    private native static boolean buttonHandler(int keycode, boolean state);
 }
diff --git a/android/src/org/rockbox/RockboxPCM.java b/android/src/org/rockbox/RockboxPCM.java
index 146e639..c1fecbc 100644
--- a/android/src/org/rockbox/RockboxPCM.java
+++ b/android/src/org/rockbox/RockboxPCM.java
@@ -23,9 +23,11 @@
 
 import java.util.Arrays;
 
+import android.content.Intent;
 import android.media.AudioFormat;
 import android.media.AudioManager;
 import android.media.AudioTrack;
+import android.net.Uri;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Process;
@@ -80,10 +82,16 @@
     private void play_pause(boolean pause) {
         if (pause)
         {
+            Intent widgetUpdate = new Intent("org.rockbox.UpdateState");
+            widgetUpdate.putExtra("state", "pause");
+            RockboxService.get_instance().sendBroadcast(widgetUpdate);
             pause();
         }
         else
         {
+            Intent widgetUpdate = new Intent("org.rockbox.UpdateState");
+            widgetUpdate.putExtra("state", "play");
+            RockboxService.get_instance().sendBroadcast(widgetUpdate);
             if (getPlayState() == AudioTrack.PLAYSTATE_STOPPED)
             {
                 RockboxService.get_instance().startForeground();
@@ -114,6 +122,9 @@
             throw new IllegalStateException(e);
         }
         RockboxService.get_instance().stopForeground();
+        Intent widgetUpdate = new Intent("org.rockbox.UpdateState");
+        widgetUpdate.putExtra("state", "stop");
+        RockboxService.get_instance().sendBroadcast(widgetUpdate);
     }
 
     @SuppressWarnings("unused")
diff --git a/android/src/org/rockbox/RockboxService.java b/android/src/org/rockbox/RockboxService.java
index 23086ea..de90999 100644
--- a/android/src/org/rockbox/RockboxService.java
+++ b/android/src/org/rockbox/RockboxService.java
@@ -43,6 +43,7 @@
 import android.os.IBinder;
 import android.os.ResultReceiver;
 import android.util.Log;
+import android.view.KeyEvent;
 
 /* This class is used as the main glue between java and c.
  * All access should be done through RockboxService.get_instance() for safety.
@@ -76,7 +77,7 @@
     @Override
     public void onCreate()
     {
-   		instance = this;
+        instance = this;
     }
     
     public static RockboxService get_instance()
@@ -115,6 +116,31 @@
         if (!rbLibLoaded)
             startservice();
         
+        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);
+            }
+            else if (intent.getAction().equals("org.rockbox.Prev"))
+            {
+                if (fb != null)
+                    fb.onKeyUp(KeyEvent.KEYCODE_MEDIA_PREVIOUS, null);
+            }
+            else if (intent.getAction().equals("org.rockbox.Next"))
+            {
+                if (fb != null)
+                    fb.onKeyUp(KeyEvent.KEYCODE_MEDIA_NEXT, null);
+            }
+            else if (intent.getAction().equals("org.rockbox.Stop"))
+            {
+                if (fb != null)
+                    fb.onKeyUp(KeyEvent.KEYCODE_MEDIA_STOP, null);
+            }
+        }
+
         /* Display a notification about us starting.  
          * We put an icon in the status bar. */
         if (fg_runner == null)
diff --git a/android/src/org/rockbox/widgets/RockboxWidgetConfigure.java b/android/src/org/rockbox/widgets/RockboxWidgetConfigure.java
new file mode 100644
index 0000000..82adb97
--- /dev/null
+++ b/android/src/org/rockbox/widgets/RockboxWidgetConfigure.java
@@ -0,0 +1,121 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2011 Antoine Cellerier <dionoea at videolan dot org>
+ *
+ * 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.widgets;
+
+import org.rockbox.R;
+
+import android.app.Activity;
+import android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.widget.CheckBox;
+
+
+public class RockboxWidgetConfigure extends Activity
+{
+    int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
+
+    public RockboxWidgetConfigure()
+    {
+        super();
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState)
+    {
+        super.onCreate(savedInstanceState);
+
+        setResult(RESULT_CANCELED);
+        setContentView(R.layout.appwidget_configure);
+
+        ((CheckBox)findViewById(R.id.enable_prev)).setChecked(false);
+        ((CheckBox)findViewById(R.id.enable_stop)).setChecked(true);
+        ((CheckBox)findViewById(R.id.enable_playpause)).setChecked(true);
+        ((CheckBox)findViewById(R.id.enable_next)).setChecked(false);
+
+        findViewById(R.id.confirm).setOnClickListener(mCreateWidget);
+
+        Intent intent = getIntent();
+        Bundle extras = intent.getExtras();
+        if (extras != null)
+            mAppWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
+
+        if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID)
+            finish();
+    }
+
+    View.OnClickListener mCreateWidget = new View.OnClickListener()
+    {
+        public void onClick(View v)
+        {
+            final Context context = RockboxWidgetConfigure.this;
+
+            WidgetPref state = new WidgetPref();
+            state.enablePrev = ((CheckBox)findViewById(R.id.enable_prev)).isChecked();
+            state.enableStop = ((CheckBox)findViewById(R.id.enable_stop)).isChecked();
+            state.enablePlayPause = ((CheckBox)findViewById(R.id.enable_playpause)).isChecked();
+            state.enableNext = ((CheckBox)findViewById(R.id.enable_next)).isChecked();
+            saveWidgetPref(context, mAppWidgetId, state);
+
+            AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+            RockboxWidgetProvider.updateAppWidget(context, appWidgetManager, mAppWidgetId, null);
+
+            Intent result = new Intent();
+            result.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
+            setResult(RESULT_OK, result);
+            finish();
+        }
+    };
+
+    static public class WidgetPref
+    {
+        public boolean enablePrev = true;
+        public boolean enableStop = true;
+        public boolean enablePlayPause = true;
+        public boolean enableNext = true;
+    }
+
+    static void saveWidgetPref(Context context, int appWidgetId, WidgetPref state)
+    {
+        SharedPreferences.Editor prefs = context.getSharedPreferences("org.rockbox.RockboxWidgetConfigure", 0).edit();
+        prefs.putBoolean("prev"+appWidgetId, state.enablePrev);
+        prefs.putBoolean("stop"+appWidgetId, state.enableStop);
+        prefs.putBoolean("playpause"+appWidgetId, state.enablePlayPause);
+        prefs.putBoolean("next"+appWidgetId, state.enableNext);
+        prefs.commit();
+    }
+
+    static WidgetPref loadWidgetPref(Context context, int appWidgetId)
+    {
+        SharedPreferences prefs = context.getSharedPreferences("org.rockbox.RockboxWidgetConfigure", 0);
+        WidgetPref state = new WidgetPref();
+        state.enablePrev = prefs.getBoolean("prev"+appWidgetId, true);
+        state.enableStop = prefs.getBoolean("stop"+appWidgetId, true);
+        state.enablePlayPause = prefs.getBoolean("playpause"+appWidgetId, true);
+        state.enableNext = prefs.getBoolean("next"+appWidgetId, true);
+        return state;
+    }
+}
diff --git a/android/src/org/rockbox/widgets/RockboxWidgetProvider.java b/android/src/org/rockbox/widgets/RockboxWidgetProvider.java
new file mode 100644
index 0000000..756d9f9
--- /dev/null
+++ b/android/src/org/rockbox/widgets/RockboxWidgetProvider.java
@@ -0,0 +1,171 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2011 Antoine Cellerier <dionoea at videolan dot org>
+ *
+ * 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.widgets;
+
+import org.rockbox.R;
+import org.rockbox.RockboxActivity;
+import org.rockbox.RockboxService;
+
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.util.Log;
+import android.view.View;
+import android.view.KeyEvent;
+import android.widget.RemoteViews;
+
+import java.util.ArrayList;
+
+public class RockboxWidgetProvider extends AppWidgetProvider
+{
+    @Override
+    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
+    {
+        final int N = appWidgetIds.length;
+        for (int i = 0; i < N; i++)
+        {
+            int appWidgetId = appWidgetIds[i];
+            updateAppWidget(context, appWidgetManager, appWidgetId, null);
+
+        }
+    }
+
+    @Override
+    public void onDeleted(Context context, int[] appWidgetIds)
+    {
+    }
+
+    @Override
+    public void onEnabled(Context context)
+    {
+    }
+
+    @Override
+    public void onDisabled(Context context)
+    {
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent)
+    {
+        String action = intent.getAction();
+        if (intent.getAction().equals("org.rockbox.TrackUpdateInfo") ||
+            intent.getAction().equals("org.rockbox.TrackFinish") ||
+            intent.getAction().equals("org.rockbox.UpdateState"))
+        {
+            AppWidgetManager gm = AppWidgetManager.getInstance(context);
+            int[] appWidgetIds = gm.getAppWidgetIds(new ComponentName(context, this.getClass()));
+            final int N = appWidgetIds.length;
+            for (int i = 0; i < N; i++)
+            {
+                updateAppWidget(context, gm, appWidgetIds[i], intent);
+            }
+        }
+        else
+        {
+            super.onReceive(context, intent);
+        }
+    }
+
+     public static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Intent args)
+     {
+        AppWidgetProviderInfo provider = appWidgetManager.getAppWidgetInfo(appWidgetId);
+        RemoteViews views = null;
+        views = new RemoteViews(context.getPackageName(), provider.initialLayout);
+
+        Intent intent = new Intent(context, RockboxActivity.class);
+        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+        views.setOnClickPendingIntent(R.id.infoDisplay, pendingIntent);
+
+        RockboxWidgetConfigure.WidgetPref state = RockboxWidgetConfigure.loadWidgetPref(context, appWidgetId);
+
+        if (state.enablePrev)
+        {
+            intent = new Intent("org.rockbox.Prev", Uri.EMPTY, context, RockboxService.class);
+            pendingIntent = PendingIntent.getService(context, 0, intent, 0);
+            views.setOnClickPendingIntent(R.id.prev, pendingIntent);
+        }
+        else
+            views.setViewVisibility(R.id.prev, View.GONE);
+
+        if (state.enablePlayPause)
+        {
+            intent = new Intent("org.rockbox.PlayPause", Uri.EMPTY, context, RockboxService.class);
+            pendingIntent = PendingIntent.getService(context, 0, intent, 0);
+            views.setOnClickPendingIntent(R.id.playPause, pendingIntent);
+        }
+        else
+            views.setViewVisibility(R.id.playPause, View.GONE);
+
+        if (state.enableNext)
+        {
+            intent = new Intent("org.rockbox.Next", Uri.EMPTY, context, RockboxService.class);
+            pendingIntent = PendingIntent.getService(context, 0, intent, 0);
+            views.setOnClickPendingIntent(R.id.next, pendingIntent);
+        }
+        else
+            views.setViewVisibility(R.id.next, View.GONE);
+
+        if (state.enableStop)
+        {
+            intent = new Intent("org.rockbox.Stop", Uri.EMPTY, context, RockboxService.class);
+            pendingIntent = PendingIntent.getService(context, 0, intent, 0);
+            views.setOnClickPendingIntent(R.id.stop, pendingIntent);
+        }
+        else
+            views.setViewVisibility(R.id.stop, View.GONE);
+
+        if (args != null)
+        {
+            if (args.getAction().equals("org.rockbox.TrackUpdateInfo"))
+            {
+                CharSequence title = args.getCharSequenceExtra("title");
+                CharSequence artist = args.getCharSequenceExtra("artist");
+                CharSequence album = args.getCharSequenceExtra("album");
+                views.setTextViewText(R.id.infoDisplay, title+"\n"+artist+"\n"+album);
+            }
+            else if (args.getAction().equals("org.rockbox.TrackFinish"))
+            {
+                // FIXME: looks like this event is always fired earlier than
+                // the actual track change (a few seconds)
+                views.setTextViewText(R.id.infoDisplay, context.getString(R.string.appwidget_infoDisplay));
+            }
+            else if (args.getAction().equals("org.rockbox.UpdateState"))
+            {
+                CharSequence playstate = args.getCharSequenceExtra("state");
+                if (playstate.equals("play"))
+                    views.setImageViewResource(R.id.playPause, R.drawable.appwidget_pause);
+                else /* pause or stop */
+                    views.setImageViewResource(R.id.playPause, R.drawable.appwidget_play);
+            }
+        }
+
+        appWidgetManager.updateAppWidget(appWidgetId, views);
+     }
+}
+
diff --git a/android/src/org/rockbox/widgets/RockboxWidgetProvider2x2.java b/android/src/org/rockbox/widgets/RockboxWidgetProvider2x2.java
new file mode 100644
index 0000000..183c427
--- /dev/null
+++ b/android/src/org/rockbox/widgets/RockboxWidgetProvider2x2.java
@@ -0,0 +1,27 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2011 Antoine Cellerier <dionoea at videolan dot org>
+ *
+ * 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.widgets;
+
+public class RockboxWidgetProvider2x2 extends RockboxWidgetProvider
+{
+}
+
diff --git a/android/src/org/rockbox/widgets/RockboxWidgetProvider4x1.java b/android/src/org/rockbox/widgets/RockboxWidgetProvider4x1.java
new file mode 100644
index 0000000..303c54c
--- /dev/null
+++ b/android/src/org/rockbox/widgets/RockboxWidgetProvider4x1.java
@@ -0,0 +1,27 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2011 Antoine Cellerier <dionoea at videolan dot org>
+ *
+ * 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.widgets;
+
+public class RockboxWidgetProvider4x1 extends RockboxWidgetProvider
+{
+}
+
diff --git a/apps/hosted/notification.c b/apps/hosted/notification.c
index 3c623aa..fa8c286 100644
--- a/apps/hosted/notification.c
+++ b/apps/hosted/notification.c
@@ -30,7 +30,7 @@
 extern jclass RockboxService_class;
 extern jobject RockboxService_instance;
 
-static jmethodID updateNotification;
+static jmethodID updateNotification, finishNotification;
 static jobject NotificationManager_instance;
 static jstring title, artist, album;
 
@@ -66,6 +66,16 @@
     }
 }
 
+/*
+ * notify about track finishing */
+static void track_finished_callback(void *param)
+{
+    (void)param;
+    JNIEnv e = *env_ptr;
+    e->CallVoidMethod(env_ptr, NotificationManager_instance,
+                      finishNotification);
+}
+
 void notification_init(void)
 {
     JNIEnv e = *env_ptr;
@@ -79,6 +89,9 @@
                                          "(Ljava/lang/String;"
                                          "Ljava/lang/String;"
                                          "Ljava/lang/String;)V");
+    finishNotification = e->GetMethodID(env_ptr, class, "finishNotification",
+                                        "()V");
 
     add_event(PLAYBACK_EVENT_TRACK_CHANGE, false, track_changed_callback);
+    add_event(PLAYBACK_EVENT_TRACK_FINISH, false, track_finished_callback);
 }
diff --git a/firmware/target/hosted/android/button-android.c b/firmware/target/hosted/android/button-android.c
index 9bfc7a3..832eef5 100644
--- a/firmware/target/hosted/android/button-android.c
+++ b/firmware/target/hosted/android/button-android.c
@@ -61,7 +61,7 @@
  * 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, jobject this,
+Java_org_rockbox_RockboxFramebuffer_buttonHandler(JNIEnv*env, jclass this,
                                                   jint keycode, jboolean state)
 {
     (void)env;