auto import from //depot/cupcake/@135843
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..93feb9d
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,14 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := user development
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+	src/com/android/music/IMediaPlaybackService.aidl
+
+LOCAL_PACKAGE_NAME := Music
+
+include $(BUILD_PACKAGE)
+
+# Use the folloing include to make our test apk.
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 0000000..a445f16
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,227 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.music">
+    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+
+    <application android:icon="@drawable/app_music"
+        android:label="@string/musicbrowserlabel"
+        android:taskAffinity="android.task.music"
+        android:allowTaskReparenting="true">
+        <activity android:name="MusicBrowserActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+             <meta-data
+                android:name="android.app.default_searchable"
+                android:value=".QueryBrowserActivity"
+             /> 
+       </activity>
+        <receiver android:name="MediaButtonIntentReceiver">
+            <intent-filter>
+                <action android:name="android.intent.action.MEDIA_BUTTON" />
+                <action android:name="android.media.AUDIO_BECOMING_NOISY" />
+            </intent-filter>
+        </receiver>
+        <!-- This is the "current music playing" panel, which has special
+             launch behavior.  We clear its task affinity, so it will not
+             be associated with the main media task and if launched
+             from a notification will not bring the rest of the media app
+             to the foreground.  We make it singleTask so that when others
+             launch it (such as media) we will launch in to our own task.
+             We set clearTaskOnLaunch because the user
+             can go to a playlist from this activity, so if they later return
+             to it we want it back in its initial state.  We exclude from
+             recents since this is accessible through a notification when
+             appropriate. -->
+        <activity android:name="MediaPlaybackActivity"
+                android:theme="@android:style/Theme.NoTitleBar"
+                android:label="@string/mediaplaybacklabel"
+                android:taskAffinity=""
+                android:launchMode="singleTask"
+                android:clearTaskOnLaunch="true"
+                android:excludeFromRecents="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="content"/>
+                <data android:scheme="file"/>
+                <data android:mimeType="audio/*"/>
+                <data android:mimeType="application/ogg"/>
+                <data android:mimeType="application/x-ogg"/>
+            </intent-filter>
+            <intent-filter>
+                <action android:name="com.android.music.PLAYBACK_VIEWER" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+        <activity android:name="StreamStarter" android:theme="@android:style/Theme.Dialog" >
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data android:scheme="http" />
+                <data android:mimeType="audio/mp3"/>
+                <data android:mimeType="audio/x-mp3"/>
+                <data android:mimeType="audio/mpeg"/>
+                <data android:mimeType="audio/mp4"/>
+                <data android:mimeType="audio/mp4a-latm"/>
+            </intent-filter>
+        </activity>
+        <activity android:name="ArtistAlbumBrowserActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.PICK" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="vnd.android.cursor.dir/artistalbum"/>
+            </intent-filter>
+        </activity>
+        <activity android:name="AlbumBrowserActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.PICK" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="vnd.android.cursor.dir/album"/>
+            </intent-filter>
+        </activity>
+        <activity android:name="NowPlayingActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.PICK" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="vnd.android.cursor.dir/nowplaying"/>
+            </intent-filter>
+        </activity>
+        <activity android:name="TrackBrowserActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.EDIT" />
+                <action android:name="android.intent.action.PICK" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="vnd.android.cursor.dir/track"/>
+            </intent-filter>
+        </activity>
+        <activity android:name="QueryBrowserActivity"
+                android:theme="@android:style/Theme.NoTitleBar">
+            <intent-filter>
+                <action android:name="android.intent.action.SEARCH" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <meta-data
+                android:name="android.app.searchable"
+                android:resource="@xml/searchable"
+            />
+        </activity>
+        <activity android:name="PlaylistBrowserActivity" android:label="@string/musicbrowserlabel">
+            <intent-filter>
+                <action android:name="android.intent.action.PICK" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="vnd.android.cursor.dir/playlist"/>
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="vnd.android.cursor.dir/playlist"/>
+            </intent-filter>
+        </activity>
+        <activity-alias android:name="PlaylistShortcutActivity"
+            android:targetActivity="PlaylistBrowserActivity"
+            android:label="@string/musicshortcutlabel">
+
+            <intent-filter>
+                <action android:name="android.intent.action.CREATE_SHORTCUT" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+
+        </activity-alias>
+        <activity android:name="VideoBrowserActivity"
+            android:taskAffinity="android.task.video"
+            android:label="@string/videobrowserlabel"
+            android:icon="@drawable/app_video">
+            <intent-filter>
+                <action android:name="android.intent.action.PICK" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="vnd.android.cursor.dir/video"/>
+            </intent-filter>
+<!--
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+-->
+        </activity>
+        <activity android:name="MediaPickerActivity" android:label="@string/mediapickerlabel">
+<!--
+            <intent-filter>
+                <action android:name="android.intent.action.PICK" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="media/*"/>
+                <data android:mimeType="audio/*"/>
+                <data android:mimeType="application/ogg"/>
+                <data android:mimeType="application/x-ogg"/>
+                <data android:mimeType="video/*"/>
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.GET_CONTENT" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.OPENABLE" />
+                <data android:mimeType="media/*"/>
+                <data android:mimeType="audio/*"/>
+                <data android:mimeType="application/ogg"/>
+                <data android:mimeType="application/x-ogg"/>
+                <data android:mimeType="video/*"/>
+            </intent-filter>
+-->
+        </activity>
+        <activity android:name="MusicPicker" android:label="@string/music_picker_title">
+            <!-- First way to invoke us: someone asks to get content of
+                 any of the audio types we support. -->
+            <intent-filter>
+                <action android:name="android.intent.action.GET_CONTENT" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.OPENABLE" />
+                <data android:mimeType="audio/*"/>
+                <data android:mimeType="application/ogg"/>
+                <data android:mimeType="application/x-ogg"/>
+            </intent-filter>
+            <!-- Second way to invoke us: someone asks to pick an item from
+                 some media Uri. -->
+            <intent-filter>
+                <action android:name="android.intent.action.PICK" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.OPENABLE" />
+                <data android:mimeType="vnd.android.cursor.dir/audio"/>
+            </intent-filter>
+        </activity>
+        <activity android:name="CreatePlaylist" android:theme="@android:style/Theme.Dialog" />
+        <activity android:name="RenamePlaylist" android:theme="@android:style/Theme.Dialog" />
+        <activity android:name="WeekSelector" android:theme="@android:style/Theme.Dialog" />
+        <activity android:name="DeleteItems" android:theme="@android:style/Theme.Dialog" />
+        <activity android:name="ScanningProgress" android:theme="@android:style/Theme.Dialog" />
+        <service android:name="MediaPlaybackService" android:exported="true" />
+
+        <receiver android:name="MediaGadgetProvider">
+            <intent-filter>
+                <action android:name="android.gadget.action.GADGET_UPDATE" />
+            </intent-filter>
+            <meta-data android:name="android.gadget.provider" android:resource="@xml/gadget_info" />
+        </receiver>
+    </application>
+</manifest>
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2005-2008, The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/res/drawable-finger/album_border_large.9.png b/res/drawable-finger/album_border_large.9.png
new file mode 100644
index 0000000..e5ffbc7
--- /dev/null
+++ b/res/drawable-finger/album_border_large.9.png
Binary files differ
diff --git a/res/drawable-finger/albumart_mp_unknown.png b/res/drawable-finger/albumart_mp_unknown.png
new file mode 100755
index 0000000..f014a45
--- /dev/null
+++ b/res/drawable-finger/albumart_mp_unknown.png
Binary files differ
diff --git a/res/drawable-finger/albumart_mp_unknown_list.png b/res/drawable-finger/albumart_mp_unknown_list.png
new file mode 100755
index 0000000..ac11762
--- /dev/null
+++ b/res/drawable-finger/albumart_mp_unknown_list.png
Binary files differ
diff --git a/res/drawable-finger/btn_music_highlight.9.png b/res/drawable-finger/btn_music_highlight.9.png
new file mode 100644
index 0000000..0e18a9e
--- /dev/null
+++ b/res/drawable-finger/btn_music_highlight.9.png
Binary files differ
diff --git a/res/drawable-finger/btn_music_normal.9.png b/res/drawable-finger/btn_music_normal.9.png
new file mode 100644
index 0000000..10d2a96
--- /dev/null
+++ b/res/drawable-finger/btn_music_normal.9.png
Binary files differ
diff --git a/res/drawable-finger/btn_music_pressed.9.png b/res/drawable-finger/btn_music_pressed.9.png
new file mode 100644
index 0000000..ef2caa6
--- /dev/null
+++ b/res/drawable-finger/btn_music_pressed.9.png
Binary files differ
diff --git a/res/drawable-finger/gadget_bg.9.png b/res/drawable-finger/gadget_bg.9.png
new file mode 100644
index 0000000..8930c08
--- /dev/null
+++ b/res/drawable-finger/gadget_bg.9.png
Binary files differ
diff --git a/res/drawable-finger/gadget_inner.9.png b/res/drawable-finger/gadget_inner.9.png
new file mode 100644
index 0000000..157afcf
--- /dev/null
+++ b/res/drawable-finger/gadget_inner.9.png
Binary files differ
diff --git a/res/drawable-finger/gadget_next.xml b/res/drawable-finger/gadget_next.xml
new file mode 100644
index 0000000..7066e1d
--- /dev/null
+++ b/res/drawable-finger/gadget_next.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_window_focused="false"
+        android:drawable="@drawable/gadget_next_normal" />
+    <item android:state_pressed="true"
+        android:drawable="@drawable/gadget_next_pressed" />
+    <item android:state_focused="true"
+        android:drawable="@drawable/gadget_next_pressed" />
+    <item
+         android:drawable="@drawable/gadget_next_normal" />
+</selector>
diff --git a/res/drawable-finger/gadget_next_normal.png b/res/drawable-finger/gadget_next_normal.png
new file mode 100644
index 0000000..072499e
--- /dev/null
+++ b/res/drawable-finger/gadget_next_normal.png
Binary files differ
diff --git a/res/drawable-finger/gadget_next_pressed.png b/res/drawable-finger/gadget_next_pressed.png
new file mode 100644
index 0000000..e8ae5d9
--- /dev/null
+++ b/res/drawable-finger/gadget_next_pressed.png
Binary files differ
diff --git a/res/drawable-finger/gadget_pause.xml b/res/drawable-finger/gadget_pause.xml
new file mode 100644
index 0000000..42d47cb
--- /dev/null
+++ b/res/drawable-finger/gadget_pause.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_window_focused="false"
+        android:drawable="@drawable/gadget_pause_normal" />
+    <item android:state_pressed="true"
+        android:drawable="@drawable/gadget_pause_pressed" />
+    <item android:state_focused="true"
+        android:drawable="@drawable/gadget_pause_pressed" />
+    <item
+         android:drawable="@drawable/gadget_pause_normal" />
+</selector>
diff --git a/res/drawable-finger/gadget_pause_normal.png b/res/drawable-finger/gadget_pause_normal.png
new file mode 100644
index 0000000..a40e247
--- /dev/null
+++ b/res/drawable-finger/gadget_pause_normal.png
Binary files differ
diff --git a/res/drawable-finger/gadget_pause_pressed.png b/res/drawable-finger/gadget_pause_pressed.png
new file mode 100644
index 0000000..2a5116c
--- /dev/null
+++ b/res/drawable-finger/gadget_pause_pressed.png
Binary files differ
diff --git a/res/drawable-finger/gadget_play.xml b/res/drawable-finger/gadget_play.xml
new file mode 100644
index 0000000..4b330e9
--- /dev/null
+++ b/res/drawable-finger/gadget_play.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_window_focused="false"
+        android:drawable="@drawable/gadget_play_normal" />
+    <item android:state_pressed="true"
+        android:drawable="@drawable/gadget_play_pressed" />
+    <item android:state_focused="true"
+        android:drawable="@drawable/gadget_play_pressed" />
+    <item
+         android:drawable="@drawable/gadget_play_normal" />
+</selector>
diff --git a/res/drawable-finger/gadget_play_normal.png b/res/drawable-finger/gadget_play_normal.png
new file mode 100644
index 0000000..f812874
--- /dev/null
+++ b/res/drawable-finger/gadget_play_normal.png
Binary files differ
diff --git a/res/drawable-finger/gadget_play_pressed.png b/res/drawable-finger/gadget_play_pressed.png
new file mode 100644
index 0000000..747b5ae
--- /dev/null
+++ b/res/drawable-finger/gadget_play_pressed.png
Binary files differ
diff --git a/res/drawable-finger/ic_menu_delete.png b/res/drawable-finger/ic_menu_delete.png
new file mode 100755
index 0000000..82f9d56
--- /dev/null
+++ b/res/drawable-finger/ic_menu_delete.png
Binary files differ
diff --git a/res/drawable-finger/ic_menu_music_library.png b/res/drawable-finger/ic_menu_music_library.png
new file mode 100755
index 0000000..3f1de60
--- /dev/null
+++ b/res/drawable-finger/ic_menu_music_library.png
Binary files differ
diff --git a/res/drawable-finger/ic_menu_party_shuffle.png b/res/drawable-finger/ic_menu_party_shuffle.png
new file mode 100755
index 0000000..51c3d02
--- /dev/null
+++ b/res/drawable-finger/ic_menu_party_shuffle.png
Binary files differ
diff --git a/res/drawable-finger/ic_menu_playback.png b/res/drawable-finger/ic_menu_playback.png
new file mode 100755
index 0000000..22f203a
--- /dev/null
+++ b/res/drawable-finger/ic_menu_playback.png
Binary files differ
diff --git a/res/drawable-finger/ic_menu_set_as_ringtone.png b/res/drawable-finger/ic_menu_set_as_ringtone.png
new file mode 100755
index 0000000..d44d7bd
--- /dev/null
+++ b/res/drawable-finger/ic_menu_set_as_ringtone.png
Binary files differ
diff --git a/res/drawable-finger/ic_menu_shuffle.png b/res/drawable-finger/ic_menu_shuffle.png
new file mode 100755
index 0000000..cb7009d
--- /dev/null
+++ b/res/drawable-finger/ic_menu_shuffle.png
Binary files differ
diff --git a/res/drawable-finger/ic_mp_album_playback.png b/res/drawable-finger/ic_mp_album_playback.png
new file mode 100755
index 0000000..5084260
--- /dev/null
+++ b/res/drawable-finger/ic_mp_album_playback.png
Binary files differ
diff --git a/res/drawable-finger/ic_mp_artist_list.png b/res/drawable-finger/ic_mp_artist_list.png
new file mode 100644
index 0000000..08cd205
--- /dev/null
+++ b/res/drawable-finger/ic_mp_artist_list.png
Binary files differ
diff --git a/res/drawable-finger/ic_mp_artist_playback.png b/res/drawable-finger/ic_mp_artist_playback.png
new file mode 100755
index 0000000..401d7ce
--- /dev/null
+++ b/res/drawable-finger/ic_mp_artist_playback.png
Binary files differ
diff --git a/res/drawable-finger/ic_mp_current_playlist_btn.png b/res/drawable-finger/ic_mp_current_playlist_btn.png
new file mode 100755
index 0000000..35449b3
--- /dev/null
+++ b/res/drawable-finger/ic_mp_current_playlist_btn.png
Binary files differ
diff --git a/res/drawable-finger/ic_mp_move.png b/res/drawable-finger/ic_mp_move.png
new file mode 100755
index 0000000..9169ae5
--- /dev/null
+++ b/res/drawable-finger/ic_mp_move.png
Binary files differ
diff --git a/res/drawable-finger/ic_mp_partyshuffle_on_btn.png b/res/drawable-finger/ic_mp_partyshuffle_on_btn.png
new file mode 100755
index 0000000..18f09b1
--- /dev/null
+++ b/res/drawable-finger/ic_mp_partyshuffle_on_btn.png
Binary files differ
diff --git a/res/drawable-finger/ic_mp_playlist_list.png b/res/drawable-finger/ic_mp_playlist_list.png
new file mode 100755
index 0000000..1fba256
--- /dev/null
+++ b/res/drawable-finger/ic_mp_playlist_list.png
Binary files differ
diff --git a/res/drawable-finger/ic_mp_playlist_recently_added_list.png b/res/drawable-finger/ic_mp_playlist_recently_added_list.png
new file mode 100644
index 0000000..bc2cb79
--- /dev/null
+++ b/res/drawable-finger/ic_mp_playlist_recently_added_list.png
Binary files differ
diff --git a/res/drawable-finger/ic_mp_repeat_all_btn.png b/res/drawable-finger/ic_mp_repeat_all_btn.png
new file mode 100755
index 0000000..d1f1e60
--- /dev/null
+++ b/res/drawable-finger/ic_mp_repeat_all_btn.png
Binary files differ
diff --git a/res/drawable-finger/ic_mp_repeat_off_btn.png b/res/drawable-finger/ic_mp_repeat_off_btn.png
new file mode 100755
index 0000000..02fbd81
--- /dev/null
+++ b/res/drawable-finger/ic_mp_repeat_off_btn.png
Binary files differ
diff --git a/res/drawable-finger/ic_mp_repeat_once_btn.png b/res/drawable-finger/ic_mp_repeat_once_btn.png
new file mode 100755
index 0000000..549da31
--- /dev/null
+++ b/res/drawable-finger/ic_mp_repeat_once_btn.png
Binary files differ
diff --git a/res/drawable-finger/ic_mp_screen_albums.png b/res/drawable-finger/ic_mp_screen_albums.png
new file mode 100755
index 0000000..4804ac0
--- /dev/null
+++ b/res/drawable-finger/ic_mp_screen_albums.png
Binary files differ
diff --git a/res/drawable-finger/ic_mp_screen_artists.png b/res/drawable-finger/ic_mp_screen_artists.png
new file mode 100755
index 0000000..1803880
--- /dev/null
+++ b/res/drawable-finger/ic_mp_screen_artists.png
Binary files differ
diff --git a/res/drawable-finger/ic_mp_screen_playlists.png b/res/drawable-finger/ic_mp_screen_playlists.png
new file mode 100755
index 0000000..068c5d1
--- /dev/null
+++ b/res/drawable-finger/ic_mp_screen_playlists.png
Binary files differ
diff --git a/res/drawable-finger/ic_mp_screen_tracks.png b/res/drawable-finger/ic_mp_screen_tracks.png
new file mode 100755
index 0000000..f9c3836
--- /dev/null
+++ b/res/drawable-finger/ic_mp_screen_tracks.png
Binary files differ
diff --git a/res/drawable-finger/ic_mp_sd_card.png b/res/drawable-finger/ic_mp_sd_card.png
new file mode 100644
index 0000000..90e5081
--- /dev/null
+++ b/res/drawable-finger/ic_mp_sd_card.png
Binary files differ
diff --git a/res/drawable-finger/ic_mp_shuffle_off_btn.png b/res/drawable-finger/ic_mp_shuffle_off_btn.png
new file mode 100755
index 0000000..635fac0
--- /dev/null
+++ b/res/drawable-finger/ic_mp_shuffle_off_btn.png
Binary files differ
diff --git a/res/drawable-finger/ic_mp_shuffle_on_btn.png b/res/drawable-finger/ic_mp_shuffle_on_btn.png
new file mode 100755
index 0000000..92bd59d
--- /dev/null
+++ b/res/drawable-finger/ic_mp_shuffle_on_btn.png
Binary files differ
diff --git a/res/drawable-finger/ic_mp_song_list.png b/res/drawable-finger/ic_mp_song_list.png
new file mode 100644
index 0000000..804a2ee
--- /dev/null
+++ b/res/drawable-finger/ic_mp_song_list.png
Binary files differ
diff --git a/res/drawable-finger/ic_mp_song_playback.png b/res/drawable-finger/ic_mp_song_playback.png
new file mode 100755
index 0000000..4fe03a5
--- /dev/null
+++ b/res/drawable-finger/ic_mp_song_playback.png
Binary files differ
diff --git a/res/drawable-finger/ic_search_category_music_album.png b/res/drawable-finger/ic_search_category_music_album.png
new file mode 100755
index 0000000..069cb15
--- /dev/null
+++ b/res/drawable-finger/ic_search_category_music_album.png
Binary files differ
diff --git a/res/drawable-finger/ic_search_category_music_artist.png b/res/drawable-finger/ic_search_category_music_artist.png
new file mode 100755
index 0000000..78053d3
--- /dev/null
+++ b/res/drawable-finger/ic_search_category_music_artist.png
Binary files differ
diff --git a/res/drawable-finger/ic_search_category_music_song.png b/res/drawable-finger/ic_search_category_music_song.png
new file mode 100755
index 0000000..b3988c0
--- /dev/null
+++ b/res/drawable-finger/ic_search_category_music_song.png
Binary files differ
diff --git a/res/drawable-finger/ic_slide_keyboard.png b/res/drawable-finger/ic_slide_keyboard.png
new file mode 100755
index 0000000..38a7dbf
--- /dev/null
+++ b/res/drawable-finger/ic_slide_keyboard.png
Binary files differ
diff --git a/res/drawable-finger/indicator_ic_mp_playing_large.png b/res/drawable-finger/indicator_ic_mp_playing_large.png
new file mode 100644
index 0000000..c95888d
--- /dev/null
+++ b/res/drawable-finger/indicator_ic_mp_playing_large.png
Binary files differ
diff --git a/res/drawable-finger/indicator_ic_mp_playing_list.png b/res/drawable-finger/indicator_ic_mp_playing_list.png
new file mode 100755
index 0000000..f32e42c
--- /dev/null
+++ b/res/drawable-finger/indicator_ic_mp_playing_list.png
Binary files differ
diff --git a/res/drawable-finger/list_selector.xml b/res/drawable-finger/list_selector.xml
new file mode 100644
index 0000000..d8afcb6
--- /dev/null
+++ b/res/drawable-finger/list_selector.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true"
+        android:drawable="@android:color/transparent" />
+    <item android:state_pressed="true" android:state_selected="false"
+        android:drawable="@android:color/transparent" />
+    <item android:state_selected="false"
+        android:drawable="@color/expanding_child_background" />
+</selector>
diff --git a/res/drawable-finger/main_menu_button.xml b/res/drawable-finger/main_menu_button.xml
new file mode 100644
index 0000000..370bc59
--- /dev/null
+++ b/res/drawable-finger/main_menu_button.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/btn_music_pressed" />
+    <item android:state_focused="true" android:state_pressed="false" android:drawable="@drawable/btn_music_highlight" />
+    <item android:drawable="@drawable/btn_music_normal" />
+</selector>
+
diff --git a/res/drawable-finger/stat_notify_musicplayer.png b/res/drawable-finger/stat_notify_musicplayer.png
new file mode 100755
index 0000000..fd92c18
--- /dev/null
+++ b/res/drawable-finger/stat_notify_musicplayer.png
Binary files differ
diff --git a/res/drawable-land-finger/albumart_mp_unknown.png b/res/drawable-land-finger/albumart_mp_unknown.png
new file mode 100755
index 0000000..ed6c969
--- /dev/null
+++ b/res/drawable-land-finger/albumart_mp_unknown.png
Binary files differ
diff --git a/res/drawable-land-finger/btn_music_highlight.9.png b/res/drawable-land-finger/btn_music_highlight.9.png
new file mode 100644
index 0000000..0e5a270
--- /dev/null
+++ b/res/drawable-land-finger/btn_music_highlight.9.png
Binary files differ
diff --git a/res/drawable-land-finger/btn_music_normal.9.png b/res/drawable-land-finger/btn_music_normal.9.png
new file mode 100644
index 0000000..80e2263
--- /dev/null
+++ b/res/drawable-land-finger/btn_music_normal.9.png
Binary files differ
diff --git a/res/drawable-land-finger/btn_music_pressed.9.png b/res/drawable-land-finger/btn_music_pressed.9.png
new file mode 100644
index 0000000..3592781
--- /dev/null
+++ b/res/drawable-land-finger/btn_music_pressed.9.png
Binary files differ
diff --git a/res/drawable/app_music.png b/res/drawable/app_music.png
new file mode 100644
index 0000000..0353b91
--- /dev/null
+++ b/res/drawable/app_music.png
Binary files differ
diff --git a/res/drawable/app_video.png b/res/drawable/app_video.png
new file mode 100644
index 0000000..0c10731
--- /dev/null
+++ b/res/drawable/app_video.png
Binary files differ
diff --git a/res/drawable/midi.png b/res/drawable/midi.png
new file mode 100644
index 0000000..87f32ac
--- /dev/null
+++ b/res/drawable/midi.png
Binary files differ
diff --git a/res/drawable/movie.png b/res/drawable/movie.png
new file mode 100644
index 0000000..bed7c78
--- /dev/null
+++ b/res/drawable/movie.png
Binary files differ
diff --git a/res/layout-finger/audio_player.xml b/res/layout-finger/audio_player.xml
new file mode 100644
index 0000000..da11163
--- /dev/null
+++ b/res/layout-finger/audio_player.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1"
+        android:orientation="horizontal"
+        android:gravity="center">
+
+        <ImageView
+            android:id="@+id/album"
+            android:background="@drawable/album_border_large"
+            android:layout_width="220dip"
+            android:layout_height="220dip"
+            android:layout_marginLeft="4dip"
+            android:layout_marginRight="2dip"
+            android:layout_marginTop="8dip" />
+
+        <LinearLayout
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:orientation="vertical"
+            android:gravity="center_horizontal">
+
+           <ImageButton android:id="@+id/curplaylist"
+                android:src="@drawable/ic_mp_current_playlist_btn"
+                android:layout_width="85dip"
+                android:layout_height="54dip"
+                android:layout_marginTop="14dip" />
+
+           <ImageButton android:id="@+id/shuffle"
+                android:layout_width="85dip"
+                android:layout_height="54dip"
+                android:layout_marginTop="20dip" />
+
+           <ImageButton android:id="@+id/repeat"
+                android:layout_width="85dip"
+                android:layout_height="54dip"
+                android:layout_marginTop="20dip" />
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:baselineAligned="false"
+        android:paddingLeft="11dip"
+        android:paddingTop="4dip"
+        android:paddingBottom="8dip">
+
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginRight="4dip"
+            android:src="@drawable/ic_mp_artist_playback" />
+
+        <TextView android:id="@+id/artistname"
+            android:textSize="18sp"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:textStyle="bold"
+            android:layout_gravity="center_vertical"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:baselineAligned="false"
+        android:paddingLeft="11dip"
+        android:paddingTop="4dip"
+        android:paddingBottom="8dip">
+
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginRight="4dip"
+            android:src="@drawable/ic_mp_album_playback" />
+
+        <TextView android:id="@+id/albumname"
+            android:textSize="14sp"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:layout_gravity="center_vertical"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:baselineAligned="false"
+        android:paddingLeft="11dip"
+        android:paddingTop="0dip"
+        android:paddingBottom="8dip">
+
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginRight="4dip"
+            android:src="@drawable/ic_mp_song_playback" />
+
+        <TextView android:id="@+id/trackname"
+            android:textSize="14sp"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:layout_gravity="center_vertical"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content" />
+
+    </LinearLayout>
+
+    <include layout="@layout/audio_player_common" />
+
+</LinearLayout>
diff --git a/res/layout-finger/audio_player_common.xml b/res/layout-finger/audio_player_common.xml
new file mode 100644
index 0000000..95320d9
--- /dev/null
+++ b/res/layout-finger/audio_player_common.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <View 
+        android:layout_width="fill_parent"
+        android:layout_height="1px"
+        android:background="#ffffffff" />
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:background="#ff5a5a5a"
+        android:paddingTop="1px"
+        android:paddingBottom="4px"
+        android:orientation="horizontal">
+
+        <TextView android:id="@+id/currenttime"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textSize="14sp"
+            android:textStyle="bold"
+            android:shadowColor="#ff000000"
+            android:shadowDx="0"
+            android:shadowDy="0"
+            android:shadowRadius="3"
+            android:layout_gravity="bottom"
+            android:layout_weight="1"
+            android:layout_width="0dip"
+            android:paddingLeft="5px"
+            android:layout_height="wrap_content" />
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:layout_gravity="bottom"
+            android:layout_marginTop="1px"
+            android:layout_marginBottom="2px"
+            android:gravity="center">
+
+            <com.android.music.RepeatingImageButton android:id="@+id/prev" style="@android:style/MediaButton.Previous" />
+
+            <ImageButton android:id="@+id/pause" style="@android:style/MediaButton.Play" />
+
+            <com.android.music.RepeatingImageButton android:id="@+id/next" style="@android:style/MediaButton.Next" />
+
+        </LinearLayout>
+
+        <TextView android:id="@+id/totaltime"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textSize="14sp"
+            android:textStyle="bold"
+            android:shadowColor="#ff000000"
+            android:shadowDx="0"
+            android:shadowDy="0"
+            android:shadowRadius="3"
+            android:gravity="right"
+            android:paddingRight="5px"
+            android:layout_gravity="bottom"
+            android:layout_weight="1"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content" />
+
+    </LinearLayout>
+
+    <SeekBar android:id="@android:id/progress"
+        android:background="#ff5a5a5a"
+        style="?android:attr/progressBarStyleHorizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="36px"
+        android:paddingLeft="5px"
+        android:paddingRight="5px"
+        android:paddingBottom="4px" />
+
+</merge>
diff --git a/res/layout-finger/confirm_delete.xml b/res/layout-finger/confirm_delete.xml
new file mode 100644
index 0000000..be8d7ec
--- /dev/null
+++ b/res/layout-finger/confirm_delete.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content">
+
+    <TextView android:id="@+id/prompt"
+        android:layout_width="fill_parent" android:layout_height="wrap_content"
+        android:layout_marginLeft="8dip"
+        android:layout_marginTop="8dip"
+        android:layout_marginBottom="8dip"
+        android:drawableLeft="@android:drawable/ic_dialog_alert"
+        android:drawablePadding="8dip">
+    </TextView>
+
+    <RelativeLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:padding="6px"
+        android:background="#ffffff" >
+
+        <Button android:id="@+id/delete"
+            android:layout_width="120px" android:layout_height="wrap_content" 
+            android:text="@string/delete_confirm_button_text"
+            android:layout_gravity="center_horizontal"
+            android:layout_alignParentLeft="true" />
+
+        <Button android:id="@+id/cancel"
+            android:layout_width="120px" android:layout_height="wrap_content" 
+            android:text="@string/cancel"
+            android:layout_alignParentRight="true" />
+
+    </RelativeLayout>
+
+</LinearLayout>
+
diff --git a/res/layout-finger/edit_track_list_item.xml b/res/layout-finger/edit_track_list_item.xml
new file mode 100644
index 0000000..4b5c02f
--- /dev/null
+++ b/res/layout-finger/edit_track_list_item.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="64dip"
+    android:gravity="bottom"
+    android:orientation="vertical"
+    android:baselineAligned="false">
+
+    <ImageView
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:background="@android:drawable/divider_horizontal_dark" />
+
+    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="fill_parent"
+        android:layout_height="64dip"
+        android:gravity="center_vertical"
+        android:ignoreGravity="@+id/icon">
+
+        <include layout="@layout/track_list_item_common" />
+
+    </RelativeLayout>
+
+</LinearLayout>
diff --git a/res/layout-finger/gadget.xml b/res/layout-finger/gadget.xml
new file mode 100644
index 0000000..0786d06
--- /dev/null
+++ b/res/layout-finger/gadget.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/album_gadget"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    >
+
+    <FrameLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_marginLeft="9dip"
+        android:layout_marginRight="10dip"
+        android:background="@drawable/gadget_bg"
+        >
+
+        <LinearLayout
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:background="@drawable/gadget_inner"
+            >
+
+            <TextView
+                android:id="@+id/title"
+                android:layout_width="fill_parent"
+                android:layout_height="0dip"
+                android:layout_weight="1"
+                android:gravity="bottom|left"
+                android:textColor="@color/gadget_text"
+                android:textStyle="bold"
+                android:textSize="11sp"
+                android:singleLine="true"
+                android:ellipsize="marquee"
+                android:fadingEdge="horizontal"
+                />
+
+            <TextView
+                android:id="@+id/artist"
+                android:layout_width="fill_parent"
+                android:layout_height="0dip"
+                android:layout_weight="1"
+                android:gravity="top|left"
+                android:textColor="@color/gadget_text"
+                android:textSize="11dip"
+                android:singleLine="true"
+                android:ellipsize="marquee"
+                android:fadingEdge="horizontal"
+                />
+
+        </LinearLayout>
+
+    </FrameLayout>
+
+    <ImageButton
+        android:id="@+id/control_next"
+        android:layout_width="wrap_content"
+        android:layout_height="fill_parent"
+        android:layout_alignParentRight="true"
+        android:layout_centerVertical="true"
+        android:paddingRight="25dip"
+        android:paddingLeft="10dip"
+        android:src="@drawable/gadget_next"
+        android:background="@android:color/transparent"
+        />
+
+    <ImageButton
+        android:id="@+id/control_play"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentBottom="true"
+        android:paddingLeft="16dip"
+        android:paddingRight="10dip"
+        android:paddingBottom="3dip"
+        android:paddingTop="10dip"
+        android:src="@drawable/gadget_play"
+        android:background="@android:color/transparent"
+        />
+
+</RelativeLayout>
diff --git a/res/layout-finger/music_library.xml b/res/layout-finger/music_library.xml
new file mode 100644
index 0000000..25737aa
--- /dev/null
+++ b/res/layout-finger/music_library.xml
@@ -0,0 +1,165 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1"
+        android:paddingTop="35dip"
+        android:paddingLeft="18dip"
+        android:paddingRight="18dip"
+        android:orientation="horizontal"
+        >
+
+        <LinearLayout
+            android:layout_weight="1"
+            android:layout_width="0dip"
+            android:layout_height="fill_parent"
+            android:orientation="vertical"
+            android:layout_marginRight="18dip"
+            >
+
+            <Button
+                android:id="@+id/browse_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="@drawable/main_menu_button"
+                android:drawableTop="@drawable/ic_mp_screen_artists"
+                android:text="@string/browse_menu"
+                android:ellipsize="marquee"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                android:textColor="?android:attr/textColorPrimaryDisableOnly"
+                android:singleLine="true"
+                android:layout_marginBottom="35dip"
+            />
+
+            <Button
+                android:id="@+id/tracks_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="@drawable/main_menu_button"
+                android:drawableTop="@drawable/ic_mp_screen_tracks"
+                android:text="@string/tracks_menu"
+                android:ellipsize="marquee"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                android:textColor="?android:attr/textColorPrimaryDisableOnly"
+                android:singleLine="true"
+                android:layout_marginBottom="35dip"
+            />
+
+        </LinearLayout>
+
+
+        <LinearLayout
+            android:layout_weight="1"
+            android:layout_width="0dip"
+            android:layout_height="fill_parent"
+            android:orientation="vertical"
+            >
+
+            <Button
+                android:id="@+id/albums_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="@drawable/main_menu_button"
+                android:drawableTop="@drawable/ic_mp_screen_albums"
+                android:text="@string/albums_menu"
+                android:ellipsize="marquee"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                android:textColor="?android:attr/textColorPrimaryDisableOnly"
+                android:singleLine="true"
+                android:layout_marginBottom="35dip"
+            />
+
+            <Button
+                android:id="@+id/playlists_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="@drawable/main_menu_button"
+                android:drawableTop="@drawable/ic_mp_screen_playlists"
+                android:text="@string/playlists_menu"
+                android:ellipsize="marquee"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                android:textColor="?android:attr/textColorPrimaryDisableOnly"
+                android:singleLine="true"
+                android:layout_marginBottom="35dip"
+            />
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/nowplaying"
+        android:layout_width="fill_parent"
+        android:layout_height="64dip"
+        android:focusable="true"
+        android:visibility="invisible"
+        android:orientation="vertical">
+
+        <ImageView
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:background="@android:drawable/divider_horizontal_dark" />
+
+        <LinearLayout
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:orientation="horizontal"
+            android:layout_marginLeft="18dip"
+            android:layout_marginRight="18dip">
+
+            <LinearLayout
+                android:layout_width="0dip"
+                android:layout_weight="1"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:orientation="vertical">
+
+                <TextView
+                    android:id="@+id/title"
+                    android:textAppearance="?android:attr/textAppearanceMedium"
+                    android:singleLine="true"
+                    android:ellipsize="end"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                />
+                <TextView
+                    android:id="@+id/artist"
+                    android:textAppearance="?android:attr/textAppearanceSmall"
+                    android:singleLine="true"
+                    android:ellipsize="end"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                />
+            </LinearLayout>
+
+            <ImageView android:id="@+id/icon"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:background="@drawable/indicator_ic_mp_playing_large"
+            />
+
+        </LinearLayout>
+    </LinearLayout>   
+</LinearLayout>
+
diff --git a/res/layout-finger/streamstarter.xml b/res/layout-finger/streamstarter.xml
new file mode 100644
index 0000000..025d889
--- /dev/null
+++ b/res/layout-finger/streamstarter.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:padding="10dip">
+
+    <ProgressBar android:id="@android:id/progress"
+        style="?android:attr/progressBarStyleLarge"
+        android:layout_gravity="center"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <TextView android:id="@+id/streamloading"
+        android:paddingTop="5dip"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:textSize="14sp"
+        android:textColor="#ffffffff" />
+
+</LinearLayout>
+
diff --git a/res/layout-finger/track_list_item.xml b/res/layout-finger/track_list_item.xml
new file mode 100644
index 0000000..aa2cd5d
--- /dev/null
+++ b/res/layout-finger/track_list_item.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="64dip"
+    android:gravity="center_vertical"
+    android:ignoreGravity="@+id/icon">
+
+    <include layout="@layout/track_list_item_common" />
+
+</RelativeLayout>
diff --git a/res/layout-finger/track_list_item_child.xml b/res/layout-finger/track_list_item_child.xml
new file mode 100644
index 0000000..5c1a07b
--- /dev/null
+++ b/res/layout-finger/track_list_item_child.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="64dip"
+    android:background="@drawable/list_selector"
+    android:gravity="center_vertical"
+    android:ignoreGravity="@+id/icon">
+
+    <include layout="@layout/track_list_item_common" />
+
+</RelativeLayout>
diff --git a/res/layout-finger/track_list_item_common.xml b/res/layout-finger/track_list_item_common.xml
new file mode 100644
index 0000000..4ba4a9a
--- /dev/null
+++ b/res/layout-finger/track_list_item_common.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <!-- icon is used for albumart, the grabber in edit playlist mode, and the playlist icon in the list of playlists -->
+    <ImageView android:id="@+id/icon"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentBottom="true"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
+    <TextView android:id="@+id/duration"
+        android:textSize="12sp"
+        android:textColor="?android:attr/textColorTertiary"
+        android:textStyle="bold"
+        android:paddingLeft="4dip"
+        android:paddingRight="11dip"
+        android:layout_alignParentRight="true"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignBaseline="@+id/line1"
+        android:singleLine="true" />
+
+    <!-- The height is set to half the height of the parent, which is 64 dip -->
+    <TextView android:id="@+id/line1"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:layout_width="wrap_content"
+        android:paddingLeft="9dip"
+        android:layout_height="wrap_content"
+        android:layout_alignWithParentIfMissing="true"
+        android:layout_toRightOf="@id/icon"
+        android:layout_toLeftOf="@id/duration"
+        android:ellipsize="marquee"
+        android:singleLine="true" />
+
+    <!-- The height is set to half the height of the parent, which is 64 dip -->
+    <TextView android:id="@+id/line2" android:visibility="visible"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:singleLine="true"
+        android:ellipsize="end"
+        android:paddingLeft="9dip"
+        android:scrollHorizontally="true"
+        android:layout_below="@id/line1"
+        android:layout_alignWithParentIfMissing="true"
+        android:layout_toRightOf="@id/icon"
+        android:layout_toLeftOf="@id/duration"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <ImageView android:id="@+id/play_indicator"
+        android:layout_alignParentRight="true"
+        android:layout_alignBottom="@id/line2"
+        android:layout_below="@id/duration"        
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginRight="12dip" />
+
+</merge>
diff --git a/res/layout-finger/track_list_item_group.xml b/res/layout-finger/track_list_item_group.xml
new file mode 100644
index 0000000..8c6f500
--- /dev/null
+++ b/res/layout-finger/track_list_item_group.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="64dip"
+    android:gravity="center_vertical"
+    android:ignoreGravity="@+id/icon"
+    android:paddingLeft="47dip">
+
+    <include layout="@layout/track_list_item_common" />
+
+</RelativeLayout>
diff --git a/res/layout-finger/weekpicker.xml b/res/layout-finger/weekpicker.xml
new file mode 100644
index 0000000..00ced6f
--- /dev/null
+++ b/res/layout-finger/weekpicker.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- Layout of time picker-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content">
+
+    <TextView
+        android:text="@string/weekpicker_title"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:layout_gravity="center_horizontal"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <!-- weeks -->
+    <com.android.internal.widget.VerticalTextSpinner
+        android:id="@+id/weeks"
+        android:layout_width="120dip"
+        android:layout_height="120dip"
+        android:focusable="true"
+        android:focusableInTouchMode="true"
+        android:layout_gravity="center_horizontal"
+        android:layout_marginTop="6px"
+        android:layout_marginBottom="6px"
+        />
+    
+    <!-- Set button -->
+    <RelativeLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:padding="6px"
+        android:background="#ffffff" >
+
+        <Button android:id="@+id/set"
+            android:layout_width="120px"
+            android:layout_height="wrap_content"
+            android:text="@string/weekpicker_set"
+            android:layout_alignParentLeft="true" />
+
+        <Button android:id="@+id/cancel"
+            android:layout_width="120px"
+            android:layout_height="wrap_content" 
+            android:text="@string/cancel"
+            android:layout_alignParentRight="true" />
+
+    </RelativeLayout>
+
+</LinearLayout>
+
diff --git a/res/layout-keysexposed/create_playlist.xml b/res/layout-keysexposed/create_playlist.xml
new file mode 100644
index 0000000..1bf2252
--- /dev/null
+++ b/res/layout-keysexposed/create_playlist.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content">
+
+    <TextView android:id="@+id/prompt"
+        android:layout_width="fill_parent" android:layout_height="wrap_content"
+        android:text="@string/create_playlist_create_text_prompt"
+        android:layout_marginTop="8dip"
+        android:layout_marginBottom="8dip"
+        android:layout_marginLeft="8dip"
+        android:layout_marginRight="8dip">
+    </TextView>
+
+    <EditText android:id="@+id/playlist"
+        android:layout_width="fill_parent" android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:layout_marginBottom="8dip"
+        android:layout_marginLeft="8dip"
+        android:layout_marginRight="8dip">
+        <requestFocus />
+    </EditText>
+
+    <RelativeLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:padding="6px"
+        android:background="#ffffff" >
+
+        <Button android:id="@+id/create"
+            android:layout_width="120px" android:layout_height="wrap_content" 
+            android:text="@string/create_playlist_create_text"
+            android:layout_alignParentLeft="true" />
+
+        <Button android:id="@+id/cancel"
+            android:layout_width="120px" android:layout_height="wrap_content" 
+            android:text="@string/cancel"
+            android:layout_alignParentRight="true" />
+
+    </RelativeLayout>
+
+</LinearLayout>
+
diff --git a/res/layout-keyshidden/create_playlist.xml b/res/layout-keyshidden/create_playlist.xml
new file mode 100644
index 0000000..bb5c6ac
--- /dev/null
+++ b/res/layout-keyshidden/create_playlist.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content">
+
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="8dip"
+        android:layout_marginTop="8dip"
+        android:layout_marginLeft="8dip"
+        android:layout_marginRight="8dip">
+
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/ic_slide_keyboard"/>
+
+        <TextView android:id="@+id/prompt"
+            android:layout_width="fill_parent" android:layout_height="wrap_content"
+            android:text="@string/create_playlist_create_text_prompt"
+            android:layout_marginLeft="8dip">
+        </TextView>
+    </LinearLayout>
+
+    <EditText android:id="@+id/playlist"
+        android:layout_width="fill_parent" android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:visibility="gone"
+        android:layout_marginBottom="8dip">
+        <requestFocus />
+    </EditText>
+
+    <RelativeLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:padding="6px"
+        android:background="#ffffff" >
+
+        <Button android:id="@+id/create"
+            android:layout_width="120px" android:layout_height="wrap_content" 
+            android:text="@string/create_playlist_create_text"
+            android:layout_alignParentLeft="true" />
+
+        <Button android:id="@+id/cancel"
+            android:layout_width="120px" android:layout_height="wrap_content" 
+            android:text="@string/cancel"
+            android:layout_alignParentRight="true" />
+
+    </RelativeLayout>
+
+</LinearLayout>
+
diff --git a/res/layout-land-finger/audio_player.xml b/res/layout-land-finger/audio_player.xml
new file mode 100644
index 0000000..6d68f4d
--- /dev/null
+++ b/res/layout-land-finger/audio_player.xml
@@ -0,0 +1,150 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical">
+
+
+    <!-- This is the LinearLayout that contains the album art, function buttons and album/artist/track info -->
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:id="@+id/album"
+            android:background="@drawable/album_border_large"
+            android:layout_width="183dip"
+            android:layout_height="183dip"
+            android:layout_marginLeft="12dip"
+            android:layout_marginRight="15dip"
+            android:layout_marginTop="12dip" />
+
+        <!-- This is the LinearLayout that contains function buttons and album/artist/track info -->
+        <LinearLayout
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:orientation="vertical">
+
+            <LinearLayout
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                android:paddingTop="20dip" >
+
+               <ImageButton android:id="@+id/curplaylist"
+                    android:src="@drawable/ic_mp_current_playlist_btn"
+                    android:layout_width="82dip"
+                    android:layout_height="45dip"
+                    android:layout_marginRight="8dip" />
+
+               <ImageButton android:id="@+id/shuffle"
+                    android:layout_width="82dip"
+                    android:layout_height="45dip"
+                    android:layout_marginRight="8dip" />
+
+               <ImageButton android:id="@+id/repeat"
+                    android:layout_width="82dip"
+                    android:layout_height="45dip" />
+
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                android:baselineAligned="false"
+                android:paddingTop="8dip"
+                android:paddingBottom="2dip">
+
+                <ImageView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginRight="4dip"
+                    android:src="@drawable/ic_mp_artist_playback" />
+
+                <TextView android:id="@+id/artistname"
+                    android:textSize="18sp"
+                    android:singleLine="true"
+                    android:ellipsize="end"
+                    android:textStyle="bold"
+                    android:layout_gravity="center_vertical"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                android:baselineAligned="false"
+                android:paddingTop="8dip"
+                android:paddingBottom="2dip">
+
+                <ImageView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginRight="4dip"
+                    android:src="@drawable/ic_mp_album_playback" />
+
+                <TextView android:id="@+id/albumname"
+                    android:textSize="14sp"
+                    android:singleLine="true"
+                    android:ellipsize="end"
+                    android:textStyle="bold"
+                    android:layout_gravity="center_vertical"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                android:baselineAligned="false"
+                android:paddingTop="4dip"
+                android:paddingBottom="2dip">
+
+                <ImageView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginRight="4dip"
+                    android:src="@drawable/ic_mp_song_playback" />
+
+                <TextView android:id="@+id/trackname"
+                    android:textSize="14sp"
+                    android:singleLine="true"
+                    android:ellipsize="end"
+                    android:textStyle="bold"
+                    android:layout_gravity="center_vertical"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+
+            </LinearLayout>
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+    <include layout="@layout/audio_player_common" />
+
+</LinearLayout>
diff --git a/res/layout-land-finger/music_library.xml b/res/layout-land-finger/music_library.xml
new file mode 100644
index 0000000..2f3019f
--- /dev/null
+++ b/res/layout-land-finger/music_library.xml
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1"
+        android:paddingTop="37dip"
+        android:paddingLeft="12dip"
+        android:paddingRight="12dip"
+        android:orientation="horizontal"
+        >
+
+        <Button
+            android:id="@+id/browse_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:background="@drawable/main_menu_button"
+            android:drawableTop="@drawable/ic_mp_screen_artists"
+            android:text="@string/browse_menu"
+            android:ellipsize="marquee"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textColor="?android:attr/textColorPrimaryDisableOnly"
+            android:singleLine="true"
+            android:layout_marginBottom="35dip"
+            android:layout_marginRight="10dip"
+        />
+
+        <Button
+            android:id="@+id/albums_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:background="@drawable/main_menu_button"
+            android:drawableTop="@drawable/ic_mp_screen_albums"
+            android:text="@string/albums_menu"
+            android:ellipsize="marquee"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textColor="?android:attr/textColorPrimaryDisableOnly"
+            android:singleLine="true"
+            android:layout_marginBottom="35dip"
+            android:layout_marginRight="10dip"
+        />
+
+        <Button
+            android:id="@+id/tracks_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:background="@drawable/main_menu_button"
+            android:drawableTop="@drawable/ic_mp_screen_tracks"
+            android:text="@string/tracks_menu"
+            android:ellipsize="marquee"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textColor="?android:attr/textColorPrimaryDisableOnly"
+            android:singleLine="true"
+            android:layout_marginBottom="35dip"
+            android:layout_marginRight="10dip"
+        />
+
+        <Button
+            android:id="@+id/playlists_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:background="@drawable/main_menu_button"
+            android:drawableTop="@drawable/ic_mp_screen_playlists"
+            android:text="@string/playlists_menu"
+            android:ellipsize="marquee"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textColor="?android:attr/textColorPrimaryDisableOnly"
+            android:singleLine="true"
+            android:layout_marginBottom="35dip"
+        />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/nowplaying"
+        android:layout_width="fill_parent"
+        android:layout_height="64dip"
+        android:focusable="true"
+        android:visibility="invisible"
+        android:orientation="vertical">
+
+        <ImageView
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:background="@android:drawable/divider_horizontal_dark" />
+
+        <LinearLayout
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:orientation="horizontal"
+            android:layout_marginLeft="12dip"
+            android:layout_marginRight="12dip">
+
+            <LinearLayout
+                android:layout_width="0dip"
+                android:layout_weight="1"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:orientation="vertical">
+
+                <TextView
+                    android:id="@+id/title"
+                    android:textAppearance="?android:attr/textAppearanceMedium"
+                    android:singleLine="true"
+                    android:ellipsize="end"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                />
+                <TextView
+                    android:id="@+id/artist"
+                    android:textAppearance="?android:attr/textAppearanceSmall"
+                    android:singleLine="true"
+                    android:ellipsize="end"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                />
+            </LinearLayout>
+
+            <ImageView android:id="@+id/icon"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:background="@drawable/indicator_ic_mp_playing_large"
+            />
+
+        </LinearLayout>
+    </LinearLayout>
+</LinearLayout>
+
diff --git a/res/layout/media_picker_activity.xml b/res/layout/media_picker_activity.xml
new file mode 100644
index 0000000..79f080e
--- /dev/null
+++ b/res/layout/media_picker_activity.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<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_vertical" >
+
+    <include layout="@layout/sd_error" />
+
+    <com.android.music.TouchInterceptor
+        android:id="@android:id/list"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:textSize="18sp"
+        android:drawSelectorOnTop="false"
+        android:fastScrollEnabled="true" /> 
+
+</LinearLayout>
diff --git a/res/layout/media_picker_activity_expanding.xml b/res/layout/media_picker_activity_expanding.xml
new file mode 100644
index 0000000..3361431
--- /dev/null
+++ b/res/layout/media_picker_activity_expanding.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<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_vertical" >
+
+    <include layout="@layout/sd_error" />
+
+    <ExpandableListView
+        android:id="@android:id/list"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:textSize="18sp"
+        android:drawSelectorOnTop="false"
+        android:fastScrollEnabled="true"
+        android:indicatorLeft="30dip" />
+        android:indicatorRight="60dip" />
+
+</LinearLayout>
diff --git a/res/layout/music_picker.xml b/res/layout/music_picker.xml
new file mode 100644
index 0000000..f9df172
--- /dev/null
+++ b/res/layout/music_picker.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:orientation="vertical"
+        android:layout_width="fill_parent" 
+        android:layout_height="fill_parent">
+    
+    <FrameLayout
+            android:layout_width="fill_parent"
+            android:layout_height="0dip"
+            android:layout_weight="1">
+        
+        <LinearLayout android:id="@+id/progressContainer"
+                android:orientation="vertical"
+                android:layout_width="fill_parent" 
+                android:layout_height="fill_parent"
+                android:gravity="center">
+            
+            <ProgressBar android:id="@+android:id/progress"
+                    style="?android:attr/progressBarStyleLarge"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+            <TextView android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:textAppearance="?android:attr/textAppearanceSmall"
+                    android:text="@string/loading"
+                    android:paddingTop="4dip"
+                    android:singleLine="true" />
+                
+        </LinearLayout>
+            
+        <FrameLayout android:id="@+id/listContainer"
+                android:layout_width="fill_parent" 
+                android:layout_height="fill_parent">
+                
+            <ListView android:id="@android:id/list"
+                    android:layout_width="fill_parent" 
+                    android:layout_height="fill_parent"
+                    android:drawSelectorOnTop="false"
+                    android:fastScrollEnabled="true" />
+            <TextView android:id="@android:id/empty"
+                    android:layout_width="fill_parent"
+                    android:layout_height="fill_parent"
+                    android:gravity="center"
+                    android:text="@string/no_tracks_title"
+                    android:textAppearance="?android:attr/textAppearanceLarge" />
+        </FrameLayout>
+            
+    </FrameLayout>
+    
+    <RelativeLayout
+            android:layout_width="fill_parent"
+            android:layout_height="?android:attr/listPreferredItemHeight"
+            android:layout_marginTop="1dip"
+            android:background="@android:drawable/bottom_bar">
+        <Button android:id="@+id/okayButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_centerVertical="true"
+                android:layout_alignParentLeft="true"
+                android:text="@android:string/ok"
+                android:minWidth="120dip"
+                android:minHeight="48dip" />
+        <Button android:id="@+id/cancelButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_centerVertical="true"
+                android:layout_alignParentRight="true"
+                android:text="@android:string/cancel"
+                android:minWidth="120dip"
+                android:minHeight="48dip" />
+    </RelativeLayout>
+        
+</LinearLayout>
diff --git a/res/layout/music_picker_item.xml b/res/layout/music_picker_item.xml
new file mode 100644
index 0000000..b6e40fe
--- /dev/null
+++ b/res/layout/music_picker_item.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, Google Inc.
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+-->
+<com.android.music.CheckableRelativeLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="64dip"
+    android:gravity="center_vertical"
+    android:ignoreGravity="@+id/radio">
+
+    <RadioButton
+        android:id="@+id/radio"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_alignParentLeft="true"
+        android:layout_centerVertical="true"
+        android:layout_marginLeft="4dip"
+        android:focusable="false"
+        android:clickable="false" />
+
+    <TextView android:id="@+id/duration"
+        android:layout_alignParentRight="true"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignBaseline="@+id/line1"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textColor="#ffe0d090"
+        android:paddingLeft="4dip"
+        android:paddingRight="5dip"
+        android:singleLine="true" />
+
+    <TextView android:id="@+id/line1"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:layout_width="wrap_content"
+        android:paddingLeft="4dip"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:layout_alignWithParentIfMissing="true"
+        android:layout_toRightOf="@id/radio"
+        android:layout_toLeftOf="@id/duration"
+        android:ellipsize="end"
+        android:singleLine="true" />
+
+    <TextView android:id="@+id/line2" android:visibility="visible"
+        android:maxLines="2"
+        android:ellipsize="end"
+        android:paddingLeft="4dip"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:layout_below="@id/line1"
+        android:layout_alignWithParentIfMissing="true"
+        android:layout_toRightOf="@id/radio"
+        android:layout_toLeftOf="@id/duration"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <ImageView android:id="@+id/play_indicator"
+        android:layout_alignParentRight="true"
+        android:layout_alignBottom="@id/line2"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginRight="6dip" />
+        
+</com.android.music.CheckableRelativeLayout>
diff --git a/res/layout/query_activity.xml b/res/layout/query_activity.xml
new file mode 100644
index 0000000..72c3513
--- /dev/null
+++ b/res/layout/query_activity.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<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_vertical" >
+
+    <include layout="@layout/sd_error" />
+
+    <ListView android:id="@android:id/list"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:drawSelectorOnTop="false">
+    </ListView>
+</LinearLayout>
+
diff --git a/res/layout/scanning.xml b/res/layout/scanning.xml
new file mode 100644
index 0000000..ff3caa6
--- /dev/null
+++ b/res/layout/scanning.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:padding="12px">
+
+    <ProgressBar android:id="@+id/spinner"
+        style="?android:attr/progressBarStyleLarge"
+        android:layout_gravity="center"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <TextView android:id="@+id/message"
+        android:layout_gravity="center"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:text="@string/scanning">
+    </TextView>
+
+</LinearLayout>
+
diff --git a/res/layout/sd_error.xml b/res/layout/sd_error.xml
new file mode 100644
index 0000000..8a40305
--- /dev/null
+++ b/res/layout/sd_error.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+     <ImageView
+        android:id="@+id/sd_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
+        android:background="@drawable/ic_mp_sd_card"
+        android:visibility="gone" />
+
+    <TextView
+        android:id="@+id/sd_message"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
+        android:padding="8dip"
+        android:text="@string/sdcard_missing_message"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:visibility="gone" />
+
+</merge>
diff --git a/res/layout/statusbar.xml b/res/layout/statusbar.xml
new file mode 100644
index 0000000..32fdc0b
--- /dev/null
+++ b/res/layout/statusbar.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="horizontal">
+
+    <ImageView android:id="@+id/icon"
+        android:padding="4dip"
+        android:gravity="center"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content">
+    </ImageView>
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <TextView android:id="@+id/trackname"
+            android:textAppearance="?android:attr/textAppearanceMediumInverse"
+            android:focusable="true"
+            android:ellipsize="marquee"
+            android:singleLine="true"
+            android:layout_gravity="left"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+        <TextView android:id="@+id/artistalbum"
+            android:textAppearance="?android:attr/textAppearanceSmallInverse"
+            android:layout_gravity="left"
+            android:maxLines="2"
+            android:scrollHorizontally="true"
+            android:ellipsize="end"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/res/values-cs-keysexposed/strings.xml b/res/values-cs-keysexposed/strings.xml
new file mode 100644
index 0000000..ffba993
--- /dev/null
+++ b/res/values-cs-keysexposed/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="create_playlist_create_text_prompt">"Název seznamu stop"</string>
+    <string name="rename_playlist_same_prompt">"Přejmenovat seznam stop <xliff:g id="PLAYLIST">%s</xliff:g> na"</string>
+    <string name="rename_playlist_diff_prompt">"Přejmenovat seznam stop <xliff:g id="PLAYLIST">%s</xliff:g> na"</string>
+</resources>
diff --git a/res/values-cs-keyshidden/strings.xml b/res/values-cs-keyshidden/strings.xml
new file mode 100644
index 0000000..96fd750
--- /dev/null
+++ b/res/values-cs-keyshidden/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="create_playlist_create_text_prompt">"Otevřete klávesnici a zadejte název nového seznamu stop. Pokud zvolíte tlačítko Uložit, seznamu stop bude přidělen název %s."</string>
+    <string name="rename_playlist_same_prompt">"Otevřete klávesnici a zadejte nový název seznamu stop <xliff:g id="PLAYLIST">%s</xliff:g>."</string>
+    <string name="rename_playlist_diff_prompt">"Otevřete klávesnici a zadejte nový název seznamu stop <xliff:g id="PLAYLIST">%s</xliff:g>, nebo zvolte tlačítko Uložit a seznamu stop bude přidělen název %s."</string>
+</resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
new file mode 100644
index 0000000..f6c5f9f
--- /dev/null
+++ b/res/values-cs/strings.xml
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="onesong">"1 skladba"</string>
+  <plurals name="Nsongs">
+    <item quantity="other">"Počet skladeb: <xliff:g id="COUNT">%d</xliff:g>"</item>
+  </plurals>
+  <plurals name="Nsongscomp">
+    <item quantity="other">"<xliff:g id="COUNT_FOR_ARTIST">%2$d</xliff:g> z celkem <xliff:g id="TOTAL_COUNT">%1$d</xliff:g> skladeb"</item>
+  </plurals>
+  <plurals name="Nalbums">
+    <item quantity="one">"1 album"</item>
+    <item quantity="other">"Počet alb: <xliff:g id="COUNT">%d</xliff:g>"</item>
+  </plurals>
+    <string name="goto_start">"Knihovna"</string>
+    <string name="goto_playback">"Přehrávání"</string>
+    <string name="party_shuffle">"Náhodně – všechny"</string>
+    <string name="party_shuffle_off">"Vypnout náhodné přehrávání všeho"</string>
+    <string name="delete_item">"Smazat"</string>
+    <string name="shuffle_all">"Náhodně – aktuální seznam"</string>
+    <string name="play_all">"Přehrát vše"</string>
+    <string name="delete_artist_desc">"Všechny skladby interpreta <xliff:g id="ARTIST">%s</xliff:g> budou trvale smazány z karty SD."</string>
+    <string name="delete_album_desc">"Celé album <xliff:g id="ALBUM">%s</xliff:g> bude trvale smazáno z karty SD."</string>
+    <string name="delete_song_desc">"Skladba <xliff:g id="SONG">%s</xliff:g> bude trvale smazána z karty SD."</string>
+    <string name="delete_confirm_button_text">"OK"</string>
+  <plurals name="NNNtracksdeleted">
+    <item quantity="one">"Byla smazána 1 skladba."</item>
+    <item quantity="other">"Smazané skladby: <xliff:g id="SONGS_TO_DELETE">%d</xliff:g>."</item>
+  </plurals>
+    <string name="scanning">"Vyhledávání na kartě SD..."</string>
+    <string name="nowplaying_title">"Nyní se přehrává"</string>
+    <string name="partyshuffle_title">"Náhodně – všechny"</string>
+    <string name="artists_title">"Interpreti"</string>
+    <string name="albums_menu">"Alba"</string>
+    <string name="albums_title">"Alba"</string>
+    <string name="tracks_menu">"Skladby"</string>
+    <string name="tracks_title">"Skladby"</string>
+    <string name="playlists_menu">"Seznamy stop"</string>
+    <string name="playlists_title">"Seznamy stop"</string>
+    <string name="videos_title">"Videa"</string>
+    <string name="all_title">"Všechna média"</string>
+    <string name="browse_menu">"Interpreti"</string>
+    <string name="search_title">"Hledat"</string>
+    <string name="no_tracks_title">"Žádné skladby"</string>
+    <string name="no_videos_title">"Žádná videa"</string>
+    <string name="no_playlists_title">"Žádné seznamy stop"</string>
+    <string name="delete_playlist_menu">"Smazat"</string>
+    <string name="edit_playlist_menu">"Upravit"</string>
+    <string name="rename_playlist_menu">"Přejmenovat"</string>
+    <string name="playlist_deleted_message">"Seznam stop byl smazán."</string>
+    <string name="playlist_renamed_message">"Seznam stop byl přejmenován."</string>
+    <string name="recentlyadded">"Nedávno přidané"</string>
+    <string name="recentlyadded_title">"Nedávno přidané"</string>
+    <string name="podcasts_listitem">"Podcasty"</string>
+    <string name="podcasts_title">"Podcasty"</string>
+    <string name="sdcard_missing_title">"Žádná karta SD není dostupná"</string>
+    <string name="sdcard_missing_message">"V telefonu není vložena karta SD."</string>
+    <string name="sdcard_busy_title">"Karta SD není dostupná"</string>
+    <string name="sdcard_busy_message">"Karta SD je zaneprázdněna."</string>
+    <string name="sdcard_error_title">"Chyba karty SD"</string>
+    <string name="sdcard_error_message">"Na kartě SD došlo k chybě."</string>
+    <string name="unknown_artist_name">"Neznámý interpret"</string>
+    <string name="unknown_album_name">"Neznámé album"</string>
+    <string name="shuffle_on_notif">"Náhodné přehrávání je zapnuto."</string>
+    <string name="shuffle_off_notif">"Náhodné přehrávání je vypnuto."</string>
+    <string name="repeat_off_notif">"Opakování je vypnuto."</string>
+    <string name="repeat_current_notif">"Opakování aktuální skladby."</string>
+    <string name="repeat_all_notif">"Opakování všech skladeb."</string>
+    <string name="ringtone_menu">"Použít jako vyzváněcí tón telefonu"</string>
+    <string name="ringtone_menu_short">"Použít jako vyzváněcí tón"</string>
+    <string name="ringtone_set">"Skladba %s byla nastavena jako vyzváněcí tón."</string>
+    <string name="play_selection">"Přehrát"</string>
+    <string name="add_to_playlist">"Přidat do seznamu stop"</string>
+    <string name="queue">"Aktuální seznam stop"</string>
+    <string name="new_playlist">"Nové"</string>
+    <string name="new_playlist_name_template">"Nový seznam stop <xliff:g id="NUMBER">%d</xliff:g>"</string>
+  <plurals name="NNNtrackstoplaylist">
+    <item quantity="one">"1 skladba byla přidána do seznamu stop."</item>
+    <item quantity="other">"Skladby přidány do seznamu stop: %d."</item>
+  </plurals>
+    <string name="emptyplaylist">"Vybraný seznam stop je prázdný."</string>
+    <string name="create_playlist_create_text">"Uložit"</string>
+    <string name="create_playlist_overwrite_text">"Přepsat"</string>
+    <string name="service_start_error_title">"Problém s přehráváním"</string>
+    <string name="service_start_error_msg">"Skladbu nelze přehrát."</string>
+    <string name="service_start_error_button">"OK"</string>
+  <string-array name="weeklist">
+    <item>"1 týden"</item>
+    <item>"2 týdny"</item>
+    <item>"3 týdny"</item>
+    <item>"4 týdny"</item>
+    <item>"5 týdnů"</item>
+    <item>"6 týdnů"</item>
+    <item>"7 týdnů"</item>
+    <item>"8 týdnů"</item>
+    <item>"9 týdnů"</item>
+    <item>"10 týdnů"</item>
+    <item>"11 týdnů"</item>
+    <item>"12 týdnů"</item>
+  </string-array>
+    <string name="weekpicker_set">"Hotovo"</string>
+    <string name="weekpicker_title">"Nastavit čas"</string>
+    <string name="save_as_playlist">"Uložit jako seznam stop"</string>
+    <string name="clear_playlist">"Vymazat seznam stop"</string>
+    <string name="musicbrowserlabel">"Hudba"</string>
+    <string name="musicshortcutlabel">"Seznam skladeb"</string>
+    <string name="mediaplaybacklabel">"Hudba"</string>
+    <string name="videobrowserlabel">"Videa"</string>
+    <string name="mediapickerlabel">"Hudba"</string>
+    <string name="playback_failed">"Přehrávač nepodporuje tento typ zvukových souborů."</string>
+    <string name="cancel">"Zrušit"</string>
+    <string name="remove_from_playlist">"Odstranit ze seznamu stop"</string>
+    <string name="streamloadingtext">"Připojování k proudu <xliff:g id="HOST">%s</xliff:g>"</string>
+    <string name="mediasearch">"Vyhledat skladbu %s pomocí:"</string>
+    <string name="working_artists">"Interpreti…"</string>
+    <string name="working_albums">"Alba…"</string>
+    <string name="working_songs">"Skladby…"</string>
+    <string name="working_playlists">"Seznamy stop…"</string>
+    <string name="loading">"Načítání"</string>
+    <string name="sort_by_track">"Stopy"</string>
+    <string name="sort_by_album">"Alba"</string>
+    <string name="sort_by_artist">"Interpreti"</string>
+    <string name="music_picker_title">"Vyberte hudební stopu"</string>
+    <string name="gadget_track">"Stopa <xliff:g id="TRACK_NUMBER">%d</xliff:g>"</string>
+</resources>
diff --git a/res/values-de-keysexposed/strings.xml b/res/values-de-keysexposed/strings.xml
new file mode 100644
index 0000000..6ec704b
--- /dev/null
+++ b/res/values-de-keysexposed/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="create_playlist_create_text_prompt">"Name der Playlist"</string>
+    <string name="rename_playlist_same_prompt">"\"<xliff:g id="PLAYLIST">%s</xliff:g>\" umbenennen in"</string>
+    <string name="rename_playlist_diff_prompt">"\"<xliff:g id="PLAYLIST">%s</xliff:g>\" umbenennen in"</string>
+</resources>
diff --git a/res/values-de-keyshidden/strings.xml b/res/values-de-keyshidden/strings.xml
new file mode 100644
index 0000000..fbc866f
--- /dev/null
+++ b/res/values-de-keyshidden/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="create_playlist_create_text_prompt">"Ziehen Sie die Tastatur heraus, um für die neue Playlist einen Namen festzulegen, oder wählen Sie \"Speichern\", um sie mit dem Namen \"%s\" zu speichern."</string>
+    <string name="rename_playlist_same_prompt">"Ziehen Sie die Tastatur heraus, um \"<xliff:g id="PLAYLIST">%s</xliff:g>\" umzubenennen."</string>
+    <string name="rename_playlist_diff_prompt">"Ziehen Sie die Tastatur heraus, um die Playlist \"<xliff:g id="PLAYLIST">%s</xliff:g>\" umzubenennen, oder wählen Sie \"Speichern\", um sie mit dem Namen \"%s\" zu speichern."</string>
+</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
new file mode 100644
index 0000000..8ca4408
--- /dev/null
+++ b/res/values-de/strings.xml
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="onesong">"1 Titel"</string>
+  <plurals name="Nsongs">
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> Titel"</item>
+  </plurals>
+  <plurals name="Nsongscomp">
+    <item quantity="other">"<xliff:g id="COUNT_FOR_ARTIST">%2$d</xliff:g> von \n<xliff:g id="TOTAL_COUNT">%1$d</xliff:g> Liedern"</item>
+  </plurals>
+  <plurals name="Nalbums">
+    <item quantity="one">"1 Album"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> Alben"</item>
+  </plurals>
+    <string name="goto_start">"Startübersicht"</string>
+    <string name="goto_playback">"Wiedergeben"</string>
+    <string name="party_shuffle">"Zufallswiedergabe (Party)"</string>
+    <string name="party_shuffle_off">"Zufallswiedergabe (Party) deaktivieren"</string>
+    <string name="delete_item">"Löschen"</string>
+    <string name="shuffle_all">"Alle zufällig wiedergeben"</string>
+    <string name="play_all">"Alle wiedergeben"</string>
+    <string name="delete_artist_desc">"Alle Titel von <xliff:g id="ARTIST">%s</xliff:g> werden endgültig von der SD-Karte gelöscht."</string>
+    <string name="delete_album_desc">"Das gesamte Album \"<xliff:g id="ALBUM">%s</xliff:g>\" wird endgültig von der SD-Karte gelöscht."</string>
+    <string name="delete_song_desc">"\"<xliff:g id="SONG">%s</xliff:g>\" wird endgültig von der SD-Karte gelöscht."</string>
+    <string name="delete_confirm_button_text">"OK"</string>
+  <plurals name="NNNtracksdeleted">
+    <item quantity="one">"1 Titel wurde gelöscht."</item>
+    <item quantity="other">"<xliff:g id="SONGS_TO_DELETE">%d</xliff:g> Titel wurden gelöscht."</item>
+  </plurals>
+    <string name="scanning">"SD-Karte wird gelesenen..."</string>
+    <string name="nowplaying_title">"Aktuelle Wiedergabe"</string>
+    <string name="partyshuffle_title">"Zufallswiedergabe (Party)"</string>
+    <string name="artists_title">"Interpreten"</string>
+    <string name="albums_menu">"Alben"</string>
+    <string name="albums_title">"Alben"</string>
+    <string name="tracks_menu">"Titel"</string>
+    <string name="tracks_title">"Titel"</string>
+    <string name="playlists_menu">"Playlists"</string>
+    <string name="playlists_title">"Playlists"</string>
+    <string name="videos_title">"Videos"</string>
+    <string name="all_title">"Alle Medien"</string>
+    <string name="browse_menu">"Interpreten"</string>
+    <string name="search_title">"Suchen"</string>
+    <string name="no_tracks_title">"Keine Titel"</string>
+    <string name="no_videos_title">"Keine Videos"</string>
+    <string name="no_playlists_title">"Keine Playlists"</string>
+    <string name="delete_playlist_menu">"Löschen"</string>
+    <string name="edit_playlist_menu">"Bearbeiten"</string>
+    <string name="rename_playlist_menu">"Umbenennen"</string>
+    <string name="playlist_deleted_message">"Playlist gelöscht"</string>
+    <string name="playlist_renamed_message">"Playlist umbenannt"</string>
+    <string name="recentlyadded">"Kürzlich hinzugefügt"</string>
+    <string name="recentlyadded_title">"Kürzlich hinzugefügt"</string>
+    <string name="podcasts_listitem">"Podcasts"</string>
+    <string name="podcasts_title">"Podcasts"</string>
+    <string name="sdcard_missing_title">"Keine SD-Karte"</string>
+    <string name="sdcard_missing_message">"Es befindet sich keine SD-Karte im Telefon."</string>
+    <string name="sdcard_busy_title">"SD-Karte nicht verfügbar"</string>
+    <string name="sdcard_busy_message">"Die SD-Karte ist ausgelastet."</string>
+    <string name="sdcard_error_title">"SD-Kartenfehler"</string>
+    <string name="sdcard_error_message">"Bei der SD-Karte ist ein Fehler aufgetreten."</string>
+    <string name="unknown_artist_name">"Unbekannter Interpret"</string>
+    <string name="unknown_album_name">"Unbekanntes Album"</string>
+    <string name="shuffle_on_notif">"Zufallswiedergabe ist aktiviert."</string>
+    <string name="shuffle_off_notif">"Zufallswiedergabe ist deaktiviert."</string>
+    <string name="repeat_off_notif">"Wiederholung ist deaktiviert."</string>
+    <string name="repeat_current_notif">"Aktueller Titel wird wiederholt."</string>
+    <string name="repeat_all_notif">"Alle Titel werden wiederholt."</string>
+    <string name="ringtone_menu">"Als Telefonklingelton verwenden"</string>
+    <string name="ringtone_menu_short">"Als Klingelton verwenden"</string>
+    <string name="ringtone_set">"\"%s\" als Telefonklingelton eingestellt"</string>
+    <string name="play_selection">"Wiedergeben"</string>
+    <string name="add_to_playlist">"Zur Playlist hinzufügen"</string>
+    <string name="queue">"Aktuelle Playlist"</string>
+    <string name="new_playlist">"Neu"</string>
+    <string name="new_playlist_name_template">"Neue Playlist <xliff:g id="NUMBER">%d</xliff:g>"</string>
+  <plurals name="NNNtrackstoplaylist">
+    <item quantity="one">"1 Titel zur Playlist hinzugefügt"</item>
+    <item quantity="other">"%d Titel zur Playlist hinzugefügt."</item>
+  </plurals>
+    <string name="emptyplaylist">"Ausgewählte Playlist ist leer"</string>
+    <string name="create_playlist_create_text">"Speichern"</string>
+    <string name="create_playlist_overwrite_text">"Überschreiben"</string>
+    <string name="service_start_error_title">"Problem bei der Wiedergabe"</string>
+    <string name="service_start_error_msg">"Der Titel kann nicht wiedergegeben werden."</string>
+    <string name="service_start_error_button">"OK"</string>
+  <string-array name="weeklist">
+    <item>"1 Woche"</item>
+    <item>"2 Wochen"</item>
+    <item>"3 Wochen"</item>
+    <item>"4 Wochen"</item>
+    <item>"5 Wochen"</item>
+    <item>"6 Wochen"</item>
+    <item>"7 Wochen"</item>
+    <item>"8 Wochen"</item>
+    <item>"9 Wochen"</item>
+    <item>"10 Wochen"</item>
+    <item>"11 Wochen"</item>
+    <item>"12 Wochen"</item>
+  </string-array>
+    <string name="weekpicker_set">"Fertig"</string>
+    <string name="weekpicker_title">"Uhrzeit einstellen"</string>
+    <string name="save_as_playlist">"Als Playlist speichern"</string>
+    <string name="clear_playlist">"Playlist löschen"</string>
+    <string name="musicbrowserlabel">"Musik"</string>
+    <string name="musicshortcutlabel">"Musikplaylist"</string>
+    <string name="mediaplaybacklabel">"Musik"</string>
+    <string name="videobrowserlabel">"Videos"</string>
+    <string name="mediapickerlabel">"Musik"</string>
+    <string name="playback_failed">"Die Wiedergabe dieses Audiodateityps wird nicht unterstützt."</string>
+    <string name="cancel">"Abbrechen"</string>
+    <string name="remove_from_playlist">"Von Playlist entfernen"</string>
+    <string name="streamloadingtext">"Verbindung mit <xliff:g id="HOST">%s</xliff:g> wird hergestellt"</string>
+    <string name="mediasearch">"Nach %s suchen mithilfe von:"</string>
+    <string name="working_artists">"Künstler…"</string>
+    <string name="working_albums">"Alben…"</string>
+    <string name="working_songs">"Lieder…"</string>
+    <string name="working_playlists">"Playlists…"</string>
+    <string name="loading">"Wird geladen"</string>
+    <string name="sort_by_track">"Titel"</string>
+    <string name="sort_by_album">"Alben"</string>
+    <string name="sort_by_artist">"Künstler"</string>
+    <string name="music_picker_title">"Musiktitel auswählen"</string>
+    <string name="gadget_track">"Track <xliff:g id="TRACK_NUMBER">%d</xliff:g>"</string>
+</resources>
diff --git a/res/values-es-keysexposed/strings.xml b/res/values-es-keysexposed/strings.xml
new file mode 100644
index 0000000..f41a5c0
--- /dev/null
+++ b/res/values-es-keysexposed/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="create_playlist_create_text_prompt">"Nombre de lista de reproducción"</string>
+    <string name="rename_playlist_same_prompt">"Cambiar \"<xliff:g id="PLAYLIST">%s</xliff:g>\" por"</string>
+    <string name="rename_playlist_diff_prompt">"Cambiar \"<xliff:g id="PLAYLIST">%s</xliff:g>\" por"</string>
+</resources>
diff --git a/res/values-es-keyshidden/strings.xml b/res/values-es-keyshidden/strings.xml
new file mode 100644
index 0000000..fd8243d
--- /dev/null
+++ b/res/values-es-keyshidden/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="create_playlist_create_text_prompt">"Abre el teclado para asignar un nombre a la nueva lista de reproducción o selecciona \"Guardar\" para llamarla \"%s\"."</string>
+    <string name="rename_playlist_same_prompt">"Abre el teclado para asignar un nombre nuevo a la lista de reproducción \"<xliff:g id="PLAYLIST">%s</xliff:g>\"."</string>
+    <string name="rename_playlist_diff_prompt">"Abre el teclado para asignar un nombre nuevo a la lista de reproducción <xliff:g id="PLAYLIST">%s</xliff:g> o selecciona \"Guardar\" para llamarla \"%s\"."</string>
+</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
new file mode 100644
index 0000000..00c743f
--- /dev/null
+++ b/res/values-es/strings.xml
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="onesong">"1 canción"</string>
+  <plurals name="Nsongs">
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> canciones"</item>
+  </plurals>
+  <plurals name="Nsongscomp">
+    <item quantity="other">"<xliff:g id="COUNT_FOR_ARTIST">%2$d</xliff:g> de <xliff:g id="TOTAL_COUNT">%1$d</xliff:g> canciones"</item>
+  </plurals>
+  <plurals name="Nalbums">
+    <item quantity="one">"1 álbum"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> álbumes"</item>
+  </plurals>
+    <string name="goto_start">"Biblioteca"</string>
+    <string name="goto_playback">"Reproducción"</string>
+    <string name="party_shuffle">"Sesión aleatoria"</string>
+    <string name="party_shuffle_off">"Desactivar sesión aleatoria"</string>
+    <string name="delete_item">"Suprimir"</string>
+    <string name="shuffle_all">"Reproducción aleatoria"</string>
+    <string name="play_all">"Reproducir todo"</string>
+    <string name="delete_artist_desc">"Todas las canciones de <xliff:g id="ARTIST">%s</xliff:g> se eliminarán de forma permanente de la tarjeta SD."</string>
+    <string name="delete_album_desc">"El álbum \"<xliff:g id="ALBUM">%s</xliff:g>\" completo se eliminará de forma permanente de la tarjeta SD."</string>
+    <string name="delete_song_desc">"\"<xliff:g id="SONG">%s</xliff:g>\" se eliminará de forma permanente de la tarjeta SD."</string>
+    <string name="delete_confirm_button_text">"Aceptar"</string>
+  <plurals name="NNNtracksdeleted">
+    <item quantity="one">"Se ha eliminado una canción."</item>
+    <item quantity="other">"Se han eliminado <xliff:g id="SONGS_TO_DELETE">%d</xliff:g> canciones."</item>
+  </plurals>
+    <string name="scanning">"Examinando tarjeta SD…"</string>
+    <string name="nowplaying_title">"Reproduciendo"</string>
+    <string name="partyshuffle_title">"Sesión aleatoria"</string>
+    <string name="artists_title">"Artistas"</string>
+    <string name="albums_menu">"Álbumes"</string>
+    <string name="albums_title">"Álbumes"</string>
+    <string name="tracks_menu">"Canciones"</string>
+    <string name="tracks_title">"Canciones"</string>
+    <string name="playlists_menu">"Listas de reproducción"</string>
+    <string name="playlists_title">"Listas de reproducción"</string>
+    <string name="videos_title">"Vídeos"</string>
+    <string name="all_title">"Todos los medios"</string>
+    <string name="browse_menu">"Artistas"</string>
+    <string name="search_title">"Buscar"</string>
+    <string name="no_tracks_title">"Ninguna canción"</string>
+    <string name="no_videos_title">"Ningún vídeo"</string>
+    <string name="no_playlists_title">"Ninguna lista de reproducción"</string>
+    <string name="delete_playlist_menu">"Suprimir"</string>
+    <string name="edit_playlist_menu">"Editar"</string>
+    <string name="rename_playlist_menu">"Cambiar nombre"</string>
+    <string name="playlist_deleted_message">"Se ha eliminado la lista de reproducción."</string>
+    <string name="playlist_renamed_message">"Se ha cambiado el nombre de la lista de reproducción."</string>
+    <string name="recentlyadded">"Añadidas recientemente"</string>
+    <string name="recentlyadded_title">"Añadidos recientemente"</string>
+    <string name="podcasts_listitem">"Podcasts"</string>
+    <string name="podcasts_title">"Podcasts"</string>
+    <string name="sdcard_missing_title">"Falta la tarjeta SD"</string>
+    <string name="sdcard_missing_message">"Falta la tarjeta SD en el teléfono"</string>
+    <string name="sdcard_busy_title">"Tarjeta SD no disponible"</string>
+    <string name="sdcard_busy_message">"La tarjeta SD está ocupada."</string>
+    <string name="sdcard_error_title">"Error de tarjeta SD"</string>
+    <string name="sdcard_error_message">"Se ha producido un error al acceder a la tarjeta SD."</string>
+    <string name="unknown_artist_name">"Artista desconocido"</string>
+    <string name="unknown_album_name">"Álbum desconocido"</string>
+    <string name="shuffle_on_notif">"La reproducción aleatoria está activada."</string>
+    <string name="shuffle_off_notif">"La reproducción aleatoria está desactivada."</string>
+    <string name="repeat_off_notif">"La repetición está desactivada."</string>
+    <string name="repeat_current_notif">"Repitiendo canción actual..."</string>
+    <string name="repeat_all_notif">"Repitiendo todas las canciones..."</string>
+    <string name="ringtone_menu">"Utilizar como tono del teléfono"</string>
+    <string name="ringtone_menu_short">"Utilizar como tono"</string>
+    <string name="ringtone_set">"Se ha establecido \"%s\" como tono del teléfono."</string>
+    <string name="play_selection">"Reproducir"</string>
+    <string name="add_to_playlist">"Añadir a lista de reproducción"</string>
+    <string name="queue">"Lista de reproducción actual"</string>
+    <string name="new_playlist">"Nuevo"</string>
+    <string name="new_playlist_name_template">"Nueva lista de reproducción <xliff:g id="NUMBER">%d</xliff:g>"</string>
+  <plurals name="NNNtrackstoplaylist">
+    <item quantity="one">"Se ha añadido una canción a la lista de reproducción."</item>
+    <item quantity="other">"Se han añadido %d canciones a la lista de reproducción."</item>
+  </plurals>
+    <string name="emptyplaylist">"La lista de reproducción seleccionada está vacía."</string>
+    <string name="create_playlist_create_text">"Guardar"</string>
+    <string name="create_playlist_overwrite_text">"Sobrescribir"</string>
+    <string name="service_start_error_title">"Problema de reproducción"</string>
+    <string name="service_start_error_msg">"No se ha podido reproducir la canción."</string>
+    <string name="service_start_error_button">"Aceptar"</string>
+  <string-array name="weeklist">
+    <item>"1 semana"</item>
+    <item>"2 semanas"</item>
+    <item>"3 semanas"</item>
+    <item>"4 semanas"</item>
+    <item>"5 semanas"</item>
+    <item>"6 semanas"</item>
+    <item>"7 semanas"</item>
+    <item>"8 semanas"</item>
+    <item>"9 semanas"</item>
+    <item>"10 semanas"</item>
+    <item>"11 semanas"</item>
+    <item>"12 semanas"</item>
+  </string-array>
+    <string name="weekpicker_set">"Hecho"</string>
+    <string name="weekpicker_title">"Establecer hora"</string>
+    <string name="save_as_playlist">"Guardar como lista de reproducción"</string>
+    <string name="clear_playlist">"Borrar lista de reproducción"</string>
+    <string name="musicbrowserlabel">"Música"</string>
+    <string name="musicshortcutlabel">"Lista de reproducción de música"</string>
+    <string name="mediaplaybacklabel">"Música"</string>
+    <string name="videobrowserlabel">"Vídeos"</string>
+    <string name="mediapickerlabel">"Música"</string>
+    <string name="playback_failed">"El reproductor no admite este tipo de archivo de audio."</string>
+    <string name="cancel">"Cancelar"</string>
+    <string name="remove_from_playlist">"Eliminar de lista de reproducción"</string>
+    <string name="streamloadingtext">"Conectando con <xliff:g id="HOST">%s</xliff:g>..."</string>
+    <string name="mediasearch">"Buscar %s con:"</string>
+    <string name="working_artists">"Artistas…"</string>
+    <string name="working_albums">"Álbumes…"</string>
+    <string name="working_songs">"Canciones…"</string>
+    <string name="working_playlists">"Listas de reproducción…"</string>
+    <string name="loading">"Cargando"</string>
+    <string name="sort_by_track">"Pistas"</string>
+    <string name="sort_by_album">"Álbumes"</string>
+    <string name="sort_by_artist">"Artistas"</string>
+    <string name="music_picker_title">"Seleccionar pista musical"</string>
+    <string name="gadget_track">"Pista <xliff:g id="TRACK_NUMBER">%d</xliff:g>"</string>
+</resources>
diff --git a/res/values-finger/strings2.xml b/res/values-finger/strings2.xml
new file mode 100644
index 0000000..2381e8d
--- /dev/null
+++ b/res/values-finger/strings2.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Do not translate. This is the separator character used when building the string that shows number of albums and songs. -->
+    <string name="albumsongseparator">\n</string>
+
+    <!-- Used for the 2nd and 3rd line (artist and album) in the notification area item for the music app -->
+    <string name="notification_artist_album"><xliff:g id="album">%2$s</xliff:g>\n<xliff:g id="artist">%1$s</xliff:g></string>
+</resources>
+
diff --git a/res/values-fr-keysexposed/strings.xml b/res/values-fr-keysexposed/strings.xml
new file mode 100644
index 0000000..9fa5d9d
--- /dev/null
+++ b/res/values-fr-keysexposed/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="create_playlist_create_text_prompt">"Nom de la playlist"</string>
+    <string name="rename_playlist_same_prompt">"Renommer \"<xliff:g id="PLAYLIST">%s</xliff:g>\" en"</string>
+    <string name="rename_playlist_diff_prompt">"Renommer \"<xliff:g id="PLAYLIST">%s</xliff:g>\" en"</string>
+</resources>
diff --git a/res/values-fr-keyshidden/strings.xml b/res/values-fr-keyshidden/strings.xml
new file mode 100644
index 0000000..2ee347c
--- /dev/null
+++ b/res/values-fr-keyshidden/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="create_playlist_create_text_prompt">"Ouvrez le clavier afin de saisir un nom pour votre nouvelle playlist ou sélectionnez Enregistrer pour la nommer \"%s\"."</string>
+    <string name="rename_playlist_same_prompt">"Ouvrez le clavier pour renommer la playlist \"<xliff:g id="PLAYLIST">%s</xliff:g>\"."</string>
+    <string name="rename_playlist_diff_prompt">"Ouvrez le clavier pour saisir un nouveau nom pour la playlist \"<xliff:g id="PLAYLIST">%s</xliff:g>\" ou sélectionnez Enregistrer pour la nommer \"%s\"."</string>
+</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
new file mode 100644
index 0000000..b434be0
--- /dev/null
+++ b/res/values-fr/strings.xml
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="onesong">"1 chanson"</string>
+  <plurals name="Nsongs">
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> chansons"</item>
+  </plurals>
+  <plurals name="Nsongscomp">
+    <item quantity="other">"<xliff:g id="COUNT_FOR_ARTIST">%2$d</xliff:g> chansons sur <xliff:g id="TOTAL_COUNT">%1$d</xliff:g>"</item>
+  </plurals>
+  <plurals name="Nalbums">
+    <item quantity="one">"1 album"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> albums"</item>
+  </plurals>
+    <string name="goto_start">"Médiathèque"</string>
+    <string name="goto_playback">"Lecture"</string>
+    <string name="party_shuffle">"Lecture aléatoire"</string>
+    <string name="party_shuffle_off">"La lecture aléatoire est désactivée."</string>
+    <string name="delete_item">"Supprimer"</string>
+    <string name="shuffle_all">"Lecture aléatoire de toutes les chansons"</string>
+    <string name="play_all">"Tout lire"</string>
+    <string name="delete_artist_desc">"Toutes les chansons de <xliff:g id="ARTIST">%s</xliff:g> seront définitivement supprimées de la carte SD."</string>
+    <string name="delete_album_desc">"L\'intégralité de l\'album \"<xliff:g id="ALBUM">%s</xliff:g>\" sera définitivement supprimée de la carte SD."</string>
+    <string name="delete_song_desc">"La chanson \"<xliff:g id="SONG">%s</xliff:g>\" sera définitivement supprimée de la carte SD."</string>
+    <string name="delete_confirm_button_text">"OK"</string>
+  <plurals name="NNNtracksdeleted">
+    <item quantity="one">"1 chanson a été supprimée."</item>
+    <item quantity="other">"<xliff:g id="SONGS_TO_DELETE">%d</xliff:g> chansons ont été supprimées."</item>
+  </plurals>
+    <string name="scanning">"Lecture de la carte SD..."</string>
+    <string name="nowplaying_title">"En écoute"</string>
+    <string name="partyshuffle_title">"Lecture aléatoire"</string>
+    <string name="artists_title">"Artistes"</string>
+    <string name="albums_menu">"Albums"</string>
+    <string name="albums_title">"Albums"</string>
+    <string name="tracks_menu">"Chansons"</string>
+    <string name="tracks_title">"Chansons"</string>
+    <string name="playlists_menu">"Playlists"</string>
+    <string name="playlists_title">"Playlists"</string>
+    <string name="videos_title">"Vidéos"</string>
+    <string name="all_title">"Tous le multimédia"</string>
+    <string name="browse_menu">"Artistes"</string>
+    <string name="search_title">"Rechercher"</string>
+    <string name="no_tracks_title">"Aucune chanson"</string>
+    <string name="no_videos_title">"Aucune vidéo trouvée"</string>
+    <string name="no_playlists_title">"Aucune playlist trouvée"</string>
+    <string name="delete_playlist_menu">"Supprimer"</string>
+    <string name="edit_playlist_menu">"Modifier"</string>
+    <string name="rename_playlist_menu">"Renommer"</string>
+    <string name="playlist_deleted_message">"La playlist a été supprimée."</string>
+    <string name="playlist_renamed_message">"La playlist a été renommée."</string>
+    <string name="recentlyadded">"Ajoutés récemment"</string>
+    <string name="recentlyadded_title">"Playlist récente"</string>
+    <string name="podcasts_listitem">"Podcasts"</string>
+    <string name="podcasts_title">"Podcasts"</string>
+    <string name="sdcard_missing_title">"Aucune carte SD trouvée"</string>
+    <string name="sdcard_missing_message">"Aucune carte SD n\'est insérée dans votre téléphone."</string>
+    <string name="sdcard_busy_title">"Carte SD non disponible"</string>
+    <string name="sdcard_busy_message">"Désolé, votre carte SD n\'est pas disponible."</string>
+    <string name="sdcard_error_title">"Erreur de carte SD"</string>
+    <string name="sdcard_error_message">"Une erreur s\'est produite sur votre carte SD."</string>
+    <string name="unknown_artist_name">"Artiste inconnu"</string>
+    <string name="unknown_album_name">"Album inconnu"</string>
+    <string name="shuffle_on_notif">"La lecture aléatoire est activée."</string>
+    <string name="shuffle_off_notif">"Le mode aléatoire est désactivé."</string>
+    <string name="repeat_off_notif">"La lecture en boucle est désactivée."</string>
+    <string name="repeat_current_notif">"Lecture en boucle de la chanson en écoute."</string>
+    <string name="repeat_all_notif">"Lecture en boucle de toutes les chansons"</string>
+    <string name="ringtone_menu">"Utiliser comme sonnerie"</string>
+    <string name="ringtone_menu_short">"Définir comme sonnerie"</string>
+    <string name="ringtone_set">"\"%s\" a été défini comme sonnerie."</string>
+    <string name="play_selection">"Lire"</string>
+    <string name="add_to_playlist">"Ajouter à la playlist"</string>
+    <string name="queue">"Playlist actuelle"</string>
+    <string name="new_playlist">"Nouveau"</string>
+    <string name="new_playlist_name_template">"Nouvelle playlist <xliff:g id="NUMBER">%d</xliff:g>"</string>
+  <plurals name="NNNtrackstoplaylist">
+    <item quantity="one">"1 chanson a été ajoutée à la playlist."</item>
+    <item quantity="other">"%d chansons ont été ajoutées à la playlist."</item>
+  </plurals>
+    <string name="emptyplaylist">"La playlist sélectionnée est vide."</string>
+    <string name="create_playlist_create_text">"Enregistrer"</string>
+    <string name="create_playlist_overwrite_text">"Remplacer"</string>
+    <string name="service_start_error_title">"Erreur de lecture"</string>
+    <string name="service_start_error_msg">"Désolé, la chanson n\'a pas pu être lue."</string>
+    <string name="service_start_error_button">"OK"</string>
+  <string-array name="weeklist">
+    <item>"1 sem."</item>
+    <item>"2 sem."</item>
+    <item>"3 sem."</item>
+    <item>"4 sem."</item>
+    <item>"5 sem."</item>
+    <item>"6 sem."</item>
+    <item>"7 sem."</item>
+    <item>"8 sem."</item>
+    <item>"9 sem."</item>
+    <item>"10 sem."</item>
+    <item>"11 sem."</item>
+    <item>"12 sem."</item>
+  </string-array>
+    <string name="weekpicker_set">"OK"</string>
+    <string name="weekpicker_title">"Définir la durée"</string>
+    <string name="save_as_playlist">"Enregistrer comme playlist"</string>
+    <string name="clear_playlist">"Effacer la playlist"</string>
+    <string name="musicbrowserlabel">"Musique"</string>
+    <string name="musicshortcutlabel">"Playlist musicale"</string>
+    <string name="mediaplaybacklabel">"Musique"</string>
+    <string name="videobrowserlabel">"Vidéos"</string>
+    <string name="mediapickerlabel">"Musique"</string>
+    <string name="playback_failed">"Désolé, le lecteur ne prend pas en charge ce type de fichier audio."</string>
+    <string name="cancel">"Annuler"</string>
+    <string name="remove_from_playlist">"Supprimer de la playlist"</string>
+    <string name="streamloadingtext">"Connexion à <xliff:g id="HOST">%s</xliff:g>"</string>
+    <string name="mediasearch">"Rechercher %s à l\'aide de :"</string>
+    <string name="working_artists">"Artistes…"</string>
+    <string name="working_albums">"Albums…"</string>
+    <string name="working_songs">"Chansons…"</string>
+    <string name="working_playlists">"Playlists…"</string>
+    <string name="loading">"Chargement"</string>
+    <string name="sort_by_track">"Pistes"</string>
+    <string name="sort_by_album">"Albums"</string>
+    <string name="sort_by_artist">"Artistes"</string>
+    <string name="music_picker_title">"Sélectionner une piste"</string>
+    <string name="gadget_track">"Piste <xliff:g id="TRACK_NUMBER">%d</xliff:g>"</string>
+</resources>
diff --git a/res/values-it-keysexposed/strings.xml b/res/values-it-keysexposed/strings.xml
new file mode 100644
index 0000000..ba7ae09
--- /dev/null
+++ b/res/values-it-keysexposed/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="create_playlist_create_text_prompt">"Nome playlist"</string>
+    <string name="rename_playlist_same_prompt">"Cambia \"<xliff:g id="PLAYLIST">%s</xliff:g>\" con"</string>
+    <string name="rename_playlist_diff_prompt">"Cambia \"<xliff:g id="PLAYLIST">%s</xliff:g>\" con"</string>
+</resources>
diff --git a/res/values-it-keyshidden/strings.xml b/res/values-it-keyshidden/strings.xml
new file mode 100644
index 0000000..637d1ad
--- /dev/null
+++ b/res/values-it-keyshidden/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="create_playlist_create_text_prompt">"Apri la tastiera per assegnare un nome alla nuova playlist o seleziona Salva per denominarla \"%s\"."</string>
+    <string name="rename_playlist_same_prompt">"Apri la tastiera per assegnare un nuovo nome alla playlist \"<xliff:g id="PLAYLIST">%s</xliff:g>\"."</string>
+    <string name="rename_playlist_diff_prompt">"Apri la tastiera per assegnare un nuovo nome alla playlist \"<xliff:g id="PLAYLIST">%s</xliff:g>\" o seleziona Salva per denominarla \"%s\"."</string>
+</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
new file mode 100644
index 0000000..2a730e9
--- /dev/null
+++ b/res/values-it/strings.xml
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="onesong">"1 brano"</string>
+  <plurals name="Nsongs">
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> brani"</item>
+  </plurals>
+  <plurals name="Nsongscomp">
+    <item quantity="other">"<xliff:g id="COUNT_FOR_ARTIST">%2$d</xliff:g> brani su <xliff:g id="TOTAL_COUNT">%1$d</xliff:g>"</item>
+  </plurals>
+  <plurals name="Nalbums">
+    <item quantity="one">"1 album"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> album"</item>
+  </plurals>
+    <string name="goto_start">"Raccolta"</string>
+    <string name="goto_playback">"Riproduci"</string>
+    <string name="party_shuffle">"Party shuffle"</string>
+    <string name="party_shuffle_off">"Party shuffle non attiva"</string>
+    <string name="delete_item">"Elimina"</string>
+    <string name="shuffle_all">"Ripr. casuale"</string>
+    <string name="play_all">"Riprod. tutti"</string>
+    <string name="delete_artist_desc">"Tutti i brani di <xliff:g id="ARTIST">%s</xliff:g> verranno eliminati definitivamente dalla scheda SD."</string>
+    <string name="delete_album_desc">"L\'intero album \"<xliff:g id="ALBUM">%s</xliff:g>\" verrà eliminato definitivamente dalla scheda SD."</string>
+    <string name="delete_song_desc">"Il brano \"<xliff:g id="SONG">%s</xliff:g>\" verrà eliminato definitivamente dalla scheda SD."</string>
+    <string name="delete_confirm_button_text">"OK"</string>
+  <plurals name="NNNtracksdeleted">
+    <item quantity="one">"1 brano eliminato."</item>
+    <item quantity="other">"<xliff:g id="SONGS_TO_DELETE">%d</xliff:g> brani eliminati."</item>
+  </plurals>
+    <string name="scanning">"Analisi scheda SD..."</string>
+    <string name="nowplaying_title">"In esecuzione"</string>
+    <string name="partyshuffle_title">"Party shuffle"</string>
+    <string name="artists_title">"Artisti"</string>
+    <string name="albums_menu">"Album"</string>
+    <string name="albums_title">"Album"</string>
+    <string name="tracks_menu">"Brani"</string>
+    <string name="tracks_title">"Brani"</string>
+    <string name="playlists_menu">"Playlist"</string>
+    <string name="playlists_title">"Playlist"</string>
+    <string name="videos_title">"Video"</string>
+    <string name="all_title">"Tutti i media"</string>
+    <string name="browse_menu">"Artisti"</string>
+    <string name="search_title">"Cerca"</string>
+    <string name="no_tracks_title">"Nessun brano"</string>
+    <string name="no_videos_title">"Nessun video"</string>
+    <string name="no_playlists_title">"Nessuna playlist"</string>
+    <string name="delete_playlist_menu">"Elimina"</string>
+    <string name="edit_playlist_menu">"Modifica"</string>
+    <string name="rename_playlist_menu">"Rinomina"</string>
+    <string name="playlist_deleted_message">"Playlist eliminata."</string>
+    <string name="playlist_renamed_message">"Playlist rinominata."</string>
+    <string name="recentlyadded">"Aggiunta di recente"</string>
+    <string name="recentlyadded_title">"Aggiunta di recente"</string>
+    <string name="podcasts_listitem">"Podcast"</string>
+    <string name="podcasts_title">"Podcast"</string>
+    <string name="sdcard_missing_title">"Nessuna scheda SD"</string>
+    <string name="sdcard_missing_message">"Il telefono non contiene una scheda SD."</string>
+    <string name="sdcard_busy_title">"Scheda SD non disponibile"</string>
+    <string name="sdcard_busy_message">"Spiacenti. La scheda SD è già in uso."</string>
+    <string name="sdcard_error_title">"Errore della scheda SD"</string>
+    <string name="sdcard_error_message">"Si è verificato un errore nella scheda SD."</string>
+    <string name="unknown_artist_name">"Artista sconosciuto"</string>
+    <string name="unknown_album_name">"Album sconosciuto"</string>
+    <string name="shuffle_on_notif">"Riproduzione casuale attiva."</string>
+    <string name="shuffle_off_notif">"Riproduzione casuale non attiva."</string>
+    <string name="repeat_off_notif">"Ripetizione non attiva."</string>
+    <string name="repeat_current_notif">"Ripetizione brano corrente."</string>
+    <string name="repeat_all_notif">"Ripetizione di tutti i brani."</string>
+    <string name="ringtone_menu">"Usa come suoneria"</string>
+    <string name="ringtone_menu_short">"Usa come suoneria"</string>
+    <string name="ringtone_set">"\"%s\" impostato come suoneria."</string>
+    <string name="play_selection">"Riprod."</string>
+    <string name="add_to_playlist">"Aggiungi a playlist"</string>
+    <string name="queue">"Playlist corrente"</string>
+    <string name="new_playlist">"Nuova"</string>
+    <string name="new_playlist_name_template">"Nuova playlist <xliff:g id="NUMBER">%d</xliff:g>"</string>
+  <plurals name="NNNtrackstoplaylist">
+    <item quantity="one">"1 brano aggiunto alla playlist."</item>
+    <item quantity="other">"%d brani aggiunti alla playlist."</item>
+  </plurals>
+    <string name="emptyplaylist">"La playlist selezionata è vuota."</string>
+    <string name="create_playlist_create_text">"Salva"</string>
+    <string name="create_playlist_overwrite_text">"Sostituisci"</string>
+    <string name="service_start_error_title">"Problema di riproduzione"</string>
+    <string name="service_start_error_msg">"Spiacenti. Impossibile riprodurre il brano."</string>
+    <string name="service_start_error_button">"OK"</string>
+  <string-array name="weeklist">
+    <item>"1 settimana"</item>
+    <item>"2 settimane"</item>
+    <item>"3 settimane"</item>
+    <item>"4 settimane"</item>
+    <item>"5 settimane"</item>
+    <item>"6 settimane"</item>
+    <item>"7 settimane"</item>
+    <item>"8 settimane"</item>
+    <item>"9 settimane"</item>
+    <item>"10 settimane"</item>
+    <item>"11 settimane"</item>
+    <item>"12 settimane"</item>
+  </string-array>
+    <string name="weekpicker_set">"Fine"</string>
+    <string name="weekpicker_title">"Imposta periodo"</string>
+    <string name="save_as_playlist">"Salva come playlist"</string>
+    <string name="clear_playlist">"Cancella playlist"</string>
+    <string name="musicbrowserlabel">"Musica"</string>
+    <string name="musicshortcutlabel">"Playlist musicale"</string>
+    <string name="mediaplaybacklabel">"Musica"</string>
+    <string name="videobrowserlabel">"Video"</string>
+    <string name="mediapickerlabel">"Musica"</string>
+    <string name="playback_failed">"Spiacenti. Il player non supporta questo tipo di file audio."</string>
+    <string name="cancel">"Annulla"</string>
+    <string name="remove_from_playlist">"Rimuovi da playlist"</string>
+    <string name="streamloadingtext">"Connessione a <xliff:g id="HOST">%s</xliff:g>"</string>
+    <string name="mediasearch">"Cerca %s con:"</string>
+    <string name="working_artists">"Artisti…"</string>
+    <string name="working_albums">"Album…"</string>
+    <string name="working_songs">"Brani…"</string>
+    <string name="working_playlists">"Playlist…"</string>
+    <string name="loading">"Caricamento"</string>
+    <string name="sort_by_track">"Tracce"</string>
+    <string name="sort_by_album">"Album"</string>
+    <string name="sort_by_artist">"Artisti"</string>
+    <string name="music_picker_title">"Seleziona traccia musicale"</string>
+    <string name="gadget_track">"Traccia <xliff:g id="TRACK_NUMBER">%d</xliff:g>"</string>
+</resources>
diff --git a/res/values-ja-keysexposed/strings.xml b/res/values-ja-keysexposed/strings.xml
new file mode 100644
index 0000000..46d1dfd
--- /dev/null
+++ b/res/values-ja-keysexposed/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="create_playlist_create_text_prompt">"プレイリスト名"</string>
+    <string name="rename_playlist_same_prompt">"「<xliff:g id="PLAYLIST">%s</xliff:g>」の新しい名前"</string>
+    <string name="rename_playlist_diff_prompt">"「<xliff:g id="PLAYLIST">%s</xliff:g>」の新しい名前"</string>
+</resources>
diff --git a/res/values-ja-keyshidden/strings.xml b/res/values-ja-keyshidden/strings.xml
new file mode 100644
index 0000000..849e675
--- /dev/null
+++ b/res/values-ja-keyshidden/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="create_playlist_create_text_prompt">"キーボードを表示して新しいプレイリストに名前を付けるか、[保存]を選択して「%s」という名前で保存します。"</string>
+    <string name="rename_playlist_same_prompt">"キーボードを表示して「<xliff:g id="PLAYLIST">%s</xliff:g>」に新しい名前を付けます。"</string>
+    <string name="rename_playlist_diff_prompt">"キーボードを表示して「<xliff:g id="PLAYLIST">%s</xliff:g>」に新しい名前を付けるか、[保存]を選択して「%s」という名前で保存します。"</string>
+</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
new file mode 100644
index 0000000..4c6eecf
--- /dev/null
+++ b/res/values-ja/strings.xml
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="onesong">"曲(1)"</string>
+  <plurals name="Nsongs">
+    <item quantity="other">"曲(<xliff:g id="COUNT">%d</xliff:g>)"</item>
+  </plurals>
+  <plurals name="Nsongscomp">
+    <item quantity="other">"<xliff:g id="TOTAL_COUNT">%1$d</xliff:g>曲中<xliff:g id="COUNT_FOR_ARTIST">%2$d</xliff:g>曲"</item>
+  </plurals>
+  <plurals name="Nalbums">
+    <item quantity="one">"アルバム(1)"</item>
+    <item quantity="other">"アルバム(<xliff:g id="COUNT">%d</xliff:g>)"</item>
+  </plurals>
+    <string name="goto_start">"ライブラリ"</string>
+    <string name="goto_playback">"再生"</string>
+    <string name="party_shuffle">"パーティシャッフル"</string>
+    <string name="party_shuffle_off">"パーティシャッフルOFF"</string>
+    <string name="delete_item">"削除"</string>
+    <string name="shuffle_all">"すべてシャッフル"</string>
+    <string name="play_all">"すべて再生"</string>
+    <string name="delete_artist_desc">"<xliff:g id="ARTIST">%s</xliff:g>の全曲をSDカードから完全に削除します。"</string>
+    <string name="delete_album_desc">"アルバム「<xliff:g id="ALBUM">%s</xliff:g>」全体をSDカードから完全に削除します。"</string>
+    <string name="delete_song_desc">"「<xliff:g id="SONG">%s</xliff:g>」をSDカードから完全に削除します。"</string>
+    <string name="delete_confirm_button_text">"OK"</string>
+  <plurals name="NNNtracksdeleted">
+    <item quantity="one">"1曲削除しました。"</item>
+    <item quantity="other">"<xliff:g id="SONGS_TO_DELETE">%d</xliff:g>曲削除しました。"</item>
+  </plurals>
+    <string name="scanning">"SDカードをスキャン中..."</string>
+    <string name="nowplaying_title">"再生中"</string>
+    <string name="partyshuffle_title">"パーティシャッフル"</string>
+    <string name="artists_title">"アーティスト"</string>
+    <string name="albums_menu">"アルバム"</string>
+    <string name="albums_title">"アルバム"</string>
+    <string name="tracks_menu">"曲"</string>
+    <string name="tracks_title">"曲"</string>
+    <string name="playlists_menu">"プレイリスト"</string>
+    <string name="playlists_title">"プレイリスト"</string>
+    <string name="videos_title">"動画"</string>
+    <string name="all_title">"すべてのメディア"</string>
+    <string name="browse_menu">"アーティスト"</string>
+    <string name="search_title">"検索"</string>
+    <string name="no_tracks_title">"曲がありません"</string>
+    <string name="no_videos_title">"動画がありません"</string>
+    <string name="no_playlists_title">"プレイリストがありません"</string>
+    <string name="delete_playlist_menu">"削除"</string>
+    <string name="edit_playlist_menu">"編集"</string>
+    <string name="rename_playlist_menu">"名前を変更"</string>
+    <string name="playlist_deleted_message">"プレイリストを削除しました。"</string>
+    <string name="playlist_renamed_message">"プレイリストの名前を変更しました。"</string>
+    <string name="recentlyadded">"最近追加したアイテム"</string>
+    <string name="recentlyadded_title">"最近追加した項目"</string>
+    <string name="podcasts_listitem">"ポッドキャスト"</string>
+    <string name="podcasts_title">"ポッドキャスト"</string>
+    <string name="sdcard_missing_title">"SDカードがありません"</string>
+    <string name="sdcard_missing_message">"SDカードが携帯電話に挿入されていません。"</string>
+    <string name="sdcard_busy_title">"SDカードを使用できません"</string>
+    <string name="sdcard_busy_message">"このSDカードは別の端末で使用中です。"</string>
+    <string name="sdcard_error_title">"SDカードエラー"</string>
+    <string name="sdcard_error_message">"SDカードでエラーが発生しました。"</string>
+    <string name="unknown_artist_name">"不明なアーティスト"</string>
+    <string name="unknown_album_name">"不明なアルバム"</string>
+    <string name="shuffle_on_notif">"シャッフルをONにしました。"</string>
+    <string name="shuffle_off_notif">"シャッフルをOFFにしました。"</string>
+    <string name="repeat_off_notif">"繰り返しをOFFにしました。"</string>
+    <string name="repeat_current_notif">"現在の曲を繰り返し再生します。"</string>
+    <string name="repeat_all_notif">"全曲繰り返しで再生します。"</string>
+    <string name="ringtone_menu">"着信音に設定"</string>
+    <string name="ringtone_menu_short">"着信音に設定"</string>
+    <string name="ringtone_set">"「%s」を着信音として設定しました。"</string>
+    <string name="play_selection">"再生"</string>
+    <string name="add_to_playlist">"プレイリストに追加"</string>
+    <string name="queue">"現在のプレイリスト"</string>
+    <string name="new_playlist">"新規"</string>
+    <string name="new_playlist_name_template">"新規プレイリスト<xliff:g id="NUMBER">%d</xliff:g>"</string>
+  <plurals name="NNNtrackstoplaylist">
+    <item quantity="one">"プレイリストに1曲追加しました。"</item>
+    <item quantity="other">"プレイリストに%d曲追加しました。"</item>
+  </plurals>
+    <string name="emptyplaylist">"選択したプレイリストは空です。"</string>
+    <string name="create_playlist_create_text">"保存"</string>
+    <string name="create_playlist_overwrite_text">"上書き保存"</string>
+    <string name="service_start_error_title">"再生エラー"</string>
+    <string name="service_start_error_msg">"再生できません。"</string>
+    <string name="service_start_error_button">"OK"</string>
+  <string-array name="weeklist">
+    <item>"1週間"</item>
+    <item>"2週間"</item>
+    <item>"3週間"</item>
+    <item>"4週間"</item>
+    <item>"5週間"</item>
+    <item>"6週間"</item>
+    <item>"7週間"</item>
+    <item>"8週間"</item>
+    <item>"9週間"</item>
+    <item>"10週間"</item>
+    <item>"11週間"</item>
+    <item>"12週間"</item>
+  </string-array>
+    <string name="weekpicker_set">"完了"</string>
+    <string name="weekpicker_title">"期間の設定"</string>
+    <string name="save_as_playlist">"プレイリストとして保存"</string>
+    <string name="clear_playlist">"プレイリストをクリア"</string>
+    <string name="musicbrowserlabel">"ミュージック"</string>
+    <string name="musicshortcutlabel">"ミュージックプレイリスト"</string>
+    <string name="mediaplaybacklabel">"ミュージック"</string>
+    <string name="videobrowserlabel">"動画"</string>
+    <string name="mediapickerlabel">"ミュージック"</string>
+    <string name="playback_failed">"プレーヤーが対応していない音声ファイル形式です。"</string>
+    <string name="cancel">"キャンセル"</string>
+    <string name="remove_from_playlist">"プレイリストから削除"</string>
+    <string name="streamloadingtext">"<xliff:g id="HOST">%s</xliff:g>に接続中"</string>
+    <string name="mediasearch">"%sの検索:"</string>
+    <string name="working_artists">"アーティスト..."</string>
+    <string name="working_albums">"アルバム..."</string>
+    <string name="working_songs">"曲..."</string>
+    <string name="working_playlists">"再生リスト..."</string>
+    <string name="loading">"読み込み中"</string>
+    <string name="sort_by_track">"トラック"</string>
+    <string name="sort_by_album">"アルバム"</string>
+    <string name="sort_by_artist">"アーティスト"</string>
+    <string name="music_picker_title">"音楽トラックを選択"</string>
+    <string name="gadget_track">"トラック<xliff:g id="TRACK_NUMBER">%d</xliff:g>"</string>
+</resources>
diff --git a/res/values-keysexposed/strings.xml b/res/values-keysexposed/strings.xml
new file mode 100644
index 0000000..0c520aa
--- /dev/null
+++ b/res/values-keysexposed/strings.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Prompt in dialog when creating a playlist. The user will enter the name of the playlist in a textfield underneath this prompt. -->
+    <string name="create_playlist_create_text_prompt">Playlist name</string>
+    <!-- Prompt in dialog when renaming a playlist, used when the current name and the new name are the same. The user will enter the new name of the playlist in a textfield underneath this prompt. -->
+    <string name="rename_playlist_same_prompt">Rename \"<xliff:g id="playlist">%s</xliff:g>\" to</string>
+    <!-- Prompt in dialog when renaming a playlist, used when the current name the new name are different. The user will enter the new name of the playlist in a textfield underneath this prompt. -->
+    <string name="rename_playlist_diff_prompt">Rename \"<xliff:g id="playlist">%s</xliff:g>\" to</string>
+</resources>
+
diff --git a/res/values-keyshidden/strings.xml b/res/values-keyshidden/strings.xml
new file mode 100644
index 0000000..b086fd0
--- /dev/null
+++ b/res/values-keyshidden/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Prompt in dialog when creating a playlist and the keyboard is closed. -->
+    <string name="create_playlist_create_text_prompt">Open the keyboard to give your new Playlist a name, or select Save to name it \"%s\".</string>
+
+    <!-- Prompt in dialog when renaming a playlist, and the entered name is the same as the old one. -->
+    <string name="rename_playlist_same_prompt">Open the keyboard to give playlist \"<xliff:g id="playlist">%s</xliff:g>\" a new name.</string>
+    <!-- Prompt in dialog when renaming a playlist, and the entered name is different from the old one. -->
+    <string name="rename_playlist_diff_prompt">Open the keyboard to give playlist \"<xliff:g id="playlist">%s</xliff:g>\" a new name, or select Save to name it \"%s\".</string>
+</resources>
+
diff --git a/res/values-ko-keysexposed/strings.xml b/res/values-ko-keysexposed/strings.xml
new file mode 100644
index 0000000..60dc323
--- /dev/null
+++ b/res/values-ko-keysexposed/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="create_playlist_create_text_prompt">"재생 목록 이름"</string>
+    <string name="rename_playlist_same_prompt">"\'<xliff:g id="PLAYLIST">%s</xliff:g>\'을(를) 다음으로 변경"</string>
+    <string name="rename_playlist_diff_prompt">"\'<xliff:g id="PLAYLIST">%s</xliff:g>\'을(를) 다음으로 변경"</string>
+</resources>
diff --git a/res/values-ko-keyshidden/strings.xml b/res/values-ko-keyshidden/strings.xml
new file mode 100644
index 0000000..4a452df
--- /dev/null
+++ b/res/values-ko-keyshidden/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="create_playlist_create_text_prompt">"키보드를 사용하여 새 재생 목록에 이름을 지정하거나 \'저장\'을 선택하여 \'%s\'(이)라고 지정합니다."</string>
+    <string name="rename_playlist_same_prompt">"키보드를 사용하여 \'<xliff:g id="PLAYLIST">%s</xliff:g>\' 재생 목록에 새 이름을 지정합니다."</string>
+    <string name="rename_playlist_diff_prompt">"키보드를 사용하여 \'<xliff:g id="PLAYLIST">%s</xliff:g>\' 재생 목록에 새 이름을 지정하거나 \'저장\'을 선택하여 \'%s\'(이)라고 지정합니다."</string>
+</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
new file mode 100644
index 0000000..2d96a00
--- /dev/null
+++ b/res/values-ko/strings.xml
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="onesong">"1개의 노래"</string>
+  <plurals name="Nsongs">
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>개의 노래"</item>
+  </plurals>
+  <plurals name="Nsongscomp">
+    <item quantity="other">"노래 <xliff:g id="TOTAL_COUNT">%1$d</xliff:g>곡 중 <xliff:g id="COUNT_FOR_ARTIST">%2$d</xliff:g>곡"</item>
+  </plurals>
+  <plurals name="Nalbums">
+    <item quantity="one">"1개의 앨범"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g>개의 앨범"</item>
+  </plurals>
+    <string name="goto_start">"라이브러리"</string>
+    <string name="goto_playback">"재생"</string>
+    <string name="party_shuffle">"파티 셔플"</string>
+    <string name="party_shuffle_off">"파티 셔플 해제"</string>
+    <string name="delete_item">"삭제"</string>
+    <string name="shuffle_all">"모두 셔플"</string>
+    <string name="play_all">"모두 재생"</string>
+    <string name="delete_artist_desc">"<xliff:g id="ARTIST">%s</xliff:g>의 모든 노래가 SD 카드에서 완전히 삭제됩니다."</string>
+    <string name="delete_album_desc">"전체 앨범 \'<xliff:g id="ALBUM">%s</xliff:g>\'이(가) SD 카드에서 완전히 삭제됩니다."</string>
+    <string name="delete_song_desc">"\'<xliff:g id="SONG">%s</xliff:g>\'이(가) SD 카드에서 완전히 삭제됩니다."</string>
+    <string name="delete_confirm_button_text">"확인"</string>
+  <plurals name="NNNtracksdeleted">
+    <item quantity="one">"노래 1개가 삭제되었습니다."</item>
+    <item quantity="other">"<xliff:g id="SONGS_TO_DELETE">%d</xliff:g>개의 노래가 삭제되었습니다."</item>
+  </plurals>
+    <string name="scanning">"SD 카드 스캔 중..."</string>
+    <string name="nowplaying_title">"지금 재생 중"</string>
+    <string name="partyshuffle_title">"파티 셔플"</string>
+    <string name="artists_title">"아티스트"</string>
+    <string name="albums_menu">"앨범"</string>
+    <string name="albums_title">"앨범"</string>
+    <string name="tracks_menu">"노래"</string>
+    <string name="tracks_title">"노래"</string>
+    <string name="playlists_menu">"재생 목록"</string>
+    <string name="playlists_title">"재생 목록"</string>
+    <string name="videos_title">"동영상"</string>
+    <string name="all_title">"전체 미디어"</string>
+    <string name="browse_menu">"아티스트"</string>
+    <string name="search_title">"검색"</string>
+    <string name="no_tracks_title">"노래 없음"</string>
+    <string name="no_videos_title">"동영상 없음"</string>
+    <string name="no_playlists_title">"재생 목록 없음"</string>
+    <string name="delete_playlist_menu">"삭제"</string>
+    <string name="edit_playlist_menu">"편집"</string>
+    <string name="rename_playlist_menu">"이름 바꾸기"</string>
+    <string name="playlist_deleted_message">"재생 목록이 삭제되었습니다."</string>
+    <string name="playlist_renamed_message">"재생 목록 이름이 변경되었습니다."</string>
+    <string name="recentlyadded">"최근 추가 목록"</string>
+    <string name="recentlyadded_title">"최근 추가 목록"</string>
+    <string name="podcasts_listitem">"Podcast"</string>
+    <string name="podcasts_title">"Podcast"</string>
+    <string name="sdcard_missing_title">"SD 카드 없음"</string>
+    <string name="sdcard_missing_message">"전화에 SD 카드가 삽입되어 있지 않습니다."</string>
+    <string name="sdcard_busy_title">"SD 카드를 사용할 수 없음"</string>
+    <string name="sdcard_busy_message">"SD 카드가 사용 중입니다."</string>
+    <string name="sdcard_error_title">"SD 카드 오류"</string>
+    <string name="sdcard_error_message">"SD 카드에 오류가 발생했습니다."</string>
+    <string name="unknown_artist_name">"알 수 없는 아티스트"</string>
+    <string name="unknown_album_name">"알 수 없는 앨범"</string>
+    <string name="shuffle_on_notif">"셔플이 설정되었습니다."</string>
+    <string name="shuffle_off_notif">"셔플이 해제되었습니다."</string>
+    <string name="repeat_off_notif">"반복이 해제되었습니다."</string>
+    <string name="repeat_current_notif">"현재 노래를 반복 중입니다."</string>
+    <string name="repeat_all_notif">"모든 노래를 반복 중입니다."</string>
+    <string name="ringtone_menu">"전화 벨소리로 사용"</string>
+    <string name="ringtone_menu_short">"벨소리로 사용"</string>
+    <string name="ringtone_set">"\'%s\'이(가) 전화 벨소리로 설정되었습니다."</string>
+    <string name="play_selection">"재생"</string>
+    <string name="add_to_playlist">"재생 목록에 추가"</string>
+    <string name="queue">"현재 재생 목록"</string>
+    <string name="new_playlist">"새로 만들기"</string>
+    <string name="new_playlist_name_template">"새 재생 목록 <xliff:g id="NUMBER">%d</xliff:g>"</string>
+  <plurals name="NNNtrackstoplaylist">
+    <item quantity="one">"노래 1개가 재생 목록에 추가되었습니다."</item>
+    <item quantity="other">"%d개의 노래가 재생 목록에 추가되었습니다."</item>
+  </plurals>
+    <string name="emptyplaylist">"선택한 재생 목록이 비어 있습니다."</string>
+    <string name="create_playlist_create_text">"저장"</string>
+    <string name="create_playlist_overwrite_text">"덮어쓰기"</string>
+    <string name="service_start_error_title">"재생 문제"</string>
+    <string name="service_start_error_msg">"노래를 재생할 수 없습니다."</string>
+    <string name="service_start_error_button">"확인"</string>
+  <string-array name="weeklist">
+    <item>"1주"</item>
+    <item>"2주"</item>
+    <item>"3주"</item>
+    <item>"4주"</item>
+    <item>"5주"</item>
+    <item>"6주"</item>
+    <item>"7주"</item>
+    <item>"8주"</item>
+    <item>"9주"</item>
+    <item>"10주"</item>
+    <item>"11주"</item>
+    <item>"12주"</item>
+  </string-array>
+    <string name="weekpicker_set">"완료"</string>
+    <string name="weekpicker_title">"시간 설정"</string>
+    <string name="save_as_playlist">"재생 목록으로 저장"</string>
+    <string name="clear_playlist">"재생 목록 지우기"</string>
+    <string name="musicbrowserlabel">"음악"</string>
+    <string name="musicshortcutlabel">"음악 재생 목록"</string>
+    <string name="mediaplaybacklabel">"음악"</string>
+    <string name="videobrowserlabel">"동영상"</string>
+    <string name="mediapickerlabel">"음악"</string>
+    <string name="playback_failed">"플레이어에서 지원하지 않는 오디오 파일 형식입니다."</string>
+    <string name="cancel">"취소"</string>
+    <string name="remove_from_playlist">"재생 목록에서 삭제"</string>
+    <string name="streamloadingtext">"<xliff:g id="HOST">%s</xliff:g>에 연결 중"</string>
+    <string name="mediasearch">"다음을 사용하여 %s 검색:"</string>
+    <string name="working_artists">"아티스트…"</string>
+    <string name="working_albums">"앨범…"</string>
+    <string name="working_songs">"노래…"</string>
+    <string name="working_playlists">"재생 목록..."</string>
+    <string name="loading">"로드 중"</string>
+    <string name="sort_by_track">"트랙"</string>
+    <string name="sort_by_album">"앨범"</string>
+    <string name="sort_by_artist">"아티스트"</string>
+    <string name="music_picker_title">"음악 트랙 선택"</string>
+    <string name="gadget_track">"트랙 <xliff:g id="TRACK_NUMBER">%d</xliff:g>"</string>
+</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
new file mode 100644
index 0000000..4adf891
--- /dev/null
+++ b/res/values-nb/strings.xml
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="onesong">"1 sang"</string>
+  <plurals name="Nsongs">
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> sanger"</item>
+  </plurals>
+  <plurals name="Nsongscomp">
+    <item quantity="other">"<xliff:g id="COUNT_FOR_ARTIST">%2$d</xliff:g> av <xliff:g id="TOTAL_COUNT">%1$d</xliff:g> sanger"</item>
+  </plurals>
+  <plurals name="Nalbums">
+    <item quantity="one">"1 album"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> album"</item>
+  </plurals>
+    <string name="goto_start">"Bibliotek"</string>
+    <string name="goto_playback">"Avspilling"</string>
+    <string name="party_shuffle">"Tilfeldig"</string>
+    <string name="party_shuffle_off">"Tilfeldig av"</string>
+    <string name="delete_item">"Slett"</string>
+    <string name="shuffle_all">"Stokk alle"</string>
+    <string name="play_all">"Spill alle"</string>
+    <string name="delete_artist_desc">"Alle sanger av <xliff:g id="ARTIST">%s</xliff:g> vil vil bli slettet permanent fra minnekortet."</string>
+    <string name="delete_album_desc">"Hele albumet \"<xliff:g id="ALBUM">%s</xliff:g>\" vil bli slettet permanent fra minnekortet."</string>
+    <string name="delete_song_desc">"\"<xliff:g id="SONG">%s</xliff:g>\" vil bli slettet permanent fra minnekortet."</string>
+    <string name="delete_confirm_button_text">"OK"</string>
+  <plurals name="NNNtracksdeleted">
+    <item quantity="one">"1 sang ble slettet."</item>
+    <item quantity="other">"<xliff:g id="SONGS_TO_DELETE">%d</xliff:g> sang ble slettet."</item>
+  </plurals>
+    <string name="scanning">"Søker i minnekort…"</string>
+    <string name="nowplaying_title">"Spilles nå"</string>
+    <string name="partyshuffle_title">"Tilfeldig"</string>
+    <string name="artists_title">"Artister"</string>
+    <string name="albums_menu">"Album"</string>
+    <string name="albums_title">"Album"</string>
+    <string name="tracks_menu">"Sanger"</string>
+    <string name="tracks_title">"Sanger"</string>
+    <string name="playlists_menu">"Spillelister"</string>
+    <string name="playlists_title">"Spillelister"</string>
+    <string name="videos_title">"Videoer"</string>
+    <string name="all_title">"Alle medier"</string>
+    <string name="browse_menu">"Artister"</string>
+    <string name="search_title">"Søk"</string>
+    <string name="no_tracks_title">"Ingen sanger"</string>
+    <string name="no_videos_title">"Ingen videoer"</string>
+    <string name="no_playlists_title">"Ingen spillelister"</string>
+    <string name="delete_playlist_menu">"Slett"</string>
+    <string name="edit_playlist_menu">"Rediger"</string>
+    <string name="rename_playlist_menu">"Gi nytt navn"</string>
+    <string name="playlist_deleted_message">"Spillelisten ble slettet."</string>
+    <string name="playlist_renamed_message">"Spillelisten fikk nytt navn."</string>
+    <string name="recentlyadded">"Nylig lagt til"</string>
+    <string name="recentlyadded_title">"Nylig lagt til"</string>
+    <string name="podcasts_listitem">"Podcasts"</string>
+    <string name="podcasts_title">"Podcasts"</string>
+    <string name="sdcard_missing_title">"Mangler minnekort"</string>
+    <string name="sdcard_missing_message">"Telefonen har ikke noe minnekort i seg."</string>
+    <string name="sdcard_busy_title">"Minnekort utilgjengelig"</string>
+    <string name="sdcard_busy_message">"Minnekortet er opptatt."</string>
+    <string name="sdcard_error_title">"Feil i minnekort"</string>
+    <string name="sdcard_error_message">"En feil oppsto i minnekortet."</string>
+    <string name="unknown_artist_name">"Ukjent artist"</string>
+    <string name="unknown_album_name">"Ukjent album"</string>
+    <string name="shuffle_on_notif">"Tilfeldig avspilling er på."</string>
+    <string name="shuffle_off_notif">"Tilfeldig avspilling er av."</string>
+    <string name="repeat_off_notif">"Ingen gjentagelse."</string>
+    <string name="repeat_current_notif">"Gjentar denne sangen."</string>
+    <string name="repeat_all_notif">"Gjentar alle sanger."</string>
+    <string name="ringtone_menu">"Bruk som telefonringetone"</string>
+    <string name="ringtone_menu_short">"Bruk som ringetone"</string>
+    <string name="ringtone_set">"\"%s\" valgt som telefonringetone."</string>
+    <string name="play_selection">"Spill"</string>
+    <string name="add_to_playlist">"Legg til spillelisten"</string>
+    <string name="queue">"Gjeldende spilleliste"</string>
+    <string name="new_playlist">"Ny"</string>
+    <string name="new_playlist_name_template">"Ny spilleliste <xliff:g id="NUMBER">%d</xliff:g>"</string>
+  <plurals name="NNNtrackstoplaylist">
+    <item quantity="one">"1 sang ble lagt til spillelisten."</item>
+    <item quantity="other">"%d sanger ble lagt til spillelisten."</item>
+  </plurals>
+    <string name="emptyplaylist">"Den valgte spillelisten er tom."</string>
+    <string name="create_playlist_create_text">"Lagre"</string>
+    <string name="create_playlist_overwrite_text">"Erstatt"</string>
+    <string name="service_start_error_title">"Avspillingsproblem"</string>
+    <string name="service_start_error_msg">"Beklager, kunne ikke spille av sangen."</string>
+    <string name="service_start_error_button">"OK"</string>
+  <string-array name="weeklist">
+    <item>"1 uke"</item>
+    <item>"2 uker"</item>
+    <item>"3 uker"</item>
+    <item>"4 uker"</item>
+    <item>"5 uker"</item>
+    <item>"6 uker"</item>
+    <item>"7 uker"</item>
+    <item>"8 uker"</item>
+    <item>"9 uker"</item>
+    <item>"10 uker"</item>
+    <item>"11 uker"</item>
+    <item>"12 uker"</item>
+  </string-array>
+    <string name="weekpicker_set">"Ferdig"</string>
+    <string name="weekpicker_title">"Velg tid"</string>
+    <string name="save_as_playlist">"Lagre som spilleliste"</string>
+    <string name="clear_playlist">"Tøm spilleliste"</string>
+    <string name="musicbrowserlabel">"Musikk"</string>
+    <string name="musicshortcutlabel">"Musikk-spilleliste"</string>
+    <string name="mediaplaybacklabel">"Musikk"</string>
+    <string name="videobrowserlabel">"Videoer"</string>
+    <string name="mediapickerlabel">"Musikk"</string>
+    <string name="playback_failed">"Beklager, spilleren støtter ikke denne typen lydfiler."</string>
+    <string name="cancel">"Avbryt"</string>
+    <string name="remove_from_playlist">"Fjern fra spilleliste"</string>
+    <string name="streamloadingtext">"Kobler til <xliff:g id="HOST">%s</xliff:g>"</string>
+    <string name="mediasearch">"Søk etter %s ved hjelp av:"</string>
+    <string name="working_artists">"Artister…"</string>
+    <string name="working_albums">"Album…"</string>
+    <string name="working_songs">"Sanger…"</string>
+    <string name="working_playlists">"Spillelister…"</string>
+    <string name="loading">"Laster"</string>
+    <string name="sort_by_track">"Spor"</string>
+    <string name="sort_by_album">"Album"</string>
+    <string name="sort_by_artist">"Artister"</string>
+    <string name="music_picker_title">"Velg musikkspor"</string>
+    <string name="gadget_track">"Spor <xliff:g id="TRACK_NUMBER">%d</xliff:g>"</string>
+</resources>
diff --git a/res/values-nl-keysexposed/strings.xml b/res/values-nl-keysexposed/strings.xml
new file mode 100644
index 0000000..a06ee49
--- /dev/null
+++ b/res/values-nl-keysexposed/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="create_playlist_create_text_prompt">"Naam afspeellijst"</string>
+    <string name="rename_playlist_same_prompt">"Naam van \'<xliff:g id="PLAYLIST">%s</xliff:g>\' wijzigen in"</string>
+    <string name="rename_playlist_diff_prompt">"Naam van \'<xliff:g id="PLAYLIST">%s</xliff:g>\' wijzigen in"</string>
+</resources>
diff --git a/res/values-nl-keyshidden/strings.xml b/res/values-nl-keyshidden/strings.xml
new file mode 100644
index 0000000..26c6e7b
--- /dev/null
+++ b/res/values-nl-keyshidden/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="create_playlist_create_text_prompt">"Open het toetsenbord om uw nieuwe afspeellijst een naam te geven of selecteer \'Opslaan\' om deze de naam \'%s\' te geven."</string>
+    <string name="rename_playlist_same_prompt">"Open het toetsenbord om afspeellijst \'<xliff:g id="PLAYLIST">%s</xliff:g>\' een nieuwe naam te geven."</string>
+    <string name="rename_playlist_diff_prompt">"Open het toetsenbord om afspeellijst \'<xliff:g id="PLAYLIST">%s</xliff:g>\' een nieuwe naam te geven of selecteer \'Opslaan\' om deze de naam \'%s\' te geven."</string>
+</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
new file mode 100644
index 0000000..830b1f7
--- /dev/null
+++ b/res/values-nl/strings.xml
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="onesong">"1 nummer"</string>
+  <plurals name="Nsongs">
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> nummers"</item>
+  </plurals>
+  <plurals name="Nsongscomp">
+    <item quantity="other">"<xliff:g id="COUNT_FOR_ARTIST">%2$d</xliff:g> van <xliff:g id="TOTAL_COUNT">%1$d</xliff:g> nummers"</item>
+  </plurals>
+  <plurals name="Nalbums">
+    <item quantity="one">"1 album"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> albums"</item>
+  </plurals>
+    <string name="goto_start">"Bibliotheek"</string>
+    <string name="goto_playback">"Afspelen"</string>
+    <string name="party_shuffle">"Party shuffle"</string>
+    <string name="party_shuffle_off">"Party shuffle uit"</string>
+    <string name="delete_item">"Verwijderen"</string>
+    <string name="shuffle_all">"Alles in willekeurige volgorde afspelen"</string>
+    <string name="play_all">"Alles afspelen"</string>
+    <string name="delete_artist_desc">"Alle nummers van <xliff:g id="ARTIST">%s</xliff:g> worden definitief van de SD-kaart verwijderd."</string>
+    <string name="delete_album_desc">"Het hele album \'<xliff:g id="ALBUM">%s</xliff:g>\' wordt definitief van de SD-kaart verwijderd."</string>
+    <string name="delete_song_desc">"<xliff:g id="SONG">%s</xliff:g>\' wordt definitief van de SD-kaart verwijderd."</string>
+    <string name="delete_confirm_button_text">"OK"</string>
+  <plurals name="NNNtracksdeleted">
+    <item quantity="one">"1 nummer is verwijderd."</item>
+    <item quantity="other">"<xliff:g id="SONGS_TO_DELETE">%d</xliff:g> nummers zijn verwijderd."</item>
+  </plurals>
+    <string name="scanning">"SD-kaart scannen..."</string>
+    <string name="nowplaying_title">"Wordt nu afgespeeld"</string>
+    <string name="partyshuffle_title">"Party shuffle"</string>
+    <string name="artists_title">"Artiesten"</string>
+    <string name="albums_menu">"Albums"</string>
+    <string name="albums_title">"Albums"</string>
+    <string name="tracks_menu">"Nummers"</string>
+    <string name="tracks_title">"Nummers"</string>
+    <string name="playlists_menu">"Afspeellijsten"</string>
+    <string name="playlists_title">"Afspeellijsten"</string>
+    <string name="videos_title">"Video\'s"</string>
+    <string name="all_title">"Alle media"</string>
+    <string name="browse_menu">"Artiesten"</string>
+    <string name="search_title">"Zoeken"</string>
+    <string name="no_tracks_title">"Geen nummers"</string>
+    <string name="no_videos_title">"Geen video\'s"</string>
+    <string name="no_playlists_title">"Geen afspeellijsten"</string>
+    <string name="delete_playlist_menu">"Verwijderen"</string>
+    <string name="edit_playlist_menu">"Bewerken"</string>
+    <string name="rename_playlist_menu">"Naam wijzigen"</string>
+    <string name="playlist_deleted_message">"Afspeellijst verwijderd."</string>
+    <string name="playlist_renamed_message">"Naam van afspeellijst gewijzigd."</string>
+    <string name="recentlyadded">"Onlangs toegevoegd"</string>
+    <string name="recentlyadded_title">"Onlangs toegevoegd"</string>
+    <string name="podcasts_listitem">"Podcasts"</string>
+    <string name="podcasts_title">"Podcasts"</string>
+    <string name="sdcard_missing_title">"Geen SD-kaart"</string>
+    <string name="sdcard_missing_message">"Er is geen SD-kaart in de telefoon geplaatst."</string>
+    <string name="sdcard_busy_title">"SD-kaart niet beschikbaar"</string>
+    <string name="sdcard_busy_message">"De SD-kaart is in gebruik."</string>
+    <string name="sdcard_error_title">"Fout met SD-kaart"</string>
+    <string name="sdcard_error_message">"Er is een fout opgetreden met de SD-kaart."</string>
+    <string name="unknown_artist_name">"Onbekende artiest"</string>
+    <string name="unknown_album_name">"Onbekend album"</string>
+    <string name="shuffle_on_notif">"Shuffle is ingeschakeld."</string>
+    <string name="shuffle_off_notif">"Shuffle is uitgeschakeld."</string>
+    <string name="repeat_off_notif">"Herhalen is uitgeschakeld."</string>
+    <string name="repeat_current_notif">"Huidig nummer wordt herhaald."</string>
+    <string name="repeat_all_notif">"Alle nummers worden herhaald."</string>
+    <string name="ringtone_menu">"Gebruiken als beltoon van telefoon"</string>
+    <string name="ringtone_menu_short">"Gebruiken als beltoon"</string>
+    <string name="ringtone_set">"%s\' ingesteld als beltoon van telefoon."</string>
+    <string name="play_selection">"Afspelen"</string>
+    <string name="add_to_playlist">"Toevoegen aan afspeellijst"</string>
+    <string name="queue">"Huidige afspeellijst"</string>
+    <string name="new_playlist">"Nieuw"</string>
+    <string name="new_playlist_name_template">"Nieuwe afspeellijst <xliff:g id="NUMBER">%d</xliff:g>"</string>
+  <plurals name="NNNtrackstoplaylist">
+    <item quantity="one">"1 nummer toegevoegd aan afspeellijst."</item>
+    <item quantity="other">"%d nummers toegevoegd aan afspeellijst."</item>
+  </plurals>
+    <string name="emptyplaylist">"Geselecteerde afspeellijst is leeg."</string>
+    <string name="create_playlist_create_text">"Opslaan"</string>
+    <string name="create_playlist_overwrite_text">"Overschrijven"</string>
+    <string name="service_start_error_title">"Probleem met afspelen"</string>
+    <string name="service_start_error_msg">"Het nummer kan niet worden afgespeeld."</string>
+    <string name="service_start_error_button">"OK"</string>
+  <string-array name="weeklist">
+    <item>"1 week"</item>
+    <item>"2 weken"</item>
+    <item>"3 weken"</item>
+    <item>"4 weken"</item>
+    <item>"5 weken"</item>
+    <item>"6 weken"</item>
+    <item>"7 weken"</item>
+    <item>"8 weken"</item>
+    <item>"9 weken"</item>
+    <item>"10 weken"</item>
+    <item>"11 weken"</item>
+    <item>"12 weken"</item>
+  </string-array>
+    <string name="weekpicker_set">"Gereed"</string>
+    <string name="weekpicker_title">"Tijd instellen"</string>
+    <string name="save_as_playlist">"Opslaan als afspeellijst"</string>
+    <string name="clear_playlist">"Afspeellijst wissen"</string>
+    <string name="musicbrowserlabel">"Muziek"</string>
+    <string name="musicshortcutlabel">"Afspeellijst voor muziek"</string>
+    <string name="mediaplaybacklabel">"Muziek"</string>
+    <string name="videobrowserlabel">"Video\'s"</string>
+    <string name="mediapickerlabel">"Muziek"</string>
+    <string name="playback_failed">"De speler ondersteunt dit type audiobestand niet."</string>
+    <string name="cancel">"Annuleren"</string>
+    <string name="remove_from_playlist">"Verwijderen uit afspeellijst"</string>
+    <string name="streamloadingtext">"Verbinding maken met <xliff:g id="HOST">%s</xliff:g>"</string>
+    <string name="mediasearch">"Zoeken naar %s via:"</string>
+    <string name="working_artists">"Artiesten…"</string>
+    <string name="working_albums">"Albums…"</string>
+    <string name="working_songs">"Nummers…"</string>
+    <string name="working_playlists">"Afspeellijsten…"</string>
+    <string name="loading">"Laden"</string>
+    <string name="sort_by_track">"Tracks"</string>
+    <string name="sort_by_album">"Albums"</string>
+    <string name="sort_by_artist">"Artiesten"</string>
+    <string name="music_picker_title">"Muziektrack selecteren"</string>
+    <string name="gadget_track">"Track <xliff:g id="TRACK_NUMBER">%d</xliff:g>"</string>
+</resources>
diff --git a/res/values-pl-keysexposed/strings.xml b/res/values-pl-keysexposed/strings.xml
new file mode 100644
index 0000000..d4294bb
--- /dev/null
+++ b/res/values-pl-keysexposed/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="create_playlist_create_text_prompt">"Nazwa playlisty"</string>
+    <string name="rename_playlist_same_prompt">"Zmień nazwę „<xliff:g id="PLAYLIST">%s</xliff:g>” na"</string>
+    <string name="rename_playlist_diff_prompt">"Zmień nazwę „<xliff:g id="PLAYLIST">%s</xliff:g>” na"</string>
+</resources>
diff --git a/res/values-pl-keyshidden/strings.xml b/res/values-pl-keyshidden/strings.xml
new file mode 100644
index 0000000..8dcbae0
--- /dev/null
+++ b/res/values-pl-keyshidden/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="create_playlist_create_text_prompt">"Otwórz klawiaturę, aby nazwać nową playlistę lub wybierz polecenie Zapisz, aby nazwać ją „%s”."</string>
+    <string name="rename_playlist_same_prompt">"Otwórz klawiaturę, aby nadać nową nazwę playliście „<xliff:g id="PLAYLIST">%s</xliff:g>”."</string>
+    <string name="rename_playlist_diff_prompt">"Otwórz klawiaturę, aby nadać playliście „<xliff:g id="PLAYLIST">%s</xliff:g>” nową nazwę lub wybierz polecenie Zapisz, aby nazwać ją „%s”."</string>
+</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
new file mode 100644
index 0000000..1b5a390
--- /dev/null
+++ b/res/values-pl/strings.xml
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="onesong">"1 utwór"</string>
+  <plurals name="Nsongs">
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> utworów"</item>
+  </plurals>
+  <plurals name="Nsongscomp">
+    <item quantity="other">"<xliff:g id="COUNT_FOR_ARTIST">%2$d</xliff:g> z <xliff:g id="TOTAL_COUNT">%1$d</xliff:g> utworów"</item>
+  </plurals>
+  <plurals name="Nalbums">
+    <item quantity="one">"1 album"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> albumów"</item>
+  </plurals>
+    <string name="goto_start">"Biblioteka"</string>
+    <string name="goto_playback">"Odtwórz"</string>
+    <string name="party_shuffle">"Losowo w trybie imprezy"</string>
+    <string name="party_shuffle_off">"Wyłączono losowe odtwarzanie w trybie imprezy"</string>
+    <string name="delete_item">"Usuń"</string>
+    <string name="shuffle_all">"Wszystkie losowo"</string>
+    <string name="play_all">"Odtwórz wszystko"</string>
+    <string name="delete_artist_desc">"Wszystkie utwory wykonawcy <xliff:g id="ARTIST">%s</xliff:g> zostaną na stałe usunięte z karty SD."</string>
+    <string name="delete_album_desc">"Cały album „<xliff:g id="ALBUM">%s</xliff:g>” zostanie na stałe usunięty z karty SD."</string>
+    <string name="delete_song_desc">"Utwór „<xliff:g id="SONG">%s</xliff:g>” zostanie na stałe usunięty z karty SD."</string>
+    <string name="delete_confirm_button_text">"OK"</string>
+  <plurals name="NNNtracksdeleted">
+    <item quantity="one">"1 utwór został usunięty."</item>
+    <item quantity="other">"Liczba usuniętych utworów: <xliff:g id="SONGS_TO_DELETE">%d</xliff:g>."</item>
+  </plurals>
+    <string name="scanning">"Skanowanie karty SD..."</string>
+    <string name="nowplaying_title">"Teraz odtwarzane"</string>
+    <string name="partyshuffle_title">"Losowo w trybie imprezy"</string>
+    <string name="artists_title">"Wykonawcy"</string>
+    <string name="albums_menu">"Albumy"</string>
+    <string name="albums_title">"Albumy"</string>
+    <string name="tracks_menu">"Utwory"</string>
+    <string name="tracks_title">"Utwory"</string>
+    <string name="playlists_menu">"Playlisty"</string>
+    <string name="playlists_title">"Playlisty"</string>
+    <string name="videos_title">"Filmy wideo"</string>
+    <string name="all_title">"Wszystkie multimedia"</string>
+    <string name="browse_menu">"Wykonawcy"</string>
+    <string name="search_title">"Szukaj"</string>
+    <string name="no_tracks_title">"Brak utworów"</string>
+    <string name="no_videos_title">"Brak filmów wideo"</string>
+    <string name="no_playlists_title">"Brak playlist"</string>
+    <string name="delete_playlist_menu">"Usuń"</string>
+    <string name="edit_playlist_menu">"Edytuj"</string>
+    <string name="rename_playlist_menu">"Zmień nazwę"</string>
+    <string name="playlist_deleted_message">"Playlista została usunięta."</string>
+    <string name="playlist_renamed_message">"Zmieniono nazwę playlisty."</string>
+    <string name="recentlyadded">"Ostatnio dodane"</string>
+    <string name="recentlyadded_title">"Ostatnio dodane"</string>
+    <string name="podcasts_listitem">"Podcasty"</string>
+    <string name="podcasts_title">"Podcasty"</string>
+    <string name="sdcard_missing_title">"Brak karty SD"</string>
+    <string name="sdcard_missing_message">"Brak karty SD w telefonie."</string>
+    <string name="sdcard_busy_title">"Karta SD jest niedostępna"</string>
+    <string name="sdcard_busy_message">"Niestety, karta SD jest zajęta."</string>
+    <string name="sdcard_error_title">"Błąd karty SD"</string>
+    <string name="sdcard_error_message">"Napotkano błąd na karcie SD."</string>
+    <string name="unknown_artist_name">"Nieznany wykonawca"</string>
+    <string name="unknown_album_name">"Nieznany album"</string>
+    <string name="shuffle_on_notif">"Odtwarzanie losowe włączone"</string>
+    <string name="shuffle_off_notif">"Odtwarzanie losowe wyłączone"</string>
+    <string name="repeat_off_notif">"Powtarzanie jest wyłączone."</string>
+    <string name="repeat_current_notif">"Powtarzanie obecnego utworu."</string>
+    <string name="repeat_all_notif">"Powtarzanie wszystkich utworów."</string>
+    <string name="ringtone_menu">"Ustaw jako dzwonek telefonu"</string>
+    <string name="ringtone_menu_short">"Ustaw jako dzwonek"</string>
+    <string name="ringtone_set">"„%s” ustawiono jako dzwonek telefonu."</string>
+    <string name="play_selection">"Odtwórz"</string>
+    <string name="add_to_playlist">"Dodaj do playlisty"</string>
+    <string name="queue">"Bieżąca playlista"</string>
+    <string name="new_playlist">"Nowy"</string>
+    <string name="new_playlist_name_template">"Nowa playlista <xliff:g id="NUMBER">%d</xliff:g>"</string>
+  <plurals name="NNNtrackstoplaylist">
+    <item quantity="one">"Dodano 1 utwór do playlisty."</item>
+    <item quantity="other">"%d utworów dodano do listy odtwarzania."</item>
+  </plurals>
+    <string name="emptyplaylist">"Wybrana playlista jest pusta."</string>
+    <string name="create_playlist_create_text">"Zapisz"</string>
+    <string name="create_playlist_overwrite_text">"Zastąp"</string>
+    <string name="service_start_error_title">"Problem z odtwarzaniem"</string>
+    <string name="service_start_error_msg">"Niestety, nie można odtworzyć utworu."</string>
+    <string name="service_start_error_button">"OK"</string>
+  <string-array name="weeklist">
+    <item>"tydzień"</item>
+    <item>"2 tygodnie"</item>
+    <item>"3 tygodnie"</item>
+    <item>"4 tygodnie"</item>
+    <item>"5 tygodni"</item>
+    <item>"6 tygodni"</item>
+    <item>"7 tygodni"</item>
+    <item>"8 tygodni"</item>
+    <item>"9 tygodni"</item>
+    <item>"10 tygodni"</item>
+    <item>"11 tygodni"</item>
+    <item>"12 tygodni"</item>
+  </string-array>
+    <string name="weekpicker_set">"Gotowe"</string>
+    <string name="weekpicker_title">"Ustaw czas"</string>
+    <string name="save_as_playlist">"Zapisz jako playlistę"</string>
+    <string name="clear_playlist">"Wyczyść playlistę"</string>
+    <string name="musicbrowserlabel">"Muzyka"</string>
+    <string name="musicshortcutlabel">"Playlista muzyczna"</string>
+    <string name="mediaplaybacklabel">"Muzyka"</string>
+    <string name="videobrowserlabel">"Filmy wideo"</string>
+    <string name="mediapickerlabel">"Muzyka"</string>
+    <string name="playback_failed">"Niestety, odtwarzacz nie obsługuje plików audio tego typu."</string>
+    <string name="cancel">"Anuluj"</string>
+    <string name="remove_from_playlist">"Usuń z playlisty"</string>
+    <string name="streamloadingtext">"Łączenie z: <xliff:g id="HOST">%s</xliff:g>"</string>
+    <string name="mediasearch">"Wyszukaj %s z wykorzystaniem:"</string>
+    <string name="working_artists">"Wykonawcy…"</string>
+    <string name="working_albums">"Albumy…"</string>
+    <string name="working_songs">"Utwory…"</string>
+    <string name="working_playlists">"Listy odtwarzania..."</string>
+    <string name="loading">"Ładowanie"</string>
+    <string name="sort_by_track">"Utwory"</string>
+    <string name="sort_by_album">"Albumy"</string>
+    <string name="sort_by_artist">"Wykonawcy"</string>
+    <string name="music_picker_title">"Wybierz utwór muzyczny"</string>
+    <string name="gadget_track">"Ścieżka <xliff:g id="TRACK_NUMBER">%d</xliff:g>"</string>
+</resources>
diff --git a/res/values-ru-keysexposed/strings.xml b/res/values-ru-keysexposed/strings.xml
new file mode 100644
index 0000000..f87495e
--- /dev/null
+++ b/res/values-ru-keysexposed/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="create_playlist_create_text_prompt">"Название плейлиста"</string>
+    <string name="rename_playlist_same_prompt">"Переименовать \"<xliff:g id="PLAYLIST">%s</xliff:g>\" в"</string>
+    <string name="rename_playlist_diff_prompt">"Переименовать \"<xliff:g id="PLAYLIST">%s</xliff:g>\" в"</string>
+</resources>
diff --git a/res/values-ru-keyshidden/strings.xml b/res/values-ru-keyshidden/strings.xml
new file mode 100644
index 0000000..8be9846
--- /dev/null
+++ b/res/values-ru-keyshidden/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="create_playlist_create_text_prompt">"Введите название нового плейлиста с клавиатуры или нажмите Сохранить, чтобы назвать его \"%s\"."</string>
+    <string name="rename_playlist_same_prompt">"Введите новое название плейлиста \"<xliff:g id="PLAYLIST">%s</xliff:g>\" с клавиатуры."</string>
+    <string name="rename_playlist_diff_prompt">"Введите новое название плейлиста \"<xliff:g id="PLAYLIST">%s</xliff:g>\" с клавиатуры или нажмите Сохранить, чтобы назвать его \"%s\"."</string>
+</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
new file mode 100644
index 0000000..cbdac78
--- /dev/null
+++ b/res/values-ru/strings.xml
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="onesong">"1 композиция"</string>
+  <plurals name="Nsongs">
+    <item quantity="other">"Композиций: <xliff:g id="COUNT">%d</xliff:g>"</item>
+  </plurals>
+  <plurals name="Nsongscomp">
+    <item quantity="other">"<xliff:g id="COUNT_FOR_ARTIST">%2$d</xliff:g> из <xliff:g id="TOTAL_COUNT">%1$d</xliff:g> песен"</item>
+  </plurals>
+  <plurals name="Nalbums">
+    <item quantity="one">"1 альбом"</item>
+    <item quantity="other">"Альбомов: <xliff:g id="COUNT">%d</xliff:g>"</item>
+  </plurals>
+    <string name="goto_start">"Библиотека"</string>
+    <string name="goto_playback">"Воспроизведение"</string>
+    <string name="party_shuffle">"Вечеринка"</string>
+    <string name="party_shuffle_off">"Режим вечеринки отключен"</string>
+    <string name="delete_item">"Удалить"</string>
+    <string name="shuffle_all">"Перемешать все"</string>
+    <string name="play_all">"Воспроизвести все"</string>
+    <string name="delete_artist_desc">"Все композиции артиста <xliff:g id="ARTIST">%s</xliff:g> будут удалены с карты SD."</string>
+    <string name="delete_album_desc">"Весь альбом \"<xliff:g id="ALBUM">%s</xliff:g>\" будет удален с карты SD."</string>
+    <string name="delete_song_desc">"Композиция \"<xliff:g id="SONG">%s</xliff:g>\" будет удалена с карты SD."</string>
+    <string name="delete_confirm_button_text">"ОК"</string>
+  <plurals name="NNNtracksdeleted">
+    <item quantity="one">"1 композиция была удалена."</item>
+    <item quantity="other">"Композиции (<xliff:g id="SONGS_TO_DELETE">%d</xliff:g>) были удалены."</item>
+  </plurals>
+    <string name="scanning">"Сканирование карты SD…"</string>
+    <string name="nowplaying_title">"Воспроизводится"</string>
+    <string name="partyshuffle_title">"Вечеринка"</string>
+    <string name="artists_title">"Артисты"</string>
+    <string name="albums_menu">"Альбомы"</string>
+    <string name="albums_title">"Альбомы"</string>
+    <string name="tracks_menu">"Композиции"</string>
+    <string name="tracks_title">"Композиции"</string>
+    <string name="playlists_menu">"Плейлисты"</string>
+    <string name="playlists_title">"Плейлисты"</string>
+    <string name="videos_title">"Видео"</string>
+    <string name="all_title">"Все мультимедийное содержание"</string>
+    <string name="browse_menu">"Артисты"</string>
+    <string name="search_title">"Поиск"</string>
+    <string name="no_tracks_title">"Нет композиций"</string>
+    <string name="no_videos_title">"Нет видео"</string>
+    <string name="no_playlists_title">"Нет плейлистов"</string>
+    <string name="delete_playlist_menu">"Удалить"</string>
+    <string name="edit_playlist_menu">"Изменить"</string>
+    <string name="rename_playlist_menu">"Переименовать"</string>
+    <string name="playlist_deleted_message">"Плейлист удален."</string>
+    <string name="playlist_renamed_message">"Плейлист переименован."</string>
+    <string name="recentlyadded">"Недавно добавленные"</string>
+    <string name="recentlyadded_title">"Недавно добавленные"</string>
+    <string name="podcasts_listitem">"Подкасты"</string>
+    <string name="podcasts_title">"Подкасты"</string>
+    <string name="sdcard_missing_title">"Нет карты SD"</string>
+    <string name="sdcard_missing_message">"В телефоне не установлена карта SD."</string>
+    <string name="sdcard_busy_title">"Карта SD недоступна"</string>
+    <string name="sdcard_busy_message">"К сожалению, карта SD занята."</string>
+    <string name="sdcard_error_title">"Ошибка карты SD"</string>
+    <string name="sdcard_error_message">"Ошибка при доступе к карте SD."</string>
+    <string name="unknown_artist_name">"Неизвестный артист"</string>
+    <string name="unknown_album_name">"Неизвестный альбом"</string>
+    <string name="shuffle_on_notif">"Перемешивание включено."</string>
+    <string name="shuffle_off_notif">"Перемешивание отключено."</string>
+    <string name="repeat_off_notif">"Повтор отключен."</string>
+    <string name="repeat_current_notif">"Повтор текущей композиции."</string>
+    <string name="repeat_all_notif">"Повтор всех композиций."</string>
+    <string name="ringtone_menu">"Использовать как мелодию звонка телефона"</string>
+    <string name="ringtone_menu_short">"Использовать как мелодию звонка"</string>
+    <string name="ringtone_set">"Установлена мелодия звонка телефона: \"%s\"."</string>
+    <string name="play_selection">"Воспроизвести"</string>
+    <string name="add_to_playlist">"Добавить в плейлист"</string>
+    <string name="queue">"Текущий плейлист"</string>
+    <string name="new_playlist">"Создать"</string>
+    <string name="new_playlist_name_template">"Новый плейлист <xliff:g id="NUMBER">%d</xliff:g>"</string>
+  <plurals name="NNNtrackstoplaylist">
+    <item quantity="one">"1 композиция добавлена в плейлист."</item>
+    <item quantity="other">"Композиции (%d) добавлены в плейлист."</item>
+  </plurals>
+    <string name="emptyplaylist">"Выбранный плейлист пуст."</string>
+    <string name="create_playlist_create_text">"Сохранить"</string>
+    <string name="create_playlist_overwrite_text">"Перезаписать"</string>
+    <string name="service_start_error_title">"Проблема с воспроизведением"</string>
+    <string name="service_start_error_msg">"К сожалению, воспроизвести композицию не удалось."</string>
+    <string name="service_start_error_button">"ОК"</string>
+  <string-array name="weeklist">
+    <item>"1 неделя"</item>
+    <item>"2 недели"</item>
+    <item>"3 недели"</item>
+    <item>"4 недели"</item>
+    <item>"5 недель"</item>
+    <item>"6 недель"</item>
+    <item>"7 недель"</item>
+    <item>"8 недель"</item>
+    <item>"9 недель"</item>
+    <item>"10 недель"</item>
+    <item>"11 недель"</item>
+    <item>"12 недель"</item>
+  </string-array>
+    <string name="weekpicker_set">"Готово"</string>
+    <string name="weekpicker_title">"Выбор времени"</string>
+    <string name="save_as_playlist">"Сохранить как плейлист"</string>
+    <string name="clear_playlist">"Очистить плейлист"</string>
+    <string name="musicbrowserlabel">"Музыка"</string>
+    <string name="musicshortcutlabel">"Музыкальный плейлист"</string>
+    <string name="mediaplaybacklabel">"Музыка"</string>
+    <string name="videobrowserlabel">"Видео"</string>
+    <string name="mediapickerlabel">"Музыка"</string>
+    <string name="playback_failed">"Извините, проигрыватель не поддерживает этот тип аудиофайлов."</string>
+    <string name="cancel">"Отмена"</string>
+    <string name="remove_from_playlist">"Удалить из плейлиста"</string>
+    <string name="streamloadingtext">"Идет подключение к хосту <xliff:g id="HOST">%s</xliff:g>"</string>
+    <string name="mediasearch">"Поиск %s с помощью:"</string>
+    <string name="working_artists">"Артисты…"</string>
+    <string name="working_albums">"Альбомы…"</string>
+    <string name="working_songs">"Композиции…"</string>
+    <string name="working_playlists">"Плейлисты..."</string>
+    <string name="loading">"Идет загрузка"</string>
+    <string name="sort_by_track">"Дорожки"</string>
+    <string name="sort_by_album">"Альбомы"</string>
+    <string name="sort_by_artist">"Артисты"</string>
+    <string name="music_picker_title">"Выбрать музыкальную дорожку"</string>
+    <string name="gadget_track">"Трек <xliff:g id="TRACK_NUMBER">%d</xliff:g>"</string>
+</resources>
diff --git a/res/values-zh-rCN-keysexposed/strings.xml b/res/values-zh-rCN-keysexposed/strings.xml
new file mode 100644
index 0000000..048c393
--- /dev/null
+++ b/res/values-zh-rCN-keysexposed/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="create_playlist_create_text_prompt">"播放列表名称"</string>
+    <string name="rename_playlist_same_prompt">"将“<xliff:g id="PLAYLIST">%s</xliff:g>”重命名为"</string>
+    <string name="rename_playlist_diff_prompt">"将“<xliff:g id="PLAYLIST">%s</xliff:g>”重命名为"</string>
+</resources>
diff --git a/res/values-zh-rCN-keyshidden/strings.xml b/res/values-zh-rCN-keyshidden/strings.xml
new file mode 100644
index 0000000..787bade
--- /dev/null
+++ b/res/values-zh-rCN-keyshidden/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="create_playlist_create_text_prompt">"打开键盘以为新播放列表命名,或选择“保存”将其命名为“%s”。"</string>
+    <string name="rename_playlist_same_prompt">"打开键盘以赋予播放列表“<xliff:g id="PLAYLIST">%s</xliff:g>”新名称。"</string>
+    <string name="rename_playlist_diff_prompt">"打开键盘以赋予播放列表“<xliff:g id="PLAYLIST">%s</xliff:g>”新名称,或选择“保存”将其命名为“%s”。"</string>
+</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..fb7ca95
--- /dev/null
+++ b/res/values-zh-rCN/strings.xml
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="onesong">"1 首歌曲"</string>
+  <plurals name="Nsongs">
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 首歌曲"</item>
+  </plurals>
+  <plurals name="Nsongscomp">
+    <item quantity="other">"共 <xliff:g id="TOTAL_COUNT">%1$d</xliff:g> 首歌曲,其中 <xliff:g id="COUNT_FOR_ARTIST">%2$d</xliff:g> 首"</item>
+  </plurals>
+  <plurals name="Nalbums">
+    <item quantity="one">"1 个专辑"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 个专辑"</item>
+  </plurals>
+    <string name="goto_start">"音乐库"</string>
+    <string name="goto_playback">"播放"</string>
+    <string name="party_shuffle">"派对随机播放"</string>
+    <string name="party_shuffle_off">"派对随机播放已关闭"</string>
+    <string name="delete_item">"删除"</string>
+    <string name="shuffle_all">"全部随机播放"</string>
+    <string name="play_all">"全部播放"</string>
+    <string name="delete_artist_desc">"<xliff:g id="ARTIST">%s</xliff:g>的所有歌曲都会从 SD 卡中永久删除。"</string>
+    <string name="delete_album_desc">"整个专辑“<xliff:g id="ALBUM">%s</xliff:g>”会从 SD 卡中永久删除。"</string>
+    <string name="delete_song_desc">"“<xliff:g id="SONG">%s</xliff:g>”会从 SD 卡中永久删除。"</string>
+    <string name="delete_confirm_button_text">"确定"</string>
+  <plurals name="NNNtracksdeleted">
+    <item quantity="one">"已删除 1 首歌曲。"</item>
+    <item quantity="other">"已删除 <xliff:g id="SONGS_TO_DELETE">%d</xliff:g> 首歌曲。"</item>
+  </plurals>
+    <string name="scanning">"正在扫描 SD 卡..."</string>
+    <string name="nowplaying_title">"正在播放"</string>
+    <string name="partyshuffle_title">"派对随机播放"</string>
+    <string name="artists_title">"艺术家"</string>
+    <string name="albums_menu">"专辑"</string>
+    <string name="albums_title">"专辑"</string>
+    <string name="tracks_menu">"歌曲"</string>
+    <string name="tracks_title">"歌曲"</string>
+    <string name="playlists_menu">"播放列表"</string>
+    <string name="playlists_title">"播放列表"</string>
+    <string name="videos_title">"视频"</string>
+    <string name="all_title">"所有媒体"</string>
+    <string name="browse_menu">"艺术家"</string>
+    <string name="search_title">"搜索"</string>
+    <string name="no_tracks_title">"无歌曲"</string>
+    <string name="no_videos_title">"无视频"</string>
+    <string name="no_playlists_title">"无播放列表"</string>
+    <string name="delete_playlist_menu">"删除"</string>
+    <string name="edit_playlist_menu">"编辑"</string>
+    <string name="rename_playlist_menu">"重命名"</string>
+    <string name="playlist_deleted_message">"已删除播放列表。"</string>
+    <string name="playlist_renamed_message">"已重命名播放列表。"</string>
+    <string name="recentlyadded">"最近添加的歌曲"</string>
+    <string name="recentlyadded_title">"最近添加的"</string>
+    <string name="podcasts_listitem">"播客"</string>
+    <string name="podcasts_title">"播客"</string>
+    <string name="sdcard_missing_title">"无 SD 卡"</string>
+    <string name="sdcard_missing_message">"您的手机未插入 SD 卡。"</string>
+    <string name="sdcard_busy_title">"SD 卡不可用"</string>
+    <string name="sdcard_busy_message">"很抱歉,SD 卡正忙。"</string>
+    <string name="sdcard_error_title">"SD 卡错误"</string>
+    <string name="sdcard_error_message">"SD 卡出现错误。"</string>
+    <string name="unknown_artist_name">"未知艺术家"</string>
+    <string name="unknown_album_name">"未知专辑"</string>
+    <string name="shuffle_on_notif">"随机播放已打开。"</string>
+    <string name="shuffle_off_notif">"随机播放已关闭。"</string>
+    <string name="repeat_off_notif">"重复播放已关闭。"</string>
+    <string name="repeat_current_notif">"正在重复播放当前的歌曲。"</string>
+    <string name="repeat_all_notif">"重复播放所有歌曲。"</string>
+    <string name="ringtone_menu">"用作手机铃声"</string>
+    <string name="ringtone_menu_short">"用作铃声"</string>
+    <string name="ringtone_set">"“%s”已设为手机铃声。"</string>
+    <string name="play_selection">"播放"</string>
+    <string name="add_to_playlist">"添加至播放列表"</string>
+    <string name="queue">"当前的播放列表"</string>
+    <string name="new_playlist">"新建"</string>
+    <string name="new_playlist_name_template">"新播放列表 <xliff:g id="NUMBER">%d</xliff:g>"</string>
+  <plurals name="NNNtrackstoplaylist">
+    <item quantity="one">"已向播放列表添加了 1 首歌曲。"</item>
+    <item quantity="other">"%d 首歌曲已添加至播放列表。"</item>
+  </plurals>
+    <string name="emptyplaylist">"选择的播放列表为空。"</string>
+    <string name="create_playlist_create_text">"保存"</string>
+    <string name="create_playlist_overwrite_text">"覆盖"</string>
+    <string name="service_start_error_title">"播放问题"</string>
+    <string name="service_start_error_msg">"很抱歉,无法播放此歌曲。"</string>
+    <string name="service_start_error_button">"确定"</string>
+  <string-array name="weeklist">
+    <item>"1 周"</item>
+    <item>"2 周"</item>
+    <item>"3 周"</item>
+    <item>"4 周"</item>
+    <item>"5 周"</item>
+    <item>"6 周"</item>
+    <item>"7 周"</item>
+    <item>"8 周"</item>
+    <item>"9 周"</item>
+    <item>"10 周"</item>
+    <item>"11 周"</item>
+    <item>"12 周"</item>
+  </string-array>
+    <string name="weekpicker_set">"完成"</string>
+    <string name="weekpicker_title">"设置时间"</string>
+    <string name="save_as_playlist">"另存为播放列表"</string>
+    <string name="clear_playlist">"清除播放列表"</string>
+    <string name="musicbrowserlabel">"音乐"</string>
+    <string name="musicshortcutlabel">"音乐播放列表"</string>
+    <string name="mediaplaybacklabel">"音乐"</string>
+    <string name="videobrowserlabel">"视频"</string>
+    <string name="mediapickerlabel">"音乐"</string>
+    <string name="playback_failed">"很抱歉,此播放器不支持这种类型的音频文件。"</string>
+    <string name="cancel">"取消"</string>
+    <string name="remove_from_playlist">"从播放列表中删除"</string>
+    <string name="streamloadingtext">"正连接至 <xliff:g id="HOST">%s</xliff:g>"</string>
+    <string name="mediasearch">"使用以下内容搜索 %s:"</string>
+    <string name="working_artists">"艺术家..."</string>
+    <string name="working_albums">"专辑..."</string>
+    <string name="working_songs">"歌曲..."</string>
+    <string name="working_playlists">"播放列表..."</string>
+    <string name="loading">"正在载入"</string>
+    <string name="sort_by_track">"曲目"</string>
+    <string name="sort_by_album">"专辑"</string>
+    <string name="sort_by_artist">"艺术家"</string>
+    <string name="music_picker_title">"选择曲目"</string>
+    <string name="gadget_track">"曲目 <xliff:g id="TRACK_NUMBER">%d</xliff:g>"</string>
+</resources>
diff --git a/res/values-zh-rTW-keysexposed/strings.xml b/res/values-zh-rTW-keysexposed/strings.xml
new file mode 100644
index 0000000..2034d1a
--- /dev/null
+++ b/res/values-zh-rTW-keysexposed/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="create_playlist_create_text_prompt">"播放清單名稱"</string>
+    <string name="rename_playlist_same_prompt">"將「<xliff:g id="PLAYLIST">%s</xliff:g>」重新命名為"</string>
+    <string name="rename_playlist_diff_prompt">"將「<xliff:g id="PLAYLIST">%s</xliff:g>」重新命名為"</string>
+</resources>
diff --git a/res/values-zh-rTW-keyshidden/strings.xml b/res/values-zh-rTW-keyshidden/strings.xml
new file mode 100644
index 0000000..09f596e
--- /dev/null
+++ b/res/values-zh-rTW-keyshidden/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="create_playlist_create_text_prompt">"開啟鍵盤為新 [播放清單] 命名,或選取 [儲存] 將之命名為「%s」。"</string>
+    <string name="rename_playlist_same_prompt">"開啟鍵盤以重新命名「<xliff:g id="PLAYLIST">%s</xliff:g>」播放清單。"</string>
+    <string name="rename_playlist_diff_prompt">"開啟鍵盤以重新命名「<xliff:g id="PLAYLIST">%s</xliff:g>」播放清單,或選取 [儲存] 將之命名為「%s」。"</string>
+</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..35ab3b5
--- /dev/null
+++ b/res/values-zh-rTW/strings.xml
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="onesong">"1 首歌曲"</string>
+  <plurals name="Nsongs">
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 首歌曲"</item>
+  </plurals>
+  <plurals name="Nsongscomp">
+    <item quantity="other">"專輯的 <xliff:g id="TOTAL_COUNT">%1$d</xliff:g> 首歌曲中,有 <xliff:g id="COUNT_FOR_ARTIST">%2$d</xliff:g> 首該演出者的歌曲"</item>
+  </plurals>
+  <plurals name="Nalbums">
+    <item quantity="one">"1 張專輯"</item>
+    <item quantity="other">"<xliff:g id="COUNT">%d</xliff:g> 專輯"</item>
+  </plurals>
+    <string name="goto_start">"媒體庫"</string>
+    <string name="goto_playback">"播放內容"</string>
+    <string name="party_shuffle">"派對隨機播放"</string>
+    <string name="party_shuffle_off">"關閉派對隨機播放"</string>
+    <string name="delete_item">"刪除"</string>
+    <string name="shuffle_all">"全部隨機播放"</string>
+    <string name="play_all">"全部播放"</string>
+    <string name="delete_artist_desc">"<xliff:g id="ARTIST">%s</xliff:g> 演唱的所有歌曲會從 SD 卡永久刪除。"</string>
+    <string name="delete_album_desc">"「<xliff:g id="ALBUM">%s</xliff:g>」的所有內容會從 SD 卡永久刪除。"</string>
+    <string name="delete_song_desc">"「<xliff:g id="SONG">%s</xliff:g>」會從 SD 卡永久刪除。"</string>
+    <string name="delete_confirm_button_text">"確定"</string>
+  <plurals name="NNNtracksdeleted">
+    <item quantity="one">"已刪除 1 首歌曲。"</item>
+    <item quantity="other">"已刪除 <xliff:g id="SONGS_TO_DELETE">%d</xliff:g> 首歌曲。"</item>
+  </plurals>
+    <string name="scanning">"掃描 SD 卡中..."</string>
+    <string name="nowplaying_title">"目前播放"</string>
+    <string name="partyshuffle_title">"派對隨機播放"</string>
+    <string name="artists_title">"演唱者"</string>
+    <string name="albums_menu">"專輯"</string>
+    <string name="albums_title">"專輯"</string>
+    <string name="tracks_menu">"歌曲"</string>
+    <string name="tracks_title">"歌曲"</string>
+    <string name="playlists_menu">"播放清單"</string>
+    <string name="playlists_title">"播放清單"</string>
+    <string name="videos_title">"影片"</string>
+    <string name="all_title">"所有媒體"</string>
+    <string name="browse_menu">"演唱者"</string>
+    <string name="search_title">"搜尋"</string>
+    <string name="no_tracks_title">"沒有歌曲"</string>
+    <string name="no_videos_title">"沒有影片"</string>
+    <string name="no_playlists_title">"沒有播放清單"</string>
+    <string name="delete_playlist_menu">"刪除"</string>
+    <string name="edit_playlist_menu">"編輯"</string>
+    <string name="rename_playlist_menu">"重新命名"</string>
+    <string name="playlist_deleted_message">"播放清單已刪除。"</string>
+    <string name="playlist_renamed_message">"已重新命名播放清單。"</string>
+    <string name="recentlyadded">"最近新增的項目"</string>
+    <string name="recentlyadded_title">"最近新增的歌曲"</string>
+    <string name="podcasts_listitem">"Podcast"</string>
+    <string name="podcasts_title">"Podcast"</string>
+    <string name="sdcard_missing_title">"沒有 SD 卡"</string>
+    <string name="sdcard_missing_message">"您的手機未插入 SD 卡。"</string>
+    <string name="sdcard_busy_title">"無法使用 SD 卡"</string>
+    <string name="sdcard_busy_message">"抱歉,SD 卡忙碌中。"</string>
+    <string name="sdcard_error_title">"SD 卡錯誤"</string>
+    <string name="sdcard_error_message">"SD 卡發生錯誤。"</string>
+    <string name="unknown_artist_name">"未知的演唱者"</string>
+    <string name="unknown_album_name">"未知的專輯"</string>
+    <string name="shuffle_on_notif">"已開啟隨機播放。"</string>
+    <string name="shuffle_off_notif">"已關閉隨機播放。"</string>
+    <string name="repeat_off_notif">"已關閉重複播放。"</string>
+    <string name="repeat_current_notif">"重複播放目前歌曲。"</string>
+    <string name="repeat_all_notif">"重複播放所有歌曲。"</string>
+    <string name="ringtone_menu">"設成來電鈴聲"</string>
+    <string name="ringtone_menu_short">"設成鈴聲"</string>
+    <string name="ringtone_set">"已將「%s」設為來電鈴聲。"</string>
+    <string name="play_selection">"播放"</string>
+    <string name="add_to_playlist">"新增至播放清單"</string>
+    <string name="queue">"目前播放清單"</string>
+    <string name="new_playlist">"新增"</string>
+    <string name="new_playlist_name_template">"新播放清單 <xliff:g id="NUMBER">%d</xliff:g>"</string>
+  <plurals name="NNNtrackstoplaylist">
+    <item quantity="one">"已將 1 首歌曲加入播放清單。"</item>
+    <item quantity="other">"%d 歌曲已新增至播放清單。"</item>
+  </plurals>
+    <string name="emptyplaylist">"您選取的播放清單是空的。"</string>
+    <string name="create_playlist_create_text">"儲存"</string>
+    <string name="create_playlist_overwrite_text">"覆寫"</string>
+    <string name="service_start_error_title">"播放內容問題"</string>
+    <string name="service_start_error_msg">"抱歉,此歌曲無法播放。"</string>
+    <string name="service_start_error_button">"確定"</string>
+  <string-array name="weeklist">
+    <item>"1 週"</item>
+    <item>"2 週"</item>
+    <item>"3 週"</item>
+    <item>"4 週"</item>
+    <item>"5 週"</item>
+    <item>"6 週"</item>
+    <item>"7 週"</item>
+    <item>"8 週"</item>
+    <item>"9 週"</item>
+    <item>"10 週"</item>
+    <item>"11 週"</item>
+    <item>"12 週"</item>
+  </string-array>
+    <string name="weekpicker_set">"完成"</string>
+    <string name="weekpicker_title">"設定時間"</string>
+    <string name="save_as_playlist">"另存為播放清單"</string>
+    <string name="clear_playlist">"清除播放清單"</string>
+    <string name="musicbrowserlabel">"音樂"</string>
+    <string name="musicshortcutlabel">"音樂播放清單"</string>
+    <string name="mediaplaybacklabel">"音樂"</string>
+    <string name="videobrowserlabel">"影片"</string>
+    <string name="mediapickerlabel">"音樂"</string>
+    <string name="playback_failed">"抱歉,撥放器不支援此音訊檔格式。"</string>
+    <string name="cancel">"取消"</string>
+    <string name="remove_from_playlist">"從播放清單移除"</string>
+    <string name="streamloadingtext">"連線到 <xliff:g id="HOST">%s</xliff:g>"</string>
+    <string name="mediasearch">"搜尋 %s,使用:"</string>
+    <string name="working_artists">"演唱者..."</string>
+    <string name="working_albums">"專輯..."</string>
+    <string name="working_songs">"歌曲..."</string>
+    <string name="working_playlists">"播放清單..."</string>
+    <string name="loading">"載入中"</string>
+    <string name="sort_by_track">"曲目"</string>
+    <string name="sort_by_album">"專輯"</string>
+    <string name="sort_by_artist">"演唱者"</string>
+    <string name="music_picker_title">"選取音樂曲目"</string>
+    <string name="gadget_track">"音軌 <xliff:g id="TRACK_NUMBER">%d</xliff:g>"</string>
+</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
new file mode 100644
index 0000000..5ebc209
--- /dev/null
+++ b/res/values/colors.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <color name="gadget_text">#ffffffff</color>
+</resources>
+
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
new file mode 100644
index 0000000..d7d705d
--- /dev/null
+++ b/res/values/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <!-- Size of gadget album art cutout -->
+    <dimen name="gadget_cutout">198dip</dimen>
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 0000000..855b443
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,286 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- used in various places to indicate there is one song for a given artist or album -->
+    <string name="onesong">1 song</string>
+    <!-- used in various places to indicate there is some number other than one songs for a given artist or album. -->
+    <plurals name="Nsongs">
+        <item quantity="other"><xliff:g id="count">%d</xliff:g> songs</item>
+    </plurals>
+
+    <!-- shows how many songs on the album are by the selected artist, out of how many total, if those two numbers are different -->
+    <plurals name="Nsongscomp">
+        <item quantity="other"><xliff:g id="count_for_artist">%2$d</xliff:g> of <xliff:g id="total_count">%1$d</xliff:g> songs</item>
+    </plurals>
+
+    <!-- Used in artists list view, indicates how many albums exist for a given artist. -->
+    <plurals name="Nalbums">
+        <!-- number of albums is one -->
+        <item quantity="one">1 album</item>
+        <!-- number of albums is not equal to one -->
+        <item quantity="other"><xliff:g id="count">%d</xliff:g> albums</item>
+    </plurals>
+    
+    <!--
+    This string is used as the format string in a String.format call,
+    and 5 additional arguments are available for printing:
+    1 - hours
+    2 - minutes
+    3 - minutes%60
+    4 - seconds
+    5 - second%60
+    -->
+    <skip/>
+    <!-- Do not translate. Duration format. -->
+    <string name="durationformat"><xliff:g id="format">%2$d:%5$02d</xliff:g></string>
+
+    <!-- Menu item that takes the user back to the top level screen of the music player -->
+    <string name="goto_start">Library</string>
+    <!-- Menu item that takes the user to the "now playing" screen of the music player -->
+    <string name="goto_playback">Playback</string>
+    <!-- Menu item that switches the music player in to party shuffle mode -->
+    <string name="party_shuffle">Party shuffle</string>
+    <!-- Menu item that switches the music player out of party shuffle mode -->
+    <string name="party_shuffle_off">Party shuffle off</string>
+    <!-- Menu item that deletes the currently selected item, which might be a single song, or a collection of songs.
+         The user will be prompted to confirm before deletion actually takes place -->
+    <string name="delete_item">Delete</string>
+    <!-- Menu item to play back all the songs in the currently showing list in shuffle mode -->
+    <string name="shuffle_all">Shuffle all</string>
+    <!-- Menu item to play back all the songs in the currently showing list in list order -->
+    <string name="play_all">Play all</string>
+
+    <!-- Delete confirmation dialog when deleting an entire artist -->
+    <string name="delete_artist_desc">All songs by <xliff:g id="artist">%s</xliff:g> will be permanently deleted from the SD card.</string>
+
+    <!-- Delete confirmation dialog when deleting an entire album -->
+    <string name="delete_album_desc">The entire album \"<xliff:g id="album">%s</xliff:g>\" will be permanently deleted from the SD card.</string>
+
+    <!-- Delete confirmation dialog when deleting a single song -->
+    <string name="delete_song_desc">\"<xliff:g id="song">%s</xliff:g>\" will be permanently deleted from the SD card.</string>
+
+    <!-- Delete confirmation dialog, confirmation button text -->
+    <string name="delete_confirm_button_text">OK</string>
+    <!-- Toast confirming that song(s) was/were deleted. -->
+    <plurals name="NNNtracksdeleted">
+        <!-- delete confirmation message for 1 song -->
+        <item quantity="one">1 song was deleted.</item>
+        <!-- delete confirmation message for 0 or more than 1 songs -->
+        <item quantity="other"><xliff:g id="songs_to_delete">%d</xliff:g> songs were deleted.</item>
+    </plurals>
+
+    <!-- shown in dialog while the media scanner is starting up -->
+    <string name="scanning">Scanning SD card\u2026</string>
+
+    <!-- title of the "current playlist" screen when not in party shuffle mode -->
+    <string name="nowplaying_title">Now playing</string>
+    <!-- title of the "current playlist" screen when in party shuffle mode-->
+    <string name="partyshuffle_title">Party shuffle</string>
+    <!-- Artist screen title -->
+    <string name="artists_title">Artists</string>
+    <!-- Category label on Library screen -->
+    <string name="albums_menu">Albums</string>
+    <!-- Albums screen title -->
+    <string name="albums_title">Albums</string>
+    <!-- Category label on Library screen -->
+    <string name="tracks_menu">Songs</string>
+    <!-- Songs screen title -->
+    <string name="tracks_title">Songs</string>
+    <!-- Category label on Library screen -->
+    <string name="playlists_menu">Playlists</string>
+    <!-- Playlists screen title -->
+    <string name="playlists_title">Playlists</string>
+    <!-- Videos screen title -->
+    <string name="videos_title">Videos</string>
+    <!-- All media screen title -->
+    <string name="all_title">All media</string>
+    <!-- Category label on Library screen -->
+    <string name="browse_menu">Artists</string>
+    <!-- Library screen, menu item -->
+    <string name="search_title">Search</string>
+    <!-- Title of screen when there are no songs, or if the SD card is busy -->
+    <string name="no_tracks_title">No songs</string>
+    <!-- Title of screen when there are no videos, or if the SD card is busy -->
+    <string name="no_videos_title">No videos</string>
+    <!-- Title of screen when there are no playlists, or if the SD card is busy -->
+    <string name="no_playlists_title">No playlists</string>
+    <!-- Playlist context menu item to delete the selected playlist. -->
+    <string name="delete_playlist_menu">Delete</string>
+    <!-- Playlist context menu item to edit the selected playlist -->
+    <string name="edit_playlist_menu">Edit</string>
+    <!-- Playlist context menu item to rename the selected playlist-->
+    <string name="rename_playlist_menu">Rename</string>
+    <!-- Transient popup message shown after deleting a playlist -->
+    <string name="playlist_deleted_message">Playlist deleted.</string>
+    <!-- Transient popup message shown after renaming a playlist -->
+    <string name="playlist_renamed_message">Playlist renamed.</string>
+    <!-- The name of the pseudo-playlist that holds all the recently added files, shown in list view -->
+    <string name="recentlyadded">Recently added</string>
+    <!-- The name of the pseudo-playlist that holds all the recently added files, shown in title bar of songs list -->
+    <string name="recentlyadded_title">Recently added</string>
+    <!-- The name of the pseudo-playlist that holds all the podcasts, shown in list view -->
+    <string name="podcasts_listitem">Podcasts</string>
+    <!-- The name of the pseudo-playlist that holds all the podcasts, shown in title bar of songs list -->
+    <string name="podcasts_title">Podcasts</string>
+    <!-- Title of screen when no sd card is present -->
+    <string name="sdcard_missing_title">No SD card</string>
+    <!-- label underneath icon used to indicate that no sd card is present -->
+    <string name="sdcard_missing_message">Your phone does not have an SD card inserted.</string>
+    <!-- label underneath icon used to indicate that the sd card is present, but currently unavailable -->
+    <string name="sdcard_busy_title">SD card unavailable</string>
+    <!-- label underneath icon used to indicate sd card is mounted to your computer via USB -->
+    <string name="sdcard_busy_message">Sorry, your SD card is busy.</string>
+    <!-- Title of screen when there was an error accessing the sd card -->
+    <string name="sdcard_error_title">SD card error</string>
+    <!-- label underneath icon used to indicate there was an error accessing the sd card -->
+    <string name="sdcard_error_message">An error was encountered on your SD card.</string>
+    <!-- Default name of artist that doesn't have a name in the metadata -->
+    <string name="unknown_artist_name">Unknown artist</string>
+    <!-- Default name of album that doesn't have a name in the metadata -->
+    <string name="unknown_album_name">Unknown album</string>
+    <!-- Toast after turning shuffle on -->
+    <string name="shuffle_on_notif">Shuffle is on.</string>
+    <!-- Toast after turning shuffle off -->
+    <string name="shuffle_off_notif">Shuffle is off.</string>
+    <!-- Toast after turning repeat off -->
+    <string name="repeat_off_notif">Repeat is off.</string>
+    <!-- Toast after turning single repeat on -->
+    <string name="repeat_current_notif">Repeating current song.</string>
+    <!-- Toast after turning repeat all on -->
+    <string name="repeat_all_notif">Repeating all songs.</string>
+    <!-- Individual song context menu item -->
+    <string name="ringtone_menu">Use as phone ringtone</string>
+    <!-- Menu item -->
+    <string name="ringtone_menu_short">Use as ringtone</string>
+    <!-- Toast after setting a song as phone ringtone -->
+    <string name="ringtone_set">\"%s\" set as phone ringtone.</string>
+    <!-- Context menu item -->
+    <string name="play_selection">Play</string>
+    <!-- Context menu item -->
+    <string name="add_to_playlist">Add to playlist</string>
+    <!-- Context menu item -->
+    <string name="queue">Current playlist</string>
+    <!-- Context menu item -->
+    <string name="new_playlist">New</string>
+    <!-- Template for newly created playlist name -->
+    <string name="new_playlist_name_template">New playlist <xliff:g id="number">%d</xliff:g></string>
+    <!-- Toasts after adding song(s) to playlists -->
+    <plurals name="NNNtrackstoplaylist">
+        <!-- message shown when one song was added -->
+        <item quantity="one">1 song added to playlist.</item>
+        <!-- message shown when zero or more than one song was added -->
+        <item quantity="other">%d songs added to playlist.</item>
+    </plurals>
+    <!-- Toast after selecting an empty playlist -->
+    <string name="emptyplaylist">Selected playlist is empty.</string>
+    <!-- Button name when saving a playlist -->
+    <string name="create_playlist_create_text">Save</string>
+    <!-- Button name when saving a playlist and the new playlist will overwrite an existing one -->
+    <string name="create_playlist_overwrite_text">Overwrite</string>
+    <!-- Dialog box title -->
+    <string name="service_start_error_title">Playback problem</string>
+    <!-- Dialog box message -->
+    <string name="service_start_error_msg">Sorry, the song could not be played.</string>
+    <!-- Dialog box button -->
+    <string name="service_start_error_button">OK</string>
+    <!-- Time span edit options that appear when editing system playlist "Recently added" -->
+    <!-- Used to indicate the number of weeks the "recently added" playlist covers in a selector widget -->
+    <string-array name="weeklist">
+        <item>"1 week"</item>
+        <item>"2 weeks"</item>
+        <item>"3 weeks"</item>
+        <item>"4 weeks"</item>
+        <item>"5 weeks"</item>
+        <item>"6 weeks"</item>
+        <item>"7 weeks"</item>
+        <item>"8 weeks"</item>
+        <item>"9 weeks"</item>
+        <item>"10 weeks"</item>
+        <item>"11 weeks"</item>
+        <item>"12 weeks"</item>
+    </string-array>
+    <!-- Button name in time span picker -->
+    <string name="weekpicker_set">Done</string>
+    <!-- Title of time span picker -->
+    <string name="weekpicker_title">Set time</string>
+    <!-- Do not translate. Background color for currently dragged item in playlist edit mode. -->
+    <color name="dragndrop_background">#e0103010</color>
+    <!-- Do not translate. Background color for albums in the artists list view. -->
+    <color name="expanding_child_background">#ff404040</color>
+
+    <!-- menu item to save the current list as a new playlist -->
+    <string name="save_as_playlist">Save as playlist</string>
+    <!-- menu item to clear the current playlist -->
+    <string name="clear_playlist">Clear playlist</string>
+
+    <!-- Activity label. This might show up in the activity-picker -->
+    <string name="musicbrowserlabel">Music</string>
+    <!-- Activity label. This might show up in the activity-picker -->
+    <string name="musicshortcutlabel">Music playlist</string>
+    <!-- Activity label. This might show up in the activity-picker -->
+    <string name="mediaplaybacklabel">Music</string>
+    <!-- Activity label. This might show up in the activity-picker -->
+    <string name="videobrowserlabel">Videos</string>
+    <!-- Activity label. This might show up in the activity-picker -->
+    <string name="mediapickerlabel">Music</string>
+
+    <!-- Shown as a transient message whenever a file fails to play -->
+    <string name="playback_failed">Sorry, the player does not support this type of audio file.</string>
+
+    <!-- Text for the "cancel" button in the "delete" and "create playlist" confirmation dialogs -->
+    <string name="cancel">Cancel</string>
+
+    <!-- context menu item to remove the selected item from the playlist -->
+    <string name="remove_from_playlist">Remove from playlist</string>
+
+    <!-- shown when connecting to a music stream, before it starts playing -->
+    <string name="streamloadingtext">Connecting to <xliff:g id="host">%s</xliff:g></string>
+
+    <!-- title of contextual music search menu -->
+    <string name="mediasearch">Search for %s using:</string>
+
+    <!-- Shown in the title bar while the list of artists is being retrieved in the background -->
+    <string name="working_artists">Artists\u2026</string>
+    <!-- Shown in the title bar while the list of albums is being retrieved in the background -->
+    <string name="working_albums">Albums\u2026</string>
+    <!-- Shown in the title bar while the list of songs is being retrieved in the background -->
+    <string name="working_songs">Songs\u2026</string>
+    <!-- Shown in the title bar while the list of playlists is being retrieved in the background -->
+    <string name="working_playlists">Playlists\u2026</string>
+    
+    <!-- Shown in the music picker while loading the music database. -->
+    <string name="loading">Loading</string>
+    <!-- Menu in music picker to sort the list by track/song name. -->
+    <string name="sort_by_track">Tracks</string>
+    <!-- Menu in music picker to sort the list by album name. -->
+    <string name="sort_by_album">Albums</string>
+    <!-- Menu in music picker to sort the list by artist name. -->
+    <string name="sort_by_artist">Artists</string>
+    <!--  Title of the music picker activity. -->
+    <string name="music_picker_title">Select music track</string>
+
+    <!-- Title for track number in music gadget -->
+    <string name="gadget_track">Track <xliff:g id="track_number">%d</xliff:g></string>
+
+    <!-- Mixes together track number and track title in music gadget -->
+    <string name="gadget_track_num_title" translatable="false"><xliff:g id="track_number">%d</xliff:g>. <xliff:g id="track_title">%s</xliff:g></string>
+</resources>
+
diff --git a/res/values/strings2.xml b/res/values/strings2.xml
new file mode 100644
index 0000000..4cbf119
--- /dev/null
+++ b/res/values/strings2.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+-->
+
+<resources>
+    <!-- Do not translate. This is the separator character used when building the string that shows number of albums and songs. -->
+    <string name="albumsongseparator">, </string>
+</resources>
+
diff --git a/res/xml/gadget_info.xml b/res/xml/gadget_info.xml
new file mode 100644
index 0000000..bbaeaa3
--- /dev/null
+++ b/res/xml/gadget_info.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<gadget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+    android:minWidth="294dip"
+    android:minHeight="72dip"
+    android:updatePeriodMillis="0"
+    android:initialLayout="@layout/gadget"
+    >
+</gadget-provider>
diff --git a/res/xml/searchable.xml b/res/xml/searchable.xml
new file mode 100644
index 0000000..1bff884
--- /dev/null
+++ b/res/xml/searchable.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<searchable xmlns:android="http://schemas.android.com/apk/res/android"
+    android:label="@string/search_title"
+
+    android:searchSuggestAuthority="media"
+    android:searchSuggestPath="external/audio"
+    android:searchSuggestIntentAction="android.intent.action.VIEW"
+/>
diff --git a/src/com/android/music/AlbumBrowserActivity.java b/src/com/android/music/AlbumBrowserActivity.java
new file mode 100644
index 0000000..c28b7da
--- /dev/null
+++ b/src/com/android/music/AlbumBrowserActivity.java
@@ -0,0 +1,638 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.app.ListActivity;
+import android.app.SearchManager;
+import android.content.AsyncQueryHandler;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.media.MediaFile;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.MediaStore;
+import android.view.ContextMenu;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.SubMenu;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.Adapter;
+import android.widget.AlphabetIndexer;
+import android.widget.CursorAdapter;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.SectionIndexer;
+import android.widget.SimpleCursorAdapter;
+import android.widget.TextView;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+
+import java.text.Collator;
+
+public class AlbumBrowserActivity extends ListActivity
+    implements View.OnCreateContextMenuListener, MusicUtils.Defs
+{
+    private String mCurrentAlbumId;
+    private String mCurrentAlbumName;
+    private String mCurrentArtistNameForAlbum;
+    private AlbumListAdapter mAdapter;
+    private boolean mAdapterSent;
+    private final static int SEARCH = CHILD_MENU_BASE;
+
+    public AlbumBrowserActivity()
+    {
+    }
+
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle icicle)
+    {
+        if (icicle != null) {
+            mCurrentAlbumId = icicle.getString("selectedalbum");
+            mArtistId = icicle.getString("artist");
+        } else {
+            mArtistId = getIntent().getStringExtra("artist");
+        }
+        super.onCreate(icicle);
+        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+        setVolumeControlStream(AudioManager.STREAM_MUSIC);
+        MusicUtils.bindToService(this);
+
+        IntentFilter f = new IntentFilter();
+        f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
+        f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
+        f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
+        f.addDataScheme("file");
+        registerReceiver(mScanListener, f);
+
+        setContentView(R.layout.media_picker_activity);
+        ListView lv = getListView();
+        lv.setFastScrollEnabled(true);
+        lv.setOnCreateContextMenuListener(this);
+        lv.setTextFilterEnabled(true);
+
+        mAdapter = (AlbumListAdapter) getLastNonConfigurationInstance();
+        if (mAdapter == null) {
+            //Log.i("@@@", "starting query");
+            mAdapter = new AlbumListAdapter(
+                    getApplication(),
+                    this,
+                    R.layout.track_list_item,
+                    mAlbumCursor,
+                    new String[] {},
+                    new int[] {});
+            setListAdapter(mAdapter);
+            setTitle(R.string.working_albums);
+            getAlbumCursor(mAdapter.getQueryHandler(), null);
+        } else {
+            mAdapter.setActivity(this);
+            setListAdapter(mAdapter);
+            mAlbumCursor = mAdapter.getCursor();
+            if (mAlbumCursor != null) {
+                init(mAlbumCursor);
+            } else {
+                getAlbumCursor(mAdapter.getQueryHandler(), null);
+            }
+        }
+    }
+
+    @Override
+    public Object onRetainNonConfigurationInstance() {
+        mAdapterSent = true;
+        return mAdapter;
+    }
+    
+    @Override
+    public void onSaveInstanceState(Bundle outcicle) {
+        // need to store the selected item so we don't lose it in case
+        // of an orientation switch. Otherwise we could lose it while
+        // in the middle of specifying a playlist to add the item to.
+        outcicle.putString("selectedalbum", mCurrentAlbumId);
+        outcicle.putString("artist", mArtistId);
+        super.onSaveInstanceState(outcicle);
+    }
+
+    @Override
+    public void onDestroy() {
+        MusicUtils.unbindFromService(this);
+        if (!mAdapterSent) {
+            Cursor c = mAdapter.getCursor();
+            if (c != null) {
+                c.close();
+            }
+        }
+        unregisterReceiver(mScanListener);
+        super.onDestroy();
+    }
+    
+    @Override
+    public void onResume() {
+        super.onResume();
+        IntentFilter f = new IntentFilter();
+        f.addAction(MediaPlaybackService.META_CHANGED);
+        f.addAction(MediaPlaybackService.QUEUE_CHANGED);
+        registerReceiver(mTrackListListener, f);
+        mTrackListListener.onReceive(null, null);
+
+        MusicUtils.setSpinnerState(this);
+    }
+
+    private BroadcastReceiver mTrackListListener = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            getListView().invalidateViews();
+        }
+    };
+    private BroadcastReceiver mScanListener = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            MusicUtils.setSpinnerState(AlbumBrowserActivity.this);
+            mReScanHandler.sendEmptyMessage(0);
+            if (intent.getAction().equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
+                MusicUtils.clearAlbumArtCache();
+            }
+        }
+    };
+    
+    private Handler mReScanHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            getAlbumCursor(mAdapter.getQueryHandler(), null);
+        }
+    };
+
+    @Override
+    public void onPause() {
+        unregisterReceiver(mTrackListListener);
+        mReScanHandler.removeCallbacksAndMessages(null);
+        super.onPause();
+    }
+
+    public void init(Cursor c) {
+
+        mAdapter.changeCursor(c); // also sets mAlbumCursor
+
+        if (mAlbumCursor == null) {
+            MusicUtils.displayDatabaseError(this);
+            closeContextMenu();
+            mReScanHandler.sendEmptyMessageDelayed(0, 1000);
+            return;
+        }
+        
+        MusicUtils.hideDatabaseError(this);
+        setTitle();
+    }
+
+    private void setTitle() {
+        CharSequence fancyName = "";
+        if (mAlbumCursor != null && mAlbumCursor.getCount() > 0) {
+            mAlbumCursor.moveToFirst();
+            fancyName = mAlbumCursor.getString(3);
+            if (MediaFile.UNKNOWN_STRING.equals(fancyName))
+                fancyName = getText(R.string.unknown_artist_name);
+        }
+
+        if (mArtistId != null && fancyName != null)
+            setTitle(fancyName);
+        else
+            setTitle(R.string.albums_title);
+    }
+    
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
+        menu.add(0, PLAY_SELECTION, 0, R.string.play_selection);
+        SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0, R.string.add_to_playlist);
+        MusicUtils.makePlaylistMenu(this, sub);
+        menu.add(0, DELETE_ITEM, 0, R.string.delete_item);
+        menu.add(0, SEARCH, 0, R.string.search_title);
+
+        AdapterContextMenuInfo mi = (AdapterContextMenuInfo) menuInfoIn;
+        mAlbumCursor.moveToPosition(mi.position);
+        mCurrentAlbumId = mAlbumCursor.getString(mAlbumCursor.getColumnIndexOrThrow(MediaStore.Audio.Albums._ID));
+        mCurrentAlbumName = mAlbumCursor.getString(mAlbumCursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));
+        mCurrentArtistNameForAlbum = mAlbumCursor.getString(
+                mAlbumCursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ARTIST));
+        menu.setHeaderTitle(mCurrentAlbumName);
+    }
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case PLAY_SELECTION: {
+                // play the selected album
+                int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
+                MusicUtils.playAll(this, list, 0);
+                return true;
+            }
+
+            case QUEUE: {
+                int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
+                MusicUtils.addToCurrentPlaylist(this, list);
+                return true;
+            }
+
+            case NEW_PLAYLIST: {
+                Intent intent = new Intent();
+                intent.setClass(this, CreatePlaylist.class);
+                startActivityForResult(intent, NEW_PLAYLIST);
+                return true;
+            }
+
+            case PLAYLIST_SELECTED: {
+                int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
+                int playlist = item.getIntent().getIntExtra("playlist", 0);
+                MusicUtils.addToPlaylist(this, list, playlist);
+                return true;
+            }
+            case DELETE_ITEM: {
+                int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
+                String f = getString(R.string.delete_album_desc); 
+                String desc = String.format(f, mCurrentAlbumName);
+                Bundle b = new Bundle();
+                b.putString("description", desc);
+                b.putIntArray("items", list);
+                Intent intent = new Intent();
+                intent.setClass(this, DeleteItems.class);
+                intent.putExtras(b);
+                startActivityForResult(intent, -1);
+                return true;
+            }
+            case SEARCH:
+                doSearch();
+                return true;
+
+        }
+        return super.onContextItemSelected(item);
+    }
+
+    void doSearch() {
+        CharSequence title = null;
+        String query = null;
+        
+        Intent i = new Intent();
+        i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH);
+        
+        title = mCurrentAlbumName;
+        query = mCurrentArtistNameForAlbum + " " + mCurrentAlbumName;
+        i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistNameForAlbum);
+        i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, mCurrentAlbumName);
+        i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE);
+        title = getString(R.string.mediasearch, title);
+        i.putExtra(SearchManager.QUERY, query);
+
+        startActivity(Intent.createChooser(i, title));
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+        switch (requestCode) {
+            case SCAN_DONE:
+                if (resultCode == RESULT_CANCELED) {
+                    finish();
+                } else {
+                    getAlbumCursor(mAdapter.getQueryHandler(), null);
+                }
+                break;
+
+            case NEW_PLAYLIST:
+                if (resultCode == RESULT_OK) {
+                    Uri uri = intent.getData();
+                    if (uri != null) {
+                        int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
+                        MusicUtils.addToPlaylist(this, list, Integer.parseInt(uri.getLastPathSegment()));
+                    }
+                }
+                break;
+        }
+    }
+
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id)
+    {
+        if (mHasHeader) {
+            position --;
+        }
+        Intent intent = new Intent(Intent.ACTION_PICK);
+        intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
+        intent.putExtra("album", Long.valueOf(id).toString());
+        intent.putExtra("artist", mArtistId);
+        startActivity(intent);
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+        menu.add(0, GOTO_START, 0, R.string.goto_start).setIcon(R.drawable.ic_menu_music_library);
+        menu.add(0, GOTO_PLAYBACK, 0, R.string.goto_playback).setIcon(R.drawable.ic_menu_playback);
+        menu.add(0, SHUFFLE_ALL, 0, R.string.shuffle_all).setIcon(R.drawable.ic_menu_shuffle);
+        return true;
+    }
+
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        menu.findItem(GOTO_PLAYBACK).setVisible(MusicUtils.isMusicLoaded());
+        return super.onPrepareOptionsMenu(menu);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        Intent intent;
+        Cursor cursor;
+        switch (item.getItemId()) {
+            case GOTO_START:
+                intent = new Intent();
+                intent.setClass(this, MusicBrowserActivity.class);
+                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                startActivity(intent);
+                return true;
+
+            case GOTO_PLAYBACK:
+                intent = new Intent("com.android.music.PLAYBACK_VIEWER");
+                startActivity(intent);
+                return true;
+
+            case SHUFFLE_ALL:
+                cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                        new String [] { MediaStore.Audio.Media._ID},
+                        MediaStore.Audio.Media.IS_MUSIC + "=1", null,
+                        MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
+                if (cursor != null) {
+                    MusicUtils.shuffleAll(this, cursor);
+                    cursor.close();
+                }
+                return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    private Cursor getAlbumCursor(AsyncQueryHandler async, String filter) {
+        StringBuilder where = new StringBuilder();
+        where.append(MediaStore.Audio.Albums.ALBUM + " != ''");
+        
+        // Add in the filtering constraints
+        String [] keywords = null;
+        if (filter != null) {
+            String [] searchWords = filter.split(" ");
+            keywords = new String[searchWords.length];
+            Collator col = Collator.getInstance();
+            col.setStrength(Collator.PRIMARY);
+            for (int i = 0; i < searchWords.length; i++) {
+                keywords[i] = '%' + MediaStore.Audio.keyFor(searchWords[i]) + '%';
+            }
+            for (int i = 0; i < searchWords.length; i++) {
+                where.append(" AND ");
+                where.append(MediaStore.Audio.Media.ARTIST_KEY + "||");
+                where.append(MediaStore.Audio.Media.ALBUM_KEY + " LIKE ?");
+            }
+        }
+
+        String whereclause = where.toString();  
+            
+        String[] cols = new String[] {
+                MediaStore.Audio.Albums._ID,
+                MediaStore.Audio.Albums.ALBUM,
+                MediaStore.Audio.Albums.ALBUM_KEY,
+                MediaStore.Audio.Albums.ARTIST,
+                MediaStore.Audio.Albums.NUMBER_OF_SONGS,
+                MediaStore.Audio.Albums.ALBUM_ART
+        };
+        Cursor ret = null;
+        if (mArtistId != null) {
+            if (async != null) {
+                async.startQuery(0, null,
+                        MediaStore.Audio.Artists.Albums.getContentUri("external",
+                                Long.valueOf(mArtistId)),
+                        cols, whereclause, keywords, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
+            } else {
+                ret = MusicUtils.query(this,
+                        MediaStore.Audio.Artists.Albums.getContentUri("external",
+                                Long.valueOf(mArtistId)),
+                        cols, whereclause, keywords, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
+            }
+        } else {
+            if (async != null) {
+                async.startQuery(0, null,
+                        MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
+                        cols, whereclause, keywords, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
+            } else {
+                ret = MusicUtils.query(this, MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
+                        cols, whereclause, keywords, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
+            }
+        }
+        return ret;
+    }
+    
+    static class AlbumListAdapter extends SimpleCursorAdapter implements SectionIndexer {
+        
+        private final Drawable mNowPlayingOverlay;
+        private final BitmapDrawable mDefaultAlbumIcon;
+        private int mAlbumIdx;
+        private int mArtistIdx;
+        private int mNumSongsIdx;
+        private int mAlbumArtIndex;
+        private final Resources mResources;
+        private final StringBuilder mStringBuilder = new StringBuilder();
+        private final String mUnknownAlbum;
+        private final String mUnknownArtist;
+        private final String mAlbumSongSeparator;
+        private final Object[] mFormatArgs = new Object[1];
+        private AlphabetIndexer mIndexer;
+        private AlbumBrowserActivity mActivity;
+        private AsyncQueryHandler mQueryHandler;
+        private String mConstraint = null;
+        private boolean mConstraintIsValid = false;
+        
+        class ViewHolder {
+            TextView line1;
+            TextView line2;
+            TextView duration;
+            ImageView play_indicator;
+            ImageView icon;
+        }
+
+        class QueryHandler extends AsyncQueryHandler {
+            QueryHandler(ContentResolver res) {
+                super(res);
+            }
+            
+            @Override
+            protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+                //Log.i("@@@", "query complete");
+                mActivity.init(cursor);
+            }
+        }
+
+        AlbumListAdapter(Context context, AlbumBrowserActivity currentactivity,
+                int layout, Cursor cursor, String[] from, int[] to) {
+            super(context, layout, cursor, from, to);
+
+            mActivity = currentactivity;
+            mQueryHandler = new QueryHandler(context.getContentResolver());
+            
+            mUnknownAlbum = context.getString(R.string.unknown_album_name);
+            mUnknownArtist = context.getString(R.string.unknown_artist_name);
+            mAlbumSongSeparator = context.getString(R.string.albumsongseparator);
+
+            Resources r = context.getResources();
+            mNowPlayingOverlay = r.getDrawable(R.drawable.indicator_ic_mp_playing_list);
+
+            Bitmap b = BitmapFactory.decodeResource(r, R.drawable.albumart_mp_unknown_list);
+            mDefaultAlbumIcon = new BitmapDrawable(b);
+            // no filter or dither, it's a lot faster and we can't tell the difference
+            mDefaultAlbumIcon.setFilterBitmap(false);
+            mDefaultAlbumIcon.setDither(false);
+            getColumnIndices(cursor);
+            mResources = context.getResources();
+        }
+
+        private void getColumnIndices(Cursor cursor) {
+            if (cursor != null) {
+                mAlbumIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM);
+                mArtistIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ARTIST);
+                mNumSongsIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.NUMBER_OF_SONGS);
+                mAlbumArtIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM_ART);
+                
+                if (mIndexer != null) {
+                    mIndexer.setCursor(cursor);
+                } else {
+                    mIndexer = new MusicAlphabetIndexer(cursor, mAlbumIdx, mResources.getString(
+                            com.android.internal.R.string.fast_scroll_alphabet));
+                }
+            }
+        }
+        
+        public void setActivity(AlbumBrowserActivity newactivity) {
+            mActivity = newactivity;
+        }
+        
+        public AsyncQueryHandler getQueryHandler() {
+            return mQueryHandler;
+        }
+
+        @Override
+        public View newView(Context context, Cursor cursor, ViewGroup parent) {
+           View v = super.newView(context, cursor, parent);
+           ViewHolder vh = new ViewHolder();
+           vh.line1 = (TextView) v.findViewById(R.id.line1);
+           vh.line2 = (TextView) v.findViewById(R.id.line2);
+           vh.duration = (TextView) v.findViewById(R.id.duration);
+           vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator);
+           vh.icon = (ImageView) v.findViewById(R.id.icon);
+           vh.icon.setBackgroundDrawable(mDefaultAlbumIcon);
+           vh.icon.setPadding(0, 0, 1, 0);
+           v.setTag(vh);
+           return v;
+        }
+
+        @Override
+        public void bindView(View view, Context context, Cursor cursor) {
+            
+            ViewHolder vh = (ViewHolder) view.getTag();
+
+            String name = cursor.getString(mAlbumIdx);
+            String displayname = name;
+            boolean unknown = name.equals(MediaFile.UNKNOWN_STRING); 
+            if (unknown) {
+                displayname = mUnknownAlbum;
+            }
+            vh.line1.setText(displayname);
+            
+            name = cursor.getString(mArtistIdx);
+            displayname = name;
+            if (MediaFile.UNKNOWN_STRING.equals(name)) {
+                displayname = mUnknownArtist;
+            }
+            vh.line2.setText(displayname);
+
+            ImageView iv = vh.icon;
+            // We don't actually need the path to the thumbnail file,
+            // we just use it to see if there is album art or not
+            String art = cursor.getString(mAlbumArtIndex);
+            if (unknown || art == null || art.length() == 0) {
+                iv.setImageDrawable(null);
+            } else {
+                int artIndex = cursor.getInt(0);
+                Drawable d = MusicUtils.getCachedArtwork(context, artIndex, mDefaultAlbumIcon);
+                iv.setImageDrawable(d);
+            }
+            
+            int currentalbumid = MusicUtils.getCurrentAlbumId();
+            int aid = cursor.getInt(0);
+            iv = vh.play_indicator;
+            if (currentalbumid == aid) {
+                iv.setImageDrawable(mNowPlayingOverlay);
+            } else {
+                iv.setImageDrawable(null);
+            }
+        }
+        
+        @Override
+        public void changeCursor(Cursor cursor) {
+            if (cursor != mActivity.mAlbumCursor) {
+                mActivity.mAlbumCursor = cursor;
+                getColumnIndices(cursor);
+                super.changeCursor(cursor);
+            }
+        }
+        
+        @Override
+        public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
+            String s = constraint.toString();
+            if (mConstraintIsValid && (
+                    (s == null && mConstraint == null) ||
+                    (s != null && s.equals(mConstraint)))) {
+                return getCursor();
+            }
+            Cursor c = mActivity.getAlbumCursor(null, s);
+            mConstraint = s;
+            mConstraintIsValid = true;
+            return c;
+        }
+        
+        public Object[] getSections() {
+            return mIndexer.getSections();
+        }
+        
+        public int getPositionForSection(int section) {
+            return mIndexer.getPositionForSection(section);
+        }
+        
+        public int getSectionForPosition(int position) {
+            return 0;
+        }
+    }
+
+    private Cursor mAlbumCursor;
+    private String mArtistId;
+    private boolean mHasHeader = false;
+}
+
diff --git a/src/com/android/music/ArtistAlbumBrowserActivity.java b/src/com/android/music/ArtistAlbumBrowserActivity.java
new file mode 100644
index 0000000..2ed367b
--- /dev/null
+++ b/src/com/android/music/ArtistAlbumBrowserActivity.java
@@ -0,0 +1,817 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import com.android.music.QueryBrowserActivity.QueryListAdapter.QueryHandler;
+
+import android.app.ExpandableListActivity;
+import android.app.SearchManager;
+import android.content.AsyncQueryHandler;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.CursorWrapper;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.media.MediaFile;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.SubMenu;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.CursorAdapter;
+import android.widget.CursorTreeAdapter;
+import android.widget.ExpandableListAdapter;
+import android.widget.ExpandableListView;
+import android.widget.ImageView;
+import android.widget.SectionIndexer;
+import android.widget.SimpleCursorTreeAdapter;
+import android.widget.TextView;
+import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
+
+import java.text.Collator;
+
+
+public class ArtistAlbumBrowserActivity extends ExpandableListActivity
+        implements View.OnCreateContextMenuListener, MusicUtils.Defs
+{
+    private String mCurrentArtistId;
+    private String mCurrentArtistName;
+    private String mCurrentAlbumId;
+    private String mCurrentAlbumName;
+    private String mCurrentArtistNameForAlbum;
+    private ArtistAlbumListAdapter mAdapter;
+    private boolean mAdapterSent;
+    private final static int SEARCH = CHILD_MENU_BASE;
+
+    public ArtistAlbumBrowserActivity()
+    {
+    }
+
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+        setVolumeControlStream(AudioManager.STREAM_MUSIC);
+        if (icicle != null) {
+            mCurrentAlbumId = icicle.getString("selectedalbum");
+            mCurrentAlbumName = icicle.getString("selectedalbumname");
+            mCurrentArtistId = icicle.getString("selectedartist");
+            mCurrentArtistName = icicle.getString("selectedartistname");
+        }
+        MusicUtils.bindToService(this);
+
+        IntentFilter f = new IntentFilter();
+        f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
+        f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
+        f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
+        f.addDataScheme("file");
+        registerReceiver(mScanListener, f);
+
+        setContentView(R.layout.media_picker_activity_expanding);
+        ExpandableListView lv = getExpandableListView();
+        lv.setFastScrollEnabled(true);
+        lv.setOnCreateContextMenuListener(this);
+        lv.setTextFilterEnabled(true);
+
+        mAdapter = (ArtistAlbumListAdapter) getLastNonConfigurationInstance();
+        if (mAdapter == null) {
+            //Log.i("@@@", "starting query");
+            mAdapter = new ArtistAlbumListAdapter(
+                    getApplication(),
+                    this,
+                    null, // cursor
+                    R.layout.track_list_item_group,
+                    new String[] {},
+                    new int[] {},
+                    R.layout.track_list_item_child,
+                    new String[] {},
+                    new int[] {});
+            setListAdapter(mAdapter);
+            setTitle(R.string.working_artists);
+            getArtistCursor(mAdapter.getQueryHandler(), null);
+        } else {
+            mAdapter.setActivity(this);
+            setListAdapter(mAdapter);
+            mArtistCursor = mAdapter.getCursor();
+            if (mArtistCursor != null) {
+                init(mArtistCursor);
+            } else {
+                getArtistCursor(mAdapter.getQueryHandler(), null);
+            }
+        }
+    }
+
+    @Override
+    public Object onRetainNonConfigurationInstance() {
+        mAdapterSent = true;
+        return mAdapter;
+    }
+    
+    @Override
+    public void onSaveInstanceState(Bundle outcicle) {
+        // need to store the selected item so we don't lose it in case
+        // of an orientation switch. Otherwise we could lose it while
+        // in the middle of specifying a playlist to add the item to.
+        outcicle.putString("selectedalbum", mCurrentAlbumId);
+        outcicle.putString("selectedalbumname", mCurrentAlbumName);
+        outcicle.putString("selectedartist", mCurrentArtistId);
+        outcicle.putString("selectedartistname", mCurrentArtistName);
+        super.onSaveInstanceState(outcicle);
+    }
+
+    @Override
+    public void onDestroy() {
+        MusicUtils.unbindFromService(this);
+        if (!mAdapterSent) {
+            Cursor c = mAdapter.getCursor();
+            if (c != null) {
+                c.close();
+            }
+        }
+        unregisterReceiver(mScanListener);
+        super.onDestroy();
+    }
+    
+    @Override
+    public void onResume() {
+        super.onResume();
+        IntentFilter f = new IntentFilter();
+        f.addAction(MediaPlaybackService.META_CHANGED);
+        f.addAction(MediaPlaybackService.QUEUE_CHANGED);
+        registerReceiver(mTrackListListener, f);
+        mTrackListListener.onReceive(null, null);
+
+        MusicUtils.setSpinnerState(this);
+    }
+
+    private BroadcastReceiver mTrackListListener = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            getExpandableListView().invalidateViews();
+        }
+    };
+    private BroadcastReceiver mScanListener = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            MusicUtils.setSpinnerState(ArtistAlbumBrowserActivity.this);
+            mReScanHandler.sendEmptyMessage(0);
+            if (intent.getAction().equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
+                MusicUtils.clearAlbumArtCache();
+            }
+        }
+    };
+    
+    private Handler mReScanHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            getArtistCursor(mAdapter.getQueryHandler(), null);
+        }
+    };
+
+    @Override
+    public void onPause() {
+        unregisterReceiver(mTrackListListener);
+        mReScanHandler.removeCallbacksAndMessages(null);
+        super.onPause();
+    }
+    
+    public void init(Cursor c) {
+
+        mAdapter.changeCursor(c); // also sets mArtistCursor
+
+        if (mArtistCursor == null) {
+            MusicUtils.displayDatabaseError(this);
+            closeContextMenu();
+            mReScanHandler.sendEmptyMessageDelayed(0, 1000);
+            return;
+        }
+
+        MusicUtils.hideDatabaseError(this);
+        setTitle();
+    }
+
+    private void setTitle() {
+        setTitle(R.string.artists_title);
+    }
+    
+    @Override
+    public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
+
+        mCurrentAlbumId = Long.valueOf(id).toString();
+        
+        Intent intent = new Intent(Intent.ACTION_PICK);
+        intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
+        intent.putExtra("album", mCurrentAlbumId);
+        Cursor c = (Cursor) getExpandableListAdapter().getChild(groupPosition, childPosition);
+        String album = c.getString(c.getColumnIndex(MediaStore.Audio.Albums.ALBUM));
+        if (album.equals(MediaFile.UNKNOWN_STRING)) {
+            // unknown album, so we should include the artist ID to limit the songs to songs only by that artist 
+            mArtistCursor.moveToPosition(groupPosition);
+            mCurrentArtistId = mArtistCursor.getString(mArtistCursor.getColumnIndex(MediaStore.Audio.Artists._ID));
+            intent.putExtra("artist", mCurrentArtistId);
+        }
+        startActivity(intent);
+        return true;
+    }
+    
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+        menu.add(0, GOTO_START, 0, R.string.goto_start).setIcon(R.drawable.ic_menu_music_library);
+        menu.add(0, GOTO_PLAYBACK, 0, R.string.goto_playback).setIcon(R.drawable.ic_menu_playback);
+        menu.add(0, SHUFFLE_ALL, 0, R.string.shuffle_all).setIcon(R.drawable.ic_menu_shuffle);
+        return true;
+    }
+    
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        menu.findItem(GOTO_PLAYBACK).setVisible(MusicUtils.isMusicLoaded());
+        return super.onPrepareOptionsMenu(menu);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        Intent intent;
+        Cursor cursor;
+        switch (item.getItemId()) {
+            case GOTO_START:
+                intent = new Intent();
+                intent.setClass(this, MusicBrowserActivity.class);
+                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                startActivity(intent);
+                return true;
+
+            case GOTO_PLAYBACK:
+                intent = new Intent("com.android.music.PLAYBACK_VIEWER");
+                startActivity(intent);
+                return true;
+                
+            case SHUFFLE_ALL:
+                cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                        new String [] { MediaStore.Audio.Media._ID}, 
+                        MediaStore.Audio.Media.IS_MUSIC + "=1", null,
+                        MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
+                if (cursor != null) {
+                    MusicUtils.shuffleAll(this, cursor);
+                    cursor.close();
+                }
+                return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
+        menu.add(0, PLAY_SELECTION, 0, R.string.play_selection);
+        SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0, R.string.add_to_playlist);
+        MusicUtils.makePlaylistMenu(this, sub);
+        menu.add(0, DELETE_ITEM, 0, R.string.delete_item);
+        menu.add(0, SEARCH, 0, R.string.search_title);
+        
+        ExpandableListContextMenuInfo mi = (ExpandableListContextMenuInfo) menuInfoIn;
+        
+        int itemtype = ExpandableListView.getPackedPositionType(mi.packedPosition);
+        int gpos = ExpandableListView.getPackedPositionGroup(mi.packedPosition);
+        int cpos = ExpandableListView.getPackedPositionChild(mi.packedPosition);
+        if (itemtype == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
+            if (gpos == -1) {
+                // this shouldn't happen
+                Log.d("Artist/Album", "no group");
+                return;
+            }
+            gpos = gpos - getExpandableListView().getHeaderViewsCount();
+            mArtistCursor.moveToPosition(gpos);
+            mCurrentArtistId = mArtistCursor.getString(mArtistCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists._ID));
+            mCurrentArtistName = mArtistCursor.getString(mArtistCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST));
+            mCurrentAlbumId = null;
+            menu.setHeaderTitle(mCurrentArtistName);
+            return;
+        } else if (itemtype == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
+            if (cpos == -1) {
+                // this shouldn't happen
+                Log.d("Artist/Album", "no child");
+                return;
+            }
+            Cursor c = (Cursor) getExpandableListAdapter().getChild(gpos, cpos);
+            c.moveToPosition(cpos);
+            mCurrentArtistId = null;
+            mCurrentAlbumId = Long.valueOf(mi.id).toString();
+            mCurrentAlbumName = c.getString(c.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));
+            gpos = gpos - getExpandableListView().getHeaderViewsCount();
+            mArtistCursor.moveToPosition(gpos);
+            mCurrentArtistNameForAlbum = mArtistCursor.getString(
+                    mArtistCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST));
+            menu.setHeaderTitle(mCurrentAlbumName);
+        }
+    }
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case PLAY_SELECTION: {
+                // play everything by the selected artist
+                int [] list =
+                    mCurrentArtistId != null ?
+                    MusicUtils.getSongListForArtist(this, Integer.parseInt(mCurrentArtistId))
+                    : MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
+                        
+                MusicUtils.playAll(this, list, 0);
+                return true;
+            }
+
+            case QUEUE: {
+                int [] list =
+                    mCurrentArtistId != null ?
+                    MusicUtils.getSongListForArtist(this, Integer.parseInt(mCurrentArtistId))
+                    : MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
+                MusicUtils.addToCurrentPlaylist(this, list);
+                return true;
+            }
+
+            case NEW_PLAYLIST: {
+                Intent intent = new Intent();
+                intent.setClass(this, CreatePlaylist.class);
+                startActivityForResult(intent, NEW_PLAYLIST);
+                return true;
+            }
+
+            case PLAYLIST_SELECTED: {
+                int [] list =
+                    mCurrentArtistId != null ?
+                    MusicUtils.getSongListForArtist(this, Integer.parseInt(mCurrentArtistId))
+                    : MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
+                int playlist = item.getIntent().getIntExtra("playlist", 0);
+                MusicUtils.addToPlaylist(this, list, playlist);
+                return true;
+            }
+            
+            case DELETE_ITEM: {
+                int [] list;
+                String desc;
+                if (mCurrentArtistId != null) {
+                    list = MusicUtils.getSongListForArtist(this, Integer.parseInt(mCurrentArtistId));
+                    String f = getString(R.string.delete_artist_desc);
+                    desc = String.format(f, mCurrentArtistName);
+                } else {
+                    list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
+                    String f = getString(R.string.delete_album_desc); 
+                    desc = String.format(f, mCurrentAlbumName);
+                }
+                Bundle b = new Bundle();
+                b.putString("description", desc);
+                b.putIntArray("items", list);
+                Intent intent = new Intent();
+                intent.setClass(this, DeleteItems.class);
+                intent.putExtras(b);
+                startActivityForResult(intent, -1);
+                return true;
+            }
+            
+            case SEARCH:
+                doSearch();
+                return true;
+        }
+        return super.onContextItemSelected(item);
+    }
+
+    void doSearch() {
+        CharSequence title = null;
+        String query = null;
+        
+        Intent i = new Intent();
+        i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH);
+        
+        if (mCurrentArtistId != null) {
+            title = mCurrentArtistName;
+            query = mCurrentArtistName;
+            i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistName);
+            i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE);
+        } else {
+            title = mCurrentAlbumName;
+            query = mCurrentArtistNameForAlbum + " " + mCurrentAlbumName;
+            i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistNameForAlbum);
+            i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, mCurrentAlbumName);
+            i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE);
+        }
+        title = getString(R.string.mediasearch, title);
+        i.putExtra(SearchManager.QUERY, query);
+
+        startActivity(Intent.createChooser(i, title));
+    }
+    
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+        switch (requestCode) {
+            case SCAN_DONE:
+                if (resultCode == RESULT_CANCELED) {
+                    finish();
+                } else {
+                    getArtistCursor(mAdapter.getQueryHandler(), null);
+                }
+                break;
+
+            case NEW_PLAYLIST:
+                if (resultCode == RESULT_OK) {
+                    Uri uri = intent.getData();
+                    if (uri != null) {
+                        int [] list = null;
+                        if (mCurrentArtistId != null) {
+                            list = MusicUtils.getSongListForArtist(this, Integer.parseInt(mCurrentArtistId));
+                        } else if (mCurrentAlbumId != null) {
+                            list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
+                        }
+                        MusicUtils.addToPlaylist(this, list, Integer.parseInt(uri.getLastPathSegment()));
+                    }
+                }
+                break;
+        }
+    }
+
+    private Cursor getArtistCursor(AsyncQueryHandler async, String filter) {
+
+        StringBuilder where = new StringBuilder();
+        where.append(MediaStore.Audio.Artists.ARTIST + " != ''");
+        
+        // Add in the filtering constraints
+        String [] keywords = null;
+        if (filter != null) {
+            String [] searchWords = filter.split(" ");
+            keywords = new String[searchWords.length];
+            Collator col = Collator.getInstance();
+            col.setStrength(Collator.PRIMARY);
+            for (int i = 0; i < searchWords.length; i++) {
+                keywords[i] = '%' + MediaStore.Audio.keyFor(searchWords[i]) + '%';
+            }
+            for (int i = 0; i < searchWords.length; i++) {
+                where.append(" AND ");
+                where.append(MediaStore.Audio.Media.ARTIST_KEY + " LIKE ?");
+            }
+        }
+
+        String whereclause = where.toString();  
+        String[] cols = new String[] {
+                MediaStore.Audio.Artists._ID,
+                MediaStore.Audio.Artists.ARTIST,
+                MediaStore.Audio.Artists.NUMBER_OF_ALBUMS,
+                MediaStore.Audio.Artists.NUMBER_OF_TRACKS
+        };
+        Cursor ret = null;
+        if (async != null) {
+            async.startQuery(0, null, MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI,
+                    cols, whereclause , keywords, MediaStore.Audio.Artists.ARTIST_KEY);
+        } else {
+            ret = MusicUtils.query(this, MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI,
+                    cols, whereclause , keywords, MediaStore.Audio.Artists.ARTIST_KEY);
+        }
+        return ret;
+    }
+    
+    static class ArtistAlbumListAdapter extends SimpleCursorTreeAdapter implements SectionIndexer {
+        
+        private final Drawable mNowPlayingOverlay;
+        private final BitmapDrawable mDefaultAlbumIcon;
+        private int mGroupArtistIdIdx;
+        private int mGroupArtistIdx;
+        private int mGroupAlbumIdx;
+        private int mGroupSongIdx;
+        private final Context mContext;
+        private final Resources mResources;
+        private final String mAlbumSongSeparator;
+        private final String mUnknownAlbum;
+        private final String mUnknownArtist;
+        private final StringBuilder mBuffer = new StringBuilder();
+        private final Object[] mFormatArgs = new Object[1];
+        private final Object[] mFormatArgs3 = new Object[3];
+        private MusicAlphabetIndexer mIndexer;
+        private ArtistAlbumBrowserActivity mActivity;
+        private AsyncQueryHandler mQueryHandler;
+        private String mConstraint = null;
+        private boolean mConstraintIsValid = false;
+        
+        class ViewHolder {
+            TextView line1;
+            TextView line2;
+            ImageView play_indicator;
+            ImageView icon;
+        }
+
+        class QueryHandler extends AsyncQueryHandler {
+            QueryHandler(ContentResolver res) {
+                super(res);
+            }
+            
+            @Override
+            protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+                //Log.i("@@@", "query complete");
+                mActivity.init(cursor);
+            }
+        }
+
+        ArtistAlbumListAdapter(Context context, ArtistAlbumBrowserActivity currentactivity,
+                Cursor cursor, int glayout, String[] gfrom, int[] gto, 
+                int clayout, String[] cfrom, int[] cto) {
+            super(context, cursor, glayout, gfrom, gto, clayout, cfrom, cto);
+            mActivity = currentactivity;
+            mQueryHandler = new QueryHandler(context.getContentResolver());
+
+            Resources r = context.getResources();
+            mNowPlayingOverlay = r.getDrawable(R.drawable.indicator_ic_mp_playing_list);
+            mDefaultAlbumIcon = (BitmapDrawable) r.getDrawable(R.drawable.albumart_mp_unknown_list);
+            // no filter or dither, it's a lot faster and we can't tell the difference
+            mDefaultAlbumIcon.setFilterBitmap(false);
+            mDefaultAlbumIcon.setDither(false);
+            
+            mContext = context;
+            getColumnIndices(cursor);
+            mResources = context.getResources();
+            mAlbumSongSeparator = context.getString(R.string.albumsongseparator);
+            mUnknownAlbum = context.getString(R.string.unknown_album_name);
+            mUnknownArtist = context.getString(R.string.unknown_artist_name);
+        }
+        
+        private void getColumnIndices(Cursor cursor) {
+            if (cursor != null) {
+                mGroupArtistIdIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists._ID);
+                mGroupArtistIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST);
+                mGroupAlbumIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.NUMBER_OF_ALBUMS);
+                mGroupSongIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.NUMBER_OF_TRACKS);
+                if (mIndexer != null) {
+                    mIndexer.setCursor(cursor);
+                } else {
+                    mIndexer = new MusicAlphabetIndexer(cursor, mGroupArtistIdx, 
+                            mResources.getString(com.android.internal.R.string.fast_scroll_alphabet));
+                }
+            }
+        }
+        
+        public void setActivity(ArtistAlbumBrowserActivity newactivity) {
+            mActivity = newactivity;
+        }
+        
+        public AsyncQueryHandler getQueryHandler() {
+            return mQueryHandler;
+        }
+
+        @Override
+        public View newGroupView(Context context, Cursor cursor, boolean isExpanded, ViewGroup parent) {
+            View v = super.newGroupView(context, cursor, isExpanded, parent);
+            ImageView iv = (ImageView) v.findViewById(R.id.icon);
+            ViewGroup.LayoutParams p = iv.getLayoutParams();
+            p.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+            p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+            ViewHolder vh = new ViewHolder();
+            vh.line1 = (TextView) v.findViewById(R.id.line1);
+            vh.line2 = (TextView) v.findViewById(R.id.line2);
+            vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator);
+            vh.icon = (ImageView) v.findViewById(R.id.icon);
+            vh.icon.setPadding(0, 0, 1, 0);
+            v.setTag(vh);
+            return v;
+        }
+
+        @Override
+        public View newChildView(Context context, Cursor cursor, boolean isLastChild,
+                ViewGroup parent) {
+            View v = super.newChildView(context, cursor, isLastChild, parent);
+            ViewHolder vh = new ViewHolder();
+            vh.line1 = (TextView) v.findViewById(R.id.line1);
+            vh.line2 = (TextView) v.findViewById(R.id.line2);
+            vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator);
+            vh.icon = (ImageView) v.findViewById(R.id.icon);
+            vh.icon.setBackgroundDrawable(mDefaultAlbumIcon);
+            vh.icon.setPadding(0, 0, 1, 0);
+            v.setTag(vh);
+            return v;
+        }
+        
+        @Override
+        public void bindGroupView(View view, Context context, Cursor cursor, boolean isexpanded) {
+
+            ViewHolder vh = (ViewHolder) view.getTag();
+
+            String artist = cursor.getString(mGroupArtistIdx);
+            String displayartist = artist;
+            boolean unknown = MediaFile.UNKNOWN_STRING.equals(artist);
+            if (unknown) {
+                displayartist = mUnknownArtist;
+            }
+            vh.line1.setText(displayartist);
+
+            int numalbums = cursor.getInt(mGroupAlbumIdx);
+            int numsongs = cursor.getInt(mGroupSongIdx);
+            
+            String songs_albums = MusicUtils.makeAlbumsLabel(context,
+                    numalbums, numsongs, unknown);
+            
+            vh.line2.setText(songs_albums);
+            
+            int currentartistid = MusicUtils.getCurrentArtistId();
+            int artistid = cursor.getInt(mGroupArtistIdIdx);
+            if (currentartistid == artistid && !isexpanded) {
+                vh.play_indicator.setImageDrawable(mNowPlayingOverlay);
+            } else {
+                vh.play_indicator.setImageDrawable(null);
+            }
+        }
+
+        @Override
+        public void bindChildView(View view, Context context, Cursor cursor, boolean islast) {
+
+            ViewHolder vh = (ViewHolder) view.getTag();
+
+            String name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));
+            String displayname = name;
+            boolean unknown = name.equals(MediaFile.UNKNOWN_STRING); 
+            if (unknown) {
+                displayname = mUnknownAlbum;
+            }
+            vh.line1.setText(displayname);
+
+            int numsongs = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.NUMBER_OF_SONGS));
+            int numartistsongs = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.NUMBER_OF_SONGS_FOR_ARTIST));
+            int first = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.FIRST_YEAR));
+            int last = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.LAST_YEAR));
+
+            if (first == 0) {
+                first = last;
+            }
+
+            final StringBuilder builder = mBuffer;
+            builder.delete(0, builder.length());
+            if (unknown) {
+                numsongs = numartistsongs;
+            }
+              
+            if (numsongs == 1) {
+                builder.append(context.getString(R.string.onesong));
+            } else {
+                if (numsongs == numartistsongs) {
+                    final Object[] args = mFormatArgs;
+                    args[0] = numsongs;
+                    builder.append(mResources.getQuantityString(R.plurals.Nsongs, numsongs, args));
+                } else {
+                    final Object[] args = mFormatArgs3;
+                    args[0] = numsongs;
+                    args[1] = numartistsongs;
+                    args[2] = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST));
+                    builder.append(mResources.getQuantityString(R.plurals.Nsongscomp, numsongs, args));
+                }
+            }
+            vh.line2.setText(builder.toString());
+            
+            ImageView iv = vh.icon;
+            // We don't actually need the path to the thumbnail file,
+            // we just use it to see if there is album art or not
+            String art = cursor.getString(cursor.getColumnIndexOrThrow(
+                    MediaStore.Audio.Albums.ALBUM_ART));
+            if (unknown || art == null || art.length() == 0) {
+                iv.setBackgroundDrawable(mDefaultAlbumIcon);
+                iv.setImageDrawable(null);
+            } else {
+                int artIndex = cursor.getInt(0);
+                Drawable d = MusicUtils.getCachedArtwork(context, artIndex, mDefaultAlbumIcon);
+                iv.setImageDrawable(d);
+            }
+
+            int currentalbumid = MusicUtils.getCurrentAlbumId();
+            int aid = cursor.getInt(0);
+            iv = vh.play_indicator;
+            if (currentalbumid == aid) {
+                iv.setImageDrawable(mNowPlayingOverlay);
+            } else {
+                iv.setImageDrawable(null);
+            }
+        }
+
+        
+        @Override
+        protected Cursor getChildrenCursor(Cursor groupCursor) {
+            
+            int id = groupCursor.getInt(groupCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists._ID));
+            
+            String[] cols = new String[] {
+                    MediaStore.Audio.Albums._ID,
+                    MediaStore.Audio.Albums.ALBUM,
+                    MediaStore.Audio.Albums.NUMBER_OF_SONGS,
+                    MediaStore.Audio.Albums.NUMBER_OF_SONGS_FOR_ARTIST,
+                    MediaStore.Audio.Albums.FIRST_YEAR,
+                    MediaStore.Audio.Albums.LAST_YEAR,
+                    MediaStore.Audio.Albums.ALBUM_ART
+            };
+            Cursor c = MusicUtils.query(mActivity,
+                    MediaStore.Audio.Artists.Albums.getContentUri("external", id),
+                    cols, null, null, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
+            
+            class MyCursorWrapper extends CursorWrapper {
+                String mArtistName;
+                int mMagicColumnIdx;
+                MyCursorWrapper(Cursor c, String artist) {
+                    super(c);
+                    mArtistName = artist;
+                    if (MediaFile.UNKNOWN_STRING.equals(mArtistName)) {
+                        mArtistName = mUnknownArtist;
+                    }
+                    mMagicColumnIdx = c.getColumnCount();
+                }
+                
+                @Override
+                public String getString(int columnIndex) {
+                    if (columnIndex != mMagicColumnIdx) {
+                        return super.getString(columnIndex);
+                    }
+                    return mArtistName;
+                }
+                
+                @Override
+                public int getColumnIndexOrThrow(String name) {
+                    if (name.equals(MediaStore.Audio.Albums.ARTIST)) {
+                        return mMagicColumnIdx;
+                    }
+                    return super.getColumnIndexOrThrow(name); 
+                }
+                
+                @Override
+                public String getColumnName(int idx) {
+                    if (idx != mMagicColumnIdx) {
+                        return super.getColumnName(idx);
+                    }
+                    return MediaStore.Audio.Albums.ARTIST;
+                }
+                
+                @Override
+                public int getColumnCount() {
+                    return super.getColumnCount() + 1;
+                }
+            }
+            return new MyCursorWrapper(c, groupCursor.getString(mGroupArtistIdx));
+        }
+
+        @Override
+        public void changeCursor(Cursor cursor) {
+            if (cursor != mActivity.mArtistCursor) {
+                mActivity.mArtistCursor = cursor;
+                getColumnIndices(cursor);
+                super.changeCursor(cursor);
+            }
+        }
+        
+        @Override
+        public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
+            String s = constraint.toString();
+            if (mConstraintIsValid && (
+                    (s == null && mConstraint == null) ||
+                    (s != null && s.equals(mConstraint)))) {
+                return getCursor();
+            }
+            Cursor c = mActivity.getArtistCursor(null, s);
+            mConstraint = s;
+            mConstraintIsValid = true;
+            return c;
+        }
+
+        public Object[] getSections() {
+            return mIndexer.getSections();
+        }
+        
+        public int getPositionForSection(int sectionIndex) {
+            return mIndexer.getPositionForSection(sectionIndex);
+        }
+        
+        public int getSectionForPosition(int position) {
+            return 0;
+        }
+    }
+    
+    private Cursor mArtistCursor;
+}
+
diff --git a/src/com/android/music/CheckableRelativeLayout.java b/src/com/android/music/CheckableRelativeLayout.java
new file mode 100644
index 0000000..25c837b
--- /dev/null
+++ b/src/com/android/music/CheckableRelativeLayout.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.Checkable;
+import android.widget.RelativeLayout;
+
+/**
+ * A special variation of RelativeLayout that can be used as a checkable object.
+ * This allows it to be used as the top-level view of a list view item, which
+ * also supports checking.  Otherwise, it works identically to a RelativeLayout.
+ */
+public class CheckableRelativeLayout extends RelativeLayout implements Checkable {
+    private boolean mChecked;
+
+    private static final int[] CHECKED_STATE_SET = {
+        android.R.attr.state_checked
+    };
+
+    public CheckableRelativeLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected int[] onCreateDrawableState(int extraSpace) {
+        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+        if (isChecked()) {
+            mergeDrawableStates(drawableState, CHECKED_STATE_SET);
+        }
+        return drawableState;
+    }
+
+    public void toggle() {
+        setChecked(!mChecked);
+    }
+    
+    public boolean isChecked() {
+        return mChecked;
+    }
+
+    public void setChecked(boolean checked) {
+        if (mChecked != checked) {
+            mChecked = checked;
+            refreshDrawableState();
+        }
+    }
+}
diff --git a/src/com/android/music/CreatePlaylist.java b/src/com/android/music/CreatePlaylist.java
new file mode 100644
index 0000000..9005023
--- /dev/null
+++ b/src/com/android/music/CreatePlaylist.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.database.Cursor;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+public class CreatePlaylist extends Activity
+{
+    private EditText mPlaylist;
+    private TextView mPrompt;
+    private Button mSaveButton;
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+        setContentView(R.layout.create_playlist);
+        getWindow().setLayout(WindowManager.LayoutParams.FILL_PARENT,
+                                    WindowManager.LayoutParams.WRAP_CONTENT);
+
+        mPrompt = (TextView)findViewById(R.id.prompt);
+        mPlaylist = (EditText)findViewById(R.id.playlist);
+        mSaveButton = (Button) findViewById(R.id.create);
+        mSaveButton.setOnClickListener(mOpenClicked);
+
+        ((Button)findViewById(R.id.cancel)).setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                finish();
+            }
+        });
+        
+        String defaultname = icicle != null ? icicle.getString("defaultname") : makePlaylistName();
+        if (defaultname == null) {
+            finish();
+            return;
+        }
+        String promptformat = getString(R.string.create_playlist_create_text_prompt);
+        String prompt = String.format(promptformat, defaultname);
+        mPrompt.setText(prompt);
+        mPlaylist.setText(defaultname);
+        mPlaylist.setSelection(defaultname.length());
+        mPlaylist.addTextChangedListener(mTextWatcher);
+    }
+    
+    TextWatcher mTextWatcher = new TextWatcher() {
+        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+            // don't care about this one
+        }
+        public void onTextChanged(CharSequence s, int start, int before, int count) {
+            // check if playlist with current name exists already, and warn the user if so.
+            if (idForplaylist(mPlaylist.getText().toString()) >= 0) {
+                mSaveButton.setText(R.string.create_playlist_overwrite_text);
+            } else {
+                mSaveButton.setText(R.string.create_playlist_create_text);
+            }
+        };
+        public void afterTextChanged(Editable s) {
+            // don't care about this one
+        }
+    };
+    
+    private int idForplaylist(String name) {
+        Cursor c = MusicUtils.query(this, MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+                new String[] { MediaStore.Audio.Playlists._ID },
+                MediaStore.Audio.Playlists.NAME + "=?",
+                new String[] { name },
+                MediaStore.Audio.Playlists.NAME);
+        int id = -1;
+        if (c != null) {
+            c.moveToFirst();
+            if (!c.isAfterLast()) {
+                id = c.getInt(0);
+            }
+        }
+        c.close();
+        return id;
+    }
+    
+    @Override
+    public void onSaveInstanceState(Bundle outcicle) {
+        outcicle.putString("defaultname", mPlaylist.getText().toString());
+    }
+    
+    @Override
+    public void onResume() {
+        super.onResume();
+    }
+
+    private String makePlaylistName() {
+
+        String template = getString(R.string.new_playlist_name_template);
+        int num = 1;
+
+        String[] cols = new String[] {
+                MediaStore.Audio.Playlists.NAME
+        };
+        ContentResolver resolver = getContentResolver();
+        String whereclause = MediaStore.Audio.Playlists.NAME + " != ''";
+        Cursor c = resolver.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+            cols, whereclause, null,
+            MediaStore.Audio.Playlists.NAME);
+
+        if (c == null) {
+            return null;
+        }
+        
+        String suggestedname;
+        suggestedname = String.format(template, num++);
+        
+        // Need to loop until we've made 1 full pass through without finding a match.
+        // Looping more than once shouldn't happen very often, but will happen if
+        // you have playlists named "New Playlist 1"/10/2/3/4/5/6/7/8/9, where
+        // making only one pass would result in "New Playlist 10" being erroneously
+        // picked for the new name.
+        boolean done = false;
+        while (!done) {
+            done = true;
+            c.moveToFirst();
+            while (! c.isAfterLast()) {
+                String playlistname = c.getString(0);
+                if (playlistname.compareToIgnoreCase(suggestedname) == 0) {
+                    suggestedname = String.format(template, num++);
+                    done = false;
+                }
+                c.moveToNext();
+            }
+        }
+        c.close();
+        return suggestedname;
+    }
+    
+    private View.OnClickListener mOpenClicked = new View.OnClickListener() {
+        public void onClick(View v) {
+            String name = mPlaylist.getText().toString();
+            if (name != null && name.length() > 0) {
+                ContentResolver resolver = getContentResolver();
+                int id = idForplaylist(name);
+                Uri uri;
+                if (id >= 0) {
+                    uri = ContentUris.withAppendedId(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, id);
+                    MusicUtils.clearPlaylist(CreatePlaylist.this, id);
+                } else {
+                    ContentValues values = new ContentValues(1);
+                    values.put(MediaStore.Audio.Playlists.NAME, name);
+                    uri = resolver.insert(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, values);
+                }
+                setResult(RESULT_OK, (new Intent()).setData(uri));
+                finish();
+            }
+        }
+    };
+}
diff --git a/src/com/android/music/DeleteItems.java b/src/com/android/music/DeleteItems.java
new file mode 100644
index 0000000..15e681f
--- /dev/null
+++ b/src/com/android/music/DeleteItems.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.database.Cursor;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+public class DeleteItems extends Activity
+{
+    private TextView mPrompt;
+    private Button mButton;
+    private int [] mItemList;
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+        setContentView(R.layout.confirm_delete);
+        getWindow().setLayout(WindowManager.LayoutParams.FILL_PARENT,
+                                    WindowManager.LayoutParams.WRAP_CONTENT);
+
+        mPrompt = (TextView)findViewById(R.id.prompt);
+        mButton = (Button) findViewById(R.id.delete);
+        mButton.setOnClickListener(mButtonClicked);
+
+        ((Button)findViewById(R.id.cancel)).setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                finish();
+            }
+        });
+
+        Bundle b = getIntent().getExtras();
+        String desc = b.getString("description");
+        mItemList = b.getIntArray("items");
+        
+        mPrompt.setText(desc);
+    }
+    
+    private View.OnClickListener mButtonClicked = new View.OnClickListener() {
+        public void onClick(View v) {
+            // delete the selected item(s)
+            MusicUtils.deleteTracks(DeleteItems.this, mItemList);
+            finish();
+        }
+    };
+}
diff --git a/src/com/android/music/IMediaPlaybackService.aidl b/src/com/android/music/IMediaPlaybackService.aidl
new file mode 100644
index 0000000..5698cc5
--- /dev/null
+++ b/src/com/android/music/IMediaPlaybackService.aidl
@@ -0,0 +1,56 @@
+/* //device/samples/SampleCode/src/com/android/samples/app/RemoteServiceInterface.java
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+package com.android.music;
+
+import android.graphics.Bitmap;
+
+interface IMediaPlaybackService
+{
+    void openfile(String path);
+    void openfileAsync(String path);
+    void open(in int [] list, int position);
+    int getQueuePosition();
+    boolean isPlaying();
+    void stop();
+    void pause();
+    void play();
+    void prev();
+    void next();
+    long duration();
+    long position();
+    long seek(long pos);
+    String getTrackName();
+    String getAlbumName();
+    int getAlbumId();
+    String getArtistName();
+    int getArtistId();
+    void enqueue(in int [] list, int action);
+    int [] getQueue();
+    void moveQueueItem(int from, int to);
+    void setQueuePosition(int index);
+    String getPath();
+    int getAudioId();
+    void setShuffleMode(int shufflemode);
+    int getShuffleMode();
+    int removeTracks(int first, int last);
+    int removeTrack(int id);
+    void setRepeatMode(int repeatmode);
+    int getRepeatMode();
+    int getMediaMountedCount();
+}
+
diff --git a/src/com/android/music/MediaButtonIntentReceiver.java b/src/com/android/music/MediaButtonIntentReceiver.java
new file mode 100644
index 0000000..76812df
--- /dev/null
+++ b/src/com/android/music/MediaButtonIntentReceiver.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothError;
+import android.content.Context;
+import android.content.Intent;
+import android.content.BroadcastReceiver;
+import android.content.SharedPreferences;
+import android.media.AudioManager;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.os.Handler;
+import android.os.Message;
+
+/**
+ * 
+ */
+public class MediaButtonIntentReceiver extends BroadcastReceiver {
+
+    private static final int MSG_LONGPRESS_TIMEOUT = 1;
+    private static final int LONG_PRESS_DELAY = 1000;
+
+    private static long mLastClickTime = 0;
+    private static boolean mDown = false;
+    private static boolean mLaunched = false;
+
+    private static Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_LONGPRESS_TIMEOUT:
+                    if (!mLaunched) {
+                        Context context = (Context)msg.obj;
+                        Intent i = new Intent();
+                        i.putExtra("autoshuffle", "true");
+                        i.setClass(context, MusicBrowserActivity.class);
+                        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                        context.startActivity(i);
+                        mLaunched = true;
+                    }
+                    break;
+            }
+        }
+    };
+    
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String intentAction = intent.getAction();
+        if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intentAction)) {
+            Intent i = new Intent(context, MediaPlaybackService.class);
+            i.setAction(MediaPlaybackService.SERVICECMD);
+            i.putExtra(MediaPlaybackService.CMDNAME, MediaPlaybackService.CMDPAUSE);
+            context.startService(i);
+        } else if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) {
+            KeyEvent event = (KeyEvent)
+                    intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+            
+            if (event == null) {
+                return;
+            }
+
+            int keycode = event.getKeyCode();
+            int action = event.getAction();
+            long eventtime = event.getEventTime();
+
+            // single quick press: pause/resume. 
+            // double press: next track
+            // long press: start auto-shuffle mode.
+            
+            String command = null;
+            switch (keycode) {
+                case KeyEvent.KEYCODE_STOP:
+                    command = MediaPlaybackService.CMDSTOP;
+                    break;
+                case KeyEvent.KEYCODE_HEADSETHOOK:
+                case KeyEvent.KEYCODE_PLAYPAUSE:
+                    command = MediaPlaybackService.CMDTOGGLEPAUSE;
+                    break;
+                case KeyEvent.KEYCODE_NEXTSONG:
+                    command = MediaPlaybackService.CMDNEXT;
+                    break;
+                case KeyEvent.KEYCODE_PREVIOUSSONG:
+                    command = MediaPlaybackService.CMDPREVIOUS;
+                    break;
+            }
+
+            if (command != null) {
+                if (action == KeyEvent.ACTION_DOWN) {
+                    if (!mDown) {
+                        // only if this isn't a repeat event
+                        
+                        if (MediaPlaybackService.CMDTOGGLEPAUSE.equals(command)) {
+                            // We're not using the original time of the event as the
+                            // base here, because in some cases it can take more than
+                            // one second for us to receive the event, in which case
+                            // we would go immediately to auto shuffle mode, even if
+                            // the user didn't long press.
+                            mHandler.sendMessageDelayed(
+                                    mHandler.obtainMessage(MSG_LONGPRESS_TIMEOUT, context),
+                                    LONG_PRESS_DELAY);
+                        }
+                        
+                        SharedPreferences pref = context.getSharedPreferences("Music", 
+                                Context.MODE_WORLD_READABLE | Context.MODE_WORLD_WRITEABLE);
+                        String q = pref.getString("queue", "");
+                        // The service may or may not be running, but we need to send it
+                        // a command.
+                        Intent i = new Intent(context, MediaPlaybackService.class);
+                        i.setAction(MediaPlaybackService.SERVICECMD);
+                        if (keycode == KeyEvent.KEYCODE_HEADSETHOOK &&
+                                eventtime - mLastClickTime < 300) {
+                            i.putExtra(MediaPlaybackService.CMDNAME, MediaPlaybackService.CMDNEXT);
+                            context.startService(i);
+                            mLastClickTime = 0;
+                        } else {
+                            i.putExtra(MediaPlaybackService.CMDNAME, command);
+                            context.startService(i);
+                            mLastClickTime = eventtime;
+                        }
+
+                        mLaunched = false;
+                        mDown = true;
+                    }
+                } else {
+                    mHandler.removeMessages(MSG_LONGPRESS_TIMEOUT);
+                    mDown = false;
+                }
+                abortBroadcast();
+            }
+        }
+    }
+}
diff --git a/src/com/android/music/MediaGadgetProvider.java b/src/com/android/music/MediaGadgetProvider.java
new file mode 100644
index 0000000..46ba391
--- /dev/null
+++ b/src/com/android/music/MediaGadgetProvider.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.gadget.GadgetManager;
+import android.gadget.GadgetProvider;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.media.MediaFile;
+import android.os.Environment;
+import android.os.SystemClock;
+import android.util.Config;
+import android.util.Log;
+import android.view.View;
+import android.widget.RemoteViews;
+
+/**
+ * Simple gadget to show currently playing album art along
+ * with play/pause and next track buttons.  
+ */
+public class MediaGadgetProvider extends GadgetProvider {
+    static final String TAG = "MusicGadgetProvider";
+    
+    public static final String CMDGADGETUPDATE = "gadgetupdate";
+    
+    static final ComponentName THIS_GADGET =
+        new ComponentName("com.android.music",
+                "com.android.music.MediaGadgetProvider");
+    
+    private static MediaGadgetProvider sInstance;
+    
+    static synchronized MediaGadgetProvider getInstance() {
+        if (sInstance == null) {
+            sInstance = new MediaGadgetProvider();
+        }
+        return sInstance;
+    }
+
+    @Override
+    public void onUpdate(Context context, GadgetManager gadgetManager, int[] gadgetIds) {
+        defaultGadget(context, gadgetIds);
+        
+        // Send broadcast intent to any running MediaPlaybackService so it can
+        // wrap around with an immediate update.
+        Intent updateIntent = new Intent(MediaPlaybackService.SERVICECMD);
+        updateIntent.putExtra(MediaPlaybackService.CMDNAME,
+                MediaGadgetProvider.CMDGADGETUPDATE);
+        updateIntent.putExtra(GadgetManager.EXTRA_GADGET_IDS, gadgetIds);
+        updateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        context.sendBroadcast(updateIntent);
+    }
+    
+    /**
+     * Initialize given gadgets to default state, where we launch Music on default click
+     * and hide actions if service not running.
+     */
+    private void defaultGadget(Context context, int[] gadgetIds) {
+        final Resources res = context.getResources();
+        final RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.gadget);
+        
+        views.setTextViewText(R.id.title, res.getText(R.string.emptyplaylist));
+
+        linkButtons(context, views, false /* not playing */);
+        pushUpdate(context, gadgetIds, views);
+    }
+    
+    private void pushUpdate(Context context, int[] gadgetIds, RemoteViews views) {
+        // Update specific list of gadgetIds if given, otherwise default to all
+        final GadgetManager gm = GadgetManager.getInstance(context);
+        if (gadgetIds != null) {
+            gm.updateGadget(gadgetIds, views);
+        } else {
+            final ComponentName thisGadget = new ComponentName(context,
+                    MediaGadgetProvider.class);
+            gm.updateGadget(thisGadget, views);
+        }
+    }
+    
+    /**
+     * Check against {@link GadgetManager} if there are any instances of this gadget.
+     */
+    private boolean hasInstances(Context context) {
+        GadgetManager gadgetManager = GadgetManager.getInstance(context);
+        int[] gadgetIds = gadgetManager.getGadgetIds(THIS_GADGET);
+        return (gadgetIds.length > 0);
+    }
+
+    /**
+     * Handle a change notification coming over from {@link MediaPlaybackService}
+     */
+    void notifyChange(MediaPlaybackService service, String what) {
+        if (hasInstances(service)) {
+            if (MediaPlaybackService.PLAYBACK_COMPLETE.equals(what) ||
+                    MediaPlaybackService.META_CHANGED.equals(what) ||
+                    MediaPlaybackService.PLAYSTATE_CHANGED.equals(what)) {
+                performUpdate(service, null);
+            }
+        }
+    }
+    
+    /**
+     * Update all active gadget instances by pushing changes 
+     */
+    void performUpdate(MediaPlaybackService service, int[] gadgetIds) {
+        final Resources res = service.getResources();
+        final RemoteViews views = new RemoteViews(service.getPackageName(), R.layout.gadget);
+        
+        final int track = service.getQueuePosition() + 1;
+        final String titleName = service.getTrackName();
+        final String artistName = service.getArtistName();
+        
+        // Format title string with track number, or show SD card message
+        CharSequence titleString = "";
+        String status = Environment.getExternalStorageState();
+        if (titleName != null) {
+            titleString = res.getString(R.string.gadget_track_num_title, track, titleName);
+        } else if (status.equals(Environment.MEDIA_SHARED) ||
+                status.equals(Environment.MEDIA_UNMOUNTED)) {
+            titleString = res.getText(R.string.sdcard_busy_title);
+        } else if (status.equals(Environment.MEDIA_REMOVED)) {
+            titleString = res.getText(R.string.sdcard_missing_title);
+        } else {
+            titleString = res.getText(R.string.emptyplaylist);
+        }
+        
+        views.setTextViewText(R.id.title, titleString);
+        views.setTextViewText(R.id.artist, artistName);
+        
+        // Set correct drawable for pause state
+        final boolean playing = service.isPlaying();
+        views.setImageViewResource(R.id.control_play, playing ?
+                R.drawable.gadget_pause : R.drawable.gadget_play);
+
+        // Link actions buttons to intents
+        linkButtons(service, views, playing);
+        
+        pushUpdate(service, gadgetIds, views);
+    }
+
+    /**
+     * Link up various button actions using {@link PendingIntents}.
+     * 
+     * @param playerActive True if player is active in background, which means
+     *            gadget click will launch {@link MediaPlaybackActivity},
+     *            otherwise we launch {@link MusicBrowserActivity}.
+     */
+    private void linkButtons(Context context, RemoteViews views, boolean playerActive) {
+        // Connect up various buttons and touch events
+        Intent intent;
+        PendingIntent pendingIntent;
+        
+        final ComponentName serviceName = new ComponentName(context, MediaPlaybackService.class);
+        
+        if (playerActive) {
+            intent = new Intent(context, MediaPlaybackActivity.class);
+            pendingIntent = PendingIntent.getActivity(context,
+                    0 /* no requestCode */, intent, 0 /* no flags */);
+            views.setOnClickPendingIntent(R.id.album_gadget, pendingIntent);
+        } else {
+            intent = new Intent(context, MusicBrowserActivity.class);
+            pendingIntent = PendingIntent.getActivity(context,
+                    0 /* no requestCode */, intent, 0 /* no flags */);
+            views.setOnClickPendingIntent(R.id.album_gadget, pendingIntent);
+        }
+        
+        intent = new Intent(MediaPlaybackService.TOGGLEPAUSE_ACTION);
+        intent.setComponent(serviceName);
+        pendingIntent = PendingIntent.getService(context,
+                0 /* no requestCode */, intent, 0 /* no flags */);
+        views.setOnClickPendingIntent(R.id.control_play, pendingIntent);
+        
+        intent = new Intent(MediaPlaybackService.NEXT_ACTION);
+        intent.setComponent(serviceName);
+        pendingIntent = PendingIntent.getService(context,
+                0 /* no requestCode */, intent, 0 /* no flags */);
+        views.setOnClickPendingIntent(R.id.control_next, pendingIntent);
+    }
+}
diff --git a/src/com/android/music/MediaPickerActivity.java b/src/com/android/music/MediaPickerActivity.java
new file mode 100644
index 0000000..9ef6375
--- /dev/null
+++ b/src/com/android/music/MediaPickerActivity.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import com.android.internal.database.SortCursor;
+
+import android.app.ListActivity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ContentUris;
+import android.database.Cursor;
+import android.media.MediaFile;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.provider.MediaStore;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+public class MediaPickerActivity extends ListActivity implements MusicUtils.Defs
+{
+
+    public MediaPickerActivity()
+    {
+    }
+
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle icicle)
+    {
+        super.onCreate(icicle);
+
+        mFirstYear = getIntent().getStringExtra("firstyear");
+        mLastYear = getIntent().getStringExtra("lastyear");
+
+        if (mFirstYear == null) {
+            setTitle(R.string.all_title);
+        } else if (mFirstYear.equals(mLastYear)) {
+            setTitle(mFirstYear);
+        } else {
+            setTitle(mFirstYear + "-" + mLastYear);
+        }
+        MusicUtils.bindToService(this);
+        init();
+    }
+
+    @Override
+    public void onDestroy() {
+        MusicUtils.unbindFromService(this);
+        super.onDestroy();
+        if (mCursor != null) {
+            mCursor.close();
+        }
+    }
+
+    public void init() {
+
+        setContentView(R.layout.media_picker_activity);
+
+        MakeCursor();
+        if (null == mCursor || 0 == mCursor.getCount()) {
+            return;
+        }
+
+        PickListAdapter adapter = new PickListAdapter(
+                this,
+                R.layout.track_list_item,
+                mCursor,
+                new String[] {},
+                new int[] {});
+
+        setListAdapter(adapter);
+    }
+
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id)
+    {
+        mCursor.moveToPosition(position);
+        String type = mCursor.getString(mCursor.getColumnIndexOrThrow(
+                MediaStore.Audio.Media.MIME_TYPE));
+
+        String action = getIntent().getAction();
+        if (Intent.ACTION_GET_CONTENT.equals(action)) {
+            Uri uri;
+
+            long mediaId;
+            if (type.startsWith("video")) {
+                uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+                mediaId = mCursor.getLong(mCursor.getColumnIndexOrThrow(
+                        MediaStore.Video.Media._ID));
+            } else {
+                uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+                mediaId = mCursor.getLong(mCursor.getColumnIndexOrThrow(
+                        MediaStore.Audio.Media._ID));
+            }
+
+            setResult(RESULT_OK, new Intent().setData(ContentUris.withAppendedId(uri, mediaId)));
+            finish();
+            return;
+        }
+
+        // Need to stop the playbackservice, in case it is busy playing audio
+        // and the user selected a video.
+        if (MusicUtils.sService != null) {
+            try {
+                MusicUtils.sService.stop();
+            } catch (RemoteException ex) {
+            }
+        }
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        intent.setDataAndType(ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id), type);
+
+        startActivity(intent);
+    }
+
+    private void MakeCursor() {
+        String[] audiocols = new String[] {
+                MediaStore.Audio.Media._ID,
+                MediaStore.Audio.Media.ARTIST,
+                MediaStore.Audio.Media.ALBUM,
+                MediaStore.Audio.Media.TITLE,
+                MediaStore.Audio.Media.DATA,
+                MediaStore.Audio.Media.MIME_TYPE,
+                MediaStore.Audio.Media.YEAR
+        };
+        String[] videocols = new String[] {
+                MediaStore.Audio.Media._ID,
+                MediaStore.Audio.Media.TITLE,
+                MediaStore.Audio.Media.ARTIST,
+                MediaStore.Audio.Media.ALBUM,
+                MediaStore.Audio.Media.TITLE,
+                MediaStore.Audio.Media.DATA,
+                MediaStore.Audio.Media.MIME_TYPE
+        };
+
+        Cursor[] cs;
+        // Use ArrayList for the moment, since we don't know the size of
+        // Cursor[]. If the length of Corsor[] larger than really used,
+        // a NPE will come up when access the content of Corsor[].
+        ArrayList<Cursor> cList = new ArrayList<Cursor>();
+        Intent intent = getIntent();
+        String type = intent.getType();
+
+        if (mFirstYear != null) {
+            // If mFirstYear is not null, the picker only for audio because
+            // video has no year column.
+            if(type.equals("video/*")) {
+                mCursor = null;
+                return;
+            }
+
+            mWhereClause = MediaStore.Audio.Media.YEAR + ">=" + mFirstYear + " AND " +
+                           MediaStore.Audio.Media.YEAR + "<=" + mLastYear;
+        }
+
+        // If use Cursor[] as before, the Cursor[i] could be null when there is
+        // no video/audio/sdcard. Then a NPE will come up when access the content of the
+        // Array.
+
+        Cursor c;
+        if (type.equals("video/*")) {
+            // Only video.
+            c = MusicUtils.query(this, MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
+                    videocols, null , null, mSortOrder);
+            if (c != null) {
+                cList.add(c);
+            }
+        } else {
+            c = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                    audiocols, mWhereClause , null, mSortOrder);
+
+            if (c != null) {
+                cList.add(c);
+            }
+
+            if (mFirstYear == null && intent.getType().equals("media/*")) {
+                // video has no year column
+                c = MusicUtils.query(this, MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
+                    videocols, null , null, mSortOrder);
+                if (c != null) {
+                    cList.add(c);
+                }
+            }
+        }
+
+        // Get the ArrayList size.
+        int size = cList.size();
+        if (0 == size) {
+            // If no video/audio/SDCard exist, return.
+            mCursor = null;
+            return;
+        }
+
+        // The size is known now, we're sure each item of Cursor[] is not null.
+        cs = new Cursor[size];
+        cs = cList.toArray(cs);
+        mCursor = new SortCursor(cs, MediaStore.Audio.Media.TITLE);
+    }
+
+    private Cursor mCursor;
+    private String mSortOrder = MediaStore.Audio.Media.TITLE + " COLLATE UNICODE";
+    private String mFirstYear;
+    private String mLastYear;
+    private String mWhereClause;
+
+    class PickListAdapter extends SimpleCursorAdapter {
+        int mTitleIdx;
+        int mArtistIdx;
+        int mAlbumIdx;
+        int mMimeIdx;
+
+        PickListAdapter(Context context, int layout, Cursor cursor, String[] from, int[] to) {
+            super(context, layout, cursor, from, to);
+
+            mTitleIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE);
+            mArtistIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST);
+            mAlbumIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM);
+            mMimeIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.MIME_TYPE);
+        }
+        
+        @Override
+        public View newView(Context context, Cursor cursor, ViewGroup parent) {
+           View v = super.newView(context, cursor, parent);
+           ImageView iv = (ImageView) v.findViewById(R.id.icon);
+           iv.setVisibility(View.VISIBLE);
+           ViewGroup.LayoutParams p = iv.getLayoutParams();
+           p.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+           p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+
+           TextView tv = (TextView) v.findViewById(R.id.duration);
+           tv.setVisibility(View.GONE);
+           iv = (ImageView) v.findViewById(R.id.play_indicator);
+           iv.setVisibility(View.GONE);
+           
+           return v;
+        }
+
+        @Override
+        public void bindView(View view, Context context, Cursor cursor) {
+
+            TextView tv = (TextView) view.findViewById(R.id.line1);
+            String name = cursor.getString(mTitleIdx);
+            tv.setText(name);
+            
+            tv = (TextView) view.findViewById(R.id.line2);
+            name = cursor.getString(mAlbumIdx);
+            StringBuilder builder = new StringBuilder();
+            if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
+                builder.append(context.getString(R.string.unknown_album_name));
+            } else {
+                builder.append(name);
+            }
+            builder.append("\n");
+            name = cursor.getString(mArtistIdx);
+            if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
+                builder.append(context.getString(R.string.unknown_artist_name));
+            } else {
+                builder.append(name);
+            }
+            tv.setText(builder.toString());
+
+            String text = cursor.getString(mMimeIdx);
+            ImageView iv = (ImageView) view.findViewById(R.id.icon);;
+            if("audio/midi".equals(text)) {
+                iv.setImageResource(R.drawable.midi);
+            } else if(text != null && (text.startsWith("audio") ||
+                    text.equals("application/ogg") ||
+                    text.equals("application/x-ogg"))) {
+                iv.setImageResource(R.drawable.ic_search_category_music_song);
+            } else if(text != null && text.startsWith("video")) {
+                iv.setImageResource(R.drawable.movie);
+            } else {
+                iv.setImageResource(0);
+            }
+        }
+    }
+}
diff --git a/src/com/android/music/MediaPlaybackActivity.java b/src/com/android/music/MediaPlaybackActivity.java
new file mode 100644
index 0000000..2b9125d
--- /dev/null
+++ b/src/com/android/music/MediaPlaybackActivity.java
@@ -0,0 +1,1310 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.SearchManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.graphics.Bitmap;
+import android.media.AudioManager;
+import android.media.MediaFile;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.provider.MediaStore;
+import android.text.Layout;
+import android.text.TextUtils.TruncateAt;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.SubMenu;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.Window;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.SeekBar;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+
+
+public class MediaPlaybackActivity extends Activity implements MusicUtils.Defs,
+    View.OnTouchListener, View.OnLongClickListener
+{
+    private static final int USE_AS_RINGTONE = CHILD_MENU_BASE;
+    
+    private boolean mOneShot = false;
+    private boolean mSeeking = false;
+    private boolean mTrackball;
+    private long mStartSeekPos = 0;
+    private long mLastSeekEventTime;
+    private IMediaPlaybackService mService = null;
+    private RepeatingImageButton mPrevButton;
+    private ImageButton mPauseButton;
+    private RepeatingImageButton mNextButton;
+    private ImageButton mRepeatButton;
+    private ImageButton mShuffleButton;
+    private ImageButton mQueueButton;
+    private Worker mAlbumArtWorker;
+    private AlbumArtHandler mAlbumArtHandler;
+    private Toast mToast;
+    private boolean mRelaunchAfterConfigChange;
+    private int mTouchSlop;
+
+    public MediaPlaybackActivity()
+    {
+    }
+
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle icicle)
+    {
+        super.onCreate(icicle);
+        setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+        mAlbumArtWorker = new Worker("album art worker");
+        mAlbumArtHandler = new AlbumArtHandler(mAlbumArtWorker.getLooper());
+
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+        setContentView(R.layout.audio_player);
+
+        mCurrentTime = (TextView) findViewById(R.id.currenttime);
+        mTotalTime = (TextView) findViewById(R.id.totaltime);
+        mProgress = (ProgressBar) findViewById(android.R.id.progress);
+        mAlbum = (ImageView) findViewById(R.id.album);
+        mArtistName = (TextView) findViewById(R.id.artistname);
+        mAlbumName = (TextView) findViewById(R.id.albumname);
+        mTrackName = (TextView) findViewById(R.id.trackname);
+
+        View v = (View)mArtistName.getParent(); 
+        v.setOnTouchListener(this);
+        v.setOnLongClickListener(this);
+
+        v = (View)mAlbumName.getParent();
+        v.setOnTouchListener(this);
+        v.setOnLongClickListener(this);
+
+        v = (View)mTrackName.getParent();
+        v.setOnTouchListener(this);
+        v.setOnLongClickListener(this);
+        
+        mPrevButton = (RepeatingImageButton) findViewById(R.id.prev);
+        mPrevButton.setOnClickListener(mPrevListener);
+        mPrevButton.setRepeatListener(mRewListener, 260);
+        mPauseButton = (ImageButton) findViewById(R.id.pause);
+        mPauseButton.requestFocus();
+        mPauseButton.setOnClickListener(mPauseListener);
+        mNextButton = (RepeatingImageButton) findViewById(R.id.next);
+        mNextButton.setOnClickListener(mNextListener);
+        mNextButton.setRepeatListener(mFfwdListener, 260);
+        seekmethod = 1;
+
+        mTrackball = true; /* (See bug 1044348) (getResources().getConfiguration().navigation == 
+            Resources.Configuration.NAVIGATION_TRACKBALL);*/
+        
+        mQueueButton = (ImageButton) findViewById(R.id.curplaylist);
+        mQueueButton.setOnClickListener(mQueueListener);
+        mShuffleButton = ((ImageButton) findViewById(R.id.shuffle));
+        mShuffleButton.setOnClickListener(mShuffleListener);
+        mRepeatButton = ((ImageButton) findViewById(R.id.repeat));
+        mRepeatButton.setOnClickListener(mRepeatListener);
+        
+        if (mProgress instanceof SeekBar) {
+            SeekBar seeker = (SeekBar) mProgress;
+            seeker.setOnSeekBarChangeListener(mSeekListener);
+        }
+        mProgress.setMax(1000);
+        
+        if (icicle != null) {
+            mRelaunchAfterConfigChange = icicle.getBoolean("configchange");
+            mOneShot = icicle.getBoolean("oneshot");
+        } else {
+            mOneShot = getIntent().getBooleanExtra("oneshot", false);
+        }
+
+        mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
+    }
+    
+    int mInitialX = -1;
+    int mLastX = -1;
+    int mTextWidth = 0;
+    int mViewWidth = 0;
+    boolean mDraggingLabel = false;
+    
+    TextView textViewForContainer(View v) {
+        View vv = v.findViewById(R.id.artistname);
+        if (vv != null) return (TextView) vv;
+        vv = v.findViewById(R.id.albumname);
+        if (vv != null) return (TextView) vv;
+        vv = v.findViewById(R.id.trackname);
+        if (vv != null) return (TextView) vv;
+        return null;
+    }
+    
+    public boolean onTouch(View v, MotionEvent event) {
+        int action = event.getAction();
+        TextView tv = textViewForContainer(v);
+        if (tv == null) {
+            return false;
+        }
+        if (action == MotionEvent.ACTION_DOWN) {
+            v.setBackgroundColor(0xff606060);
+            mInitialX = mLastX = (int) event.getX();
+            mDraggingLabel = false;
+        } else if (action == MotionEvent.ACTION_UP ||
+                action == MotionEvent.ACTION_CANCEL) {
+            v.setBackgroundColor(0);
+            if (mDraggingLabel) {
+                Message msg = mLabelScroller.obtainMessage(0, tv);
+                mLabelScroller.sendMessageDelayed(msg, 1000);
+            }
+        } else if (action == MotionEvent.ACTION_MOVE) {
+            if (mDraggingLabel) {
+                int scrollx = tv.getScrollX();
+                int x = (int) event.getX();
+                int delta = mLastX - x;
+                if (delta != 0) {
+                    mLastX = x;
+                    scrollx += delta;
+                    if (scrollx > mTextWidth) {
+                        // scrolled the text completely off the view to the left
+                        scrollx -= mTextWidth;
+                        scrollx -= mViewWidth;
+                    }
+                    if (scrollx < -mViewWidth) {
+                        // scrolled the text completely off the view to the right
+                        scrollx += mViewWidth;
+                        scrollx += mTextWidth;
+                    }
+                    tv.scrollTo(scrollx, 0);
+                }
+                return true;
+            }
+            int delta = mInitialX - (int) event.getX();
+            if (Math.abs(delta) > mTouchSlop) {
+                // start moving
+                mLabelScroller.removeMessages(0, tv);
+                
+                // Only turn ellipsizing off when it's not already off, because it
+                // causes the scroll position to be reset to 0.
+                if (tv.getEllipsize() != null) {
+                    tv.setEllipsize(null);
+                }
+                Layout ll = tv.getLayout();
+                // layout might be null if the text just changed, or ellipsizing
+                // was just turned off
+                if (ll == null) {
+                    return false;
+                }
+                // get the non-ellipsized line width, to determine whether scrolling
+                // should even be allowed
+                mTextWidth = (int) tv.getLayout().getLineWidth(0);
+                mViewWidth = tv.getWidth();
+                if (mViewWidth > mTextWidth) {
+                    tv.setEllipsize(TruncateAt.END);
+                    v.cancelLongPress();
+                    return false;
+                }
+                mDraggingLabel = true;
+                tv.setHorizontalFadingEdgeEnabled(true);
+                v.cancelLongPress();
+                return true;
+            }
+        }
+        return false; 
+    }
+
+    Handler mLabelScroller = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            TextView tv = (TextView) msg.obj;
+            int x = tv.getScrollX();
+            x = x * 3 / 4;
+            tv.scrollTo(x, 0);
+            if (x == 0) {
+                tv.setEllipsize(TruncateAt.END);
+            } else {
+                Message newmsg = obtainMessage(0, tv);
+                mLabelScroller.sendMessageDelayed(newmsg, 15);
+            }
+        }
+    };
+    
+    public boolean onLongClick(View view) {
+
+        CharSequence title = null;
+        String mime = null;
+        String query = null;
+        String artist;
+        String album;
+        String song;
+        
+        try {
+            artist = mService.getArtistName();
+            album = mService.getAlbumName();
+            song = mService.getTrackName();
+        } catch (RemoteException ex) {
+            return true;
+        }
+        
+        boolean knownartist = !MediaFile.UNKNOWN_STRING.equals(artist);
+        boolean knownalbum = !MediaFile.UNKNOWN_STRING.equals(album);
+        
+        if (knownartist && view.equals(mArtistName.getParent())) {
+            title = artist;
+            query = artist.toString();
+            mime = MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE;
+        } else if (knownalbum && view.equals(mAlbumName.getParent())) {
+            title = album;
+            if (knownartist) {
+                query = artist + " " + album;
+            } else {
+                query = album;
+            }
+            mime = MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE;
+        } else if (view.equals(mTrackName.getParent()) || !knownartist || !knownalbum) {
+            title = song;
+            if (knownartist) {
+                query = artist + " " + song;
+            } else {
+                query = song;
+            }
+            mime = "audio/*"; // the specific type doesn't matter, so don't bother retrieving it
+        } else {
+            throw new RuntimeException("shouldn't be here");
+        }
+        title = getString(R.string.mediasearch, title);
+
+        Intent i = new Intent();
+        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH);
+        i.putExtra(SearchManager.QUERY, query);
+        if(knownartist) {
+            i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, artist);
+        }
+        if(knownalbum) {
+            i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, album);
+        }
+        i.putExtra(MediaStore.EXTRA_MEDIA_TITLE, song);
+        i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, mime);
+
+        startActivity(Intent.createChooser(i, title));
+        return true;
+    }
+
+    private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
+        public void onStartTrackingTouch(SeekBar bar) {
+            mLastSeekEventTime = 0;
+        }
+        public void onProgressChanged(SeekBar bar, int progress, boolean fromtouch) {
+            if (mService == null) return;
+            if (fromtouch) {
+                long now = SystemClock.elapsedRealtime();
+                if ((now - mLastSeekEventTime) > 250) {
+                    mLastSeekEventTime = now;
+                    mPosOverride = mDuration * progress / 1000;
+                    try {
+                        mService.seek(mPosOverride);
+                    } catch (RemoteException ex) {
+                    }
+                }
+            }
+        }
+        public void onStopTrackingTouch(SeekBar bar) {
+            mPosOverride = -1;
+        }
+    };
+    
+    private View.OnClickListener mQueueListener = new View.OnClickListener() {
+        public void onClick(View v) {
+            startActivity(
+                    new Intent(Intent.ACTION_EDIT)
+                    .setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track")
+                    .putExtra("playlist", "nowplaying")
+            );
+        }
+    };
+    
+    private View.OnClickListener mShuffleListener = new View.OnClickListener() {
+        public void onClick(View v) {
+            toggleShuffle();
+        }
+    };
+
+    private View.OnClickListener mRepeatListener = new View.OnClickListener() {
+        public void onClick(View v) {
+            cycleRepeat();
+        }
+    };
+
+    private View.OnClickListener mPauseListener = new View.OnClickListener() {
+        public void onClick(View v) {
+            doPauseResume();
+        }
+    };
+
+    private View.OnClickListener mPrevListener = new View.OnClickListener() {
+        public void onClick(View v) {
+            if (mService == null) return;
+            try {
+                if (mService.position() < 2000) {
+                    mService.prev();
+                } else {
+                    mService.seek(0);
+                    mService.play();
+                }
+            } catch (RemoteException ex) {
+            }
+        }
+    };
+
+    private View.OnClickListener mNextListener = new View.OnClickListener() {
+        public void onClick(View v) {
+            if (mService == null) return;
+            try {
+                mService.next();
+            } catch (RemoteException ex) {
+            }
+        }
+    };
+
+    private RepeatingImageButton.RepeatListener mRewListener =
+        new RepeatingImageButton.RepeatListener() {
+        public void onRepeat(View v, long howlong, int repcnt) {
+            scanBackward(repcnt, howlong);
+        }
+    };
+    
+    private RepeatingImageButton.RepeatListener mFfwdListener =
+        new RepeatingImageButton.RepeatListener() {
+        public void onRepeat(View v, long howlong, int repcnt) {
+            scanForward(repcnt, howlong);
+        }
+    };
+   
+    @Override
+    public void onStop() {
+        paused = true;
+        if (mService != null && mOneShot && getChangingConfigurations() == 0) {
+            try {
+                mService.stop();
+            } catch (RemoteException ex) {
+            }
+        }
+        mHandler.removeMessages(REFRESH);
+        unregisterReceiver(mStatusListener);
+        MusicUtils.unbindFromService(this);
+        super.onStop();
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        outState.putBoolean("configchange", getChangingConfigurations() != 0);
+        outState.putBoolean("oneshot", mOneShot);
+        super.onSaveInstanceState(outState);
+    }
+    
+    @Override
+    public void onStart() {
+        super.onStart();
+        paused = false;
+
+        if (false == MusicUtils.bindToService(this, osc)) {
+            // something went wrong
+            mHandler.sendEmptyMessage(QUIT);
+        }
+        
+        IntentFilter f = new IntentFilter();
+        f.addAction(MediaPlaybackService.PLAYSTATE_CHANGED);
+        f.addAction(MediaPlaybackService.META_CHANGED);
+        f.addAction(MediaPlaybackService.PLAYBACK_COMPLETE);
+        registerReceiver(mStatusListener, new IntentFilter(f));
+        updateTrackInfo();
+        long next = refreshNow();
+        queueNextRefresh(next);
+    }
+    
+    @Override
+    public void onNewIntent(Intent intent) {
+        setIntent(intent);
+        mOneShot = intent.getBooleanExtra("oneshot", false);
+    }
+    
+    @Override
+    public void onResume() {
+        super.onResume();
+        updateTrackInfo();
+        setPauseButtonImage();
+    }
+    
+    @Override
+    public void onDestroy()
+    {
+        mAlbumArtWorker.quit();
+        super.onDestroy();
+        //System.out.println("***************** playback activity onDestroy\n");
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+        // Don't show the menu items if we got launched by path/filedescriptor, since
+        // those tend to not be in the media database.
+        if (MusicUtils.getCurrentAudioId() >= 0) {
+            if (!mOneShot) {
+                menu.add(0, GOTO_START, 0, R.string.goto_start).setIcon(R.drawable.ic_menu_music_library);
+                menu.add(0, PARTY_SHUFFLE, 0, R.string.party_shuffle); // icon will be set in onPrepareOptionsMenu()
+            }
+            SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0,
+                    R.string.add_to_playlist).setIcon(android.R.drawable.ic_menu_add);
+            MusicUtils.makePlaylistMenu(this, sub);
+            menu.add(0, USE_AS_RINGTONE, 0, R.string.ringtone_menu_short).setIcon(R.drawable.ic_menu_set_as_ringtone);
+            menu.add(0, DELETE_ITEM, 0, R.string.delete_item).setIcon(R.drawable.ic_menu_delete);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        MenuItem item = menu.findItem(PARTY_SHUFFLE);
+        if (item != null) {
+            int shuffle = MusicUtils.getCurrentShuffleMode();
+            if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
+                item.setIcon(R.drawable.ic_menu_party_shuffle);
+                item.setTitle(R.string.party_shuffle_off);
+            } else {
+                item.setIcon(R.drawable.ic_menu_party_shuffle);
+                item.setTitle(R.string.party_shuffle);
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        Intent intent;
+        try {
+            switch (item.getItemId()) {
+                case GOTO_START:
+                    intent = new Intent();
+                    intent.setClass(this, MusicBrowserActivity.class);
+                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                            | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                    startActivity(intent);
+                    break;
+                case USE_AS_RINGTONE: {
+                    // Set the system setting to make this the current ringtone
+                    if (mService != null) {
+                        MusicUtils.setRingtone(this, mService.getAudioId());
+                    }
+                    return true;
+                }
+                case PARTY_SHUFFLE:
+                    if (mService != null) {
+                        int shuffle = mService.getShuffleMode();
+                        if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
+                            mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
+                        } else {
+                            mService.setShuffleMode(MediaPlaybackService.SHUFFLE_AUTO);
+                        }
+                    }
+                    setShuffleButtonImage();
+                    break;
+                    
+                case NEW_PLAYLIST: {
+                    intent = new Intent();
+                    intent.setClass(this, CreatePlaylist.class);
+                    startActivityForResult(intent, NEW_PLAYLIST);
+                    return true;
+                }
+
+                case PLAYLIST_SELECTED: {
+                    int [] list = new int[1];
+                    list[0] = MusicUtils.getCurrentAudioId();
+                    int playlist = item.getIntent().getIntExtra("playlist", 0);
+                    MusicUtils.addToPlaylist(this, list, playlist);
+                    return true;
+                }
+                
+                case DELETE_ITEM: {
+                    if (mService != null) {
+                        int [] list = new int[1];
+                        list[0] = MusicUtils.getCurrentAudioId();
+                        Bundle b = new Bundle();
+                        b.putString("description", getString(R.string.delete_song_desc,
+                                mService.getTrackName()));
+                        b.putIntArray("items", list);
+                        intent = new Intent();
+                        intent.setClass(this, DeleteItems.class);
+                        intent.putExtras(b);
+                        startActivityForResult(intent, -1);
+                    }
+                    return true;
+                }
+            }
+        } catch (RemoteException ex) {
+        }
+        return super.onOptionsItemSelected(item);
+    }
+    
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+        if (resultCode != RESULT_OK) {
+            return;
+        }
+        switch (requestCode) {
+            case NEW_PLAYLIST:
+                Uri uri = intent.getData();
+                if (uri != null) {
+                    int [] list = new int[1];
+                    list[0] = MusicUtils.getCurrentAudioId();
+                    int playlist = Integer.parseInt(uri.getLastPathSegment());
+                    MusicUtils.addToPlaylist(this, list, playlist);
+                }
+                break;
+        }
+    }
+    private final int keyboard[][] = {
+        {
+            KeyEvent.KEYCODE_Q,
+            KeyEvent.KEYCODE_W,
+            KeyEvent.KEYCODE_E,
+            KeyEvent.KEYCODE_R,
+            KeyEvent.KEYCODE_T,
+            KeyEvent.KEYCODE_Y,
+            KeyEvent.KEYCODE_U,
+            KeyEvent.KEYCODE_I,
+            KeyEvent.KEYCODE_O,
+            KeyEvent.KEYCODE_P,
+        },
+        {
+            KeyEvent.KEYCODE_A,
+            KeyEvent.KEYCODE_S,
+            KeyEvent.KEYCODE_D,
+            KeyEvent.KEYCODE_F,
+            KeyEvent.KEYCODE_G,
+            KeyEvent.KEYCODE_H,
+            KeyEvent.KEYCODE_J,
+            KeyEvent.KEYCODE_K,
+            KeyEvent.KEYCODE_L,
+            KeyEvent.KEYCODE_DEL,
+        },
+        {
+            KeyEvent.KEYCODE_Z,
+            KeyEvent.KEYCODE_X,
+            KeyEvent.KEYCODE_C,
+            KeyEvent.KEYCODE_V,
+            KeyEvent.KEYCODE_B,
+            KeyEvent.KEYCODE_N,
+            KeyEvent.KEYCODE_M,
+            KeyEvent.KEYCODE_COMMA,
+            KeyEvent.KEYCODE_PERIOD,
+            KeyEvent.KEYCODE_ENTER
+        }
+
+    };
+
+    private int lastX;
+    private int lastY;
+
+    private boolean seekMethod1(int keyCode)
+    {
+        for(int x=0;x<10;x++) {
+            for(int y=0;y<3;y++) {
+                if(keyboard[y][x] == keyCode) {
+                    int dir = 0;
+                    // top row
+                    if(x == lastX && y == lastY) dir = 0;
+                    else if (y == 0 && lastY == 0 && x > lastX) dir = 1;
+                    else if (y == 0 && lastY == 0 && x < lastX) dir = -1;
+                    // bottom row
+                    else if (y == 2 && lastY == 2 && x > lastX) dir = -1;
+                    else if (y == 2 && lastY == 2 && x < lastX) dir = 1;
+                    // moving up
+                    else if (y < lastY && x <= 4) dir = 1; 
+                    else if (y < lastY && x >= 5) dir = -1; 
+                    // moving down
+                    else if (y > lastY && x <= 4) dir = -1; 
+                    else if (y > lastY && x >= 5) dir = 1; 
+                    lastX = x;
+                    lastY = y;
+                    try {
+                        mService.seek(mService.position() + dir * 5);
+                    } catch (RemoteException ex) {
+                    }
+                    refreshNow();
+                    return true;
+                }
+            }
+        }
+        lastX = -1;
+        lastY = -1;
+        return false;
+    }
+
+    private boolean seekMethod2(int keyCode)
+    {
+        if (mService == null) return false;
+        for(int i=0;i<10;i++) {
+            if(keyboard[0][i] == keyCode) {
+                int seekpercentage = 100*i/10;
+                try {
+                    mService.seek(mService.duration() * seekpercentage / 100);
+                } catch (RemoteException ex) {
+                }
+                refreshNow();
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        try {
+            switch(keyCode)
+            {
+                case KeyEvent.KEYCODE_DPAD_LEFT:
+                    if (mTrackball) {
+                        break;
+                    }
+                    if (mService != null) {
+                        if (!mSeeking && mStartSeekPos >= 0) {
+                            mPauseButton.requestFocus();
+                            if (mStartSeekPos < 1000) {
+                                mService.prev();
+                            } else {
+                                mService.seek(0);
+                            }
+                        } else {
+                            scanBackward(-1, event.getEventTime() - event.getDownTime());
+                            mPauseButton.requestFocus();
+                            mStartSeekPos = -1;
+                        }
+                    }
+                    mSeeking = false;
+                    mPosOverride = -1;
+                    return true;
+                case KeyEvent.KEYCODE_DPAD_RIGHT:
+                    if (mTrackball) {
+                        break;
+                    }
+                    if (mService != null) {
+                        if (!mSeeking && mStartSeekPos >= 0) {
+                            mPauseButton.requestFocus();
+                            mService.next();
+                        } else {
+                            scanForward(-1, event.getEventTime() - event.getDownTime());
+                            mPauseButton.requestFocus();
+                            mStartSeekPos = -1;
+                        }
+                    }
+                    mSeeking = false;
+                    mPosOverride = -1;
+                    return true;
+            }
+        } catch (RemoteException ex) {
+        }
+        return super.onKeyUp(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event)
+    {
+        int direction = -1;
+        int repcnt = event.getRepeatCount();
+
+        if((seekmethod==0)?seekMethod1(keyCode):seekMethod2(keyCode))
+            return true;
+
+        switch(keyCode)
+        {
+/*
+            // image scale
+            case KeyEvent.KEYCODE_Q: av.adjustParams(-0.05, 0.0, 0.0, 0.0, 0.0,-1.0); break;
+            case KeyEvent.KEYCODE_E: av.adjustParams( 0.05, 0.0, 0.0, 0.0, 0.0, 1.0); break;
+            // image translate
+            case KeyEvent.KEYCODE_W: av.adjustParams(    0.0, 0.0,-1.0, 0.0, 0.0, 0.0); break;
+            case KeyEvent.KEYCODE_X: av.adjustParams(    0.0, 0.0, 1.0, 0.0, 0.0, 0.0); break;
+            case KeyEvent.KEYCODE_A: av.adjustParams(    0.0,-1.0, 0.0, 0.0, 0.0, 0.0); break;
+            case KeyEvent.KEYCODE_D: av.adjustParams(    0.0, 1.0, 0.0, 0.0, 0.0, 0.0); break;
+            // camera rotation
+            case KeyEvent.KEYCODE_R: av.adjustParams(    0.0, 0.0, 0.0, 0.0, 0.0,-1.0); break;
+            case KeyEvent.KEYCODE_U: av.adjustParams(    0.0, 0.0, 0.0, 0.0, 0.0, 1.0); break;
+            // camera translate
+            case KeyEvent.KEYCODE_Y: av.adjustParams(    0.0, 0.0, 0.0, 0.0,-1.0, 0.0); break;
+            case KeyEvent.KEYCODE_N: av.adjustParams(    0.0, 0.0, 0.0, 0.0, 1.0, 0.0); break;
+            case KeyEvent.KEYCODE_G: av.adjustParams(    0.0, 0.0, 0.0,-1.0, 0.0, 0.0); break;
+            case KeyEvent.KEYCODE_J: av.adjustParams(    0.0, 0.0, 0.0, 1.0, 0.0, 0.0); break;
+
+*/
+
+            case KeyEvent.KEYCODE_SLASH:
+                seekmethod = 1 - seekmethod;
+                return true;
+
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+                if (mTrackball) {
+                    break;
+                }
+                if (!mPrevButton.hasFocus()) {
+                    mPrevButton.requestFocus();
+                }
+                scanBackward(repcnt, event.getEventTime() - event.getDownTime());
+                return true;
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                if (mTrackball) {
+                    break;
+                }
+                if (!mNextButton.hasFocus()) {
+                    mNextButton.requestFocus();
+                }
+                scanForward(repcnt, event.getEventTime() - event.getDownTime());
+                return true;
+
+            case KeyEvent.KEYCODE_S:
+                toggleShuffle();
+                return true;
+
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+            case KeyEvent.KEYCODE_SPACE:
+                doPauseResume();
+                return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+    
+    private void scanBackward(int repcnt, long delta) {
+        if(mService == null) return;
+        try {
+            if(repcnt == 0) {
+                mStartSeekPos = mService.position();
+                mLastSeekEventTime = 0;
+                mSeeking = false;
+            } else {
+                mSeeking = true;
+                if (delta < 5000) {
+                    // seek at 10x speed for the first 5 seconds
+                    delta = delta * 10; 
+                } else {
+                    // seek at 40x after that
+                    delta = 50000 + (delta - 5000) * 40;
+                }
+                long newpos = mStartSeekPos - delta;
+                if (newpos < 0) {
+                    // move to previous track
+                    mService.prev();
+                    long duration = mService.duration();
+                    mStartSeekPos += duration;
+                    newpos += duration;
+                }
+                if (((delta - mLastSeekEventTime) > 250) || repcnt < 0){
+                    mService.seek(newpos);
+                    mLastSeekEventTime = delta;
+                }
+                if (repcnt >= 0) {
+                    mPosOverride = newpos;
+                } else {
+                    mPosOverride = -1;
+                }
+                refreshNow();
+            }
+        } catch (RemoteException ex) {
+        }
+    }
+
+    private void scanForward(int repcnt, long delta) {
+        if(mService == null) return;
+        try {
+            if(repcnt == 0) {
+                mStartSeekPos = mService.position();
+                mLastSeekEventTime = 0;
+                mSeeking = false;
+            } else {
+                mSeeking = true;
+                if (delta < 5000) {
+                    // seek at 10x speed for the first 5 seconds
+                    delta = delta * 10; 
+                } else {
+                    // seek at 40x after that
+                    delta = 50000 + (delta - 5000) * 40;
+                }
+                long newpos = mStartSeekPos + delta;
+                long duration = mService.duration();
+                if (newpos >= duration) {
+                    // move to next track
+                    mService.next();
+                    mStartSeekPos -= duration; // is OK to go negative
+                    newpos -= duration;
+                }
+                if (((delta - mLastSeekEventTime) > 250) || repcnt < 0){
+                    mService.seek(newpos);
+                    mLastSeekEventTime = delta;
+                }
+                if (repcnt >= 0) {
+                    mPosOverride = newpos;
+                } else {
+                    mPosOverride = -1;
+                }
+                refreshNow();
+            }
+        } catch (RemoteException ex) {
+        }
+    }
+    
+    private void doPauseResume() {
+        try {
+            if(mService != null) {
+                if (mService.isPlaying()) {
+                    mService.pause();
+                } else {
+                    mService.play();
+                }
+                refreshNow();
+                setPauseButtonImage();
+            }
+        } catch (RemoteException ex) {
+        }
+    }
+    
+    private void toggleShuffle() {
+        if (mService == null) {
+            return;
+        }
+        try {
+            int shuffle = mService.getShuffleMode();
+            if (shuffle == MediaPlaybackService.SHUFFLE_NONE) {
+                mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NORMAL);
+                if (mService.getRepeatMode() == MediaPlaybackService.REPEAT_CURRENT) {
+                    mService.setRepeatMode(MediaPlaybackService.REPEAT_ALL);
+                    setRepeatButtonImage();
+                }
+                showToast(R.string.shuffle_on_notif);
+            } else if (shuffle == MediaPlaybackService.SHUFFLE_NORMAL ||
+                    shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
+                mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
+                showToast(R.string.shuffle_off_notif);
+            } else {
+                Log.e("MediaPlaybackActivity", "Invalid shuffle mode: " + shuffle);
+            }
+            setShuffleButtonImage();
+        } catch (RemoteException ex) {
+        }
+    }
+    
+    private void cycleRepeat() {
+        if (mService == null) {
+            return;
+        }
+        try {
+            int mode = mService.getRepeatMode();
+            if (mode == MediaPlaybackService.REPEAT_NONE) {
+                mService.setRepeatMode(MediaPlaybackService.REPEAT_ALL);
+                showToast(R.string.repeat_all_notif);
+            } else if (mode == MediaPlaybackService.REPEAT_ALL) {
+                mService.setRepeatMode(MediaPlaybackService.REPEAT_CURRENT);
+                if (mService.getShuffleMode() != MediaPlaybackService.SHUFFLE_NONE) {
+                    mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
+                    setShuffleButtonImage();
+                }
+                showToast(R.string.repeat_current_notif);
+            } else {
+                mService.setRepeatMode(MediaPlaybackService.REPEAT_NONE);
+                showToast(R.string.repeat_off_notif);
+            }
+            setRepeatButtonImage();
+        } catch (RemoteException ex) {
+        }
+        
+    }
+    
+    private void showToast(int resid) {
+        if (mToast == null) {
+            mToast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
+        }
+        mToast.setText(resid);
+        mToast.show();
+    }
+
+    private void startPlayback() {
+
+        if(mService == null)
+            return;
+        Intent intent = getIntent();
+        String filename = "";
+        Uri uri = intent.getData();
+        if (uri != null && uri.toString().length() > 0) {
+            // If this is a file:// URI, just use the path directly instead
+            // of going through the open-from-filedescriptor codepath.
+            String scheme = uri.getScheme();
+            if ("file".equals(scheme)) {
+                filename = uri.getPath();
+            } else {
+                filename = uri.toString();
+            }
+            try {
+                mOneShot = true;
+                if (! mRelaunchAfterConfigChange) {
+                    mService.stop();
+                    mService.openfile(filename);
+                    mService.play();
+                }
+            } catch (Exception ex) {
+                Log.d("MediaPlaybackActivity", "couldn't start playback: " + ex);
+            }
+        }
+
+        updateTrackInfo();
+        long next = refreshNow();
+        queueNextRefresh(next);
+    }
+
+    private ServiceConnection osc = new ServiceConnection() {
+            public void onServiceConnected(ComponentName classname, IBinder obj) {
+                mService = IMediaPlaybackService.Stub.asInterface(obj);
+                if (MusicUtils.sService == null) {
+                    MusicUtils.sService = mService;
+                }
+                startPlayback();
+                try {
+                    // Assume something is playing when the service says it is,
+                    // but also if the audio ID is valid but the service is paused.
+                    if (mService.getAudioId() >= 0 || mService.isPlaying() ||
+                            mService.getPath() != null) {
+                        // something is playing now, we're done
+                        if (mOneShot || mService.getAudioId() < 0) {
+                            mRepeatButton.setVisibility(View.INVISIBLE);
+                            mShuffleButton.setVisibility(View.INVISIBLE);
+                            mQueueButton.setVisibility(View.INVISIBLE);
+                        } else {
+                            mRepeatButton.setVisibility(View.VISIBLE);
+                            mShuffleButton.setVisibility(View.VISIBLE);
+                            mQueueButton.setVisibility(View.VISIBLE);
+                            setRepeatButtonImage();
+                            setShuffleButtonImage();
+                        }
+                        setPauseButtonImage();
+                        return;
+                    }
+                } catch (RemoteException ex) {
+                }
+                // Service is dead or not playing anything. If we got here as part
+                // of a "play this file" Intent, exit. Otherwise go to the Music
+                // app start screen.
+                if (getIntent().getData() == null) {
+                    Intent intent = new Intent(Intent.ACTION_MAIN);
+                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    intent.setClass(MediaPlaybackActivity.this, MusicBrowserActivity.class);
+                    startActivity(intent);
+                }
+                finish();
+            }
+            public void onServiceDisconnected(ComponentName classname) {
+            }
+    };
+
+    private void setRepeatButtonImage() {
+        try {
+            switch (mService.getRepeatMode()) {
+                case MediaPlaybackService.REPEAT_ALL:
+                    mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_all_btn);
+                    break;
+                case MediaPlaybackService.REPEAT_CURRENT:
+                    mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_once_btn);
+                    break;
+                default:
+                    mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_off_btn);
+                    break;
+            }
+        } catch (RemoteException ex) {
+        }
+    }
+    
+    private void setShuffleButtonImage() {
+        try {
+            switch (mService.getShuffleMode()) {
+                case MediaPlaybackService.SHUFFLE_NONE:
+                    mShuffleButton.setImageResource(R.drawable.ic_mp_shuffle_off_btn);
+                    break;
+                case MediaPlaybackService.SHUFFLE_AUTO:
+                    mShuffleButton.setImageResource(R.drawable.ic_mp_partyshuffle_on_btn);
+                    break;
+                default:
+                    mShuffleButton.setImageResource(R.drawable.ic_mp_shuffle_on_btn);
+                    break;
+            }
+        } catch (RemoteException ex) {
+        }
+    }
+    
+    private void setPauseButtonImage() {
+        try {
+            if (mService != null && mService.isPlaying()) {
+                mPauseButton.setImageResource(android.R.drawable.ic_media_pause);
+            } else {
+                mPauseButton.setImageResource(android.R.drawable.ic_media_play);
+            }
+        } catch (RemoteException ex) {
+        }
+    }
+    
+    private ImageView mAlbum;
+    private TextView mCurrentTime;
+    private TextView mTotalTime;
+    private TextView mArtistName;
+    private TextView mAlbumName;
+    private TextView mTrackName;
+    private ProgressBar mProgress;
+    private long mPosOverride = -1;
+    private long mDuration;
+    private int seekmethod;
+    private boolean paused;
+
+    private static final int REFRESH = 1;
+    private static final int QUIT = 2;
+    private static final int GET_ALBUM_ART = 3;
+    private static final int ALBUM_ART_DECODED = 4;
+
+    private void queueNextRefresh(long delay) {
+        if (!paused) {
+            Message msg = mHandler.obtainMessage(REFRESH);
+            mHandler.removeMessages(REFRESH);
+            mHandler.sendMessageDelayed(msg, delay);
+        }
+    }
+
+    private long refreshNow() {
+        if(mService == null)
+            return 500;
+        try {
+            long pos = mPosOverride < 0 ? mService.position() : mPosOverride;
+            long remaining = 1000 - (pos % 1000);
+            if ((pos >= 0) && (mDuration > 0)) {
+                mCurrentTime.setText(MusicUtils.makeTimeString(this, pos / 1000));
+                
+                if (mService.isPlaying()) {
+                    mCurrentTime.setVisibility(View.VISIBLE);
+                } else {
+                    // blink the counter
+                    int vis = mCurrentTime.getVisibility();
+                    mCurrentTime.setVisibility(vis == View.INVISIBLE ? View.VISIBLE : View.INVISIBLE);
+                    remaining = 500;
+                }
+
+                mProgress.setProgress((int) (1000 * pos / mDuration));
+            } else {
+                mCurrentTime.setText("--:--");
+                mProgress.setProgress(1000);
+            }
+            // return the number of milliseconds until the next full second, so
+            // the counter can be updated at just the right time
+            return remaining;
+        } catch (RemoteException ex) {
+        }
+        return 500;
+    }
+    
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case ALBUM_ART_DECODED:
+                    mAlbum.setImageBitmap((Bitmap)msg.obj);
+                    mAlbum.getDrawable().setDither(true);
+                    break;
+
+                case REFRESH:
+                    long next = refreshNow();
+                    queueNextRefresh(next);
+                    break;
+                    
+                case QUIT:
+                    // This can be moved back to onCreate once the bug that prevents
+                    // Dialogs from being started from onCreate/onResume is fixed.
+                    new AlertDialog.Builder(MediaPlaybackActivity.this)
+                            .setTitle(R.string.service_start_error_title)
+                            .setMessage(R.string.service_start_error_msg)
+                            .setPositiveButton(R.string.service_start_error_button,
+                                    new DialogInterface.OnClickListener() {
+                                        public void onClick(DialogInterface dialog, int whichButton) {
+                                            finish();
+                                        }
+                                    })
+                            .setCancelable(false)
+                            .show();
+                    break;
+
+                default:
+                    break;
+            }
+        }
+    };
+
+    private BroadcastReceiver mStatusListener = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(MediaPlaybackService.META_CHANGED)) {
+                // redraw the artist/title info and
+                // set new max for progress bar
+                updateTrackInfo();
+                setPauseButtonImage();
+                queueNextRefresh(1);
+            } else if (action.equals(MediaPlaybackService.PLAYBACK_COMPLETE)) {
+                if (mOneShot) {
+                    finish();
+                } else {
+                    setPauseButtonImage();
+                }
+            } else if (action.equals(MediaPlaybackService.PLAYSTATE_CHANGED)) {
+                setPauseButtonImage();
+            }
+        }
+    };
+
+    private void updateTrackInfo() {
+        if (mService == null) {
+            return;
+        }
+        try {
+            String path = mService.getPath();
+            if (path == null) {
+                finish();
+                return;
+            }
+            
+            if (mService.getAudioId() < 0 && path.toLowerCase().startsWith("http://")) {
+                ((View) mArtistName.getParent()).setVisibility(View.INVISIBLE);
+                ((View) mAlbumName.getParent()).setVisibility(View.INVISIBLE);
+                mAlbum.setVisibility(View.GONE);
+                mTrackName.setText(path);
+                mAlbumArtHandler.removeMessages(GET_ALBUM_ART);
+                mAlbumArtHandler.obtainMessage(GET_ALBUM_ART, -1, 0).sendToTarget();
+            } else {
+                ((View) mArtistName.getParent()).setVisibility(View.VISIBLE);
+                ((View) mAlbumName.getParent()).setVisibility(View.VISIBLE);
+                String artistName = mService.getArtistName();
+                if (MediaFile.UNKNOWN_STRING.equals(artistName)) {
+                    artistName = getString(R.string.unknown_artist_name);
+                }
+                mArtistName.setText(artistName);
+                String albumName = mService.getAlbumName();
+                int albumid = mService.getAlbumId();
+                if (MediaFile.UNKNOWN_STRING.equals(albumName)) {
+                    albumName = getString(R.string.unknown_album_name);
+                    albumid = -1;
+                }
+                mAlbumName.setText(albumName);
+                mTrackName.setText(mService.getTrackName());
+                mAlbumArtHandler.removeMessages(GET_ALBUM_ART);
+                mAlbumArtHandler.obtainMessage(GET_ALBUM_ART, albumid, 0).sendToTarget();
+                mAlbum.setVisibility(View.VISIBLE);
+            }
+            mDuration = mService.duration();
+            mTotalTime.setText(MusicUtils.makeTimeString(this, mDuration / 1000));
+        } catch (RemoteException ex) {
+            finish();
+        }
+    }
+    
+    public class AlbumArtHandler extends Handler {
+        private int mAlbumId = -1;
+        
+        public AlbumArtHandler(Looper looper) {
+            super(looper);
+        }
+        public void handleMessage(Message msg)
+        {
+            int albumid = msg.arg1;
+            if (msg.what == GET_ALBUM_ART && (mAlbumId != albumid || albumid < 0)) {
+                // while decoding the new image, show the default album art
+                Message numsg = mHandler.obtainMessage(ALBUM_ART_DECODED, null);
+                mHandler.removeMessages(ALBUM_ART_DECODED);
+                mHandler.sendMessageDelayed(numsg, 300);
+                Bitmap bm = MusicUtils.getArtwork(MediaPlaybackActivity.this, albumid);
+                if (bm == null) {
+                    bm = MusicUtils.getArtwork(MediaPlaybackActivity.this, -1);
+                    albumid = -1;
+                }
+                if (bm != null) {
+                    numsg = mHandler.obtainMessage(ALBUM_ART_DECODED, bm);
+                    mHandler.removeMessages(ALBUM_ART_DECODED);
+                    mHandler.sendMessage(numsg);
+                }
+                mAlbumId = albumid;
+            }
+        }
+    }
+    
+    private class Worker implements Runnable {
+        private final Object mLock = new Object();
+        private Looper mLooper;
+        
+        /**
+         * Creates a worker thread with the given name. The thread
+         * then runs a {@link android.os.Looper}.
+         * @param name A name for the new thread
+         */
+        Worker(String name) {
+            Thread t = new Thread(null, this, name);
+            t.setPriority(Thread.MIN_PRIORITY);
+            t.start();
+            synchronized (mLock) {
+                while (mLooper == null) {
+                    try {
+                        mLock.wait();
+                    } catch (InterruptedException ex) {
+                    }
+                }
+            }
+        }
+        
+        public Looper getLooper() {
+            return mLooper;
+        }
+        
+        public void run() {
+            synchronized (mLock) {
+                Looper.prepare();
+                mLooper = Looper.myLooper();
+                mLock.notifyAll();
+            }
+            Looper.loop();
+        }
+        
+        public void quit() {
+            mLooper.quit();
+        }
+    }
+}
+
diff --git a/src/com/android/music/MediaPlaybackService.java b/src/com/android/music/MediaPlaybackService.java
new file mode 100644
index 0000000..ac90156
--- /dev/null
+++ b/src/com/android/music/MediaPlaybackService.java
@@ -0,0 +1,1833 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.BroadcastReceiver;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.gadget.GadgetManager;
+import android.media.AudioManager;
+import android.media.MediaFile;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.os.PowerManager.WakeLock;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.widget.RemoteViews;
+import android.widget.Toast;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneStateIntentReceiver;
+
+import java.io.IOException;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ * Provides "background" audio playback capabilities, allowing the
+ * user to switch between activities without stopping playback.
+ */
+public class MediaPlaybackService extends Service {
+    /** used to specify whether enqueue() should start playing
+     * the new list of files right away, next or once all the currently
+     * queued files have been played
+     */
+    public static final int NOW = 1;
+    public static final int NEXT = 2;
+    public static final int LAST = 3;
+    public static final int PLAYBACKSERVICE_STATUS = 1;
+    
+    public static final int SHUFFLE_NONE = 0;
+    public static final int SHUFFLE_NORMAL = 1;
+    public static final int SHUFFLE_AUTO = 2;
+    
+    public static final int REPEAT_NONE = 0;
+    public static final int REPEAT_CURRENT = 1;
+    public static final int REPEAT_ALL = 2;
+
+    public static final String PLAYSTATE_CHANGED = "com.android.music.playstatechanged";
+    public static final String META_CHANGED = "com.android.music.metachanged";
+    public static final String QUEUE_CHANGED = "com.android.music.queuechanged";
+    public static final String PLAYBACK_COMPLETE = "com.android.music.playbackcomplete";
+    public static final String ASYNC_OPEN_COMPLETE = "com.android.music.asyncopencomplete";
+
+    public static final String SERVICECMD = "com.android.music.musicservicecommand";
+    public static final String CMDNAME = "command";
+    public static final String CMDTOGGLEPAUSE = "togglepause";
+    public static final String CMDSTOP = "stop";
+    public static final String CMDPAUSE = "pause";
+    public static final String CMDPREVIOUS = "previous";
+    public static final String CMDNEXT = "next";
+
+    public static final String TOGGLEPAUSE_ACTION = "com.android.music.musicservicecommand.togglepause";
+    public static final String PAUSE_ACTION = "com.android.music.musicservicecommand.pause";
+    public static final String PREVIOUS_ACTION = "com.android.music.musicservicecommand.previous";
+    public static final String NEXT_ACTION = "com.android.music.musicservicecommand.next";
+
+    private static final int PHONE_CHANGED = 1;
+    private static final int TRACK_ENDED = 1;
+    private static final int RELEASE_WAKELOCK = 2;
+    private static final int SERVER_DIED = 3;
+    private static final int FADEIN = 4;
+    private static final int MAX_HISTORY_SIZE = 10;
+    
+    private MultiPlayer mPlayer;
+    private String mFileToPlay;
+    private PhoneStateIntentReceiver mPsir;
+    private int mShuffleMode = SHUFFLE_NONE;
+    private int mRepeatMode = REPEAT_NONE;
+    private int mMediaMountedCount = 0;
+    private int [] mAutoShuffleList = null;
+    private boolean mOneShot;
+    private int [] mPlayList = null;
+    private int mPlayListLen = 0;
+    private Vector<Integer> mHistory = new Vector<Integer>(MAX_HISTORY_SIZE);
+    private Cursor mCursor;
+    private int mPlayPos = -1;
+    private static final String LOGTAG = "MediaPlaybackService";
+    private final Shuffler mRand = new Shuffler();
+    private int mOpenFailedCounter = 0;
+    String[] mCursorCols = new String[] {
+            "audio._id AS _id",             // index must match IDCOLIDX below
+            MediaStore.Audio.Media.ARTIST,
+            MediaStore.Audio.Media.ALBUM,
+            MediaStore.Audio.Media.TITLE,
+            MediaStore.Audio.Media.DATA,
+            MediaStore.Audio.Media.MIME_TYPE,
+            MediaStore.Audio.Media.ALBUM_ID,
+            MediaStore.Audio.Media.ARTIST_ID,
+            MediaStore.Audio.Media.IS_PODCAST, // index must match PODCASTCOLIDX below
+            MediaStore.Audio.Media.BOOKMARK    // index must match BOOKMARKCOLIDX below
+    };
+    private final static int IDCOLIDX = 0;
+    private final static int PODCASTCOLIDX = 8;
+    private final static int BOOKMARKCOLIDX = 9;
+    private BroadcastReceiver mUnmountReceiver = null;
+    private WakeLock mWakeLock;
+    private int mServiceStartId = -1;
+    private boolean mServiceInUse = false;
+    private boolean mResumeAfterCall = false;
+    private boolean mWasPlaying = false;
+    private boolean mQuietMode = false;
+    
+    private SharedPreferences mPreferences;
+    // We use this to distinguish between different cards when saving/restoring playlists.
+    // This will have to change if we want to support multiple simultaneous cards.
+    private int mCardId;
+    
+    private MediaGadgetProvider mGadgetProvider = MediaGadgetProvider.getInstance();
+    
+    // interval after which we stop the service when idle
+    private static final int IDLE_DELAY = 60000; 
+
+    private Handler mPhoneHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case PHONE_CHANGED:
+                    Phone.State state = mPsir.getPhoneState();
+                    if (state == Phone.State.RINGING) {
+                        AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+                        int ringvolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
+                        if (ringvolume > 0) {
+                            mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);
+                            pause();
+                        }
+                    } else if (state == Phone.State.OFFHOOK) {
+                        // pause the music while a conversation is in progress
+                        mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);
+                        pause();
+                    } else if (state == Phone.State.IDLE) {
+                        // start playing again
+                        if (mResumeAfterCall) {
+                            // resume playback only if music was playing
+                            // when the call was answered
+                            startAndFadeIn();
+                            mResumeAfterCall = false;
+                        }
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
+    };
+    
+    private void startAndFadeIn() {
+        mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
+    }
+    
+    private Handler mMediaplayerHandler = new Handler() {
+        float mCurrentVolume = 1.0f;
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case FADEIN:
+                    if (!isPlaying()) {
+                        mCurrentVolume = 0f;
+                        mPlayer.setVolume(mCurrentVolume);
+                        play();
+                        mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
+                    } else {
+                        mCurrentVolume += 0.01f;
+                        if (mCurrentVolume < 1.0f) {
+                            mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
+                        } else {
+                            mCurrentVolume = 1.0f;
+                        }
+                        mPlayer.setVolume(mCurrentVolume);
+                    }
+                    break;
+                case SERVER_DIED:
+                    if (mWasPlaying) {
+                        next(true);
+                    } else {
+                        // the server died when we were idle, so just
+                        // reopen the same song (it will start again
+                        // from the beginning though when the user
+                        // restarts)
+                        openCurrent();
+                    }
+                    break;
+                case TRACK_ENDED:
+                    if (mRepeatMode == REPEAT_CURRENT) {
+                        seek(0);
+                        play();
+                    } else if (!mOneShot) {
+                        next(false);
+                    } else {
+                        notifyChange(PLAYBACK_COMPLETE);
+                    }
+                    break;
+                case RELEASE_WAKELOCK:
+                    mWakeLock.release();
+                    break;
+                default:
+                    break;
+            }
+        }
+    };
+
+    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            String cmd = intent.getStringExtra("command");
+            if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
+                next(true);
+            } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
+                prev();
+            } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
+                if (isPlaying()) {
+                    pause();
+                } else {
+                    play();
+                }
+            } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
+                pause();
+            } else if (CMDSTOP.equals(cmd)) {
+                pause();
+                seek(0);
+            } else if (MediaGadgetProvider.CMDGADGETUPDATE.equals(cmd)) {
+                // Someone asked us to refresh a set of specific gadgets, probably
+                // because they were just added.
+                int[] gadgetIds = intent.getIntArrayExtra(GadgetManager.EXTRA_GADGET_IDS);
+                mGadgetProvider.performUpdate(MediaPlaybackService.this, gadgetIds);
+            }
+        }
+    };
+
+    public MediaPlaybackService() {
+        mPsir = new PhoneStateIntentReceiver(this, mPhoneHandler);
+        mPsir.notifyPhoneCallState(PHONE_CHANGED);
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        
+        mPreferences = getSharedPreferences("Music", MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
+        mCardId = FileUtils.getFatVolumeId(Environment.getExternalStorageDirectory().getPath());
+        
+        registerExternalStorageListener();
+
+        // Needs to be done in this thread, since otherwise ApplicationContext.getPowerManager() crashes.
+        mPlayer = new MultiPlayer();
+        mPlayer.setHandler(mMediaplayerHandler);
+
+        // Clear leftover notification in case this service previously got killed while playing
+        NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+        nm.cancel(PLAYBACKSERVICE_STATUS);
+        
+        reloadQueue();
+        
+        IntentFilter commandFilter = new IntentFilter();
+        commandFilter.addAction(SERVICECMD);
+        commandFilter.addAction(TOGGLEPAUSE_ACTION);
+        commandFilter.addAction(PAUSE_ACTION);
+        commandFilter.addAction(NEXT_ACTION);
+        commandFilter.addAction(PREVIOUS_ACTION);
+        registerReceiver(mIntentReceiver, commandFilter);
+        
+        mPsir.registerIntent();
+        PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
+        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
+        mWakeLock.setReferenceCounted(false);
+
+        // If the service was idle, but got killed before it stopped itself, the
+        // system will relaunch it. Make sure it gets stopped again in that case.
+        Message msg = mDelayedStopHandler.obtainMessage();
+        mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
+    }
+
+    @Override
+    public void onDestroy() {
+        // Check that we're not being destroyed while something is still playing.
+        if (isPlaying()) {
+            Log.e("MediaPlaybackService", "Service being destroyed while still playing.");
+        }
+        // and for good measure, call mPlayer.stop(), which calls MediaPlayer.reset(), which
+        // releases the MediaPlayer's wake lock, if any.
+        mPlayer.stop();
+        
+        if (mCursor != null) {
+            mCursor.close();
+            mCursor = null;
+        }
+
+        unregisterReceiver(mIntentReceiver);
+        if (mUnmountReceiver != null) {
+            unregisterReceiver(mUnmountReceiver);
+            mUnmountReceiver = null;
+        }
+        mPsir.unregisterIntent();
+        mWakeLock.release();
+        super.onDestroy();
+    }
+    
+    private final char hexdigits [] = new char [] {
+            '0', '1', '2', '3',
+            '4', '5', '6', '7',
+            '8', '9', 'a', 'b',
+            'c', 'd', 'e', 'f'
+    };
+
+    private void saveQueue(boolean full) {
+        if (mOneShot) {
+            return;
+        }
+        Editor ed = mPreferences.edit();
+        //long start = System.currentTimeMillis();
+        if (full) {
+            StringBuilder q = new StringBuilder();
+            
+            // The current playlist is saved as a list of "reverse hexadecimal"
+            // numbers, which we can generate faster than normal decimal or
+            // hexadecimal numbers, which in turn allows us to save the playlist
+            // more often without worrying too much about performance.
+            // (saving the full state takes about 40 ms under no-load conditions
+            // on the phone)
+            int len = mPlayListLen;
+            for (int i = 0; i < len; i++) {
+                int n = mPlayList[i];
+                if (n == 0) {
+                    q.append("0;");
+                } else {
+                    while (n != 0) {
+                        int digit = n & 0xf;
+                        n >>= 4;
+                        q.append(hexdigits[digit]);
+                    }
+                    q.append(";");
+                }
+            }
+            //Log.i("@@@@ service", "created queue string in " + (System.currentTimeMillis() - start) + " ms");
+            ed.putString("queue", q.toString());
+            ed.putInt("cardid", mCardId);
+        }
+        ed.putInt("curpos", mPlayPos);
+        if (mPlayer.isInitialized()) {
+            ed.putLong("seekpos", mPlayer.position());
+        }
+        ed.putInt("repeatmode", mRepeatMode);
+        ed.putInt("shufflemode", mShuffleMode);
+        ed.commit();
+  
+        //Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis() - start) + " ms");
+    }
+
+    private void reloadQueue() {
+        String q = null;
+        
+        boolean newstyle = false;
+        int id = mCardId;
+        if (mPreferences.contains("cardid")) {
+            newstyle = true;
+            id = mPreferences.getInt("cardid", ~mCardId);
+        }
+        if (id == mCardId) {
+            // Only restore the saved playlist if the card is still
+            // the same one as when the playlist was saved
+            q = mPreferences.getString("queue", "");
+        }
+        if (q != null && q.length() > 1) {
+            //Log.i("@@@@ service", "loaded queue: " + q);
+            String [] entries = q.split(";");
+            int len = entries.length;
+            ensurePlayListCapacity(len);
+            for (int i = 0; i < len; i++) {
+                if (newstyle) {
+                    String revhex = entries[i];
+                    int n = 0;
+                    for (int j = revhex.length() - 1; j >= 0 ; j--) {
+                        n <<= 4;
+                        char c = revhex.charAt(j);
+                        if (c >= '0' && c <= '9') {
+                            n += (c - '0');
+                        } else if (c >= 'a' && c <= 'f') {
+                            n += (10 + c - 'a');
+                        } else {
+                            // bogus playlist data
+                            len = 0;
+                            break;
+                        }
+                    }
+                    mPlayList[i] = n;
+                } else {
+                    mPlayList[i] = Integer.parseInt(entries[i]);
+                }
+            }
+            mPlayListLen = len;
+
+            int pos = mPreferences.getInt("curpos", 0);
+            if (pos < 0 || pos >= len) {
+                // The saved playlist is bogus, discard it
+                mPlayListLen = 0;
+                return;
+            }
+            mPlayPos = pos;
+            
+            // When reloadQueue is called in response to a card-insertion,
+            // we might not be able to query the media provider right away.
+            // To deal with this, try querying for the current file, and if
+            // that fails, wait a while and try again. If that too fails,
+            // assume there is a problem and don't restore the state.
+            Cursor c = MusicUtils.query(this,
+                        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                        new String [] {"_id"}, "_id=" + mPlayList[mPlayPos] , null, null);
+            if (c == null || c.getCount() == 0) {
+                // wait a bit and try again
+                SystemClock.sleep(3000);
+                c = getContentResolver().query(
+                        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                        mCursorCols, "_id=" + mPlayList[mPlayPos] , null, null);
+            }
+            if (c != null) {
+                c.close();
+            }
+
+            // Make sure we don't auto-skip to the next song, since that
+            // also starts playback. What could happen in that case is:
+            // - music is paused
+            // - go to UMS and delete some files, including the currently playing one
+            // - come back from UMS
+            // (time passes)
+            // - music app is killed for some reason (out of memory)
+            // - music service is restarted, service restores state, doesn't find
+            //   the "current" file, goes to the next and: playback starts on its
+            //   own, potentially at some random inconvenient time.
+            mOpenFailedCounter = 20;
+            mQuietMode = true;
+            openCurrent();
+            mQuietMode = false;
+            if (!mPlayer.isInitialized()) {
+                // couldn't restore the saved state
+                mPlayListLen = 0;
+                return;
+            }
+            
+            long seekpos = mPreferences.getLong("seekpos", 0);
+            seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
+            
+            int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
+            if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
+                repmode = REPEAT_NONE;
+            }
+            mRepeatMode = repmode;
+
+            int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
+            if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
+                shufmode = SHUFFLE_NONE;
+            }
+            if (shufmode == SHUFFLE_AUTO) {
+                if (! makeAutoShuffleList()) {
+                    shufmode = SHUFFLE_NONE;
+                }
+            }
+            mShuffleMode = shufmode;
+        }
+    }
+    
+    @Override
+    public IBinder onBind(Intent intent) {
+        mDelayedStopHandler.removeCallbacksAndMessages(null);
+        mServiceInUse = true;
+        return mBinder;
+    }
+
+    @Override
+    public void onRebind(Intent intent) {
+        mDelayedStopHandler.removeCallbacksAndMessages(null);
+        mServiceInUse = true;
+    }
+
+    @Override
+    public void onStart(Intent intent, int startId) {
+        mServiceStartId = startId;
+        mDelayedStopHandler.removeCallbacksAndMessages(null);
+        
+        String action = intent.getAction();
+        String cmd = intent.getStringExtra("command");
+        
+        if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
+            next(true);
+        } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
+            prev();
+        } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
+            if (isPlaying()) {
+                pause();
+            } else {
+                play();
+            }
+        } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
+            pause();
+        } else if (CMDSTOP.equals(cmd)) {
+            pause();
+            seek(0);
+        }
+        
+        // make sure the service will shut down on its own if it was
+        // just started but not bound to and nothing is playing
+        mDelayedStopHandler.removeCallbacksAndMessages(null);
+        Message msg = mDelayedStopHandler.obtainMessage();
+        mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
+    }
+    
+    @Override
+    public boolean onUnbind(Intent intent) {
+        mServiceInUse = false;
+
+        // Take a snapshot of the current playlist
+        saveQueue(true);
+
+        if (isPlaying() || mResumeAfterCall) {
+            // something is currently playing, or will be playing once 
+            // an in-progress call ends, so don't stop the service now.
+            return true;
+        }
+        
+        // If there is a playlist but playback is paused, then wait a while
+        // before stopping the service, so that pause/resume isn't slow.
+        // Also delay stopping the service if we're transitioning between tracks.
+        if (mPlayListLen > 0  || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
+            Message msg = mDelayedStopHandler.obtainMessage();
+            mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
+            return true;
+        }
+        
+        // No active playlist, OK to stop the service right now
+        stopSelf(mServiceStartId);
+        return true;
+    }
+    
+    private Handler mDelayedStopHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            // Check again to make sure nothing is playing right now
+            if (isPlaying() || mResumeAfterCall || mServiceInUse
+                    || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
+                return;
+            }
+            // save the queue again, because it might have changed
+            // since the user exited the music app (because of
+            // party-shuffle or because the play-position changed)
+            saveQueue(true);
+            stopSelf(mServiceStartId);
+        }
+    };
+    
+    /**
+     * Called when we receive a ACTION_MEDIA_EJECT notification.
+     *
+     * @param storagePath path to mount point for the removed media
+     */
+    public void closeExternalStorageFiles(String storagePath) {
+        // stop playback and clean up if the SD card is going to be unmounted.
+        stop(true);
+        notifyChange(QUEUE_CHANGED);
+        notifyChange(META_CHANGED);
+    }
+
+    /**
+     * Registers an intent to listen for ACTION_MEDIA_EJECT notifications.
+     * The intent will call closeExternalStorageFiles() if the external media
+     * is going to be ejected, so applications can clean up any files they have open.
+     */
+    public void registerExternalStorageListener() {
+        if (mUnmountReceiver == null) {
+            mUnmountReceiver = new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    String action = intent.getAction();
+                    if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
+                        saveQueue(true);
+                        mOneShot = true; // This makes us not save the state again later,
+                                         // which would be wrong because the song ids and
+                                         // card id might not match. 
+                        closeExternalStorageFiles(intent.getData().getPath());
+                    } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
+                        mMediaMountedCount++;
+                        mCardId = FileUtils.getFatVolumeId(intent.getData().getPath());
+                        reloadQueue();
+                        notifyChange(QUEUE_CHANGED);
+                        notifyChange(META_CHANGED);
+                    }
+                }
+            };
+            IntentFilter iFilter = new IntentFilter();
+            iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
+            iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
+            iFilter.addDataScheme("file");
+            registerReceiver(mUnmountReceiver, iFilter);
+        }
+    }
+
+    /**
+     * Notify the change-receivers that something has changed.
+     * The intent that is sent contains the following data
+     * for the currently playing track:
+     * "id" - Integer: the database row ID
+     * "artist" - String: the name of the artist
+     * "album" - String: the name of the album
+     * "track" - String: the name of the track
+     * The intent has an action that is one of
+     * "com.android.music.metachanged"
+     * "com.android.music.queuechanged",
+     * "com.android.music.playbackcomplete"
+     * "com.android.music.playstatechanged"
+     * respectively indicating that a new track has
+     * started playing, that the playback queue has
+     * changed, that playback has stopped because
+     * the last file in the list has been played,
+     * or that the play-state changed (paused/resumed).
+     */
+    private void notifyChange(String what) {
+        
+        Intent i = new Intent(what);
+        i.putExtra("id", Integer.valueOf(getAudioId()));
+        i.putExtra("artist", getArtistName());
+        i.putExtra("album",getAlbumName());
+        i.putExtra("track", getTrackName());
+        sendBroadcast(i);
+        
+        if (what.equals(QUEUE_CHANGED)) {
+            saveQueue(true);
+        } else {
+            saveQueue(false);
+        }
+        
+        // Share this notification directly with our gadgets
+        mGadgetProvider.notifyChange(this, what);
+    }
+
+    private void ensurePlayListCapacity(int size) {
+        if (mPlayList == null || size > mPlayList.length) {
+            // reallocate at 2x requested size so we don't
+            // need to grow and copy the array for every
+            // insert
+            int [] newlist = new int[size * 2];
+            int len = mPlayListLen;
+            for (int i = 0; i < len; i++) {
+                newlist[i] = mPlayList[i];
+            }
+            mPlayList = newlist;
+        }
+        // FIXME: shrink the array when the needed size is much smaller
+        // than the allocated size
+    }
+    
+    // insert the list of songs at the specified position in the playlist
+    private void addToPlayList(int [] list, int position) {
+        int addlen = list.length;
+        if (position < 0) { // overwrite
+            mPlayListLen = 0;
+            position = 0;
+        }
+        ensurePlayListCapacity(mPlayListLen + addlen);
+        if (position > mPlayListLen) {
+            position = mPlayListLen;
+        }
+        
+        // move part of list after insertion point
+        int tailsize = mPlayListLen - position;
+        for (int i = tailsize ; i > 0 ; i--) {
+            mPlayList[position + i] = mPlayList[position + i - addlen]; 
+        }
+        
+        // copy list into playlist
+        for (int i = 0; i < addlen; i++) {
+            mPlayList[position + i] = list[i];
+        }
+        mPlayListLen += addlen;
+    }
+    
+    /**
+     * Appends a list of tracks to the current playlist.
+     * If nothing is playing currently, playback will be started at
+     * the first track.
+     * If the action is NOW, playback will switch to the first of
+     * the new tracks immediately.
+     * @param list The list of tracks to append.
+     * @param action NOW, NEXT or LAST
+     */
+    public void enqueue(int [] list, int action) {
+        synchronized(this) {
+            if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
+                addToPlayList(list, mPlayPos + 1);
+                notifyChange(QUEUE_CHANGED);
+            } else {
+                // action == LAST || action == NOW || mPlayPos + 1 == mPlayListLen
+                addToPlayList(list, Integer.MAX_VALUE);
+                notifyChange(QUEUE_CHANGED);
+                if (action == NOW) {
+                    mPlayPos = mPlayListLen - list.length;
+                    openCurrent();
+                    play();
+                    notifyChange(META_CHANGED);
+                    return;
+                }
+            }
+            if (mPlayPos < 0) {
+                mPlayPos = 0;
+                openCurrent();
+                play();
+                notifyChange(META_CHANGED);
+            }
+        }
+    }
+
+    /**
+     * Replaces the current playlist with a new list,
+     * and prepares for starting playback at the specified
+     * position in the list, or a random position if the
+     * specified position is 0.
+     * @param list The new list of tracks.
+     */
+    public void open(int [] list, int position) {
+        synchronized (this) {
+            if (mShuffleMode == SHUFFLE_AUTO) {
+                mShuffleMode = SHUFFLE_NORMAL;
+            }
+            int listlength = list.length;
+            boolean newlist = true;
+            if (mPlayListLen == listlength) {
+                // possible fast path: list might be the same
+                newlist = false;
+                for (int i = 0; i < listlength; i++) {
+                    if (list[i] != mPlayList[i]) {
+                        newlist = true;
+                        break;
+                    }
+                }
+            }
+            if (newlist) {
+                addToPlayList(list, -1);
+                notifyChange(QUEUE_CHANGED);
+            }
+            int oldpos = mPlayPos;
+            if (position >= 0) {
+                mPlayPos = position;
+            } else {
+                mPlayPos = mRand.nextInt(mPlayListLen);
+            }
+            mHistory.clear();
+
+            saveBookmarkIfNeeded();
+            openCurrent();
+            if (!newlist && mPlayPos != oldpos) {
+                // the queue didn't change, but the position did
+                notifyChange(META_CHANGED);
+            }
+        }
+    }
+    
+    /**
+     * Moves the item at index1 to index2.
+     * @param index1
+     * @param index2
+     */
+    public void moveQueueItem(int index1, int index2) {
+        synchronized (this) {
+            if (index1 >= mPlayListLen) {
+                index1 = mPlayListLen - 1;
+            }
+            if (index2 >= mPlayListLen) {
+                index2 = mPlayListLen - 1;
+            }
+            if (index1 < index2) {
+                int tmp = mPlayList[index1];
+                for (int i = index1; i < index2; i++) {
+                    mPlayList[i] = mPlayList[i+1];
+                }
+                mPlayList[index2] = tmp;
+                if (mPlayPos == index1) {
+                    mPlayPos = index2;
+                } else if (mPlayPos >= index1 && mPlayPos <= index2) {
+                        mPlayPos--;
+                }
+            } else if (index2 < index1) {
+                int tmp = mPlayList[index1];
+                for (int i = index1; i > index2; i--) {
+                    mPlayList[i] = mPlayList[i-1];
+                }
+                mPlayList[index2] = tmp;
+                if (mPlayPos == index1) {
+                    mPlayPos = index2;
+                } else if (mPlayPos >= index2 && mPlayPos <= index1) {
+                        mPlayPos++;
+                }
+            }
+            notifyChange(QUEUE_CHANGED);
+        }
+    }
+
+    /**
+     * Returns the current play list
+     * @return An array of integers containing the IDs of the tracks in the play list
+     */
+    public int [] getQueue() {
+        synchronized (this) {
+            int len = mPlayListLen;
+            int [] list = new int[len];
+            for (int i = 0; i < len; i++) {
+                list[i] = mPlayList[i];
+            }
+            return list;
+        }
+    }
+
+    private void openCurrent() {
+        synchronized (this) {
+            if (mCursor != null) {
+                mCursor.close();
+                mCursor = null;
+            }
+            if (mPlayListLen == 0) {
+                return;
+            }
+            stop(false);
+
+            String id = String.valueOf(mPlayList[mPlayPos]);
+            
+            mCursor = getContentResolver().query(
+                    MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                    mCursorCols, "_id=" + id , null, null);
+            if (mCursor != null) {
+                mCursor.moveToFirst();
+                open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id, false);
+                // go to bookmark if needed
+                if (isPodcast()) {
+                    long bookmark = getBookmark();
+                    // Start playing a little bit before the bookmark,
+                    // so it's easier to get back in to the narrative.
+                    seek(bookmark - 5000);
+                }
+            }
+        }
+    }
+
+    public void openAsync(String path) {
+        synchronized (this) {
+            if (path == null) {
+                return;
+            }
+            
+            mRepeatMode = REPEAT_NONE;
+            ensurePlayListCapacity(1);
+            mPlayListLen = 1;
+            mPlayPos = -1;
+            
+            mFileToPlay = path;
+            mCursor = null;
+            mPlayer.setDataSourceAsync(mFileToPlay);
+            mOneShot = true;
+        }
+    }
+    
+    /**
+     * Opens the specified file and readies it for playback.
+     *
+     * @param path The full path of the file to be opened.
+     * @param oneshot when set to true, playback will stop after this file completes, instead
+     * of moving on to the next track in the list 
+     */
+    public void open(String path, boolean oneshot) {
+        synchronized (this) {
+            if (path == null) {
+                return;
+            }
+            
+            if (oneshot) {
+                mRepeatMode = REPEAT_NONE;
+                ensurePlayListCapacity(1);
+                mPlayListLen = 1;
+                mPlayPos = -1;
+            }
+            
+            // if mCursor is null, try to associate path with a database cursor
+            if (mCursor == null) {
+
+                ContentResolver resolver = getContentResolver();
+                Uri uri;
+                String where;
+                String selectionArgs[];
+                if (path.startsWith("content://media/")) {
+                    uri = Uri.parse(path);
+                    where = null;
+                    selectionArgs = null;
+                } else {
+                   uri = MediaStore.Audio.Media.getContentUriForPath(path);
+                   where = MediaStore.Audio.Media.DATA + "=?";
+                   selectionArgs = new String[] { path };
+                }
+                
+                try {
+                    mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
+                    if  (mCursor != null) {
+                        if (mCursor.getCount() == 0) {
+                            mCursor.close();
+                            mCursor = null;
+                        } else {
+                            mCursor.moveToNext();
+                            ensurePlayListCapacity(1);
+                            mPlayListLen = 1;
+                            mPlayList[0] = mCursor.getInt(IDCOLIDX);
+                            mPlayPos = 0;
+                        }
+                    }
+                } catch (UnsupportedOperationException ex) {
+                }
+            }
+            mFileToPlay = path;
+            mPlayer.setDataSource(mFileToPlay);
+            mOneShot = oneshot;
+            if (! mPlayer.isInitialized()) {
+                stop(true);
+                if (mOpenFailedCounter++ < 10 &&  mPlayListLen > 1) {
+                    // beware: this ends up being recursive because next() calls open() again.
+                    next(false);
+                }
+                if (! mPlayer.isInitialized() && mOpenFailedCounter != 0) {
+                    // need to make sure we only shows this once
+                    mOpenFailedCounter = 0;
+                    if (!mQuietMode) {
+                        Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
+                    }
+                }
+            } else {
+                mOpenFailedCounter = 0;
+            }
+        }
+    }
+
+    /**
+     * Starts playback of a previously opened file.
+     */
+    public void play() {
+        if (mPlayer.isInitialized()) {
+            mPlayer.start();
+            setForeground(true);
+
+            NotificationManager nm = (NotificationManager)
+            getSystemService(Context.NOTIFICATION_SERVICE);
+    
+            RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
+            views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
+            if (getAudioId() < 0) {
+                // streaming
+                views.setTextViewText(R.id.trackname, getPath());
+                views.setTextViewText(R.id.artistalbum, null);
+            } else {
+                String artist = getArtistName();
+                views.setTextViewText(R.id.trackname, getTrackName());
+                if (artist == null || artist.equals(MediaFile.UNKNOWN_STRING)) {
+                    artist = getString(R.string.unknown_artist_name);
+                }
+                String album = getAlbumName();
+                if (album == null || album.equals(MediaFile.UNKNOWN_STRING)) {
+                    album = getString(R.string.unknown_album_name);
+                }
+                
+                views.setTextViewText(R.id.artistalbum,
+                        getString(R.string.notification_artist_album, artist, album)
+                        );
+            }
+            
+            Intent statusintent = new Intent("com.android.music.PLAYBACK_VIEWER");
+            statusintent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+            Notification status = new Notification();
+            status.contentView = views;
+            status.flags |= Notification.FLAG_ONGOING_EVENT;
+            status.icon = R.drawable.stat_notify_musicplayer;
+            status.contentIntent = PendingIntent.getActivity(this, 0,
+                    new Intent("com.android.music.PLAYBACK_VIEWER"), 0);
+            nm.notify(PLAYBACKSERVICE_STATUS, status);
+            if (!mWasPlaying) {
+                notifyChange(PLAYSTATE_CHANGED);
+            }
+            mWasPlaying = true;
+        } else if (mPlayListLen <= 0) {
+            // This is mostly so that if you press 'play' on a bluetooth headset
+            // without every having played anything before, it will still play
+            // something.
+            setShuffleMode(SHUFFLE_AUTO);
+        }
+    }
+    
+    private void stop(boolean remove_status_icon) {
+        if (mPlayer.isInitialized()) {
+            mPlayer.stop();
+        }
+        mFileToPlay = null;
+        if (mCursor != null) {
+            mCursor.close();
+            mCursor = null;
+        }
+        if (remove_status_icon) {
+            gotoIdleState();
+        }
+        setForeground(false);
+        if (remove_status_icon) {
+            mWasPlaying = false;
+        }
+    }
+
+    /**
+     * Stops playback.
+     */
+    public void stop() {
+        stop(true);
+    }
+
+    /**
+     * Pauses playback (call play() to resume)
+     */
+    public void pause() {
+        if (isPlaying()) {
+            mPlayer.pause();
+            gotoIdleState();
+            setForeground(false);
+            mWasPlaying = false;
+            notifyChange(PLAYSTATE_CHANGED);
+            saveBookmarkIfNeeded();
+        }
+    }
+
+    /** Returns whether playback is currently paused
+     *
+     * @return true if playback is paused, false if not
+     */
+    public boolean isPlaying() {
+        if (mPlayer.isInitialized()) {
+            return mPlayer.isPlaying();
+        }
+        return false;
+    }
+
+    /*
+      Desired behavior for prev/next/shuffle:
+
+      - NEXT will move to the next track in the list when not shuffling, and to
+        a track randomly picked from the not-yet-played tracks when shuffling.
+        If all tracks have already been played, pick from the full set, but
+        avoid picking the previously played track if possible.
+      - when shuffling, PREV will go to the previously played track. Hitting PREV
+        again will go to the track played before that, etc. When the start of the
+        history has been reached, PREV is a no-op.
+        When not shuffling, PREV will go to the sequentially previous track (the
+        difference with the shuffle-case is mainly that when not shuffling, the
+        user can back up to tracks that are not in the history).
+
+        Example:
+        When playing an album with 10 tracks from the start, and enabling shuffle
+        while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.
+        the final play order might be 1-2-3-4-5-8-10-6-9-7.
+        When hitting 'prev' 8 times while playing track 7 in this example, the
+        user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next',
+        a random track will be picked again. If at any time user disables shuffling
+        the next/previous track will be picked in sequential order again.
+     */
+
+    public void prev() {
+        synchronized (this) {
+            if (mOneShot) {
+                // we were playing a specific file not part of a playlist, so there is no 'previous'
+                seek(0);
+                play();
+                return;
+            }
+            if (mShuffleMode == SHUFFLE_NORMAL) {
+                // go to previously-played track and remove it from the history
+                int histsize = mHistory.size();
+                if (histsize == 0) {
+                    // prev is a no-op
+                    return;
+                }
+                Integer pos = mHistory.remove(histsize - 1);
+                mPlayPos = pos.intValue();
+            } else {
+                if (mPlayPos > 0) {
+                    mPlayPos--;
+                } else {
+                    mPlayPos = mPlayListLen - 1;
+                }
+            }
+            saveBookmarkIfNeeded();
+            stop(false);
+            openCurrent();
+            play();
+            notifyChange(META_CHANGED);
+        }
+    }
+
+    public void next(boolean force) {
+        synchronized (this) {
+            if (mOneShot) {
+                // we were playing a specific file not part of a playlist, so there is no 'next'
+                seek(0);
+                play();
+                return;
+            }
+
+            // Store the current file in the history, but keep the history at a
+            // reasonable size
+            if (mPlayPos >= 0) {
+                mHistory.add(Integer.valueOf(mPlayPos));
+            }
+            if (mHistory.size() > MAX_HISTORY_SIZE) {
+                mHistory.removeElementAt(0);
+            }
+
+            if (mShuffleMode == SHUFFLE_NORMAL) {
+                // Pick random next track from the not-yet-played ones
+                // TODO: make it work right after adding/removing items in the queue.
+
+                int numTracks = mPlayListLen;
+                int[] tracks = new int[numTracks];
+                for (int i=0;i < numTracks; i++) {
+                    tracks[i] = i;
+                }
+
+                int numHistory = mHistory.size();
+                int numUnplayed = numTracks;
+                for (int i=0;i < numHistory; i++) {
+                    int idx = mHistory.get(i).intValue();
+                    if (idx < numTracks && tracks[idx] >= 0) {
+                        numUnplayed--;
+                        tracks[idx] = -1;
+                    }
+                }
+
+                // 'numUnplayed' now indicates how many tracks have not yet
+                // been played, and 'tracks' contains the indices of those
+                // tracks.
+                if (numUnplayed <=0) {
+                    // everything's already been played
+                    if (mRepeatMode == REPEAT_ALL || force) {
+                        //pick from full set
+                        numUnplayed = numTracks;
+                        for (int i=0;i < numTracks; i++) {
+                            tracks[i] = i;
+                        }
+                    } else {
+                        // all done
+                        gotoIdleState();
+                        return;
+                    }
+                }
+                int skip = mRand.nextInt(numUnplayed);
+                int cnt = -1;
+                while (true) {
+                    while (tracks[++cnt] < 0)
+                        ;
+                    skip--;
+                    if (skip < 0) {
+                        break;
+                    }
+                }
+                mPlayPos = cnt;
+            } else if (mShuffleMode == SHUFFLE_AUTO) {
+                doAutoShuffleUpdate();
+                mPlayPos++;
+            } else {
+                if (mPlayPos >= mPlayListLen - 1) {
+                    // we're at the end of the list
+                    if (mRepeatMode == REPEAT_NONE && !force) {
+                        // all done
+                        gotoIdleState();
+                        notifyChange(PLAYBACK_COMPLETE);
+                        return;
+                    } else if (mRepeatMode == REPEAT_ALL || force) {
+                        mPlayPos = 0;
+                    }
+                } else {
+                    mPlayPos++;
+                }
+            }
+            saveBookmarkIfNeeded();
+            stop(false);
+            openCurrent();
+            play();
+            notifyChange(META_CHANGED);
+        }
+    }
+    
+    private void gotoIdleState() {
+        NotificationManager nm =
+            (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+        nm.cancel(PLAYBACKSERVICE_STATUS);
+        mDelayedStopHandler.removeCallbacksAndMessages(null);
+        Message msg = mDelayedStopHandler.obtainMessage();
+        mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
+    }
+    
+    private void saveBookmarkIfNeeded() {
+        try {
+            if (isPodcast()) {
+                long pos = position();
+                long bookmark = getBookmark();
+                long duration = duration();
+                if ((pos < bookmark && (pos + 10000) > bookmark) ||
+                        (pos > bookmark && (pos - 10000) < bookmark)) {
+                    // The existing bookmark is close to the current
+                    // position, so don't update it.
+                    return;
+                }
+                if (pos < 15000 || (pos + 10000) > duration) {
+                    // if we're near the start or end, clear the bookmark
+                    pos = 0;
+                }
+                
+                // write 'pos' to the bookmark field
+                ContentValues values = new ContentValues();
+                values.put(MediaStore.Audio.Media.BOOKMARK, pos);
+                Uri uri = ContentUris.withAppendedId(
+                        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursor.getLong(IDCOLIDX));
+                getContentResolver().update(uri, values, null, null);
+            }
+        } catch (SQLiteException ex) {
+        }
+    }
+
+    // Make sure there are at least 5 items after the currently playing item
+    // and no more than 10 items before.
+    private void doAutoShuffleUpdate() {
+        boolean notify = false;
+        // remove old entries
+        if (mPlayPos > 10) {
+            removeTracks(0, mPlayPos - 9);
+            notify = true;
+        }
+        // add new entries if needed
+        int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
+        for (int i = 0; i < to_add; i++) {
+            // pick something at random from the list
+            int idx = mRand.nextInt(mAutoShuffleList.length);
+            Integer which = mAutoShuffleList[idx];
+            ensurePlayListCapacity(mPlayListLen + 1);
+            mPlayList[mPlayListLen++] = which;
+            notify = true;
+        }
+        if (notify) {
+            notifyChange(QUEUE_CHANGED);
+        }
+    }
+
+    // A simple variation of Random that makes sure that the
+    // value it returns is not equal to the value it returned
+    // previously, unless the interval is 1.
+    private class Shuffler {
+        private int mPrevious;
+        private Random mRandom = new Random();
+        public int nextInt(int interval) {
+            int ret;
+            do {
+                ret = mRandom.nextInt(interval);
+            } while (ret == mPrevious && interval > 1);
+            mPrevious = ret;
+            return ret;
+        }
+    };
+
+    private boolean makeAutoShuffleList() {
+        ContentResolver res = getContentResolver();
+        Cursor c = null;
+        try {
+            c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                    new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
+                    null, null);
+            if (c == null || c.getCount() == 0) {
+                return false;
+            }
+            int len = c.getCount();
+            int[] list = new int[len];
+            for (int i = 0; i < len; i++) {
+                c.moveToNext();
+                list[i] = c.getInt(0);
+            }
+            mAutoShuffleList = list;
+            return true;
+        } catch (RuntimeException ex) {
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+        return false;
+    }
+    
+    /**
+     * Removes the range of tracks specified from the play list. If a file within the range is
+     * the file currently being played, playback will move to the next file after the
+     * range. 
+     * @param first The first file to be removed
+     * @param last The last file to be removed
+     * @return the number of tracks deleted
+     */
+    public int removeTracks(int first, int last) {
+        int numremoved = removeTracksInternal(first, last);
+        if (numremoved > 0) {
+            notifyChange(QUEUE_CHANGED);
+        }
+        return numremoved;
+    }
+    
+    private int removeTracksInternal(int first, int last) {
+        synchronized (this) {
+            if (last < first) return 0;
+            if (first < 0) first = 0;
+            if (last >= mPlayListLen) last = mPlayListLen - 1;
+
+            boolean gotonext = false;
+            if (first <= mPlayPos && mPlayPos <= last) {
+                mPlayPos = first;
+                gotonext = true;
+            } else if (mPlayPos > last) {
+                mPlayPos -= (last - first + 1);
+            }
+            int num = mPlayListLen - last - 1;
+            for (int i = 0; i < num; i++) {
+                mPlayList[first + i] = mPlayList[last + 1 + i];
+            }
+            mPlayListLen -= last - first + 1;
+            
+            if (gotonext) {
+                if (mPlayListLen == 0) {
+                    stop(true);
+                    mPlayPos = -1;
+                } else {
+                    if (mPlayPos >= mPlayListLen) {
+                        mPlayPos = 0;
+                    }
+                    boolean wasPlaying = isPlaying();
+                    stop(false);
+                    openCurrent();
+                    if (wasPlaying) {
+                        play();
+                    }
+                }
+            }
+            return last - first + 1;
+        }
+    }
+    
+    /**
+     * Removes all instances of the track with the given id
+     * from the playlist.
+     * @param id The id to be removed
+     * @return how many instances of the track were removed
+     */
+    public int removeTrack(int id) {
+        int numremoved = 0;
+        synchronized (this) {
+            for (int i = 0; i < mPlayListLen; i++) {
+                if (mPlayList[i] == id) {
+                    numremoved += removeTracksInternal(i, i);
+                    i--;
+                }
+            }
+        }
+        if (numremoved > 0) {
+            notifyChange(QUEUE_CHANGED);
+        }
+        return numremoved;
+    }
+    
+    public void setShuffleMode(int shufflemode) {
+        synchronized(this) {
+            if (mShuffleMode == shufflemode && mPlayListLen > 0) {
+                return;
+            }
+            mShuffleMode = shufflemode;
+            if (mShuffleMode == SHUFFLE_AUTO) {
+                if (makeAutoShuffleList()) {
+                    mPlayListLen = 0;
+                    doAutoShuffleUpdate();
+                    mPlayPos = 0;
+                    openCurrent();
+                    play();
+                    notifyChange(META_CHANGED);
+                    return;
+                } else {
+                    // failed to build a list of files to shuffle
+                    mShuffleMode = SHUFFLE_NONE;
+                }
+            }
+            saveQueue(false);
+        }
+    }
+    public int getShuffleMode() {
+        return mShuffleMode;
+    }
+    
+    public void setRepeatMode(int repeatmode) {
+        synchronized(this) {
+            mRepeatMode = repeatmode;
+            saveQueue(false);
+        }
+    }
+    public int getRepeatMode() {
+        return mRepeatMode;
+    }
+
+    public int getMediaMountedCount() {
+        return mMediaMountedCount;
+    }
+
+    /**
+     * Returns the path of the currently playing file, or null if
+     * no file is currently playing.
+     */
+    public String getPath() {
+        return mFileToPlay;
+    }
+    
+    /**
+     * Returns the rowid of the currently playing file, or -1 if
+     * no file is currently playing.
+     */
+    public int getAudioId() {
+        synchronized (this) {
+            if (mPlayPos >= 0 && mPlayer.isInitialized()) {
+                return mPlayList[mPlayPos];
+            }
+        }
+        return -1;
+    }
+    
+    /**
+     * Returns the position in the queue 
+     * @return the position in the queue
+     */
+    public int getQueuePosition() {
+        synchronized(this) {
+            return mPlayPos;
+        }
+    }
+    
+    /**
+     * Starts playing the track at the given position in the queue.
+     * @param pos The position in the queue of the track that will be played.
+     */
+    public void setQueuePosition(int pos) {
+        synchronized(this) {
+            stop(false);
+            mPlayPos = pos;
+            openCurrent();
+            play();
+            notifyChange(META_CHANGED);
+        }
+    }
+
+    public String getArtistName() {
+        synchronized(this) {
+            if (mCursor == null) {
+                return null;
+            }
+            return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
+        }
+    }
+    
+    public int getArtistId() {
+        synchronized (this) {
+            if (mCursor == null) {
+                return -1;
+            }
+            return mCursor.getInt(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID));
+        }
+    }
+
+    public String getAlbumName() {
+        synchronized (this) {
+            if (mCursor == null) {
+                return null;
+            }
+            return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
+        }
+    }
+
+    public int getAlbumId() {
+        synchronized (this) {
+            if (mCursor == null) {
+                return -1;
+            }
+            return mCursor.getInt(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
+        }
+    }
+
+    public String getTrackName() {
+        synchronized (this) {
+            if (mCursor == null) {
+                return null;
+            }
+            return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
+        }
+    }
+
+    private boolean isPodcast() {
+        synchronized (this) {
+            if (mCursor == null) {
+                return false;
+            }
+            return (mCursor.getInt(PODCASTCOLIDX) > 0);
+        }
+    }
+    
+    private long getBookmark() {
+        synchronized (this) {
+            if (mCursor == null) {
+                return 0;
+            }
+            return mCursor.getLong(BOOKMARKCOLIDX);
+        }
+    }
+    
+    /**
+     * Returns the duration of the file in milliseconds.
+     * Currently this method returns -1 for the duration of MIDI files.
+     */
+    public long duration() {
+        if (mPlayer.isInitialized()) {
+            return mPlayer.duration();
+        }
+        return -1;
+    }
+
+    /**
+     * Returns the current playback position in milliseconds
+     */
+    public long position() {
+        if (mPlayer.isInitialized()) {
+            return mPlayer.position();
+        }
+        return -1;
+    }
+
+    /**
+     * Seeks to the position specified.
+     *
+     * @param pos The position to seek to, in milliseconds
+     */
+    public long seek(long pos) {
+        if (mPlayer.isInitialized()) {
+            if (pos < 0) pos = 0;
+            if (pos > mPlayer.duration()) pos = mPlayer.duration();
+            return mPlayer.seek(pos);
+        }
+        return -1;
+    }
+
+    /**
+     * Provides a unified interface for dealing with midi files and
+     * other media files.
+     */
+    private class MultiPlayer {
+        private MediaPlayer mMediaPlayer = new MediaPlayer();
+        private Handler mHandler;
+        private boolean mIsInitialized = false;
+
+        public MultiPlayer() {
+            mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
+        }
+
+        public void setDataSourceAsync(String path) {
+            try {
+                mMediaPlayer.reset();
+                mMediaPlayer.setDataSource(path);
+                mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+                mMediaPlayer.setOnPreparedListener(preparedlistener);
+                mMediaPlayer.prepareAsync();
+            } catch (IOException ex) {
+                // TODO: notify the user why the file couldn't be opened
+                mIsInitialized = false;
+                return;
+            } catch (IllegalArgumentException ex) {
+                // TODO: notify the user why the file couldn't be opened
+                mIsInitialized = false;
+                return;
+            }
+            mMediaPlayer.setOnCompletionListener(listener);
+            mMediaPlayer.setOnErrorListener(errorListener);
+            
+            mIsInitialized = true;
+        }
+        
+        public void setDataSource(String path) {
+            try {
+                mMediaPlayer.reset();
+                mMediaPlayer.setOnPreparedListener(null);
+                if (path.startsWith("content://")) {
+                    mMediaPlayer.setDataSource(MediaPlaybackService.this, Uri.parse(path));
+                } else {
+                    mMediaPlayer.setDataSource(path);
+                }
+                mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+                mMediaPlayer.prepare();
+            } catch (IOException ex) {
+                // TODO: notify the user why the file couldn't be opened
+                mIsInitialized = false;
+                return;
+            } catch (IllegalArgumentException ex) {
+                // TODO: notify the user why the file couldn't be opened
+                mIsInitialized = false;
+                return;
+            }
+            mMediaPlayer.setOnCompletionListener(listener);
+            mMediaPlayer.setOnErrorListener(errorListener);
+            
+            mIsInitialized = true;
+        }
+        
+        public boolean isInitialized() {
+            return mIsInitialized;
+        }
+
+        public void start() {
+            mMediaPlayer.start();
+        }
+
+        public void stop() {
+            mMediaPlayer.reset();
+            mIsInitialized = false;
+        }
+
+        public void pause() {
+            mMediaPlayer.pause();
+        }
+        
+        public boolean isPlaying() {
+            return mMediaPlayer.isPlaying();
+        }
+
+        public void setHandler(Handler handler) {
+            mHandler = handler;
+        }
+
+        MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
+            public void onCompletion(MediaPlayer mp) {
+                // Acquire a temporary wakelock, since when we return from
+                // this callback the MediaPlayer will release its wakelock
+                // and allow the device to go to sleep.
+                // This temporary wakelock is released when the RELEASE_WAKELOCK
+                // message is processed, but just in case, put a timeout on it.
+                mWakeLock.acquire(30000);
+                mHandler.sendEmptyMessage(TRACK_ENDED);
+                mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
+            }
+        };
+
+        MediaPlayer.OnPreparedListener preparedlistener = new MediaPlayer.OnPreparedListener() {
+            public void onPrepared(MediaPlayer mp) {
+                notifyChange(ASYNC_OPEN_COMPLETE);
+            }
+        };
+ 
+        MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
+            public boolean onError(MediaPlayer mp, int what, int extra) {
+                switch (what) {
+                case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
+                    mIsInitialized = false;
+                    mMediaPlayer.release();
+                    // Creating a new MediaPlayer and settings its wakemode does not
+                    // require the media service, so it's OK to do this now, while the
+                    // service is still being restarted
+                    mMediaPlayer = new MediaPlayer(); 
+                    mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
+                    mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
+                    return true;
+                default:
+                    break;
+                }
+                return false;
+           }
+        };
+
+        public long duration() {
+            return mMediaPlayer.getDuration();
+        }
+
+        public long position() {
+            return mMediaPlayer.getCurrentPosition();
+        }
+
+        public long seek(long whereto) {
+            mMediaPlayer.seekTo((int) whereto);
+            return whereto;
+        }
+
+        public void setVolume(float vol) {
+            mMediaPlayer.setVolume(vol, vol);
+        }
+    }
+
+    private final IMediaPlaybackService.Stub mBinder = new IMediaPlaybackService.Stub()
+    {
+        public void openfileAsync(String path)
+        {
+            MediaPlaybackService.this.openAsync(path);
+        }
+        public void openfile(String path)
+        {
+            MediaPlaybackService.this.open(path, true);
+        }
+        public void open(int [] list, int position) {
+            MediaPlaybackService.this.open(list, position);
+        }
+        public int getQueuePosition() {
+            return MediaPlaybackService.this.getQueuePosition();
+        }
+        public void setQueuePosition(int index) {
+            MediaPlaybackService.this.setQueuePosition(index);
+        }
+        public boolean isPlaying() {
+            return MediaPlaybackService.this.isPlaying();
+        }
+        public void stop() {
+            MediaPlaybackService.this.stop();
+        }
+        public void pause() {
+            MediaPlaybackService.this.pause();
+        }
+        public void play() {
+            MediaPlaybackService.this.play();
+        }
+        public void prev() {
+            MediaPlaybackService.this.prev();
+        }
+        public void next() {
+            MediaPlaybackService.this.next(true);
+        }
+        public String getTrackName() {
+            return MediaPlaybackService.this.getTrackName();
+        }
+        public String getAlbumName() {
+            return MediaPlaybackService.this.getAlbumName();
+        }
+        public int getAlbumId() {
+            return MediaPlaybackService.this.getAlbumId();
+        }
+        public String getArtistName() {
+            return MediaPlaybackService.this.getArtistName();
+        }
+        public int getArtistId() {
+            return MediaPlaybackService.this.getArtistId();
+        }
+        public void enqueue(int [] list , int action) {
+            MediaPlaybackService.this.enqueue(list, action);
+        }
+        public int [] getQueue() {
+            return MediaPlaybackService.this.getQueue();
+        }
+        public void moveQueueItem(int from, int to) {
+            MediaPlaybackService.this.moveQueueItem(from, to);
+        }
+        public String getPath() {
+            return MediaPlaybackService.this.getPath();
+        }
+        public int getAudioId() {
+            return MediaPlaybackService.this.getAudioId();
+        }
+        public long position() {
+            return MediaPlaybackService.this.position();
+        }
+        public long duration() {
+            return MediaPlaybackService.this.duration();
+        }
+        public long seek(long pos) {
+            return MediaPlaybackService.this.seek(pos);
+        }
+        public void setShuffleMode(int shufflemode) {
+            MediaPlaybackService.this.setShuffleMode(shufflemode);
+        }
+        public int getShuffleMode() {
+            return MediaPlaybackService.this.getShuffleMode();
+        }
+        public int removeTracks(int first, int last) {
+            return MediaPlaybackService.this.removeTracks(first, last);
+        }
+        public int removeTrack(int id) {
+            return MediaPlaybackService.this.removeTrack(id);
+        }
+        public void setRepeatMode(int repeatmode) {
+            MediaPlaybackService.this.setRepeatMode(repeatmode);
+        }
+        public int getRepeatMode() {
+            return MediaPlaybackService.this.getRepeatMode();
+        }
+        public int getMediaMountedCount() {
+            return MediaPlaybackService.this.getMediaMountedCount();
+        }
+    };
+}
diff --git a/src/com/android/music/MusicAlphabetIndexer.java b/src/com/android/music/MusicAlphabetIndexer.java
new file mode 100644
index 0000000..c05f3c1
--- /dev/null
+++ b/src/com/android/music/MusicAlphabetIndexer.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.database.Cursor;
+import android.provider.MediaStore;
+import android.widget.AlphabetIndexer;
+
+/**
+ * Handles comparisons in a different way because the Album, Song and Artist name
+ * are stripped of some prefixes such as "a", "an", "the" and some symbols.
+ *
+ */
+class MusicAlphabetIndexer extends AlphabetIndexer {
+    
+    public MusicAlphabetIndexer(Cursor cursor, int sortedColumnIndex, CharSequence alphabet) {
+        super(cursor, sortedColumnIndex, alphabet);
+    }
+    
+    @Override
+    protected int compare(String word, String letter) {
+        String wordKey = MediaStore.Audio.keyFor(word);
+        String letterKey = MediaStore.Audio.keyFor(letter);
+        if (wordKey.startsWith(letter)) {
+            return 0;
+        } else {
+            return wordKey.compareTo(letterKey);
+        }
+    }
+}
diff --git a/src/com/android/music/MusicBrowserActivity.java b/src/com/android/music/MusicBrowserActivity.java
new file mode 100644
index 0000000..632db1e
--- /dev/null
+++ b/src/com/android/music/MusicBrowserActivity.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.app.Activity;
+import android.app.SearchManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.media.MediaFile;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.Window;
+import android.widget.ImageButton;
+import android.widget.TextView;
+
+public class MusicBrowserActivity extends Activity
+    implements MusicUtils.Defs, View.OnClickListener {
+    private View mNowPlayingView;
+    private TextView mTitle;
+    private TextView mArtist;
+    private boolean mAutoShuffle = false;
+    private static final int SEARCH_MUSIC = CHILD_MENU_BASE;
+
+    public MusicBrowserActivity() {
+    }
+
+    /**
+     * Called when the activity is first created.
+     */
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setVolumeControlStream(AudioManager.STREAM_MUSIC);
+        String shuf = getIntent().getStringExtra("autoshuffle");
+        if ("true".equals(shuf)) {
+            mAutoShuffle = true;
+        }
+        MusicUtils.bindToService(this, new ServiceConnection() {
+            public void onServiceConnected(ComponentName classname, IBinder obj) {
+                updateMenu();
+            }
+
+            public void onServiceDisconnected(ComponentName classname) {
+                updateMenu();
+            }
+        
+        });
+        setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
+        init();
+    }
+
+    @Override
+    public void onDestroy() {
+        MusicUtils.unbindFromService(this);
+        super.onDestroy();
+    }
+
+    public void init() {
+        setContentView(R.layout.music_library);
+        mNowPlayingView = findViewById(R.id.nowplaying);
+        mTitle = (TextView) mNowPlayingView.findViewById(R.id.title);
+        mArtist = (TextView) mNowPlayingView.findViewById(R.id.artist);
+        
+        View b = (View) findViewById(R.id.browse_button); 
+        b.setOnClickListener(this);
+        
+        b = (View) findViewById(R.id.albums_button);
+        b.setOnClickListener(this);
+
+        b = (View) findViewById(R.id.tracks_button);
+        b.setOnClickListener(this);
+
+        b = (View) findViewById(R.id.playlists_button);
+        b.setOnClickListener(this);
+    }
+    
+    private void updateMenu() {
+        try {
+            if (MusicUtils.sService != null && MusicUtils.sService.getAudioId() != -1) {
+                makeNowPlayingView();
+                mNowPlayingView.setVisibility(View.VISIBLE);
+                return;
+            }
+        } catch (RemoteException ex) {
+        }
+        mNowPlayingView.setVisibility(View.INVISIBLE);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        IntentFilter f = new IntentFilter();
+        f.addAction(MediaPlaybackService.META_CHANGED);
+        registerReceiver(mStatusListener, new IntentFilter(f));
+        updateMenu();
+        if (mAutoShuffle) {
+            mAutoShuffle = false;
+            doAutoShuffle();
+        }
+    }
+    
+    @Override
+    public void onPause() {
+        super.onPause();
+        unregisterReceiver(mStatusListener);
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+         menu.add(0, PARTY_SHUFFLE, 0, R.string.party_shuffle); // icon will be set in onPrepareOptionsMenu()
+         menu.add(0, SEARCH_MUSIC, 0, R.string.search_title).setIcon(android.R.drawable.ic_menu_search);
+        return true;
+    }
+
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        MenuItem item = menu.findItem(PARTY_SHUFFLE);
+        if (item != null) {
+            int shuffle = MusicUtils.getCurrentShuffleMode();
+            if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
+                item.setIcon(R.drawable.ic_menu_party_shuffle);
+                item.setTitle(R.string.party_shuffle_off);
+            } else {
+                item.setIcon(R.drawable.ic_menu_party_shuffle);
+                item.setTitle(R.string.party_shuffle);
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        Intent intent;
+        try {
+            switch (item.getItemId()) {
+                case PARTY_SHUFFLE:
+                    int shuffle = MusicUtils.sService.getShuffleMode();
+                    if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
+                        MusicUtils.sService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
+                    } else {
+                        MusicUtils.sService.setShuffleMode(MediaPlaybackService.SHUFFLE_AUTO);
+                    }
+                    break;
+                    
+                case SEARCH_MUSIC: {
+                    startSearch("", false, null, false);
+                    return true;
+                }
+            }
+        } catch (RemoteException ex) {
+        }
+        return super.onOptionsItemSelected(item);
+    }
+    
+    public void onClick(View v) {
+        Intent intent;
+        switch (v.getId()) {
+            case R.id.browse_button:
+                intent = new Intent(Intent.ACTION_PICK);
+                intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/artistalbum");
+                startActivity(intent);
+                break;
+            case R.id.albums_button:
+                intent = new Intent(Intent.ACTION_PICK);
+                intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album");
+                startActivity(intent);
+                break;
+            case R.id.tracks_button:
+                intent = new Intent(Intent.ACTION_PICK);
+                intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
+                startActivity(intent);
+                break;
+            case R.id.playlists_button:
+                intent = new Intent(Intent.ACTION_PICK);
+                intent.setDataAndType(Uri.EMPTY, MediaStore.Audio.Playlists.CONTENT_TYPE);
+                startActivity(intent);
+                break;
+            case R.id.nowplaying:
+                intent = new Intent("com.android.music.PLAYBACK_VIEWER");
+                startActivity(intent);
+                break;
+        }
+    }
+
+    private void doAutoShuffle() {
+        bindService((new Intent()).setClass(this, MediaPlaybackService.class), autoshuffle, 0);
+    }
+
+    private ServiceConnection autoshuffle = new ServiceConnection() {
+        public void onServiceConnected(ComponentName classname, IBinder obj) {
+            // we need to be able to bind again, so unbind
+            unbindService(this);
+            IMediaPlaybackService serv = IMediaPlaybackService.Stub.asInterface(obj);
+            if (serv != null) {
+                try {
+                    serv.setShuffleMode(MediaPlaybackService.SHUFFLE_AUTO);
+                    updateMenu();
+                } catch (RemoteException ex) {
+                }
+            }
+        }
+
+        public void onServiceDisconnected(ComponentName classname) {
+        }
+    };
+
+    private void makeNowPlayingView() {
+        try {
+            mTitle.setText(MusicUtils.sService.getTrackName());
+            String artistName = MusicUtils.sService.getArtistName();
+            if (MediaFile.UNKNOWN_STRING.equals(artistName)) {
+                artistName = getString(R.string.unknown_artist_name);
+            }
+            mArtist.setText(artistName);
+            mNowPlayingView.setOnFocusChangeListener(mFocuser);
+            mNowPlayingView.setOnClickListener(this);
+        } catch (RemoteException ex) {
+
+        }
+    }
+
+    View.OnFocusChangeListener mFocuser = new View.OnFocusChangeListener() {
+        Drawable mBack;
+
+        public void onFocusChange(View v, boolean hasFocus) {
+            if (hasFocus) {
+                if (mBack == null) {
+                    mBack = mNowPlayingView.getBackground();
+                }
+                Drawable dr = getResources().getDrawable(android.R.drawable.menuitem_background);
+                dr.setState(new int[] { android.R.attr.state_focused});
+                mNowPlayingView.setBackgroundDrawable(dr);
+                mNowPlayingView.setSelected(true);
+            } else {
+                mNowPlayingView.setBackgroundDrawable(mBack);
+                mNowPlayingView.setSelected(false);
+            }
+        }
+    };
+
+    private BroadcastReceiver mStatusListener = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // this receiver is only used for META_CHANGED events
+            updateMenu();
+        }
+    };
+}
+
diff --git a/src/com/android/music/MusicPicker.java b/src/com/android/music/MusicPicker.java
new file mode 100644
index 0000000..c5be26d
--- /dev/null
+++ b/src/com/android/music/MusicPicker.java
@@ -0,0 +1,735 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.app.ListActivity;
+import android.content.AsyncQueryHandler;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.database.CharArrayBuffer;
+import android.database.Cursor;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.animation.AnimationUtils;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.RadioButton;
+import android.widget.SectionIndexer;
+import android.widget.SimpleCursorAdapter;
+import android.widget.TextView;
+
+import java.io.IOException;
+import java.text.Collator;
+import java.util.Formatter;
+import java.util.Locale;
+
+/**
+ * Activity allowing the user to select a music track on the device, and
+ * return it to its caller.  The music picker user interface is fairly
+ * extensive, providing information about each track like the music
+ * application (title, author, album, duration), as well as the ability to
+ * previous tracks and sort them in different orders.
+ * 
+ * <p>This class also illustrates how you can load data from a content
+ * provider asynchronously, providing a good UI while doing so, perform
+ * indexing of the content for use inside of a {@link FastScrollView}, and
+ * perform filtering of the data as the user presses keys.
+ */
+public class MusicPicker extends ListActivity
+        implements View.OnClickListener, MediaPlayer.OnCompletionListener,
+        MusicUtils.Defs {
+    static final boolean DBG = false;
+    static final String TAG = "MusicPicker";
+    
+    /** Holds the previous state of the list, to restore after the async
+     * query has completed. */
+    static final String LIST_STATE_KEY = "liststate";
+    /** Remember whether the list last had focus for restoring its state. */
+    static final String FOCUS_KEY = "focused";
+    /** Remember the last ordering mode for restoring state. */
+    static final String SORT_MODE_KEY = "sortMode";
+    
+    /** Arbitrary number, doesn't matter since we only do one query type. */
+    final int MY_QUERY_TOKEN = 42;
+    
+    /** Menu item to sort the music list by track title. */
+    static final int TRACK_MENU = Menu.FIRST;
+    /** Menu item to sort the music list by album title. */
+    static final int ALBUM_MENU = Menu.FIRST+1;
+    /** Menu item to sort the music list by artist name. */
+    static final int ARTIST_MENU = Menu.FIRST+2;
+    
+    /** These are the columns in the music cursor that we are interested in. */
+    static final String[] CURSOR_COLS = new String[] {
+            MediaStore.Audio.Media._ID,
+            MediaStore.Audio.Media.TITLE,
+            MediaStore.Audio.Media.TITLE_KEY,
+            MediaStore.Audio.Media.DATA,
+            MediaStore.Audio.Media.ALBUM,
+            MediaStore.Audio.Media.ARTIST,
+            MediaStore.Audio.Media.ARTIST_ID,
+            MediaStore.Audio.Media.DURATION,
+            MediaStore.Audio.Media.TRACK
+    };
+    
+    /** Formatting optimization to avoid creating many temporary objects. */
+    static StringBuilder sFormatBuilder = new StringBuilder();
+    /** Formatting optimization to avoid creating many temporary objects. */
+    static Formatter sFormatter = new Formatter(sFormatBuilder, Locale.getDefault());
+    /** Formatting optimization to avoid creating many temporary objects. */
+    static final Object[] sTimeArgs = new Object[5];
+
+    /** Uri to the directory of all music being displayed. */
+    Uri mBaseUri;
+    
+    /** This is the adapter used to display all of the tracks. */
+    TrackListAdapter mAdapter;
+    /** Our instance of QueryHandler used to perform async background queries. */
+    QueryHandler mQueryHandler;
+    
+    /** Used to keep track of the last scroll state of the list. */
+    Parcelable mListState = null;
+    /** Used to keep track of whether the list last had focus. */
+    boolean mListHasFocus;
+    
+    /** The current cursor on the music that is being displayed. */
+    Cursor mCursor;
+    /** The actual sort order the user has selected. */
+    int mSortMode = -1;
+    /** SQL order by string describing the currently selected sort order. */
+    String mSortOrder;
+
+    /** Container of the in-screen progress indicator, to be able to hide it
+     * when done loading the initial cursor. */
+    View mProgressContainer;
+    /** Container of the list view hierarchy, to be able to show it when done
+     * loading the initial cursor. */
+    View mListContainer;
+    /** Set to true when the list view has been shown for the first time. */
+    boolean mListShown;
+    
+    /** View holding the okay button. */
+    View mOkayButton;
+    /** View holding the cancel button. */
+    View mCancelButton;
+    
+    /** Which track row ID the user has last selected. */
+    long mSelectedId = -1;
+    /** Completel Uri that the user has last selected. */
+    Uri mSelectedUri;
+    
+    /** If >= 0, we are currently playing a track for preview, and this is its
+     * row ID. */
+    long mPlayingId = -1;
+    
+    /** This is used for playing previews of the music files. */
+    MediaPlayer mMediaPlayer;
+    
+    /**
+     * A special implementation of SimpleCursorAdapter that knows how to bind
+     * our cursor data to our list item structure, and takes care of other
+     * advanced features such as indexing and filtering.
+     */
+    class TrackListAdapter extends SimpleCursorAdapter
+            implements SectionIndexer {
+        final ListView mListView;
+        
+        private final StringBuilder mBuilder = new StringBuilder();
+        private final String mUnknownArtist;
+        private final String mUnknownAlbum;
+
+        private int mIdIdx;
+        private int mTitleIdx;
+        private int mArtistIdx;
+        private int mAlbumIdx;
+        private int mDurationIdx;
+        private int mAudioIdIdx;
+        private int mTrackIdx;
+
+        private boolean mLoading = true;
+        private int mIndexerSortMode;
+        private boolean mIndexerOutOfDate;
+        private MusicAlphabetIndexer mIndexer;
+        
+        class ViewHolder {
+            TextView line1;
+            TextView line2;
+            TextView duration;
+            RadioButton radio;
+            ImageView play_indicator;
+            CharArrayBuffer buffer1;
+            char [] buffer2;
+        }
+        
+        TrackListAdapter(Context context, ListView listView, int layout,
+                String[] from, int[] to) {
+            super(context, layout, null, from, to);
+            mListView = listView;
+            mUnknownArtist = context.getString(R.string.unknown_artist_name);
+            mUnknownAlbum = context.getString(R.string.unknown_album_name);
+        }
+
+        /**
+         * The mLoading flag is set while we are performing a background
+         * query, to avoid displaying the "No music" empty view during
+         * this time.
+         */
+        public void setLoading(boolean loading) {
+            mLoading = loading;
+        }
+
+        @Override
+        public boolean isEmpty() {
+            if (mLoading) {
+                // We don't want the empty state to show when loading.
+                return false;
+            } else {
+                return super.isEmpty();
+            }
+        }
+        
+        @Override
+        public View newView(Context context, Cursor cursor, ViewGroup parent) {
+            View v = super.newView(context, cursor, parent);
+            ViewHolder vh = new ViewHolder();
+            vh.line1 = (TextView) v.findViewById(R.id.line1);
+            vh.line2 = (TextView) v.findViewById(R.id.line2);
+            vh.duration = (TextView) v.findViewById(R.id.duration);
+            vh.radio = (RadioButton) v.findViewById(R.id.radio);
+            vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator);
+            vh.buffer1 = new CharArrayBuffer(100);
+            vh.buffer2 = new char[200];
+            v.setTag(vh);
+            return v;
+        }
+
+        @Override
+        public void bindView(View view, Context context, Cursor cursor) {
+            ViewHolder vh = (ViewHolder) view.getTag();
+            
+            cursor.copyStringToBuffer(mTitleIdx, vh.buffer1);
+            vh.line1.setText(vh.buffer1.data, 0, vh.buffer1.sizeCopied);
+            
+            int secs = cursor.getInt(mDurationIdx) / 1000;
+            if (secs == 0) {
+                vh.duration.setText("");
+            } else {
+                vh.duration.setText(makeTimeString(context, secs));
+            }
+            
+            final StringBuilder builder = mBuilder;
+            builder.delete(0, builder.length());
+
+            String name = cursor.getString(mAlbumIdx);
+            if (name == null || name.equals("<unknown>")) {
+                builder.append(mUnknownAlbum);
+            } else {
+                builder.append(name);
+            }
+            builder.append('\n');
+            name = cursor.getString(mArtistIdx);
+            if (name == null || name.equals("<unknown>")) {
+                builder.append(mUnknownArtist);
+            } else {
+                builder.append(name);
+            }
+            int len = builder.length();
+            if (vh.buffer2.length < len) {
+                vh.buffer2 = new char[len];
+            }
+            builder.getChars(0, len, vh.buffer2, 0);
+            vh.line2.setText(vh.buffer2, 0, len);
+
+            // Update the checkbox of the item, based on which the user last
+            // selected.  Note that doing it this way means we must have the
+            // list view update all of its items when the selected item
+            // changes.
+            final long id = cursor.getLong(mIdIdx);
+            vh.radio.setChecked(id == mSelectedId);
+            if (DBG) Log.v(TAG, "Binding id=" + id + " sel=" + mSelectedId
+                    + " playing=" + mPlayingId + " cursor=" + cursor);
+            
+            // Likewise, display the "now playing" icon if this item is
+            // currently being previewed for the user.
+            ImageView iv = vh.play_indicator;
+            if (id == mPlayingId) {
+                iv.setImageResource(R.drawable.indicator_ic_mp_playing_list);
+                iv.setVisibility(View.VISIBLE);
+            } else {
+                iv.setVisibility(View.GONE);
+            }
+        }
+        
+        /**
+         * This method is called whenever we receive a new cursor due to
+         * an async query, and must take care of plugging the new one in
+         * to the adapter.
+         */
+        @Override
+        public void changeCursor(Cursor cursor) {
+            super.changeCursor(cursor);
+            if (DBG) Log.v(TAG, "Setting cursor to: " + cursor
+                    + " from: " + MusicPicker.this.mCursor);
+            
+            MusicPicker.this.mCursor = cursor;
+            
+            if (cursor != null) {
+                // Retrieve indices of the various columns we are interested in.
+                mIdIdx = cursor.getColumnIndex(MediaStore.Audio.Media._ID);
+                mTitleIdx = cursor.getColumnIndex(MediaStore.Audio.Media.TITLE);
+                mArtistIdx = cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST);
+                mAlbumIdx = cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM);
+                mDurationIdx = cursor.getColumnIndex(MediaStore.Audio.Media.DURATION);
+                int audioIdIdx = cursor.getColumnIndex(MediaStore.Audio.Playlists.Members.AUDIO_ID);
+                if (audioIdIdx < 0) {
+                    audioIdIdx = cursor.getColumnIndex(MediaStore.Audio.Media._ID);
+                }
+                mAudioIdIdx = audioIdIdx;
+                mTrackIdx = cursor.getColumnIndex(MediaStore.Audio.Media.TRACK);
+            }
+            
+            // The next time the indexer is needed, we will need to rebind it
+            // to this cursor.
+            mIndexerOutOfDate = true;
+            
+            // Ensure that the list is shown (and initial progress indicator
+            // hidden) in case this is the first cursor we have gotten.
+            makeListShown();
+        }
+        
+        /**
+         * This method is called from a background thread by the list view
+         * when the user has typed a letter that should result in a filtering
+         * of the displayed items.  It returns a Cursor, when will then be
+         * handed to changeCursor.
+         */
+        @Override
+        public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
+            if (DBG) Log.v(TAG, "Getting new cursor...");
+            return doQuery(true, constraint.toString());
+        }
+        
+        public int getPositionForSection(int section) {
+            Cursor cursor = getCursor();
+            if (cursor == null) {
+                // No cursor, the section doesn't exist so just return 0
+                return 0;
+            }
+            
+            // If the sort mode has changed, or we haven't yet created an
+            // indexer one, then create a new one that is indexing the
+            // appropriate column based on the sort mode.
+            if (mIndexerSortMode != mSortMode || mIndexer == null) {
+                mIndexerSortMode = mSortMode;
+                int idx = mTitleIdx;
+                switch (mIndexerSortMode) {
+                    case ARTIST_MENU:
+                        idx = mArtistIdx;
+                        break;
+                    case ALBUM_MENU:
+                        idx = mAlbumIdx;
+                        break;
+                }
+                mIndexer = new MusicAlphabetIndexer(cursor, idx,
+                        getResources().getString(
+                                com.android.internal.R.string.fast_scroll_alphabet));
+                
+            // If we have a valid indexer, but the cursor has changed since
+            // its last use, then point it to the current cursor.
+            } else if (mIndexerOutOfDate) {
+                mIndexer.setCursor(cursor);
+            }
+            
+            mIndexerOutOfDate = false;
+            
+            return mIndexer.getPositionForSection(section);
+        }
+
+        public int getSectionForPosition(int position) {
+            return 0;
+        }
+
+        public Object[] getSections() {
+            return mIndexer.getSections();
+        }
+    }
+
+    /**
+     * This is our specialization of AsyncQueryHandler applies new cursors
+     * to our state as they become available.
+     */
+    private final class QueryHandler extends AsyncQueryHandler {
+        public QueryHandler(Context context) {
+            super(context.getContentResolver());
+        }
+
+        @Override
+        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+            if (!isFinishing()) {
+                // Update the adapter: we are no longer loading, and have
+                // a new cursor for it.
+                mAdapter.setLoading(false);
+                mAdapter.changeCursor(cursor);
+                setProgressBarIndeterminateVisibility(false);
+    
+                // Now that the cursor is populated again, it's possible to restore the list state
+                if (mListState != null) {
+                    getListView().onRestoreInstanceState(mListState);
+                    if (mListHasFocus) {
+                        getListView().requestFocus();
+                    }
+                    mListHasFocus = false;
+                    mListState = null;
+                }
+            } else {
+                cursor.close();
+            }
+        }
+    }
+
+    public static String makeTimeString(Context context, long secs) {
+        String durationformat = context.getString(R.string.durationformat);
+        
+        /* Provide multiple arguments so the format can be changed easily
+         * by modifying the xml.
+         */
+        sFormatBuilder.setLength(0);
+
+        final Object[] timeArgs = sTimeArgs;
+        timeArgs[0] = secs / 3600;
+        timeArgs[1] = secs / 60;
+        timeArgs[2] = (secs / 60) % 60;
+        timeArgs[3] = secs;
+        timeArgs[4] = secs % 60;
+
+        return sFormatter.format(durationformat, timeArgs).toString();
+    }
+    
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        
+        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+        
+        int sortMode = TRACK_MENU;
+        if (icicle == null) {
+            mSelectedUri = getIntent().getParcelableExtra(
+                    RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
+        } else {
+            mSelectedUri = (Uri)icicle.getParcelable(
+                    RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
+            // Retrieve list state. This will be applied after the
+            // QueryHandler has run
+            mListState = icicle.getParcelable(LIST_STATE_KEY);
+            mListHasFocus = icicle.getBoolean(FOCUS_KEY);
+            sortMode = icicle.getInt(SORT_MODE_KEY, sortMode);
+        }
+        if (Intent.ACTION_GET_CONTENT.equals(getIntent().getAction())) {
+            mBaseUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+        } else {
+            mBaseUri = getIntent().getData();
+            if (mBaseUri == null) {
+                Log.w("MusicPicker", "No data URI given to PICK action");
+                finish();
+                return;
+            }
+        }
+        
+        setContentView(R.layout.music_picker);
+
+        mSortOrder = MediaStore.Audio.Media.TITLE_KEY;
+
+        final ListView listView = getListView();
+
+        listView.setItemsCanFocus(false);
+        
+        mAdapter = new TrackListAdapter(this, listView,
+                R.layout.music_picker_item, new String[] {},
+                new int[] {});
+
+        setListAdapter(mAdapter);
+        
+        listView.setTextFilterEnabled(true);
+        
+        // We manually save/restore the listview state
+        listView.setSaveEnabled(false);
+
+        mQueryHandler = new QueryHandler(this);
+        
+        mProgressContainer = findViewById(R.id.progressContainer);
+        mListContainer = findViewById(R.id.listContainer);
+        
+        mOkayButton = findViewById(R.id.okayButton);
+        mOkayButton.setOnClickListener(this);
+        mCancelButton = findViewById(R.id.cancelButton);
+        mCancelButton.setOnClickListener(this);
+        
+        // If there is a currently selected Uri, then try to determine who
+        // it is.
+        if (mSelectedUri != null) {
+            Uri.Builder builder = mSelectedUri.buildUpon();
+            String path = mSelectedUri.getEncodedPath();
+            int idx = path.lastIndexOf('/');
+            if (idx >= 0) {
+                path = path.substring(0, idx);
+            }
+            builder.encodedPath(path);
+            Uri baseSelectedUri = builder.build();
+            if (DBG) Log.v(TAG, "Selected Uri: " + mSelectedUri);
+            if (DBG) Log.v(TAG, "Selected base Uri: " + baseSelectedUri);
+            if (DBG) Log.v(TAG, "Base Uri: " + mBaseUri);
+            if (baseSelectedUri.equals(mBaseUri)) {
+                // If the base Uri of the selected Uri is the same as our
+                // content's base Uri, then use the selection!
+                mSelectedId = ContentUris.parseId(mSelectedUri);
+            }
+        }
+        
+        setSortMode(sortMode);
+    }
+
+    @Override public void onRestart() {
+        super.onRestart();
+        doQuery(false, null);
+    }
+    
+    @Override public boolean onOptionsItemSelected(MenuItem item) {
+        if (setSortMode(item.getItemId())) {
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+        menu.add(Menu.NONE, TRACK_MENU, Menu.NONE, R.string.sort_by_track);
+        menu.add(Menu.NONE, ALBUM_MENU, Menu.NONE, R.string.sort_by_album);
+        menu.add(Menu.NONE, ARTIST_MENU, Menu.NONE, R.string.sort_by_artist);
+        return true;
+    }
+
+    @Override protected void onSaveInstanceState(Bundle icicle) {
+        super.onSaveInstanceState(icicle);
+        // Save list state in the bundle so we can restore it after the
+        // QueryHandler has run
+        icicle.putParcelable(LIST_STATE_KEY, getListView().onSaveInstanceState());
+        icicle.putBoolean(FOCUS_KEY, getListView().hasFocus());
+        icicle.putInt(SORT_MODE_KEY, mSortMode);
+    }
+    
+    @Override public void onPause() {
+        super.onPause();
+        stopMediaPlayer();
+    }
+    
+    @Override public void onStop() {
+        super.onStop();
+        
+        // We don't want the list to display the empty state, since when we
+        // resume it will still be there and show up while the new query is
+        // happening. After the async query finishes in response to onResume()
+        // setLoading(false) will be called.
+        mAdapter.setLoading(true);
+        mAdapter.changeCursor(null);
+    }
+    
+    /**
+     * Changes the current sort order, building the appropriate query string
+     * for the selected order.
+     */
+    boolean setSortMode(int sortMode) {
+        if (sortMode != mSortMode) {
+            switch (sortMode) {
+                case TRACK_MENU:
+                    mSortMode = sortMode;
+                    mSortOrder = MediaStore.Audio.Media.TITLE_KEY;
+                    doQuery(false, null);
+                    return true;
+                case ALBUM_MENU:
+                    mSortMode = sortMode;
+                    mSortOrder = MediaStore.Audio.Media.ALBUM_KEY + " ASC, "
+                            + MediaStore.Audio.Media.TRACK + " ASC, "
+                            + MediaStore.Audio.Media.TITLE_KEY + " ASC";
+                    doQuery(false, null);
+                    return true;
+                case ARTIST_MENU:
+                    mSortMode = sortMode;
+                    mSortOrder = MediaStore.Audio.Media.ARTIST_KEY + " ASC, "
+                            + MediaStore.Audio.Media.ALBUM_KEY + " ASC, "
+                            + MediaStore.Audio.Media.TRACK + " ASC, "
+                            + MediaStore.Audio.Media.TITLE_KEY + " ASC";
+                    doQuery(false, null);
+                    return true;
+            }
+            
+        }
+        return false;
+    }
+    
+    /**
+     * The first time this is called, we hide the large progress indicator
+     * and show the list view, doing fade animations between them.
+     */
+    void makeListShown() {
+        if (!mListShown) {
+            mListShown = true;
+            mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
+                    this, android.R.anim.fade_out));
+            mProgressContainer.setVisibility(View.GONE);
+            mListContainer.startAnimation(AnimationUtils.loadAnimation(
+                    this, android.R.anim.fade_in));
+            mListContainer.setVisibility(View.VISIBLE);
+        }
+    }
+    
+    /**
+     * Common method for performing a query of the music database, called for
+     * both top-level queries and filtering.
+     * 
+     * @param sync If true, this query should be done synchronously and the
+     * resulting cursor returned.  If false, it will be done asynchronously and
+     * null returned.
+     * @param filterstring If non-null, this is a filter to apply to the query.
+     */
+    Cursor doQuery(boolean sync, String filterstring) {
+        // Cancel any pending queries
+        mQueryHandler.cancelOperation(MY_QUERY_TOKEN);
+        
+        StringBuilder where = new StringBuilder();
+        where.append(MediaStore.Audio.Media.TITLE + " != ''");
+        
+        // Add in the filtering constraints
+        String [] keywords = null;
+        if (filterstring != null) {
+            String [] searchWords = filterstring.split(" ");
+            keywords = new String[searchWords.length];
+            Collator col = Collator.getInstance();
+            col.setStrength(Collator.PRIMARY);
+            for (int i = 0; i < searchWords.length; i++) {
+                keywords[i] = '%' + MediaStore.Audio.keyFor(searchWords[i]) + '%';
+            }
+            for (int i = 0; i < searchWords.length; i++) {
+                where.append(" AND ");
+                where.append(MediaStore.Audio.Media.ARTIST_KEY + "||");
+                where.append(MediaStore.Audio.Media.ALBUM_KEY + "||");
+                where.append(MediaStore.Audio.Media.TITLE_KEY + " LIKE ?");
+            }
+        }
+        
+        // We want to show all audio files, even recordings.  Enforcing the
+        // following condition would hide recordings.
+        //where.append(" AND " + MediaStore.Audio.Media.IS_MUSIC + "=1");
+        
+        if (sync) {
+            try {
+                return getContentResolver().query(mBaseUri, CURSOR_COLS,
+                        where.toString(), keywords, mSortOrder);
+            } catch (UnsupportedOperationException ex) {
+            }
+        } else {
+            mAdapter.setLoading(true);
+            setProgressBarIndeterminateVisibility(true);
+            mQueryHandler.startQuery(MY_QUERY_TOKEN, null, mBaseUri, CURSOR_COLS,
+                    where.toString(), keywords, mSortOrder);
+        }
+        return null;
+    }
+    
+    @Override protected void onListItemClick(ListView l, View v, int position,
+            long id) {
+        mCursor.moveToPosition(position);
+        if (DBG) Log.v(TAG, "Click on " + position + " (id=" + id
+                + ", cursid="
+                + mCursor.getLong(mCursor.getColumnIndex(MediaStore.Audio.Media._ID))
+                + ") in cursor " + mCursor
+                + " adapter=" + l.getAdapter());
+        setSelected(mCursor);
+    }
+    
+    void setSelected(Cursor c) {
+        Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+        long newId = mCursor.getLong(mCursor.getColumnIndex(MediaStore.Audio.Media._ID));
+        mSelectedUri = ContentUris.withAppendedId(uri, newId);
+        
+        mSelectedId = newId;
+        if (newId != mPlayingId || mMediaPlayer == null) {
+            stopMediaPlayer();
+            mMediaPlayer = new MediaPlayer();
+            try {
+                mMediaPlayer.setDataSource(this, mSelectedUri);
+                mMediaPlayer.setOnCompletionListener(this);
+                mMediaPlayer.setAudioStreamType(AudioManager.STREAM_RING);
+                mMediaPlayer.prepare();
+                mMediaPlayer.start();
+                mPlayingId = newId;
+                getListView().invalidateViews();
+            } catch (IOException e) {
+                Log.w("MusicPicker", "Unable to play track", e);
+            }
+        } else if (mMediaPlayer != null) {
+            stopMediaPlayer();
+            getListView().invalidateViews();
+        }
+    }
+    
+    public void onCompletion(MediaPlayer mp) {
+        if (mMediaPlayer == mp) {
+            mp.stop();
+            mp.release();
+            mMediaPlayer = null;
+            mPlayingId = -1;
+            getListView().invalidateViews();
+        }
+    }
+    
+    void stopMediaPlayer() {
+        if (mMediaPlayer != null) {
+            mMediaPlayer.stop();
+            mMediaPlayer.release();
+            mMediaPlayer = null;
+            mPlayingId = -1;
+        }
+    }
+    
+    public void onClick(View v) {
+        switch (v.getId()) {
+            case R.id.okayButton:
+                if (mSelectedId >= 0) {
+                    setResult(RESULT_OK, new Intent().setData(mSelectedUri));
+                    finish();
+                }
+                break;
+
+            case R.id.cancelButton:
+                finish();
+                break;
+        }
+    }
+}
diff --git a/src/com/android/music/MusicUtils.java b/src/com/android/music/MusicUtils.java
new file mode 100644
index 0000000..2f5aeca
--- /dev/null
+++ b/src/com/android/music/MusicUtils.java
@@ -0,0 +1,1196 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.Locale;
+
+import android.app.Activity;
+import android.app.ExpandableListActivity;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.media.MediaFile;
+import android.media.MediaScanner;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.provider.MediaStore;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.SubMenu;
+import android.view.View;
+import android.view.Window;
+import android.widget.TextView;
+import android.widget.Toast;
+
+public class MusicUtils {
+
+    private static final String TAG = "MusicUtils";
+
+    public interface Defs {
+        public final static int OPEN_URL = 0;
+        public final static int ADD_TO_PLAYLIST = 1;
+        public final static int USE_AS_RINGTONE = 2;
+        public final static int PLAYLIST_SELECTED = 3;
+        public final static int NEW_PLAYLIST = 4;
+        public final static int PLAY_SELECTION = 5;
+        public final static int GOTO_START = 6;
+        public final static int GOTO_PLAYBACK = 7;
+        public final static int PARTY_SHUFFLE = 8;
+        public final static int SHUFFLE_ALL = 9;
+        public final static int DELETE_ITEM = 10;
+        public final static int SCAN_DONE = 11;
+        public final static int QUEUE = 12;
+        public final static int CHILD_MENU_BASE = 13; // this should be the last item
+    }
+
+    public static String makeAlbumsLabel(Context context, int numalbums, int numsongs, boolean isUnknown) {
+        // There are two formats for the albums/songs information:
+        // "N Song(s)"  - used for unknown artist/album
+        // "N Album(s)" - used for known albums
+        
+        StringBuilder songs_albums = new StringBuilder();
+
+        Resources r = context.getResources();
+        if (isUnknown) {
+            if (numsongs == 1) {
+                songs_albums.append(context.getString(R.string.onesong));
+            } else {
+                String f = r.getQuantityText(R.plurals.Nsongs, numsongs).toString();
+                sFormatBuilder.setLength(0);
+                sFormatter.format(f, Integer.valueOf(numsongs));
+                songs_albums.append(sFormatBuilder);
+            }
+        } else {
+            String f = r.getQuantityText(R.plurals.Nalbums, numalbums).toString();
+            sFormatBuilder.setLength(0);
+            sFormatter.format(f, Integer.valueOf(numalbums));
+            songs_albums.append(sFormatBuilder);
+            songs_albums.append(context.getString(R.string.albumsongseparator));
+        }
+        return songs_albums.toString();
+    }
+
+    /**
+     * This is now only used for the query screen
+     */
+    public static String makeAlbumsSongsLabel(Context context, int numalbums, int numsongs, boolean isUnknown) {
+        // There are several formats for the albums/songs information:
+        // "1 Song"   - used if there is only 1 song
+        // "N Songs" - used for the "unknown artist" item
+        // "1 Album"/"N Songs" 
+        // "N Album"/"M Songs"
+        // Depending on locale, these may need to be further subdivided
+        
+        StringBuilder songs_albums = new StringBuilder();
+
+        if (numsongs == 1) {
+            songs_albums.append(context.getString(R.string.onesong));
+        } else {
+            Resources r = context.getResources();
+            if (! isUnknown) {
+                String f = r.getQuantityText(R.plurals.Nalbums, numalbums).toString();
+                sFormatBuilder.setLength(0);
+                sFormatter.format(f, Integer.valueOf(numalbums));
+                songs_albums.append(sFormatBuilder);
+                songs_albums.append(context.getString(R.string.albumsongseparator));
+            }
+            String f = r.getQuantityText(R.plurals.Nsongs, numsongs).toString();
+            sFormatBuilder.setLength(0);
+            sFormatter.format(f, Integer.valueOf(numsongs));
+            songs_albums.append(sFormatBuilder);
+        }
+        return songs_albums.toString();
+    }
+    
+    public static IMediaPlaybackService sService = null;
+    private static HashMap<Context, ServiceBinder> sConnectionMap = new HashMap<Context, ServiceBinder>();
+
+    public static boolean bindToService(Context context) {
+        return bindToService(context, null);
+    }
+
+    public static boolean bindToService(Context context, ServiceConnection callback) {
+        context.startService(new Intent(context, MediaPlaybackService.class));
+        ServiceBinder sb = new ServiceBinder(callback);
+        sConnectionMap.put(context, sb);
+        return context.bindService((new Intent()).setClass(context,
+                MediaPlaybackService.class), sb, 0);
+    }
+    
+    public static void unbindFromService(Context context) {
+        ServiceBinder sb = (ServiceBinder) sConnectionMap.remove(context);
+        if (sb == null) {
+            Log.e("MusicUtils", "Trying to unbind for unknown Context");
+            return;
+        }
+        context.unbindService(sb);
+        if (sConnectionMap.isEmpty()) {
+            // presumably there is nobody interested in the service at this point,
+            // so don't hang on to the ServiceConnection
+            sService = null;
+        }
+    }
+
+    private static class ServiceBinder implements ServiceConnection {
+        ServiceConnection mCallback;
+        ServiceBinder(ServiceConnection callback) {
+            mCallback = callback;
+        }
+        
+        public void onServiceConnected(ComponentName className, android.os.IBinder service) {
+            sService = IMediaPlaybackService.Stub.asInterface(service);
+            initAlbumArtCache();
+            if (mCallback != null) {
+                mCallback.onServiceConnected(className, service);
+            }
+        }
+        
+        public void onServiceDisconnected(ComponentName className) {
+            if (mCallback != null) {
+                mCallback.onServiceDisconnected(className);
+            }
+            sService = null;
+        }
+    }
+    
+    public static int getCurrentAlbumId() {
+        if (sService != null) {
+            try {
+                return sService.getAlbumId();
+            } catch (RemoteException ex) {
+            }
+        }
+        return -1;
+    }
+
+    public static int getCurrentArtistId() {
+        if (MusicUtils.sService != null) {
+            try {
+                return sService.getArtistId();
+            } catch (RemoteException ex) {
+            }
+        }
+        return -1;
+    }
+
+    public static int getCurrentAudioId() {
+        if (MusicUtils.sService != null) {
+            try {
+                return sService.getAudioId();
+            } catch (RemoteException ex) {
+            }
+        }
+        return -1;
+    }
+    
+    public static int getCurrentShuffleMode() {
+        int mode = MediaPlaybackService.SHUFFLE_NONE;
+        if (sService != null) {
+            try {
+                mode = sService.getShuffleMode();
+            } catch (RemoteException ex) {
+            }
+        }
+        return mode;
+    }
+    
+    /*
+     * Returns true if a file is currently opened for playback (regardless
+     * of whether it's playing or paused).
+     */
+    public static boolean isMusicLoaded() {
+        if (MusicUtils.sService != null) {
+            try {
+                return sService.getPath() != null;
+            } catch (RemoteException ex) {
+            }
+        }
+        return false;
+    }
+
+    private final static int [] sEmptyList = new int[0];
+    
+    public static int [] getSongListForCursor(Cursor cursor) {
+        if (cursor == null) {
+            return sEmptyList;
+        }
+        int len = cursor.getCount();
+        int [] list = new int[len];
+        cursor.moveToFirst();
+        int colidx = -1;
+        try {
+            colidx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.Members.AUDIO_ID);
+        } catch (IllegalArgumentException ex) {
+            colidx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);
+        }
+        for (int i = 0; i < len; i++) {
+            list[i] = cursor.getInt(colidx);
+            cursor.moveToNext();
+        }
+        return list;
+    }
+
+    public static int [] getSongListForArtist(Context context, int id) {
+        final String[] ccols = new String[] { MediaStore.Audio.Media._ID };
+        String where = MediaStore.Audio.Media.ARTIST_ID + "=" + id + " AND " + 
+        MediaStore.Audio.Media.IS_MUSIC + "=1";
+        Cursor cursor = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                ccols, where, null,
+                MediaStore.Audio.Media.ALBUM_KEY + ","  + MediaStore.Audio.Media.TRACK);
+        
+        if (cursor != null) {
+            int [] list = getSongListForCursor(cursor);
+            cursor.close();
+            return list;
+        }
+        return sEmptyList;
+    }
+
+    public static int [] getSongListForAlbum(Context context, int id) {
+        final String[] ccols = new String[] { MediaStore.Audio.Media._ID };
+        String where = MediaStore.Audio.Media.ALBUM_ID + "=" + id + " AND " + 
+                MediaStore.Audio.Media.IS_MUSIC + "=1";
+        Cursor cursor = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                ccols, where, null, MediaStore.Audio.Media.TRACK);
+
+        if (cursor != null) {
+            int [] list = getSongListForCursor(cursor);
+            cursor.close();
+            return list;
+        }
+        return sEmptyList;
+    }
+
+    public static int [] getSongListForPlaylist(Context context, long plid) {
+        final String[] ccols = new String[] { MediaStore.Audio.Playlists.Members.AUDIO_ID };
+        Cursor cursor = query(context, MediaStore.Audio.Playlists.Members.getContentUri("external", plid),
+                ccols, null, null, MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER);
+        
+        if (cursor != null) {
+            int [] list = getSongListForCursor(cursor);
+            cursor.close();
+            return list;
+        }
+        return sEmptyList;
+    }
+    
+    public static void playPlaylist(Context context, long plid) {
+        int [] list = getSongListForPlaylist(context, plid);
+        if (list != null) {
+            playAll(context, list, -1, false);
+        }
+    }
+
+    public static int [] getAllSongs(Context context) {
+        Cursor c = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
+                null, null);
+        try {
+            if (c == null || c.getCount() == 0) {
+                return null;
+            }
+            int len = c.getCount();
+            int[] list = new int[len];
+            for (int i = 0; i < len; i++) {
+                c.moveToNext();
+                list[i] = c.getInt(0);
+            }
+
+            return list;
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+    }
+
+    /**
+     * Fills out the given submenu with items for "new playlist" and
+     * any existing playlists. When the user selects an item, the
+     * application will receive PLAYLIST_SELECTED with the Uri of
+     * the selected playlist, NEW_PLAYLIST if a new playlist
+     * should be created, and QUEUE if the "current playlist" was
+     * selected.
+     * @param context The context to use for creating the menu items
+     * @param sub The submenu to add the items to.
+     */
+    public static void makePlaylistMenu(Context context, SubMenu sub) {
+        String[] cols = new String[] {
+                MediaStore.Audio.Playlists._ID,
+                MediaStore.Audio.Playlists.NAME
+        };
+        ContentResolver resolver = context.getContentResolver();
+        if (resolver == null) {
+            System.out.println("resolver = null");
+        } else {
+            String whereclause = MediaStore.Audio.Playlists.NAME + " != ''";
+            Cursor cur = resolver.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+                cols, whereclause, null,
+                MediaStore.Audio.Playlists.NAME);
+            sub.clear();
+            sub.add(1, Defs.QUEUE, 0, R.string.queue);
+            sub.add(1, Defs.NEW_PLAYLIST, 0, R.string.new_playlist);
+            if (cur != null && cur.getCount() > 0) {
+                //sub.addSeparator(1, 0);
+                cur.moveToFirst();
+                while (! cur.isAfterLast()) {
+                    Intent intent = new Intent();
+                    intent.putExtra("playlist", cur.getInt(0));
+//                    if (cur.getInt(0) == mLastPlaylistSelected) {
+//                        sub.add(0, MusicBaseActivity.PLAYLIST_SELECTED, cur.getString(1)).setIntent(intent);
+//                    } else {
+                        sub.add(1, Defs.PLAYLIST_SELECTED, 0, cur.getString(1)).setIntent(intent);
+//                    }
+                    cur.moveToNext();
+                }
+            }
+            if (cur != null) {
+                cur.close();
+            }
+        }
+    }
+
+    public static void clearPlaylist(Context context, int plid) {
+        
+        Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", plid);
+        context.getContentResolver().delete(uri, null, null);
+        return;
+    }
+    
+    public static void deleteTracks(Context context, int [] list) {
+        
+        String [] cols = new String [] { MediaStore.Audio.Media._ID, 
+                MediaStore.Audio.Media.DATA, MediaStore.Audio.Media.ALBUM_ID };
+        StringBuilder where = new StringBuilder();
+        where.append(MediaStore.Audio.Media._ID + " IN (");
+        for (int i = 0; i < list.length; i++) {
+            where.append(list[i]);
+            if (i < list.length - 1) {
+                where.append(",");
+            }
+        }
+        where.append(")");
+        Cursor c = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, cols,
+                where.toString(), null, null);
+
+        if (c != null) {
+
+            // step 1: remove selected tracks from the current playlist, as well
+            // as from the album art cache
+            try {
+                c.moveToFirst();
+                while (! c.isAfterLast()) {
+                    // remove from current playlist
+                    int id = c.getInt(0);
+                    sService.removeTrack(id);
+                    // remove from album art cache
+                    int artIndex = c.getInt(2);
+                    synchronized(sArtCache) {
+                        sArtCache.remove(artIndex);
+                    }
+                    c.moveToNext();
+                }
+            } catch (RemoteException ex) {
+            }
+
+            // step 2: remove selected tracks from the database
+            context.getContentResolver().delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, where.toString(), null);
+
+            // step 3: remove files from card
+            c.moveToFirst();
+            while (! c.isAfterLast()) {
+                String name = c.getString(1);
+                File f = new File(name);
+                try {  // File.delete can throw a security exception
+                    if (!f.delete()) {
+                        // I'm not sure if we'd ever get here (deletion would
+                        // have to fail, but no exception thrown)
+                        Log.e("MusicUtils", "Failed to delete file " + name);
+                    }
+                    c.moveToNext();
+                } catch (SecurityException ex) {
+                    c.moveToNext();
+                }
+            }
+            c.close();
+        }
+
+        String message = context.getResources().getQuantityString(
+                R.plurals.NNNtracksdeleted, list.length, Integer.valueOf(list.length));
+        
+        Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
+        // We deleted a number of tracks, which could affect any number of things
+        // in the media content domain, so update everything.
+        context.getContentResolver().notifyChange(Uri.parse("content://media"), null);
+    }
+    
+    public static void addToCurrentPlaylist(Context context, int [] list) {
+        if (sService == null) {
+            return;
+        }
+        try {
+            sService.enqueue(list, MediaPlaybackService.LAST);
+            String message = context.getResources().getQuantityString(
+                    R.plurals.NNNtrackstoplaylist, list.length, Integer.valueOf(list.length));
+            Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
+        } catch (RemoteException ex) {
+        }
+    }
+    
+    public static void addToPlaylist(Context context, int [] ids, long playlistid) {
+        if (ids == null) {
+            // this shouldn't happen (the menuitems shouldn't be visible
+            // unless the selected item represents something playable
+            Log.e("MusicBase", "ListSelection null");
+        } else {
+            int size = ids.length;
+            ContentValues values [] = new ContentValues[size];
+            ContentResolver resolver = context.getContentResolver();
+            // need to determine the number of items currently in the playlist,
+            // so the play_order field can be maintained.
+            String[] cols = new String[] {
+                    "count(*)"
+            };
+            Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistid);
+            Cursor cur = resolver.query(uri, cols, null, null, null);
+            cur.moveToFirst();
+            int base = cur.getInt(0);
+            cur.close();
+
+            for (int i = 0; i < size; i++) {
+                values[i] = new ContentValues();
+                values[i].put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, Integer.valueOf(base + i));
+                values[i].put(MediaStore.Audio.Playlists.Members.AUDIO_ID, ids[i]);
+            }
+            resolver.bulkInsert(uri, values);
+            String message = context.getResources().getQuantityString(
+                    R.plurals.NNNtrackstoplaylist, size, Integer.valueOf(size));
+            Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
+            //mLastPlaylistSelected = playlistid;
+        }
+    }
+
+    public static Cursor query(Context context, Uri uri, String[] projection,
+            String selection, String[] selectionArgs, String sortOrder) {
+        try {
+            ContentResolver resolver = context.getContentResolver();
+            if (resolver == null) {
+                return null;
+            }
+            return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
+         } catch (UnsupportedOperationException ex) {
+            return null;
+        }
+        
+    }
+    
+    public static boolean isMediaScannerScanning(Context context) {
+        boolean result = false;
+        Cursor cursor = query(context, MediaStore.getMediaScannerUri(), 
+                new String [] { MediaStore.MEDIA_SCANNER_VOLUME }, null, null, null);
+        if (cursor != null) {
+            if (cursor.getCount() == 1) {
+                cursor.moveToFirst();
+                result = "external".equals(cursor.getString(0));
+            }
+            cursor.close(); 
+        } 
+
+        return result;
+    }
+    
+    public static void setSpinnerState(Activity a) {
+        if (isMediaScannerScanning(a)) {
+            // start the progress spinner
+            a.getWindow().setFeatureInt(
+                    Window.FEATURE_INDETERMINATE_PROGRESS,
+                    Window.PROGRESS_INDETERMINATE_ON);
+
+            a.getWindow().setFeatureInt(
+                    Window.FEATURE_INDETERMINATE_PROGRESS,
+                    Window.PROGRESS_VISIBILITY_ON);
+        } else {
+            // stop the progress spinner
+            a.getWindow().setFeatureInt(
+                    Window.FEATURE_INDETERMINATE_PROGRESS,
+                    Window.PROGRESS_VISIBILITY_OFF);
+        }
+    }
+    
+    public static void displayDatabaseError(Activity a) {
+        String status = Environment.getExternalStorageState();
+        int title = R.string.sdcard_error_title;
+        int message = R.string.sdcard_error_message;
+        
+        if (status.equals(Environment.MEDIA_SHARED) ||
+                status.equals(Environment.MEDIA_UNMOUNTED)) {
+            title = R.string.sdcard_busy_title;
+            message = R.string.sdcard_busy_message;
+        } else if (status.equals(Environment.MEDIA_REMOVED)) {
+            title = R.string.sdcard_missing_title;
+            message = R.string.sdcard_missing_message;
+        } else if (status.equals(Environment.MEDIA_MOUNTED)){
+            // The card is mounted, but we didn't get a valid cursor.
+            // This probably means the mediascanner hasn't started scanning the
+            // card yet (there is a small window of time during boot where this
+            // will happen).
+            a.setTitle("");
+            Intent intent = new Intent();
+            intent.setClass(a, ScanningProgress.class);
+            a.startActivityForResult(intent, Defs.SCAN_DONE);
+        } else {
+            Log.d(TAG, "sd card: " + status);
+        }
+
+        a.setTitle(title);
+        View v = a.findViewById(R.id.sd_message);
+        if (v != null) {
+            v.setVisibility(View.VISIBLE);
+        }
+        v = a.findViewById(R.id.sd_icon);
+        if (v != null) {
+            v.setVisibility(View.VISIBLE);
+        }
+        v = a.findViewById(android.R.id.list);
+        if (v != null) {
+            v.setVisibility(View.GONE);
+        }
+        TextView tv = (TextView) a.findViewById(R.id.sd_message);
+        tv.setText(message);
+    }
+    
+    public static void hideDatabaseError(Activity a) {
+        View v = a.findViewById(R.id.sd_message);
+        if (v != null) {
+            v.setVisibility(View.GONE);
+        }
+        v = a.findViewById(R.id.sd_icon);
+        if (v != null) {
+            v.setVisibility(View.GONE);
+        }
+        v = a.findViewById(android.R.id.list);
+        if (v != null) {
+            v.setVisibility(View.VISIBLE);
+        }
+    }
+
+    static protected Uri getContentURIForPath(String path) {
+        return Uri.fromFile(new File(path));
+    }
+
+    
+    /*  Try to use String.format() as little as possible, because it creates a
+     *  new Formatter every time you call it, which is very inefficient.
+     *  Reusing an existing Formatter more than tripled the speed of
+     *  makeTimeString().
+     *  This Formatter/StringBuilder are also used by makeAlbumSongsLabel()
+     */
+    private static StringBuilder sFormatBuilder = new StringBuilder();
+    private static Formatter sFormatter = new Formatter(sFormatBuilder, Locale.getDefault());
+    private static final Object[] sTimeArgs = new Object[5];
+
+    public static String makeTimeString(Context context, long secs) {
+        String durationformat = context.getString(R.string.durationformat);
+        
+        /* Provide multiple arguments so the format can be changed easily
+         * by modifying the xml.
+         */
+        sFormatBuilder.setLength(0);
+
+        final Object[] timeArgs = sTimeArgs;
+        timeArgs[0] = secs / 3600;
+        timeArgs[1] = secs / 60;
+        timeArgs[2] = (secs / 60) % 60;
+        timeArgs[3] = secs;
+        timeArgs[4] = secs % 60;
+
+        return sFormatter.format(durationformat, timeArgs).toString();
+    }
+    
+    public static void shuffleAll(Context context, Cursor cursor) {
+        playAll(context, cursor, 0, true);
+    }
+
+    public static void playAll(Context context, Cursor cursor) {
+        playAll(context, cursor, 0, false);
+    }
+    
+    public static void playAll(Context context, Cursor cursor, int position) {
+        playAll(context, cursor, position, false);
+    }
+    
+    public static void playAll(Context context, int [] list, int position) {
+        playAll(context, list, position, false);
+    }
+    
+    private static void playAll(Context context, Cursor cursor, int position, boolean force_shuffle) {
+    
+        int [] list = getSongListForCursor(cursor);
+        playAll(context, list, position, force_shuffle);
+    }
+    
+    private static void playAll(Context context, int [] list, int position, boolean force_shuffle) {
+        if (list.length == 0 || sService == null) {
+            Log.d("MusicUtils", "attempt to play empty song list");
+            // Don't try to play empty playlists. Nothing good will come of it.
+            String message = context.getString(R.string.emptyplaylist, list.length);
+            Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
+            return;
+        }
+        try {
+            if (force_shuffle) {
+                sService.setShuffleMode(MediaPlaybackService.SHUFFLE_NORMAL);
+            }
+            int curid = sService.getAudioId();
+            int curpos = sService.getQueuePosition();
+            if (position != -1 && curpos == position && curid == list[position]) {
+                // The selected file is the file that's currently playing;
+                // figure out if we need to restart with a new playlist,
+                // or just launch the playback activity.
+                int [] playlist = sService.getQueue();
+                if (Arrays.equals(list, playlist)) {
+                    // we don't need to set a new list, but we should resume playback if needed
+                    sService.play();
+                    return; // the 'finally' block will still run
+                }
+            }
+            if (position < 0) {
+                position = 0;
+            }
+            sService.open(list, force_shuffle ? -1 : position);
+            sService.play();
+        } catch (RemoteException ex) {
+        } finally {
+            Intent intent = new Intent("com.android.music.PLAYBACK_VIEWER")
+                .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+            context.startActivity(intent);
+        }
+    }
+    
+    public static void clearQueue() {
+        try {
+            sService.removeTracks(0, Integer.MAX_VALUE);
+        } catch (RemoteException ex) {
+        }
+    }
+    
+    // A really simple BitmapDrawable-like class, that doesn't do
+    // scaling, dithering or filtering.
+    private static class FastBitmapDrawable extends Drawable {
+        private Bitmap mBitmap;
+        public FastBitmapDrawable(Bitmap b) {
+            mBitmap = b;
+        }
+        @Override
+        public void draw(Canvas canvas) {
+            canvas.drawBitmap(mBitmap, 0, 0, null);
+        }
+        @Override
+        public int getOpacity() {
+            return PixelFormat.OPAQUE;
+        }
+        @Override
+        public void setAlpha(int alpha) {
+        }
+        @Override
+        public void setColorFilter(ColorFilter cf) {
+        }
+    }
+    
+    private static int sArtId = -2;
+    private static byte [] mCachedArt;
+    private static Bitmap mCachedBit = null;
+    private static final BitmapFactory.Options sBitmapOptionsCache = new BitmapFactory.Options();
+    private static final BitmapFactory.Options sBitmapOptions = new BitmapFactory.Options();
+    private static final Uri sArtworkUri = Uri.parse("content://media/external/audio/albumart");
+    private static final HashMap<Integer, Drawable> sArtCache = new HashMap<Integer, Drawable>();
+    private static int sArtCacheId = -1;
+    
+    static {
+        // for the cache, 
+        // 565 is faster to decode and display
+        // and we don't want to dither here because the image will be scaled down later
+        sBitmapOptionsCache.inPreferredConfig = Bitmap.Config.RGB_565;
+        sBitmapOptionsCache.inDither = false;
+
+        sBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565;
+        sBitmapOptions.inDither = false;
+    }
+
+    public static void initAlbumArtCache() {
+        try {
+            int id = sService.getMediaMountedCount();
+            if (id != sArtCacheId) {
+                clearAlbumArtCache();
+                sArtCacheId = id; 
+            }
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static void clearAlbumArtCache() {
+        synchronized(sArtCache) {
+            sArtCache.clear();
+        }
+    }
+    
+    public static Drawable getCachedArtwork(Context context, int artIndex, BitmapDrawable defaultArtwork) {
+        Drawable d = null;
+        synchronized(sArtCache) {
+            d = sArtCache.get(artIndex);
+        }
+        if (d == null) {
+            d = defaultArtwork;
+            final Bitmap icon = defaultArtwork.getBitmap();
+            int w = icon.getWidth();
+            int h = icon.getHeight();
+            Bitmap b = MusicUtils.getArtworkQuick(context, artIndex, w, h);
+            if (b != null) {
+                d = new FastBitmapDrawable(b);
+                synchronized(sArtCache) {
+                    // the cache may have changed since we checked
+                    Drawable value = sArtCache.get(artIndex);
+                    if (value == null) {
+                        sArtCache.put(artIndex, d);
+                    } else {
+                        d = value;
+                    }
+                }
+            }
+        }
+        return d;
+    }
+
+    // Get album art for specified album. This method will not try to
+    // fall back to getting artwork directly from the file, nor will
+    // it attempt to repair the database.
+    private static Bitmap getArtworkQuick(Context context, int album_id, int w, int h) {
+        // NOTE: There is in fact a 1 pixel border on the right side in the ImageView
+        // used to display this drawable. Take it into account now, so we don't have to
+        // scale later.
+        w -= 1;
+        ContentResolver res = context.getContentResolver();
+        Uri uri = ContentUris.withAppendedId(sArtworkUri, album_id);
+        if (uri != null) {
+            ParcelFileDescriptor fd = null;
+            try {
+                fd = res.openFileDescriptor(uri, "r");
+                int sampleSize = 1;
+                
+                // Compute the closest power-of-two scale factor 
+                // and pass that to sBitmapOptionsCache.inSampleSize, which will
+                // result in faster decoding and better quality
+                sBitmapOptionsCache.inJustDecodeBounds = true;
+                BitmapFactory.decodeFileDescriptor(
+                        fd.getFileDescriptor(), null, sBitmapOptionsCache);
+                int nextWidth = sBitmapOptionsCache.outWidth >> 1;
+                int nextHeight = sBitmapOptionsCache.outHeight >> 1;
+                while (nextWidth>w && nextHeight>h) {
+                    sampleSize <<= 1;
+                    nextWidth >>= 1;
+                    nextHeight >>= 1;
+                }
+
+                sBitmapOptionsCache.inSampleSize = sampleSize;
+                sBitmapOptionsCache.inJustDecodeBounds = false;
+                Bitmap b = BitmapFactory.decodeFileDescriptor(
+                        fd.getFileDescriptor(), null, sBitmapOptionsCache);
+
+                if (b != null) {
+                    // finally rescale to exactly the size we need
+                    if (sBitmapOptionsCache.outWidth != w || sBitmapOptionsCache.outHeight != h) {
+                        Bitmap tmp = Bitmap.createScaledBitmap(b, w, h, true);
+                        // Bitmap.createScaledBitmap() can return the same bitmap
+                        if (tmp != b) b.recycle();
+                        b = tmp;
+                    }
+                }
+                
+                return b;
+            } catch (FileNotFoundException e) {
+            } finally {
+                try {
+                    if (fd != null)
+                        fd.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+        return null;
+    }
+    
+    /** Get album art for specified album. You should not pass in the album id
+     * for the "unknown" album here (use -1 instead)
+     */
+    public static Bitmap getArtwork(Context context, int album_id) {
+        return getArtwork(context, album_id, true);
+    }
+    
+    /** Get album art for specified album. You should not pass in the album id
+     * for the "unknown" album here (use -1 instead)
+     */
+    public static Bitmap getArtwork(Context context, int album_id, boolean allowDefault) {
+
+        if (album_id < 0) {
+            // This is something that is not in the database, so get the album art directly
+            // from the file.
+            Bitmap bm = getArtworkFromFile(context, null, -1);
+            if (bm != null) {
+                return bm;
+            }
+            if (allowDefault) {
+                return getDefaultArtwork(context);
+            } else {
+                return null;
+            }
+        }
+
+        ContentResolver res = context.getContentResolver();
+        Uri uri = ContentUris.withAppendedId(sArtworkUri, album_id);
+        if (uri != null) {
+            InputStream in = null;
+            try {
+                in = res.openInputStream(uri);
+                return BitmapFactory.decodeStream(in, null, sBitmapOptions);
+            } catch (FileNotFoundException ex) {
+                // The album art thumbnail does not actually exist. Maybe the user deleted it, or
+                // maybe it never existed to begin with.
+                Bitmap bm = getArtworkFromFile(context, null, album_id);
+                if (bm != null) {
+                    // Put the newly found artwork in the database.
+                    // Note that this shouldn't be done for the "unknown" album,
+                    // but if this method is called correctly, that won't happen.
+                    
+                    // first write it somewhere
+                    String file = Environment.getExternalStorageDirectory()
+                        + "/albumthumbs/" + String.valueOf(System.currentTimeMillis());
+                    if (ensureFileExists(file)) {
+                        try {
+                            OutputStream outstream = new FileOutputStream(file);
+                            if (bm.getConfig() == null) {
+                                bm = bm.copy(Bitmap.Config.RGB_565, false);
+                                if (bm == null) {
+                                    if (allowDefault) {
+                                        return getDefaultArtwork(context);
+                                    } else {
+                                        return null;
+                                    }
+                                }
+                            }
+                            boolean success = bm.compress(Bitmap.CompressFormat.JPEG, 75, outstream);
+                            outstream.close();
+                            if (success) {
+                                ContentValues values = new ContentValues();
+                                values.put("album_id", album_id);
+                                values.put("_data", file);
+                                Uri newuri = res.insert(sArtworkUri, values);
+                                if (newuri == null) {
+                                    // Failed to insert in to the database. The most likely
+                                    // cause of this is that the item already existed in the
+                                    // database, and the most likely cause of that is that
+                                    // the album was scanned before, but the user deleted the
+                                    // album art from the sd card.
+                                    // We can ignore that case here, since the media provider
+                                    // will regenerate the album art for those entries when
+                                    // it detects this.
+                                    success = false;
+                                }
+                            }
+                            if (!success) {
+                                File f = new File(file);
+                                f.delete();
+                            }
+                        } catch (FileNotFoundException e) {
+                            Log.e(TAG, "error creating file", e);
+                        } catch (IOException e) {
+                            Log.e(TAG, "error creating file", e);
+                        }
+                    }
+                } else if (allowDefault) {
+                    bm = getDefaultArtwork(context);
+                } else {
+                    bm = null;
+                }
+                return bm;
+            } finally {
+                try {
+                    if (in != null) {
+                        in.close();
+                    }
+                } catch (IOException ex) {
+                }
+            }
+        }
+        
+        return null;
+    }
+
+    // copied from MediaProvider
+    private static boolean ensureFileExists(String path) {
+        File file = new File(path);
+        if (file.exists()) {
+            return true;
+        } else {
+            // we will not attempt to create the first directory in the path
+            // (for example, do not create /sdcard if the SD card is not mounted)
+            int secondSlash = path.indexOf('/', 1);
+            if (secondSlash < 1) return false;
+            String directoryPath = path.substring(0, secondSlash);
+            File directory = new File(directoryPath);
+            if (!directory.exists())
+                return false;
+            file.getParentFile().mkdirs();
+            try {
+                return file.createNewFile();
+            } catch(IOException ioe) {
+                Log.d(TAG, "File creation failed for " + path);
+            }
+            return false;
+        }
+    }
+    
+    // get album art for specified file
+    private static final String sExternalMediaUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString();
+    private static Bitmap getArtworkFromFile(Context context, Uri uri, int albumid) {
+        Bitmap bm = null;
+        byte [] art = null;
+        String path = null;
+
+        if (sArtId == albumid) {
+            //Log.i("@@@@@@ ", "reusing cached data", new Exception());
+            if (mCachedBit != null) {
+                return mCachedBit;
+            }
+            art = mCachedArt;
+        } else {
+            // try reading embedded artwork
+            if (uri == null) {
+                try {
+                    int curalbum = sService.getAlbumId();
+                    if (curalbum == albumid || albumid < 0) {
+                        path = sService.getPath();
+                        if (path != null) {
+                            uri = Uri.parse(path);
+                        }
+                    }
+                } catch (RemoteException ex) {
+                    return null;
+                } catch (NullPointerException ex) {
+                    return null;
+                }
+            }
+            if (uri == null) {
+                if (albumid >= 0) {
+                    Cursor c = query(context,MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                            new String[] { MediaStore.Audio.Media._ID, MediaStore.Audio.Media.ALBUM },
+                            MediaStore.Audio.Media.ALBUM_ID + "=?", new String [] {String.valueOf(albumid)},
+                            null);
+                    if (c != null) {
+                        c.moveToFirst();
+                        if (!c.isAfterLast()) {
+                            int trackid = c.getInt(0);
+                            uri = ContentUris.withAppendedId(
+                                    MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, trackid);
+                        }
+                        if (c.getString(1).equals(MediaFile.UNKNOWN_STRING)) {
+                            albumid = -1;
+                        }
+                        c.close();
+                    }
+                }
+            }
+            if (uri != null) {
+                MediaScanner scanner = new MediaScanner(context);
+                ParcelFileDescriptor pfd = null;
+                try {
+                    pfd = context.getContentResolver().openFileDescriptor(uri, "r");
+                    if (pfd != null) {
+                        FileDescriptor fd = pfd.getFileDescriptor();
+                        art = scanner.extractAlbumArt(fd);
+                    }
+                } catch (IOException ex) {
+                } catch (SecurityException ex) {
+                } finally {
+                    try {
+                        if (pfd != null) {
+                            pfd.close();
+                        }
+                    } catch (IOException ex) {
+                    }
+                }
+            }
+        }
+        // if no embedded art exists, look for AlbumArt.jpg in same directory as the media file
+        if (art == null && path != null) {
+            if (path.startsWith(sExternalMediaUri)) {
+                // get the real path
+                Cursor c = query(context,Uri.parse(path),
+                        new String[] { MediaStore.Audio.Media.DATA},
+                        null, null, null);
+                if (c != null) {
+                    c.moveToFirst();
+                    if (!c.isAfterLast()) {
+                        path = c.getString(0);
+                    }
+                    c.close();
+                }
+            }
+            int lastSlash = path.lastIndexOf('/');
+            if (lastSlash > 0) {
+                String artPath = path.substring(0, lastSlash + 1) + "AlbumArt.jpg";
+                File file = new File(artPath);
+                if (file.exists()) {
+                    art = new byte[(int)file.length()];
+                    FileInputStream stream = null;
+                    try {
+                        stream = new FileInputStream(file);
+                        stream.read(art);
+                    } catch (IOException ex) {
+                        art = null;
+                    } finally {
+                        try {
+                            if (stream != null) {
+                                stream.close();
+                            }
+                        } catch (IOException ex) {
+                        }
+                    }
+                } else {
+                    // TODO: try getting album art from the web
+                }
+            }
+        }
+        
+        if (art != null) {
+            try {
+                // get the size of the bitmap
+                BitmapFactory.Options opts = new BitmapFactory.Options();
+                opts.inJustDecodeBounds = true;
+                opts.inSampleSize = 1;
+                BitmapFactory.decodeByteArray(art, 0, art.length, opts);
+                
+                // request a reasonably sized output image
+                // TODO: don't hardcode the size
+                while (opts.outHeight > 320 || opts.outWidth > 320) {
+                    opts.outHeight /= 2;
+                    opts.outWidth /= 2;
+                    opts.inSampleSize *= 2;
+                }
+                
+                // get the image for real now
+                opts.inJustDecodeBounds = false;
+                bm = BitmapFactory.decodeByteArray(art, 0, art.length, opts);
+                if (albumid != -1) {
+                    sArtId = albumid;
+                }
+                mCachedArt = art;
+                mCachedBit = bm;
+            } catch (Exception e) {
+            }
+        }
+        return bm;
+    }
+    
+    private static Bitmap getDefaultArtwork(Context context) {
+        BitmapFactory.Options opts = new BitmapFactory.Options();
+        opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
+        return BitmapFactory.decodeStream(
+                context.getResources().openRawResource(R.drawable.albumart_mp_unknown), null, opts);
+    }
+    
+    static int getIntPref(Context context, String name, int def) {
+        SharedPreferences prefs =
+            context.getSharedPreferences("com.android.music", Context.MODE_PRIVATE);
+        return prefs.getInt(name, def);
+    }
+    
+    static void setIntPref(Context context, String name, int value) {
+        SharedPreferences prefs =
+            context.getSharedPreferences("com.android.music", Context.MODE_PRIVATE);
+        Editor ed = prefs.edit();
+        ed.putInt(name, value);
+        ed.commit();
+    }
+
+    static void setRingtone(Context context, long id) {
+        ContentResolver resolver = context.getContentResolver();
+        // Set the flag in the database to mark this as a ringtone
+        Uri ringUri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
+        try {
+            ContentValues values = new ContentValues(2);
+            values.put(MediaStore.Audio.Media.IS_RINGTONE, "1");
+            values.put(MediaStore.Audio.Media.IS_ALARM, "1");
+            resolver.update(ringUri, values, null, null);
+        } catch (UnsupportedOperationException ex) {
+            // most likely the card just got unmounted
+            Log.e(TAG, "couldn't set ringtone flag for id " + id);
+            return;
+        }
+
+        String[] cols = new String[] {
+                MediaStore.Audio.Media._ID,
+                MediaStore.Audio.Media.DATA,
+                MediaStore.Audio.Media.TITLE
+        };
+
+        String where = MediaStore.Audio.Media._ID + "=" + id;
+        Cursor cursor = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                cols, where , null, null);
+        try {
+            if (cursor != null && cursor.getCount() == 1) {
+                // Set the system setting to make this the current ringtone
+                cursor.moveToFirst();
+                Settings.System.putString(resolver, Settings.System.RINGTONE, ringUri.toString());
+                String message = context.getString(R.string.ringtone_set, cursor.getString(2));
+                Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+    }
+}
diff --git a/src/com/android/music/PlaylistBrowserActivity.java b/src/com/android/music/PlaylistBrowserActivity.java
new file mode 100644
index 0000000..aa0525e
--- /dev/null
+++ b/src/com/android/music/PlaylistBrowserActivity.java
@@ -0,0 +1,605 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import java.text.Collator;
+import java.util.ArrayList;
+
+import android.app.ListActivity;
+import android.content.AsyncQueryHandler;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+
+import com.android.internal.database.ArrayListCursor;
+
+import android.database.Cursor;
+import android.database.MergeCursor;
+import android.database.sqlite.SQLiteException;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+
+public class PlaylistBrowserActivity extends ListActivity
+    implements View.OnCreateContextMenuListener, MusicUtils.Defs
+{
+    private static final String TAG = "PlaylistBrowserActivity";
+    private static final int DELETE_PLAYLIST = CHILD_MENU_BASE + 1;
+    private static final int EDIT_PLAYLIST = CHILD_MENU_BASE + 2;
+    private static final int RENAME_PLAYLIST = CHILD_MENU_BASE + 3;
+    private static final int CHANGE_WEEKS = CHILD_MENU_BASE + 4;
+    private static final long RECENTLY_ADDED_PLAYLIST = -1;
+    private static final long ALL_SONGS_PLAYLIST = -2;
+    private static final long PODCASTS_PLAYLIST = -3;
+    private PlaylistListAdapter mAdapter;
+    boolean mAdapterSent;
+
+    private boolean mCreateShortcut;
+
+    public PlaylistBrowserActivity()
+    {
+    }
+
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle icicle)
+    {
+        super.onCreate(icicle);
+
+        final Intent intent = getIntent();
+        final String action = intent.getAction();
+        if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) {
+            mCreateShortcut = true;
+        }
+
+        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+        setVolumeControlStream(AudioManager.STREAM_MUSIC);
+        MusicUtils.bindToService(this, new ServiceConnection() {
+            public void onServiceConnected(ComponentName classname, IBinder obj) {
+                if (Intent.ACTION_VIEW.equals(action)) {
+                    long id = Long.parseLong(intent.getExtras().getString("playlist"));
+                    if (id == RECENTLY_ADDED_PLAYLIST) {
+                        playRecentlyAdded();
+                    } else if (id == PODCASTS_PLAYLIST) {
+                        playPodcasts();
+                    } else if (id == ALL_SONGS_PLAYLIST) {
+                        int [] list = MusicUtils.getAllSongs(PlaylistBrowserActivity.this);
+                        if (list != null) {
+                            MusicUtils.playAll(PlaylistBrowserActivity.this, list, 0);
+                        }
+                    } else {
+                        MusicUtils.playPlaylist(PlaylistBrowserActivity.this, id);
+                    }
+                    finish();
+                }
+            }
+
+            public void onServiceDisconnected(ComponentName classname) {
+            }
+        
+        });
+        IntentFilter f = new IntentFilter();
+        f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
+        f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
+        f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
+        f.addDataScheme("file");
+        registerReceiver(mScanListener, f);
+
+        setContentView(R.layout.media_picker_activity);
+        ListView lv = getListView();
+        lv.setOnCreateContextMenuListener(this);
+        lv.setTextFilterEnabled(true);
+
+        mAdapter = (PlaylistListAdapter) getLastNonConfigurationInstance();
+        if (mAdapter == null) {
+            //Log.i("@@@", "starting query");
+            mAdapter = new PlaylistListAdapter(
+                    getApplication(),
+                    this,
+                    R.layout.track_list_item,
+                    mPlaylistCursor,
+                    new String[] { MediaStore.Audio.Playlists.NAME},
+                    new int[] { android.R.id.text1 });
+            setListAdapter(mAdapter);
+            setTitle(R.string.working_playlists);
+            getPlaylistCursor(mAdapter.getQueryHandler(), null);
+        } else {
+            mAdapter.setActivity(this);
+            setListAdapter(mAdapter);
+            mPlaylistCursor = mAdapter.getCursor();
+            // If mPlaylistCursor is null, this can be because it doesn't have
+            // a cursor yet (because the initial query that sets its cursor
+            // is still in progress), or because the query failed.
+            // In order to not flash the error dialog at the user for the
+            // first case, simply retry the query when the cursor is null.
+            // Worst case, we end up doing the same query twice.
+            if (mPlaylistCursor != null) {
+                init(mPlaylistCursor);
+            } else {
+                setTitle(R.string.working_playlists);
+                getPlaylistCursor(mAdapter.getQueryHandler(), null);
+            }
+        }
+    }
+    
+    @Override
+    public Object onRetainNonConfigurationInstance() {
+        PlaylistListAdapter a = mAdapter;
+        mAdapterSent = true;
+        return a;
+    }
+    
+    @Override
+    public void onDestroy() {
+        MusicUtils.unbindFromService(this);
+        if (!mAdapterSent) {
+            Cursor c = mAdapter.getCursor();
+            if (c != null) {
+                c.close();
+            }
+        }        
+        unregisterReceiver(mScanListener);
+        super.onDestroy();
+    }
+    
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        MusicUtils.setSpinnerState(this);
+    }
+    @Override
+    public void onPause() {
+        mReScanHandler.removeCallbacksAndMessages(null);
+        super.onPause();
+    }
+    private BroadcastReceiver mScanListener = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            MusicUtils.setSpinnerState(PlaylistBrowserActivity.this);
+            mReScanHandler.sendEmptyMessage(0);
+        }
+    };
+    
+    private Handler mReScanHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            getPlaylistCursor(mAdapter.getQueryHandler(), null);
+        }
+    };
+    public void init(Cursor cursor) {
+
+        mAdapter.changeCursor(cursor);
+
+        if (mPlaylistCursor == null) {
+            MusicUtils.displayDatabaseError(this);
+            closeContextMenu();
+            mReScanHandler.sendEmptyMessageDelayed(0, 1000);
+            return;
+        }
+
+        MusicUtils.hideDatabaseError(this);
+        setTitle();
+    }
+
+    private void setTitle() {
+        setTitle(R.string.playlists_title);
+    }
+    
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        if (!mCreateShortcut) {
+            menu.add(0, GOTO_START, 0, R.string.goto_start).setIcon(
+                    R.drawable.ic_menu_music_library);
+            menu.add(0, GOTO_PLAYBACK, 0, R.string.goto_playback).setIcon(
+                    R.drawable.ic_menu_playback).setVisible(MusicUtils.isMusicLoaded());
+        }
+        return super.onCreateOptionsMenu(menu);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        Intent intent;
+        switch (item.getItemId()) {
+            case GOTO_START:
+                intent = new Intent();
+                intent.setClass(this, MusicBrowserActivity.class);
+                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                startActivity(intent);
+                return true;
+
+            case GOTO_PLAYBACK:
+                intent = new Intent("com.android.music.PLAYBACK_VIEWER");
+                startActivity(intent);
+                return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+    
+    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
+        if (mCreateShortcut) {
+            return;
+        }
+
+        AdapterContextMenuInfo mi = (AdapterContextMenuInfo) menuInfoIn;
+
+        menu.add(0, PLAY_SELECTION, 0, R.string.play_selection);
+
+        if (mi.id >= 0 /*|| mi.id == PODCASTS_PLAYLIST*/) {
+            menu.add(0, DELETE_PLAYLIST, 0, R.string.delete_playlist_menu);
+        }
+
+        if (mi.id == RECENTLY_ADDED_PLAYLIST) {
+            menu.add(0, EDIT_PLAYLIST, 0, R.string.edit_playlist_menu);
+        }
+
+        if (mi.id >= 0) {
+            menu.add(0, RENAME_PLAYLIST, 0, R.string.rename_playlist_menu);
+        }
+
+        mPlaylistCursor.moveToPosition(mi.position);
+        menu.setHeaderTitle(mPlaylistCursor.getString(mPlaylistCursor.getColumnIndexOrThrow(
+                MediaStore.Audio.Playlists.NAME)));
+    }
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        AdapterContextMenuInfo mi = (AdapterContextMenuInfo) item.getMenuInfo();
+        switch (item.getItemId()) {
+            case PLAY_SELECTION:
+                if (mi.id == RECENTLY_ADDED_PLAYLIST) {
+                    playRecentlyAdded();
+                } else if (mi.id == PODCASTS_PLAYLIST) {
+                    playPodcasts();
+                } else {
+                    MusicUtils.playPlaylist(this, mi.id);
+                }
+                break;
+            case DELETE_PLAYLIST:
+                Uri uri = ContentUris.withAppendedId(
+                        MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, mi.id);
+                getContentResolver().delete(uri, null, null);
+                Toast.makeText(this, R.string.playlist_deleted_message, Toast.LENGTH_SHORT).show();
+                if (mPlaylistCursor.getCount() == 0) {
+                    setTitle(R.string.no_playlists_title);
+                }
+                break;
+            case EDIT_PLAYLIST:
+                if (mi.id == RECENTLY_ADDED_PLAYLIST) {
+                    Intent intent = new Intent();
+                    intent.setClass(this, WeekSelector.class);
+                    startActivityForResult(intent, CHANGE_WEEKS);
+                    return true;
+                } else {
+                    Log.e(TAG, "should not be here");
+                }
+                break;
+            case RENAME_PLAYLIST:
+                Intent intent = new Intent();
+                intent.setClass(this, RenamePlaylist.class);
+                intent.putExtra("rename", mi.id);
+                startActivityForResult(intent, RENAME_PLAYLIST);
+                break;
+        }
+        return true;
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+        switch (requestCode) {
+            case SCAN_DONE:
+                if (resultCode == RESULT_CANCELED) {
+                    finish();
+                } else {
+                    getPlaylistCursor(mAdapter.getQueryHandler(), null);
+                }
+                break;
+        }
+    }
+
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id)
+    {
+        if (mCreateShortcut) {
+            final Intent shortcut = new Intent();
+            shortcut.setAction(Intent.ACTION_VIEW);
+            shortcut.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/playlist");
+            shortcut.putExtra("playlist", String.valueOf(id));
+
+            final Intent intent = new Intent();
+            intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcut);
+            intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, ((TextView) v.findViewById(R.id.line1)).getText());
+            intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(
+                    this, R.drawable.app_music));
+
+            setResult(RESULT_OK, intent);
+            finish();
+            return;
+        }
+        if (id == RECENTLY_ADDED_PLAYLIST) {
+            Intent intent = new Intent(Intent.ACTION_PICK);
+            intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
+            intent.putExtra("playlist", "recentlyadded");
+            startActivity(intent);
+        } else if (id == PODCASTS_PLAYLIST) {
+            Intent intent = new Intent(Intent.ACTION_PICK);
+            intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
+            intent.putExtra("playlist", "podcasts");
+            startActivity(intent);
+        } else {
+            Intent intent = new Intent(Intent.ACTION_EDIT);
+            intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
+            intent.putExtra("playlist", Long.valueOf(id).toString());
+            startActivity(intent);
+        }
+    }
+
+    private void playRecentlyAdded() {
+        // do a query for all songs added in the last X weeks
+        int X = MusicUtils.getIntPref(this, "numweeks", 2) * (3600 * 24 * 7);
+        final String[] ccols = new String[] { MediaStore.Audio.Media._ID};
+        String where = MediaStore.MediaColumns.DATE_ADDED + ">" + (System.currentTimeMillis() / 1000 - X);
+        Cursor cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                ccols, where, null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
+        
+        if (cursor == null) {
+            // Todo: show a message
+            return;
+        }
+        try {
+            int len = cursor.getCount();
+            int [] list = new int[len];
+            for (int i = 0; i < len; i++) {
+                cursor.moveToNext();
+                list[i] = cursor.getInt(0);
+            }
+            MusicUtils.playAll(this, list, 0);
+        } catch (SQLiteException ex) {
+        } finally {
+            cursor.close();
+        }
+    }
+
+    private void playPodcasts() {
+        // do a query for all files that are podcasts
+        final String[] ccols = new String[] { MediaStore.Audio.Media._ID};
+        Cursor cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                ccols, MediaStore.Audio.Media.IS_PODCAST + "=1",
+                null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
+        
+        if (cursor == null) {
+            // Todo: show a message
+            return;
+        }
+        try {
+            int len = cursor.getCount();
+            int [] list = new int[len];
+            for (int i = 0; i < len; i++) {
+                cursor.moveToNext();
+                list[i] = cursor.getInt(0);
+            }
+            MusicUtils.playAll(this, list, 0);
+        } catch (SQLiteException ex) {
+        } finally {
+            cursor.close();
+        }
+    }
+
+    
+    String[] mCols = new String[] {
+            MediaStore.Audio.Playlists._ID,
+            MediaStore.Audio.Playlists.NAME
+    };
+
+    private Cursor getPlaylistCursor(AsyncQueryHandler async, String filterstring) {
+
+        StringBuilder where = new StringBuilder();
+        where.append(MediaStore.Audio.Playlists.NAME + " != ''");
+        
+        // Add in the filtering constraints
+        String [] keywords = null;
+        if (filterstring != null) {
+            String [] searchWords = filterstring.split(" ");
+            keywords = new String[searchWords.length];
+            Collator col = Collator.getInstance();
+            col.setStrength(Collator.PRIMARY);
+            for (int i = 0; i < searchWords.length; i++) {
+                keywords[i] = '%' + searchWords[i] + '%';
+            }
+            for (int i = 0; i < searchWords.length; i++) {
+                where.append(" AND ");
+                where.append(MediaStore.Audio.Playlists.NAME + " LIKE ?");
+            }
+        }
+        
+        String whereclause = where.toString();
+        
+        
+        if (async != null) {
+            async.startQuery(0, null, MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+                    mCols, whereclause, keywords, MediaStore.Audio.Playlists.NAME);
+            return null;
+        }
+        Cursor c = null;
+        c = MusicUtils.query(this, MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+                mCols, whereclause, keywords, MediaStore.Audio.Playlists.NAME);
+        
+        return mergedCursor(c);
+    }
+    
+    private Cursor mergedCursor(Cursor c) {
+        if (c == null) {
+            return null;
+        }
+        if (c instanceof MergeCursor) {
+            // this shouldn't happen, but fail gracefully
+            Log.d("PlaylistBrowserActivity", "Already wrapped");
+            return c;
+        }
+        ArrayList<ArrayList> autoplaylists = new ArrayList<ArrayList>();
+        if (mCreateShortcut) {
+            ArrayList<Object> all = new ArrayList<Object>(2);
+            all.add(ALL_SONGS_PLAYLIST);
+            all.add(getString(R.string.play_all));
+            autoplaylists.add(all);
+        }
+        ArrayList<Object> recent = new ArrayList<Object>(2);
+        recent.add(RECENTLY_ADDED_PLAYLIST);
+        recent.add(getString(R.string.recentlyadded));
+        autoplaylists.add(recent);
+        
+        // check if there are any podcasts
+        Cursor counter = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                new String[] {"count(*)"}, "is_podcast=1", null, null);
+        if (counter != null) {
+            counter.moveToFirst();
+            int numpodcasts = counter.getInt(0);
+            counter.close();
+            if (numpodcasts > 0) {
+                ArrayList<Object> podcasts = new ArrayList<Object>(2);
+                podcasts.add(PODCASTS_PLAYLIST);
+                podcasts.add(getString(R.string.podcasts_listitem));
+                autoplaylists.add(podcasts);
+            }
+        }
+
+        ArrayListCursor autoplaylistscursor = new ArrayListCursor(mCols, autoplaylists);
+        
+        Cursor cc = new MergeCursor(new Cursor [] {autoplaylistscursor, c});
+        return cc;
+    }
+    
+    static class PlaylistListAdapter extends SimpleCursorAdapter {
+        int mTitleIdx;
+        int mIdIdx;
+        private PlaylistBrowserActivity mActivity = null;
+        private AsyncQueryHandler mQueryHandler;
+        private String mConstraint = null;
+        private boolean mConstraintIsValid = false;
+
+        class QueryHandler extends AsyncQueryHandler {
+            QueryHandler(ContentResolver res) {
+                super(res);
+            }
+            
+            @Override
+            protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+                //Log.i("@@@", "query complete: " + cursor.getCount() + "   " + mActivity);
+                if (cursor != null) {
+                    cursor = mActivity.mergedCursor(cursor);
+                }
+                mActivity.init(cursor);
+            }
+        }
+
+        PlaylistListAdapter(Context context, PlaylistBrowserActivity currentactivity,
+                int layout, Cursor cursor, String[] from, int[] to) {
+            super(context, layout, cursor, from, to);
+            mActivity = currentactivity;
+            getColumnIndices(cursor);
+            mQueryHandler = new QueryHandler(context.getContentResolver());
+        }
+        private void getColumnIndices(Cursor cursor) {
+            if (cursor != null) {
+                mTitleIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.NAME);
+                mIdIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists._ID);
+            }
+        }
+
+        public void setActivity(PlaylistBrowserActivity newactivity) {
+            mActivity = newactivity;
+        }
+        
+        public AsyncQueryHandler getQueryHandler() {
+            return mQueryHandler;
+        }
+
+        @Override
+        public void bindView(View view, Context context, Cursor cursor) {
+            
+            TextView tv = (TextView) view.findViewById(R.id.line1);
+            
+            String name = cursor.getString(mTitleIdx);
+            tv.setText(name);
+            
+            long id = cursor.getLong(mIdIdx);
+            
+            ImageView iv = (ImageView) view.findViewById(R.id.icon);
+            if (id == RECENTLY_ADDED_PLAYLIST) {
+                iv.setImageResource(R.drawable.ic_mp_playlist_recently_added_list);
+            } else {
+                iv.setImageResource(R.drawable.ic_mp_playlist_list);
+            }
+            ViewGroup.LayoutParams p = iv.getLayoutParams();
+            p.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+            p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+
+            iv = (ImageView) view.findViewById(R.id.play_indicator);
+            iv.setVisibility(View.GONE);
+
+            view.findViewById(R.id.line2).setVisibility(View.GONE);
+        }
+
+        @Override
+        public void changeCursor(Cursor cursor) {
+            if (cursor != mActivity.mPlaylistCursor) {
+                mActivity.mPlaylistCursor = cursor;
+                super.changeCursor(cursor);
+                getColumnIndices(cursor);
+            }
+        }
+        
+        @Override
+        public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
+            String s = constraint.toString();
+            if (mConstraintIsValid && (
+                    (s == null && mConstraint == null) ||
+                    (s != null && s.equals(mConstraint)))) {
+                return getCursor();
+            }
+            Cursor c = mActivity.getPlaylistCursor(null, s);
+            mConstraint = s;
+            mConstraintIsValid = true;
+            return c;
+        }
+    }
+    
+    private Cursor mPlaylistCursor;
+}
+
diff --git a/src/com/android/music/QueryBrowserActivity.java b/src/com/android/music/QueryBrowserActivity.java
new file mode 100644
index 0000000..2a482ae
--- /dev/null
+++ b/src/com/android/music/QueryBrowserActivity.java
@@ -0,0 +1,421 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.app.ListActivity;
+import android.app.SearchManager;
+import android.content.AsyncQueryHandler;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.media.AudioManager;
+import android.media.MediaFile;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.MediaStore;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.ViewGroup.OnHierarchyChangeListener;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+public class QueryBrowserActivity extends ListActivity implements MusicUtils.Defs
+{
+    private final static int PLAY_NOW = 0;
+    private final static int ADD_TO_QUEUE = 1;
+    private final static int PLAY_NEXT = 2;
+    private final static int PLAY_ARTIST = 3;
+    private final static int EXPLORE_ARTIST = 4;
+    private final static int PLAY_ALBUM = 5;
+    private final static int EXPLORE_ALBUM = 6;
+    private final static int REQUERY = 3;
+    private QueryListAdapter mAdapter;
+    private boolean mAdapterSent;
+    private String mFilterString = "";
+
+    public QueryBrowserActivity()
+    {
+    }
+
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle icicle)
+    {
+        super.onCreate(icicle);
+        setVolumeControlStream(AudioManager.STREAM_MUSIC);
+        MusicUtils.bindToService(this);
+        IntentFilter f = new IntentFilter();
+        f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
+        f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
+        f.addDataScheme("file");
+        registerReceiver(mScanListener, f);
+        
+        if (icicle == null) {
+            Intent intent = getIntent();
+            
+            if (intent.getAction().equals(Intent.ACTION_VIEW)) {
+                // this is something we got from the search bar
+                Uri uri = intent.getData();
+                String path = uri.toString();
+                if (path.startsWith("content://media/external/audio/media/")) {
+                    // This is a specific file
+                    String id = uri.getLastPathSegment();
+                    int [] list = new int[] { Integer.valueOf(id) };
+                    MusicUtils.playAll(this, list, 0);
+                    finish();
+                    return;
+                } else if (path.startsWith("content://media/external/audio/albums/")) {
+                    // This is an album, show the songs on it
+                    Intent i = new Intent(Intent.ACTION_PICK);
+                    i.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
+                    i.putExtra("album", uri.getLastPathSegment());
+                    startActivity(i);
+                    finish();
+                    return;
+                } else if (path.startsWith("content://media/external/audio/artists/")) {
+                    // This is an artist, show the albums for that artist
+                    Intent i = new Intent(Intent.ACTION_PICK);
+                    i.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album");
+                    i.putExtra("artist", uri.getLastPathSegment());
+                    startActivity(i);
+                    finish();
+                    return;
+                }
+            }
+            mFilterString = intent.getStringExtra(SearchManager.QUERY);
+        }
+
+        setContentView(R.layout.query_activity);
+        mTrackList = getListView();
+        mTrackList.setTextFilterEnabled(true);
+        mAdapter = (QueryListAdapter) getLastNonConfigurationInstance();
+        if (mAdapter == null) {
+            mAdapter = new QueryListAdapter(
+                    getApplication(),
+                    this,
+                    R.layout.track_list_item,
+                    null, // cursor
+                    new String[] {},
+                    new int[] {});
+            setListAdapter(mAdapter);
+            if (TextUtils.isEmpty(mFilterString)) {
+                getQueryCursor(mAdapter.getQueryHandler(), null);
+            } else {
+                mTrackList.setFilterText(mFilterString);
+                mFilterString = null;
+            }
+        } else {
+            mAdapter.setActivity(this);
+            setListAdapter(mAdapter);
+            mQueryCursor = mAdapter.getCursor();
+            if (mQueryCursor != null) {
+                init(mQueryCursor);
+            } else {
+                getQueryCursor(mAdapter.getQueryHandler(), mFilterString);
+            }
+        }
+    }
+    
+    @Override
+    public Object onRetainNonConfigurationInstance() {
+        mAdapterSent = true;
+        return mAdapter;
+    }
+    
+    @Override
+    public void onPause() {
+        mReScanHandler.removeCallbacksAndMessages(null);
+        super.onPause();
+    }
+
+    @Override
+    public void onDestroy() {
+        MusicUtils.unbindFromService(this);
+        unregisterReceiver(mScanListener);
+        super.onDestroy();
+        if (!mAdapterSent && mAdapter != null) {
+            Cursor c = mAdapter.getCursor();
+            if (c != null) {
+                c.close();
+            }
+        }
+    }
+    
+    /*
+     * This listener gets called when the media scanner starts up, and when the
+     * sd card is unmounted.
+     */
+    private BroadcastReceiver mScanListener = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            MusicUtils.setSpinnerState(QueryBrowserActivity.this);
+            mReScanHandler.sendEmptyMessage(0);
+        }
+    };
+    
+    private Handler mReScanHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            getQueryCursor(mAdapter.getQueryHandler(), null);
+            // if the query results in a null cursor, onQueryComplete() will
+            // call init(), which will post a delayed message to this handler
+            // in order to try again.
+        }
+    };
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+        switch (requestCode) {
+            case SCAN_DONE:
+                if (resultCode == RESULT_CANCELED) {
+                    finish();
+                } else {
+                    getQueryCursor(mAdapter.getQueryHandler(), null);
+                }
+                break;
+        }
+    }
+    
+    public void init(Cursor c) {
+        
+        mAdapter.changeCursor(c);
+
+        if (mQueryCursor == null) {
+            MusicUtils.displayDatabaseError(this);
+            setListAdapter(null);
+            mReScanHandler.sendEmptyMessageDelayed(0, 1000);
+            return;
+        }
+        MusicUtils.hideDatabaseError(this);
+    }
+    
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id)
+    {
+        // Dialog doesn't allow us to wait for a result, so we need to store
+        // the info we need for when the dialog posts its result
+        mQueryCursor.moveToPosition(position);
+        if (mQueryCursor.isBeforeFirst() || mQueryCursor.isAfterLast()) {
+            return;
+        }
+        String selectedType = mQueryCursor.getString(mQueryCursor.getColumnIndexOrThrow(
+                MediaStore.Audio.Media.MIME_TYPE));
+        
+        if ("artist".equals(selectedType)) {
+            Intent intent = new Intent(Intent.ACTION_PICK);
+            intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album");
+            intent.putExtra("artist", Long.valueOf(id).toString());
+            startActivity(intent);
+        } else if ("album".equals(selectedType)) {
+            Intent intent = new Intent(Intent.ACTION_PICK);
+            intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
+            intent.putExtra("album", Long.valueOf(id).toString());
+            startActivity(intent);
+        } else if (position >= 0 && id >= 0){
+            int [] list = new int[] { (int) id };
+            MusicUtils.playAll(this, list, 0);
+        } else {
+            Log.e("QueryBrowser", "invalid position/id: " + position + "/" + id);
+        }
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case USE_AS_RINGTONE: {
+                // Set the system setting to make this the current ringtone
+                MusicUtils.setRingtone(this, mTrackList.getSelectedItemId());
+                return true;
+            }
+
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    private Cursor getQueryCursor(AsyncQueryHandler async, String filter) {
+        if (filter == null) {
+            filter = "";
+        }
+        String[] ccols = new String[] {
+                "_id",   // this will be the artist, album or track ID
+                MediaStore.Audio.Media.MIME_TYPE, // mimetype of audio file, or "artist" or "album"
+                SearchManager.SUGGEST_COLUMN_TEXT_1,
+                "data1",
+                "data2"
+        };
+
+        Uri search = Uri.parse("content://media/external/audio/" + 
+                SearchManager.SUGGEST_URI_PATH_QUERY + "/" + Uri.encode(filter));
+        
+        Cursor ret = null;
+        if (async != null) {
+            async.startQuery(0, null, search, ccols, null, null, null);
+        } else {
+            ret = MusicUtils.query(this, search, ccols, null, null, null);
+        }
+        return ret;
+    }
+    
+    static class QueryListAdapter extends SimpleCursorAdapter {
+        private QueryBrowserActivity mActivity = null;
+        private AsyncQueryHandler mQueryHandler;
+        private String mConstraint = null;
+        private boolean mConstraintIsValid = false;
+
+        class QueryHandler extends AsyncQueryHandler {
+            QueryHandler(ContentResolver res) {
+                super(res);
+            }
+            
+            @Override
+            protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+                mActivity.init(cursor);
+            }
+        }
+
+        QueryListAdapter(Context context, QueryBrowserActivity currentactivity,
+                int layout, Cursor cursor, String[] from, int[] to) {
+            super(context, layout, cursor, from, to);
+            mActivity = currentactivity;
+            mQueryHandler = new QueryHandler(context.getContentResolver());
+        }
+
+        public void setActivity(QueryBrowserActivity newactivity) {
+            mActivity = newactivity;
+        }
+        
+        public AsyncQueryHandler getQueryHandler() {
+            return mQueryHandler;
+        }
+
+        @Override
+        public void bindView(View view, Context context, Cursor cursor) {
+            
+            TextView tv1 = (TextView) view.findViewById(R.id.line1);
+            TextView tv2 = (TextView) view.findViewById(R.id.line2);
+            ImageView iv = (ImageView) view.findViewById(R.id.icon);
+            ViewGroup.LayoutParams p = iv.getLayoutParams();
+            if (p == null) {
+                // seen this happen, not sure why
+                DatabaseUtils.dumpCursor(cursor);
+                return;
+            }
+            p.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+            p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+            
+            String mimetype = cursor.getString(cursor.getColumnIndexOrThrow(
+                    MediaStore.Audio.Media.MIME_TYPE));
+            
+            if (mimetype == null) {
+                mimetype = "audio/";
+            }
+            if (mimetype.equals("artist")) {
+                iv.setImageResource(R.drawable.ic_mp_artist_list);
+                String name = cursor.getString(cursor.getColumnIndexOrThrow(
+                        SearchManager.SUGGEST_COLUMN_TEXT_1));
+                String displayname = name;
+                if (name.equals(MediaFile.UNKNOWN_STRING)) {
+                    displayname = context.getString(R.string.unknown_artist_name);
+                }
+                tv1.setText(displayname);
+
+                int numalbums = cursor.getInt(cursor.getColumnIndexOrThrow("data1"));
+                int numsongs = cursor.getInt(cursor.getColumnIndexOrThrow("data2"));
+                
+                String songs_albums = MusicUtils.makeAlbumsSongsLabel(context,
+                        numalbums, numsongs, name.equals(MediaFile.UNKNOWN_STRING));
+                
+                tv2.setText(songs_albums);
+            
+            } else if (mimetype.equals("album")) {
+                iv.setImageResource(R.drawable.albumart_mp_unknown_list);
+                String name = cursor.getString(cursor.getColumnIndexOrThrow(
+                        SearchManager.SUGGEST_COLUMN_TEXT_1));
+                String displayname = name;
+                if (name.equals(MediaFile.UNKNOWN_STRING)) {
+                    displayname = context.getString(R.string.unknown_album_name);
+                }
+                tv1.setText(displayname);
+                
+                name = cursor.getString(cursor.getColumnIndexOrThrow("data1"));
+                displayname = name;
+                if (name.equals(MediaFile.UNKNOWN_STRING)) {
+                    displayname = context.getString(R.string.unknown_artist_name);
+                }
+                tv2.setText(displayname);
+                
+            } else if(mimetype.startsWith("audio/") ||
+                    mimetype.equals("application/ogg") ||
+                    mimetype.equals("application/x-ogg")) {
+                iv.setImageResource(R.drawable.ic_mp_song_list);
+                String name = cursor.getString(cursor.getColumnIndexOrThrow(
+                        SearchManager.SUGGEST_COLUMN_TEXT_1));
+                tv1.setText(name);
+
+                String displayname = cursor.getString(cursor.getColumnIndexOrThrow("data1"));
+                if (name.equals(MediaFile.UNKNOWN_STRING)) {
+                    displayname = context.getString(R.string.unknown_artist_name);
+                }
+                name = cursor.getString(cursor.getColumnIndexOrThrow("data2"));
+                if (name.equals(MediaFile.UNKNOWN_STRING)) {
+                    name = context.getString(R.string.unknown_artist_name);
+                }
+                tv2.setText(displayname + " - " + name);
+            }
+        }
+        @Override
+        public void changeCursor(Cursor cursor) {
+            if (cursor != mActivity.mQueryCursor) {
+                mActivity.mQueryCursor = cursor;
+                super.changeCursor(cursor);
+            }
+        }
+        @Override
+        public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
+            String s = constraint.toString();
+            if (mConstraintIsValid && (
+                    (s == null && mConstraint == null) ||
+                    (s != null && s.equals(mConstraint)))) {
+                return getCursor();
+            }
+            Cursor c = mActivity.getQueryCursor(null, s);
+            mConstraint = s;
+            mConstraintIsValid = true;
+            return c;
+        }
+    }
+
+    private ListView mTrackList;
+    private Cursor mQueryCursor;
+}
+
diff --git a/src/com/android/music/RenamePlaylist.java b/src/com/android/music/RenamePlaylist.java
new file mode 100644
index 0000000..f81e2af
--- /dev/null
+++ b/src/com/android/music/RenamePlaylist.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+public class RenamePlaylist extends Activity
+{
+    private EditText mPlaylist;
+    private TextView mPrompt;
+    private Button mSaveButton;
+    private long mRenameId;
+    private String mOriginalName;
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+        setContentView(R.layout.create_playlist);
+        getWindow().setLayout(WindowManager.LayoutParams.FILL_PARENT,
+                                    WindowManager.LayoutParams.WRAP_CONTENT);
+
+        mPrompt = (TextView)findViewById(R.id.prompt);
+        mPlaylist = (EditText)findViewById(R.id.playlist);
+        mSaveButton = (Button) findViewById(R.id.create);
+        mSaveButton.setOnClickListener(mOpenClicked);
+
+        ((Button)findViewById(R.id.cancel)).setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                finish();
+            }
+        });
+
+        mRenameId = icicle != null ? icicle.getLong("rename")
+                : getIntent().getLongExtra("rename", -1);
+        mOriginalName = nameForId(mRenameId);
+        String defaultname = icicle != null ? icicle.getString("defaultname") : mOriginalName;
+        
+        if (mRenameId < 0 || mOriginalName == null || defaultname == null) {
+            Log.i("@@@@", "Rename failed: " + mRenameId + "/" + defaultname);
+            finish();
+            return;
+        }
+        
+        String promptformat;
+        if (mOriginalName.equals(defaultname)) {
+            promptformat = getString(R.string.rename_playlist_same_prompt);
+        } else {
+            promptformat = getString(R.string.rename_playlist_diff_prompt);
+        }
+                
+        String prompt = String.format(promptformat, mOriginalName, defaultname);
+        mPrompt.setText(prompt);
+        mPlaylist.setText(defaultname);
+        mPlaylist.setSelection(defaultname.length());
+        mPlaylist.addTextChangedListener(mTextWatcher);
+        setSaveButton();
+    }
+    
+    TextWatcher mTextWatcher = new TextWatcher() {
+        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+            // don't care about this one
+        }
+        public void onTextChanged(CharSequence s, int start, int before, int count) {
+            // check if playlist with current name exists already, and warn the user if so.
+            setSaveButton();
+        };
+        public void afterTextChanged(Editable s) {
+            // don't care about this one
+        }
+    };
+    
+    private void setSaveButton() {
+        String typedname = mPlaylist.getText().toString(); 
+        if (idForplaylist(typedname) >= 0
+                && ! mOriginalName.equals(typedname)) {
+            mSaveButton.setText(R.string.create_playlist_overwrite_text);
+        } else {
+            mSaveButton.setText(R.string.create_playlist_create_text);
+        }
+    }
+    
+    private int idForplaylist(String name) {
+        Cursor c = MusicUtils.query(this, MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+                new String[] { MediaStore.Audio.Playlists._ID },
+                MediaStore.Audio.Playlists.NAME + "=?",
+                new String[] { name },
+                MediaStore.Audio.Playlists.NAME);
+        int id = -1;
+        if (c != null) {
+            c.moveToFirst();
+            if (!c.isAfterLast()) {
+                id = c.getInt(0);
+            }
+        }
+        c.close();
+        return id;
+    }
+    
+    private String nameForId(long id) {
+        Cursor c = MusicUtils.query(this, MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+                new String[] { MediaStore.Audio.Playlists.NAME },
+                MediaStore.Audio.Playlists._ID + "=?",
+                new String[] { Long.valueOf(id).toString() },
+                MediaStore.Audio.Playlists.NAME);
+        String name = null;
+        if (c != null) {
+            c.moveToFirst();
+            if (!c.isAfterLast()) {
+                name = c.getString(0);
+            }
+        }
+        c.close();
+        return name;
+    }
+    
+    
+    @Override
+    public void onSaveInstanceState(Bundle outcicle) {
+        outcicle.putString("defaultname", mPlaylist.getText().toString());
+        outcicle.putLong("rename", mRenameId);
+    }
+    
+    @Override
+    public void onResume() {
+        super.onResume();
+    }
+
+    private View.OnClickListener mOpenClicked = new View.OnClickListener() {
+        public void onClick(View v) {
+            String name = mPlaylist.getText().toString();
+            if (name != null && name.length() > 0) {
+                ContentResolver resolver = getContentResolver();
+                ContentValues values = new ContentValues(1);
+                values.put(MediaStore.Audio.Playlists.NAME, name);
+                resolver.update(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+                        values,
+                        MediaStore.Audio.Playlists._ID + "=?",
+                        new String[] { Long.valueOf(mRenameId).toString()});
+                
+                setResult(RESULT_OK);
+                Toast.makeText(RenamePlaylist.this, R.string.playlist_renamed_message, Toast.LENGTH_SHORT).show();
+                finish();
+            }
+        }
+    };
+}
diff --git a/src/com/android/music/RepeatingImageButton.java b/src/com/android/music/RepeatingImageButton.java
new file mode 100644
index 0000000..08c951c
--- /dev/null
+++ b/src/com/android/music/RepeatingImageButton.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.content.Context;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.ImageButton;
+
+/**
+ * A button that will repeatedly call a 'listener' method
+ * as long as the button is pressed.
+ */
+public class RepeatingImageButton extends ImageButton {
+
+    private long mStartTime;
+    private int mRepeatCount;
+    private RepeatListener mListener;
+    private long mInterval = 500;
+    
+    public RepeatingImageButton(Context context) {
+        this(context, null);
+    }
+
+    public RepeatingImageButton(Context context, AttributeSet attrs) {
+        this(context, attrs, android.R.attr.imageButtonStyle);
+    }
+
+    public RepeatingImageButton(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        setFocusable(true);
+        setLongClickable(true);
+    }
+    
+    /**
+     * Sets the listener to be called while the button is pressed and
+     * the interval in milliseconds with which it will be called.
+     * @param l The listener that will be called
+     * @param interval The interval in milliseconds for calls 
+     */
+    public void setRepeatListener(RepeatListener l, long interval) {
+        mListener = l;
+        mInterval = interval;
+    }
+    
+    @Override
+    public boolean performLongClick() {
+        mStartTime = SystemClock.elapsedRealtime();
+        mRepeatCount = 0;
+        post(mRepeater);
+        return true;
+    }
+    
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_UP) {
+            // remove the repeater, but call the hook one more time
+            removeCallbacks(mRepeater);
+            if (mStartTime != 0) {
+                doRepeat(true);
+                mStartTime = 0;
+            }
+        }
+        return super.onTouchEvent(event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+        case KeyEvent.KEYCODE_DPAD_CENTER:
+        case KeyEvent.KEYCODE_ENTER:
+            // remove the repeater, but call the hook one more time
+            removeCallbacks(mRepeater);
+            if (mStartTime != 0) {
+                doRepeat(true);
+                mStartTime = 0;
+            }
+        }
+        return super.onKeyUp(keyCode, event);
+    }
+    
+    private Runnable mRepeater = new Runnable() {
+        public void run() {
+            doRepeat(false);
+            if (isPressed()) {
+                postDelayed(this, mInterval);
+            }
+        }
+    };
+
+    private  void doRepeat(boolean last) {
+        long now = SystemClock.elapsedRealtime();
+        if (mListener != null) {
+            mListener.onRepeat(this, now - mStartTime, last ? -1 : mRepeatCount++);
+        }
+    }
+    
+    public interface RepeatListener {
+        /**
+         * This method will be called repeatedly at roughly the interval
+         * specified in setRepeatListener(), for as long as the button
+         * is pressed.
+         * @param v The button as a View.
+         * @param duration The number of milliseconds the button has been pressed so far.
+         * @param repeatcount The number of previous calls in this sequence.
+         * If this is going to be the last call in this sequence (i.e. the user
+         * just stopped pressing the button), the value will be -1.  
+         */
+        void onRepeat(View v, long duration, int repeatcount);
+    }
+}
diff --git a/src/com/android/music/ScanningProgress.java b/src/com/android/music/ScanningProgress.java
new file mode 100644
index 0000000..e5eae94
--- /dev/null
+++ b/src/com/android/music/ScanningProgress.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.app.Activity;
+import android.database.Cursor;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.MediaStore;
+import android.view.Window;
+import android.view.WindowManager;
+
+public class ScanningProgress extends Activity
+{
+    private final static int CHECK = 0;
+    private Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg)
+        {
+            if (msg.what == CHECK) {
+                String status = Environment.getExternalStorageState();
+                if (!status.equals(Environment.MEDIA_MOUNTED)) {
+                    // If the card suddenly got unmounted again, there's
+                    // really no need to keep waiting for the media scanner.
+                    finish();
+                    return;
+                }
+                Cursor c = MusicUtils.query(ScanningProgress.this,
+                        MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+                        null, null, null, null);
+                if (c != null) {
+                    // The external media database is now ready for querying
+                    // (though it may still be in the process of being filled).
+                    c.close();
+                    setResult(RESULT_OK);
+                    finish();
+                    return;
+                }
+                Message next = obtainMessage(CHECK);
+                sendMessageDelayed(next, 3000);
+            }
+        }
+    };
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+        setContentView(R.layout.scanning);
+        getWindow().setLayout(WindowManager.LayoutParams.WRAP_CONTENT,
+                                    WindowManager.LayoutParams.WRAP_CONTENT);
+        setResult(RESULT_CANCELED);
+        
+        Message msg = mHandler.obtainMessage(CHECK);
+        mHandler.sendMessageDelayed(msg, 1000);
+    }
+    
+    @Override
+    public void onDestroy() {
+        mHandler.removeMessages(CHECK);
+        super.onDestroy();
+    }
+}
diff --git a/src/com/android/music/StreamStarter.java b/src/com/android/music/StreamStarter.java
new file mode 100644
index 0000000..0537bad
--- /dev/null
+++ b/src/com/android/music/StreamStarter.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.Window;
+import android.widget.TextView;
+
+public class StreamStarter extends Activity
+{
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+        setContentView(R.layout.streamstarter);
+        
+        TextView tv = (TextView) findViewById(R.id.streamloading);
+        
+        Uri uri = getIntent().getData();
+        String msg = getString(R.string.streamloadingtext, uri.getHost());
+        tv.setText(msg);
+    }
+    
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        MusicUtils.bindToService(this, new ServiceConnection() {
+            public void onServiceConnected(ComponentName classname, IBinder obj) {
+                try {
+                    IntentFilter f = new IntentFilter();
+                    f.addAction(MediaPlaybackService.ASYNC_OPEN_COMPLETE);
+                    registerReceiver(mStatusListener, new IntentFilter(f));
+                    MusicUtils.sService.openfileAsync(getIntent().getData().toString());
+                } catch (RemoteException ex) {
+                }
+            }
+
+            public void onServiceDisconnected(ComponentName classname) {
+            }
+        });
+    }
+    
+    private BroadcastReceiver mStatusListener = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            try {
+                MusicUtils.sService.play();
+                intent = new Intent("com.android.music.PLAYBACK_VIEWER");
+                intent.putExtra("oneshot", true);
+                startActivity(intent);
+            } catch (RemoteException ex) {
+            }
+            finish();
+        }
+    };
+
+    @Override
+    public void onPause() {
+        if (MusicUtils.sService != null) {
+            try {
+                // This looks a little weird (when it's not playing, stop playing)
+                // but it is correct. When nothing is playing, it means that this
+                // was paused before a connection was established, in which case
+                // we stop trying to connect/play.
+                // Otherwise, this call to onPause() was a result of the call to
+                // finish() above, and we should let playback continue.
+                if (! MusicUtils.sService.isPlaying()) {
+                    MusicUtils.sService.stop();
+                }
+            } catch (RemoteException ex) {
+            }
+        }
+        unregisterReceiver(mStatusListener);
+        MusicUtils.unbindFromService(this);
+        super.onPause();
+    }
+}
diff --git a/src/com/android/music/TouchInterceptor.java b/src/com/android/music/TouchInterceptor.java
new file mode 100644
index 0000000..4276b7b
--- /dev/null
+++ b/src/com/android/music/TouchInterceptor.java
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.GestureDetector.SimpleOnGestureListener;
+import android.widget.AdapterView;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+public class TouchInterceptor extends ListView {
+    
+    private View mDragView;
+    private WindowManager mWindowManager;
+    private WindowManager.LayoutParams mWindowParams;
+    private int mDragPos;      // which item is being dragged
+    private int mFirstDragPos; // where was the dragged item originally
+    private int mDragPoint;    // at what offset inside the item did the user grab it
+    private int mCoordOffset;  // the difference between screen coordinates and coordinates in this view
+    private DragListener mDragListener;
+    private DropListener mDropListener;
+    private RemoveListener mRemoveListener;
+    private int mUpperBound;
+    private int mLowerBound;
+    private int mHeight;
+    private GestureDetector mGestureDetector;
+    private static final int FLING = 0;
+    private static final int SLIDE = 1;
+    private int mRemoveMode = -1;
+    private Rect mTempRect = new Rect();
+    private Bitmap mDragBitmap;
+    private final int mTouchSlop;
+
+    public TouchInterceptor(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        SharedPreferences pref = context.getSharedPreferences("Music", 3);
+        mRemoveMode = pref.getInt("deletemode", -1);
+        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+    }
+    
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (mRemoveListener != null && mGestureDetector == null) {
+            if (mRemoveMode == FLING) {
+                mGestureDetector = new GestureDetector(getContext(), new SimpleOnGestureListener() {
+                    @Override
+                    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+                            float velocityY) {
+                        if (mDragView != null) {
+                            if (velocityX > 1000) {
+                                Rect r = mTempRect;
+                                mDragView.getDrawingRect(r);
+                                if ( e2.getX() > r.right * 2 / 3) {
+                                    // fast fling right with release near the right edge of the screen
+                                    stopDragging();
+                                    mRemoveListener.remove(mFirstDragPos);
+                                    unExpandViews(true);
+                                }
+                            }
+                            // flinging while dragging should have no effect
+                            return true;
+                        }
+                        return false;
+                    }
+                });
+            }
+        }
+        if (mDragListener != null || mDropListener != null) {
+            switch (ev.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    int x = (int) ev.getX();
+                    int y = (int) ev.getY();
+                    int itemnum = pointToPosition(x, y);
+                    if (itemnum == AdapterView.INVALID_POSITION) {
+                        break;
+                    }
+                    ViewGroup item = (ViewGroup) getChildAt(itemnum - getFirstVisiblePosition());
+                    mDragPoint = y - item.getTop();
+                    mCoordOffset = ((int)ev.getRawY()) - y;
+                    View dragger = item.findViewById(R.id.icon);
+                    Rect r = mTempRect;
+                    dragger.getDrawingRect(r);
+                    if (x < r.right) {
+                        item.setDrawingCacheEnabled(true);
+                        // Create a copy of the drawing cache so that it does not get recycled
+                        // by the framework when the list tries to clean up memory
+                        Bitmap bitmap = Bitmap.createBitmap(item.getDrawingCache());
+                        startDragging(bitmap, y);
+                        mDragPos = itemnum;
+                        mFirstDragPos = mDragPos;
+                        mHeight = getHeight();
+                        int touchSlop = mTouchSlop;
+                        mUpperBound = Math.min(y - touchSlop, mHeight / 3);
+                        mLowerBound = Math.max(y + touchSlop, mHeight * 2 /3);
+                        return false;
+                    }
+                    mDragView = null;
+                    break;
+            }
+        }
+        return super.onInterceptTouchEvent(ev);
+    }
+    
+    /*
+     * pointToPosition() doesn't consider invisible views, but we
+     * need to, so implement a slightly different version.
+     */
+    private int myPointToPosition(int x, int y) {
+        Rect frame = mTempRect;
+        final int count = getChildCount();
+        for (int i = count - 1; i >= 0; i--) {
+            final View child = getChildAt(i);
+            child.getHitRect(frame);
+            if (frame.contains(x, y)) {
+                return getFirstVisiblePosition() + i;
+            }
+        }
+        return INVALID_POSITION;
+    }
+    
+    private int getItemForPosition(int y) {
+        int adjustedy = y - mDragPoint - 32;
+        int pos = myPointToPosition(0, adjustedy);
+        if (pos >= 0) {
+            if (pos <= mFirstDragPos) {
+                pos += 1;
+            }
+        } else if (adjustedy < 0) {
+            pos = 0;
+        }
+        return pos;
+    }
+    
+    private void adjustScrollBounds(int y) {
+        if (y >= mHeight / 3) {
+            mUpperBound = mHeight / 3;
+        }
+        if (y <= mHeight * 2 / 3) {
+            mLowerBound = mHeight * 2 / 3;
+        }
+    }
+
+    /*
+     * Restore size and visibility for all listitems
+     */
+    private void unExpandViews(boolean deletion) {
+        for (int i = 0;; i++) {
+            View v = getChildAt(i);
+            if (v == null) {
+                if (deletion) {
+                    // HACK force update of mItemCount
+                    int position = getFirstVisiblePosition();
+                    int y = getChildAt(0).getTop();
+                    setAdapter(getAdapter());
+                    setSelectionFromTop(position, y);
+                    // end hack
+                }
+                layoutChildren(); // force children to be recreated where needed
+                v = getChildAt(i);
+                if (v == null) {
+                    break;
+                }
+            }
+            ViewGroup.LayoutParams params = v.getLayoutParams();
+            params.height = 64;
+            v.setLayoutParams(params);
+            v.setVisibility(View.VISIBLE);
+        }
+    }
+    
+    /* Adjust visibility and size to make it appear as though
+     * an item is being dragged around and other items are making
+     * room for it:
+     * If dropping the item would result in it still being in the
+     * same place, then make the dragged listitem's size normal,
+     * but make the item invisible.
+     * Otherwise, if the dragged listitem is still on screen, make
+     * it as small as possible and expand the item below the insert
+     * point.
+     * If the dragged item is not on screen, only expand the item
+     * below the current insertpoint.
+     */
+    private void doExpansion() {
+        int childnum = mDragPos - getFirstVisiblePosition();
+        if (mDragPos > mFirstDragPos) {
+            childnum++;
+        }
+
+        View first = getChildAt(mFirstDragPos - getFirstVisiblePosition());
+
+        for (int i = 0;; i++) {
+            View vv = getChildAt(i);
+            if (vv == null) {
+                break;
+            }
+            int height = 64;
+            int visibility = View.VISIBLE;
+            if (vv.equals(first)) {
+                // processing the item that is being dragged
+                if (mDragPos == mFirstDragPos) {
+                    // hovering over the original location
+                    visibility = View.INVISIBLE;
+                } else {
+                    // not hovering over it
+                    height = 1;
+                }
+            } else if (i == childnum) {
+                if (mDragPos < getCount() - 1) {
+                    height = 128;
+                }
+            }
+            ViewGroup.LayoutParams params = vv.getLayoutParams();
+            params.height = height;
+            vv.setLayoutParams(params);
+            vv.setVisibility(visibility);
+        }
+    }
+    
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (mGestureDetector != null) {
+            mGestureDetector.onTouchEvent(ev);
+        }
+        if ((mDragListener != null || mDropListener != null) && mDragView != null) {
+            int action = ev.getAction(); 
+            switch (action) {
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_CANCEL:
+                    Rect r = mTempRect;
+                    mDragView.getDrawingRect(r);
+                    stopDragging();
+                    if (mRemoveMode == SLIDE && ev.getX() > r.right * 3 / 4) {
+                        if (mRemoveListener != null) {
+                            mRemoveListener.remove(mFirstDragPos);
+                        }
+                        unExpandViews(true);
+                    } else {
+                        if (mDropListener != null && mDragPos >= 0 && mDragPos < getCount()) {
+                            mDropListener.drop(mFirstDragPos, mDragPos);
+                        }
+                        unExpandViews(false);
+                    }
+                    break;
+                    
+                case MotionEvent.ACTION_DOWN:
+                case MotionEvent.ACTION_MOVE:
+                    int x = (int) ev.getX();
+                    int y = (int) ev.getY();
+                    dragView(x, y);
+                    int itemnum = getItemForPosition(y);
+                    if (itemnum >= 0) {
+                        if (action == MotionEvent.ACTION_DOWN || itemnum != mDragPos) {
+                            if (mDragListener != null) {
+                                mDragListener.drag(mDragPos, itemnum);
+                            }
+                            mDragPos = itemnum;
+                            doExpansion();
+                        }
+                        int speed = 0;
+                        adjustScrollBounds(y);
+                        if (y > mLowerBound) {
+                            // scroll the list up a bit
+                            speed = y > (mHeight + mLowerBound) / 2 ? 16 : 4;
+                        } else if (y < mUpperBound) {
+                            // scroll the list down a bit
+                            speed = y < mUpperBound / 2 ? -16 : -4;
+                        }
+                        if (speed != 0) {
+                            int ref = pointToPosition(0, mHeight / 2);
+                            if (ref == AdapterView.INVALID_POSITION) {
+                                //we hit a divider or an invisible view, check somewhere else
+                                ref = pointToPosition(0, mHeight / 2 + getDividerHeight() + 64);
+                            }
+                            View v = getChildAt(ref - getFirstVisiblePosition());
+                            if (v!= null) {
+                                int pos = v.getTop();
+                                setSelectionFromTop(ref, pos - speed);
+                            }
+                        }
+                    }
+                    break;
+            }
+            return true;
+        }
+        return super.onTouchEvent(ev);
+    }
+    
+    private void startDragging(Bitmap bm, int y) {
+        mWindowParams = new WindowManager.LayoutParams();
+        mWindowParams.gravity = Gravity.TOP;
+        mWindowParams.x = 0;
+        mWindowParams.y = y - mDragPoint + mCoordOffset;
+
+        mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
+        mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
+        mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+                | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+        mWindowParams.format = PixelFormat.TRANSLUCENT;
+        mWindowParams.windowAnimations = 0;
+        
+        ImageView v = new ImageView(mContext);
+        int backGroundColor = mContext.getResources().getColor(R.color.dragndrop_background);
+        v.setBackgroundColor(backGroundColor);
+        v.setImageBitmap(bm);
+
+        if (mDragBitmap != null) {
+            mDragBitmap.recycle();
+        }
+        mDragBitmap = bm;
+
+        mWindowManager = (WindowManager)mContext.getSystemService("window");
+        mWindowManager.addView(v, mWindowParams);
+        mDragView = v;
+    }
+    
+    private void dragView(int x, int y) {
+        if (mRemoveMode == SLIDE) {
+            float alpha = 1.0f;
+            int width = mDragView.getWidth();
+            if (x > width / 2) {
+                alpha = ((float)(width - x)) / (width / 2);
+            }
+            mWindowParams.alpha = alpha;
+        }
+        mWindowParams.y = y - mDragPoint + mCoordOffset;
+        mWindowManager.updateViewLayout(mDragView, mWindowParams);
+    }
+    
+    private void stopDragging() {
+        WindowManager wm = (WindowManager)mContext.getSystemService("window");
+        wm.removeView(mDragView);
+        mDragView = null;
+        if (mDragBitmap != null) {
+            mDragBitmap.recycle();
+            mDragBitmap = null;
+        }
+    }
+    
+    public void setDragListener(DragListener l) {
+        mDragListener = l;
+    }
+    
+    public void setDropListener(DropListener l) {
+        mDropListener = l;
+    }
+    
+    public void setRemoveListener(RemoveListener l) {
+        mRemoveListener = l;
+    }
+
+    public interface DragListener {
+        void drag(int from, int to);
+    }
+    public interface DropListener {
+        void drop(int from, int to);
+    }
+    public interface RemoveListener {
+        void remove(int which);
+    }
+}
diff --git a/src/com/android/music/TrackBrowserActivity.java b/src/com/android/music/TrackBrowserActivity.java
new file mode 100644
index 0000000..58f556c
--- /dev/null
+++ b/src/com/android/music/TrackBrowserActivity.java
@@ -0,0 +1,1461 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.app.ListActivity;
+import android.app.SearchManager;
+import android.content.AsyncQueryHandler;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.database.AbstractCursor;
+import android.database.CharArrayBuffer;
+import android.database.Cursor;
+import android.media.AudioManager;
+import android.media.MediaFile;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Audio.Playlists;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.SubMenu;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.AlphabetIndexer;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.SectionIndexer;
+import android.widget.SimpleCursorAdapter;
+import android.widget.TextView;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+
+import java.text.Collator;
+import java.util.Arrays;
+
+public class TrackBrowserActivity extends ListActivity
+        implements View.OnCreateContextMenuListener, MusicUtils.Defs, ServiceConnection
+{
+    private final int Q_SELECTED = CHILD_MENU_BASE;
+    private final int Q_ALL = CHILD_MENU_BASE + 1;
+    private final int SAVE_AS_PLAYLIST = CHILD_MENU_BASE + 2;
+    private final int PLAY_ALL = CHILD_MENU_BASE + 3;
+    private final int CLEAR_PLAYLIST = CHILD_MENU_BASE + 4;
+    private final int REMOVE = CHILD_MENU_BASE + 5;
+    private final int SEARCH = CHILD_MENU_BASE + 6;
+
+
+    private static final String LOGTAG = "TrackBrowser";
+
+    private String[] mCursorCols;
+    private String[] mPlaylistMemberCols;
+    private boolean mDeletedOneRow = false;
+    private boolean mEditMode = false;
+    private String mCurrentTrackName;
+    private String mCurrentAlbumName;
+    private String mCurrentArtistNameForAlbum;
+    private ListView mTrackList;
+    private Cursor mTrackCursor;
+    private TrackListAdapter mAdapter;
+    private boolean mAdapterSent = false;
+    private String mAlbumId;
+    private String mArtistId;
+    private String mPlaylist;
+    private String mGenre;
+    private String mSortOrder;
+    private int mSelectedPosition;
+    private long mSelectedId;
+
+    public TrackBrowserActivity()
+    {
+    }
+
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle icicle)
+    {
+        super.onCreate(icicle);
+        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+        setVolumeControlStream(AudioManager.STREAM_MUSIC);
+        if (icicle != null) {
+            mSelectedId = icicle.getLong("selectedtrack");
+            mAlbumId = icicle.getString("album");
+            mArtistId = icicle.getString("artist");
+            mPlaylist = icicle.getString("playlist");
+            mGenre = icicle.getString("genre");
+            mEditMode = icicle.getBoolean("editmode", false);
+        } else {
+            mAlbumId = getIntent().getStringExtra("album");
+            // If we have an album, show everything on the album, not just stuff
+            // by a particular artist.
+            Intent intent = getIntent();
+            mArtistId = intent.getStringExtra("artist");
+            mPlaylist = intent.getStringExtra("playlist");
+            mGenre = intent.getStringExtra("genre");
+            mEditMode = intent.getAction().equals(Intent.ACTION_EDIT);
+        }
+
+        mCursorCols = new String[] {
+                MediaStore.Audio.Media._ID,
+                MediaStore.Audio.Media.TITLE,
+                MediaStore.Audio.Media.TITLE_KEY,
+                MediaStore.Audio.Media.DATA,
+                MediaStore.Audio.Media.ALBUM,
+                MediaStore.Audio.Media.ARTIST,
+                MediaStore.Audio.Media.ARTIST_ID,
+                MediaStore.Audio.Media.DURATION
+        };
+        mPlaylistMemberCols = new String[] {
+                MediaStore.Audio.Playlists.Members._ID,
+                MediaStore.Audio.Media.TITLE,
+                MediaStore.Audio.Media.TITLE_KEY,
+                MediaStore.Audio.Media.DATA,
+                MediaStore.Audio.Media.ALBUM,
+                MediaStore.Audio.Media.ARTIST,
+                MediaStore.Audio.Media.ARTIST_ID,
+                MediaStore.Audio.Media.DURATION,
+                MediaStore.Audio.Playlists.Members.PLAY_ORDER,
+                MediaStore.Audio.Playlists.Members.AUDIO_ID
+        };
+
+        setContentView(R.layout.media_picker_activity);
+        mTrackList = getListView();
+        mTrackList.setOnCreateContextMenuListener(this);
+        if (mEditMode) {
+            //((TouchInterceptor) mTrackList).setDragListener(mDragListener);
+            ((TouchInterceptor) mTrackList).setDropListener(mDropListener);
+            ((TouchInterceptor) mTrackList).setRemoveListener(mRemoveListener);
+            mTrackList.setCacheColorHint(0);
+        } else {
+            mTrackList.setTextFilterEnabled(true);
+        }
+        mAdapter = (TrackListAdapter) getLastNonConfigurationInstance();
+        
+        if (mAdapter != null) {
+            mAdapter.setActivity(this);
+            setListAdapter(mAdapter);
+        }
+        MusicUtils.bindToService(this, this);
+    }
+
+    public void onServiceConnected(ComponentName name, IBinder service)
+    {
+        IntentFilter f = new IntentFilter();
+        f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
+        f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
+        f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
+        f.addDataScheme("file");
+        registerReceiver(mScanListener, f);
+
+        if (mAdapter == null) {
+            //Log.i("@@@", "starting query");
+            mAdapter = new TrackListAdapter(
+                    getApplication(), // need to use application context to avoid leaks
+                    this,
+                    mEditMode ? R.layout.edit_track_list_item : R.layout.track_list_item,
+                    null, // cursor
+                    new String[] {},
+                    new int[] {},
+                    "nowplaying".equals(mPlaylist),
+                    mPlaylist != null &&
+                    !(mPlaylist.equals("podcasts") || mPlaylist.equals("recentlyadded")));
+            setListAdapter(mAdapter);
+            setTitle(R.string.working_songs);
+            getTrackCursor(mAdapter.getQueryHandler(), null);
+        } else {
+            mTrackCursor = mAdapter.getCursor();
+            // If mTrackCursor is null, this can be because it doesn't have
+            // a cursor yet (because the initial query that sets its cursor
+            // is still in progress), or because the query failed.
+            // In order to not flash the error dialog at the user for the
+            // first case, simply retry the query when the cursor is null.
+            // Worst case, we end up doing the same query twice.
+            if (mTrackCursor != null) {
+                init(mTrackCursor);
+            } else {
+                setTitle(R.string.working_songs);
+                getTrackCursor(mAdapter.getQueryHandler(), null);
+            }
+        }
+    }
+    
+    public void onServiceDisconnected(ComponentName name) {
+        // we can't really function without the service, so don't
+        finish();
+    }
+
+    @Override
+    public Object onRetainNonConfigurationInstance() {
+        TrackListAdapter a = mAdapter;
+        mAdapterSent = true;
+        return a;
+    }
+    
+    @Override
+    public void onDestroy() {
+        MusicUtils.unbindFromService(this);
+        try {
+            if ("nowplaying".equals(mPlaylist)) {
+                unregisterReceiver(mNowPlayingListener);
+            } else {
+                unregisterReceiver(mTrackListListener);
+            }
+        } catch (IllegalArgumentException ex) {
+            // we end up here in case we never registered the listeners
+        }
+        
+        // if we didn't send the adapter off to another activity, we should
+        // close the cursor
+        if (!mAdapterSent) {
+            Cursor c = mAdapter.getCursor();
+            if (c != null) {
+                c.close();
+            }
+        }
+        unregisterReceiver(mScanListener);
+        super.onDestroy();
+   }
+    
+    @Override
+    public void onResume() {
+        super.onResume();
+        if (mTrackCursor != null) {
+            getListView().invalidateViews();
+        }
+        MusicUtils.setSpinnerState(this);
+    }
+    @Override
+    public void onPause() {
+        mReScanHandler.removeCallbacksAndMessages(null);
+        super.onPause();
+    }
+    
+    /*
+     * This listener gets called when the media scanner starts up or finishes, and
+     * when the sd card is unmounted.
+     */
+    private BroadcastReceiver mScanListener = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (Intent.ACTION_MEDIA_SCANNER_STARTED.equals(action) ||
+                    Intent.ACTION_MEDIA_SCANNER_FINISHED.equals(action)) {
+                MusicUtils.setSpinnerState(TrackBrowserActivity.this);
+            }
+            mReScanHandler.sendEmptyMessage(0);
+        }
+    };
+    
+    private Handler mReScanHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            getTrackCursor(mAdapter.getQueryHandler(), null);
+            // if the query results in a null cursor, onQueryComplete() will
+            // call init(), which will post a delayed message to this handler
+            // in order to try again.
+        }
+    };
+    
+    public void onSaveInstanceState(Bundle outcicle) {
+        // need to store the selected item so we don't lose it in case
+        // of an orientation switch. Otherwise we could lose it while
+        // in the middle of specifying a playlist to add the item to.
+        outcicle.putLong("selectedtrack", mSelectedId);
+        outcicle.putString("artist", mArtistId);
+        outcicle.putString("album", mAlbumId);
+        outcicle.putString("playlist", mPlaylist);
+        outcicle.putString("genre", mGenre);
+        outcicle.putBoolean("editmode", mEditMode);
+        super.onSaveInstanceState(outcicle);
+    }
+    
+    public void init(Cursor newCursor) {
+
+        mAdapter.changeCursor(newCursor); // also sets mTrackCursor
+        
+        if (mTrackCursor == null) {
+            MusicUtils.displayDatabaseError(this);
+            closeContextMenu();
+            mReScanHandler.sendEmptyMessageDelayed(0, 1000);
+            return;
+        }
+        
+        MusicUtils.hideDatabaseError(this);
+        setTitle();
+
+        // When showing the queue, position the selection on the currently playing track
+        // Otherwise, position the selection on the first matching artist, if any
+        IntentFilter f = new IntentFilter();
+        f.addAction(MediaPlaybackService.META_CHANGED);
+        f.addAction(MediaPlaybackService.QUEUE_CHANGED);
+        if ("nowplaying".equals(mPlaylist)) {
+            try {
+                int cur = MusicUtils.sService.getQueuePosition();
+                setSelection(cur);
+                registerReceiver(mNowPlayingListener, new IntentFilter(f));
+                mNowPlayingListener.onReceive(this, new Intent(MediaPlaybackService.META_CHANGED));
+            } catch (RemoteException ex) {
+            }
+        } else {
+            String key = getIntent().getStringExtra("artist");
+            if (key != null) {
+                int keyidx = mTrackCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID);
+                mTrackCursor.moveToFirst();
+                while (! mTrackCursor.isAfterLast()) {
+                    String artist = mTrackCursor.getString(keyidx);
+                    if (artist.equals(key)) {
+                        setSelection(mTrackCursor.getPosition());
+                        break;
+                    }
+                    mTrackCursor.moveToNext();
+                }
+            }
+            registerReceiver(mTrackListListener, new IntentFilter(f));
+            mTrackListListener.onReceive(this, new Intent(MediaPlaybackService.META_CHANGED));
+        }
+    }
+
+    private void setTitle() {
+
+        CharSequence fancyName = null;
+        if (mAlbumId != null) {
+            int numresults = mTrackCursor != null ? mTrackCursor.getCount() : 0;
+            if (numresults > 0) {
+                mTrackCursor.moveToFirst();
+                int idx = mTrackCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM);
+                fancyName = mTrackCursor.getString(idx);
+                // For compilation albums show only the album title,
+                // but for regular albums show "artist - album".
+                // To determine whether something is a compilation
+                // album, do a query for the artist + album of the
+                // first item, and see if it returns the same number
+                // of results as the album query.
+                String where = MediaStore.Audio.Media.ALBUM_ID + "='" + mAlbumId +
+                        "' AND " + MediaStore.Audio.Media.ARTIST_ID + "=" + 
+                        mTrackCursor.getLong(mTrackCursor.getColumnIndexOrThrow(
+                                MediaStore.Audio.Media.ARTIST_ID));
+                Cursor cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                    new String[] {MediaStore.Audio.Media.ALBUM}, where, null, null);
+                if (cursor != null) {
+                    if (cursor.getCount() != numresults) {
+                        // compilation album
+                        fancyName = mTrackCursor.getString(idx);
+                    }    
+                    cursor.deactivate();
+                }
+                if (fancyName.equals(MediaFile.UNKNOWN_STRING)) {
+                    fancyName = getString(R.string.unknown_album_name);
+                }
+            }
+        } else if (mPlaylist != null) {
+            if (mPlaylist.equals("nowplaying")) {
+                if (MusicUtils.getCurrentShuffleMode() == MediaPlaybackService.SHUFFLE_AUTO) {
+                    fancyName = getText(R.string.partyshuffle_title);
+                } else {
+                    fancyName = getText(R.string.nowplaying_title);
+                }
+            } else if (mPlaylist.equals("podcasts")){
+                fancyName = getText(R.string.podcasts_title);
+            } else if (mPlaylist.equals("recentlyadded")){
+                fancyName = getText(R.string.recentlyadded_title);
+            } else {
+                String [] cols = new String [] {
+                MediaStore.Audio.Playlists.NAME
+                };
+                Cursor cursor = MusicUtils.query(this,
+                        ContentUris.withAppendedId(Playlists.EXTERNAL_CONTENT_URI, Long.valueOf(mPlaylist)),
+                        cols, null, null, null);
+                if (cursor != null) {
+                    if (cursor.getCount() != 0) {
+                        cursor.moveToFirst();
+                        fancyName = cursor.getString(0);
+                    }
+                    cursor.deactivate();
+                }
+            }
+        } else if (mGenre != null) {
+            String [] cols = new String [] {
+            MediaStore.Audio.Genres.NAME
+            };
+            Cursor cursor = MusicUtils.query(this,
+                    ContentUris.withAppendedId(MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI, Long.valueOf(mGenre)),
+                    cols, null, null, null);
+            if (cursor != null) {
+                if (cursor.getCount() != 0) {
+                    cursor.moveToFirst();
+                    fancyName = cursor.getString(0);
+                }
+                cursor.deactivate();
+            }
+        }
+
+        if (fancyName != null) {
+            setTitle(fancyName);
+        } else {
+            setTitle(R.string.tracks_title);
+        }
+    }
+    
+    private TouchInterceptor.DragListener mDragListener =
+        new TouchInterceptor.DragListener() {
+        public void drag(int from, int to) {
+            if (mTrackCursor instanceof NowPlayingCursor) {
+                NowPlayingCursor c = (NowPlayingCursor) mTrackCursor;
+                c.moveItem(from, to);
+                ((TrackListAdapter)getListAdapter()).notifyDataSetChanged();
+                getListView().invalidateViews();
+                mDeletedOneRow = true;
+            }
+        }
+    };
+    private TouchInterceptor.DropListener mDropListener =
+        new TouchInterceptor.DropListener() {
+        public void drop(int from, int to) {
+            if (mTrackCursor instanceof NowPlayingCursor) {
+                // update the currently playing list
+                NowPlayingCursor c = (NowPlayingCursor) mTrackCursor;
+                c.moveItem(from, to);
+                ((TrackListAdapter)getListAdapter()).notifyDataSetChanged();
+                getListView().invalidateViews();
+                mDeletedOneRow = true;
+            } else {
+                // update a saved playlist
+                Uri baseUri = MediaStore.Audio.Playlists.Members.getContentUri("external",
+                        Long.valueOf(mPlaylist));
+                ContentValues values = new ContentValues();
+                String where = MediaStore.Audio.Playlists.Members._ID + "=?";
+                String [] wherearg = new String[1];
+                ContentResolver res = getContentResolver();
+                
+                int colidx = mTrackCursor.getColumnIndexOrThrow(
+                        MediaStore.Audio.Playlists.Members.PLAY_ORDER);
+                if (from < to) {
+                    // move the item to somewhere later in the list
+                    mTrackCursor.moveToPosition(to);
+                    int toidx = mTrackCursor.getInt(colidx);
+                    mTrackCursor.moveToPosition(from);
+                    values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, toidx);
+                    wherearg[0] = mTrackCursor.getString(0);
+                    res.update(baseUri, values, where, wherearg);
+                    for (int i = from + 1; i <= to; i++) {
+                        mTrackCursor.moveToPosition(i);
+                        values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, i - 1);
+                        wherearg[0] = mTrackCursor.getString(0);
+                        res.update(baseUri, values, where, wherearg);
+                    }
+                } else if (from > to) {
+                    // move the item to somewhere earlier in the list
+                    mTrackCursor.moveToPosition(to);
+                    int toidx = mTrackCursor.getInt(colidx);
+                    mTrackCursor.moveToPosition(from);
+                    values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, toidx);
+                    wherearg[0] = mTrackCursor.getString(0);
+                    res.update(baseUri, values, where, wherearg);
+                    for (int i = from - 1; i >= to; i--) {
+                        mTrackCursor.moveToPosition(i);
+                        values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, i + 1);
+                        wherearg[0] = mTrackCursor.getString(0);
+                        res.update(baseUri, values, where, wherearg);
+                    }
+                }
+            }
+        }
+    };
+    
+    private TouchInterceptor.RemoveListener mRemoveListener =
+        new TouchInterceptor.RemoveListener() {
+        public void remove(int which) {
+            removePlaylistItem(which);
+        }
+    };
+
+    private void removePlaylistItem(int which) {
+        View v = mTrackList.getChildAt(which - mTrackList.getFirstVisiblePosition());
+        try {
+            if (MusicUtils.sService != null
+                    && which != MusicUtils.sService.getQueuePosition()) {
+                mDeletedOneRow = true;
+            }
+        } catch (RemoteException e) {
+            // Service died, so nothing playing.
+            mDeletedOneRow = true;
+        }
+        v.setVisibility(View.GONE);
+        mTrackList.invalidateViews();
+        if (mTrackCursor instanceof NowPlayingCursor) {
+            ((NowPlayingCursor)mTrackCursor).removeItem(which);
+        } else {
+            int colidx = mTrackCursor.getColumnIndexOrThrow(
+                    MediaStore.Audio.Playlists.Members._ID);
+            mTrackCursor.moveToPosition(which);
+            long id = mTrackCursor.getLong(colidx);
+            Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external",
+                    Long.valueOf(mPlaylist));
+            getContentResolver().delete(
+                    ContentUris.withAppendedId(uri, id), null, null);
+        }
+        v.setVisibility(View.VISIBLE);
+        mTrackList.invalidateViews();
+    }
+    
+    private BroadcastReceiver mTrackListListener = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            getListView().invalidateViews();
+        }
+    };
+
+    private BroadcastReceiver mNowPlayingListener = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(MediaPlaybackService.META_CHANGED)) {
+                getListView().invalidateViews();
+            } else if (intent.getAction().equals(MediaPlaybackService.QUEUE_CHANGED)) {
+                if (mDeletedOneRow) {
+                    // This is the notification for a single row that was
+                    // deleted previously, which is already reflected in
+                    // the UI.
+                    mDeletedOneRow = false;
+                    return;
+                }
+                Cursor c = new NowPlayingCursor(MusicUtils.sService, mCursorCols);
+                if (c.getCount() == 0) {
+                    finish();
+                    return;
+                }
+                mAdapter.changeCursor(c);
+            }
+        }
+    };
+
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
+        menu.add(0, PLAY_SELECTION, 0, R.string.play_selection);
+        SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0, R.string.add_to_playlist);
+        MusicUtils.makePlaylistMenu(this, sub);
+        if (mEditMode) {
+            menu.add(0, REMOVE, 0, R.string.remove_from_playlist);
+        }
+        menu.add(0, USE_AS_RINGTONE, 0, R.string.ringtone_menu);
+        menu.add(0, DELETE_ITEM, 0, R.string.delete_item);
+        menu.add(0, SEARCH, 0, R.string.search_title);
+        AdapterContextMenuInfo mi = (AdapterContextMenuInfo) menuInfoIn;
+        mSelectedPosition =  mi.position;
+        mTrackCursor.moveToPosition(mSelectedPosition);
+        try {
+            int id_idx = mTrackCursor.getColumnIndexOrThrow(
+                    MediaStore.Audio.Playlists.Members.AUDIO_ID);
+            mSelectedId = mTrackCursor.getInt(id_idx);
+        } catch (IllegalArgumentException ex) {
+            mSelectedId = mi.id;
+        }
+        mCurrentAlbumName = mTrackCursor.getString(mTrackCursor.getColumnIndexOrThrow(
+                MediaStore.Audio.Media.ALBUM));
+        mCurrentArtistNameForAlbum = mTrackCursor.getString(mTrackCursor.getColumnIndexOrThrow(
+                MediaStore.Audio.Media.ARTIST));
+        mCurrentTrackName = mTrackCursor.getString(mTrackCursor.getColumnIndexOrThrow(
+                MediaStore.Audio.Media.TITLE));
+        menu.setHeaderTitle(mCurrentTrackName);
+    }
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case PLAY_SELECTION: {
+                // play the track
+                int position = mSelectedPosition;
+                MusicUtils.playAll(this, mTrackCursor, position);
+                return true;
+            }
+
+            case QUEUE: {
+                int [] list = new int[] { (int) mSelectedId };
+                MusicUtils.addToCurrentPlaylist(this, list);
+                return true;
+            }
+
+            case NEW_PLAYLIST: {
+                Intent intent = new Intent();
+                intent.setClass(this, CreatePlaylist.class);
+                startActivityForResult(intent, NEW_PLAYLIST);
+                return true;
+            }
+
+            case PLAYLIST_SELECTED: {
+                int [] list = new int[] { (int) mSelectedId };
+                int playlist = item.getIntent().getIntExtra("playlist", 0);
+                MusicUtils.addToPlaylist(this, list, playlist);
+                return true;
+            }
+
+            case USE_AS_RINGTONE:
+                // Set the system setting to make this the current ringtone
+                MusicUtils.setRingtone(this, mSelectedId);
+                return true;
+
+            case DELETE_ITEM: {
+                int [] list = new int[1];
+                list[0] = (int) mSelectedId;
+                Bundle b = new Bundle();
+                String f = getString(R.string.delete_song_desc); 
+                String desc = String.format(f, mCurrentTrackName);
+                b.putString("description", desc);
+                b.putIntArray("items", list);
+                Intent intent = new Intent();
+                intent.setClass(this, DeleteItems.class);
+                intent.putExtras(b);
+                startActivityForResult(intent, -1);
+                return true;
+            }
+            
+            case REMOVE:
+                removePlaylistItem(mSelectedPosition);
+                return true;
+                
+            case SEARCH:
+                doSearch();
+                return true;
+        }
+        return super.onContextItemSelected(item);
+    }
+
+    void doSearch() {
+        CharSequence title = null;
+        String query = null;
+        
+        Intent i = new Intent();
+        i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH);
+        
+        title = mCurrentAlbumName;
+        query = mCurrentArtistNameForAlbum + " " + mCurrentAlbumName;
+        i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistNameForAlbum);
+        i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, mCurrentAlbumName);
+        i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, "audio/*");
+        title = getString(R.string.mediasearch, title);
+        i.putExtra(SearchManager.QUERY, query);
+
+        startActivity(Intent.createChooser(i, title));
+    }
+
+    // In order to use alt-up/down as a shortcut for moving the selected item
+    // in the list, we need to override dispatchKeyEvent, not onKeyDown.
+    // (onKeyDown never sees these events, since they are handled by the list)
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        if (mPlaylist != null && event.getMetaState() != 0 &&
+                event.getAction() == KeyEvent.ACTION_DOWN) {
+            switch (event.getKeyCode()) {
+                case KeyEvent.KEYCODE_DPAD_UP:
+                    moveItem(true);
+                    return true;
+                case KeyEvent.KEYCODE_DPAD_DOWN:
+                    moveItem(false);
+                    return true;
+                case KeyEvent.KEYCODE_DEL:
+                    removeItem();
+                    return true;
+            }
+        }
+
+        return super.dispatchKeyEvent(event);
+    }
+
+    private void removeItem() {
+        int curcount = mTrackCursor.getCount();
+        int curpos = mTrackList.getSelectedItemPosition();
+        if (curcount == 0 || curpos < 0) {
+            return;
+        }
+        
+        if ("nowplaying".equals(mPlaylist)) {
+            // remove track from queue
+
+            // Work around bug 902971. To get quick visual feedback
+            // of the deletion of the item, hide the selected view.
+            try {
+                if (curpos != MusicUtils.sService.getQueuePosition()) {
+                    mDeletedOneRow = true;
+                }
+            } catch (RemoteException ex) {
+            }
+            View v = mTrackList.getSelectedView();
+            v.setVisibility(View.GONE);
+            mTrackList.invalidateViews();
+            ((NowPlayingCursor)mTrackCursor).removeItem(curpos);
+            v.setVisibility(View.VISIBLE);
+            mTrackList.invalidateViews();
+        } else {
+            // remove track from playlist
+            int colidx = mTrackCursor.getColumnIndexOrThrow(
+                    MediaStore.Audio.Playlists.Members._ID);
+            mTrackCursor.moveToPosition(curpos);
+            long id = mTrackCursor.getLong(colidx);
+            Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external",
+                    Long.valueOf(mPlaylist));
+            getContentResolver().delete(
+                    ContentUris.withAppendedId(uri, id), null, null);
+            curcount--;
+            if (curcount == 0) {
+                finish();
+            } else {
+                mTrackList.setSelection(curpos < curcount ? curpos : curcount);
+            }
+        }
+    }
+    
+    private void moveItem(boolean up) {
+        int curcount = mTrackCursor.getCount(); 
+        int curpos = mTrackList.getSelectedItemPosition();
+        if ( (up && curpos < 1) || (!up  && curpos >= curcount - 1)) {
+            return;
+        }
+
+        if (mTrackCursor instanceof NowPlayingCursor) {
+            NowPlayingCursor c = (NowPlayingCursor) mTrackCursor;
+            c.moveItem(curpos, up ? curpos - 1 : curpos + 1);
+            ((TrackListAdapter)getListAdapter()).notifyDataSetChanged();
+            getListView().invalidateViews();
+            mDeletedOneRow = true;
+            if (up) {
+                mTrackList.setSelection(curpos - 1);
+            } else {
+                mTrackList.setSelection(curpos + 1);
+            }
+        } else {
+            int colidx = mTrackCursor.getColumnIndexOrThrow(
+                    MediaStore.Audio.Playlists.Members.PLAY_ORDER);
+            mTrackCursor.moveToPosition(curpos);
+            int currentplayidx = mTrackCursor.getInt(colidx);
+            Uri baseUri = MediaStore.Audio.Playlists.Members.getContentUri("external",
+                    Long.valueOf(mPlaylist));
+            ContentValues values = new ContentValues();
+            String where = MediaStore.Audio.Playlists.Members._ID + "=?";
+            String [] wherearg = new String[1];
+            ContentResolver res = getContentResolver();
+            if (up) {
+                values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, currentplayidx - 1);
+                wherearg[0] = mTrackCursor.getString(0);
+                res.update(baseUri, values, where, wherearg);
+                mTrackCursor.moveToPrevious();
+            } else {
+                values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, currentplayidx + 1);
+                wherearg[0] = mTrackCursor.getString(0);
+                res.update(baseUri, values, where, wherearg);
+                mTrackCursor.moveToNext();
+            }
+            values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, currentplayidx);
+            wherearg[0] = mTrackCursor.getString(0);
+            res.update(baseUri, values, where, wherearg);
+        }
+    }
+    
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id)
+    {
+        if (mTrackCursor.getCount() == 0) {
+            return;
+        }
+        MusicUtils.playAll(this, mTrackCursor, position);
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        /* This activity is used for a number of different browsing modes, and the menu can
+         * be different for each of them:
+         * - all tracks, optionally restricted to an album, artist or playlist
+         * - the list of currently playing songs
+         */
+        super.onCreateOptionsMenu(menu);
+        if (mPlaylist == null) {
+            menu.add(0, PLAY_ALL, 0, R.string.play_all).setIcon(com.android.internal.R.drawable.ic_menu_play_clip);
+        }
+        menu.add(0, GOTO_START, 0, R.string.goto_start).setIcon(R.drawable.ic_menu_music_library);
+        menu.add(0, GOTO_PLAYBACK, 0, R.string.goto_playback).setIcon(R.drawable.ic_menu_playback)
+                .setVisible(MusicUtils.isMusicLoaded());
+        menu.add(0, SHUFFLE_ALL, 0, R.string.shuffle_all).setIcon(R.drawable.ic_menu_shuffle);
+        if (mPlaylist != null) {
+            menu.add(0, SAVE_AS_PLAYLIST, 0, R.string.save_as_playlist).setIcon(android.R.drawable.ic_menu_save);
+            if (mPlaylist.equals("nowplaying")) {
+                menu.add(0, CLEAR_PLAYLIST, 0, R.string.clear_playlist).setIcon(com.android.internal.R.drawable.ic_menu_clear_playlist);
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        Intent intent;
+        Cursor cursor;
+        switch (item.getItemId()) {
+            case PLAY_ALL: {
+                MusicUtils.playAll(this, mTrackCursor);
+                return true;
+            }
+
+            case GOTO_START:
+                intent = new Intent();
+                intent.setClass(this, MusicBrowserActivity.class);
+                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                startActivity(intent);
+                return true;
+
+            case GOTO_PLAYBACK:
+                intent = new Intent("com.android.music.PLAYBACK_VIEWER");
+                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                startActivity(intent);
+                return true;
+                
+            case SHUFFLE_ALL:
+                // Should 'shuffle all' shuffle ALL, or only the tracks shown?
+                cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                        new String [] { MediaStore.Audio.Media._ID}, 
+                        MediaStore.Audio.Media.IS_MUSIC + "=1", null,
+                        MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
+                if (cursor != null) {
+                    MusicUtils.shuffleAll(this, cursor);
+                    cursor.close();
+                }
+                return true;
+                
+            case SAVE_AS_PLAYLIST:
+                intent = new Intent();
+                intent.setClass(this, CreatePlaylist.class);
+                startActivityForResult(intent, SAVE_AS_PLAYLIST);
+                return true;
+                
+            case CLEAR_PLAYLIST:
+                // We only clear the current playlist
+                MusicUtils.clearQueue();
+                return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+        switch (requestCode) {
+            case SCAN_DONE:
+                if (resultCode == RESULT_CANCELED) {
+                    finish();
+                } else {
+                    getTrackCursor(mAdapter.getQueryHandler(), null);
+                }
+                break;
+                
+            case NEW_PLAYLIST:
+                if (resultCode == RESULT_OK) {
+                    Uri uri = intent.getData();
+                    if (uri != null) {
+                        int [] list = new int[] { (int) mSelectedId };
+                        MusicUtils.addToPlaylist(this, list, Integer.valueOf(uri.getLastPathSegment()));
+                    }
+                }
+                break;
+
+            case SAVE_AS_PLAYLIST:
+                if (resultCode == RESULT_OK) {
+                    Uri uri = intent.getData();
+                    if (uri != null) {
+                        int [] list = MusicUtils.getSongListForCursor(mTrackCursor);
+                        int plid = Integer.parseInt(uri.getLastPathSegment());
+                        MusicUtils.addToPlaylist(this, list, plid);
+                    }
+                }
+                break;
+        }
+    }
+    
+    private Cursor getTrackCursor(AsyncQueryHandler async, String filter) {
+        Cursor ret = null;
+        mSortOrder = MediaStore.Audio.Media.TITLE_KEY;
+        StringBuilder where = new StringBuilder();
+        where.append(MediaStore.Audio.Media.TITLE + " != ''");
+        
+        // Add in the filtering constraints
+        String [] keywords = null;
+        if (filter != null) {
+            String [] searchWords = filter.split(" ");
+            keywords = new String[searchWords.length];
+            Collator col = Collator.getInstance();
+            col.setStrength(Collator.PRIMARY);
+            for (int i = 0; i < searchWords.length; i++) {
+                keywords[i] = '%' + MediaStore.Audio.keyFor(searchWords[i]) + '%';
+            }
+            for (int i = 0; i < searchWords.length; i++) {
+                where.append(" AND ");
+                where.append(MediaStore.Audio.Media.ARTIST_KEY + "||");
+                where.append(MediaStore.Audio.Media.ALBUM_KEY + "||");
+                where.append(MediaStore.Audio.Media.TITLE_KEY + " LIKE ?");
+            }
+        }
+        
+        if (mGenre != null) {
+            mSortOrder = MediaStore.Audio.Genres.Members.DEFAULT_SORT_ORDER;
+            if (async != null) {
+                async.startQuery(0, null,
+                        MediaStore.Audio.Genres.Members.getContentUri("external",
+                        Integer.valueOf(mGenre)),
+                        mCursorCols, where.toString(), keywords, mSortOrder);
+                ret = null;
+            } else {
+                ret = MusicUtils.query(this,
+                        MediaStore.Audio.Genres.Members.getContentUri("external", Integer.valueOf(mGenre)),
+                        mCursorCols, where.toString(), keywords, mSortOrder);
+            }
+        } else if (mPlaylist != null) {
+            if (mPlaylist.equals("nowplaying")) {
+                if (MusicUtils.sService != null) {
+                    ret = new NowPlayingCursor(MusicUtils.sService, mCursorCols);
+                    if (ret.getCount() == 0) {
+                        finish();
+                    }
+                } else {
+                    // Nothing is playing.
+                }
+            } else if (mPlaylist.equals("podcasts")) {
+                where.append(" AND " + MediaStore.Audio.Media.IS_PODCAST + "=1");
+                if (async != null) {
+                    async.startQuery(0, null,
+                            MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursorCols,
+                            where.toString(), keywords, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
+                    ret = null;
+                 } else {
+                    ret = MusicUtils.query(this,
+                            MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursorCols,
+                            where.toString(), keywords, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
+                }
+            } else if (mPlaylist.equals("recentlyadded")) {
+                // do a query for all songs added in the last X weeks
+                int X = MusicUtils.getIntPref(this, "numweeks", 2) * (3600 * 24 * 7);
+                where.append(" AND " + MediaStore.MediaColumns.DATE_ADDED + ">");
+                where.append(System.currentTimeMillis() / 1000 - X);
+                if (async != null) {
+                    async.startQuery(0, null,
+                            MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursorCols,
+                            where.toString(), keywords, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
+                    ret = null;
+                 } else {
+                    ret = MusicUtils.query(this,
+                            MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursorCols,
+                            where.toString(), keywords, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
+                }
+            } else {
+                mSortOrder = MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER;
+                if (async != null) {
+                    async.startQuery(0, null,
+                            MediaStore.Audio.Playlists.Members.getContentUri("external", Long.valueOf(mPlaylist)),
+                            mPlaylistMemberCols, where.toString(), keywords, mSortOrder);
+                    ret = null;
+                } else {
+                    ret = MusicUtils.query(this,
+                            MediaStore.Audio.Playlists.Members.getContentUri("external", Long.valueOf(mPlaylist)),
+                            mPlaylistMemberCols, where.toString(), keywords, mSortOrder);
+                }
+            }
+        } else {
+            if (mAlbumId != null) {
+                where.append(" AND " + MediaStore.Audio.Media.ALBUM_ID + "=" + mAlbumId);
+                mSortOrder = MediaStore.Audio.Media.TRACK + ", " + mSortOrder;
+            }
+            if (mArtistId != null) {
+                where.append(" AND " + MediaStore.Audio.Media.ARTIST_ID + "=" + mArtistId);
+            }
+            where.append(" AND " + MediaStore.Audio.Media.IS_MUSIC + "=1");
+            if (async != null) {
+                async.startQuery(0, null,
+                        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                        mCursorCols, where.toString() , keywords, mSortOrder);
+                ret = null;
+            } else {
+                ret = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                        mCursorCols, where.toString() , keywords, mSortOrder);
+            }
+        }
+        
+        // This special case is for the "nowplaying" cursor, which cannot be handled
+        // asynchronously using AsyncQueryHandler, so we do some extra initialization here.
+        if (ret != null && async != null) {
+            init(ret);
+            setTitle();
+        }
+        return ret;
+    }
+
+    private class NowPlayingCursor extends AbstractCursor
+    {
+        public NowPlayingCursor(IMediaPlaybackService service, String [] cols)
+        {
+            mCols = cols;
+            mService  = service;
+            makeNowPlayingCursor();
+        }
+        private void makeNowPlayingCursor() {
+            mCurrentPlaylistCursor = null;
+            try {
+                mNowPlaying = mService.getQueue();
+            } catch (RemoteException ex) {
+                mNowPlaying = new int[0];
+            }
+            mSize = mNowPlaying.length;
+            if (mSize == 0) {
+                return;
+            }
+
+            StringBuilder where = new StringBuilder();
+            where.append(MediaStore.Audio.Media._ID + " IN (");
+            for (int i = 0; i < mSize; i++) {
+                where.append(mNowPlaying[i]);
+                if (i < mSize - 1) {
+                    where.append(",");
+                }
+            }
+            where.append(")");
+
+            mCurrentPlaylistCursor = MusicUtils.query(TrackBrowserActivity.this,
+                    MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                    mCols, where.toString(), null, MediaStore.Audio.Media._ID);
+
+            if (mCurrentPlaylistCursor == null) {
+                mSize = 0;
+                return;
+            }
+            
+            int size = mCurrentPlaylistCursor.getCount();
+            mCursorIdxs = new int[size];
+            mCurrentPlaylistCursor.moveToFirst();
+            int colidx = mCurrentPlaylistCursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);
+            for (int i = 0; i < size; i++) {
+                mCursorIdxs[i] = mCurrentPlaylistCursor.getInt(colidx);
+                mCurrentPlaylistCursor.moveToNext();
+            }
+            mCurrentPlaylistCursor.moveToFirst();
+            mCurPos = -1;
+            
+            // At this point we can verify the 'now playing' list we got
+            // earlier to make sure that all the items in there still exist
+            // in the database, and remove those that aren't. This way we
+            // don't get any blank items in the list.
+            try {
+                int removed = 0;
+                for (int i = mNowPlaying.length - 1; i >= 0; i--) {
+                    int trackid = mNowPlaying[i];
+                    int crsridx = Arrays.binarySearch(mCursorIdxs, trackid);
+                    if (crsridx < 0) {
+                        //Log.i("@@@@@", "item no longer exists in db: " + trackid);
+                        removed += mService.removeTrack(trackid);
+                    }
+                }
+                if (removed > 0) {
+                    mNowPlaying = mService.getQueue();
+                    mSize = mNowPlaying.length;
+                    if (mSize == 0) {
+                        mCursorIdxs = null;
+                        return;
+                    }
+                }
+            } catch (RemoteException ex) {
+                mNowPlaying = new int[0];
+            }
+        }
+
+        @Override
+        public int getCount()
+        {
+            return mSize;
+        }
+
+        @Override
+        public boolean onMove(int oldPosition, int newPosition)
+        {
+            if (oldPosition == newPosition)
+                return true;
+            
+            if (mNowPlaying == null || mCursorIdxs == null) {
+                return false;
+            }
+
+            // The cursor doesn't have any duplicates in it, and is not ordered
+            // in queue-order, so we need to figure out where in the cursor we
+            // should be.
+           
+            int newid = mNowPlaying[newPosition];
+            int crsridx = Arrays.binarySearch(mCursorIdxs, newid);
+            mCurrentPlaylistCursor.moveToPosition(crsridx);
+            mCurPos = newPosition;
+            
+            return true;
+        }
+
+        public boolean removeItem(int which)
+        {
+            try {
+                if (mService.removeTracks(which, which) == 0) {
+                    return false; // delete failed
+                }
+                int i = (int) which;
+                mSize--;
+                while (i < mSize) {
+                    mNowPlaying[i] = mNowPlaying[i+1];
+                    i++;
+                }
+                onMove(-1, (int) mCurPos);
+            } catch (RemoteException ex) {
+            }
+            return true;
+        }
+        
+        public void moveItem(int from, int to) {
+            try {
+                mService.moveQueueItem(from, to);
+                mNowPlaying = mService.getQueue();
+                onMove(-1, mCurPos); // update the underlying cursor
+            } catch (RemoteException ex) {
+            }
+        }
+
+        private void dump() {
+            String where = "(";
+            for (int i = 0; i < mSize; i++) {
+                where += mNowPlaying[i];
+                if (i < mSize - 1) {
+                    where += ",";
+                }
+            }
+            where += ")";
+            Log.i("NowPlayingCursor: ", where);
+        }
+
+        @Override
+        public String getString(int column)
+        {
+            try {
+                return mCurrentPlaylistCursor.getString(column);
+            } catch (Exception ex) {
+                onChange(true);
+                return "";
+            }
+        }
+
+        @Override
+        public short getShort(int column)
+        {
+            return mCurrentPlaylistCursor.getShort(column);
+        }
+
+        @Override
+        public int getInt(int column)
+        {
+            try {
+                return mCurrentPlaylistCursor.getInt(column);
+            } catch (Exception ex) {
+                onChange(true);
+                return 0;
+            }
+        }
+
+        @Override
+        public long getLong(int column)
+        {
+            try {
+                return mCurrentPlaylistCursor.getLong(column);
+            } catch (Exception ex) {
+                onChange(true);
+                return 0;
+            }
+        }
+
+        @Override
+        public float getFloat(int column)
+        {
+            return mCurrentPlaylistCursor.getFloat(column);
+        }
+
+        @Override
+        public double getDouble(int column)
+        {
+            return mCurrentPlaylistCursor.getDouble(column);
+        }
+
+        @Override
+        public boolean isNull(int column)
+        {
+            return mCurrentPlaylistCursor.isNull(column);
+        }
+
+        @Override
+        public String[] getColumnNames()
+        {
+            return mCols;
+        }
+        
+        @Override
+        public void deactivate()
+        {
+            if (mCurrentPlaylistCursor != null)
+                mCurrentPlaylistCursor.deactivate();
+        }
+
+        @Override
+        public boolean requery()
+        {
+            makeNowPlayingCursor();
+            return true;
+        }
+
+        private String [] mCols;
+        private Cursor mCurrentPlaylistCursor;     // updated in onMove
+        private int mSize;          // size of the queue
+        private int[] mNowPlaying;
+        private int[] mCursorIdxs;
+        private int mCurPos;
+        private IMediaPlaybackService mService;
+    }
+    
+    static class TrackListAdapter extends SimpleCursorAdapter implements SectionIndexer {
+        boolean mIsNowPlaying;
+        boolean mDisableNowPlayingIndicator;
+
+        int mTitleIdx;
+        int mArtistIdx;
+        int mAlbumIdx;
+        int mDurationIdx;
+        int mAudioIdIdx;
+
+        private final StringBuilder mBuilder = new StringBuilder();
+        private final String mUnknownArtist;
+        private final String mUnknownAlbum;
+        
+        private AlphabetIndexer mIndexer;
+        
+        private TrackBrowserActivity mActivity = null;
+        private AsyncQueryHandler mQueryHandler;
+        private String mConstraint = null;
+        private boolean mConstraintIsValid = false;
+        
+        class ViewHolder {
+            TextView line1;
+            TextView line2;
+            TextView duration;
+            ImageView play_indicator;
+            CharArrayBuffer buffer1;
+            char [] buffer2;
+        }
+
+        class QueryHandler extends AsyncQueryHandler {
+            QueryHandler(ContentResolver res) {
+                super(res);
+            }
+            
+            @Override
+            protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+                //Log.i("@@@", "query complete: " + cursor.getCount() + "   " + mActivity);
+                mActivity.init(cursor);
+            }
+        }
+        
+        TrackListAdapter(Context context, TrackBrowserActivity currentactivity,
+                int layout, Cursor cursor, String[] from, int[] to,
+                boolean isnowplaying, boolean disablenowplayingindicator) {
+            super(context, layout, cursor, from, to);
+            mActivity = currentactivity;
+            getColumnIndices(cursor);
+            mIsNowPlaying = isnowplaying;
+            mDisableNowPlayingIndicator = disablenowplayingindicator;
+            mUnknownArtist = context.getString(R.string.unknown_artist_name);
+            mUnknownAlbum = context.getString(R.string.unknown_album_name);
+            
+            mQueryHandler = new QueryHandler(context.getContentResolver());
+        }
+        
+        public void setActivity(TrackBrowserActivity newactivity) {
+            mActivity = newactivity;
+        }
+        
+        public AsyncQueryHandler getQueryHandler() {
+            return mQueryHandler;
+        }
+        
+        private void getColumnIndices(Cursor cursor) {
+            if (cursor != null) {
+                mTitleIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE);
+                mArtistIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST);
+                mAlbumIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM);
+                mDurationIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION);
+                try {
+                    mAudioIdIdx = cursor.getColumnIndexOrThrow(
+                            MediaStore.Audio.Playlists.Members.AUDIO_ID);
+                } catch (IllegalArgumentException ex) {
+                    mAudioIdIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);
+                }
+                
+                if (mIndexer != null) {
+                    mIndexer.setCursor(cursor);
+                } else if (!mActivity.mEditMode) {
+                    String alpha = mActivity.getString(
+                            com.android.internal.R.string.fast_scroll_alphabet);
+                
+                    mIndexer = new MusicAlphabetIndexer(cursor, mTitleIdx, alpha);
+                }
+            }
+        }
+
+        @Override
+        public View newView(Context context, Cursor cursor, ViewGroup parent) {
+            View v = super.newView(context, cursor, parent);
+            ImageView iv = (ImageView) v.findViewById(R.id.icon);
+            if (mActivity.mEditMode) {
+                iv.setVisibility(View.VISIBLE);
+                iv.setImageResource(R.drawable.ic_mp_move);
+            } else {
+                iv.setVisibility(View.GONE);
+            }
+            
+            ViewHolder vh = new ViewHolder();
+            vh.line1 = (TextView) v.findViewById(R.id.line1);
+            vh.line2 = (TextView) v.findViewById(R.id.line2);
+            vh.duration = (TextView) v.findViewById(R.id.duration);
+            vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator);
+            vh.buffer1 = new CharArrayBuffer(100);
+            vh.buffer2 = new char[200];
+            v.setTag(vh);
+            return v;
+        }
+
+        @Override
+        public void bindView(View view, Context context, Cursor cursor) {
+            
+            ViewHolder vh = (ViewHolder) view.getTag();
+            
+            cursor.copyStringToBuffer(mTitleIdx, vh.buffer1);
+            vh.line1.setText(vh.buffer1.data, 0, vh.buffer1.sizeCopied);
+            
+            int secs = cursor.getInt(mDurationIdx) / 1000;
+            if (secs == 0) {
+                vh.duration.setText("");
+            } else {
+                vh.duration.setText(MusicUtils.makeTimeString(context, secs));
+            }
+            
+            final StringBuilder builder = mBuilder;
+            builder.delete(0, builder.length());
+
+            String name = cursor.getString(mArtistIdx);
+            if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
+                builder.append(mUnknownArtist);
+            } else {
+                builder.append(name);
+            }
+            int len = builder.length();
+            if (vh.buffer2.length < len) {
+                vh.buffer2 = new char[len];
+            }
+            builder.getChars(0, len, vh.buffer2, 0);
+            vh.line2.setText(vh.buffer2, 0, len);
+
+            ImageView iv = vh.play_indicator;
+            int id = -1;
+            if (MusicUtils.sService != null) {
+                // TODO: IPC call on each bind??
+                try {
+                    if (mIsNowPlaying) {
+                        id = MusicUtils.sService.getQueuePosition();
+                    } else {
+                        id = MusicUtils.sService.getAudioId();
+                    }
+                } catch (RemoteException ex) {
+                }
+            }
+            
+            // Determining whether and where to show the "now playing indicator
+            // is tricky, because we don't actually keep track of where the songs
+            // in the current playlist came from after they've started playing.
+            //
+            // If the "current playlists" is shown, then we can simply match by position,
+            // otherwise, we need to match by id. Match-by-id gets a little weird if
+            // a song appears in a playlist more than once, and you're in edit-playlist
+            // mode. In that case, both items will have the "now playing" indicator.
+            // For this reason, we don't show the play indicator at all when in edit
+            // playlist mode (except when you're viewing the "current playlist",
+            // which is not really a playlist)
+            if ( (mIsNowPlaying && cursor.getPosition() == id) ||
+                 (!mIsNowPlaying && !mDisableNowPlayingIndicator && cursor.getInt(mAudioIdIdx) == id)) {
+                iv.setImageResource(R.drawable.indicator_ic_mp_playing_list);
+                iv.setVisibility(View.VISIBLE);
+            } else {
+                iv.setVisibility(View.GONE);
+            }
+        }
+        
+        @Override
+        public void changeCursor(Cursor cursor) {
+            if (cursor != mActivity.mTrackCursor) {
+                mActivity.mTrackCursor = cursor;
+                super.changeCursor(cursor);
+                getColumnIndices(cursor);
+            }
+        }
+        
+        @Override
+        public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
+            String s = constraint.toString();
+            if (mConstraintIsValid && (
+                    (s == null && mConstraint == null) ||
+                    (s != null && s.equals(mConstraint)))) {
+                return getCursor();
+            }
+            Cursor c = mActivity.getTrackCursor(null, s);
+            mConstraint = s;
+            mConstraintIsValid = true;
+            return c;
+        }
+        
+        // SectionIndexer methods
+        
+        public Object[] getSections() {
+            if (mIndexer != null) { 
+                return mIndexer.getSections();
+            } else {
+                return null;
+            }
+        }
+        
+        public int getPositionForSection(int section) {
+            int pos = mIndexer.getPositionForSection(section);
+            return pos;
+        }
+        
+        public int getSectionForPosition(int position) {
+            return 0;
+        }        
+    }
+}
+
diff --git a/src/com/android/music/VideoBrowserActivity.java b/src/com/android/music/VideoBrowserActivity.java
new file mode 100644
index 0000000..e8aaf74
--- /dev/null
+++ b/src/com/android/music/VideoBrowserActivity.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.app.ListActivity;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Intent;
+import android.database.Cursor;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.view.View;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+
+import java.lang.Integer;
+
+public class VideoBrowserActivity extends ListActivity implements MusicUtils.Defs
+{
+    public VideoBrowserActivity()
+    {
+    }
+
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle icicle)
+    {
+        super.onCreate(icicle);
+        setVolumeControlStream(AudioManager.STREAM_MUSIC);
+        init();
+    }
+
+    public void init() {
+
+        // Set the layout for this activity.  You can find it
+        // in assets/res/any/layout/media_picker_activity.xml
+        setContentView(R.layout.media_picker_activity);
+
+        MakeCursor();
+
+        if (mCursor == null) {
+            MusicUtils.displayDatabaseError(this);
+            return;
+        }
+
+        if (mCursor.getCount() > 0) {
+            setTitle(R.string.videos_title);
+        } else {
+            setTitle(R.string.no_videos_title);
+        }
+
+        // Map Cursor columns to views defined in media_list_item.xml
+        SimpleCursorAdapter adapter = new SimpleCursorAdapter(
+                this,
+                android.R.layout.simple_list_item_1,
+                mCursor,
+                new String[] { MediaStore.Video.Media.TITLE},
+                new int[] { android.R.id.text1 });
+
+        setListAdapter(adapter);
+    }
+
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id)
+    {
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        mCursor.moveToPosition(position);
+        String type = mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Video.Media.MIME_TYPE));
+        intent.setDataAndType(ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id), type);
+        
+        startActivity(intent);
+    }
+
+    private void MakeCursor() {
+        String[] cols = new String[] {
+                MediaStore.Video.Media._ID,
+                MediaStore.Video.Media.TITLE,
+                MediaStore.Video.Media.DATA,
+                MediaStore.Video.Media.MIME_TYPE,
+                MediaStore.Video.Media.ARTIST
+        };
+        ContentResolver resolver = getContentResolver();
+        if (resolver == null) {
+            System.out.println("resolver = null");
+        } else {
+            mSortOrder = MediaStore.Video.Media.TITLE + " COLLATE UNICODE";
+            mWhereClause = MediaStore.Video.Media.TITLE + " != ''";
+            mCursor = resolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
+                cols, mWhereClause , null, mSortOrder);
+        }
+    }
+
+    private Cursor mCursor;
+    private String mWhereClause;
+    private String mSortOrder;
+}
+
diff --git a/src/com/android/music/WeekSelector.java b/src/com/android/music/WeekSelector.java
new file mode 100644
index 0000000..9fe5bdc
--- /dev/null
+++ b/src/com/android/music/WeekSelector.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import com.android.internal.widget.VerticalTextSpinner;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+public class WeekSelector extends Activity
+{
+    VerticalTextSpinner mWeeks;
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+        setContentView(R.layout.weekpicker);
+        getWindow().setLayout(WindowManager.LayoutParams.FILL_PARENT,
+                                    WindowManager.LayoutParams.WRAP_CONTENT);
+
+        mWeeks = (VerticalTextSpinner)findViewById(R.id.weeks);
+        mWeeks.setItems(getResources().getStringArray(R.array.weeklist));
+        mWeeks.setWrapAround(false);
+        mWeeks.setScrollInterval(200);
+        
+        int def = MusicUtils.getIntPref(this, "numweeks", 2); 
+        int pos = icicle != null ? icicle.getInt("numweeks", def - 1) : def - 1;
+        mWeeks.setSelectedPos(pos);
+        
+        ((Button) findViewById(R.id.set)).setOnClickListener(mListener);
+        
+        ((Button) findViewById(R.id.cancel)).setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                setResult(RESULT_CANCELED);
+                finish();
+            }
+        });
+    }
+    
+    @Override
+    public void onSaveInstanceState(Bundle outcicle) {
+        outcicle.putInt("numweeks", mWeeks.getCurrentSelectedPos());
+    }
+    
+    @Override
+    public void onResume() {
+        super.onResume();
+    }
+    
+    private View.OnClickListener mListener = new View.OnClickListener() {
+        public void onClick(View v) {
+            int numweeks = mWeeks.getCurrentSelectedPos() + 1;
+            MusicUtils.setIntPref(WeekSelector.this, "numweeks", numweeks);
+            setResult(RESULT_OK);
+            finish();
+        }
+    };
+}
diff --git a/tests/Android.mk b/tests/Android.mk
new file mode 100644
index 0000000..589842e
--- /dev/null
+++ b/tests/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+# Include all test java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := MusicTests
+
+LOCAL_INSTRUMENTATION_FOR := Music
+
+include $(BUILD_PACKAGE)
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
new file mode 100644
index 0000000..b8a6fe7
--- /dev/null
+++ b/tests/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.music.tests">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name=".MusicPlayerLaunchPerformance"
+        android:targetPackage="com.android.music"
+        android:label="Music Launch Performance">
+    </instrumentation>
+    
+    <instrumentation android:name=".MusicPlayerFunctionalTestRunner"
+        android:targetPackage="com.android.music"
+        android:label="Music Player Functional Test">
+    </instrumentation>
+    
+    <instrumentation android:name=".MusicPlayerStressTestRunner"
+        android:targetPackage="com.android.music"
+        android:label="Music Player Stress Test">
+    </instrumentation>
+
+</manifest> 
diff --git a/tests/src/com/android/music/MusicPlayerFunctionalTestRunner.java b/tests/src/com/android/music/MusicPlayerFunctionalTestRunner.java
new file mode 100644
index 0000000..7801bf1
--- /dev/null
+++ b/tests/src/com/android/music/MusicPlayerFunctionalTestRunner.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music.tests;
+
+import android.test.InstrumentationTestRunner;
+import android.test.InstrumentationTestSuite;
+import com.android.music.tests.functional.TestSongs;
+import com.android.music.tests.functional.TestPlaylist;
+
+import junit.framework.TestSuite;
+
+
+/**
+ * Instrumentation Test Runner for all Music Player tests.
+ * 
+ * Precondition: Opened keyboard and wipe the userdata
+ * 
+ * Running all tests:
+ *
+ * adb shell am instrument \
+ *   -w com.android.music.tests/.MusicPlayerFunctionalTestRunner
+ */
+
+public class MusicPlayerFunctionalTestRunner extends InstrumentationTestRunner {
+
+
+    @Override
+    public TestSuite getAllTests() {
+        TestSuite suite = new InstrumentationTestSuite(this);  
+        suite.addTestSuite(TestSongs.class);
+        suite.addTestSuite(TestPlaylist.class);
+        return suite;
+    }
+
+    @Override
+    public ClassLoader getLoader() {
+        return MusicPlayerFunctionalTestRunner.class.getClassLoader();
+    }
+}
+
+
+
diff --git a/tests/src/com/android/music/MusicPlayerLaunchPerformance.java b/tests/src/com/android/music/MusicPlayerLaunchPerformance.java
new file mode 100644
index 0000000..80342ba
--- /dev/null
+++ b/tests/src/com/android/music/MusicPlayerLaunchPerformance.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music.tests;
+
+import android.app.Activity;
+import android.test.LaunchPerformanceBase;
+import android.os.Bundle;
+
+import java.util.Map;
+
+/**
+ * Instrumentation class for Media Player launch performance testing.
+ */
+public class MusicPlayerLaunchPerformance extends LaunchPerformanceBase {
+ 
+    public static final String LOG_TAG = "MusicPlayerLaunchPerformance";
+
+    public MusicPlayerLaunchPerformance() {
+        super();
+    }
+
+    @Override
+    public void onCreate(Bundle arguments) {
+        super.onCreate(arguments);
+
+        mIntent.setClassName(getTargetContext(), "com.android.music.MusicBrowserActivity");
+        start();
+    }
+
+    /**
+     * Calls LaunchApp and finish.
+     */
+    @Override
+    public void onStart() {
+        super.onStart();
+        LaunchApp();
+        finish(Activity.RESULT_OK, mResults);
+    }
+}
diff --git a/tests/src/com/android/music/MusicPlayerNames.java b/tests/src/com/android/music/MusicPlayerNames.java
new file mode 100644
index 0000000..f5f5d48
--- /dev/null
+++ b/tests/src/com/android/music/MusicPlayerNames.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music.tests;
+
+/**
+ * 
+ * This class has the names of the all the activity name and variables 
+ * in the instrumentation test.
+ *
+ */
+public class MusicPlayerNames {
+  
+  //Expected result of the sorted playlistname
+    public static final String expectedPlaylistTitle[] = { "**1E?:|}{[]~~.,;'",
+        "//><..", "0123456789",
+        "0random@112", "MyPlaylist", "UPPERLETTER",
+        "combination011", "loooooooog",
+        "normal", "~!@#$%^&*()_+"    
+    }; 
+  
+  //Unsorted input playlist name
+    public static final String unsortedPlaylistTitle[] = { "//><..","MyPlaylist",
+        "0random@112", "UPPERLETTER","normal", 
+        "combination011", "0123456789",
+        "~!@#$%^&*()_+","**1E?:|}{[]~~.,;'",
+        "loooooooog"    
+    };
+    
+    public static final String DELETE_PLAYLIST_NAME = "testDeletPlaylist";
+    public static final String ORIGINAL_PLAYLIST_NAME = "original_playlist_name";
+    public static final String RENAMED_PLAYLIST_NAME = "rename_playlist_name";
+    
+    public static int NO_OF_PLAYLIST = 10;
+    public static int WAIT_SHORT_TIME = 1000;
+    public static int WAIT_LONG_TIME = 2000;
+    public static int WAIT_VERY_LONG_TIME = 6000;
+    public static int SKIP_WAIT_TIME = 500;
+    public static int DEFAULT_PLAYLIST_LENGTH = 15;
+    public static int NO_ALBUMS_TOBE_PLAYED = 50;
+    public static int NO_SKIPPING_SONGS = 500;
+    
+    public static final String DELETESONG = "/sdcard/toBeDeleted.amr"; 
+    public static final String GOLDENSONG = "/sdcard/media_api/music/AMRNB.amr";
+    public static final String TOBEDELETESONGNAME = "toBeDeleted";   
+    
+    public static int EXPECTED_NO_RINGTONE = 1;
+}
diff --git a/tests/src/com/android/music/MusicPlayerStressTestRunner.java b/tests/src/com/android/music/MusicPlayerStressTestRunner.java
new file mode 100644
index 0000000..269627d
--- /dev/null
+++ b/tests/src/com/android/music/MusicPlayerStressTestRunner.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.music.tests;
+
+import com.android.music.tests.stress.AlbumsPlaybackStress;
+import com.android.music.tests.stress.MusicPlaybackStress;
+
+import android.test.InstrumentationTestRunner;
+import android.test.InstrumentationTestSuite;
+
+import junit.framework.TestSuite;
+
+/**
+ * Instrumentation Test Runner for all music player stress tests.
+ * 
+ * Running all tests:
+ *
+ * adb shell am instrument \
+ *   -w com.android.music.tests/.MusicPlayerStressTestRunner
+ */
+
+public class MusicPlayerStressTestRunner extends InstrumentationTestRunner {
+
+  @Override
+    public TestSuite getAllTests() {
+      TestSuite suite = new InstrumentationTestSuite(this);  
+      //suite.addTestSuite(MusicPlaybackStress.class);
+      suite.addTestSuite(AlbumsPlaybackStress.class);
+      return suite;
+    }
+
+    @Override
+    public ClassLoader getLoader() {
+      return MusicPlayerStressTestRunner.class.getClassLoader();
+    }
+}
+
diff --git a/tests/src/com/android/music/functional/TestPlaylist.java b/tests/src/com/android/music/functional/TestPlaylist.java
new file mode 100644
index 0000000..64432d7
--- /dev/null
+++ b/tests/src/com/android/music/functional/TestPlaylist.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music.tests.functional;
+
+import android.app.Activity;
+import android.content.*;
+import android.app.Instrumentation;
+import android.app.Instrumentation.ActivityMonitor;
+import android.content.Intent;
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.KeyEvent;
+import android.provider.MediaStore;
+import android.content.ContentResolver;
+import android.database.Cursor;
+
+import com.android.music.CreatePlaylist;
+import com.android.music.MusicUtils;
+import com.android.music.PlaylistBrowserActivity;
+import com.android.music.TrackBrowserActivity;
+
+import com.android.music.tests.MusicPlayerNames;
+import com.android.music.tests.functional.TestSongs;
+
+/**
+ * Junit / Instrumentation test case for the PlaylistBrowserActivity
+ * This test case need to run in the landscape mode and opened keyboard
+ 
+ */
+public class TestPlaylist extends ActivityInstrumentationTestCase <PlaylistBrowserActivity>{
+    private static String TAG = "musicplayertests";
+  
+    public TestPlaylist() {
+        super("com.android.music",PlaylistBrowserActivity.class);
+    }
+
+    @Override 
+    protected void setUp() throws Exception {   
+        super.setUp(); 
+    }
+    
+    @Override 
+    protected void tearDown() throws Exception {   
+        super.tearDown();           
+    }
+    
+    
+    private void clearSearchString(int length){
+        Instrumentation inst = getInstrumentation();
+        for (int j=0; j< length; j++)
+            inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DEL);
+    }
+    /**
+     * Remove playlist
+     */
+    public void deletePlaylist(String playlistname) throws Exception{
+        Instrumentation inst = getInstrumentation();
+        inst.sendStringSync(playlistname);
+        Thread.sleep(MusicPlayerNames.WAIT_SHORT_TIME);
+        inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);       
+        inst.invokeContextMenuAction(getActivity(), MusicUtils.Defs.CHILD_MENU_BASE + 1, 0);
+        Thread.sleep(MusicPlayerNames.WAIT_SHORT_TIME);
+        clearSearchString(playlistname.length());
+        
+    }
+
+    /**
+     *  Start the trackBrowserActivity and add the new playlist
+     */
+    public void addNewPlaylist(String playListName) throws Exception{
+        Instrumentation inst = getInstrumentation();
+        Activity trackBrowserActivity;
+        ActivityMonitor trackBrowserMon = inst.addMonitor("com.android.music.TrackBrowserActivity", 
+                null, false);
+        Intent intent = new Intent();
+        intent.setAction(Intent.ACTION_PICK);
+        intent.setClassName("com.android.music", "com.android.music.TrackBrowserActivity");
+        getActivity().startActivity(intent);     
+        Thread.sleep(MusicPlayerNames.WAIT_LONG_TIME);
+        trackBrowserActivity = trackBrowserMon.waitForActivityWithTimeout(2000);
+        inst.invokeContextMenuAction(trackBrowserActivity, MusicUtils.Defs.NEW_PLAYLIST, 0);
+        Thread.sleep(MusicPlayerNames.WAIT_SHORT_TIME);
+        //Remove the default playlist name
+        clearSearchString(MusicPlayerNames.DEFAULT_PLAYLIST_LENGTH);
+        inst.sendStringSync(playListName);
+        inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
+        inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
+        Thread.sleep(MusicPlayerNames.WAIT_LONG_TIME);
+        trackBrowserActivity.finish();
+        clearSearchString(playListName.length());
+
+    }
+    
+    /**
+     * Rename playlist
+     */
+    public void renamePlaylist(String oldPlaylistName, String newPlaylistName) throws Exception{
+        Instrumentation inst = getInstrumentation();
+        inst.sendStringSync(oldPlaylistName);
+        Thread.sleep(MusicPlayerNames.WAIT_SHORT_TIME);
+        inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);       
+        inst.invokeContextMenuAction(getActivity(), MusicUtils.Defs.CHILD_MENU_BASE + 3, 0);
+        Thread.sleep(MusicPlayerNames.WAIT_SHORT_TIME);
+        //Remove the old playlist name
+        clearSearchString(oldPlaylistName.length());
+        inst.sendStringSync(newPlaylistName);
+        inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
+        inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
+        Thread.sleep(MusicPlayerNames.WAIT_LONG_TIME);
+        clearSearchString(oldPlaylistName.length());
+    }
+
+    public boolean verifyPlaylist(String playlistname) throws Exception{
+        Cursor mCursor;
+        boolean isEmptyPlaylist = true;
+        String[] cols = new String[] {
+                MediaStore.Audio.Playlists.NAME
+        };
+        ContentResolver resolver = getActivity().getContentResolver();
+        if (resolver == null) {
+            System.out.println("resolver = null");
+            assertNull(TAG, resolver);
+        } else {
+            String whereclause = MediaStore.Audio.Playlists.NAME + " = '" + playlistname +"'";
+            mCursor = resolver.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+                    cols, whereclause, null,
+                    MediaStore.Audio.Playlists.NAME);
+            isEmptyPlaylist = mCursor.moveToFirst();
+        }
+        return isEmptyPlaylist;
+    }
+
+    /**
+     * Test case 1: Add a playlist and delet the playlist just added.
+     * Verification: The mediastore playlist should be empty
+     */
+    @LargeTest
+    public void testDeletePlaylist() throws Exception{
+        boolean isEmptyPlaylist = true;
+        addNewPlaylist(MusicPlayerNames.DELETE_PLAYLIST_NAME);
+        deletePlaylist(MusicPlayerNames.DELETE_PLAYLIST_NAME);  
+        isEmptyPlaylist = verifyPlaylist(MusicPlayerNames.DELETE_PLAYLIST_NAME);
+        assertFalse("testDeletePlaylist", isEmptyPlaylist);
+    }
+    
+    /**
+     * Test case 2: Add playlist and rename the playlist just added.
+     * Verification: The mediastore playlist should contain the updated name.
+     */
+    @LargeTest
+    public void testRenamePlaylist() throws Exception{
+        boolean isEmptyPlaylist = true;
+        addNewPlaylist(MusicPlayerNames.ORIGINAL_PLAYLIST_NAME);
+        renamePlaylist(MusicPlayerNames.ORIGINAL_PLAYLIST_NAME, MusicPlayerNames.RENAMED_PLAYLIST_NAME);  
+        isEmptyPlaylist = verifyPlaylist(MusicPlayerNames.RENAMED_PLAYLIST_NAME);
+        deletePlaylist(MusicPlayerNames.RENAMED_PLAYLIST_NAME);
+        assertTrue("testDeletePlaylist", isEmptyPlaylist);
+    }
+
+}    
diff --git a/tests/src/com/android/music/functional/TestSongs.java b/tests/src/com/android/music/functional/TestSongs.java
new file mode 100644
index 0000000..488d691
--- /dev/null
+++ b/tests/src/com/android/music/functional/TestSongs.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music.tests.functional;
+
+import android.app.Activity;
+import android.content.*;
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.content.ContentResolver;
+import android.content.pm.ActivityInfo;
+import android.database.Cursor;
+import android.content.Intent;
+import android.content.BroadcastReceiver;
+import android.content.IntentFilter;
+
+import com.android.music.CreatePlaylist;
+import com.android.music.TrackBrowserActivity;
+import com.android.music.MusicUtils;
+
+import com.android.music.tests.MusicPlayerNames;
+
+import java.io.*;
+
+/**
+ * Junit / Instrumentation test case for the TrackBrowserActivity
+ 
+ */
+public class TestSongs extends ActivityInstrumentationTestCase <TrackBrowserActivity>{
+    private static String TAG = "musicplayertests";
+    
+    public TestSongs() {
+        super("com.android.music",TrackBrowserActivity.class);
+    }
+
+    @Override 
+    protected void setUp() throws Exception {   
+        super.setUp(); 
+    }
+    
+    @Override 
+    protected void tearDown() throws Exception {   
+        super.tearDown();           
+    }
+    
+    /**
+     * Add 10 new playlists with unsorted title order
+     */
+    public void addNewPlaylist() throws Exception{
+      Instrumentation inst = getInstrumentation();      
+      for (int i=0; i< MusicPlayerNames.NO_OF_PLAYLIST; i++){
+        inst.invokeContextMenuAction(getActivity(), MusicUtils.Defs.NEW_PLAYLIST, 0);
+        Thread.sleep(MusicPlayerNames.WAIT_SHORT_TIME);
+        //Remove the default playlist name
+        for (int j=0; j< MusicPlayerNames.DEFAULT_PLAYLIST_LENGTH; j++)
+          inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DEL);
+        inst.sendStringSync(MusicPlayerNames.unsortedPlaylistTitle[i]);
+        inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
+        inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
+        Thread.sleep(MusicPlayerNames.WAIT_LONG_TIME);
+        inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
+        Thread.sleep(MusicPlayerNames.WAIT_LONG_TIME);
+      }
+    }
+    
+    private void copy(File src, File dst) throws IOException {
+        InputStream in = new FileInputStream(src);
+        OutputStream out = new FileOutputStream(dst);
+      
+        // Transfer bytes from in to out
+        byte[] buf = new byte[1024];
+        int len;
+        while ((len = in.read(buf)) > 0) {
+            out.write(buf, 0, len);
+        }
+        in.close();
+        out.close();
+        Log.v(TAG, "Copy file");
+      }
+      
+      //Rescan the sdcard after copy the file
+      private void rescanSdcard() throws Exception{     
+        Intent scanIntent = new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://"
+             + Environment.getExternalStorageDirectory()));    
+        Log.v(TAG,"start the intent");
+        IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_SCANNER_STARTED);
+        intentFilter.addDataScheme("file");     
+        getActivity().sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://"
+            + Environment.getExternalStorageDirectory())));    
+          Thread.sleep(MusicPlayerNames.WAIT_VERY_LONG_TIME);
+      }
+      
+ 
+    /**
+     * Test case 1: tests the new playlist added with sorted order.
+     * Verification: The new playlist title should be sorted in alphabetical order
+     */
+    @LargeTest
+    public void testAddPlaylist() throws Exception{
+      Cursor mCursor;
+      addNewPlaylist();
+      
+      //Verify the new playlist is created, check the playlist table
+      String[] cols = new String[] {
+          MediaStore.Audio.Playlists.NAME
+      };
+      ContentResolver resolver = getActivity().getContentResolver();
+      if (resolver == null) {
+        System.out.println("resolver = null");
+      } else {
+        String whereclause = MediaStore.Audio.Playlists.NAME + " != ''";
+        mCursor = resolver.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+          cols, whereclause, null,
+          MediaStore.Audio.Playlists.NAME);
+        //Check the new playlist
+        mCursor.moveToFirst();
+        
+        for (int j=0;j<10;j++){
+          assertEquals("New sorted Playlist title:", MusicPlayerNames.expectedPlaylistTitle[j], mCursor.getString(0)); 
+          mCursor.moveToNext();
+        }
+      }
+    }   
+   
+    /**
+     * Test case 2: Set a song as ringtone
+     * Test case precondition: The testing device should wipe data before 
+     * run the test case.
+     * Verification: The count of audio.media.is_ringtone equal to 1. 
+     */
+    @LargeTest
+    public void testSetRingtone() throws Exception{
+      Cursor mCursor;
+      Instrumentation inst = getInstrumentation();      
+      inst.invokeContextMenuAction(getActivity(), MusicUtils.Defs.USE_AS_RINGTONE, 0);
+      //This only check if there only 1 ringtone set in music player
+      ContentResolver resolver = getActivity().getContentResolver();
+      if (resolver == null) {
+        System.out.println("resolver = null");
+      } else {
+        String whereclause = MediaStore.Audio.Media.IS_RINGTONE + " = 1";
+        mCursor = resolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+           null, whereclause, null, null);
+        //Check the new playlist
+        mCursor.moveToFirst();
+        int isRingtoneSet = mCursor.getCount();
+        assertEquals(TAG, MusicPlayerNames.EXPECTED_NO_RINGTONE, isRingtoneSet);
+      }
+    }
+    
+    /**
+     * Test case 3: Delete a song
+     * Test case precondition: Copy a song and rescan the sdcard
+     * Verification: The song is deleted from the sdcard and mediastore
+     */
+    @LargeTest
+    public void testDeleteSong() throws Exception{
+      Instrumentation inst = getInstrumentation();      
+      Cursor mCursor;
+      
+      //Copy a song from the golden directory
+      Log.v(TAG, "Copy a temp file to the sdcard");
+      File goldenfile = new File(MusicPlayerNames.GOLDENSONG);
+      File toBeDeleteSong = new File(MusicPlayerNames.DELETESONG);
+      copy(goldenfile, toBeDeleteSong);
+      rescanSdcard();
+       
+      //Delete the file from music player
+      Thread.sleep(MusicPlayerNames.WAIT_LONG_TIME);
+      inst.sendStringSync(MusicPlayerNames.TOBEDELETESONGNAME);
+      Thread.sleep(MusicPlayerNames.WAIT_LONG_TIME);
+      inst.invokeContextMenuAction(getActivity(), MusicUtils.Defs.DELETE_ITEM, 0);
+      inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
+      inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
+      Thread.sleep(MusicPlayerNames.WAIT_LONG_TIME);
+      
+      //Clear the search string
+      for (int j=0; j< MusicPlayerNames.TOBEDELETESONGNAME.length(); j++)
+          inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DEL);
+      
+      //Verfiy the item is removed from sdcard
+      File checkDeletedFile = new File(MusicPlayerNames.DELETESONG);
+      assertFalse(TAG, checkDeletedFile.exists());
+      
+      ContentResolver resolver = getActivity().getContentResolver();
+      if (resolver == null) {
+        System.out.println("resolver = null");
+      } else {
+        String whereclause = MediaStore.Audio.Media.DISPLAY_NAME + " = '" + 
+        MusicPlayerNames.TOBEDELETESONGNAME + "'";
+        mCursor = resolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+           null, whereclause, null, null);
+        boolean isEmptyCursor = mCursor.moveToFirst();
+        assertFalse(TAG,isEmptyCursor);
+      }     
+    } 
+}
+ 
diff --git a/tests/src/com/android/music/stress/AlbumsPlaybackStress.java b/tests/src/com/android/music/stress/AlbumsPlaybackStress.java
new file mode 100644
index 0000000..f396317
--- /dev/null
+++ b/tests/src/com/android/music/stress/AlbumsPlaybackStress.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.music.tests.stress;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.Instrumentation;
+import android.app.Instrumentation.ActivityMonitor;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.KeyEvent;
+import android.util.Log;
+
+import com.android.music.AlbumBrowserActivity;
+import com.android.music.tests.MusicPlayerNames;
+
+public class AlbumsPlaybackStress extends ActivityInstrumentationTestCase <AlbumBrowserActivity>{
+  
+  private Activity browseActivity;
+  private String[] testing;
+  private String TAG = "AlbumsPlaybackStress";
+  
+  public AlbumsPlaybackStress() {
+      super("com.android.music",AlbumBrowserActivity.class);
+  }
+  
+  @Override 
+  protected void setUp() throws Exception { 
+      super.setUp(); 
+  }
+  
+  @Override 
+  protected void tearDown() throws Exception {   
+      super.tearDown();           
+  }
+
+  /*
+   * Test case: Keeps launching music playback from Albums and then go 
+   * back to the album screen
+   * Verification: Check if it is in low memory
+   * The test depends on the test media in the sdcard
+   */
+    @LargeTest
+    public void testAlbumPlay() { 
+      Instrumentation inst = getInstrumentation();
+      try{
+        inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_RIGHT);
+        inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_RIGHT);
+        inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
+        inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
+        inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
+        Thread.sleep(MusicPlayerNames.WAIT_LONG_TIME);
+        inst.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
+        inst.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);     
+        for(int i=0; i< MusicPlayerNames.NO_ALBUMS_TOBE_PLAYED; i++){
+          inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
+          inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
+          inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
+          Thread.sleep(MusicPlayerNames.WAIT_LONG_TIME);
+          inst.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
+          inst.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);       
+        } 
+      }catch (Exception e){
+          Log.v(TAG, e.toString());
+      }
+      inst.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
+    
+      //Verification: check if it is in low memory
+      ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
+      ((ActivityManager)getActivity().getSystemService("activity")).getMemoryInfo(mi);
+      assertFalse(TAG, mi.lowMemory); 
+     
+   
+  }
+}
diff --git a/tests/src/com/android/music/stress/MusicPlaybackStress.java b/tests/src/com/android/music/stress/MusicPlaybackStress.java
new file mode 100644
index 0000000..80661f2
--- /dev/null
+++ b/tests/src/com/android/music/stress/MusicPlaybackStress.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.music.tests.stress;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.Instrumentation;
+import android.app.Instrumentation.ActivityMonitor;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.KeyEvent;
+import android.util.Log;
+import android.content.Context;
+import android.os.ServiceManager;
+
+
+import com.android.music.MusicBrowserActivity;
+import com.android.music.MusicUtils;
+import com.android.music.TrackBrowserActivity;
+import com.android.music.tests.MusicPlayerNames;
+
+public class MusicPlaybackStress extends ActivityInstrumentationTestCase <TrackBrowserActivity>{
+    private static String TAG = "mediaplayertests";
+  
+    public MusicPlaybackStress() {
+      super("com.android.music",TrackBrowserActivity.class);
+    }
+  
+    @Override 
+    protected void setUp() throws Exception { 
+      super.setUp(); 
+    }
+  
+    @Override 
+    protected void tearDown() throws Exception {   
+      super.tearDown();           
+    }
+
+    @LargeTest
+    public void testPlayAllSongs() {
+      Activity mediaPlaybackActivity;
+      try{
+        Instrumentation inst = getInstrumentation();
+        ActivityMonitor mediaPlaybackMon = inst.addMonitor("com.android.music.MediaPlaybackActivity", 
+          null, false);
+        inst.invokeMenuActionSync(getActivity(), MusicUtils.Defs.CHILD_MENU_BASE + 3, 0);
+        Thread.sleep(MusicPlayerNames.WAIT_LONG_TIME);
+        mediaPlaybackActivity = mediaPlaybackMon.waitForActivityWithTimeout(2000);
+        for (int i=0;i< MusicPlayerNames.NO_SKIPPING_SONGS;i++){               
+          Thread.sleep(MusicPlayerNames.SKIP_WAIT_TIME);
+          if (i==0){
+            //Set the repeat all
+            inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_RIGHT);
+            inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_UP);
+            inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
+     
+            //Set focus on the next button
+            inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
+          }
+          inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);      
+        }   
+        mediaPlaybackActivity.finish();
+      }catch (Exception e){
+        Log.e(TAG, e.toString());
+      }
+      //Verification: check if it is in low memory
+      ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
+      ((ActivityManager)getActivity().getSystemService("activity")).getMemoryInfo(mi);
+      assertFalse(TAG, mi.lowMemory);      
+    }
+}