Merge "Support MediaBrowserService in AOSP Music" am: 25e17f5eb0 am: fa4067f55d
am: e152712c5f

Change-Id: I9b184b4fc5b2bc33f51cdaf810a305aaf9c89b33
diff --git a/Android.mk b/Android.mk
index d2b7691..3e620f3 100644
--- a/Android.mk
+++ b/Android.mk
@@ -3,8 +3,7 @@
 
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src) \
-	src/com/android/music/IMediaPlaybackService.aidl
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_PACKAGE_NAME := Music
 
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 363dda7..b8e337b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,85 +1,89 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2007 The Android Open Source Project
+<!--
+    Copyright (C) 2017 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.
+    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">
+          package="com.android.music"
+          android:versionCode="2"
+          android:versionName="2.0">
 
-    <original-package android:name="com.android.music" />
-    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="24"/>
-    <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" />
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.BROADCAST_STICKY" />
+    <!-- Package Name -->
+    <original-package android:name="com.android.music"/>
 
-    <application android:icon="@drawable/app_music"
-        android:label="@string/musicbrowserlabel"
-        android:taskAffinity="android.task.music"
-        android:allowTaskReparenting="true"
-        android:usesCleartextTraffic="true">
+    <!-- SDK Versions -->
+    <uses-sdk android:minSdkVersion="19"
+              android:targetSdkVersion="24"/>
 
-        <meta-data
-            android:name="android.app.default_searchable"
-            android:value="com.android.music.QueryBrowserActivity"/>
+    <!-- Permissions -->
+    <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"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.BROADCAST_STICKY"/>
 
-        <!-- The main activity -->
+    <!-- App Declaration -->
+    <application
+            android:icon="@drawable/app_music"
+            android:label="@string/musicbrowserlabel"
+            android:taskAffinity="android.task.music"
+            android:allowTaskReparenting="true"
+            android:usesCleartextTraffic="true">
+
+        <!-- Meta Data -->
+        <meta-data android:name="com.google.android.gms.car.application"
+                   android:resource="@xml/automotive_app_desc"/>
+
+        <!-- Main Activity -->
         <activity android:name="com.android.music.MusicBrowserActivity"
-            android:theme="@android:style/Theme.NoTitleBar"
-            android:exported="true">
+                  android:theme="@android:style/Theme.NoTitleBar"
+                  android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <action android:name="android.intent.action.MUSIC_PLAYER" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
-                <category android:name="android.intent.category.APP_MUSIC" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+                <category android:name="android.intent.category.APP_MUSIC"/>
             </intent-filter>
         </activity>
 
-        <receiver android:name="com.android.music.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. -->
+        <!--
+            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="com.android.music.MediaPlaybackActivity"
-                android:theme="@android:style/Theme.NoTitleBar"
-                android:label="@string/mediaplaybacklabel"
-                android:taskAffinity=""
-                android:launchMode="singleTask"
-                android:clearTaskOnLaunch="true"
-                android:excludeFromRecents="true"
-                android:exported="true" >
+                  android:theme="@android:style/Theme.NoTitleBar"
+                  android:label="@string/mediaplaybacklabel"
+                  android:taskAffinity=""
+                  android:launchMode="singleTask"
+                  android:clearTaskOnLaunch="true"
+                  android:excludeFromRecents="true"
+                  android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
                 <data android:scheme="content"/>
                 <data android:host="media"/>
                 <data android:mimeType="audio/*"/>
@@ -88,195 +92,59 @@
                 <data android:mimeType="application/itunes"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="com.android.music.PLAYBACK_VIEWER" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.music.PLAYBACK_VIEWER"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="AudioPreview" android:theme="@android:style/Theme.Dialog"
-                android:taskAffinity=""
-                android:excludeFromRecents="true" android:exported="true" >
+        <activity android:name="com.android.music.ArtistAlbumBrowserActivity"
+                  android:exported="false">
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="file"/>
-                <data android:mimeType="audio/*"/>
-                <data android:mimeType="application/ogg"/>
-                <data android:mimeType="application/x-ogg"/>
-                <data android:mimeType="application/itunes"/>
-            </intent-filter>
-            <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/*"/>
-                <data android:mimeType="application/ogg"/>
-                <data android:mimeType="application/x-ogg"/>
-                <data android:mimeType="application/itunes"/>
-            </intent-filter>
-            <intent-filter
-                android:priority="-1">
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="content" />
-                <data android:mimeType="audio/*"/>
-                <data android:mimeType="application/ogg"/>
-                <data android:mimeType="application/x-ogg"/>
-                <data android:mimeType="application/itunes"/>
-            </intent-filter>
-        </activity>
-
-        <activity android:name="com.android.music.ArtistAlbumBrowserActivity" android:exported="false" >
-            <intent-filter>
-                <action android:name="android.intent.action.PICK" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <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="com.android.music.AlbumBrowserActivity" android:exported="false" >
+
+        <activity android:name="com.android.music.AlbumBrowserActivity" android:exported="false">
             <intent-filter>
-                <action android:name="android.intent.action.PICK" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <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="com.android.music.NowPlayingActivity" android:exported="false" >
+
+        <activity android:name="com.android.music.TrackBrowserActivity" android:exported="false">
             <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="com.android.music.TrackBrowserActivity" android:exported="false" >
-            <intent-filter>
-                <action android:name="android.intent.action.EDIT" />
-                <action android:name="android.intent.action.PICK" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <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="com.android.music.QueryBrowserActivity"
-                android:theme="@android:style/Theme.NoTitleBar"
-                android:exported="true" >
-            <intent-filter>
-                <action android:name="android.intent.action.SEARCH" />
-                <action android:name="android.intent.action.MEDIA_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="com.android.music.PlaylistBrowserActivity"
-                android:label="@string/musicbrowserlabel" 
-                android:exported="true" >
+                  android:label="@string/musicbrowserlabel"
+                  android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.PICK" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <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" />
+                <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="com.android.music.PlaylistShortcutActivity"
-            android:targetActivity="com.android.music.PlaylistBrowserActivity"
-            android:label="@string/musicshortcutlabel"
-            android:icon="@drawable/ic_launcher_shortcut_music_playlist"
-            android:exported="true" >
 
-            <intent-filter>
-                <action android:name="android.intent.action.CREATE_SHORTCUT" />
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-
-        </activity-alias>
-        <activity android:name="com.android.music.VideoBrowserActivity"
-            android:taskAffinity="android.task.video"
-            android:label="@string/videobrowserlabel"
-            android:icon="@drawable/app_video"
-            android:exported="false" >
-            <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="com.android.music.MediaPickerActivity"
-                android:label="@string/mediapickerlabel" android:exported="false" >
-<!--
-            <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="com.android.music.MusicPicker"
-                android:label="@string/music_picker_title" android:exported="true" >
-            <!-- 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="com.android.music.CreatePlaylist"
-            android:theme="@android:style/Theme.Dialog" android:exported="false" />
-        <activity android:name="com.android.music.RenamePlaylist"
-            android:theme="@android:style/Theme.Dialog" android:exported="false" />
-        <activity android:name="com.android.music.WeekSelector"
-            android:theme="@android:style/Theme.Dialog" android:exported="false" />
-        <activity android:name="com.android.music.DeleteItems"
-            android:theme="@android:style/Theme.Dialog" android:exported="false" />
-        <activity android:name="com.android.music.ScanningProgress"
-            android:theme="@android:style/Theme.Dialog" android:exported="false" />
         <service android:name="com.android.music.MediaPlaybackService"
-            android:exported="false" />
-
-        <receiver android:name="com.android.music.MediaAppWidgetProvider">
+                 android:exported="true"
+                 android:label="Android Open Source Music Player">
             <intent-filter>
-                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+                <action android:name="android.media.browse.MediaBrowserService" />
             </intent-filter>
-            <meta-data android:name="android.appwidget.provider" android:resource="@xml/appwidget_info" />
-        </receiver>
+        </service>
+
     </application>
+
 </manifest>
diff --git a/proguard.flags b/proguard.flags
index 8d4db53..e69de29 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -1,3 +0,0 @@
--keep class com.android.music.AudioPreview {
-  public void playPauseClicked(android.view.View);
-}
diff --git a/res/drawable-hdpi/ic_notification.png b/res/drawable-hdpi/ic_notification.png
new file mode 100644
index 0000000..a8cba40
--- /dev/null
+++ b/res/drawable-hdpi/ic_notification.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pause_white_24dp.png b/res/drawable-hdpi/ic_pause_white_24dp.png
new file mode 100644
index 0000000..b4bdbb5
--- /dev/null
+++ b/res/drawable-hdpi/ic_pause_white_24dp.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_play_arrow_white_24dp.png b/res/drawable-hdpi/ic_play_arrow_white_24dp.png
new file mode 100644
index 0000000..164385d
--- /dev/null
+++ b/res/drawable-hdpi/ic_play_arrow_white_24dp.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_skip_next_white_24dp.png b/res/drawable-hdpi/ic_skip_next_white_24dp.png
new file mode 100644
index 0000000..4eaf7ca
--- /dev/null
+++ b/res/drawable-hdpi/ic_skip_next_white_24dp.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_skip_previous_white_24dp.png b/res/drawable-hdpi/ic_skip_previous_white_24dp.png
new file mode 100644
index 0000000..e59dedb
--- /dev/null
+++ b/res/drawable-hdpi/ic_skip_previous_white_24dp.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pause_white_24dp.png b/res/drawable-xhdpi/ic_pause_white_24dp.png
new file mode 100644
index 0000000..14b6d17
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pause_white_24dp.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_play_arrow_white_24dp.png b/res/drawable-xhdpi/ic_play_arrow_white_24dp.png
new file mode 100644
index 0000000..a55d199
--- /dev/null
+++ b/res/drawable-xhdpi/ic_play_arrow_white_24dp.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_skip_next_white_24dp.png b/res/drawable-xhdpi/ic_skip_next_white_24dp.png
new file mode 100644
index 0000000..f282b92
--- /dev/null
+++ b/res/drawable-xhdpi/ic_skip_next_white_24dp.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_skip_previous_white_24dp.png b/res/drawable-xhdpi/ic_skip_previous_white_24dp.png
new file mode 100644
index 0000000..2522877
--- /dev/null
+++ b/res/drawable-xhdpi/ic_skip_previous_white_24dp.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_default_art.png b/res/drawable-xxhdpi/ic_default_art.png
new file mode 100644
index 0000000..dfb9e67
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_default_art.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_pause_white_24dp.png b/res/drawable-xxhdpi/ic_pause_white_24dp.png
new file mode 100644
index 0000000..72dfa9f
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_pause_white_24dp.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png b/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png
new file mode 100644
index 0000000..043acd8
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_skip_next_white_24dp.png b/res/drawable-xxhdpi/ic_skip_next_white_24dp.png
new file mode 100644
index 0000000..4fe6088
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_skip_next_white_24dp.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_skip_previous_white_24dp.png b/res/drawable-xxhdpi/ic_skip_previous_white_24dp.png
new file mode 100644
index 0000000..2c9310a
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_skip_previous_white_24dp.png
Binary files differ
diff --git a/res/layout-land/audio_player.xml b/res/layout-land/audio_player.xml
index 675f430..982c2cc 100644
--- a/res/layout-land/audio_player.xml
+++ b/res/layout-land/audio_player.xml
@@ -67,78 +67,86 @@
 
             </LinearLayout>
 
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal"
-                android:baselineAligned="false"
-                android:paddingTop="8dip"
-                android:paddingBottom="2dip">
+            <!-- This is the LinearLayout that contains album/artist/track info -->
+            <LinearLayout android:id="@+id/trackinfo"
+                          android:layout_width="match_parent"
+                          android:layout_height="wrap_content"
+                          android:orientation="vertical">
 
-                <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"
+                <LinearLayout
                     android:layout_width="match_parent"
-                    android:layout_height="wrap_content" />
-
-            </LinearLayout>
-
-            <LinearLayout
-                android:layout_width="match_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" />
+                    android:orientation="horizontal"
+                    android:baselineAligned="false"
+                    android:paddingTop="8dip"
+                    android:paddingBottom="2dip">
 
-                <TextView android:id="@+id/albumname"
-                    android:textSize="14sp"
-                    android:singleLine="true"
-                    android:ellipsize="end"
-                    android:textStyle="bold"
-                    android:layout_gravity="center_vertical"
+                    <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="match_parent"
+                        android:layout_height="wrap_content" />
+
+                </LinearLayout>
+
+                <LinearLayout
                     android:layout_width="match_parent"
-                    android:layout_height="wrap_content" />
-
-            </LinearLayout>
-
-            <LinearLayout
-                android:layout_width="match_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" />
+                    android:orientation="horizontal"
+                    android:baselineAligned="false"
+                    android:paddingTop="8dip"
+                    android:paddingBottom="2dip">
 
-                <TextView android:id="@+id/trackname"
-                    android:textSize="14sp"
-                    android:singleLine="true"
-                    android:ellipsize="end"
-                    android:textStyle="bold"
-                    android:layout_gravity="center_vertical"
+                    <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="match_parent"
+                        android:layout_height="wrap_content" />
+
+                </LinearLayout>
+
+                <LinearLayout
                     android:layout_width="match_parent"
-                    android:layout_height="wrap_content" />
+                    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="match_parent"
+                        android:layout_height="wrap_content" />
+
+                </LinearLayout>
 
             </LinearLayout>
 
diff --git a/res/layout-port-finger-854x480/audio_player.xml b/res/layout-port-finger-854x480/audio_player.xml
index 471d321..95f857c 100644
--- a/res/layout-port-finger-854x480/audio_player.xml
+++ b/res/layout-port-finger-854x480/audio_player.xml
@@ -51,80 +51,85 @@
 
     </LinearLayout>
 
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal"
-        android:baselineAligned="false"
-        android:paddingLeft="11dip"
-        android:paddingTop="2dip"
-        android:paddingBottom="4dip">
-
-        <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"
+    <LinearLayout android:id="@+id/trackinfo"
+                  android:layout_width="match_parent"
+                  android:layout_height="wrap_content"
+                  android:orientation="vertical">
+        <LinearLayout
             android:layout_width="match_parent"
-            android:layout_height="wrap_content" />
-
-    </LinearLayout>
-
-    <LinearLayout
-        android:layout_width="match_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" />
+            android:orientation="horizontal"
+            android:baselineAligned="false"
+            android:paddingLeft="11dip"
+            android:paddingTop="2dip"
+            android:paddingBottom="4dip">
 
-        <TextView android:id="@+id/albumname"
-            android:textSize="14sp"
-            android:singleLine="true"
-            android:ellipsize="end"
-            android:layout_gravity="center_vertical"
+            <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="match_parent"
+                android:layout_height="wrap_content" />
+
+        </LinearLayout>
+
+        <LinearLayout
             android:layout_width="match_parent"
-            android:layout_height="wrap_content" />
-
-    </LinearLayout>
-
-    <LinearLayout
-        android:layout_width="match_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" />
+            android:orientation="horizontal"
+            android:baselineAligned="false"
+            android:paddingLeft="11dip"
+            android:paddingTop="4dip"
+            android:paddingBottom="8dip">
 
-        <TextView android:id="@+id/trackname"
-            android:textSize="14sp"
-            android:singleLine="true"
-            android:ellipsize="end"
-            android:layout_gravity="center_vertical"
+            <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="match_parent"
+                android:layout_height="wrap_content" />
+
+        </LinearLayout>
+
+        <LinearLayout
             android:layout_width="match_parent"
-            android:layout_height="wrap_content" />
+            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="match_parent"
+                android:layout_height="wrap_content" />
+
+        </LinearLayout>
     </LinearLayout>
 
     <include layout="@layout/audio_player_common" />
diff --git a/res/layout/audio_player.xml b/res/layout/audio_player.xml
index 1c7390b..a8a1921 100644
--- a/res/layout/audio_player.xml
+++ b/res/layout/audio_player.xml
@@ -62,80 +62,85 @@
 
     </LinearLayout>
 
-    <LinearLayout
+    <LinearLayout android:id="@+id/trackinfo"
         android:layout_width="match_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:orientation="vertical">
+        <LinearLayout
             android:layout_width="match_parent"
-            android:layout_height="wrap_content" />
-
-    </LinearLayout>
-
-    <LinearLayout
-        android:layout_width="match_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" />
+            android:orientation="horizontal"
+            android:baselineAligned="false"
+            android:paddingLeft="11dip"
+            android:paddingTop="4dip"
+            android:paddingBottom="8dip">
 
-        <TextView android:id="@+id/albumname"
-            android:textSize="14sp"
-            android:singleLine="true"
-            android:ellipsize="end"
-            android:layout_gravity="center_vertical"
+            <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="match_parent"
+                      android:layout_height="wrap_content" />
+
+        </LinearLayout>
+
+        <LinearLayout
             android:layout_width="match_parent"
-            android:layout_height="wrap_content" />
-
-    </LinearLayout>
-
-    <LinearLayout
-        android:layout_width="match_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" />
+            android:orientation="horizontal"
+            android:baselineAligned="false"
+            android:paddingLeft="11dip"
+            android:paddingTop="4dip"
+            android:paddingBottom="8dip">
 
-        <TextView android:id="@+id/trackname"
-            android:textSize="14sp"
-            android:singleLine="true"
-            android:ellipsize="end"
-            android:layout_gravity="center_vertical"
+            <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="match_parent"
+                      android:layout_height="wrap_content" />
+
+        </LinearLayout>
+
+        <LinearLayout
             android:layout_width="match_parent"
-            android:layout_height="wrap_content" />
+            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="match_parent"
+                      android:layout_height="wrap_content" />
+
+        </LinearLayout>
     </LinearLayout>
 
     <include layout="@layout/audio_player_common" />
diff --git a/res/layout/music_picker_item.xml b/res/layout/music_picker_item.xml
index 0d0ae21..cf0f637 100644
--- a/res/layout/music_picker_item.xml
+++ b/res/layout/music_picker_item.xml
@@ -16,7 +16,7 @@
 ** limitations under the License.
 */
 -->
-<com.android.music.CheckableRelativeLayout
+<com.android.music.utils.CheckableRelativeLayout
         xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="64dip"
@@ -76,4 +76,4 @@
         android:layout_height="wrap_content"
         android:layout_marginRight="6dip" />
         
-</com.android.music.CheckableRelativeLayout>
+</com.android.music.utils.CheckableRelativeLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index dcd6883..c7f9082 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -313,5 +313,21 @@
 
     <!-- Menu text for the item controlling the audio effects panel [CHAR LIMIT=15] -->
     <string name="effectspanel">Sound effects</string>
+
+    <string name="app_name">Android Open Source Music Player</string>
+    <string name="favorite">Favorite</string>
+    <string name="error_no_metadata">Unable to retrieve metadata.</string>
+    <string name="browse_genres">Genres</string>
+    <string name="browse_genre_subtitle">Songs by genre</string>
+    <string name="browse_musics_by_genre_subtitle">%1$s songs</string>
+    <string name="random_queue_title">Random music</string>
+    <string name="error_cannot_skip">Cannot skip</string>
+    <string name="error_loading_media">Error Loading Media</string>
+    <string name="play_item">Play item</string>
+    <string name="skip_previous">Skip to previous</string>
+    <string name="play_pause">play or pause</string>
+    <string name="skip_next">Skip to next</string>
+    <string name="no_search_results">No search results.</string>
+
 </resources>
 
diff --git a/res/xml/automotive_app_desc.xml b/res/xml/automotive_app_desc.xml
new file mode 100644
index 0000000..0a6a3c9
--- /dev/null
+++ b/res/xml/automotive_app_desc.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<automotiveApp>
+    <uses name="media"/>
+</automotiveApp>
\ No newline at end of file
diff --git a/src/com/android/music/AlbumBrowserActivity.java b/src/com/android/music/AlbumBrowserActivity.java
index 7492768..ce5dcab 100644
--- a/src/com/android/music/AlbumBrowserActivity.java
+++ b/src/com/android/music/AlbumBrowserActivity.java
@@ -16,649 +16,283 @@
 
 package com.android.music;
 
-import com.android.music.MusicUtils.ServiceToken;
-
 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.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.ServiceConnection;
-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.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.browse.MediaBrowser;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
 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.text.TextUtils;
 import android.util.Log;
-import android.view.ContextMenu;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.SubMenu;
+import android.view.LayoutInflater;
 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.ExpandableListView;
-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 android.widget.*;
+import com.android.music.utils.LogHelper;
+import com.android.music.utils.MediaIDHelper;
+import com.android.music.utils.Playback;
 
-import java.text.Collator;
+import java.util.ArrayList;
+import java.util.List;
 
-public class AlbumBrowserActivity extends ListActivity
-        implements View.OnCreateContextMenuListener, MusicUtils.Defs, ServiceConnection {
-    private String mCurrentAlbumId;
-    private String mCurrentAlbumName;
-    private String mCurrentArtistNameForAlbum;
-    boolean mIsUnknownArtist;
-    boolean mIsUnknownAlbum;
-    private AlbumListAdapter mAdapter;
-    private boolean mAdapterSent;
-    private final static int SEARCH = CHILD_MENU_BASE;
-    private static int mLastListPosCourse = -1;
-    private static int mLastListPosFine = -1;
-    private ServiceToken mToken;
+/*
+This activity is the albums browsing tab
+ */
+public class AlbumBrowserActivity extends ListActivity {
+    private static final String TAG = LogHelper.makeLogTag(AlbumBrowserActivity.class);
+    private static final MediaBrowser.MediaItem DEFAULT_PARENT_ITEM =
+            new MediaBrowser.MediaItem(new MediaDescription.Builder()
+                                               .setMediaId(MediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM)
+                                               .build(),
+                    MediaBrowser.MediaItem.FLAG_BROWSABLE);
 
-    public AlbumBrowserActivity() {}
+    private ListView mAlbumList;
+    private AlbumBrowseAdapter mBrowseListAdapter;
+    private MediaBrowser mMediaBrowser;
+    private MediaBrowser.MediaItem mParentItem;
 
     /** 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");
-        }
+        Log.d(TAG, "onCreate()");
         super.onCreate(icicle);
-        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+        if (icicle != null) {
+            mParentItem = icicle.getParcelable(MusicUtils.TAG_PARENT_ITEM);
+        } else if (getIntent() != null) {
+            mParentItem = getIntent().getExtras().getParcelable(MusicUtils.TAG_PARENT_ITEM);
+        }
+        if (mParentItem == null) {
+            mParentItem = DEFAULT_PARENT_ITEM;
+        }
         requestWindowFeature(Window.FEATURE_NO_TITLE);
         setVolumeControlStream(AudioManager.STREAM_MUSIC);
-        mToken = MusicUtils.bindToService(this, 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);
         MusicUtils.updateButtonBar(this, R.id.albumtab);
-        ListView lv = getListView();
-        lv.setOnCreateContextMenuListener(this);
-        lv.setTextFilterEnabled(true);
+        mAlbumList = getListView();
+        mAlbumList.setOnCreateContextMenuListener(this);
+        mAlbumList.setTextFilterEnabled(true);
 
-        mAdapter = (AlbumListAdapter) getLastNonConfigurationInstance();
-        if (mAdapter == null) {
+        mBrowseListAdapter = (AlbumBrowseAdapter) getLastNonConfigurationInstance();
+        if (mBrowseListAdapter == null) {
             // Log.i("@@@", "starting query");
-            mAdapter = new AlbumListAdapter(getApplication(), this, R.layout.track_list_item,
-                    mAlbumCursor, new String[] {}, new int[] {});
-            setListAdapter(mAdapter);
+            mBrowseListAdapter = new AlbumBrowseAdapter(this, R.layout.track_list_item);
             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);
+        setListAdapter(mBrowseListAdapter);
+        Log.d(TAG, "Creating MediaBrowser");
+        mMediaBrowser = new MediaBrowser(this, new ComponentName(this, MediaPlaybackService.class),
+                mConnectionCallback, null);
     }
 
     @Override
     public void onDestroy() {
+        Log.d(TAG, "onDestroy()");
         ListView lv = getListView();
-        if (lv != null) {
-            mLastListPosCourse = lv.getFirstVisiblePosition();
-            View cv = lv.getChildAt(0);
-            if (cv != null) {
-                mLastListPosFine = cv.getTop();
-            }
-        }
-        MusicUtils.unbindFromService(mToken);
-        // If we have an adapter and didn't send it off to another activity yet, we should
-        // close its cursor, which we do by assigning a null cursor to it. Doing this
-        // instead of closing the cursor directly keeps the framework from accessing
-        // the closed cursor later.
-        if (!mAdapterSent && mAdapter != null) {
-            mAdapter.changeCursor(null);
-        }
         // Because we pass the adapter to the next activity, we need to make
         // sure it doesn't keep a reference to this activity. We can do this
         // by clearing its DatasetObservers, which setListAdapter(null) does.
         setListAdapter(null);
-        mAdapter = null;
-        unregisterReceiver(mScanListener);
+        mBrowseListAdapter = null;
         super.onDestroy();
     }
 
     @Override
     public void onResume() {
+        Log.d(TAG, "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();
-            MusicUtils.updateNowPlaying(AlbumBrowserActivity.this);
-        }
-    };
-    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();
-            }
-        }
-    };
+    @Override
+    public void onStart() {
+        Log.d(TAG, "onStart()");
+        super.onStart();
+        mMediaBrowser.connect();
+    }
 
-    private Handler mReScanHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            if (mAdapter != null) {
-                getAlbumCursor(mAdapter.getQueryHandler(), null);
-            }
-        }
-    };
+    @Override
+    public void onStop() {
+        Log.d(TAG, "onStop()");
+        super.onStop();
+        mMediaBrowser.disconnect();
+    }
 
     @Override
     public void onPause() {
-        unregisterReceiver(mTrackListListener);
-        mReScanHandler.removeCallbacksAndMessages(null);
+        Log.d(TAG, "onPause()");
         super.onPause();
     }
 
-    public void init(Cursor c) {
-        if (mAdapter == null) {
-            return;
-        }
-        mAdapter.changeCursor(c); // also sets mAlbumCursor
-
-        if (mAlbumCursor == null) {
-            MusicUtils.displayDatabaseError(this);
-            closeContextMenu();
-            mReScanHandler.sendEmptyMessageDelayed(0, 1000);
-            return;
-        }
-
-        // restore previous position
-        if (mLastListPosCourse >= 0) {
-            getListView().setSelectionFromTop(mLastListPosCourse, mLastListPosFine);
-            mLastListPosCourse = -1;
-        }
-
-        MusicUtils.hideDatabaseError(this);
-        MusicUtils.updateButtonBar(this, R.id.albumtab);
-        setTitle();
+    @Override
+    public void onSaveInstanceState(Bundle outcicle) {
+        outcicle.putParcelable(MusicUtils.TAG_PARENT_ITEM, mParentItem);
+        super.onSaveInstanceState(outcicle);
     }
 
     private void setTitle() {
         CharSequence fancyName = "";
-        if (mAlbumCursor != null && mAlbumCursor.getCount() > 0) {
-            mAlbumCursor.moveToFirst();
-            fancyName = mAlbumCursor.getString(
-                    mAlbumCursor.getColumnIndex(MediaStore.Audio.Albums.ARTIST));
-            if (fancyName == null || fancyName.equals(MediaStore.UNKNOWN_STRING))
-                fancyName = getText(R.string.unknown_artist_name);
-        }
-
-        if (mArtistId != null && fancyName != null)
-            setTitle(fancyName);
-        else
-            setTitle(R.string.albums_title);
+        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);
+    private MediaBrowser.SubscriptionCallback mSubscriptionCallback =
+            new MediaBrowser.SubscriptionCallback() {
 
-        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));
-        mIsUnknownArtist = mCurrentArtistNameForAlbum == null
-                || mCurrentArtistNameForAlbum.equals(MediaStore.UNKNOWN_STRING);
-        mIsUnknownAlbum =
-                mCurrentAlbumName == null || mCurrentAlbumName.equals(MediaStore.UNKNOWN_STRING);
-        if (mIsUnknownAlbum) {
-            menu.setHeaderTitle(getString(R.string.unknown_album_name));
-        } else {
-            menu.setHeaderTitle(mCurrentAlbumName);
-        }
-        if (!mIsUnknownAlbum || !mIsUnknownArtist) {
-            menu.add(0, SEARCH, 0, R.string.search_title);
-        }
-    }
-
-    @Override
-    public boolean onContextItemSelected(MenuItem item) {
-        switch (item.getItemId()) {
-            case PLAY_SELECTION: {
-                // play the selected album
-                long[] list = MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
-                MusicUtils.playAll(this, list, 0);
-                return true;
-            }
-
-            case QUEUE: {
-                long[] list = MusicUtils.getSongListForAlbum(this, Long.parseLong(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: {
-                long[] list = MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
-                long playlist = item.getIntent().getLongExtra("playlist", 0);
-                MusicUtils.addToPlaylist(this, list, playlist);
-                return true;
-            }
-            case DELETE_ITEM: {
-                long[] list = MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
-                String f;
-                if (android.os.Environment.isExternalStorageRemovable()) {
-                    f = getString(R.string.delete_album_desc);
-                } else {
-                    f = getString(R.string.delete_album_desc_nosdcard);
+                @Override
+                public void onChildrenLoaded(
+                        String parentId, List<MediaBrowser.MediaItem> children) {
+                    mBrowseListAdapter.clear();
+                    mBrowseListAdapter.notifyDataSetInvalidated();
+                    for (MediaBrowser.MediaItem item : children) {
+                        mBrowseListAdapter.add(item);
+                    }
+                    mBrowseListAdapter.notifyDataSetChanged();
                 }
-                String desc = String.format(f, mCurrentAlbumName);
-                Bundle b = new Bundle();
-                b.putString("description", desc);
-                b.putLongArray("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 = "";
-
-        Intent i = new Intent();
-        i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH);
-        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
-        title = "";
-        if (!mIsUnknownAlbum) {
-            query = mCurrentAlbumName;
-            i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, mCurrentAlbumName);
-            title = mCurrentAlbumName;
-        }
-        if (!mIsUnknownArtist) {
-            query = query + " " + mCurrentArtistNameForAlbum;
-            i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistNameForAlbum);
-            title = title + " " + mCurrentArtistNameForAlbum;
-        }
-        // Since we hide the 'search' menu item when both album and artist are
-        // unknown, the query and title strings will have at least one of those.
-        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);
+                @Override
+                public void onError(String id) {
+                    Toast.makeText(getApplicationContext(), R.string.error_loading_media,
+                                 Toast.LENGTH_LONG)
+                            .show();
                 }
-                break;
+            };
 
-            case NEW_PLAYLIST:
-                if (resultCode == RESULT_OK) {
-                    Uri uri = intent.getData();
-                    if (uri != null) {
-                        long[] list = MusicUtils.getSongListForAlbum(
-                                this, Long.parseLong(mCurrentAlbumId));
-                        MusicUtils.addToPlaylist(
-                                this, list, Long.parseLong(uri.getLastPathSegment()));
+    private MediaBrowser.ConnectionCallback mConnectionCallback =
+            new MediaBrowser.ConnectionCallback() {
+                @Override
+                public void onConnected() {
+                    Log.d(TAG, "onConnected: session token " + mMediaBrowser.getSessionToken());
+                    mMediaBrowser.subscribe(mParentItem.getMediaId(), mSubscriptionCallback);
+                    if (mMediaBrowser.getSessionToken() == null) {
+                        throw new IllegalArgumentException("No Session token");
+                    }
+                    MediaController mediaController = new MediaController(
+                            AlbumBrowserActivity.this, mMediaBrowser.getSessionToken());
+                    mediaController.registerCallback(mMediaControllerCallback);
+                    AlbumBrowserActivity.this.setMediaController(mediaController);
+                    if (mediaController.getMetadata() != null) {
+                        MusicUtils.updateNowPlaying(AlbumBrowserActivity.this);
                     }
                 }
-                break;
-        }
-    }
 
-    @Override
-    protected void onListItemClick(ListView l, View v, int position, long id) {
-        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, PARTY_SHUFFLE, 0,
-                R.string.party_shuffle); // icon will be set in onPrepareOptionsMenu()
-        menu.add(0, SHUFFLE_ALL, 0, R.string.shuffle_all).setIcon(R.drawable.ic_menu_shuffle);
-        return true;
-    }
-
-    @Override
-    public boolean onPrepareOptionsMenu(Menu menu) {
-        MusicUtils.setPartyShuffleMenuIcon(menu);
-        return super.onPrepareOptionsMenu(menu);
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        Intent intent;
-        Cursor cursor;
-        switch (item.getItemId()) {
-            case PARTY_SHUFFLE:
-                MusicUtils.togglePartyShuffle();
-                break;
-
-            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();
+                @Override
+                public void onConnectionFailed() {
+                    Log.d(TAG, "onConnectionFailed");
                 }
-                return true;
-        }
-        return super.onOptionsItemSelected(item);
-    }
 
-    private Cursor getAlbumCursor(AsyncQueryHandler async, String filter) {
-        String[] cols = new String[] {MediaStore.Audio.Albums._ID, MediaStore.Audio.Albums.ARTIST,
-                MediaStore.Audio.Albums.ALBUM, MediaStore.Audio.Albums.ALBUM_ART};
+                @Override
+                public void onConnectionSuspended() {
+                    Log.d(TAG, "onConnectionSuspended");
+                    AlbumBrowserActivity.this.setMediaController(null);
+                }
+            };
 
-        Cursor ret = null;
-        if (mArtistId != null) {
-            Uri uri = MediaStore.Audio.Artists.Albums.getContentUri(
-                    "external", Long.valueOf(mArtistId));
-            if (!TextUtils.isEmpty(filter)) {
-                uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filter)).build();
-            }
-            if (async != null) {
-                async.startQuery(
-                        0, null, uri, cols, null, null, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
-            } else {
-                ret = MusicUtils.query(
-                        this, uri, cols, null, null, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
-            }
-        } else {
-            Uri uri = MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI;
-            if (!TextUtils.isEmpty(filter)) {
-                uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filter)).build();
-            }
-            if (async != null) {
-                async.startQuery(
-                        0, null, uri, cols, null, null, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
-            } else {
-                ret = MusicUtils.query(
-                        this, uri, cols, null, null, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
+    private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() {
+        @Override
+        public void onMetadataChanged(MediaMetadata metadata) {
+            super.onMetadataChanged(metadata);
+            MusicUtils.updateNowPlaying(AlbumBrowserActivity.this);
+            if (mBrowseListAdapter != null) {
+                mBrowseListAdapter.notifyDataSetChanged();
             }
         }
-        return ret;
-    }
+    };
 
-    static class AlbumListAdapter extends SimpleCursorAdapter implements SectionIndexer {
+    // An adapter for showing the list of browsed MediaItem's
+    private class AlbumBrowseAdapter extends ArrayAdapter<MediaBrowser.MediaItem> {
         private final Drawable mNowPlayingOverlay;
         private final BitmapDrawable mDefaultAlbumIcon;
-        private int mAlbumIdx;
-        private int mArtistIdx;
-        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;
+        private int mLayoutId;
 
-        static class ViewHolder {
+        private 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);
-            }
-        }
-
-        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);
+        AlbumBrowseAdapter(Context context, int layout) {
+            super(context, layout, new ArrayList<>());
+            mNowPlayingOverlay = context.getResources().getDrawable(
+                    R.drawable.indicator_ic_mp_playing_list, context.getTheme());
+            Bitmap b = BitmapFactory.decodeResource(
+                    context.getResources(), R.drawable.albumart_mp_unknown_list);
             mDefaultAlbumIcon = new BitmapDrawable(context.getResources(), 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);
-                mAlbumArtIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM_ART);
-
-                if (mIndexer != null) {
-                    mIndexer.setCursor(cursor);
-                } else {
-                    mIndexer = new MusicAlphabetIndexer(
-                            cursor, mAlbumIdx, mResources.getString(R.string.fast_scroll_alphabet));
-                }
-            }
-        }
-
-        public void setActivity(AlbumBrowserActivity newactivity) {
-            mActivity = newactivity;
-        }
-
-        public AsyncQueryHandler getQueryHandler() {
-            return mQueryHandler;
+            mLayoutId = layout;
         }
 
         @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.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 == null || name.equals(MediaStore.UNKNOWN_STRING);
-            if (unknown) {
-                displayname = mUnknownAlbum;
+        public View getView(int position, View convertView, ViewGroup parent) {
+            Log.d(TAG, "getView()");
+            if (convertView == null) {
+                convertView = LayoutInflater.from(getContext()).inflate(mLayoutId, parent, false);
+                ViewHolder vh = new ViewHolder();
+                vh.line1 = (TextView) convertView.findViewById(R.id.line1);
+                vh.line2 = (TextView) convertView.findViewById(R.id.line2);
+                vh.play_indicator = (ImageView) convertView.findViewById(R.id.play_indicator);
+                vh.icon = (ImageView) convertView.findViewById(R.id.icon);
+                vh.icon.setBackground(mDefaultAlbumIcon);
+                vh.icon.setPadding(0, 0, 1, 0);
+                vh.icon.setVisibility(View.VISIBLE);
+                convertView.setTag(vh);
             }
-            vh.line1.setText(displayname);
-
-            name = cursor.getString(mArtistIdx);
-            displayname = name;
-            if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) {
-                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);
-            long aid = cursor.getLong(0);
-            if (unknown || art == null || art.length() == 0) {
-                iv.setImageDrawable(null);
+            ViewHolder vh = (ViewHolder) convertView.getTag();
+            MediaBrowser.MediaItem item = getItem(position);
+            Log.d(TAG, "Album: " + item.getDescription().getTitle());
+            vh.line1.setText(item.getDescription().getTitle());
+            Log.d(TAG, "Artist: " + item.getDescription().getSubtitle());
+            vh.line2.setText(item.getDescription().getSubtitle());
+            Bitmap albumArt = item.getDescription().getIconBitmap();
+            LogHelper.d(TAG, "looking for album art");
+            if (albumArt != null) {
+                vh.icon.setImageDrawable(MusicUtils.getDrawableBitmap(albumArt, mDefaultAlbumIcon));
             } else {
-                Drawable d = MusicUtils.getCachedArtwork(context, aid, mDefaultAlbumIcon);
-                iv.setImageDrawable(d);
+                vh.icon.setImageDrawable(mDefaultAlbumIcon);
             }
-
-            long currentalbumid = MusicUtils.getCurrentAlbumId();
-            iv = vh.play_indicator;
-            if (currentalbumid == aid) {
-                iv.setImageDrawable(mNowPlayingOverlay);
+            MediaController mediaController = AlbumBrowserActivity.this.getMediaController();
+            if (mediaController == null) {
+                vh.play_indicator.setImageDrawable(null);
+                return convertView;
+            }
+            MediaMetadata metadata = mediaController.getMetadata();
+            if (metadata == null) {
+                vh.play_indicator.setImageDrawable(null);
+                return convertView;
+            }
+            if (metadata.getString(MediaMetadata.METADATA_KEY_ALBUM)
+                            .equals(item.getDescription().getTitle())) {
+                vh.play_indicator.setImageDrawable(mNowPlayingOverlay);
             } else {
-                iv.setImageDrawable(null);
+                vh.play_indicator.setImageDrawable(null);
             }
-        }
-
-        @Override
-        public void changeCursor(Cursor cursor) {
-            if (mActivity.isFinishing() && cursor != null) {
-                cursor.close();
-                cursor = null;
-            }
-            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;
+            return convertView;
         }
     }
 
-    private Cursor mAlbumCursor;
-    private String mArtistId;
-
-    public void onServiceConnected(ComponentName name, IBinder service) {
-        MusicUtils.updateNowPlaying(this);
-    }
-
-    public void onServiceDisconnected(ComponentName name) {
-        finish();
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id) {
+        Log.d(TAG, "onListItemClick at position " + position + ", id " + id);
+        MediaBrowser.MediaItem item = mBrowseListAdapter.getItem(position);
+        if (item.isBrowsable()) {
+            Intent intent = new Intent(Intent.ACTION_PICK);
+            intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
+            intent.putExtra(MusicUtils.TAG_PARENT_ITEM, item);
+            intent.putExtra(MusicUtils.TAG_WITH_TABS, false);
+            startActivity(intent);
+        }
     }
 }
diff --git a/src/com/android/music/ArtistAlbumBrowserActivity.java b/src/com/android/music/ArtistAlbumBrowserActivity.java
index 474ff37..ffccd32 100644
--- a/src/com/android/music/ArtistAlbumBrowserActivity.java
+++ b/src/com/android/music/ArtistAlbumBrowserActivity.java
@@ -16,617 +16,271 @@
 
 package com.android.music;
 
-import com.android.music.MusicUtils.ServiceToken;
-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.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.ServiceConnection;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.CursorWrapper;
+import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.browse.MediaBrowser;
+import android.media.session.MediaController;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.provider.MediaStore;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.SparseArray;
-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.ExpandableListView;
 import android.widget.ImageView;
-import android.widget.SectionIndexer;
-import android.widget.SimpleCursorTreeAdapter;
+import android.widget.SimpleExpandableListAdapter;
 import android.widget.TextView;
-import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
+import android.widget.Toast;
 
-import java.text.Collator;
+import com.android.music.utils.LogHelper;
+import com.android.music.utils.MediaIDHelper;
 
-public class ArtistAlbumBrowserActivity extends ExpandableListActivity
-        implements View.OnCreateContextMenuListener, MusicUtils.Defs, ServiceConnection {
-    private String mCurrentArtistId;
-    private String mCurrentArtistName;
-    private String mCurrentAlbumId;
-    private String mCurrentAlbumName;
-    private String mCurrentArtistNameForAlbum;
-    boolean mIsUnknownArtist;
-    boolean mIsUnknownAlbum;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class ArtistAlbumBrowserActivity extends ExpandableListActivity {
+    private static final String TAG = LogHelper.makeLogTag(ArtistAlbumBrowserActivity.class);
+    private static final String KEY_NUM_ALBUMS = "__NUM_ALBUMS__";
+    private static final MediaBrowser.MediaItem DEFAULT_PARENT_ITEM =
+            new MediaBrowser.MediaItem(new MediaDescription.Builder()
+                                               .setMediaId(MediaIDHelper.MEDIA_ID_MUSICS_BY_ARTIST)
+                                               .build(),
+                    MediaBrowser.MediaItem.FLAG_BROWSABLE);
+
     private ArtistAlbumListAdapter mAdapter;
-    private boolean mAdapterSent;
-    private final static int SEARCH = CHILD_MENU_BASE;
-    private static int mLastListPosCourse = -1;
-    private static int mLastListPosFine = -1;
-    private ServiceToken mToken;
+    private MediaBrowser mMediaBrowser;
+    private MediaBrowser.MediaItem mParentItem;
 
     /** Called when the activity is first created. */
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
-        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+        LogHelper.d(TAG, "onCreate()");
+        // Handle past states
+        if (icicle != null) {
+            mParentItem = icicle.getParcelable(MusicUtils.TAG_PARENT_ITEM);
+        } else if (getIntent() != null) {
+            mParentItem = getIntent().getExtras().getParcelable(MusicUtils.TAG_PARENT_ITEM);
+        }
+        if (mParentItem == null) {
+            mParentItem = DEFAULT_PARENT_ITEM;
+        }
         requestWindowFeature(Window.FEATURE_NO_TITLE);
         setVolumeControlStream(AudioManager.STREAM_MUSIC);
-        if (icicle != null) {
-            mCurrentAlbumId = icicle.getString("selectedalbum");
-            mCurrentAlbumName = icicle.getString("selectedalbumname");
-            mCurrentArtistId = icicle.getString("selectedartist");
-            mCurrentArtistName = icicle.getString("selectedartistname");
-        }
-        mToken = MusicUtils.bindToService(this, 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);
-
+        // Init layout
         setContentView(R.layout.media_picker_activity_expanding);
         MusicUtils.updateButtonBar(this, R.id.artisttab);
-        ExpandableListView lv = getExpandableListView();
-        lv.setOnCreateContextMenuListener(this);
-        lv.setTextFilterEnabled(true);
 
+        // Init expandable list
+        ExpandableListView lv = getExpandableListView();
+        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[] {});
+            mAdapter = new ArtistAlbumListAdapter(this, new ArrayList<>(), new ArrayList<>());
             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);
-            }
         }
+        setListAdapter(mAdapter);
+        LogHelper.d(TAG, "Creating MediaBrowser");
+        mMediaBrowser = new MediaBrowser(this, new ComponentName(this, MediaPlaybackService.class),
+                mConnectionCallback, 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);
+        outcicle.putParcelable(MusicUtils.TAG_PARENT_ITEM, mParentItem);
         super.onSaveInstanceState(outcicle);
     }
 
     @Override
     public void onDestroy() {
-        ExpandableListView lv = getExpandableListView();
-        if (lv != null) {
-            mLastListPosCourse = lv.getFirstVisiblePosition();
-            View cv = lv.getChildAt(0);
-            if (cv != null) {
-                mLastListPosFine = cv.getTop();
-            }
-        }
-
-        MusicUtils.unbindFromService(mToken);
-        // If we have an adapter and didn't send it off to another activity yet, we should
-        // close its cursor, which we do by assigning a null cursor to it. Doing this
-        // instead of closing the cursor directly keeps the framework from accessing
-        // the closed cursor later.
-        if (!mAdapterSent && mAdapter != null) {
-            mAdapter.changeCursor(null);
-        }
-        // Because we pass the adapter to the next activity, we need to make
-        // sure it doesn't keep a reference to this activity. We can do this
-        // by clearing its DatasetObservers, which setListAdapter(null) does.
         setListAdapter(null);
         mAdapter = null;
-        unregisterReceiver(mScanListener);
         setListAdapter(null);
         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);
+    public void onStart() {
+        LogHelper.d(TAG, "onStart()");
+        super.onStart();
+        mMediaBrowser.connect();
     }
 
-    private BroadcastReceiver mTrackListListener = new BroadcastReceiver() {
+    @Override
+    public void onStop() {
+        LogHelper.d(TAG, "onStop()");
+        super.onStop();
+        mMediaBrowser.disconnect();
+    }
+
+    private MediaBrowser
+            .SubscriptionCallback mSubscriptionCallback = new MediaBrowser.SubscriptionCallback() {
         @Override
-        public void onReceive(Context context, Intent intent) {
-            getExpandableListView().invalidateViews();
+        public void onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children) {
+            if (parentId.equals(MediaIDHelper.MEDIA_ID_MUSICS_BY_ARTIST)) {
+                mAdapter.getArtistMap().clear();
+                mAdapter.getGroupData().clear();
+                mAdapter.notifyDataSetInvalidated();
+                for (MediaBrowser.MediaItem item : children) {
+                    ConcurrentHashMap<String, MediaBrowser.MediaItem> entry =
+                            new ConcurrentHashMap<>();
+                    entry.put(MediaIDHelper.MEDIA_ID_MUSICS_BY_ARTIST, item);
+                    synchronized (this) {
+                        mAdapter.getArtistMap().put(item.getDescription().getTitle().toString(),
+                                mAdapter.getGroupData().size());
+                        mAdapter.getGroupData().add(entry);
+                        mAdapter.getChildData().add(new ArrayList<>());
+                    }
+                    mMediaBrowser.subscribe(item.getMediaId(), this);
+                }
+                mAdapter.notifyDataSetChanged();
+            } else if (parentId.startsWith(MediaIDHelper.MEDIA_ID_MUSICS_BY_ARTIST)) {
+                String artist = MediaIDHelper.getHierarchy(parentId)[1];
+                if (!mAdapter.getArtistMap().containsKey(artist)) {
+                    return;
+                }
+                int artistIndex = mAdapter.getArtistMap().get(artist);
+                mAdapter.getChildData().get(artistIndex).clear();
+                mAdapter.notifyDataSetInvalidated();
+                Bundle extras = new Bundle();
+                extras.putLong(KEY_NUM_ALBUMS, children.size());
+                MediaBrowser.MediaItem newArtistItem =
+                        new MediaBrowser.MediaItem(new MediaDescription.Builder()
+                                                           .setMediaId("Count")
+                                                           .setExtras(extras)
+                                                           .build(),
+                                0);
+                mAdapter.getGroupData().get(artistIndex).put(KEY_NUM_ALBUMS, newArtistItem);
+                for (MediaBrowser.MediaItem item : children) {
+                    ConcurrentHashMap<String, MediaBrowser.MediaItem> entry =
+                            new ConcurrentHashMap<>();
+                    entry.put(MediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM, item);
+                    mAdapter.getChildData().get(artistIndex).add(entry);
+                }
+                mAdapter.notifyDataSetChanged();
+            }
+        }
+
+        @Override
+        public void onError(String id) {
+            Toast.makeText(getApplicationContext(), R.string.error_loading_media, Toast.LENGTH_LONG)
+                    .show();
+        }
+    };
+
+    private MediaBrowser.ConnectionCallback mConnectionCallback =
+            new MediaBrowser.ConnectionCallback() {
+                @Override
+                public void onConnected() {
+                    LogHelper.d(
+                            TAG, "onConnected: session token ", mMediaBrowser.getSessionToken());
+                    mMediaBrowser.subscribe(mParentItem.getMediaId(), mSubscriptionCallback);
+                    if (mMediaBrowser.getSessionToken() == null) {
+                        throw new IllegalArgumentException("No Session token");
+                    }
+                    MediaController mediaController = new MediaController(
+                            ArtistAlbumBrowserActivity.this, mMediaBrowser.getSessionToken());
+                    mediaController.registerCallback(mMediaControllerCallback);
+                    ArtistAlbumBrowserActivity.this.setMediaController(mediaController);
+                    if (mediaController.getMetadata() != null) {
+                        MusicUtils.updateNowPlaying(ArtistAlbumBrowserActivity.this);
+                    }
+                }
+
+                @Override
+                public void onConnectionFailed() {
+                    LogHelper.d(TAG, "onConnectionFailed");
+                }
+
+                @Override
+                public void onConnectionSuspended() {
+                    LogHelper.d(TAG, "onConnectionSuspended");
+                    ArtistAlbumBrowserActivity.this.setMediaController(null);
+                }
+            };
+
+    private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() {
+        @Override
+        public void onMetadataChanged(MediaMetadata metadata) {
+            super.onMetadataChanged(metadata);
             MusicUtils.updateNowPlaying(ArtistAlbumBrowserActivity.this);
         }
     };
-    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) {
-            if (mAdapter != null) {
-                getArtistCursor(mAdapter.getQueryHandler(), null);
-            }
-        }
-    };
-
-    @Override
-    public void onPause() {
-        unregisterReceiver(mTrackListListener);
-        mReScanHandler.removeCallbacksAndMessages(null);
-        super.onPause();
-    }
-
-    public void init(Cursor c) {
-        if (mAdapter == null) {
-            return;
-        }
-        mAdapter.changeCursor(c); // also sets mArtistCursor
-
-        if (mArtistCursor == null) {
-            MusicUtils.displayDatabaseError(this);
-            closeContextMenu();
-            mReScanHandler.sendEmptyMessageDelayed(0, 1000);
-            return;
-        }
-
-        // restore previous position
-        if (mLastListPosCourse >= 0) {
-            ExpandableListView elv = getExpandableListView();
-            elv.setSelectionFromTop(mLastListPosCourse, mLastListPosFine);
-            mLastListPosCourse = -1;
-        }
-
-        MusicUtils.hideDatabaseError(this);
-        MusicUtils.updateButtonBar(this, R.id.artisttab);
-        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 == null || album.equals(MediaStore.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, PARTY_SHUFFLE, 0,
-                R.string.party_shuffle); // icon will be set in onPrepareOptionsMenu()
-        menu.add(0, SHUFFLE_ALL, 0, R.string.shuffle_all).setIcon(R.drawable.ic_menu_shuffle);
-        return true;
-    }
-
-    @Override
-    public boolean onPrepareOptionsMenu(Menu menu) {
-        MusicUtils.setPartyShuffleMenuIcon(menu);
-        return super.onPrepareOptionsMenu(menu);
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        Intent intent;
-        Cursor cursor;
-        switch (item.getItemId()) {
-            case PARTY_SHUFFLE:
-                MusicUtils.togglePartyShuffle();
-                break;
-
-            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);
-
-        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;
-            mIsUnknownArtist = mCurrentArtistName == null
-                    || mCurrentArtistName.equals(MediaStore.UNKNOWN_STRING);
-            mIsUnknownAlbum = true;
-            if (mIsUnknownArtist) {
-                menu.setHeaderTitle(getString(R.string.unknown_artist_name));
-            } else {
-                menu.setHeaderTitle(mCurrentArtistName);
-                menu.add(0, SEARCH, 0, R.string.search_title);
-            }
-            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));
-            mIsUnknownArtist = mCurrentArtistNameForAlbum == null
-                    || mCurrentArtistNameForAlbum.equals(MediaStore.UNKNOWN_STRING);
-            mIsUnknownAlbum = mCurrentAlbumName == null
-                    || mCurrentAlbumName.equals(MediaStore.UNKNOWN_STRING);
-            if (mIsUnknownAlbum) {
-                menu.setHeaderTitle(getString(R.string.unknown_album_name));
-            } else {
-                menu.setHeaderTitle(mCurrentAlbumName);
-            }
-            if (!mIsUnknownAlbum || !mIsUnknownArtist) {
-                menu.add(0, SEARCH, 0, R.string.search_title);
-            }
-        }
-    }
-
-    @Override
-    public boolean onContextItemSelected(MenuItem item) {
-        switch (item.getItemId()) {
-            case PLAY_SELECTION: {
-                // play everything by the selected artist
-                long[] list = mCurrentArtistId != null
-                        ? MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId))
-                        : MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
-
-                MusicUtils.playAll(this, list, 0);
-                return true;
-            }
-
-            case QUEUE: {
-                long[] list = mCurrentArtistId != null
-                        ? MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId))
-                        : MusicUtils.getSongListForAlbum(this, Long.parseLong(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: {
-                long[] list = mCurrentArtistId != null
-                        ? MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId))
-                        : MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
-                long playlist = item.getIntent().getLongExtra("playlist", 0);
-                MusicUtils.addToPlaylist(this, list, playlist);
-                return true;
-            }
-
-            case DELETE_ITEM: {
-                long[] list;
-                String desc;
-                if (mCurrentArtistId != null) {
-                    list = MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId));
-                    String f;
-                    if (android.os.Environment.isExternalStorageRemovable()) {
-                        f = getString(R.string.delete_artist_desc);
-                    } else {
-                        f = getString(R.string.delete_artist_desc_nosdcard);
-                    }
-                    desc = String.format(f, mCurrentArtistName);
-                } else {
-                    list = MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
-                    String f;
-                    if (android.os.Environment.isExternalStorageRemovable()) {
-                        f = getString(R.string.delete_album_desc);
-                    } else {
-                        f = getString(R.string.delete_album_desc_nosdcard);
-                    }
-
-                    desc = String.format(f, mCurrentAlbumName);
-                }
-                Bundle b = new Bundle();
-                b.putString("description", desc);
-                b.putLongArray("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);
-        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
-        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 {
-            if (mIsUnknownAlbum) {
-                title = query = mCurrentArtistNameForAlbum;
-            } else {
-                title = query = mCurrentAlbumName;
-                if (!mIsUnknownArtist) {
-                    query = query + " " + mCurrentArtistNameForAlbum;
-                }
-            }
-            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) {
-                        long[] list = null;
-                        if (mCurrentArtistId != null) {
-                            list = MusicUtils.getSongListForArtist(
-                                    this, Long.parseLong(mCurrentArtistId));
-                        } else if (mCurrentAlbumId != null) {
-                            list = MusicUtils.getSongListForAlbum(
-                                    this, Long.parseLong(mCurrentAlbumId));
-                        }
-                        MusicUtils.addToPlaylist(
-                                this, list, Long.parseLong(uri.getLastPathSegment()));
-                    }
-                }
-                break;
-        }
-    }
-
-    private Cursor getArtistCursor(AsyncQueryHandler async, String filter) {
-        String[] cols = new String[] {MediaStore.Audio.Artists._ID, MediaStore.Audio.Artists.ARTIST,
-                MediaStore.Audio.Artists.NUMBER_OF_ALBUMS,
-                MediaStore.Audio.Artists.NUMBER_OF_TRACKS};
-
-        Uri uri = MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI;
-        if (!TextUtils.isEmpty(filter)) {
-            uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filter)).build();
-        }
-
-        Cursor ret = null;
-        if (async != null) {
-            async.startQuery(0, null, uri, cols, null, null, MediaStore.Audio.Artists.ARTIST_KEY);
-        } else {
-            ret = MusicUtils.query(
-                    this, uri, cols, null, null, MediaStore.Audio.Artists.ARTIST_KEY);
-        }
-        return ret;
-    }
-
-    static class ArtistAlbumListAdapter extends SimpleCursorTreeAdapter implements SectionIndexer {
+    private class ArtistAlbumListAdapter extends SimpleExpandableListAdapter {
         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;
+        private ArrayList<ConcurrentHashMap<String, MediaBrowser.MediaItem>> mGroupData;
+        private ArrayList < ArrayList
+                < ConcurrentHashMap<String, MediaBrowser.MediaItem>>> mChildData;
+        private ConcurrentHashMap<String, Integer> mArtistMap;
 
-        static class ViewHolder {
+        private 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);
+        ArtistAlbumListAdapter(ArtistAlbumBrowserActivity currentActivity,
+                ArrayList<ConcurrentHashMap<String, MediaBrowser.MediaItem>> groupData,
+                ArrayList < ArrayList
+                        < ConcurrentHashMap<String, MediaBrowser.MediaItem>>> childData) {
+            super(currentActivity, groupData, R.layout.track_list_item_group, new String[] {},
+                    new int[] {}, childData, R.layout.track_list_item_child, new String[] {},
+                    new int[] {});
+            mGroupData = groupData;
+            mChildData = childData;
+            mActivity = currentActivity;
+            mNowPlayingOverlay = currentActivity.getResources().getDrawable(
+                    R.drawable.indicator_ic_mp_playing_list, currentActivity.getTheme());
+            mDefaultAlbumIcon = (BitmapDrawable) currentActivity.getResources().getDrawable(
+                    R.drawable.albumart_mp_unknown_list, currentActivity.getTheme());
             // 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);
+            mArtistMap = new ConcurrentHashMap<>();
         }
 
-        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(R.string.fast_scroll_alphabet));
-                }
-            }
+        public ArrayList<ConcurrentHashMap<String, MediaBrowser.MediaItem>> getGroupData() {
+            return mGroupData;
+        }
+
+        public ArrayList < ArrayList
+                < ConcurrentHashMap<String, MediaBrowser.MediaItem>>> getChildData() {
+            return mChildData;
+        }
+
+        public Map<String, Integer> getArtistMap() {
+            return mArtistMap;
         }
 
         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);
+        public View newGroupView(boolean isExpanded, ViewGroup parent) {
+            View v = super.newGroupView(isExpanded, parent);
             ImageView iv = (ImageView) v.findViewById(R.id.icon);
             ViewGroup.LayoutParams p = iv.getLayoutParams();
             p.width = ViewGroup.LayoutParams.WRAP_CONTENT;
@@ -642,218 +296,111 @@
         }
 
         @Override
-        public View newChildView(
-                Context context, Cursor cursor, boolean isLastChild, ViewGroup parent) {
-            View v = super.newChildView(context, cursor, isLastChild, parent);
+        public View newChildView(boolean isLastChild, ViewGroup parent) {
+            View v = super.newChildView(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.setBackground(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 = artist == null || artist.equals(MediaStore.UNKNOWN_STRING);
-            if (unknown) {
-                displayartist = mUnknownArtist;
+        public View getGroupView(
+                int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = newGroupView(isExpanded, parent);
             }
-            vh.line1.setText(displayartist);
-
-            int numalbums = cursor.getInt(mGroupAlbumIdx);
-            int numsongs = cursor.getInt(mGroupSongIdx);
-
-            String songs_albums = MusicUtils.makeAlbumsLabel(context, numalbums, numsongs, unknown);
-
+            Map<String, MediaBrowser.MediaItem> artistEntry =
+                    (Map<String, MediaBrowser.MediaItem>) getGroup(groupPosition);
+            MediaBrowser.MediaItem artistItem =
+                    artistEntry.get(MediaIDHelper.MEDIA_ID_MUSICS_BY_ARTIST);
+            MediaBrowser.MediaItem countItem = artistEntry.get(KEY_NUM_ALBUMS);
+            ViewHolder vh = (ViewHolder) convertView.getTag();
+            vh.line1.setText(artistItem.getDescription().getTitle());
+            int numalbums = -1;
+            if (countItem != null) {
+                Bundle extras = countItem.getDescription().getExtras();
+                if (extras != null) {
+                    numalbums = (int) extras.getLong(KEY_NUM_ALBUMS);
+                }
+            }
+            String songs_albums = MusicUtils.makeAlbumsLabel(mActivity, numalbums, -1, false);
             vh.line2.setText(songs_albums);
-
-            long currentartistid = MusicUtils.getCurrentArtistId();
-            long artistid = cursor.getLong(mGroupArtistIdIdx);
-            if (currentartistid == artistid && !isexpanded) {
+            MediaController mediaController = mActivity.getMediaController();
+            if (mediaController == null) {
+                vh.play_indicator.setImageDrawable(null);
+                return convertView;
+            }
+            MediaMetadata metadata = mediaController.getMetadata();
+            if (metadata == null) {
+                vh.play_indicator.setImageDrawable(null);
+                return convertView;
+            }
+            if (metadata.getString(MediaMetadata.METADATA_KEY_ARTIST)
+                            .equals(artistItem.getDescription().getTitle())
+                    && !isExpanded) {
                 vh.play_indicator.setImageDrawable(mNowPlayingOverlay);
             } else {
                 vh.play_indicator.setImageDrawable(null);
             }
+            return convertView;
         }
 
         @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 == null || name.equals(MediaStore.UNKNOWN_STRING);
-            if (unknown) {
-                displayname = mUnknownAlbum;
+        public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
+                View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = newChildView(isLastChild, parent);
             }
-            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));
-
-            final StringBuilder builder = mBuffer;
-            builder.delete(0, builder.length());
-            if (unknown) {
-                numsongs = numartistsongs;
-            }
-
-            if (numsongs == 1) {
-                builder.append(context.getString(R.string.onesong));
+            Map<String, MediaBrowser.MediaItem> albumEntry =
+                    (Map<String, MediaBrowser.MediaItem>) getChild(groupPosition, childPosition);
+            MediaBrowser.MediaItem albumItem =
+                    albumEntry.get(MediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM);
+            ViewHolder vh = (ViewHolder) convertView.getTag();
+            vh.line1.setText(albumItem.getDescription().getTitle());
+            vh.line2.setText(albumItem.getDescription().getDescription());
+            Bitmap albumArt = albumItem.getDescription().getIconBitmap();
+            if (albumArt == null) {
+                vh.icon.setBackground(mDefaultAlbumIcon);
             } 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.icon.setImageDrawable(MusicUtils.getDrawableBitmap(albumArt, mDefaultAlbumIcon));
             }
-            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);
+            MediaController mediaController = mActivity.getMediaController();
+            if (mediaController == null) {
+                vh.play_indicator.setImageDrawable(null);
+                return convertView;
+            }
+            MediaMetadata metadata = mediaController.getMetadata();
+            if (metadata == null) {
+                vh.play_indicator.setImageDrawable(null);
+                return convertView;
+            }
+            if (albumItem.getDescription().getTitle().equals(
+                        metadata.getString(MediaMetadata.METADATA_KEY_ALBUM))) {
+                vh.play_indicator.setImageDrawable(mNowPlayingOverlay);
             } else {
-                long artIndex = cursor.getLong(0);
-                Drawable d = MusicUtils.getCachedArtwork(context, artIndex, mDefaultAlbumIcon);
-                iv.setImageDrawable(d);
+                vh.play_indicator.setImageDrawable(null);
             }
-
-            long currentalbumid = MusicUtils.getCurrentAlbumId();
-            long aid = cursor.getLong(0);
-            iv = vh.play_indicator;
-            if (currentalbumid == aid) {
-                iv.setImageDrawable(mNowPlayingOverlay);
-            } else {
-                iv.setImageDrawable(null);
-            }
-        }
-
-        @Override
-        protected Cursor getChildrenCursor(Cursor groupCursor) {
-            long id = groupCursor.getLong(
-                    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.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 (mArtistName == null || mArtistName.equals(MediaStore.UNKNOWN_STRING)) {
-                        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 (MediaStore.Audio.Albums.ARTIST.equals(name)) {
-                        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 (mActivity.isFinishing() && cursor != null) {
-                cursor.close();
-                cursor = null;
-            }
-            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;
+            return convertView;
         }
     }
 
-    private Cursor mArtistCursor;
-
-    public void onServiceConnected(ComponentName name, IBinder service) {
-        MusicUtils.updateNowPlaying(this);
-    }
-
-    public void onServiceDisconnected(ComponentName name) {
-        finish();
+    @Override
+    public boolean onChildClick(
+            ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
+        Map<String, MediaBrowser.MediaItem> albumEntry =
+                (Map<String, MediaBrowser.MediaItem>) mAdapter.getChild(
+                        groupPosition, childPosition);
+        Intent intent = new Intent(Intent.ACTION_PICK);
+        intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
+        intent.putExtra(
+                MusicUtils.TAG_PARENT_ITEM, albumEntry.get(MediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM));
+        startActivity(intent);
+        return true;
     }
 }
diff --git a/src/com/android/music/AudioPreview.java b/src/com/android/music/AudioPreview.java
deleted file mode 100644
index 57b4030..0000000
--- a/src/com/android/music/AudioPreview.java
+++ /dev/null
@@ -1,487 +0,0 @@
-/*
- * Copyright (C) 2010 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.AsyncQueryHandler;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.media.AudioManager;
-import android.media.MediaPlayer;
-import android.media.AudioManager.OnAudioFocusChangeListener;
-import android.media.MediaPlayer.OnCompletionListener;
-import android.media.MediaPlayer.OnErrorListener;
-import android.media.MediaPlayer.OnPreparedListener;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.provider.MediaStore;
-import android.provider.OpenableColumns;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.Window;
-import android.view.WindowManager;
-import android.widget.ImageButton;
-import android.widget.ProgressBar;
-import android.widget.SeekBar;
-import android.widget.TextView;
-import android.widget.SeekBar.OnSeekBarChangeListener;
-import android.widget.Toast;
-
-import java.io.IOException;
-
-/**
- * Dialog that comes up in response to various music-related VIEW intents.
- */
-public class AudioPreview
-        extends Activity implements OnPreparedListener, OnErrorListener, OnCompletionListener {
-    private final static String TAG = "AudioPreview";
-    private PreviewPlayer mPlayer;
-    private TextView mTextLine1;
-    private TextView mTextLine2;
-    private TextView mLoadingText;
-    private SeekBar mSeekBar;
-    private Handler mProgressRefresher;
-    private boolean mSeeking = false;
-    private boolean mUiPaused = true;
-    private int mDuration;
-    private Uri mUri;
-    private long mMediaId = -1;
-    private static final int OPEN_IN_MUSIC = 1;
-    private AudioManager mAudioManager;
-    private boolean mPausedByTransientLossOfFocus;
-
-    @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        Intent intent = getIntent();
-        if (intent == null) {
-            finish();
-            return;
-        }
-        mUri = intent.getData();
-        if (mUri == null) {
-            finish();
-            return;
-        }
-        String scheme = mUri.getScheme();
-
-        setVolumeControlStream(AudioManager.STREAM_MUSIC);
-        requestWindowFeature(Window.FEATURE_NO_TITLE);
-        setContentView(R.layout.audiopreview);
-
-        mTextLine1 = (TextView) findViewById(R.id.line1);
-        mTextLine2 = (TextView) findViewById(R.id.line2);
-        mLoadingText = (TextView) findViewById(R.id.loading);
-        if (scheme.equals("http")) {
-            String msg = getString(R.string.streamloadingtext, mUri.getHost());
-            mLoadingText.setText(msg);
-        } else {
-            mLoadingText.setVisibility(View.GONE);
-        }
-        mSeekBar = (SeekBar) findViewById(R.id.progress);
-        mProgressRefresher = new Handler();
-        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
-
-        PreviewPlayer player = (PreviewPlayer) getLastNonConfigurationInstance();
-        if (player == null) {
-            mPlayer = new PreviewPlayer();
-            mPlayer.setActivity(this);
-            try {
-                mPlayer.setDataSourceAndPrepare(mUri);
-            } catch (Exception ex) {
-                // catch generic Exception, since we may be called with a media
-                // content URI, another content provider's URI, a file URI,
-                // an http URI, and there are different exceptions associated
-                // with failure to open each of those.
-                Log.d(TAG, "Failed to open file: " + ex);
-                Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
-                finish();
-                return;
-            }
-        } else {
-            mPlayer = player;
-            mPlayer.setActivity(this);
-            // onResume will update the UI
-        }
-
-        AsyncQueryHandler mAsyncQueryHandler = new AsyncQueryHandler(getContentResolver()) {
-            @Override
-            protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
-                if (cursor != null && cursor.moveToFirst()) {
-                    int titleIdx = cursor.getColumnIndex(MediaStore.Audio.Media.TITLE);
-                    int artistIdx = cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST);
-                    int idIdx = cursor.getColumnIndex(MediaStore.Audio.Media._ID);
-                    int displaynameIdx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
-
-                    if (idIdx >= 0) {
-                        mMediaId = cursor.getLong(idIdx);
-                    }
-
-                    if (titleIdx >= 0) {
-                        String title = cursor.getString(titleIdx);
-                        mTextLine1.setText(title);
-                        if (artistIdx >= 0) {
-                            String artist = cursor.getString(artistIdx);
-                            mTextLine2.setText(artist);
-                        }
-                    } else if (displaynameIdx >= 0) {
-                        String name = cursor.getString(displaynameIdx);
-                        mTextLine1.setText(name);
-                    } else {
-                        // Couldn't find anything to display, what to do now?
-                        Log.w(TAG, "Cursor had no names for us");
-                    }
-                } else {
-                    Log.w(TAG, "empty cursor");
-                }
-
-                if (cursor != null) {
-                    cursor.close();
-                }
-                setNames();
-            }
-        };
-
-        if (scheme.equals(ContentResolver.SCHEME_CONTENT)) {
-            if (mUri.getAuthority() == MediaStore.AUTHORITY) {
-                // try to get title and artist from the media content provider
-                mAsyncQueryHandler.startQuery(0, null, mUri,
-                        new String[] {MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.ARTIST},
-                        null, null, null);
-            } else {
-                // Try to get the display name from another content provider.
-                // Don't specifically ask for the display name though, since the
-                // provider might not actually support that column.
-                mAsyncQueryHandler.startQuery(0, null, mUri, null, null, null, null);
-            }
-        } else if (scheme.equals("file")) {
-            // check if this file is in the media database (clicking on a download
-            // in the download manager might follow this path
-            String path = mUri.getPath();
-            mAsyncQueryHandler.startQuery(0, null, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
-                    new String[] {MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE,
-                            MediaStore.Audio.Media.ARTIST},
-                    MediaStore.Audio.Media.DATA + "=?", new String[] {path}, null);
-        } else {
-            // We can't get metadata from the file/stream itself yet, because
-            // that API is hidden, so instead we display the URI being played
-            if (mPlayer.isPrepared()) {
-                setNames();
-            }
-        }
-    }
-
-    @Override
-    public void onPause() {
-        super.onPause();
-        mUiPaused = true;
-        if (mProgressRefresher != null) {
-            mProgressRefresher.removeCallbacksAndMessages(null);
-        }
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        mUiPaused = false;
-        if (mPlayer.isPrepared()) {
-            showPostPrepareUI();
-        }
-    }
-
-    @Override
-    public Object onRetainNonConfigurationInstance() {
-        PreviewPlayer player = mPlayer;
-        mPlayer = null;
-        return player;
-    }
-
-    @Override
-    public void onDestroy() {
-        stopPlayback();
-        super.onDestroy();
-    }
-
-    private void stopPlayback() {
-        if (mProgressRefresher != null) {
-            mProgressRefresher.removeCallbacksAndMessages(null);
-        }
-        if (mPlayer != null) {
-            mPlayer.release();
-            mPlayer = null;
-            mAudioManager.abandonAudioFocus(mAudioFocusListener);
-        }
-    }
-
-    @Override
-    public void onUserLeaveHint() {
-        stopPlayback();
-        finish();
-        super.onUserLeaveHint();
-    }
-
-    public void onPrepared(MediaPlayer mp) {
-        if (isFinishing()) return;
-        mPlayer = (PreviewPlayer) mp;
-        setNames();
-        mPlayer.start();
-        showPostPrepareUI();
-    }
-
-    private void showPostPrepareUI() {
-        ProgressBar pb = (ProgressBar) findViewById(R.id.spinner);
-        pb.setVisibility(View.GONE);
-        mDuration = mPlayer.getDuration();
-        if (mDuration != 0) {
-            mSeekBar.setMax(mDuration);
-            mSeekBar.setVisibility(View.VISIBLE);
-            if (!mSeeking) {
-                mSeekBar.setProgress(mPlayer.getCurrentPosition());
-            }
-        }
-        mSeekBar.setOnSeekBarChangeListener(mSeekListener);
-        mLoadingText.setVisibility(View.GONE);
-        View v = findViewById(R.id.titleandbuttons);
-        v.setVisibility(View.VISIBLE);
-        mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,
-                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
-        if (mProgressRefresher != null) {
-            mProgressRefresher.removeCallbacksAndMessages(null);
-            mProgressRefresher.postDelayed(new ProgressRefresher(), 200);
-        }
-        updatePlayPause();
-    }
-
-    private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
-        public void onAudioFocusChange(int focusChange) {
-            if (mPlayer == null) {
-                // this activity has handed its MediaPlayer off to the next activity
-                // (e.g. portrait/landscape switch) and should abandon its focus
-                mAudioManager.abandonAudioFocus(this);
-                return;
-            }
-            switch (focusChange) {
-                case AudioManager.AUDIOFOCUS_LOSS:
-                    mPausedByTransientLossOfFocus = false;
-                    mPlayer.pause();
-                    break;
-                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
-                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
-                    if (mPlayer.isPlaying()) {
-                        mPausedByTransientLossOfFocus = true;
-                        mPlayer.pause();
-                    }
-                    break;
-                case AudioManager.AUDIOFOCUS_GAIN:
-                    if (mPausedByTransientLossOfFocus) {
-                        mPausedByTransientLossOfFocus = false;
-                        start();
-                    }
-                    break;
-            }
-            updatePlayPause();
-        }
-    };
-
-    private void start() {
-        mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,
-                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
-        mPlayer.start();
-        mProgressRefresher.postDelayed(new ProgressRefresher(), 200);
-    }
-
-    public void setNames() {
-        if (TextUtils.isEmpty(mTextLine1.getText())) {
-            mTextLine1.setText(mUri.getLastPathSegment());
-        }
-        if (TextUtils.isEmpty(mTextLine2.getText())) {
-            mTextLine2.setVisibility(View.GONE);
-        } else {
-            mTextLine2.setVisibility(View.VISIBLE);
-        }
-    }
-
-    class ProgressRefresher implements Runnable {
-        @Override
-        public void run() {
-            if (mPlayer != null && !mSeeking && mDuration != 0) {
-                mSeekBar.setProgress(mPlayer.getCurrentPosition());
-            }
-            mProgressRefresher.removeCallbacksAndMessages(null);
-            if (!mUiPaused) {
-                mProgressRefresher.postDelayed(new ProgressRefresher(), 200);
-            }
-        }
-    }
-
-    private void updatePlayPause() {
-        ImageButton b = (ImageButton) findViewById(R.id.playpause);
-        if (b != null && mPlayer != null) {
-            if (mPlayer.isPlaying()) {
-                b.setImageResource(R.drawable.btn_playback_ic_pause_small);
-            } else {
-                b.setImageResource(R.drawable.btn_playback_ic_play_small);
-                mProgressRefresher.removeCallbacksAndMessages(null);
-            }
-        }
-    }
-
-    private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
-        public void onStartTrackingTouch(SeekBar bar) {
-            mSeeking = true;
-        }
-        public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {
-            if (!fromuser) {
-                return;
-            }
-            // Protection for case of simultaneously tapping on seek bar and exit
-            if (mPlayer == null) {
-                return;
-            }
-            mPlayer.seekTo(progress);
-        }
-        public void onStopTrackingTouch(SeekBar bar) {
-            mSeeking = false;
-        }
-    };
-
-    public boolean onError(MediaPlayer mp, int what, int extra) {
-        Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
-        finish();
-        return true;
-    }
-
-    public void onCompletion(MediaPlayer mp) {
-        mSeekBar.setProgress(mDuration);
-        updatePlayPause();
-    }
-
-    public void playPauseClicked(View v) {
-        // Protection for case of simultaneously tapping on play/pause and exit
-        if (mPlayer == null) {
-            return;
-        }
-        if (mPlayer.isPlaying()) {
-            mPlayer.pause();
-        } else {
-            start();
-        }
-        updatePlayPause();
-    }
-
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        super.onCreateOptionsMenu(menu);
-        // TODO: if mMediaId != -1, then the playing file has an entry in the media
-        // database, and we could open it in the full music app instead.
-        // Ideally, we would hand off the currently running mediaplayer
-        // to the music UI, which can probably be done via a public static
-        menu.add(0, OPEN_IN_MUSIC, 0, "open in music");
-        return true;
-    }
-
-    @Override
-    public boolean onPrepareOptionsMenu(Menu menu) {
-        MenuItem item = menu.findItem(OPEN_IN_MUSIC);
-        if (mMediaId >= 0) {
-            item.setVisible(true);
-            return true;
-        }
-        item.setVisible(false);
-        return false;
-    }
-
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        switch (keyCode) {
-            case KeyEvent.KEYCODE_HEADSETHOOK:
-            case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
-                if (mPlayer.isPlaying()) {
-                    mPlayer.pause();
-                } else {
-                    start();
-                }
-                updatePlayPause();
-                return true;
-            case KeyEvent.KEYCODE_MEDIA_PLAY:
-                start();
-                updatePlayPause();
-                return true;
-            case KeyEvent.KEYCODE_MEDIA_PAUSE:
-                if (mPlayer.isPlaying()) {
-                    mPlayer.pause();
-                }
-                updatePlayPause();
-                return true;
-            case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
-            case KeyEvent.KEYCODE_MEDIA_NEXT:
-            case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
-            case KeyEvent.KEYCODE_MEDIA_REWIND:
-                return true;
-            case KeyEvent.KEYCODE_MEDIA_STOP:
-            case KeyEvent.KEYCODE_BACK:
-                stopPlayback();
-                finish();
-                return true;
-        }
-        return super.onKeyDown(keyCode, event);
-    }
-
-    /*
-     * Wrapper class to help with handing off the MediaPlayer to the next instance
-     * of the activity in case of orientation change, without losing any state.
-     */
-    private static class PreviewPlayer extends MediaPlayer implements OnPreparedListener {
-        AudioPreview mActivity;
-        boolean mIsPrepared = false;
-
-        public void setActivity(AudioPreview activity) {
-            mActivity = activity;
-            setOnPreparedListener(this);
-            setOnErrorListener(mActivity);
-            setOnCompletionListener(mActivity);
-        }
-
-        public void setDataSourceAndPrepare(Uri uri) throws IllegalArgumentException,
-                                                            SecurityException,
-                                                            IllegalStateException, IOException {
-            setDataSource(mActivity, uri);
-            prepareAsync();
-        }
-
-        /* (non-Javadoc)
-         * @see android.media.MediaPlayer.OnPreparedListener#onPrepared(android.media.MediaPlayer)
-         */
-        @Override
-        public void onPrepared(MediaPlayer mp) {
-            mIsPrepared = true;
-            mActivity.onPrepared(mp);
-        }
-
-        boolean isPrepared() {
-            return mIsPrepared;
-        }
-    }
-}
diff --git a/src/com/android/music/CreatePlaylist.java b/src/com/android/music/CreatePlaylist.java
deleted file mode 100644
index 0f4ef88..0000000
--- a/src/com/android/music/CreatePlaylist.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * 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.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.MATCH_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) {
-            String newText = mPlaylist.getText().toString();
-            if (newText.trim().length() == 0) {
-                mSaveButton.setEnabled(false);
-            } else {
-                mSaveButton.setEnabled(true);
-                // check if playlist with current name exists already, and warn the user if so.
-                if (idForplaylist(newText) >= 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
deleted file mode 100644
index 59f9bba..0000000
--- a/src/com/android/music/DeleteItems.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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.media.AudioManager;
-import android.os.Bundle;
-import android.view.View;
-import android.view.Window;
-import android.view.WindowManager;
-import android.widget.Button;
-import android.widget.TextView;
-
-public class DeleteItems extends Activity {
-    private TextView mPrompt;
-    private Button mButton;
-    private long[] 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.MATCH_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.getLongArray("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
deleted file mode 100644
index 232abf2..0000000
--- a/src/com/android/music/IMediaPlaybackService.aidl
+++ /dev/null
@@ -1,56 +0,0 @@
-/* //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 open(in long [] 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();
-    long getAlbumId();
-    String getArtistName();
-    long getArtistId();
-    void enqueue(in long [] list, int action);
-    long [] getQueue();
-    void moveQueueItem(int from, int to);
-    void setQueuePosition(int index);
-    String getPath();
-    long getAudioId();
-    void setShuffleMode(int shufflemode);
-    int getShuffleMode();
-    int removeTracks(int first, int last);
-    int removeTrack(long id);
-    void setRepeatMode(int repeatmode);
-    int getRepeatMode();
-    int getMediaMountedCount();
-    int getAudioSessionId();
-}
-
diff --git a/src/com/android/music/MediaAppWidgetProvider.java b/src/com/android/music/MediaAppWidgetProvider.java
deleted file mode 100644
index 2f5b4f2..0000000
--- a/src/com/android/music/MediaAppWidgetProvider.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * 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.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProvider;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.os.Environment;
-import android.view.View;
-import android.widget.RemoteViews;
-
-/**
- * Simple widget to show currently playing album art along
- * with play/pause and next track buttons.
- */
-public class MediaAppWidgetProvider extends AppWidgetProvider {
-    static final String TAG = "MusicAppWidgetProvider";
-
-    public static final String CMDAPPWIDGETUPDATE = "appwidgetupdate";
-
-    private static MediaAppWidgetProvider sInstance;
-
-    static synchronized MediaAppWidgetProvider getInstance() {
-        if (sInstance == null) {
-            sInstance = new MediaAppWidgetProvider();
-        }
-        return sInstance;
-    }
-
-    @Override
-    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
-        defaultAppWidget(context, appWidgetIds);
-
-        // 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, MediaAppWidgetProvider.CMDAPPWIDGETUPDATE);
-        updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
-        updateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-        context.sendBroadcast(updateIntent);
-    }
-
-    /**
-     * Initialize given widgets to default state, where we launch Music on default click
-     * and hide actions if service not running.
-     */
-    private void defaultAppWidget(Context context, int[] appWidgetIds) {
-        final Resources res = context.getResources();
-        final RemoteViews views =
-                new RemoteViews(context.getPackageName(), R.layout.album_appwidget);
-
-        views.setViewVisibility(R.id.title, View.GONE);
-        views.setTextViewText(R.id.artist, res.getText(R.string.widget_initial_text));
-
-        linkButtons(context, views, false /* not playing */);
-        pushUpdate(context, appWidgetIds, views);
-    }
-
-    private void pushUpdate(Context context, int[] appWidgetIds, RemoteViews views) {
-        // Update specific list of appWidgetIds if given, otherwise default to all
-        final AppWidgetManager gm = AppWidgetManager.getInstance(context);
-        if (appWidgetIds != null) {
-            gm.updateAppWidget(appWidgetIds, views);
-        } else {
-            gm.updateAppWidget(new ComponentName(context, this.getClass()), views);
-        }
-    }
-
-    /**
-     * Check against {@link AppWidgetManager} if there are any instances of this widget.
-     */
-    private boolean hasInstances(Context context) {
-        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
-        int[] appWidgetIds =
-                appWidgetManager.getAppWidgetIds(new ComponentName(context, this.getClass()));
-        return (appWidgetIds.length > 0);
-    }
-
-    /**
-     * Handle a change notification coming over from {@link MediaPlaybackService}
-     */
-    void notifyChange(MediaPlaybackService service, String what) {
-        if (hasInstances(service)) {
-            if (MediaPlaybackService.META_CHANGED.equals(what)
-                    || MediaPlaybackService.PLAYSTATE_CHANGED.equals(what)) {
-                performUpdate(service, null);
-            }
-        }
-    }
-
-    /**
-     * Update all active widget instances by pushing changes
-     */
-    void performUpdate(MediaPlaybackService service, int[] appWidgetIds) {
-        final Resources res = service.getResources();
-        final RemoteViews views =
-                new RemoteViews(service.getPackageName(), R.layout.album_appwidget);
-
-        CharSequence titleName = service.getTrackName();
-        CharSequence artistName = service.getArtistName();
-        CharSequence errorState = null;
-
-        // Format title string with track number, or show SD card message
-        String status = Environment.getExternalStorageState();
-        if (status.equals(Environment.MEDIA_SHARED) || status.equals(Environment.MEDIA_UNMOUNTED)) {
-            if (android.os.Environment.isExternalStorageRemovable()) {
-                errorState = res.getText(R.string.sdcard_busy_title);
-            } else {
-                errorState = res.getText(R.string.sdcard_busy_title_nosdcard);
-            }
-        } else if (status.equals(Environment.MEDIA_REMOVED)) {
-            if (android.os.Environment.isExternalStorageRemovable()) {
-                errorState = res.getText(R.string.sdcard_missing_title);
-            } else {
-                errorState = res.getText(R.string.sdcard_missing_title_nosdcard);
-            }
-        } else if (titleName == null) {
-            errorState = res.getText(R.string.emptyplaylist);
-        }
-
-        if (errorState != null) {
-            // Show error state to user
-            views.setViewVisibility(R.id.title, View.GONE);
-            views.setTextViewText(R.id.artist, errorState);
-
-        } else {
-            // No error, so show normal titles
-            views.setViewVisibility(R.id.title, View.VISIBLE);
-            views.setTextViewText(R.id.title, titleName);
-            views.setTextViewText(R.id.artist, artistName);
-        }
-
-        // Set correct drawable for pause state
-        final boolean playing = service.isPlaying();
-        if (playing) {
-            views.setImageViewResource(R.id.control_play, R.drawable.ic_appwidget_music_pause);
-        } else {
-            views.setImageViewResource(R.id.control_play, R.drawable.ic_appwidget_music_play);
-        }
-
-        // Link actions buttons to intents
-        linkButtons(service, views, playing);
-
-        pushUpdate(service, appWidgetIds, views);
-    }
-
-    /**
-     * Link up various button actions using {@link PendingIntents}.
-     *
-     * @param playerActive True if player is active in background, which means
-     *            widget 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_appwidget, pendingIntent);
-        } else {
-            intent = new Intent(context, MusicBrowserActivity.class);
-            pendingIntent = PendingIntent.getActivity(
-                    context, 0 /* no requestCode */, intent, 0 /* no flags */);
-            views.setOnClickPendingIntent(R.id.album_appwidget, 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/MediaButtonIntentReceiver.java b/src/com/android/music/MediaButtonIntentReceiver.java
deleted file mode 100644
index a509039..0000000
--- a/src/com/android/music/MediaButtonIntentReceiver.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * 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.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.media.AudioManager;
-import android.os.Handler;
-import android.os.Message;
-import android.view.KeyEvent;
-
-/**
- *
- */
-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_MEDIA_STOP:
-                    command = MediaPlaybackService.CMDSTOP;
-                    break;
-                case KeyEvent.KEYCODE_HEADSETHOOK:
-                case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
-                    command = MediaPlaybackService.CMDTOGGLEPAUSE;
-                    break;
-                case KeyEvent.KEYCODE_MEDIA_NEXT:
-                    command = MediaPlaybackService.CMDNEXT;
-                    break;
-                case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
-                    command = MediaPlaybackService.CMDPREVIOUS;
-                    break;
-                case KeyEvent.KEYCODE_MEDIA_PAUSE:
-                    command = MediaPlaybackService.CMDPAUSE;
-                    break;
-                case KeyEvent.KEYCODE_MEDIA_PLAY:
-                    command = MediaPlaybackService.CMDPLAY;
-                    break;
-            }
-
-            if (command != null) {
-                if (action == KeyEvent.ACTION_DOWN) {
-                    if (mDown) {
-                        if ((MediaPlaybackService.CMDTOGGLEPAUSE.equals(command)
-                                    || MediaPlaybackService.CMDPLAY.equals(command))
-                                && mLastClickTime != 0
-                                && eventtime - mLastClickTime > LONG_PRESS_DELAY) {
-                            mHandler.sendMessage(
-                                    mHandler.obtainMessage(MSG_LONGPRESS_TIMEOUT, context));
-                        }
-                    } else if (event.getRepeatCount() == 0) {
-                        // only consider the first event in a sequence, not the repeat events,
-                        // so that we don't trigger in cases where the first event went to
-                        // a different app (e.g. when the user ends a phone call by
-                        // long pressing the headset button)
-
-                        // 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;
-                }
-                if (isOrderedBroadcast()) {
-                    abortBroadcast();
-                }
-            }
-        }
-    }
-}
diff --git a/src/com/android/music/MediaPickerActivity.java b/src/com/android/music/MediaPickerActivity.java
deleted file mode 100644
index 08bbb0e..0000000
--- a/src/com/android/music/MediaPickerActivity.java
+++ /dev/null
@@ -1,277 +0,0 @@
-/*
- * 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.MusicUtils.ServiceToken;
-
-import android.app.ListActivity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ContentUris;
-import android.database.Cursor;
-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 {
-    private ServiceToken mToken;
-
-    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);
-        }
-        mToken = MusicUtils.bindToService(this);
-        init();
-    }
-
-    @Override
-    public void onDestroy() {
-        MusicUtils.unbindFromService(mToken);
-        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;
-
-    static 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(MediaStore.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(MediaStore.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
index 671c5b4..099ef4c 100644
--- a/src/com/android/music/MediaPlaybackActivity.java
+++ b/src/com/android/music/MediaPlaybackActivity.java
@@ -16,140 +16,315 @@
 
 package com.android.music;
 
-import com.android.music.MusicUtils.ServiceToken;
-
 import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.KeyguardManager;
 import android.app.SearchManager;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.DialogInterface;
 import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.ServiceConnection;
-import android.content.pm.ResolveInfo;
-import android.content.res.Configuration;
-import android.database.Cursor;
 import android.graphics.Bitmap;
-import android.media.audiofx.AudioEffect;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
 import android.media.AudioManager;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.browse.MediaBrowser;
+import android.media.session.MediaController;
+import android.media.session.PlaybackState;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
 import android.os.Message;
-import android.os.RemoteException;
 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.*;
 import android.widget.SeekBar.OnSeekBarChangeListener;
+import com.android.music.utils.LogHelper;
+import com.android.music.utils.MediaIDHelper;
+import com.android.music.utils.MusicProvider;
 
-public class MediaPlaybackActivity extends Activity
-        implements MusicUtils.Defs, View.OnTouchListener, View.OnLongClickListener {
-    private static final int USE_AS_RINGTONE = CHILD_MENU_BASE;
+/*
+This is the Now Playing Activity
+ */
+public class MediaPlaybackActivity
+        extends Activity implements View.OnTouchListener, View.OnLongClickListener {
+    private static final String TAG = LogHelper.makeLogTag(MediaPlaybackActivity.class);
 
-    private boolean mSeeking = false;
-    private boolean mDeviceHasDpad;
     private long mStartSeekPos = 0;
     private long mLastSeekEventTime;
-    private IMediaPlaybackService mService = null;
     private RepeatingImageButton mPrevButton;
-    private ImageButton mPauseButton;
+    private ImageButton mPlayPauseButton;
     private RepeatingImageButton mNextButton;
     private ImageButton mRepeatButton;
     private ImageButton mShuffleButton;
     private ImageButton mQueueButton;
-    private Worker mAlbumArtWorker;
-    private AlbumArtHandler mAlbumArtHandler;
-    private Toast mToast;
     private int mTouchSlop;
-    private ServiceToken mToken;
 
-    public MediaPlaybackActivity() {}
+    private ImageView mAlbumArt;
+    private TextView mCurrentTime;
+    private TextView mTotalTime;
+    private TextView mArtistName;
+    private TextView mAlbumName;
+    private TextView mTrackName;
+    private LinearLayout mTrackInfo;
+    private ProgressBar mProgress;
+    private BitmapDrawable mDefaultAlbumArt;
+    private Toast mToast;
+
+    private MediaBrowser mMediaBrowser;
+    private final Handler mHandler = new Handler();
 
     /** Called when the activity is first created. */
     @Override
     public void onCreate(Bundle icicle) {
+        LogHelper.d(TAG, "onCreate()");
         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);
+        mAlbumArt = (ImageView) findViewById(R.id.album);
         mArtistName = (TextView) findViewById(R.id.artistname);
         mAlbumName = (TextView) findViewById(R.id.albumname);
         mTrackName = (TextView) findViewById(R.id.trackname);
+        mTrackInfo = (LinearLayout) findViewById(R.id.trackinfo);
 
+        Bitmap b = BitmapFactory.decodeResource(getResources(), R.drawable.albumart_mp_unknown);
+        mDefaultAlbumArt = new BitmapDrawable(getResources(), b);
+        // no filter or dither, it's a lot faster and we can't tell the difference
+        mDefaultAlbumArt.setFilterBitmap(false);
+        mDefaultAlbumArt.setDither(false);
+
+        /* Set metadata listeners */
         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);
 
+        /* Set button listeners */
         mPrevButton = (RepeatingImageButton) findViewById(R.id.prev);
-        mPrevButton.setOnClickListener(mPrevListener);
-        mPrevButton.setRepeatListener(mRewListener, 260);
-        mPauseButton = (ImageButton) findViewById(R.id.pause);
-        mPauseButton.requestFocus();
-        mPauseButton.setOnClickListener(mPauseListener);
+        mPrevButton.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                if (getMediaController() == null) return;
+                if (getMediaController().getPlaybackState().getPosition() < 2000) {
+                    getMediaController().getTransportControls().skipToPrevious();
+                } else {
+                    getMediaController().getTransportControls().seekTo(0);
+                    getMediaController().getTransportControls().play();
+                }
+            }
+        });
+        mPrevButton.setRepeatListener(new RepeatingImageButton.RepeatListener() {
+            public void onRepeat(View v, long howlong, int repcnt) {
+                scanBackward(repcnt, howlong);
+            }
+        }, 260);
+        mPlayPauseButton = (ImageButton) findViewById(R.id.pause);
+        mPlayPauseButton.requestFocus();
+        mPlayPauseButton.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                if (getMediaController() != null) {
+                    if (getMediaController().getPlaybackState().getState()
+                            != PlaybackState.STATE_PLAYING) {
+                        getMediaController().getTransportControls().play();
+                    } else {
+                        getMediaController().getTransportControls().pause();
+                    }
+                }
+            }
+        });
         mNextButton = (RepeatingImageButton) findViewById(R.id.next);
-        mNextButton.setOnClickListener(mNextListener);
-        mNextButton.setRepeatListener(mFfwdListener, 260);
-        seekmethod = 1;
-
-        mDeviceHasDpad =
-                (getResources().getConfiguration().navigation == Configuration.NAVIGATION_DPAD);
-
+        mNextButton.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                if (getMediaController() == null) return;
+                getMediaController().getTransportControls().skipToNext();
+            }
+        });
+        mNextButton.setRepeatListener(new RepeatingImageButton.RepeatListener() {
+            public void onRepeat(View v, long howlong, int repcnt) {
+                scanForward(repcnt, howlong);
+            }
+        }, 260);
         mQueueButton = (ImageButton) findViewById(R.id.curplaylist);
-        mQueueButton.setOnClickListener(mQueueListener);
+        mQueueButton.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                LogHelper.d(TAG, "mQueueButton onClick");
+                MediaBrowser.MediaItem parentItem = new MediaBrowser.MediaItem(
+                        new MediaDescription.Builder()
+                                .setMediaId(MediaIDHelper.createBrowseCategoryMediaID(
+                                        MediaIDHelper.MEDIA_ID_MUSICS_BY_PLAYLIST,
+                                        MediaIDHelper.MEDIA_ID_NOW_PLAYING))
+                                .setTitle("Now Playing")
+                                .build(),
+                        MediaBrowser.MediaItem.FLAG_BROWSABLE);
+                Intent intent = new Intent(Intent.ACTION_PICK)
+                                        .setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track")
+                                        .putExtra(MusicUtils.TAG_WITH_TABS, false)
+                                        .putExtra(MusicUtils.TAG_PARENT_ITEM, parentItem);
+                startActivity(intent);
+            }
+        });
         mShuffleButton = ((ImageButton) findViewById(R.id.shuffle));
-        mShuffleButton.setOnClickListener(mShuffleListener);
+        mShuffleButton.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                toggleShuffle();
+            }
+        });
         mRepeatButton = ((ImageButton) findViewById(R.id.repeat));
-        mRepeatButton.setOnClickListener(mRepeatListener);
+        mRepeatButton.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                LogHelper.d(TAG, "Repeat button clicked");
+                if (getMediaController() == null) return;
+                Bundle extras = getMediaController().getExtras();
+                if (extras == null) return;
+                MediaPlaybackService.RepeatMode repeatMode =
+                        MediaPlaybackService.RepeatMode
+                                .values()[extras.getInt(MediaPlaybackService.REPEAT_MODE)];
+                MediaPlaybackService.RepeatMode nextRepeatMode;
+                switch (repeatMode) {
+                    case REPEAT_NONE:
+                        nextRepeatMode = MediaPlaybackService.RepeatMode.REPEAT_ALL;
+                        showToast(R.string.repeat_all_notif);
+                        break;
+                    case REPEAT_ALL:
+                        nextRepeatMode = MediaPlaybackService.RepeatMode.REPEAT_CURRENT;
+                        showToast(R.string.repeat_current_notif);
+                        break;
+                    case REPEAT_CURRENT:
+                    default:
+                        nextRepeatMode = MediaPlaybackService.RepeatMode.REPEAT_NONE;
+                        showToast(R.string.repeat_off_notif);
+                        break;
+                }
+                setRepeatMode(nextRepeatMode);
+                // TODO(siyuanh): Should use a callback to register changes on service side
+                setRepeatButtonImage(nextRepeatMode);
+            }
+        });
 
         if (mProgress instanceof SeekBar) {
             SeekBar seeker = (SeekBar) mProgress;
-            seeker.setOnSeekBarChangeListener(mSeekListener);
+            seeker.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
+                boolean mmFromTouch = false;
+                public void onStartTrackingTouch(SeekBar bar) {
+                    mLastSeekEventTime = 0;
+                    mmFromTouch = true;
+                }
+                public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {
+                    if (!fromuser || (getMediaController() == null)) return;
+                    long now = SystemClock.elapsedRealtime();
+                    if ((now - mLastSeekEventTime) > 250) {
+                        mLastSeekEventTime = now;
+                        long duration = getMediaController().getMetadata().getLong(
+                                MediaMetadata.METADATA_KEY_DURATION);
+                        long position = duration * progress / 1000;
+                        getMediaController().getTransportControls().seekTo(position);
+                        // trackball event, allow progress updates
+                        if (!mmFromTouch) {
+                            updateProgressBar();
+                        }
+                    }
+                }
+                public void onStopTrackingTouch(SeekBar bar) {
+                    mmFromTouch = false;
+                }
+            });
+        } else {
+            LogHelper.d(TAG, "Seeking not supported");
         }
         mProgress.setMax(1000);
 
         mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
+
+        Log.d(TAG, "Creating MediaBrowser");
+        mMediaBrowser = new MediaBrowser(this, new ComponentName(this, MediaPlaybackService.class),
+                mConnectionCallback, null);
     }
 
+    // Receive callbacks from the MediaController. Here we update our state such as which queue
+    // is being shown, the current title and description and the PlaybackState.
+    private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() {
+
+        @Override
+        public void onSessionDestroyed() {
+            LogHelper.d(TAG, "Session destroyed. Need to fetch a new Media Session");
+        }
+
+        @Override
+        public void onPlaybackStateChanged(PlaybackState state) {
+            if (state == null) {
+                return;
+            }
+            LogHelper.d(TAG, "Received playback state change to state ", state.toString());
+            updateProgressBar();
+            setPauseButtonImage();
+        }
+
+        @Override
+        public void onMetadataChanged(MediaMetadata metadata) {
+            if (metadata == null) {
+                return;
+            }
+            LogHelper.d(TAG, "Received updated metadata: ", metadata);
+            updateTrackInfo();
+        }
+    };
+
+    private MediaBrowser.ConnectionCallback mConnectionCallback =
+            new MediaBrowser.ConnectionCallback() {
+                @Override
+                public void onConnected() {
+                    Log.d(TAG, "onConnected: session token " + mMediaBrowser.getSessionToken());
+                    if (mMediaBrowser.getSessionToken() == null) {
+                        throw new IllegalArgumentException("No Session token");
+                    }
+                    MediaController mediaController = new MediaController(
+                            MediaPlaybackActivity.this, mMediaBrowser.getSessionToken());
+                    mediaController.registerCallback(mMediaControllerCallback);
+                    MediaPlaybackActivity.this.setMediaController(mediaController);
+                    mRepeatButton.setVisibility(View.VISIBLE);
+                    mShuffleButton.setVisibility(View.VISIBLE);
+                    mQueueButton.setVisibility(View.VISIBLE);
+                    setRepeatButtonImage(null);
+                    setShuffleButtonImage();
+                    setPauseButtonImage();
+                    updateTrackInfo();
+                    mHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            long delay = updateProgressBar();
+                            mHandler.postDelayed(this, delay);
+                        }
+                    });
+                }
+
+                @Override
+                public void onConnectionFailed() {
+                    Log.d(TAG, "onConnectionFailed");
+                }
+
+                @Override
+                public void onConnectionSuspended() {
+                    Log.d(TAG, "onConnectionSuspended");
+                    mHandler.removeCallbacksAndMessages(null);
+                    MediaPlaybackActivity.this.setMediaController(null);
+                }
+            };
+
     int mInitialX = -1;
     int mLastX = -1;
     int mTextWidth = 0;
@@ -258,26 +433,22 @@
         CharSequence title = null;
         String mime = null;
         String query = null;
-        String artist;
-        String album;
-        String song;
-        long audioid;
-
-        try {
-            artist = mService.getArtistName();
-            album = mService.getAlbumName();
-            song = mService.getTrackName();
-            audioid = mService.getAudioId();
-        } catch (RemoteException ex) {
-            return true;
-        } catch (NullPointerException ex) {
-            // we might not actually have the service yet
+        if (getMediaController() == null) {
+            LogHelper.d(TAG, "No media controller avalable yet");
             return true;
         }
+        MediaMetadata metadata = getMediaController().getMetadata();
+        if (metadata == null) {
+            LogHelper.d(TAG, "No metadata avalable yet");
+            return true;
+        }
+        String artist = metadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
+        String album = metadata.getString(MediaMetadata.METADATA_KEY_ALBUM);
+        String song = metadata.getString(MediaMetadata.METADATA_KEY_TITLE);
+        long audioid = metadata.getLong(MediaMetadata.METADATA_KEY_MEDIA_ID);
 
-        if (MediaStore.UNKNOWN_STRING.equals(album) && MediaStore.UNKNOWN_STRING.equals(artist)
-                && song != null && song.startsWith("recording")) {
-            // not music
+        if (album == null && artist == null && song != null && song.startsWith("recording")) {
+            LogHelper.d(TAG, "Item is not music");
             return false;
         }
 
@@ -285,22 +456,7 @@
             return false;
         }
 
-        Cursor c = MusicUtils.query(this,
-                ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, audioid),
-                new String[] {MediaStore.Audio.Media.IS_MUSIC}, null, null, null);
-        boolean ismusic = true;
-        if (c != null) {
-            if (c.moveToFirst()) {
-                ismusic = c.getInt(0) != 0;
-            }
-            c.close();
-        }
-        if (!ismusic) {
-            return false;
-        }
-
         boolean knownartist = (artist != null) && !MediaStore.UNKNOWN_STRING.equals(artist);
-
         boolean knownalbum = (album != null) && !MediaStore.UNKNOWN_STRING.equals(album);
 
         if (knownartist && view.equals(mArtistName.getParent())) {
@@ -351,137 +507,23 @@
         return true;
     }
 
-    private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
-        public void onStartTrackingTouch(SeekBar bar) {
-            mLastSeekEventTime = 0;
-            mFromTouch = true;
-        }
-        public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {
-            if (!fromuser || (mService == null)) return;
-            long now = SystemClock.elapsedRealtime();
-            if ((now - mLastSeekEventTime) > 250) {
-                mLastSeekEventTime = now;
-                mPosOverride = mDuration * progress / 1000;
-                try {
-                    mService.seek(mPosOverride);
-                } catch (RemoteException ex) {
-                }
-
-                // trackball event, allow progress updates
-                if (!mFromTouch) {
-                    refreshNow();
-                    mPosOverride = -1;
-                }
-            }
-        }
-        public void onStopTrackingTouch(SeekBar bar) {
-            mPosOverride = -1;
-            mFromTouch = false;
-        }
-    };
-
-    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 onStart() {
+        LogHelper.d(TAG, "onStart()");
+        super.onStart();
+        mMediaBrowser.connect();
+    }
 
     @Override
     public void onStop() {
-        paused = true;
-        mHandler.removeMessages(REFRESH);
-        unregisterReceiver(mStatusListener);
-        MusicUtils.unbindFromService(mToken);
-        mService = null;
+        LogHelper.d(TAG, "onStop()");
+        mMediaBrowser.disconnect();
         super.onStop();
     }
 
     @Override
-    public void onStart() {
-        super.onStart();
-        paused = false;
-
-        mToken = MusicUtils.bindToService(this, osc);
-        if (mToken == null) {
-            // something went wrong
-            mHandler.sendEmptyMessage(QUIT);
-        }
-
-        IntentFilter f = new IntentFilter();
-        f.addAction(MediaPlaybackService.PLAYSTATE_CHANGED);
-        f.addAction(MediaPlaybackService.META_CHANGED);
-        registerReceiver(mStatusListener, new IntentFilter(f));
-        updateTrackInfo();
-        long next = refreshNow();
-        queueNextRefresh(next);
-    }
-
-    @Override
-    public void onNewIntent(Intent intent) {
-        setIntent(intent);
-    }
-
-    @Override
     public void onResume() {
+        LogHelper.d(TAG, "onResume()");
         super.onResume();
         updateTrackInfo();
         setPauseButtonImage();
@@ -489,509 +531,102 @@
 
     @Override
     public void onDestroy() {
-        mAlbumArtWorker.quit();
+        LogHelper.d(TAG, "onDestroy()");
         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, or
-        // if we're in one shot mode. In most cases, these menu items are not
-        // useful in those modes, so for consistency we never show them in these
-        // modes, instead of tailoring them to the specific file being played.
-        if (MusicUtils.getCurrentAudioId() >= 0) {
-            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);
-            // these next two are in a separate group, so they can be shown/hidden as needed
-            // based on the keyguard state
-            menu.add(1, USE_AS_RINGTONE, 0, R.string.ringtone_menu_short)
-                    .setIcon(R.drawable.ic_menu_set_as_ringtone);
-            menu.add(1, DELETE_ITEM, 0, R.string.delete_item).setIcon(R.drawable.ic_menu_delete);
-
-            Intent i = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
-            if (getPackageManager().resolveActivity(i, 0) != null) {
-                menu.add(0, EFFECTS_PANEL, 0, R.string.effectspanel).setIcon(R.drawable.ic_menu_eq);
-            }
-
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public boolean onPrepareOptionsMenu(Menu menu) {
-        if (mService == null) return false;
-        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);
-            }
-        }
-
-        item = menu.findItem(ADD_TO_PLAYLIST);
-        if (item != null) {
-            SubMenu sub = item.getSubMenu();
-            MusicUtils.makePlaylistMenu(this, sub);
-        }
-
-        KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
-        menu.setGroupVisible(1, !km.inKeyguardRestrictedInputMode());
-
-        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_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
-                    startActivity(intent);
-                    finish();
-                    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:
-                    MusicUtils.togglePartyShuffle();
-                    setShuffleButtonImage();
-                    break;
-
-                case NEW_PLAYLIST: {
-                    intent = new Intent();
-                    intent.setClass(this, CreatePlaylist.class);
-                    startActivityForResult(intent, NEW_PLAYLIST);
-                    return true;
-                }
-
-                case PLAYLIST_SELECTED: {
-                    long[] list = new long[1];
-                    list[0] = MusicUtils.getCurrentAudioId();
-                    long playlist = item.getIntent().getLongExtra("playlist", 0);
-                    MusicUtils.addToPlaylist(this, list, playlist);
-                    return true;
-                }
-
-                case DELETE_ITEM: {
-                    if (mService != null) {
-                        long[] list = new long[1];
-                        list[0] = MusicUtils.getCurrentAudioId();
-                        Bundle b = new Bundle();
-                        String f;
-                        if (android.os.Environment.isExternalStorageRemovable()) {
-                            f = getString(R.string.delete_song_desc, mService.getTrackName());
-                        } else {
-                            f = getString(
-                                    R.string.delete_song_desc_nosdcard, mService.getTrackName());
-                        }
-                        b.putString("description", f);
-                        b.putLongArray("items", list);
-                        intent = new Intent();
-                        intent.setClass(this, DeleteItems.class);
-                        intent.putExtras(b);
-                        startActivityForResult(intent, -1);
-                    }
-                    return true;
-                }
-
-                case EFFECTS_PANEL: {
-                    Intent i = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
-                    i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, mService.getAudioSessionId());
-                    startActivityForResult(i, EFFECTS_PANEL);
-                    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) {
-                    long[] list = new long[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) {
-        if (mService == null) return false;
-        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 (!useDpadMusicControl()) {
-                        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 (!useDpadMusicControl()) {
-                        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);
-    }
-
-    private boolean useDpadMusicControl() {
-        if (mDeviceHasDpad && (mPrevButton.isFocused() || mNextButton.isFocused()
-                                      || mPauseButton.isFocused())) {
-            return true;
-        }
-        return false;
-    }
-
-    @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 (!useDpadMusicControl()) {
-                    break;
-                }
-                if (!mPrevButton.hasFocus()) {
-                    mPrevButton.requestFocus();
-                }
-                scanBackward(repcnt, event.getEventTime() - event.getDownTime());
-                return true;
-            case KeyEvent.KEYCODE_DPAD_RIGHT:
-                if (!useDpadMusicControl()) {
-                    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;
+        if (getMediaController() == null) return;
+        if (repcnt == 0) {
+            mStartSeekPos = getMediaController().getPlaybackState().getPosition();
+            mLastSeekEventTime = 0;
+        } else {
+            if (delta < 5000) {
+                // seek at 10x speed for the first 5 seconds
+                delta = delta * 10;
             } 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();
+                // seek at 40x after that
+                delta = 50000 + (delta - 5000) * 40;
             }
-        } catch (RemoteException ex) {
+            long newpos = mStartSeekPos - delta;
+            if (newpos < 0) {
+                // move to previous track
+                getMediaController().getTransportControls().skipToPrevious();
+                long duration = getMediaController().getMetadata().getLong(
+                        MediaMetadata.METADATA_KEY_DURATION);
+                mStartSeekPos += duration;
+                newpos += duration;
+            }
+            if (((delta - mLastSeekEventTime) > 250) || repcnt < 0) {
+                getMediaController().getTransportControls().seekTo(newpos);
+                mLastSeekEventTime = delta;
+            }
+            updateProgressBar();
         }
     }
 
     private void scanForward(int repcnt, long delta) {
-        if (mService == null) return;
-        try {
-            if (repcnt == 0) {
-                mStartSeekPos = mService.position();
-                mLastSeekEventTime = 0;
-                mSeeking = false;
+        if (getMediaController() == null) return;
+        if (repcnt == 0) {
+            mStartSeekPos = getMediaController().getPlaybackState().getPosition();
+            mLastSeekEventTime = 0;
+        } else {
+            if (delta < 5000) {
+                // seek at 10x speed for the first 5 seconds
+                delta = delta * 10;
             } 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();
+                // seek at 40x after that
+                delta = 50000 + (delta - 5000) * 40;
             }
-        } catch (RemoteException ex) {
-        }
-    }
-
-    private void doPauseResume() {
-        try {
-            if (mService != null) {
-                if (mService.isPlaying()) {
-                    mService.pause();
-                } else {
-                    mService.play();
-                }
-                refreshNow();
-                setPauseButtonImage();
+            long newpos = mStartSeekPos + delta;
+            long duration =
+                    getMediaController().getMetadata().getLong(MediaMetadata.METADATA_KEY_DURATION);
+            if (newpos >= duration) {
+                // move to next track
+                getMediaController().getTransportControls().skipToNext();
+                mStartSeekPos -= duration; // is OK to go negative
+                newpos -= duration;
             }
-        } catch (RemoteException ex) {
+            if (((delta - mLastSeekEventTime) > 250) || repcnt < 0) {
+                getMediaController().getTransportControls().seekTo(newpos);
+                mLastSeekEventTime = delta;
+            }
+            updateProgressBar();
         }
     }
 
     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) {
-        }
+        // TODO(b/36371715): Implement shuffle for SHUFFLE_NORMAL, SHUFFLE_AUTO, SHUFFLE_NONE
+        LogHelper.d(TAG, "Shuffle not implemented yet");
+        Toast.makeText(this, "Shuffle not implemented yet", Toast.LENGTH_SHORT).show();
     }
 
-    private void cycleRepeat() {
-        if (mService == null) {
-            return;
+    private void setRepeatMode(MediaPlaybackService.RepeatMode repeatMode) {
+        Bundle extras = new Bundle();
+        extras.putInt(MediaPlaybackService.REPEAT_MODE, repeatMode.ordinal());
+        getMediaController().getTransportControls().sendCustomAction(
+                MediaPlaybackService.CMD_REPEAT, extras);
+    }
+
+    private void setRepeatButtonImage(MediaPlaybackService.RepeatMode repeatMode) {
+        if (getMediaController() == null) return;
+        Bundle extras = getMediaController().getExtras();
+        if (extras == null) return;
+        if (repeatMode == null) {
+            repeatMode = MediaPlaybackService.RepeatMode
+                                 .values()[extras.getInt(MediaPlaybackService.REPEAT_MODE)];
         }
-        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) {
+        switch (repeatMode) {
+            case REPEAT_CURRENT:
+                mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_once_btn);
+                break;
+            case REPEAT_ALL:
+                mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_all_btn);
+                break;
+            case REPEAT_NONE:
+            default:
+                mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_off_btn);
+                break;
         }
     }
 
@@ -1003,368 +638,93 @@
         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 {
-                mService.stop();
-                mService.openFile(filename);
-                mService.play();
-                setIntent(new Intent());
-            } 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);
-            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
-                    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) {
-            mService = null;
-        }
-    };
-
-    private void setRepeatButtonImage() {
-        if (mService == null) return;
-        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() {
-        if (mService == null) return;
-        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) {
-        }
+        if (getMediaController() == null) return;
+        mShuffleButton.setImageResource(R.drawable.ic_mp_shuffle_off_btn);
     }
 
     private void setPauseButtonImage() {
-        try {
-            if (mService != null && mService.isPlaying()) {
-                mPauseButton.setImageResource(android.R.drawable.ic_media_pause);
+        if (getMediaController() == null) {
+            return;
+        }
+        if (getMediaController().getPlaybackState().getState() != PlaybackState.STATE_PLAYING) {
+            mPlayPauseButton.setImageResource(android.R.drawable.ic_media_play);
+        } else {
+            mPlayPauseButton.setImageResource(android.R.drawable.ic_media_pause);
+        }
+    }
+
+    private long updateProgressBar() {
+        MediaController mediaController = getMediaController();
+        if (mediaController == null) {
+            return 500;
+        }
+        long duration =
+                getMediaController().getMetadata().getLong(MediaMetadata.METADATA_KEY_DURATION);
+        long pos = mediaController.getPlaybackState().getPosition();
+        if ((pos >= 0) && (duration > 0)) {
+            mCurrentTime.setText(MusicUtils.makeTimeString(this, pos / 1000));
+            int progress = (int) (1000 * pos / duration);
+            mProgress.setProgress(progress);
+
+            if (mediaController.getPlaybackState().getState() == PlaybackState.STATE_PLAYING) {
+                mCurrentTime.setVisibility(View.VISIBLE);
             } else {
-                mPauseButton.setImageResource(android.R.drawable.ic_media_play);
+                // blink the counter
+                int vis = mCurrentTime.getVisibility();
+                mCurrentTime.setVisibility(vis == View.INVISIBLE ? View.VISIBLE : View.INVISIBLE);
+                return 500;
             }
-        } catch (RemoteException ex) {
+        } else {
+            mCurrentTime.setText("--:--");
+            mProgress.setProgress(1000);
         }
-    }
+        // calculate the number of milliseconds until the next full second, so
+        // the counter can be updated at just the right time
+        long remaining = 1000 - (pos % 1000);
 
-    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 boolean mFromTouch = false;
-    private long mDuration;
-    private int seekmethod;
-    private boolean paused;
+        // approximate how often we would need to refresh the slider to
+        // move it smoothly
+        int width = mProgress.getWidth();
+        if (width == 0) width = 320;
+        long smoothrefreshtime = duration / width;
 
-    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;
-            if ((pos >= 0) && (mDuration > 0)) {
-                mCurrentTime.setText(MusicUtils.makeTimeString(this, pos / 1000));
-                int progress = (int) (1000 * pos / mDuration);
-                mProgress.setProgress(progress);
-
-                if (mService.isPlaying()) {
-                    mCurrentTime.setVisibility(View.VISIBLE);
-                } else {
-                    // blink the counter
-                    int vis = mCurrentTime.getVisibility();
-                    mCurrentTime.setVisibility(
-                            vis == View.INVISIBLE ? View.VISIBLE : View.INVISIBLE);
-                    return 500;
-                }
-            } else {
-                mCurrentTime.setText("--:--");
-                mProgress.setProgress(1000);
-            }
-            // calculate the number of milliseconds until the next full second, so
-            // the counter can be updated at just the right time
-            long remaining = 1000 - (pos % 1000);
-
-            // approximate how often we would need to refresh the slider to
-            // move it smoothly
-            int width = mProgress.getWidth();
-            if (width == 0) width = 320;
-            long smoothrefreshtime = mDuration / width;
-
-            if (smoothrefreshtime > remaining) return remaining;
-            if (smoothrefreshtime < 20) return 20;
-            return smoothrefreshtime;
-        } 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.PLAYSTATE_CHANGED)) {
-                setPauseButtonImage();
-            }
-        }
-    };
-
-    private static class AlbumSongIdWrapper {
-        public long albumid;
-        public long songid;
-        AlbumSongIdWrapper(long aid, long sid) {
-            albumid = aid;
-            songid = sid;
-        }
+        if (smoothrefreshtime > remaining) return remaining;
+        if (smoothrefreshtime < 20) return 20;
+        return smoothrefreshtime;
     }
 
     private void updateTrackInfo() {
-        if (mService == null) {
+        LogHelper.d(TAG, "updateTrackInfo()");
+        if (getMediaController() == null) {
             return;
         }
-        try {
-            String path = mService.getPath();
-            if (path == null) {
-                finish();
-                return;
-            }
-
-            long songid = mService.getAudioId();
-            if (songid < 0 && path.toLowerCase().startsWith("http://")) {
-                // Once we can get album art and meta data from MediaPlayer, we
-                // can show that info again when streaming.
-                ((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, new AlbumSongIdWrapper(-1, -1))
-                        .sendToTarget();
-            } else {
-                ((View) mArtistName.getParent()).setVisibility(View.VISIBLE);
-                ((View) mAlbumName.getParent()).setVisibility(View.VISIBLE);
-                String artistName = mService.getArtistName();
-                if (MediaStore.UNKNOWN_STRING.equals(artistName)) {
-                    artistName = getString(R.string.unknown_artist_name);
-                }
-                mArtistName.setText(artistName);
-                String albumName = mService.getAlbumName();
-                long albumid = mService.getAlbumId();
-                if (MediaStore.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, new AlbumSongIdWrapper(albumid, songid))
-                        .sendToTarget();
-                mAlbum.setVisibility(View.VISIBLE);
-            }
-            mDuration = mService.duration();
-            mTotalTime.setText(MusicUtils.makeTimeString(this, mDuration / 1000));
-        } catch (RemoteException ex) {
-            finish();
+        MediaMetadata metadata = getMediaController().getMetadata();
+        if (metadata == null) {
+            return;
         }
-    }
-
-    public class AlbumArtHandler extends Handler {
-        private long mAlbumId = -1;
-
-        public AlbumArtHandler(Looper looper) {
-            super(looper);
+        mTrackInfo.setVisibility(View.VISIBLE);
+        mTrackName.setText(metadata.getString(MediaMetadata.METADATA_KEY_TITLE));
+        LogHelper.d(TAG, "Track Name: ", mTrackName.getText());
+        String artistName = metadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
+        if (artistName.equals(MusicProvider.UNKOWN)) {
+            artistName = getString(R.string.unknown_artist_name);
         }
-        @Override
-        public void handleMessage(Message msg) {
-            long albumid = ((AlbumSongIdWrapper) msg.obj).albumid;
-            long songid = ((AlbumSongIdWrapper) msg.obj).songid;
-            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);
-                // Don't allow default artwork here, because we want to fall back to song-specific
-                // album art if we can't find anything for the album.
-                Bitmap bm =
-                        MusicUtils.getArtwork(MediaPlaybackActivity.this, songid, albumid, false);
-                if (bm == null) {
-                    bm = MusicUtils.getArtwork(MediaPlaybackActivity.this, songid, -1);
-                    albumid = -1;
-                }
-                if (bm != null) {
-                    numsg = mHandler.obtainMessage(ALBUM_ART_DECODED, bm);
-                    mHandler.removeMessages(ALBUM_ART_DECODED);
-                    mHandler.sendMessage(numsg);
-                }
-                mAlbumId = albumid;
-            }
+        mArtistName.setText(artistName);
+        String albumName = metadata.getString(MediaMetadata.METADATA_KEY_ALBUM);
+        if (albumName.equals(MusicProvider.UNKOWN)) {
+            albumName = getString(R.string.unknown_album_name);
         }
-    }
-
-    private static 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) {
-                    }
-                }
-            }
+        mAlbumName.setText(albumName);
+        Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+        if (albumArt != null) {
+            mAlbumArt.setImageBitmap(albumArt);
+        } else {
+            mAlbumArt.setImageDrawable(mDefaultAlbumArt);
         }
+        mAlbumArt.setVisibility(View.VISIBLE);
 
-        public Looper getLooper() {
-            return mLooper;
-        }
-
-        public void run() {
-            synchronized (mLock) {
-                Looper.prepare();
-                mLooper = Looper.myLooper();
-                mLock.notifyAll();
-            }
-            Looper.loop();
-        }
-
-        public void quit() {
-            mLooper.quit();
-        }
+        long duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
+        mTotalTime.setText(MusicUtils.makeTimeString(this, duration / 1000));
     }
 }
diff --git a/src/com/android/music/MediaPlaybackService.java b/src/com/android/music/MediaPlaybackService.java
index 07cd81d..70d4f4b 100644
--- a/src/com/android/music/MediaPlaybackService.java
+++ b/src/com/android/music/MediaPlaybackService.java
@@ -16,2173 +16,740 @@
 
 package com.android.music;
 
-import android.app.Notification;
 import android.app.PendingIntent;
-import android.app.Service;
-import android.appwidget.AppWidgetManager;
-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.BroadcastReceiver;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteException;
 import android.graphics.Bitmap;
-import android.media.audiofx.AudioEffect;
-import android.media.AudioManager;
-import android.media.AudioManager.OnAudioFocusChangeListener;
-import android.media.MediaMetadataRetriever;
-import android.media.MediaPlayer;
-import android.media.MediaPlayer.OnCompletionListener;
-import android.media.RemoteControlClient;
-import android.media.RemoteControlClient.MetadataEditor;
-import android.net.Uri;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.browse.MediaBrowser.MediaItem;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.os.Bundle;
 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.service.media.MediaBrowserService;
+import android.text.TextUtils;
 import android.util.Log;
-import android.widget.RemoteViews;
-import android.widget.Toast;
+import com.android.music.utils.*;
 
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
-import java.util.Random;
-import java.util.Vector;
+import java.util.*;
+
+import static com.android.music.utils.MediaIDHelper.*;
 
 /**
  * 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 class MediaPlaybackService extends MediaBrowserService implements Playback.Callback {
+    private static final String TAG = LogHelper.makeLogTag(MediaPlaybackService.class);
 
-    public static final int SHUFFLE_NONE = 0;
-    public static final int SHUFFLE_NORMAL = 1;
-    public static final int SHUFFLE_AUTO = 2;
+    // Delay stopSelf by using a handler.
+    private static final int STOP_DELAY = 30000;
 
-    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 ACTION_CMD = "com.android.music.ACTION_CMD";
+    public static final String CMD_NAME = "CMD_NAME";
+    public static final String CMD_PAUSE = "CMD_PAUSE";
+    public static final String CMD_REPEAT = "CMD_PAUSE";
+    public static final String REPEAT_MODE = "REPEAT_MODE";
 
-    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 enum RepeatMode { REPEAT_NONE, REPEAT_ALL, REPEAT_CURRENT }
 
-    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 CMDPLAY = "play";
-    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 TRACK_ENDED = 1;
-    private static final int RELEASE_WAKELOCK = 2;
-    private static final int SERVER_DIED = 3;
-    private static final int FOCUSCHANGE = 4;
-    private static final int FADEDOWN = 5;
-    private static final int FADEUP = 6;
-    private static final int TRACK_WENT_TO_NEXT = 7;
-    private static final int MAX_HISTORY_SIZE = 100;
-
-    private MultiPlayer mPlayer;
-    private String mFileToPlay;
-    private int mShuffleMode = SHUFFLE_NONE;
-    private int mRepeatMode = REPEAT_NONE;
-    private int mMediaMountedCount = 0;
-    private long[] mAutoShuffleList = null;
-    private long[] mPlayList = null;
-    private int mPlayListLen = 0;
-    private Vector<Integer> mHistory = new Vector<Integer>(MAX_HISTORY_SIZE);
-    private Cursor mCursor;
-    private int mPlayPos = -1;
-    private int mNextPlayPos = -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 mIsSupposedToBePlaying = false;
-    private boolean mQuietMode = false;
-    private AudioManager mAudioManager;
-    private boolean mQueueIsSaveable = true;
-    // used to track what type of audio focus loss caused the playback to pause
-    private boolean mPausedByTransientLossOfFocus = 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 MediaAppWidgetProvider mAppWidgetProvider = MediaAppWidgetProvider.getInstance();
-
-    // interval after which we stop the service when idle
-    private static final int IDLE_DELAY = 60000;
-
-    private RemoteControlClient mRemoteControlClient;
-
-    private Handler mMediaplayerHandler = new Handler() {
-        float mCurrentVolume = 1.0f;
-        @Override
-        public void handleMessage(Message msg) {
-            MusicUtils.debugLog("mMediaplayerHandler.handleMessage " + msg.what);
-            switch (msg.what) {
-                case FADEDOWN:
-                    mCurrentVolume -= .05f;
-                    if (mCurrentVolume > .2f) {
-                        mMediaplayerHandler.sendEmptyMessageDelayed(FADEDOWN, 10);
-                    } else {
-                        mCurrentVolume = .2f;
-                    }
-                    mPlayer.setVolume(mCurrentVolume);
-                    break;
-                case FADEUP:
-                    mCurrentVolume += .01f;
-                    if (mCurrentVolume < 1.0f) {
-                        mMediaplayerHandler.sendEmptyMessageDelayed(FADEUP, 10);
-                    } else {
-                        mCurrentVolume = 1.0f;
-                    }
-                    mPlayer.setVolume(mCurrentVolume);
-                    break;
-                case SERVER_DIED:
-                    if (mIsSupposedToBePlaying) {
-                        gotoNext(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)
-                        openCurrentAndNext();
-                    }
-                    break;
-                case TRACK_WENT_TO_NEXT:
-                    mPlayPos = mNextPlayPos;
-                    if (mCursor != null) {
-                        mCursor.close();
-                        mCursor = null;
-                    }
-                    if (mPlayPos >= 0 && mPlayPos < mPlayList.length) {
-                        mCursor = getCursorForId(mPlayList[mPlayPos]);
-                    }
-                    notifyChange(META_CHANGED);
-                    updateNotification();
-                    setNextTrack();
-                    break;
-                case TRACK_ENDED:
-                    if (mRepeatMode == REPEAT_CURRENT) {
-                        seek(0);
-                        play();
-                    } else {
-                        gotoNext(false);
-                    }
-                    break;
-                case RELEASE_WAKELOCK:
-                    mWakeLock.release();
-                    break;
-
-                case FOCUSCHANGE:
-                    // This code is here so we can better synchronize it with the code that
-                    // handles fade-in
-                    switch (msg.arg1) {
-                        case AudioManager.AUDIOFOCUS_LOSS:
-                            Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS");
-                            if (isPlaying()) {
-                                mPausedByTransientLossOfFocus = false;
-                            }
-                            pause();
-                            break;
-                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
-                            mMediaplayerHandler.removeMessages(FADEUP);
-                            mMediaplayerHandler.sendEmptyMessage(FADEDOWN);
-                            break;
-                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
-                            Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS_TRANSIENT");
-                            if (isPlaying()) {
-                                mPausedByTransientLossOfFocus = true;
-                            }
-                            pause();
-                            break;
-                        case AudioManager.AUDIOFOCUS_GAIN:
-                            Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_GAIN");
-                            if (!isPlaying() && mPausedByTransientLossOfFocus) {
-                                mPausedByTransientLossOfFocus = false;
-                                mCurrentVolume = 0f;
-                                mPlayer.setVolume(mCurrentVolume);
-                                play(); // also queues a fade-in
-                            } else {
-                                mMediaplayerHandler.removeMessages(FADEDOWN);
-                                mMediaplayerHandler.sendEmptyMessage(FADEUP);
-                            }
-                            break;
-                        default:
-                            Log.e(LOGTAG, "Unknown audio focus change code");
-                    }
-                    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");
-            MusicUtils.debugLog("mIntentReceiver.onReceive " + action + " / " + cmd);
-            if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
-                gotoNext(true);
-            } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
-                prev();
-            } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
-                if (isPlaying()) {
-                    pause();
-                    mPausedByTransientLossOfFocus = false;
-                } else {
-                    play();
-                }
-            } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
-                pause();
-                mPausedByTransientLossOfFocus = false;
-            } else if (CMDPLAY.equals(cmd)) {
-                play();
-            } else if (CMDSTOP.equals(cmd)) {
-                pause();
-                mPausedByTransientLossOfFocus = false;
-                seek(0);
-            } else if (MediaAppWidgetProvider.CMDAPPWIDGETUPDATE.equals(cmd)) {
-                // Someone asked us to refresh a set of specific widgets, probably
-                // because they were just added.
-                int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
-                mAppWidgetProvider.performUpdate(MediaPlaybackService.this, appWidgetIds);
-            }
-        }
-    };
-
-    private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
-        public void onAudioFocusChange(int focusChange) {
-            mMediaplayerHandler.obtainMessage(FOCUSCHANGE, focusChange, 0).sendToTarget();
-        }
-    };
+    // Music catalog manager
+    private MusicProvider mMusicProvider;
+    private MediaSession mSession;
+    // "Now playing" queue:
+    private List<MediaSession.QueueItem> mPlayingQueue = null;
+    private int mCurrentIndexOnQueue = -1;
+    private MediaNotificationManager mMediaNotificationManager;
+    // Indicates whether the service was started.
+    private boolean mServiceStarted;
+    private DelayedStopHandler mDelayedStopHandler = new DelayedStopHandler(this);
+    private Playback mPlayback;
+    // Default mode is repeat none
+    private RepeatMode mRepeatMode = RepeatMode.REPEAT_NONE;
+    // Extra information for this session
+    private Bundle mExtras;
 
     public MediaPlaybackService() {}
 
     @Override
     public void onCreate() {
+        LogHelper.d(TAG, "onCreate()");
         super.onCreate();
+        LogHelper.d(TAG, "Create MusicProvider");
+        mPlayingQueue = new ArrayList<>();
+        mMusicProvider = new MusicProvider(this);
 
-        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
-        ComponentName rec =
-                new ComponentName(getPackageName(), MediaButtonIntentReceiver.class.getName());
-        mAudioManager.registerMediaButtonEventReceiver(rec);
+        LogHelper.d(TAG, "Create MediaSession");
+        // Start a new MediaSession
+        mSession = new MediaSession(this, "MediaPlaybackService");
+        // Set extra information
+        mExtras = new Bundle();
+        mExtras.putInt(REPEAT_MODE, mRepeatMode.ordinal());
+        mSession.setExtras(mExtras);
+        // Enable callbacks from MediaButtons and TransportControls
+        mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
+                | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
+        // Set an initial PlaybackState with ACTION_PLAY, so media buttons can start the player
+        PlaybackState.Builder stateBuilder = new PlaybackState.Builder().setActions(
+                PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PLAY_PAUSE);
+        mSession.setPlaybackState(stateBuilder.build());
+        // MediaSessionCallback() has methods that handle callbacks from a media controller
+        mSession.setCallback(new MediaSessionCallback());
+        // Set the session's token so that client activities can communicate with it.
+        setSessionToken(mSession.getSessionToken());
 
-        Intent i = new Intent(Intent.ACTION_MEDIA_BUTTON);
-        i.setComponent(rec);
-        PendingIntent pi = PendingIntent.getBroadcast(
-                this /*context*/, 0 /*requestCode, ignored*/, i /*intent*/, 0 /*flags*/);
-        mRemoteControlClient = new RemoteControlClient(pi);
-        mAudioManager.registerRemoteControlClient(mRemoteControlClient);
+        mPlayback = new Playback(this, mMusicProvider);
+        mPlayback.setState(PlaybackState.STATE_NONE);
+        mPlayback.setCallback(this);
+        mPlayback.start();
 
-        int flags = RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS
-                | RemoteControlClient.FLAG_KEY_MEDIA_NEXT | RemoteControlClient.FLAG_KEY_MEDIA_PLAY
-                | RemoteControlClient.FLAG_KEY_MEDIA_PAUSE
-                | RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE
-                | RemoteControlClient.FLAG_KEY_MEDIA_STOP;
-        mRemoteControlClient.setTransportControlFlags(flags);
+        Context context = getApplicationContext();
+        Intent intent = new Intent(context, MusicBrowserActivity.class);
+        PendingIntent pi = PendingIntent.getActivity(
+                context, 99 /*request code*/, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+        mSession.setSessionActivity(pi);
 
-        mPreferences = getSharedPreferences("Music", MODE_PRIVATE);
-        mCardId = MusicUtils.getCardId(this);
+        updatePlaybackState(null);
 
-        registerExternalStorageListener();
-
-        // Needs to be done in this thread, since otherwise ApplicationContext.getPowerManager()
-        // crashes.
-        mPlayer = new MultiPlayer();
-        mPlayer.setHandler(mMediaplayerHandler);
-
-        reloadQueue();
-        notifyChange(QUEUE_CHANGED);
-        notifyChange(META_CHANGED);
-
-        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);
-
-        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);
+        mMediaNotificationManager = new MediaNotificationManager(this);
     }
 
     @Override
-    public void onDestroy() {
-        // Check that we're not being destroyed while something is still playing.
-        if (isPlaying()) {
-            Log.e(LOGTAG, "Service being destroyed while still playing.");
-        }
-        // release all MediaPlayer resources, including the native player and wakelocks
-        Intent i = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
-        i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
-        i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
-        sendBroadcast(i);
-        mPlayer.release();
-        mPlayer = null;
-
-        mAudioManager.abandonAudioFocus(mAudioFocusListener);
-        mAudioManager.unregisterRemoteControlClient(mRemoteControlClient);
-
-        // make sure there aren't any other messages coming
-        mDelayedStopHandler.removeCallbacksAndMessages(null);
-        mMediaplayerHandler.removeCallbacksAndMessages(null);
-
-        if (mCursor != null) {
-            mCursor.close();
-            mCursor = null;
-        }
-
-        unregisterReceiver(mIntentReceiver);
-        if (mUnmountReceiver != null) {
-            unregisterReceiver(mUnmountReceiver);
-            mUnmountReceiver = null;
-        }
-        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 (!mQueueIsSaveable) {
-            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++) {
-                long n = mPlayList[i];
-                if (n < 0) {
-                    continue;
-                } else if (n == 0) {
-                    q.append("0;");
-                } else {
-                    while (n != 0) {
-                        int digit = (int) (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);
-            if (mShuffleMode != SHUFFLE_NONE) {
-                // In shuffle mode we need to save the history too
-                len = mHistory.size();
-                q.setLength(0);
-                for (int i = 0; i < len; i++) {
-                    int n = mHistory.get(i);
-                    if (n == 0) {
-                        q.append("0;");
-                    } else {
-                        while (n != 0) {
-                            int digit = (n & 0xf);
-                            n >>>= 4;
-                            q.append(hexdigits[digit]);
-                        }
-                        q.append(";");
-                    }
-                }
-                ed.putString("history", q.toString());
-            }
-        }
-        ed.putInt("curpos", mPlayPos);
-        if (mPlayer.isInitialized()) {
-            ed.putLong("seekpos", mPlayer.position());
-        }
-        ed.putInt("repeatmode", mRepeatMode);
-        ed.putInt("shufflemode", mShuffleMode);
-        SharedPreferencesCompat.apply(ed);
-
-        // 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", "");
-        }
-        int qlen = q != null ? q.length() : 0;
-        if (qlen > 1) {
-            // Log.i("@@@@ service", "loaded queue: " + q);
-            int plen = 0;
-            int n = 0;
-            int shift = 0;
-            for (int i = 0; i < qlen; i++) {
-                char c = q.charAt(i);
-                if (c == ';') {
-                    ensurePlayListCapacity(plen + 1);
-                    mPlayList[plen] = n;
-                    plen++;
-                    n = 0;
-                    shift = 0;
-                } else {
-                    if (c >= '0' && c <= '9') {
-                        n += ((c - '0') << shift);
-                    } else if (c >= 'a' && c <= 'f') {
-                        n += ((10 + c - 'a') << shift);
-                    } else {
-                        // bogus playlist data
-                        plen = 0;
-                        break;
-                    }
-                    shift += 4;
-                }
-            }
-            mPlayListLen = plen;
-
-            int pos = mPreferences.getInt("curpos", 0);
-            if (pos < 0 || pos >= mPlayListLen) {
-                // 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 crsr = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
-                    new String[] {"_id"}, "_id=" + mPlayList[mPlayPos], null, null);
-            if (crsr == null || crsr.getCount() == 0) {
-                // wait a bit and try again
-                SystemClock.sleep(3000);
-                crsr = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
-                        mCursorCols, "_id=" + mPlayList[mPlayPos], null, null);
-            }
-            if (crsr != null) {
-                crsr.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;
-            openCurrentAndNext();
-            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);
-            Log.d(LOGTAG, "restored queue, currently at position " + position() + "/" + duration()
-                            + " (requested " + seekpos + ")");
-
-            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_NONE) {
-                // in shuffle mode we need to restore the history too
-                q = mPreferences.getString("history", "");
-                qlen = q != null ? q.length() : 0;
-                if (qlen > 1) {
-                    plen = 0;
-                    n = 0;
-                    shift = 0;
-                    mHistory.clear();
-                    for (int i = 0; i < qlen; i++) {
-                        char c = q.charAt(i);
-                        if (c == ';') {
-                            if (n >= mPlayListLen) {
-                                // bogus history data
-                                mHistory.clear();
-                                break;
-                            }
-                            mHistory.add(n);
-                            n = 0;
-                            shift = 0;
-                        } else {
-                            if (c >= '0' && c <= '9') {
-                                n += ((c - '0') << shift);
-                            } else if (c >= 'a' && c <= 'f') {
-                                n += ((10 + c - 'a') << shift);
-                            } else {
-                                // bogus history data
-                                mHistory.clear();
-                                break;
-                            }
-                            shift += 4;
-                        }
+    public int onStartCommand(Intent startIntent, int flags, int startId) {
+        if (startIntent != null) {
+            String action = startIntent.getAction();
+            String command = startIntent.getStringExtra(CMD_NAME);
+            if (ACTION_CMD.equals(action)) {
+                if (CMD_PAUSE.equals(command)) {
+                    if (mPlayback != null && mPlayback.isPlaying()) {
+                        handlePauseRequest();
                     }
                 }
             }
-            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 int onStartCommand(Intent intent, int flags, int startId) {
-        mServiceStartId = startId;
-        mDelayedStopHandler.removeCallbacksAndMessages(null);
-
-        if (intent != null) {
-            String action = intent.getAction();
-            String cmd = intent.getStringExtra("command");
-            MusicUtils.debugLog("onStartCommand " + action + " / " + cmd);
-
-            if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
-                gotoNext(true);
-            } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
-                if (position() < 2000) {
-                    prev();
-                } else {
-                    seek(0);
-                    play();
-                }
-            } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
-                if (isPlaying()) {
-                    pause();
-                    mPausedByTransientLossOfFocus = false;
-                } else {
-                    play();
-                }
-            } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
-                pause();
-                mPausedByTransientLossOfFocus = false;
-            } else if (CMDPLAY.equals(cmd)) {
-                play();
-            } else if (CMDSTOP.equals(cmd)) {
-                pause();
-                mPausedByTransientLossOfFocus = false;
-                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);
         return START_STICKY;
     }
 
     @Override
-    public boolean onUnbind(Intent intent) {
-        mServiceInUse = false;
+    public void onDestroy() {
+        Log.d(TAG, "onDestroy");
+        // Service is being killed, so make sure we release our resources
+        handleStopRequest(null);
 
-        // Take a snapshot of the current playlist
-        saveQueue(true);
-
-        if (isPlaying() || mPausedByTransientLossOfFocus) {
-            // something is currently playing, or will be playing once
-            // an in-progress action requesting audio focus 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;
+        mDelayedStopHandler.removeCallbacksAndMessages(null);
+        // Always release the MediaSession to clean up resources
+        // and notify associated MediaController(s).
+        mSession.release();
     }
 
-    private Handler mDelayedStopHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            // Check again to make sure nothing is playing right now
-            if (isPlaying() || mPausedByTransientLossOfFocus || 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);
+    @Override
+    public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
+        Log.d(TAG,
+                "OnGetRoot: clientPackageName=" + clientPackageName + "; clientUid=" + clientUid
+                        + " ; rootHints=" + rootHints);
+        // Allow everyone to browse
+        return new BrowserRoot(MEDIA_ID_ROOT, null);
     }
 
-    /**
-     * 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 onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) {
+        Log.d(TAG, "OnLoadChildren: parentMediaId=" + parentMediaId);
+        //  Browsing not allowed
+        if (parentMediaId == null) {
+            result.sendResult(null);
+            return;
+        }
+        if (!mMusicProvider.isInitialized()) {
+            // Use result.detach to allow calling result.sendResult from another thread:
+            result.detach();
+
+            mMusicProvider.retrieveMediaAsync(new MusicProvider.MusicProviderCallback() {
                 @Override
-                public void onReceive(Context context, Intent intent) {
-                    String action = intent.getAction();
-                    if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
-                        saveQueue(true);
-                        mQueueIsSaveable = false;
-                        closeExternalStorageFiles(intent.getData().getPath());
-                    } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
-                        mMediaMountedCount++;
-                        mCardId = MusicUtils.getCardId(MediaPlaybackService.this);
-                        reloadQueue();
-                        mQueueIsSaveable = true;
-                        notifyChange(QUEUE_CHANGED);
-                        notifyChange(META_CHANGED);
+                public void onMusicCatalogReady(boolean success) {
+                    Log.d(TAG, "Received catalog result, success:  " + String.valueOf(success));
+                    if (success) {
+                        onLoadChildren(parentMediaId, result);
+                    } else {
+                        result.sendResult(Collections.emptyList());
                     }
                 }
-            };
-            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", Long.valueOf(getAudioId()));
-        i.putExtra("artist", getArtistName());
-        i.putExtra("album", getAlbumName());
-        i.putExtra("track", getTrackName());
-        i.putExtra("playing", isPlaying());
-        sendStickyBroadcast(i);
-
-        if (what.equals(PLAYSTATE_CHANGED)) {
-            mRemoteControlClient.setPlaybackState(isPlaying()
-                            ? RemoteControlClient.PLAYSTATE_PLAYING
-                            : RemoteControlClient.PLAYSTATE_PAUSED);
-        } else if (what.equals(META_CHANGED)) {
-            RemoteControlClient.MetadataEditor ed = mRemoteControlClient.editMetadata(true);
-            ed.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, getTrackName());
-            ed.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, getAlbumName());
-            ed.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, getArtistName());
-            ed.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, duration());
-            Bitmap b = MusicUtils.getArtwork(this, getAudioId(), getAlbumId(), false);
-            if (b != null) {
-                ed.putBitmap(MetadataEditor.BITMAP_KEY_ARTWORK, b);
-            }
-            ed.apply();
-        }
-
-        if (what.equals(QUEUE_CHANGED)) {
-            saveQueue(true);
         } else {
-            saveQueue(false);
-        }
+            // If our music catalog is already loaded/cached, load them into result immediately
+            List<MediaItem> mediaItems = new ArrayList<>();
 
-        // Share this notification directly with our widgets
-        mAppWidgetProvider.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
-            long[] newlist = new long[size * 2];
-            int len = mPlayList != null ? mPlayList.length : mPlayListLen;
-            for (int i = 0; i < len; i++) {
-                newlist[i] = mPlayList[i];
+            switch (parentMediaId) {
+                case MEDIA_ID_ROOT:
+                    Log.d(TAG, "OnLoadChildren.ROOT");
+                    mediaItems.add(new MediaItem(new MediaDescription.Builder()
+                                                         .setMediaId(MEDIA_ID_MUSICS_BY_ARTIST)
+                                                         .setTitle("Artists")
+                                                         .build(),
+                            MediaItem.FLAG_BROWSABLE));
+                    mediaItems.add(new MediaItem(new MediaDescription.Builder()
+                                                         .setMediaId(MEDIA_ID_MUSICS_BY_ALBUM)
+                                                         .setTitle("Albums")
+                                                         .build(),
+                            MediaItem.FLAG_BROWSABLE));
+                    mediaItems.add(new MediaItem(new MediaDescription.Builder()
+                                                         .setMediaId(MEDIA_ID_MUSICS_BY_SONG)
+                                                         .setTitle("Songs")
+                                                         .build(),
+                            MediaItem.FLAG_BROWSABLE));
+                    mediaItems.add(new MediaItem(new MediaDescription.Builder()
+                                                         .setMediaId(MEDIA_ID_MUSICS_BY_PLAYLIST)
+                                                         .setTitle("Playlists")
+                                                         .build(),
+                            MediaItem.FLAG_BROWSABLE));
+                    break;
+                case MEDIA_ID_MUSICS_BY_ARTIST:
+                    Log.d(TAG, "OnLoadChildren.ARTIST");
+                    for (String artist : mMusicProvider.getArtists()) {
+                        MediaItem item = new MediaItem(
+                                new MediaDescription.Builder()
+                                        .setMediaId(MediaIDHelper.createBrowseCategoryMediaID(
+                                                MEDIA_ID_MUSICS_BY_ARTIST, artist))
+                                        .setTitle(artist)
+                                        .build(),
+                                MediaItem.FLAG_BROWSABLE);
+                        mediaItems.add(item);
+                    }
+                    break;
+                case MEDIA_ID_MUSICS_BY_PLAYLIST:
+                    LogHelper.d(TAG, "OnLoadChildren.PLAYLIST");
+                    for (String playlist : mMusicProvider.getPlaylists()) {
+                        MediaItem item = new MediaItem(
+                                new MediaDescription.Builder()
+                                        .setMediaId(MediaIDHelper.createBrowseCategoryMediaID(
+                                                MEDIA_ID_MUSICS_BY_PLAYLIST, playlist))
+                                        .setTitle(playlist)
+                                        .build(),
+                                MediaItem.FLAG_BROWSABLE);
+                        mediaItems.add(item);
+                    }
+                    break;
+                case MEDIA_ID_MUSICS_BY_ALBUM:
+                    Log.d(TAG, "OnLoadChildren.ALBUM");
+                    loadAlbum(mMusicProvider.getAlbums(), mediaItems);
+                    break;
+                case MEDIA_ID_MUSICS_BY_SONG:
+                    Log.d(TAG, "OnLoadChildren.SONG");
+                    String hierarchyAwareMediaID = MediaIDHelper.createBrowseCategoryMediaID(
+                            parentMediaId, MEDIA_ID_MUSICS_BY_SONG);
+                    loadSong(mMusicProvider.getMusicList(), mediaItems, hierarchyAwareMediaID);
+                    break;
+                default:
+                    if (parentMediaId.startsWith(MEDIA_ID_MUSICS_BY_ARTIST)) {
+                        String artist = MediaIDHelper.getHierarchy(parentMediaId)[1];
+                        Log.d(TAG, "OnLoadChildren.SONGS_BY_ARTIST  artist=" + artist);
+                        loadAlbum(mMusicProvider.getAlbumByArtist(artist), mediaItems);
+                    } else if (parentMediaId.startsWith(MEDIA_ID_MUSICS_BY_ALBUM)) {
+                        String album = MediaIDHelper.getHierarchy(parentMediaId)[1];
+                        Log.d(TAG, "OnLoadChildren.SONGS_BY_ALBUM  album=" + album);
+                        loadSong(mMusicProvider.getMusicsByAlbum(album), mediaItems, parentMediaId);
+                    } else if (parentMediaId.startsWith(MEDIA_ID_MUSICS_BY_PLAYLIST)) {
+                        String playlist = MediaIDHelper.getHierarchy(parentMediaId)[1];
+                        LogHelper.d(TAG, "OnLoadChildren.SONGS_BY_PLAYLIST playlist=", playlist);
+                        if (playlist.equals(MEDIA_ID_NOW_PLAYING) && mPlayingQueue != null
+                                && mPlayingQueue.size() > 0) {
+                            loadPlayingQueue(mediaItems, parentMediaId);
+                        } else {
+                            loadSong(mMusicProvider.getMusicsByPlaylist(playlist), mediaItems,
+                                    parentMediaId);
+                        }
+                    } else {
+                        Log.w(TAG, "Skipping unmatched parentMediaId: " + parentMediaId);
+                    }
+                    break;
             }
-            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(long[] 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;
-        if (mPlayListLen == 0) {
-            mCursor.close();
-            mCursor = null;
-            notifyChange(META_CHANGED);
+            Log.d(TAG,
+                    "OnLoadChildren sending " + mediaItems.size() + " results for "
+                            + parentMediaId);
+            result.sendResult(mediaItems);
         }
     }
 
-    /**
-     * 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(long[] 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;
-                    openCurrentAndNext();
-                    play();
-                    notifyChange(META_CHANGED);
-                    return;
+    private void loadPlayingQueue(List<MediaItem> mediaItems, String parentId) {
+        for (MediaSession.QueueItem queueItem : mPlayingQueue) {
+            MediaItem mediaItem =
+                    new MediaItem(queueItem.getDescription(), MediaItem.FLAG_PLAYABLE);
+            mediaItems.add(mediaItem);
+        }
+    }
+
+    private void loadSong(
+            Iterable<MediaMetadata> songList, List<MediaItem> mediaItems, String parentId) {
+        for (MediaMetadata metadata : songList) {
+            String hierarchyAwareMediaID =
+                    MediaIDHelper.createMediaID(metadata.getDescription().getMediaId(), parentId);
+            Bundle songExtra = new Bundle();
+            songExtra.putLong(MediaMetadata.METADATA_KEY_DURATION,
+                    metadata.getLong(MediaMetadata.METADATA_KEY_DURATION));
+            String title = metadata.getString(MediaMetadata.METADATA_KEY_TITLE);
+            String artistName = metadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
+            MediaItem item = new MediaItem(new MediaDescription.Builder()
+                                                   .setMediaId(hierarchyAwareMediaID)
+                                                   .setTitle(title)
+                                                   .setSubtitle(artistName)
+                                                   .setExtras(songExtra)
+                                                   .build(),
+                    MediaItem.FLAG_PLAYABLE);
+            mediaItems.add(item);
+        }
+    }
+
+    private void loadAlbum(Iterable<MediaMetadata> albumList, List<MediaItem> mediaItems) {
+        for (MediaMetadata albumMetadata : albumList) {
+            String albumName = albumMetadata.getString(MediaMetadata.METADATA_KEY_ALBUM);
+            String artistName = albumMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
+            Bundle albumExtra = new Bundle();
+            albumExtra.putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS,
+                    albumMetadata.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS));
+            MediaItem item = new MediaItem(
+                    new MediaDescription.Builder()
+                            .setMediaId(MediaIDHelper.createBrowseCategoryMediaID(
+                                    MEDIA_ID_MUSICS_BY_ALBUM, albumName))
+                            .setTitle(albumName)
+                            .setSubtitle(artistName)
+                            .setIconBitmap(
+                                    albumMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART))
+                            .setExtras(albumExtra)
+                            .build(),
+                    MediaItem.FLAG_BROWSABLE);
+            mediaItems.add(item);
+        }
+    }
+
+    private final class MediaSessionCallback extends MediaSession.Callback {
+        @Override
+        public void onPlay() {
+            Log.d(TAG, "play");
+
+            if (mPlayingQueue == null || mPlayingQueue.isEmpty()) {
+                mPlayingQueue = QueueHelper.getRandomQueue(mMusicProvider);
+                mSession.setQueue(mPlayingQueue);
+                mSession.setQueueTitle(getString(R.string.random_queue_title));
+                // start playing from the beginning of the queue
+                mCurrentIndexOnQueue = 0;
+            }
+
+            if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) {
+                handlePlayRequest();
+            }
+        }
+
+        @Override
+        public void onSkipToQueueItem(long queueId) {
+            LogHelper.d(TAG, "OnSkipToQueueItem:", queueId);
+
+            if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) {
+                // set the current index on queue from the music Id:
+                mCurrentIndexOnQueue = QueueHelper.getMusicIndexOnQueue(mPlayingQueue, queueId);
+                // play the music
+                handlePlayRequest();
+            }
+        }
+
+        @Override
+        public void onSeekTo(long position) {
+            Log.d(TAG, "onSeekTo:" + position);
+            mPlayback.seekTo((int) position);
+        }
+
+        @Override
+        public void onPlayFromMediaId(String mediaId, Bundle extras) {
+            LogHelper.d(TAG, "playFromMediaId mediaId:", mediaId, "  extras=", extras);
+
+            // The mediaId used here is not the unique musicId. This one comes from the
+            // MediaBrowser, and is actually a "hierarchy-aware mediaID": a concatenation of
+            // the hierarchy in MediaBrowser and the actual unique musicID. This is necessary
+            // so we can build the correct playing queue, based on where the track was
+            // selected from.
+            mPlayingQueue = QueueHelper.getPlayingQueue(mediaId, mMusicProvider);
+            mSession.setQueue(mPlayingQueue);
+            String queueTitle = getString(R.string.browse_musics_by_genre_subtitle,
+                    MediaIDHelper.extractBrowseCategoryValueFromMediaID(mediaId));
+            mSession.setQueueTitle(queueTitle);
+
+            if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) {
+                // set the current index on queue from the media Id:
+                mCurrentIndexOnQueue = QueueHelper.getMusicIndexOnQueue(mPlayingQueue, mediaId);
+
+                if (mCurrentIndexOnQueue < 0) {
+                    LogHelper.e(TAG, "playFromMediaId: media ID ", mediaId,
+                            " could not be found on queue. Ignoring.");
+                } else {
+                    // play the music
+                    handlePlayRequest();
                 }
             }
-            if (mPlayPos < 0) {
-                mPlayPos = 0;
-                openCurrentAndNext();
-                play();
-                notifyChange(META_CHANGED);
+        }
+
+        @Override
+        public void onPause() {
+            LogHelper.d(TAG, "pause. current state=" + mPlayback.getState());
+            handlePauseRequest();
+        }
+
+        @Override
+        public void onStop() {
+            LogHelper.d(TAG, "stop. current state=" + mPlayback.getState());
+            handleStopRequest(null);
+        }
+
+        @Override
+        public void onSkipToNext() {
+            LogHelper.d(TAG, "skipToNext");
+            mCurrentIndexOnQueue++;
+            if (mPlayingQueue != null && mCurrentIndexOnQueue >= mPlayingQueue.size()) {
+                // This sample's behavior: skipping to next when in last song returns to the
+                // first song.
+                mCurrentIndexOnQueue = 0;
+            }
+            if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
+                handlePlayRequest();
+            } else {
+                LogHelper.e(TAG,
+                        "skipToNext: cannot skip to next. next Index=" + mCurrentIndexOnQueue
+                                + " queue length="
+                                + (mPlayingQueue == null ? "null" : mPlayingQueue.size()));
+                handleStopRequest("Cannot skip");
+            }
+        }
+
+        @Override
+        public void onSkipToPrevious() {
+            LogHelper.d(TAG, "skipToPrevious");
+            mCurrentIndexOnQueue--;
+            if (mPlayingQueue != null && mCurrentIndexOnQueue < 0) {
+                // This sample's behavior: skipping to previous when in first song restarts the
+                // first song.
+                mCurrentIndexOnQueue = 0;
+            }
+            if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
+                handlePlayRequest();
+            } else {
+                LogHelper.e(TAG,
+                        "skipToPrevious: cannot skip to previous. previous Index="
+                                + mCurrentIndexOnQueue + " queue length="
+                                + (mPlayingQueue == null ? "null" : mPlayingQueue.size()));
+                handleStopRequest("Cannot skip");
+            }
+        }
+
+        @Override
+        public void onPlayFromSearch(String query, Bundle extras) {
+            LogHelper.d(TAG, "playFromSearch  query=", query);
+
+            if (TextUtils.isEmpty(query)) {
+                // A generic search like "Play music" sends an empty query
+                // and it's expected that we start playing something. What will be played depends
+                // on the app: favorite playlist, "I'm feeling lucky", most recent, etc.
+                mPlayingQueue = QueueHelper.getRandomQueue(mMusicProvider);
+            } else {
+                mPlayingQueue = QueueHelper.getPlayingQueueFromSearch(query, mMusicProvider);
+            }
+
+            LogHelper.d(TAG, "playFromSearch  playqueue.length=" + mPlayingQueue.size());
+            mSession.setQueue(mPlayingQueue);
+
+            if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) {
+                // immediately start playing from the beginning of the search results
+                mCurrentIndexOnQueue = 0;
+
+                handlePlayRequest();
+            } else {
+                // if nothing was found, we need to warn the user and stop playing
+                handleStopRequest(getString(R.string.no_search_results));
+            }
+        }
+
+        @Override
+        public void onCustomAction(String action, Bundle extras) {
+            LogHelper.d(TAG, "onCustomAction action=", action, ", extras=", extras);
+            switch (action) {
+                case CMD_REPEAT:
+                    mRepeatMode = RepeatMode.values()[extras.getInt(REPEAT_MODE)];
+                    mExtras.putInt(REPEAT_MODE, mRepeatMode.ordinal());
+                    mSession.setExtras(mExtras);
+                    LogHelper.d(TAG, "modified repeatMode=", mRepeatMode);
+                    break;
+                default:
+                    LogHelper.d(TAG, "Unkown action=", action);
+                    break;
             }
         }
     }
 
     /**
-     * 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.
+     * Handle a request to play music
      */
-    public void open(long[] list, int position) {
-        synchronized (this) {
-            if (mShuffleMode == SHUFFLE_AUTO) {
-                mShuffleMode = SHUFFLE_NORMAL;
-            }
-            long oldId = getAudioId();
-            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;
+    private void handlePlayRequest() {
+        LogHelper.d(TAG, "handlePlayRequest: mState=" + mPlayback.getState());
+
+        mDelayedStopHandler.removeCallbacksAndMessages(null);
+        if (!mServiceStarted) {
+            LogHelper.v(TAG, "Starting service");
+            // The MusicService needs to keep running even after the calling MediaBrowser
+            // is disconnected. Call startService(Intent) and then stopSelf(..) when we no longer
+            // need to play media.
+            startService(new Intent(getApplicationContext(), MediaPlaybackService.class));
+            mServiceStarted = true;
+        }
+
+        if (!mSession.isActive()) {
+            mSession.setActive(true);
+        }
+
+        if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
+            updateMetadata();
+            mPlayback.play(mPlayingQueue.get(mCurrentIndexOnQueue));
+        }
+    }
+
+    /**
+     * Handle a request to pause music
+     */
+    private void handlePauseRequest() {
+        LogHelper.d(TAG, "handlePauseRequest: mState=" + mPlayback.getState());
+        mPlayback.pause();
+        // reset the delayed stop handler.
+        mDelayedStopHandler.removeCallbacksAndMessages(null);
+        mDelayedStopHandler.sendEmptyMessageDelayed(0, STOP_DELAY);
+    }
+
+    /**
+     * Handle a request to stop music
+     */
+    private void handleStopRequest(String withError) {
+        LogHelper.d(
+                TAG, "handleStopRequest: mState=" + mPlayback.getState() + " error=", withError);
+        mPlayback.stop(true);
+        // reset the delayed stop handler.
+        mDelayedStopHandler.removeCallbacksAndMessages(null);
+        mDelayedStopHandler.sendEmptyMessageDelayed(0, STOP_DELAY);
+
+        updatePlaybackState(withError);
+
+        // service is no longer necessary. Will be started again if needed.
+        stopSelf();
+        mServiceStarted = false;
+    }
+
+    private void updateMetadata() {
+        if (!QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
+            LogHelper.e(TAG, "Can't retrieve current metadata.");
+            updatePlaybackState(getResources().getString(R.string.error_no_metadata));
+            return;
+        }
+        MediaSession.QueueItem queueItem = mPlayingQueue.get(mCurrentIndexOnQueue);
+        String musicId =
+                MediaIDHelper.extractMusicIDFromMediaID(queueItem.getDescription().getMediaId());
+        MediaMetadata track = mMusicProvider.getMusicByMediaId(musicId).getMetadata();
+        final String trackId = track.getString(MediaMetadata.METADATA_KEY_MEDIA_ID);
+        if (!musicId.equals(trackId)) {
+            IllegalStateException e = new IllegalStateException("track ID should match musicId.");
+            LogHelper.e(TAG, "track ID should match musicId.", " musicId=", musicId,
+                    " trackId=", trackId,
+                    " mediaId from queueItem=", queueItem.getDescription().getMediaId(),
+                    " title from queueItem=", queueItem.getDescription().getTitle(),
+                    " mediaId from track=", track.getDescription().getMediaId(),
+                    " title from track=", track.getDescription().getTitle(),
+                    " source.hashcode from track=",
+                    track.getString(MusicProvider.CUSTOM_METADATA_TRACK_SOURCE).hashCode(), e);
+            throw e;
+        }
+        LogHelper.d(TAG, "Updating metadata for MusicID= " + musicId);
+        mSession.setMetadata(track);
+
+        // Set the proper album artwork on the media session, so it can be shown in the
+        // locked screen and in other places.
+        if (track.getDescription().getIconBitmap() == null
+                && track.getDescription().getIconUri() != null) {
+            String albumUri = track.getDescription().getIconUri().toString();
+            AlbumArtCache.getInstance().fetch(albumUri, new AlbumArtCache.FetchListener() {
+                @Override
+                public void onFetched(String artUrl, Bitmap bitmap, Bitmap icon) {
+                    MediaSession.QueueItem queueItem = mPlayingQueue.get(mCurrentIndexOnQueue);
+                    MediaMetadata track = mMusicProvider.getMusicByMediaId(trackId).getMetadata();
+                    track = new MediaMetadata
+                                    .Builder(track)
+
+                                    // set high resolution bitmap in METADATA_KEY_ALBUM_ART. This is
+                                    // used, for
+                                    // example, on the lockscreen background when the media session
+                                    // is active.
+                                    .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, bitmap)
+
+                                    // set small version of the album art in the DISPLAY_ICON. This
+                                    // is used on
+                                    // the MediaDescription and thus it should be small to be
+                                    // serialized if
+                                    // necessary..
+                                    .putBitmap(MediaMetadata.METADATA_KEY_DISPLAY_ICON, icon)
+
+                                    .build();
+
+                    mMusicProvider.updateMusic(trackId, track);
+
+                    // If we are still playing the same music
+                    String currentPlayingId = MediaIDHelper.extractMusicIDFromMediaID(
+                            queueItem.getDescription().getMediaId());
+                    if (trackId.equals(currentPlayingId)) {
+                        mSession.setMetadata(track);
                     }
                 }
-            }
-            if (newlist) {
-                addToPlayList(list, -1);
-                notifyChange(QUEUE_CHANGED);
-            }
-            int oldpos = mPlayPos;
-            if (position >= 0) {
-                mPlayPos = position;
-            } else {
-                mPlayPos = mRand.nextInt(mPlayListLen);
-            }
-            mHistory.clear();
-
-            saveBookmarkIfNeeded();
-            openCurrentAndNext();
-            if (oldId != getAudioId()) {
-                notifyChange(META_CHANGED);
-            }
+            });
         }
     }
 
     /**
-     * Moves the item at index1 to index2.
-     * @param index1
-     * @param index2
+     * Update the current media player state, optionally showing an error message.
+     *
+     * @param error if not null, error message to present to the user.
      */
-    public void moveQueueItem(int index1, int index2) {
-        synchronized (this) {
-            if (index1 >= mPlayListLen) {
-                index1 = mPlayListLen - 1;
-            }
-            if (index2 >= mPlayListLen) {
-                index2 = mPlayListLen - 1;
-            }
-            if (index1 < index2) {
-                long 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) {
-                long 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);
+    private void updatePlaybackState(String error) {
+        LogHelper.d(TAG, "updatePlaybackState, playback state=" + mPlayback.getState());
+        long position = PlaybackState.PLAYBACK_POSITION_UNKNOWN;
+        if (mPlayback != null && mPlayback.isConnected()) {
+            position = mPlayback.getCurrentStreamPosition();
         }
+
+        PlaybackState.Builder stateBuilder =
+                new PlaybackState.Builder().setActions(getAvailableActions());
+
+        int state = mPlayback.getState();
+
+        // If there is an error message, send it to the playback state:
+        if (error != null) {
+            // Error states are really only supposed to be used for errors that cause playback to
+            // stop unexpectedly and persist until the user takes action to fix it.
+            stateBuilder.setErrorMessage(error);
+            state = PlaybackState.STATE_ERROR;
+        }
+        stateBuilder.setState(state, position, 1.0f, SystemClock.elapsedRealtime());
+
+        // Set the activeQueueItemId if the current index is valid.
+        if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
+            MediaSession.QueueItem item = mPlayingQueue.get(mCurrentIndexOnQueue);
+            stateBuilder.setActiveQueueItemId(item.getQueueId());
+        }
+
+        mSession.setPlaybackState(stateBuilder.build());
+
+        if (state == PlaybackState.STATE_PLAYING || state == PlaybackState.STATE_PAUSED) {
+            mMediaNotificationManager.startNotification();
+        }
+    }
+
+    private long getAvailableActions() {
+        long actions = PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PLAY_FROM_MEDIA_ID
+                | PlaybackState.ACTION_PLAY_FROM_SEARCH;
+        if (mPlayingQueue == null || mPlayingQueue.isEmpty()) {
+            return actions;
+        }
+        if (mPlayback.isPlaying()) {
+            actions |= PlaybackState.ACTION_PAUSE;
+        }
+        if (mCurrentIndexOnQueue > 0) {
+            actions |= PlaybackState.ACTION_SKIP_TO_PREVIOUS;
+        }
+        if (mCurrentIndexOnQueue < mPlayingQueue.size() - 1) {
+            actions |= PlaybackState.ACTION_SKIP_TO_NEXT;
+        }
+        return actions;
+    }
+
+    private MediaMetadata getCurrentPlayingMusic() {
+        if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
+            MediaSession.QueueItem item = mPlayingQueue.get(mCurrentIndexOnQueue);
+            if (item != null) {
+                LogHelper.d(TAG,
+                        "getCurrentPlayingMusic for musicId=", item.getDescription().getMediaId());
+                return mMusicProvider
+                        .getMusicByMediaId(MediaIDHelper.extractMusicIDFromMediaID(
+                                item.getDescription().getMediaId()))
+                        .getMetadata();
+            }
+        }
+        return null;
     }
 
     /**
-     * Returns the current play list
-     * @return An array of integers containing the IDs of the tracks in the play list
+     * Implementation of the Playback.Callback interface
      */
-    public long[] getQueue() {
-        synchronized (this) {
-            int len = mPlayListLen;
-            long[] list = new long[len];
-            for (int i = 0; i < len; i++) {
-                list[i] = mPlayList[i];
-            }
-            return list;
-        }
-    }
-
-    private Cursor getCursorForId(long lid) {
-        String id = String.valueOf(lid);
-
-        Cursor c = getContentResolver().query(
-                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursorCols, "_id=" + id, null, null);
-        if (c != null) {
-            c.moveToFirst();
-        }
-        return c;
-    }
-
-    private void openCurrentAndNext() {
-        synchronized (this) {
-            if (mCursor != null) {
-                mCursor.close();
-                mCursor = null;
-            }
-
-            if (mPlayListLen == 0) {
-                return;
-            }
-            stop(false);
-
-            mCursor = getCursorForId(mPlayList[mPlayPos]);
-            while (true) {
-                if (mCursor != null && mCursor.getCount() != 0
-                        && open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/"
-                                   + mCursor.getLong(IDCOLIDX))) {
+    @Override
+    public void onCompletion() {
+        // The media player finished playing the current song, so we go ahead
+        // and start the next.
+        if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) {
+            switch (mRepeatMode) {
+                case REPEAT_ALL:
+                    // Increase the index
+                    mCurrentIndexOnQueue++;
+                    // Restart queue when reaching the end
+                    if (mCurrentIndexOnQueue >= mPlayingQueue.size()) {
+                        mCurrentIndexOnQueue = 0;
+                    }
                     break;
-                }
-                // if we get here then opening the file failed. We can close the cursor now, because
-                // we're either going to create a new one next, or stop trying
-                if (mCursor != null) {
-                    mCursor.close();
-                    mCursor = null;
-                }
-                if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) {
-                    int pos = getNextPosition(false);
-                    if (pos < 0) {
-                        gotoIdleState();
-                        if (mIsSupposedToBePlaying) {
-                            mIsSupposedToBePlaying = false;
-                            notifyChange(PLAYSTATE_CHANGED);
-                        }
+                case REPEAT_CURRENT:
+                    // Do not change the index
+                    break;
+                case REPEAT_NONE:
+                default:
+                    // Increase the index
+                    mCurrentIndexOnQueue++;
+                    // Stop the queue when reaching the end
+                    if (mCurrentIndexOnQueue >= mPlayingQueue.size()) {
+                        handleStopRequest(null);
                         return;
                     }
-                    mPlayPos = pos;
-                    stop(false);
-                    mPlayPos = pos;
-                    mCursor = getCursorForId(mPlayList[mPlayPos]);
-                } else {
-                    mOpenFailedCounter = 0;
-                    if (!mQuietMode) {
-                        Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
-                    }
-                    Log.d(LOGTAG, "Failed to open file for playback");
-                    gotoIdleState();
-                    if (mIsSupposedToBePlaying) {
-                        mIsSupposedToBePlaying = false;
-                        notifyChange(PLAYSTATE_CHANGED);
-                    }
-                    return;
-                }
-            }
-
-            // 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);
-            }
-            setNextTrack();
-        }
-    }
-
-    private void setNextTrack() {
-        mNextPlayPos = getNextPosition(false);
-        if (mNextPlayPos >= 0) {
-            long id = mPlayList[mNextPlayPos];
-            mPlayer.setNextDataSource(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id);
-        } else {
-            mPlayer.setNextDataSource(null);
-        }
-    }
-
-    /**
-     * Opens the specified file and readies it for playback.
-     *
-     * @param path The full path of the file to be opened.
-     */
-    public boolean open(String path) {
-        synchronized (this) {
-            if (path == null) {
-                return false;
-            }
-
-            // 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.getLong(IDCOLIDX);
-                            mPlayPos = 0;
-                        }
-                    }
-                } catch (UnsupportedOperationException ex) {
-                }
-            }
-            mFileToPlay = path;
-            mPlayer.setDataSource(mFileToPlay);
-            if (mPlayer.isInitialized()) {
-                mOpenFailedCounter = 0;
-                return true;
-            }
-            stop(true);
-            return false;
-        }
-    }
-
-    /**
-     * Starts playback of a previously opened file.
-     */
-    public void play() {
-        mAudioManager.requestAudioFocus(
-                mAudioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
-        mAudioManager.registerMediaButtonEventReceiver(new ComponentName(
-                this.getPackageName(), MediaButtonIntentReceiver.class.getName()));
-
-        if (mPlayer.isInitialized()) {
-            // if we are at the end of the song, go to the next song first
-            long duration = mPlayer.duration();
-            if (mRepeatMode != REPEAT_CURRENT && duration > 2000
-                    && mPlayer.position() >= duration - 2000) {
-                gotoNext(true);
-            }
-
-            mPlayer.start();
-            // make sure we fade in, in case a previous fadein was stopped because
-            // of another focus loss
-            mMediaplayerHandler.removeMessages(FADEDOWN);
-            mMediaplayerHandler.sendEmptyMessage(FADEUP);
-
-            updateNotification();
-            if (!mIsSupposedToBePlaying) {
-                mIsSupposedToBePlaying = true;
-                notifyChange(PLAYSTATE_CHANGED);
-            }
-
-        } 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 updateNotification() {
-        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(MediaStore.UNKNOWN_STRING)) {
-                artist = getString(R.string.unknown_artist_name);
-            }
-            String album = getAlbumName();
-            if (album == null || album.equals(MediaStore.UNKNOWN_STRING)) {
-                album = getString(R.string.unknown_album_name);
-            }
-
-            views.setTextViewText(
-                    R.id.artistalbum, getString(R.string.notification_artist_album, artist, album));
-        }
-        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")
-                                                           .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
-                        0);
-        startForeground(PLAYBACKSERVICE_STATUS, status);
-    }
-
-    private void stop(boolean remove_status_icon) {
-        if (mPlayer != null && mPlayer.isInitialized()) {
-            mPlayer.stop();
-        }
-        mFileToPlay = null;
-        if (mCursor != null) {
-            mCursor.close();
-            mCursor = null;
-        }
-        if (remove_status_icon) {
-            gotoIdleState();
-        } else {
-            stopForeground(false);
-        }
-        if (remove_status_icon) {
-            mIsSupposedToBePlaying = false;
-        }
-    }
-
-    /**
-     * Stops playback.
-     */
-    public void stop() {
-        stop(true);
-    }
-
-    /**
-     * Pauses playback (call play() to resume)
-     */
-    public void pause() {
-        synchronized (this) {
-            mMediaplayerHandler.removeMessages(FADEUP);
-            if (isPlaying()) {
-                mPlayer.pause();
-                gotoIdleState();
-                mIsSupposedToBePlaying = false;
-                notifyChange(PLAYSTATE_CHANGED);
-                saveBookmarkIfNeeded();
-            }
-        }
-    }
-
-    /** Returns whether something is currently playing
-     *
-     * @return true if something is playing (or will be playing shortly, in case
-     * we're currently transitioning between tracks), false if not.
-     */
-    public boolean isPlaying() {
-        return mIsSupposedToBePlaying;
-    }
-
-    /*
-      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 (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);
-            openCurrentAndNext();
-            play();
-            notifyChange(META_CHANGED);
-        }
-    }
-
-    /**
-     * Get the next position to play. Note that this may actually modify mPlayPos
-     * if playback is in SHUFFLE_AUTO mode and the shuffle list window needed to
-     * be adjusted. Either way, the return value is the next value that should be
-     * assigned to mPlayPos;
-     */
-    private int getNextPosition(boolean force) {
-        if (mRepeatMode == REPEAT_CURRENT) {
-            if (mPlayPos < 0) return 0;
-            return mPlayPos;
-        } else 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.
-
-            // Store the current file in the history, but keep the history at a
-            // reasonable size
-            if (mPlayPos >= 0) {
-                mHistory.add(mPlayPos);
-            }
-            if (mHistory.size() > MAX_HISTORY_SIZE) {
-                mHistory.removeElementAt(0);
-            }
-
-            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
-                    return -1;
-                }
-            }
-            int skip = mRand.nextInt(numUnplayed);
-            int cnt = -1;
-            while (true) {
-                while (tracks[++cnt] < 0)
-                    ;
-                skip--;
-                if (skip < 0) {
                     break;
-                }
             }
-            return cnt;
-        } else if (mShuffleMode == SHUFFLE_AUTO) {
-            doAutoShuffleUpdate();
-            return mPlayPos + 1;
+            handlePlayRequest();
         } else {
-            if (mPlayPos >= mPlayListLen - 1) {
-                // we're at the end of the list
-                if (mRepeatMode == REPEAT_NONE && !force) {
-                    // all done
-                    return -1;
-                } else if (mRepeatMode == REPEAT_ALL || force) {
-                    return 0;
-                }
-                return -1;
-            } else {
-                return mPlayPos + 1;
-            }
-        }
-    }
-
-    public void gotoNext(boolean force) {
-        synchronized (this) {
-            if (mPlayListLen <= 0) {
-                Log.d(LOGTAG, "No play queue");
-                return;
-            }
-
-            int pos = getNextPosition(force);
-            if (pos < 0) {
-                gotoIdleState();
-                if (mIsSupposedToBePlaying) {
-                    mIsSupposedToBePlaying = false;
-                    notifyChange(PLAYSTATE_CHANGED);
-                }
-                return;
-            }
-            mPlayPos = pos;
-            saveBookmarkIfNeeded();
-            stop(false);
-            mPlayPos = pos;
-            openCurrentAndNext();
-            play();
-            notifyChange(META_CHANGED);
-        }
-    }
-
-    private void gotoIdleState() {
-        mDelayedStopHandler.removeCallbacksAndMessages(null);
-        Message msg = mDelayedStopHandler.obtainMessage();
-        mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
-        stopForeground(true);
-    }
-
-    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 lookback = mHistory.size();
-            int idx = -1;
-            while (true) {
-                idx = mRand.nextInt(mAutoShuffleList.length);
-                if (!wasRecentlyUsed(idx, lookback)) {
-                    break;
-                }
-                lookback /= 2;
-            }
-            mHistory.add(idx);
-            if (mHistory.size() > MAX_HISTORY_SIZE) {
-                mHistory.remove(0);
-            }
-            ensurePlayListCapacity(mPlayListLen + 1);
-            mPlayList[mPlayListLen++] = mAutoShuffleList[idx];
-            notify = true;
-        }
-        if (notify) {
-            notifyChange(QUEUE_CHANGED);
-        }
-    }
-
-    // check that the specified idx is not in the history (but only look at at
-    // most lookbacksize entries in the history)
-    private boolean wasRecentlyUsed(int idx, int lookbacksize) {
-        // early exit to prevent infinite loops in case idx == mPlayPos
-        if (lookbacksize == 0) {
-            return false;
-        }
-
-        int histsize = mHistory.size();
-        if (histsize < lookbacksize) {
-            Log.d(LOGTAG, "lookback too big");
-            lookbacksize = histsize;
-        }
-        int maxidx = histsize - 1;
-        for (int i = 0; i < lookbacksize; i++) {
-            long entry = mHistory.get(maxidx - i);
-            if (entry == idx) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    // 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 static 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();
-            long[] list = new long[len];
-            for (int i = 0; i < len; i++) {
-                c.moveToNext();
-                list[i] = c.getLong(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;
-                    if (mCursor != null) {
-                        mCursor.close();
-                        mCursor = null;
-                    }
-                } else {
-                    if (mPlayPos >= mPlayListLen) {
-                        mPlayPos = 0;
-                    }
-                    boolean wasPlaying = isPlaying();
-                    stop(false);
-                    openCurrentAndNext();
-                    if (wasPlaying) {
-                        play();
-                    }
-                }
-                notifyChange(META_CHANGED);
-            }
-            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(long 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;
-                    openCurrentAndNext();
-                    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;
-            setNextTrack();
-            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 long 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;
-            openCurrentAndNext();
-            play();
-            notifyChange(META_CHANGED);
-            if (mShuffleMode == SHUFFLE_AUTO) {
-                doAutoShuffleUpdate();
-            }
-        }
-    }
-
-    public String getArtistName() {
-        synchronized (this) {
-            if (mCursor == null) {
-                return null;
-            }
-            return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
-        }
-    }
-
-    public long getArtistId() {
-        synchronized (this) {
-            if (mCursor == null) {
-                return -1;
-            }
-            return mCursor.getLong(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 long getAlbumId() {
-        synchronized (this) {
-            if (mCursor == null) {
-                return -1;
-            }
-            return mCursor.getLong(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;
-    }
-
-    /**
-     * Sets the audio session ID.
-     *
-     * @param sessionId: the audio session ID.
-     */
-    public void setAudioSessionId(int sessionId) {
-        synchronized (this) {
-            mPlayer.setAudioSessionId(sessionId);
-        }
-    }
-
-    /**
-     * Returns the audio session ID.
-     */
-    public int getAudioSessionId() {
-        synchronized (this) {
-            return mPlayer.getAudioSessionId();
-        }
-    }
-
-    /**
-     * Provides a unified interface for dealing with midi files and
-     * other media files.
-     */
-    private class MultiPlayer {
-        private CompatMediaPlayer mCurrentMediaPlayer = new CompatMediaPlayer();
-        private CompatMediaPlayer mNextMediaPlayer;
-        private Handler mHandler;
-        private boolean mIsInitialized = false;
-
-        public MultiPlayer() {
-            mCurrentMediaPlayer.setWakeMode(
-                    MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
-        }
-
-        public void setDataSource(String path) {
-            mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path);
-            if (mIsInitialized) {
-                setNextDataSource(null);
-            }
-        }
-
-        private boolean setDataSourceImpl(MediaPlayer player, String path) {
-            try {
-                player.reset();
-                player.setOnPreparedListener(null);
-                if (path.startsWith("content://")) {
-                    player.setDataSource(MediaPlaybackService.this, Uri.parse(path));
-                } else {
-                    player.setDataSource(path);
-                }
-                player.setAudioStreamType(AudioManager.STREAM_MUSIC);
-                player.prepare();
-            } catch (IOException ex) {
-                // TODO: notify the user why the file couldn't be opened
-                return false;
-            } catch (IllegalArgumentException ex) {
-                // TODO: notify the user why the file couldn't be opened
-                return false;
-            }
-            player.setOnCompletionListener(listener);
-            player.setOnErrorListener(errorListener);
-            Intent i = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
-            i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
-            i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
-            sendBroadcast(i);
-            return true;
-        }
-
-        public void setNextDataSource(String path) {
-            mCurrentMediaPlayer.setNextMediaPlayer(null);
-            if (mNextMediaPlayer != null) {
-                mNextMediaPlayer.release();
-                mNextMediaPlayer = null;
-            }
-            if (path == null) {
-                return;
-            }
-            mNextMediaPlayer = new CompatMediaPlayer();
-            mNextMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
-            mNextMediaPlayer.setAudioSessionId(getAudioSessionId());
-            if (setDataSourceImpl(mNextMediaPlayer, path)) {
-                mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer);
-            } else {
-                // failed to open next, we'll transition the old fashioned way,
-                // which will skip over the faulty file
-                mNextMediaPlayer.release();
-                mNextMediaPlayer = null;
-            }
-        }
-
-        public boolean isInitialized() {
-            return mIsInitialized;
-        }
-
-        public void start() {
-            MusicUtils.debugLog(new Exception("MultiPlayer.start called"));
-            mCurrentMediaPlayer.start();
-        }
-
-        public void stop() {
-            mCurrentMediaPlayer.reset();
-            mIsInitialized = false;
-        }
-
-        /**
-         * You CANNOT use this player anymore after calling release()
-         */
-        public void release() {
-            stop();
-            mCurrentMediaPlayer.release();
-        }
-
-        public void pause() {
-            mCurrentMediaPlayer.pause();
-        }
-
-        public void setHandler(Handler handler) {
-            mHandler = handler;
-        }
-
-        MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
-            public void onCompletion(MediaPlayer mp) {
-                if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) {
-                    mCurrentMediaPlayer.release();
-                    mCurrentMediaPlayer = mNextMediaPlayer;
-                    mNextMediaPlayer = null;
-                    mHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT);
-                } else {
-                    // 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.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
-            public boolean onError(MediaPlayer mp, int what, int extra) {
-                switch (what) {
-                    case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
-                        mIsInitialized = false;
-                        mCurrentMediaPlayer.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
-                        mCurrentMediaPlayer = new CompatMediaPlayer();
-                        mCurrentMediaPlayer.setWakeMode(
-                                MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
-                        mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
-                        return true;
-                    default:
-                        Log.d("MultiPlayer", "Error: " + what + "," + extra);
-                        break;
-                }
-                return false;
-            }
-        };
-
-        public long duration() {
-            return mCurrentMediaPlayer.getDuration();
-        }
-
-        public long position() {
-            return mCurrentMediaPlayer.getCurrentPosition();
-        }
-
-        public long seek(long whereto) {
-            mCurrentMediaPlayer.seekTo((int) whereto);
-            return whereto;
-        }
-
-        public void setVolume(float vol) {
-            mCurrentMediaPlayer.setVolume(vol, vol);
-        }
-
-        public void setAudioSessionId(int sessionId) {
-            mCurrentMediaPlayer.setAudioSessionId(sessionId);
-        }
-
-        public int getAudioSessionId() {
-            return mCurrentMediaPlayer.getAudioSessionId();
-        }
-    }
-
-    static class CompatMediaPlayer extends MediaPlayer implements OnCompletionListener {
-        private boolean mCompatMode = true;
-        private MediaPlayer mNextPlayer;
-        private OnCompletionListener mCompletion;
-
-        public CompatMediaPlayer() {
-            try {
-                MediaPlayer.class.getMethod("setNextMediaPlayer", MediaPlayer.class);
-                mCompatMode = false;
-            } catch (NoSuchMethodException e) {
-                mCompatMode = true;
-                super.setOnCompletionListener(this);
-            }
-        }
-
-        public void setNextMediaPlayer(MediaPlayer next) {
-            if (mCompatMode) {
-                mNextPlayer = next;
-            } else {
-                super.setNextMediaPlayer(next);
-            }
-        }
-
-        @Override
-        public void setOnCompletionListener(OnCompletionListener listener) {
-            if (mCompatMode) {
-                mCompletion = listener;
-            } else {
-                super.setOnCompletionListener(listener);
-            }
-        }
-
-        @Override
-        public void onCompletion(MediaPlayer mp) {
-            if (mNextPlayer != null) {
-                // as it turns out, starting a new MediaPlayer on the completion
-                // of a previous player ends up slightly overlapping the two
-                // playbacks, so slightly delaying the start of the next player
-                // gives a better user experience
-                SystemClock.sleep(50);
-                mNextPlayer.start();
-            }
-            mCompletion.onCompletion(this);
-        }
-    }
-
-    /*
-     * By making this a static class with a WeakReference to the Service, we
-     * ensure that the Service can be GCd even when the system process still
-     * has a remote reference to the stub.
-     */
-    static class ServiceStub extends IMediaPlaybackService.Stub {
-        WeakReference<MediaPlaybackService> mService;
-
-        ServiceStub(MediaPlaybackService service) {
-            mService = new WeakReference<MediaPlaybackService>(service);
-        }
-
-        public void openFile(String path) {
-            mService.get().open(path);
-        }
-        public void open(long[] list, int position) {
-            mService.get().open(list, position);
-        }
-        public int getQueuePosition() {
-            return mService.get().getQueuePosition();
-        }
-        public void setQueuePosition(int index) {
-            mService.get().setQueuePosition(index);
-        }
-        public boolean isPlaying() {
-            return mService.get().isPlaying();
-        }
-        public void stop() {
-            mService.get().stop();
-        }
-        public void pause() {
-            mService.get().pause();
-        }
-        public void play() {
-            mService.get().play();
-        }
-        public void prev() {
-            mService.get().prev();
-        }
-        public void next() {
-            mService.get().gotoNext(true);
-        }
-        public String getTrackName() {
-            return mService.get().getTrackName();
-        }
-        public String getAlbumName() {
-            return mService.get().getAlbumName();
-        }
-        public long getAlbumId() {
-            return mService.get().getAlbumId();
-        }
-        public String getArtistName() {
-            return mService.get().getArtistName();
-        }
-        public long getArtistId() {
-            return mService.get().getArtistId();
-        }
-        public void enqueue(long[] list, int action) {
-            mService.get().enqueue(list, action);
-        }
-        public long[] getQueue() {
-            return mService.get().getQueue();
-        }
-        public void moveQueueItem(int from, int to) {
-            mService.get().moveQueueItem(from, to);
-        }
-        public String getPath() {
-            return mService.get().getPath();
-        }
-        public long getAudioId() {
-            return mService.get().getAudioId();
-        }
-        public long position() {
-            return mService.get().position();
-        }
-        public long duration() {
-            return mService.get().duration();
-        }
-        public long seek(long pos) {
-            return mService.get().seek(pos);
-        }
-        public void setShuffleMode(int shufflemode) {
-            mService.get().setShuffleMode(shufflemode);
-        }
-        public int getShuffleMode() {
-            return mService.get().getShuffleMode();
-        }
-        public int removeTracks(int first, int last) {
-            return mService.get().removeTracks(first, last);
-        }
-        public int removeTrack(long id) {
-            return mService.get().removeTrack(id);
-        }
-        public void setRepeatMode(int repeatmode) {
-            mService.get().setRepeatMode(repeatmode);
-        }
-        public int getRepeatMode() {
-            return mService.get().getRepeatMode();
-        }
-        public int getMediaMountedCount() {
-            return mService.get().getMediaMountedCount();
-        }
-        public int getAudioSessionId() {
-            return mService.get().getAudioSessionId();
+            // If there is nothing to play, we stop and release the resources:
+            handleStopRequest(null);
         }
     }
 
     @Override
-    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
-        writer.println("" + mPlayListLen + " items in queue, currently at index " + mPlayPos);
-        writer.println("Currently loaded:");
-        writer.println(getArtistName());
-        writer.println(getAlbumName());
-        writer.println(getTrackName());
-        writer.println(getPath());
-        writer.println("playing: " + mIsSupposedToBePlaying);
-        writer.println("actual: " + mPlayer.mCurrentMediaPlayer.isPlaying());
-        writer.println("shuffle mode: " + mShuffleMode);
-        MusicUtils.debugDump(writer);
+    public void onPlaybackStatusChanged(int state) {
+        updatePlaybackState(null);
     }
 
-    private final IBinder mBinder = new ServiceStub(this);
+    @Override
+    public void onError(String error) {
+        updatePlaybackState(error);
+    }
+
+    /**
+     * A simple handler that stops the service if playback is not active (playing)
+     */
+    private static class DelayedStopHandler extends Handler {
+        private final WeakReference<MediaPlaybackService> mWeakReference;
+
+        private DelayedStopHandler(MediaPlaybackService service) {
+            mWeakReference = new WeakReference<>(service);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            MediaPlaybackService service = mWeakReference.get();
+            if (service != null && service.mPlayback != null) {
+                if (service.mPlayback.isPlaying()) {
+                    Log.d(TAG, "Ignoring delayed stop since the media player is in use.");
+                    return;
+                }
+                Log.d(TAG, "Stopping service with delay handler.");
+                service.stopSelf();
+                service.mServiceStarted = false;
+            }
+        }
+    }
 }
diff --git a/src/com/android/music/MusicBrowserActivity.java b/src/com/android/music/MusicBrowserActivity.java
index 426b91c..b6b30e8 100644
--- a/src/com/android/music/MusicBrowserActivity.java
+++ b/src/com/android/music/MusicBrowserActivity.java
@@ -17,18 +17,14 @@
 package com.android.music;
 
 import android.Manifest.permission;
-import android.content.pm.PackageManager;
-import com.android.music.MusicUtils.ServiceToken;
-
 import android.app.Activity;
-import android.content.ComponentName;
-import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
 import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
+import com.android.music.utils.LogHelper;
 
-public class MusicBrowserActivity extends Activity implements MusicUtils.Defs {
-    private ServiceToken mToken;
+public class MusicBrowserActivity extends Activity {
+    private static final String TAG = LogHelper.makeLogTag(MusicBrowserActivity.class);
+
     private static final int MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 42;
 
     public MusicBrowserActivity() {}
@@ -39,6 +35,7 @@
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
+        LogHelper.d(TAG, "onCreate()");
         if (checkSelfPermission(permission.READ_EXTERNAL_STORAGE)
                 != PackageManager.PERMISSION_GRANTED) {
             requestPermissions(new String[] {permission.READ_EXTERNAL_STORAGE},
@@ -50,45 +47,20 @@
 
     public void initApp() {
         int activeTab = MusicUtils.getIntPref(this, "activetab", R.id.artisttab);
+        LogHelper.d(TAG, "initApp() activeTab = ", activeTab);
         if (activeTab != R.id.artisttab && activeTab != R.id.albumtab && activeTab != R.id.songtab
                 && activeTab != R.id.playlisttab) {
             activeTab = R.id.artisttab;
         }
         MusicUtils.activateTab(this, activeTab);
-
-        String shuf = getIntent().getStringExtra("autoshuffle");
-        if ("true".equals(shuf)) {
-            mToken = MusicUtils.bindToService(this, autoshuffle);
-        }
     }
 
     @Override
     public void onDestroy() {
-        if (mToken != null) {
-            MusicUtils.unbindFromService(mToken);
-        }
+        LogHelper.d(TAG, "onDestroy()");
         super.onDestroy();
     }
 
-    private ServiceConnection autoshuffle = new ServiceConnection() {
-        public void onServiceConnected(ComponentName classname, IBinder obj) {
-            // we need to be able to bind again, so unbind
-            try {
-                unbindService(this);
-            } catch (IllegalArgumentException e) {
-            }
-            IMediaPlaybackService serv = IMediaPlaybackService.Stub.asInterface(obj);
-            if (serv != null) {
-                try {
-                    serv.setShuffleMode(MediaPlaybackService.SHUFFLE_AUTO);
-                } catch (RemoteException ex) {
-                }
-            }
-        }
-
-        public void onServiceDisconnected(ComponentName classname) {}
-    };
-
     @Override
     public void onRequestPermissionsResult(
             int requestCode, String permissions[], int[] grantResults) {
diff --git a/src/com/android/music/MusicPicker.java b/src/com/android/music/MusicPicker.java
deleted file mode 100644
index 9972fe2..0000000
--- a/src/com/android/music/MusicPicker.java
+++ /dev/null
@@ -1,686 +0,0 @@
-/*
- * 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.text.TextUtils;
-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. */
-    static 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 boolean mLoading = true;
-        private int mIndexerSortMode;
-        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(MusicUtils.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);
-
-                // 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(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 {
-                    mIndexer.setCursor(cursor);
-                }
-            }
-
-            // 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;
-            }
-
-            return mIndexer.getPositionForSection(section);
-        }
-
-        public int getSectionForPosition(int position) {
-            return 0;
-        }
-
-        public Object[] getSections() {
-            if (mIndexer != null) {
-                return mIndexer.getSections();
-            }
-            return null;
-        }
-    }
-
-    /**
-     * 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();
-            }
-        }
-    }
-
-    /** 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 + " != ''");
-
-        // 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");
-
-        Uri uri = mBaseUri;
-        if (!TextUtils.isEmpty(filterstring)) {
-            uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filterstring)).build();
-        }
-
-        if (sync) {
-            try {
-                return getContentResolver().query(
-                        uri, CURSOR_COLS, where.toString(), null, mSortOrder);
-            } catch (UnsupportedOperationException ex) {
-            }
-        } else {
-            mAdapter.setLoading(true);
-            setProgressBarIndeterminateVisibility(true);
-            mQueryHandler.startQuery(
-                    MY_QUERY_TOKEN, null, uri, CURSOR_COLS, where.toString(), null, 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
index 7cb057b..a8a93e1 100644
--- a/src/com/android/music/MusicUtils.java
+++ b/src/com/android/music/MusicUtils.java
@@ -17,77 +17,76 @@
 package com.android.music;
 
 import android.app.Activity;
-import android.content.ComponentName;
 import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
 import android.content.Context;
-import android.content.ContextWrapper;
 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.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
-import android.graphics.Matrix;
-import android.graphics.Paint;
 import android.graphics.PixelFormat;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.media.MediaMetadata;
+import android.media.session.MediaController;
 import android.net.Uri;
-import android.os.Environment;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
 import android.provider.MediaStore;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.text.format.Time;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.SubMenu;
 import android.view.View;
-import android.view.Window;
 import android.widget.TabWidget;
 import android.widget.TextView;
-import android.widget.Toast;
+import com.android.music.utils.LogHelper;
+import com.android.music.utils.MusicProvider;
 
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.PrintWriter;
-import java.util.Arrays;
 import java.util.Formatter;
-import java.util.HashMap;
+import java.util.Iterator;
 import java.util.Locale;
 
+/*
+Static methods useful for activities
+ */
 public class MusicUtils {
-    private static final String TAG = "MusicUtils";
+    private static final String TAG = LogHelper.makeLogTag(MusicUtils.class);
 
-    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 EFFECTS_PANEL = 13;
-        public final static int CHILD_MENU_BASE = 14; // this should be the last item
+    public static final String TAG_MEDIA_ID = "__MEDIA_ID";
+    public static final String TAG_PARENT_ITEM = "__PARENT_ITEM";
+    public static final String TAG_WITH_TABS = "__WITH_TABS";
+
+    // 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) {}
+    }
+
+    public static Bitmap resizeBitmap(Bitmap bitmap, Bitmap ref) {
+        int w = ref.getWidth();
+        int h = ref.getHeight();
+        return Bitmap.createScaledBitmap(bitmap, w, h, false);
+    }
+
+    public static Drawable getDrawableBitmap(Bitmap bitmap, BitmapDrawable defaultArtwork) {
+        final Bitmap icon = defaultArtwork.getBitmap();
+        int w = icon.getWidth();
+        int h = icon.getHeight();
+        bitmap = Bitmap.createScaledBitmap(bitmap, w, h, false);
+        return new FastBitmapDrawable(bitmap);
     }
 
     public static String makeAlbumsLabel(
@@ -151,585 +150,6 @@
         return songs_albums.toString();
     }
 
-    public static IMediaPlaybackService sService = null;
-    private static HashMap<Context, ServiceBinder> sConnectionMap =
-            new HashMap<Context, ServiceBinder>();
-
-    public static class ServiceToken {
-        ContextWrapper mWrappedContext;
-        ServiceToken(ContextWrapper context) {
-            mWrappedContext = context;
-        }
-    }
-
-    public static ServiceToken bindToService(Activity context) {
-        return bindToService(context, null);
-    }
-
-    public static ServiceToken bindToService(Activity context, ServiceConnection callback) {
-        Activity realActivity = context.getParent();
-        if (realActivity == null) {
-            realActivity = context;
-        }
-        ContextWrapper cw = new ContextWrapper(realActivity);
-        cw.startService(new Intent(cw, MediaPlaybackService.class));
-        ServiceBinder sb = new ServiceBinder(callback);
-        if (cw.bindService((new Intent()).setClass(cw, MediaPlaybackService.class), sb, 0)) {
-            sConnectionMap.put(cw, sb);
-            return new ServiceToken(cw);
-        }
-        Log.e("Music", "Failed to bind to service");
-        return null;
-    }
-
-    public static void unbindFromService(ServiceToken token) {
-        if (token == null) {
-            Log.e("MusicUtils", "Trying to unbind with null token");
-            return;
-        }
-        ContextWrapper cw = token.mWrappedContext;
-        ServiceBinder sb = sConnectionMap.remove(cw);
-        if (sb == null) {
-            Log.e("MusicUtils", "Trying to unbind for unknown Context");
-            return;
-        }
-        cw.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 long getCurrentAlbumId() {
-        if (sService != null) {
-            try {
-                return sService.getAlbumId();
-            } catch (RemoteException ex) {
-            }
-        }
-        return -1;
-    }
-
-    public static long getCurrentArtistId() {
-        if (MusicUtils.sService != null) {
-            try {
-                return sService.getArtistId();
-            } catch (RemoteException ex) {
-            }
-        }
-        return -1;
-    }
-
-    public static long 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;
-    }
-
-    public static void togglePartyShuffle() {
-        if (sService != null) {
-            int shuffle = getCurrentShuffleMode();
-            try {
-                if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
-                    sService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
-                } else {
-                    sService.setShuffleMode(MediaPlaybackService.SHUFFLE_AUTO);
-                }
-            } catch (RemoteException ex) {
-            }
-        }
-    }
-
-    public static void setPartyShuffleMenuIcon(Menu menu) {
-        MenuItem item = menu.findItem(Defs.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);
-            }
-        }
-    }
-
-    /*
-     * 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 long[] sEmptyList = new long[0];
-
-    public static long[] getSongListForCursor(Cursor cursor) {
-        if (cursor == null) {
-            return sEmptyList;
-        }
-        int len = cursor.getCount();
-        long[] list = new long[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.getLong(colidx);
-            cursor.moveToNext();
-        }
-        return list;
-    }
-
-    public static long[] getSongListForArtist(Context context, long 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) {
-            long[] list = getSongListForCursor(cursor);
-            cursor.close();
-            return list;
-        }
-        return sEmptyList;
-    }
-
-    public static long[] getSongListForAlbum(Context context, long 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) {
-            long[] list = getSongListForCursor(cursor);
-            cursor.close();
-            return list;
-        }
-        return sEmptyList;
-    }
-
-    public static long[] 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) {
-            long[] list = getSongListForCursor(cursor);
-            cursor.close();
-            return list;
-        }
-        return sEmptyList;
-    }
-
-    public static void playPlaylist(Context context, long plid) {
-        long[] list = getSongListForPlaylist(context, plid);
-        if (list != null) {
-            playAll(context, list, -1, false);
-        }
-    }
-
-    public static long[] 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();
-            long[] list = new long[len];
-            for (int i = 0; i < len; i++) {
-                c.moveToNext();
-                list[i] = c.getLong(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.getLong(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, long[] 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
-                    long id = c.getLong(0);
-                    sService.removeTrack(id);
-                    // remove from album art cache
-                    long artIndex = c.getLong(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, long[] 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) {
-        }
-    }
-
-    private static ContentValues[] sContentValuesCache = null;
-
-    /**
-     * @param ids The source array containing all the ids to be added to the playlist
-     * @param offset Where in the 'ids' array we start reading
-     * @param len How many items to copy during this pass
-     * @param base The play order offset to use for this pass
-     */
-    private static void makeInsertItems(long[] ids, int offset, int len, int base) {
-        // adjust 'len' if would extend beyond the end of the source array
-        if (offset + len > ids.length) {
-            len = ids.length - offset;
-        }
-        // allocate the ContentValues array, or reallocate if it is the wrong size
-        if (sContentValuesCache == null || sContentValuesCache.length != len) {
-            sContentValuesCache = new ContentValues[len];
-        }
-        // fill in the ContentValues array with the right values for this pass
-        for (int i = 0; i < len; i++) {
-            if (sContentValuesCache[i] == null) {
-                sContentValuesCache[i] = new ContentValues();
-            }
-
-            sContentValuesCache[i].put(
-                    MediaStore.Audio.Playlists.Members.PLAY_ORDER, base + offset + i);
-            sContentValuesCache[i].put(
-                    MediaStore.Audio.Playlists.Members.AUDIO_ID, ids[offset + i]);
-        }
-    }
-
-    public static void addToPlaylist(Context context, long[] 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;
-            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();
-            int numinserted = 0;
-            for (int i = 0; i < size; i += 1000) {
-                makeInsertItems(ids, i, 1000, base);
-                numinserted += resolver.bulkInsert(uri, sContentValuesCache);
-            }
-            String message = context.getResources().getQuantityString(
-                    R.plurals.NNNtrackstoplaylist, numinserted, numinserted);
-            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, int limit) {
-        try {
-            ContentResolver resolver = context.getContentResolver();
-            if (resolver == null) {
-                return null;
-            }
-            if (limit > 0) {
-                uri = uri.buildUpon().appendQueryParameter("limit", "" + limit).build();
-            }
-            return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
-        } catch (UnsupportedOperationException ex) {
-            return null;
-        }
-    }
-    public static Cursor query(Context context, Uri uri, String[] projection, String selection,
-            String[] selectionArgs, String sortOrder) {
-        return query(context, uri, projection, selection, selectionArgs, sortOrder, 0);
-    }
-
-    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);
-        }
-    }
-
-    private static String mLastSdStatus;
-
-    public static void displayDatabaseError(Activity a) {
-        if (a.isFinishing()) {
-            // When switching tabs really fast, we can end up with a null
-            // cursor (not sure why), which will bring us here.
-            // Don't bother showing an error message in that case.
-            return;
-        }
-
-        String status = Environment.getExternalStorageState();
-        int title, message;
-
-        if (android.os.Environment.isExternalStorageRemovable()) {
-            title = R.string.sdcard_error_title;
-            message = R.string.sdcard_error_message;
-        } else {
-            title = R.string.sdcard_error_title_nosdcard;
-            message = R.string.sdcard_error_message_nosdcard;
-        }
-
-        if (status.equals(Environment.MEDIA_SHARED) || status.equals(Environment.MEDIA_UNMOUNTED)) {
-            if (android.os.Environment.isExternalStorageRemovable()) {
-                title = R.string.sdcard_busy_title;
-                message = R.string.sdcard_busy_message;
-            } else {
-                title = R.string.sdcard_busy_title_nosdcard;
-                message = R.string.sdcard_busy_message_nosdcard;
-            }
-        } else if (status.equals(Environment.MEDIA_REMOVED)) {
-            if (android.os.Environment.isExternalStorageRemovable()) {
-                title = R.string.sdcard_missing_title;
-                message = R.string.sdcard_missing_message;
-            } else {
-                title = R.string.sdcard_missing_title_nosdcard;
-                message = R.string.sdcard_missing_message_nosdcard;
-            }
-        } 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 if (!TextUtils.equals(mLastSdStatus, status)) {
-            mLastSdStatus = status;
-            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);
-        }
-        v = a.findViewById(R.id.buttonbar);
-        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
@@ -759,325 +179,6 @@
         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, long[] list, int position) {
-        playAll(context, list, position, false);
-    }
-
-    private static void playAll(
-            Context context, Cursor cursor, int position, boolean force_shuffle) {
-        long[] list = getSongListForCursor(cursor);
-        playAll(context, list, position, force_shuffle);
-    }
-
-    private static void playAll(Context context, long[] 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);
-            }
-            long 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.
-                long[] 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 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<Long, Drawable> sArtCache = new HashMap<Long, 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, long 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, long 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)
-     * This method always returns the default album art icon when no album art is found.
-     */
-    public static Bitmap getArtwork(Context context, long song_id, long album_id) {
-        return getArtwork(context, song_id, 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, long song_id, long 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.
-            if (song_id >= 0) {
-                Bitmap bm = getArtworkFromFile(context, song_id, -1);
-                if (bm != null) {
-                    return bm;
-                }
-            }
-            if (allowdefault) {
-                return getDefaultArtwork(context);
-            }
-            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, song_id, album_id);
-                if (bm != null) {
-                    if (bm.getConfig() == null) {
-                        bm = bm.copy(Bitmap.Config.RGB_565, false);
-                        if (bm == null && allowdefault) {
-                            return getDefaultArtwork(context);
-                        }
-                    }
-                } else if (allowdefault) {
-                    bm = getDefaultArtwork(context);
-                }
-                return bm;
-            } finally {
-                try {
-                    if (in != null) {
-                        in.close();
-                    }
-                } catch (IOException ex) {
-                }
-            }
-        }
-
-        return null;
-    }
-
-    // get album art for specified file
-    private static final String sExternalMediaUri =
-            MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString();
-    private static Bitmap getArtworkFromFile(Context context, long songid, long albumid) {
-        Bitmap bm = null;
-        byte[] art = null;
-        String path = null;
-
-        if (albumid < 0 && songid < 0) {
-            throw new IllegalArgumentException("Must specify an album or a song id");
-        }
-
-        try {
-            if (albumid < 0) {
-                Uri uri = Uri.parse("content://media/external/audio/media/" + songid + "/albumart");
-                ParcelFileDescriptor pfd =
-                        context.getContentResolver().openFileDescriptor(uri, "r");
-                if (pfd != null) {
-                    FileDescriptor fd = pfd.getFileDescriptor();
-                    bm = BitmapFactory.decodeFileDescriptor(fd);
-                }
-            } else {
-                Uri uri = ContentUris.withAppendedId(sArtworkUri, albumid);
-                ParcelFileDescriptor pfd =
-                        context.getContentResolver().openFileDescriptor(uri, "r");
-                if (pfd != null) {
-                    FileDescriptor fd = pfd.getFileDescriptor();
-                    bm = BitmapFactory.decodeFileDescriptor(fd);
-                }
-            }
-        } catch (IllegalStateException ex) {
-        } catch (FileNotFoundException ex) {
-        }
-        if (bm != null) {
-            mCachedBit = bm;
-        }
-        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(context.getPackageName(), Context.MODE_PRIVATE);
@@ -1092,42 +193,6 @@
         SharedPreferencesCompat.apply(ed);
     }
 
-    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();
-            }
-        }
-    }
-
     static int sActiveTabIndex = -1;
 
     static boolean updateButtonBar(Activity a, int highlight) {
@@ -1135,7 +200,7 @@
         boolean withtabs = false;
         Intent intent = a.getIntent();
         if (intent != null) {
-            withtabs = intent.getBooleanExtra("withtabs", false);
+            withtabs = intent.getBooleanExtra(MusicUtils.TAG_WITH_TABS, false);
         }
 
         if (highlight == 0 || !withtabs) {
@@ -1216,7 +281,7 @@
             default:
                 return;
         }
-        intent.putExtra("withtabs", true);
+        intent.putExtra(MusicUtils.TAG_WITH_TABS, true);
         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
         a.startActivity(intent);
         a.finish();
@@ -1228,26 +293,20 @@
         if (nowPlayingView == null) {
             return;
         }
-        try {
-            boolean withtabs = false;
-            Intent intent = a.getIntent();
-            if (intent != null) {
-                withtabs = intent.getBooleanExtra("withtabs", false);
-            }
-            if (true && MusicUtils.sService != null && MusicUtils.sService.getAudioId() != -1) {
+        MediaController controller = a.getMediaController();
+        if (controller != null) {
+            MediaMetadata metadata = controller.getMetadata();
+            if (metadata != null) {
                 TextView title = (TextView) nowPlayingView.findViewById(R.id.title);
                 TextView artist = (TextView) nowPlayingView.findViewById(R.id.artist);
-                title.setText(MusicUtils.sService.getTrackName());
-                String artistName = MusicUtils.sService.getArtistName();
-                if (MediaStore.UNKNOWN_STRING.equals(artistName)) {
+                title.setText(metadata.getString(MediaMetadata.METADATA_KEY_TITLE));
+                String artistName = metadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
+                if (MusicProvider.UNKOWN.equals(artistName)) {
                     artistName = a.getString(R.string.unknown_artist_name);
                 }
                 artist.setText(artistName);
-                // mNowPlayingView.setOnFocusChangeListener(mFocuser);
-                // mNowPlayingView.setOnClickListener(this);
                 nowPlayingView.setVisibility(View.VISIBLE);
                 nowPlayingView.setOnClickListener(new View.OnClickListener() {
-
                     public void onClick(View v) {
                         Context c = v.getContext();
                         c.startActivity(new Intent(c, MediaPlaybackActivity.class));
@@ -1255,101 +314,7 @@
                 });
                 return;
             }
-        } catch (RemoteException ex) {
         }
         nowPlayingView.setVisibility(View.GONE);
     }
-
-    static void setBackground(View v, Bitmap bm) {
-        if (bm == null) {
-            v.setBackgroundResource(0);
-            return;
-        }
-
-        int vwidth = v.getWidth();
-        int vheight = v.getHeight();
-        int bwidth = bm.getWidth();
-        int bheight = bm.getHeight();
-        float scalex = (float) vwidth / bwidth;
-        float scaley = (float) vheight / bheight;
-        float scale = Math.max(scalex, scaley) * 1.3f;
-
-        Bitmap.Config config = Bitmap.Config.ARGB_8888;
-        Bitmap bg = Bitmap.createBitmap(vwidth, vheight, config);
-        Canvas c = new Canvas(bg);
-        Paint paint = new Paint();
-        paint.setAntiAlias(true);
-        paint.setFilterBitmap(true);
-        ColorMatrix greymatrix = new ColorMatrix();
-        greymatrix.setSaturation(0);
-        ColorMatrix darkmatrix = new ColorMatrix();
-        darkmatrix.setScale(.3f, .3f, .3f, 1.0f);
-        greymatrix.postConcat(darkmatrix);
-        ColorFilter filter = new ColorMatrixColorFilter(greymatrix);
-        paint.setColorFilter(filter);
-        Matrix matrix = new Matrix();
-        matrix.setTranslate(-bwidth / 2, -bheight / 2); // move bitmap center to origin
-        matrix.postRotate(10);
-        matrix.postScale(scale, scale);
-        matrix.postTranslate(vwidth / 2, vheight / 2); // Move bitmap center to view center
-        c.drawBitmap(bm, matrix, paint);
-        v.setBackgroundDrawable(new BitmapDrawable(bg));
-    }
-
-    static int getCardId(Context context) {
-        ContentResolver res = context.getContentResolver();
-        Cursor c = res.query(Uri.parse("content://media/external/fs_id"), null, null, null, null);
-        int id = -1;
-        if (c != null) {
-            c.moveToFirst();
-            id = c.getInt(0);
-            c.close();
-        }
-        return id;
-    }
-
-    static class LogEntry {
-        Object item;
-        long time;
-
-        LogEntry(Object o) {
-            item = o;
-            time = System.currentTimeMillis();
-        }
-
-        void dump(PrintWriter out) {
-            sTime.set(time);
-            out.print(sTime.toString() + " : ");
-            if (item instanceof Exception) {
-                ((Exception) item).printStackTrace(out);
-            } else {
-                out.println(item);
-            }
-        }
-    }
-
-    private static LogEntry[] sMusicLog = new LogEntry[100];
-    private static int sLogPtr = 0;
-    private static Time sTime = new Time();
-
-    static void debugLog(Object o) {
-        sMusicLog[sLogPtr] = new LogEntry(o);
-        sLogPtr++;
-        if (sLogPtr >= sMusicLog.length) {
-            sLogPtr = 0;
-        }
-    }
-
-    static void debugDump(PrintWriter out) {
-        for (int i = 0; i < sMusicLog.length; i++) {
-            int idx = (sLogPtr + i);
-            if (idx >= sMusicLog.length) {
-                idx -= sMusicLog.length;
-            }
-            LogEntry entry = sMusicLog[idx];
-            if (entry != null) {
-                entry.dump(out);
-            }
-        }
-    }
 }
diff --git a/src/com/android/music/PlaylistBrowserActivity.java b/src/com/android/music/PlaylistBrowserActivity.java
index aa686fe..fccee6d 100644
--- a/src/com/android/music/PlaylistBrowserActivity.java
+++ b/src/com/android/music/PlaylistBrowserActivity.java
@@ -16,122 +16,73 @@
 
 package com.android.music;
 
-import com.android.music.MusicUtils.ServiceToken;
-
+import android.app.Activity;
 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 android.content.*;
 import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.database.MergeCursor;
 import android.database.sqlite.SQLiteException;
 import android.media.AudioManager;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.browse.MediaBrowser;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
 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.LayoutInflater;
 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;
+import android.widget.*;
+import com.android.music.utils.LogHelper;
+import com.android.music.utils.MediaIDHelper;
 
 import java.text.Collator;
 import java.util.ArrayList;
+import java.util.List;
 
 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;
+        extends ListActivity implements View.OnCreateContextMenuListener {
+    private static final String TAG = LogHelper.makeLogTag(PlaylistBrowserActivity.class);
     private PlaylistListAdapter mAdapter;
     boolean mAdapterSent;
-    private static int mLastListPosCourse = -1;
-    private static int mLastListPosFine = -1;
+    private static final MediaBrowser.MediaItem DEFAULT_PARENT_ITEM = new MediaBrowser.MediaItem(
+            new MediaDescription.Builder()
+                    .setMediaId(MediaIDHelper.MEDIA_ID_MUSICS_BY_PLAYLIST)
+                    .setTitle("Playlists")
+                    .build(),
+            MediaBrowser.MediaItem.FLAG_BROWSABLE);
 
-    private boolean mCreateShortcut;
-    private ServiceToken mToken;
-
-    public PlaylistBrowserActivity() {}
+    private MediaBrowser mMediaBrowser;
+    private MediaBrowser.MediaItem mParentItem;
 
     /** 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;
+        LogHelper.d(TAG, "onCreate()");
+        // Process past states
+        Intent intent = getIntent();
+        if (icicle != null) {
+            LogHelper.d(TAG, "Launch by saved instance state");
+            mParentItem = icicle.getParcelable(MusicUtils.TAG_PARENT_ITEM);
+            MusicUtils.updateNowPlaying(this);
+        } else if (intent != null) {
+            LogHelper.d(TAG, "Launch by intent");
+            mParentItem = intent.getExtras().getParcelable(MusicUtils.TAG_PARENT_ITEM);
         }
-
-        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+        if (mParentItem == null) {
+            LogHelper.d(TAG, "Launch by default parameters");
+            mParentItem = DEFAULT_PARENT_ITEM;
+        }
         requestWindowFeature(Window.FEATURE_NO_TITLE);
         setVolumeControlStream(AudioManager.STREAM_MUSIC);
-        mToken = MusicUtils.bindToService(this, new ServiceConnection() {
-            public void onServiceConnected(ComponentName classname, IBinder obj) {
-                if (Intent.ACTION_VIEW.equals(action)) {
-                    Bundle b = intent.getExtras();
-                    if (b == null) {
-                        Log.w(TAG, "Unexpected:getExtras() returns null.");
-                    } else {
-                        try {
-                            long id = Long.parseLong(b.getString("playlist"));
-                            if (id == RECENTLY_ADDED_PLAYLIST) {
-                                playRecentlyAdded();
-                            } else if (id == PODCASTS_PLAYLIST) {
-                                playPodcasts();
-                            } else if (id == ALL_SONGS_PLAYLIST) {
-                                long[] list = MusicUtils.getAllSongs(PlaylistBrowserActivity.this);
-                                if (list != null) {
-                                    MusicUtils.playAll(PlaylistBrowserActivity.this, list, 0);
-                                }
-                            } else {
-                                MusicUtils.playPlaylist(PlaylistBrowserActivity.this, id);
-                            }
-                        } catch (NumberFormatException e) {
-                            Log.w(TAG, "Playlist id missing or broken");
-                        }
-                    }
-                    finish();
-                    return;
-                }
-                MusicUtils.updateNowPlaying(PlaylistBrowserActivity.this);
-            }
 
-            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);
-
+        setTitle(R.string.playlists_title);
         setContentView(R.layout.media_picker_activity);
         MusicUtils.updateButtonBar(this, R.id.playlisttab);
         ListView lv = getListView();
@@ -141,29 +92,15 @@
         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);
+            mAdapter = new PlaylistListAdapter(this, R.layout.track_list_item);
             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);
-            }
         }
+        setListAdapter(mAdapter);
+        Log.d(TAG, "Creating MediaBrowser");
+        mMediaBrowser = new MediaBrowser(this, new ComponentName(this, MediaPlaybackService.class),
+                mConnectionCallback, null);
     }
 
     @Override
@@ -175,455 +112,146 @@
 
     @Override
     public void onDestroy() {
-        ListView lv = getListView();
-        if (lv != null) {
-            mLastListPosCourse = lv.getFirstVisiblePosition();
-            View cv = lv.getChildAt(0);
-            if (cv != null) {
-                mLastListPosFine = cv.getTop();
-            }
-        }
-        MusicUtils.unbindFromService(mToken);
-        // If we have an adapter and didn't send it off to another activity yet, we should
-        // close its cursor, which we do by assigning a null cursor to it. Doing this
-        // instead of closing the cursor directly keeps the framework from accessing
-        // the closed cursor later.
-        if (!mAdapterSent && mAdapter != null) {
-            mAdapter.changeCursor(null);
-        }
-        // Because we pass the adapter to the next activity, we need to make
-        // sure it doesn't keep a reference to this activity. We can do this
-        // by clearing its DatasetObservers, which setListAdapter(null) does.
         setListAdapter(null);
         mAdapter = null;
-        unregisterReceiver(mScanListener);
         super.onDestroy();
     }
 
     @Override
     public void onResume() {
         super.onResume();
-
-        MusicUtils.setSpinnerState(this);
-        MusicUtils.updateNowPlaying(PlaylistBrowserActivity.this);
     }
+
     @Override
     public void onPause() {
-        mReScanHandler.removeCallbacksAndMessages(null);
         super.onPause();
     }
-    private BroadcastReceiver mScanListener = new BroadcastReceiver() {
+
+    @Override
+    public void onStart() {
+        Log.d(TAG, "onStart()");
+        super.onStart();
+        mMediaBrowser.connect();
+    }
+
+    @Override
+    public void onStop() {
+        Log.d(TAG, "onStop()");
+        super.onStop();
+        mMediaBrowser.disconnect();
+    }
+
+    private MediaBrowser.SubscriptionCallback mSubscriptionCallback =
+            new MediaBrowser.SubscriptionCallback() {
+
+                @Override
+                public void onChildrenLoaded(
+                        String parentId, List<MediaBrowser.MediaItem> children) {
+                    mAdapter.clear();
+                    mAdapter.notifyDataSetInvalidated();
+                    for (MediaBrowser.MediaItem item : children) {
+                        mAdapter.add(item);
+                    }
+                    mAdapter.notifyDataSetChanged();
+                }
+
+                @Override
+                public void onError(String id) {
+                    Toast.makeText(getApplicationContext(), R.string.error_loading_media,
+                                 Toast.LENGTH_LONG)
+                            .show();
+                }
+            };
+
+    private MediaBrowser.ConnectionCallback mConnectionCallback =
+            new MediaBrowser.ConnectionCallback() {
+                @Override
+                public void onConnected() {
+                    Log.d(TAG, "onConnected: session token " + mMediaBrowser.getSessionToken());
+                    mMediaBrowser.subscribe(mParentItem.getMediaId(), mSubscriptionCallback);
+                    if (mMediaBrowser.getSessionToken() == null) {
+                        throw new IllegalArgumentException("No Session token");
+                    }
+                    MediaController mediaController = new MediaController(
+                            PlaylistBrowserActivity.this, mMediaBrowser.getSessionToken());
+                    mediaController.registerCallback(mMediaControllerCallback);
+                    PlaylistBrowserActivity.this.setMediaController(mediaController);
+                    if (mediaController.getMetadata() != null) {
+                        MusicUtils.updateNowPlaying(PlaylistBrowserActivity.this);
+                    }
+                }
+
+                @Override
+                public void onConnectionFailed() {
+                    Log.d(TAG, "onConnectionFailed");
+                }
+
+                @Override
+                public void onConnectionSuspended() {
+                    Log.d(TAG, "onConnectionSuspended");
+                    PlaylistBrowserActivity.this.setMediaController(null);
+                }
+            };
+
+    private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() {
         @Override
-        public void onReceive(Context context, Intent intent) {
-            MusicUtils.setSpinnerState(PlaylistBrowserActivity.this);
-            mReScanHandler.sendEmptyMessage(0);
+        public void onMetadataChanged(MediaMetadata metadata) {
+            super.onMetadataChanged(metadata);
+            MusicUtils.updateNowPlaying(PlaylistBrowserActivity.this);
         }
     };
 
-    private Handler mReScanHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            if (mAdapter != null) {
-                getPlaylistCursor(mAdapter.getQueryHandler(), null);
-            }
-        }
-    };
-    public void init(Cursor cursor) {
-        if (mAdapter == null) {
-            return;
-        }
-        mAdapter.changeCursor(cursor);
+    private class PlaylistListAdapter extends ArrayAdapter<MediaBrowser.MediaItem> {
+        private int mLayoutId;
+        private Activity mActivity;
 
-        if (mPlaylistCursor == null) {
-            MusicUtils.displayDatabaseError(this);
-            closeContextMenu();
-            mReScanHandler.sendEmptyMessageDelayed(0, 1000);
-            return;
-        }
-
-        // restore previous position
-        if (mLastListPosCourse >= 0) {
-            getListView().setSelectionFromTop(mLastListPosCourse, mLastListPosFine);
-            mLastListPosCourse = -1;
-        }
-        MusicUtils.hideDatabaseError(this);
-        MusicUtils.updateButtonBar(this, R.id.playlisttab);
-        setTitle();
-    }
-
-    private void setTitle() {
-        setTitle(R.string.playlists_title);
-    }
-
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        if (!mCreateShortcut) {
-            menu.add(0, PARTY_SHUFFLE, 0,
-                    R.string.party_shuffle); // icon will be set in onPrepareOptionsMenu()
-        }
-        return super.onCreateOptionsMenu(menu);
-    }
-
-    @Override
-    public boolean onPrepareOptionsMenu(Menu menu) {
-        MusicUtils.setPartyShuffleMenuIcon(menu);
-        return super.onPrepareOptionsMenu(menu);
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        Intent intent;
-        switch (item.getItemId()) {
-            case PARTY_SHUFFLE:
-                MusicUtils.togglePartyShuffle();
-                break;
-        }
-        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 if (mAdapter != null) {
-                    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.ic_launcher_shortcut_music_playlist));
-
-            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();
-            long[] list = new long[len];
-            for (int i = 0; i < len; i++) {
-                cursor.moveToNext();
-                list[i] = cursor.getLong(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();
-            long[] list = new long[len];
-            for (int i = 0; i < len; i++) {
-                cursor.moveToNext();
-                list[i] = cursor.getLong(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;
-        }
-        MatrixCursor autoplaylistscursor = new MatrixCursor(mCols);
-        if (mCreateShortcut) {
-            ArrayList<Object> all = new ArrayList<Object>(2);
-            all.add(ALL_SONGS_PLAYLIST);
-            all.add(getString(R.string.play_all));
-            autoplaylistscursor.addRow(all);
-        }
-        ArrayList<Object> recent = new ArrayList<Object>(2);
-        recent.add(RECENTLY_ADDED_PLAYLIST);
-        recent.add(getString(R.string.recentlyadded));
-        autoplaylistscursor.addRow(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));
-                autoplaylistscursor.addRow(podcasts);
-            }
-        }
-
-        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);
+        PlaylistListAdapter(PlaylistBrowserActivity currentactivity, int layout) {
+            super(currentactivity, layout);
             mActivity = currentactivity;
-            getColumnIndices(cursor);
-            mQueryHandler = new QueryHandler(context.getContentResolver());
+            mLayoutId = layout;
         }
-        private void getColumnIndices(Cursor cursor) {
-            if (cursor != null) {
-                mTitleIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.NAME);
-                mIdIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists._ID);
-            }
+
+        private class ViewHolder {
+            TextView line1;
+            TextView line2;
+            ImageView icon;
+            ImageView play_indicator;
         }
 
         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);
+        public View getView(int position, View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = LayoutInflater.from(getContext()).inflate(mLayoutId, parent, false);
+                ViewHolder vhx = new ViewHolder();
+                vhx.line1 = (TextView) convertView.findViewById(R.id.line1);
+                vhx.line2 = (TextView) convertView.findViewById(R.id.line2);
+                vhx.icon = (ImageView) convertView.findViewById(R.id.icon);
+                vhx.play_indicator = (ImageView) convertView.findViewById(R.id.play_indicator);
+                convertView.setTag(vhx);
             }
-            ViewGroup.LayoutParams p = iv.getLayoutParams();
+            ViewHolder vh = (ViewHolder) convertView.getTag();
+            MediaBrowser.MediaItem item = getItem(position);
+            vh.line1.setText(item.getDescription().getTitle());
+            vh.line2.setVisibility(View.GONE);
+            vh.play_indicator.setVisibility(View.GONE);
+            vh.icon.setImageResource(R.drawable.ic_mp_playlist_list);
+            ViewGroup.LayoutParams p = vh.icon.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 (mActivity.isFinishing() && cursor != null) {
-                cursor.close();
-                cursor = null;
-            }
-            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;
+            return convertView;
         }
     }
 
-    private Cursor mPlaylistCursor;
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id) {
+        MediaBrowser.MediaItem item = mAdapter.getItem(position);
+        Intent intent = new Intent(Intent.ACTION_PICK);
+        intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
+        intent.putExtra(MusicUtils.TAG_PARENT_ITEM, item);
+        startActivity(intent);
+    }
 }
diff --git a/src/com/android/music/QueryBrowserActivity.java b/src/com/android/music/QueryBrowserActivity.java
deleted file mode 100644
index 7e1228d..0000000
--- a/src/com/android/music/QueryBrowserActivity.java
+++ /dev/null
@@ -1,462 +0,0 @@
-/*
- * 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.MusicUtils.ServiceToken;
-
-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.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.ServiceConnection;
-
-import android.database.Cursor;
-import android.database.DatabaseUtils;
-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.BaseColumns;
-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, ServiceConnection {
-    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 = "";
-    private ServiceToken mToken;
-
-    public QueryBrowserActivity() {}
-
-    /** Called when the activity is first created. */
-    @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        setVolumeControlStream(AudioManager.STREAM_MUSIC);
-        mAdapter = (QueryListAdapter) getLastNonConfigurationInstance();
-        mToken = MusicUtils.bindToService(this, this);
-        // defer the real work until we're bound to the service
-    }
-
-    public void onServiceConnected(ComponentName name, IBinder service) {
-        IntentFilter f = new IntentFilter();
-        f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
-        f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
-        f.addDataScheme("file");
-        registerReceiver(mScanListener, f);
-
-        Intent intent = getIntent();
-        String action = intent != null ? intent.getAction() : null;
-
-        if (Intent.ACTION_VIEW.equals(action)) {
-            // 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();
-                long[] list = new long[] {Long.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);
-        if (MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)) {
-            String focus = intent.getStringExtra(MediaStore.EXTRA_MEDIA_FOCUS);
-            String artist = intent.getStringExtra(MediaStore.EXTRA_MEDIA_ARTIST);
-            String album = intent.getStringExtra(MediaStore.EXTRA_MEDIA_ALBUM);
-            String title = intent.getStringExtra(MediaStore.EXTRA_MEDIA_TITLE);
-            if (focus != null) {
-                if (focus.startsWith("audio/") && title != null) {
-                    mFilterString = title;
-                } else if (focus.equals(MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE)) {
-                    if (album != null) {
-                        mFilterString = album;
-                        if (artist != null) {
-                            mFilterString = mFilterString + " " + artist;
-                        }
-                    }
-                } else if (focus.equals(MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE)) {
-                    if (artist != null) {
-                        mFilterString = artist;
-                    }
-                }
-            }
-        }
-
-        setContentView(R.layout.query_activity);
-        mTrackList = getListView();
-        mTrackList.setTextFilterEnabled(true);
-        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);
-            }
-        }
-    }
-
-    public void onServiceDisconnected(ComponentName name) {}
-
-    @Override
-    public Object onRetainNonConfigurationInstance() {
-        mAdapterSent = true;
-        return mAdapter;
-    }
-
-    @Override
-    public void onPause() {
-        mReScanHandler.removeCallbacksAndMessages(null);
-        super.onPause();
-    }
-
-    @Override
-    public void onDestroy() {
-        MusicUtils.unbindFromService(mToken);
-        unregisterReceiver(mScanListener);
-        // If we have an adapter and didn't send it off to another activity yet, we should
-        // close its cursor, which we do by assigning a null cursor to it. Doing this
-        // instead of closing the cursor directly keeps the framework from accessing
-        // the closed cursor later.
-        if (!mAdapterSent && mAdapter != null) {
-            mAdapter.changeCursor(null);
-        }
-        // Because we pass the adapter to the next activity, we need to make
-        // sure it doesn't keep a reference to this activity. We can do this
-        // by clearing its DatasetObservers, which setListAdapter(null) does.
-        if (getListView() != null) {
-            setListAdapter(null);
-        }
-        mAdapter = null;
-        super.onDestroy();
-    }
-
-    /*
-     * 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) {
-            if (mAdapter != null) {
-                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) {
-        if (mAdapter == null) {
-            return;
-        }
-        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.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-            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.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-            intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
-            intent.putExtra("album", Long.valueOf(id).toString());
-            startActivity(intent);
-        } else if (position >= 0 && id >= 0) {
-            long[] list = new long[] {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[] {
-                BaseColumns._ID, // this will be the artist, album or track ID
-                MediaStore.Audio.Media.MIME_TYPE, // mimetype of audio file, or "artist" or "album"
-                MediaStore.Audio.Artists.ARTIST, MediaStore.Audio.Albums.ALBUM,
-                MediaStore.Audio.Media.TITLE, "data1", "data2"};
-
-        Uri search = Uri.parse("content://media/external/audio/search/fancy/" + 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(MediaStore.Audio.Artists.ARTIST));
-                String displayname = name;
-                boolean isunknown = false;
-                if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) {
-                    displayname = context.getString(R.string.unknown_artist_name);
-                    isunknown = true;
-                }
-                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, isunknown);
-
-                tv2.setText(songs_albums);
-
-            } else if (mimetype.equals("album")) {
-                iv.setImageResource(R.drawable.albumart_mp_unknown_list);
-                String name = cursor.getString(
-                        cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));
-                String displayname = name;
-                if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) {
-                    displayname = context.getString(R.string.unknown_album_name);
-                }
-                tv1.setText(displayname);
-
-                name = cursor.getString(
-                        cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST));
-                displayname = name;
-                if (name == null || name.equals(MediaStore.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(MediaStore.Audio.Media.TITLE));
-                tv1.setText(name);
-
-                String displayname = cursor.getString(
-                        cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST));
-                if (displayname == null || displayname.equals(MediaStore.UNKNOWN_STRING)) {
-                    displayname = context.getString(R.string.unknown_artist_name);
-                }
-                name = cursor.getString(
-                        cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));
-                if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) {
-                    name = context.getString(R.string.unknown_album_name);
-                }
-                tv2.setText(displayname + " - " + name);
-            }
-        }
-        @Override
-        public void changeCursor(Cursor cursor) {
-            if (mActivity.isFinishing() && cursor != null) {
-                cursor.close();
-                cursor = null;
-            }
-            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
deleted file mode 100644
index 56fa4ec..0000000
--- a/src/com/android/music/RenamePlaylist.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * 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.database.Cursor;
-import android.media.AudioManager;
-import android.os.Bundle;
-import android.provider.MediaStore;
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.util.Log;
-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.MATCH_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 (typedname.trim().length() == 0) {
-            mSaveButton.setEnabled(false);
-        } else {
-            mSaveButton.setEnabled(true);
-            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/ScanningProgress.java b/src/com/android/music/ScanningProgress.java
deleted file mode 100644
index 05719fd..0000000
--- a/src/com/android/music/ScanningProgress.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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);
-        if (android.os.Environment.isExternalStorageRemovable()) {
-            setContentView(R.layout.scanning);
-        } else {
-            setContentView(R.layout.scanning_nosdcard);
-        }
-        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/SortCursor.java b/src/com/android/music/SortCursor.java
deleted file mode 100644
index cdb19f9..0000000
--- a/src/com/android/music/SortCursor.java
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
- * 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.database.AbstractCursor;
-import android.database.Cursor;
-import android.database.DataSetObserver;
-import android.util.Log;
-
-/**
- * A variant of MergeCursor that sorts the cursors being merged. If decent
- * performance is ever obtained, it can be put back under android.database.
- */
-public class SortCursor extends AbstractCursor {
-    private static final String TAG = "SortCursor";
-    private Cursor mCursor; // updated in onMove
-    private Cursor[] mCursors;
-    private int[] mSortColumns;
-    private final int ROWCACHESIZE = 64;
-    private int mRowNumCache[] = new int[ROWCACHESIZE];
-    private int mCursorCache[] = new int[ROWCACHESIZE];
-    private int mCurRowNumCache[][];
-    private int mLastCacheHit = -1;
-
-    private DataSetObserver mObserver = new DataSetObserver() {
-
-        @Override
-        public void onChanged() {
-            // Reset our position so the optimizations in move-related code
-            // don't screw us over
-            mPos = -1;
-        }
-
-        @Override
-        public void onInvalidated() {
-            mPos = -1;
-        }
-    };
-
-    public SortCursor(Cursor[] cursors, String sortcolumn) {
-        mCursors = cursors;
-
-        int length = mCursors.length;
-        mSortColumns = new int[length];
-        for (int i = 0; i < length; i++) {
-            if (mCursors[i] == null) continue;
-
-            // Register ourself as a data set observer
-            mCursors[i].registerDataSetObserver(mObserver);
-
-            mCursors[i].moveToFirst();
-
-            // We don't catch the exception
-            mSortColumns[i] = mCursors[i].getColumnIndexOrThrow(sortcolumn);
-        }
-        mCursor = null;
-        String smallest = "";
-        for (int j = 0; j < length; j++) {
-            if (mCursors[j] == null || mCursors[j].isAfterLast()) continue;
-            String current = mCursors[j].getString(mSortColumns[j]);
-            if (mCursor == null || current.compareToIgnoreCase(smallest) < 0) {
-                smallest = current;
-                mCursor = mCursors[j];
-            }
-        }
-
-        for (int i = mRowNumCache.length - 1; i >= 0; i--) {
-            mRowNumCache[i] = -2;
-        }
-        mCurRowNumCache = new int[ROWCACHESIZE][length];
-    }
-
-    @Override
-    public int getCount() {
-        int count = 0;
-        int length = mCursors.length;
-        for (int i = 0; i < length; i++) {
-            if (mCursors[i] != null) {
-                count += mCursors[i].getCount();
-            }
-        }
-        return count;
-    }
-
-    @Override
-    public boolean onMove(int oldPosition, int newPosition) {
-        if (oldPosition == newPosition) return true;
-
-        /* Find the right cursor
-         * Because the client of this cursor (the listadapter/view) tends
-         * to jump around in the cursor somewhat, a simple cache strategy
-         * is used to avoid having to search all cursors from the start.
-         * TODO: investigate strategies for optimizing random access and
-         * reverse-order access.
-         */
-
-        int cache_entry = newPosition % ROWCACHESIZE;
-
-        if (mRowNumCache[cache_entry] == newPosition) {
-            int which = mCursorCache[cache_entry];
-            mCursor = mCursors[which];
-            if (mCursor == null) {
-                Log.w(TAG, "onMove: cache results in a null cursor.");
-                return false;
-            }
-            mCursor.moveToPosition(mCurRowNumCache[cache_entry][which]);
-            mLastCacheHit = cache_entry;
-            return true;
-        }
-
-        mCursor = null;
-        int length = mCursors.length;
-
-        if (mLastCacheHit >= 0) {
-            for (int i = 0; i < length; i++) {
-                if (mCursors[i] == null) continue;
-                mCursors[i].moveToPosition(mCurRowNumCache[mLastCacheHit][i]);
-            }
-        }
-
-        if (newPosition < oldPosition || oldPosition == -1) {
-            for (int i = 0; i < length; i++) {
-                if (mCursors[i] == null) continue;
-                mCursors[i].moveToFirst();
-            }
-            oldPosition = 0;
-        }
-        if (oldPosition < 0) {
-            oldPosition = 0;
-        }
-
-        // search forward to the new position
-        int smallestIdx = -1;
-        for (int i = oldPosition; i <= newPosition; i++) {
-            String smallest = "";
-            smallestIdx = -1;
-            for (int j = 0; j < length; j++) {
-                if (mCursors[j] == null || mCursors[j].isAfterLast()) {
-                    continue;
-                }
-                String current = mCursors[j].getString(mSortColumns[j]);
-                if (smallestIdx < 0 || current.compareToIgnoreCase(smallest) < 0) {
-                    smallest = current;
-                    smallestIdx = j;
-                }
-            }
-            if (i == newPosition) break;
-            if (mCursors[smallestIdx] != null) {
-                mCursors[smallestIdx].moveToNext();
-            }
-        }
-        mCursor = mCursors[smallestIdx];
-        mRowNumCache[cache_entry] = newPosition;
-        mCursorCache[cache_entry] = smallestIdx;
-        for (int i = 0; i < length; i++) {
-            if (mCursors[i] != null) {
-                mCurRowNumCache[cache_entry][i] = mCursors[i].getPosition();
-            }
-        }
-        mLastCacheHit = -1;
-        return true;
-    }
-
-    @Override
-    public String getString(int column) {
-        return mCursor.getString(column);
-    }
-
-    @Override
-    public short getShort(int column) {
-        return mCursor.getShort(column);
-    }
-
-    @Override
-    public int getInt(int column) {
-        return mCursor.getInt(column);
-    }
-
-    @Override
-    public long getLong(int column) {
-        return mCursor.getLong(column);
-    }
-
-    @Override
-    public float getFloat(int column) {
-        return mCursor.getFloat(column);
-    }
-
-    @Override
-    public double getDouble(int column) {
-        return mCursor.getDouble(column);
-    }
-
-    @Override
-    public int getType(int column) {
-        return mCursor.getType(column);
-    }
-
-    @Override
-    public boolean isNull(int column) {
-        return mCursor.isNull(column);
-    }
-
-    @Override
-    public byte[] getBlob(int column) {
-        return mCursor.getBlob(column);
-    }
-
-    @Override
-    public String[] getColumnNames() {
-        if (mCursor != null) {
-            return mCursor.getColumnNames();
-        } else {
-            // All of the cursors may be empty, but they can still return
-            // this information.
-            int length = mCursors.length;
-            for (int i = 0; i < length; i++) {
-                if (mCursors[i] != null) {
-                    return mCursors[i].getColumnNames();
-                }
-            }
-            throw new IllegalStateException("No cursor that can return names");
-        }
-    }
-
-    @Override
-    public void deactivate() {
-        int length = mCursors.length;
-        for (int i = 0; i < length; i++) {
-            if (mCursors[i] == null) continue;
-            mCursors[i].deactivate();
-        }
-    }
-
-    @Override
-    public void close() {
-        int length = mCursors.length;
-        for (int i = 0; i < length; i++) {
-            if (mCursors[i] == null) continue;
-            mCursors[i].close();
-        }
-    }
-
-    @Override
-    public void registerDataSetObserver(DataSetObserver observer) {
-        int length = mCursors.length;
-        for (int i = 0; i < length; i++) {
-            if (mCursors[i] != null) {
-                mCursors[i].registerDataSetObserver(observer);
-            }
-        }
-    }
-
-    @Override
-    public void unregisterDataSetObserver(DataSetObserver observer) {
-        int length = mCursors.length;
-        for (int i = 0; i < length; i++) {
-            if (mCursors[i] != null) {
-                mCursors[i].unregisterDataSetObserver(observer);
-            }
-        }
-    }
-
-    @Override
-    public boolean requery() {
-        int length = mCursors.length;
-        for (int i = 0; i < length; i++) {
-            if (mCursors[i] == null) continue;
-
-            if (mCursors[i].requery() == false) {
-                return false;
-            }
-        }
-
-        return true;
-    }
-}
diff --git a/src/com/android/music/TrackBrowserActivity.java b/src/com/android/music/TrackBrowserActivity.java
index f697b53..69ce726 100644
--- a/src/com/android/music/TrackBrowserActivity.java
+++ b/src/com/android/music/TrackBrowserActivity.java
@@ -16,1518 +16,302 @@
 
 package com.android.music;
 
-import com.android.music.MusicUtils.ServiceToken;
-
+import android.app.Activity;
 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.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
-import android.net.Uri;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.browse.MediaBrowser;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
 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.text.TextUtils;
 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.LayoutInflater;
 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 android.widget.*;
+import com.android.music.utils.LogHelper;
+import com.android.music.utils.MediaIDHelper;
 
-import java.text.Collator;
-import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
 
-public class TrackBrowserActivity extends ListActivity
-        implements View.OnCreateContextMenuListener, MusicUtils.Defs, ServiceConnection {
-    private static final int Q_SELECTED = CHILD_MENU_BASE;
-    private static final int Q_ALL = CHILD_MENU_BASE + 1;
-    private static final int SAVE_AS_PLAYLIST = CHILD_MENU_BASE + 2;
-    private static final int PLAY_ALL = CHILD_MENU_BASE + 3;
-    private static final int CLEAR_PLAYLIST = CHILD_MENU_BASE + 4;
-    private static final int REMOVE = CHILD_MENU_BASE + 5;
-    private static final int SEARCH = CHILD_MENU_BASE + 6;
+/*
+This activity shows when there is a need for
+ 1. Songs tab [withtab = true]
+ 2. Browse songs within an album [withtab = false]
+ 3. Browse songs within a playlist [withtab = false]
+ 4. Browse songs within now playing queue [withtab = false]
+ */
+public class TrackBrowserActivity extends ListActivity {
+    private static final String TAG = LogHelper.makeLogTag(TrackBrowserActivity.class);
+    private static final MediaBrowser.MediaItem DEFAULT_PARENT_ITEM =
+            new MediaBrowser.MediaItem(new MediaDescription.Builder()
+                                               .setMediaId(MediaIDHelper.MEDIA_ID_MUSICS_BY_SONG)
+                                               .setTitle("Songs")
+                                               .build(),
+                    MediaBrowser.MediaItem.FLAG_BROWSABLE);
 
-    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;
+    // Underlining ListView of this Activity
     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;
-    private static int mLastListPosCourse = -1;
-    private static int mLastListPosFine = -1;
-    private boolean mUseLastListPos = false;
-    private ServiceToken mToken;
+    // The mediaId to be used for subscribing for children using the MediaBrowser.
+    private MediaBrowser.MediaItem mParentItem;
+    private MediaBrowser mMediaBrowser;
+    private TrackBrowseAdapter mBrowseListAdapter;
+    private boolean mWithTabs;
 
-    public TrackBrowserActivity() {}
-
-    /** Called when the activity is first created. */
+    /**
+     * Called when the activity is first created.
+     */
     @Override
     public void onCreate(Bundle icicle) {
+        Log.d(TAG, "onCreate()");
         super.onCreate(icicle);
-        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+        // Process past states
         Intent intent = getIntent();
-        if (intent != null) {
-            if (intent.getBooleanExtra("withtabs", false)) {
-                requestWindowFeature(Window.FEATURE_NO_TITLE);
-            }
-        }
-        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 = intent.getStringExtra("album");
-            // If we have an album, show everything on the album, not just stuff
-            // by a particular artist.
-            mArtistId = intent.getStringExtra("artist");
-            mPlaylist = intent.getStringExtra("playlist");
-            mGenre = intent.getStringExtra("genre");
-            mEditMode = intent.getAction().equals(Intent.ACTION_EDIT);
+            LogHelper.d(TAG, "Launch by saved instance state");
+            mParentItem = icicle.getParcelable(MusicUtils.TAG_PARENT_ITEM);
+            mWithTabs = icicle.getBoolean(MusicUtils.TAG_WITH_TABS);
+            MusicUtils.updateNowPlaying(this);
+        } else if (intent != null) {
+            LogHelper.d(TAG, "Launch by intent");
+            mParentItem = intent.getExtras().getParcelable(MusicUtils.TAG_PARENT_ITEM);
+            mWithTabs = intent.getBooleanExtra(MusicUtils.TAG_WITH_TABS, false);
         }
+        if (mParentItem == null) {
+            LogHelper.d(TAG, "Launch by default parameters");
+            mParentItem = DEFAULT_PARENT_ITEM;
+            mWithTabs = true;
+        }
+        if (mWithTabs) {
+            requestWindowFeature(Window.FEATURE_NO_TITLE);
+        }
+        setTitle(mParentItem.getDescription().getTitle());
+        setVolumeControlStream(AudioManager.STREAM_MUSIC);
 
-        mCursorCols = new String[] {MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE,
-                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.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, MediaStore.Audio.Media.IS_MUSIC};
-
+        // Init layout
+        LogHelper.d(TAG, "init layout");
         setContentView(R.layout.media_picker_activity);
-        mUseLastListPos = MusicUtils.updateButtonBar(this, R.id.songtab);
+        MusicUtils.updateButtonBar(this, R.id.songtab);
+
+        // Init the ListView
+        Log.d(TAG, "Creating ListView");
         mTrackList = getListView();
-        mTrackList.setOnCreateContextMenuListener(this);
         mTrackList.setCacheColorHint(0);
-        if (mEditMode) {
-            ((TouchInterceptor) mTrackList).setDropListener(mDropListener);
-            ((TouchInterceptor) mTrackList).setRemoveListener(mRemoveListener);
-            mTrackList.setDivider(null);
-            mTrackList.setSelector(R.drawable.list_selector_background);
-        } else {
-            mTrackList.setTextFilterEnabled(true);
+        mTrackList.setTextFilterEnabled(true);
+        mBrowseListAdapter = (TrackBrowseAdapter) getLastNonConfigurationInstance();
+        if (mBrowseListAdapter == null) {
+            mBrowseListAdapter = new TrackBrowseAdapter(this, R.layout.track_list_item);
         }
-        mAdapter = (TrackListAdapter) getLastNonConfigurationInstance();
-
-        if (mAdapter != null) {
-            mAdapter.setActivity(this);
-            setListAdapter(mAdapter);
-        }
-        mToken = MusicUtils.bindToService(this, this);
-
+        setListAdapter(mBrowseListAdapter);
         // don't set the album art until after the view has been layed out
         mTrackList.post(new Runnable() {
-
             public void run() {
-                setAlbumArtBackground();
+                mTrackList.setBackgroundColor(Color.WHITE);
+                mTrackList.setCacheColorHint(0);
             }
         });
-    }
 
-    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, true);
-        } 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, false);
-            } else {
-                setTitle(R.string.working_songs);
-                getTrackCursor(mAdapter.getQueryHandler(), null, true);
-            }
-        }
-        if (!mEditMode) {
-            MusicUtils.updateNowPlaying(this);
-        }
-    }
-
-    public void onServiceDisconnected(ComponentName name) {
-        // we can't really function without the service, so don't
-        finish();
+        // Create media browser
+        Log.d(TAG, "Creating MediaBrowser");
+        mMediaBrowser = new MediaBrowser(this, new ComponentName(this, MediaPlaybackService.class),
+                mConnectionCallback, null);
     }
 
     @Override
-    public Object onRetainNonConfigurationInstance() {
-        TrackListAdapter a = mAdapter;
-        mAdapterSent = true;
-        return a;
+    public void onStart() {
+        Log.d(TAG, "onStart()");
+        super.onStart();
+        mMediaBrowser.connect();
+    }
+
+    @Override
+    public void onStop() {
+        Log.d(TAG, "onStop()");
+        super.onStop();
+        mMediaBrowser.disconnect();
     }
 
     @Override
     public void onDestroy() {
+        Log.d(TAG, "onDestroy()");
         ListView lv = getListView();
-        if (lv != null) {
-            if (mUseLastListPos) {
-                mLastListPosCourse = lv.getFirstVisiblePosition();
-                View cv = lv.getChildAt(0);
-                if (cv != null) {
-                    mLastListPosFine = cv.getTop();
-                }
-            }
-            if (mEditMode) {
-                // clear the listeners so we won't get any more callbacks
-                ((TouchInterceptor) lv).setDropListener(null);
-                ((TouchInterceptor) lv).setRemoveListener(null);
-            }
-        }
-
-        MusicUtils.unbindFromService(mToken);
-        try {
-            if ("nowplaying".equals(mPlaylist)) {
-                unregisterReceiverSafe(mNowPlayingListener);
-            } else {
-                unregisterReceiverSafe(mTrackListListener);
-            }
-        } catch (IllegalArgumentException ex) {
-            // we end up here in case we never registered the listeners
-        }
-
-        // If we have an adapter and didn't send it off to another activity yet, we should
-        // close its cursor, which we do by assigning a null cursor to it. Doing this
-        // instead of closing the cursor directly keeps the framework from accessing
-        // the closed cursor later.
-        if (!mAdapterSent && mAdapter != null) {
-            mAdapter.changeCursor(null);
-        }
         // Because we pass the adapter to the next activity, we need to make
         // sure it doesn't keep a reference to this activity. We can do this
         // by clearing its DatasetObservers, which setListAdapter(null) does.
         setListAdapter(null);
-        mAdapter = null;
-        unregisterReceiverSafe(mScanListener);
+        mBrowseListAdapter = null;
         super.onDestroy();
     }
 
-    /**
-     * Unregister a receiver, but eat the exception that is thrown if the
-     * receiver was never registered to begin with. This is a little easier
-     * than keeping track of whether the receivers have actually been
-     * registered by the time onDestroy() is called.
-     */
-    private void unregisterReceiverSafe(BroadcastReceiver receiver) {
-        try {
-            unregisterReceiver(receiver);
-        } catch (IllegalArgumentException e) {
-            // ignore
-        }
-    }
-
     @Override
     public void onResume() {
+        Log.d(TAG, "onResume()");
         super.onResume();
-        if (mTrackCursor != null) {
-            getListView().invalidateViews();
-        }
-        MusicUtils.setSpinnerState(this);
     }
+
     @Override
     public void onPause() {
-        mReScanHandler.removeCallbacksAndMessages(null);
+        Log.d(TAG, "onPause()");
         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) {
-            if (mAdapter != null) {
-                getTrackCursor(mAdapter.getQueryHandler(), null, true);
-            }
-            // 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
     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);
+        outcicle.putParcelable(MusicUtils.TAG_PARENT_ITEM, mParentItem);
+        outcicle.putBoolean(MusicUtils.TAG_WITH_TABS, mWithTabs);
         super.onSaveInstanceState(outcicle);
     }
 
-    public void init(Cursor newCursor, boolean isLimited) {
-        if (mAdapter == null) {
-            return;
-        }
-        mAdapter.changeCursor(newCursor); // also sets mTrackCursor
+    private MediaBrowser.SubscriptionCallback mSubscriptionCallback =
+            new MediaBrowser.SubscriptionCallback() {
 
-        if (mTrackCursor == null) {
-            MusicUtils.displayDatabaseError(this);
-            closeContextMenu();
-            mReScanHandler.sendEmptyMessageDelayed(0, 1000);
-            return;
-        }
-
-        MusicUtils.hideDatabaseError(this);
-        mUseLastListPos = MusicUtils.updateButtonBar(this, R.id.songtab);
-        setTitle();
-
-        // Restore previous position
-        if (mLastListPosCourse >= 0 && mUseLastListPos) {
-            ListView lv = getListView();
-            // this hack is needed because otherwise the position doesn't change
-            // for the 2nd (non-limited) cursor
-            lv.setAdapter(lv.getAdapter());
-            lv.setSelectionFromTop(mLastListPosCourse, mLastListPosFine);
-            if (!isLimited) {
-                mLastListPosCourse = -1;
-            }
-        }
-
-        // 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;
+                @Override
+                public void onChildrenLoaded(
+                        String parentId, List<MediaBrowser.MediaItem> children) {
+                    mBrowseListAdapter.clear();
+                    mBrowseListAdapter.notifyDataSetInvalidated();
+                    for (MediaBrowser.MediaItem item : children) {
+                        mBrowseListAdapter.add(item);
                     }
-                    mTrackCursor.moveToNext();
+                    mBrowseListAdapter.notifyDataSetChanged();
                 }
-            }
-            registerReceiver(mTrackListListener, new IntentFilter(f));
-            mTrackListListener.onReceive(this, new Intent(MediaPlaybackService.META_CHANGED));
-        }
-    }
 
-    private void setAlbumArtBackground() {
-        if (!mEditMode) {
-            try {
-                long albumid = Long.valueOf(mAlbumId);
-                Bitmap bm = MusicUtils.getArtwork(TrackBrowserActivity.this, -1, albumid, false);
-                if (bm != null) {
-                    MusicUtils.setBackground(mTrackList, bm);
-                    mTrackList.setCacheColorHint(0);
-                    return;
-                }
-            } catch (Exception ex) {
-            }
-        }
-        mTrackList.setBackgroundColor(0xff000000);
-        mTrackList.setCacheColorHint(0);
-    }
-
-    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 == null || fancyName.equals(MediaStore.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.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
-                MediaStore.Audio.Playlists.Members.moveItem(
-                        getContentResolver(), Long.valueOf(mPlaylist), from, to);
-            }
-        }
-    };
-
-    private TouchInterceptor.RemoveListener mRemoveListener =
-            new TouchInterceptor.RemoveListener() {
-                public void remove(int which) {
-                    removePlaylistItem(which);
+                @Override
+                public void onError(String id) {
+                    Toast.makeText(getApplicationContext(), R.string.error_loading_media,
+                                 Toast.LENGTH_LONG)
+                            .show();
                 }
             };
 
-    private void removePlaylistItem(int which) {
-        View v = mTrackList.getChildAt(which - mTrackList.getFirstVisiblePosition());
-        if (v == null) {
-            Log.d(LOGTAG, "No view when removing playlist item " + which);
-            return;
-        }
-        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 MediaBrowser.ConnectionCallback mConnectionCallback =
+            new MediaBrowser.ConnectionCallback() {
+                @Override
+                public void onConnected() {
+                    Log.d(TAG, "onConnected: session token " + mMediaBrowser.getSessionToken());
+                    mMediaBrowser.subscribe(mParentItem.getMediaId(), mSubscriptionCallback);
+                    if (mMediaBrowser.getSessionToken() == null) {
+                        throw new IllegalArgumentException("No Session token");
+                    }
+                    MediaController mediaController = new MediaController(
+                            TrackBrowserActivity.this, mMediaBrowser.getSessionToken());
+                    mediaController.registerCallback(mMediaControllerCallback);
+                    TrackBrowserActivity.this.setMediaController(mediaController);
+                    if (mediaController.getMetadata() != null && mWithTabs) {
+                        MusicUtils.updateNowPlaying(TrackBrowserActivity.this);
+                    }
+                }
 
-    private BroadcastReceiver mTrackListListener = new BroadcastReceiver() {
+                @Override
+                public void onConnectionFailed() {
+                    Log.d(TAG, "onConnectionFailed");
+                }
+
+                @Override
+                public void onConnectionSuspended() {
+                    Log.d(TAG, "onConnectionSuspended");
+                    TrackBrowserActivity.this.setMediaController(null);
+                }
+            };
+
+    private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() {
         @Override
-        public void onReceive(Context context, Intent intent) {
-            getListView().invalidateViews();
-            if (!mEditMode) {
+        public void onMetadataChanged(MediaMetadata metadata) {
+            super.onMetadataChanged(metadata);
+            if (mWithTabs) {
                 MusicUtils.updateNowPlaying(TrackBrowserActivity.this);
             }
-        }
-    };
-
-    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;
-                }
-                // The service could disappear while the broadcast was in flight,
-                // so check to see if it's still valid
-                if (MusicUtils.sService == null) {
-                    finish();
-                    return;
-                }
-                if (mAdapter != null) {
-                    Cursor c = new NowPlayingCursor(MusicUtils.sService, mCursorCols);
-                    if (c.getCount() == 0) {
-                        finish();
-                        return;
-                    }
-                    mAdapter.changeCursor(c);
-                }
+            if (mBrowseListAdapter != null) {
+                mBrowseListAdapter.notifyDataSetChanged();
             }
         }
     };
 
-    // Cursor should be positioned on the entry to be checked
-    // Returns false if the entry matches the naming pattern used for recordings,
-    // or if it is marked as not music in the database.
-    private boolean isMusic(Cursor c) {
-        int titleidx = c.getColumnIndex(MediaStore.Audio.Media.TITLE);
-        int albumidx = c.getColumnIndex(MediaStore.Audio.Media.ALBUM);
-        int artistidx = c.getColumnIndex(MediaStore.Audio.Media.ARTIST);
-
-        String title = c.getString(titleidx);
-        String album = c.getString(albumidx);
-        String artist = c.getString(artistidx);
-        if (MediaStore.UNKNOWN_STRING.equals(album) && MediaStore.UNKNOWN_STRING.equals(artist)
-                && title != null && title.startsWith("recording")) {
-            // not music
-            return false;
-        }
-
-        int ismusic_idx = c.getColumnIndex(MediaStore.Audio.Media.IS_MUSIC);
-        boolean ismusic = true;
-        if (ismusic_idx >= 0) {
-            ismusic = mTrackCursor.getInt(ismusic_idx) != 0;
-        }
-        return ismusic;
-    }
-
-    @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);
-        AdapterContextMenuInfo mi = (AdapterContextMenuInfo) menuInfoIn;
-        mSelectedPosition = mi.position;
-        mTrackCursor.moveToPosition(mSelectedPosition);
-        try {
-            int id_idx =
-                    mTrackCursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.Members.AUDIO_ID);
-            mSelectedId = mTrackCursor.getLong(id_idx);
-        } catch (IllegalArgumentException ex) {
-            mSelectedId = mi.id;
-        }
-        // only add the 'search' menu if the selected item is music
-        if (isMusic(mTrackCursor)) {
-            menu.add(0, SEARCH, 0, R.string.search_title);
-        }
-        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: {
-                long[] list = new long[] {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: {
-                long[] list = new long[] {mSelectedId};
-                long playlist = item.getIntent().getLongExtra("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: {
-                long[] list = new long[1];
-                list[0] = (int) mSelectedId;
-                Bundle b = new Bundle();
-                String f;
-                if (android.os.Environment.isExternalStorageRemovable()) {
-                    f = getString(R.string.delete_song_desc);
-                } else {
-                    f = getString(R.string.delete_song_desc_nosdcard);
-                }
-                String desc = String.format(f, mCurrentTrackName);
-                b.putString("description", desc);
-                b.putLongArray("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);
-        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
-        title = mCurrentTrackName;
-        if (MediaStore.UNKNOWN_STRING.equals(mCurrentArtistNameForAlbum)) {
-            query = mCurrentTrackName;
-        } else {
-            query = mCurrentArtistNameForAlbum + " " + mCurrentTrackName;
-            i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistNameForAlbum);
-        }
-        if (MediaStore.UNKNOWN_STRING.equals(mCurrentAlbumName)) {
-            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) {
-        int curpos = mTrackList.getSelectedItemPosition();
-        if (mPlaylist != null && !mPlaylist.equals("recentlyadded") && curpos >= 0
-                && 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;
-        }
-        // When selecting a track from the queue, just jump there instead of
-        // reloading the queue. This is both faster, and prevents accidentally
-        // dropping out of party shuffle.
-        if (mTrackCursor instanceof NowPlayingCursor) {
-            if (MusicUtils.sService != null) {
-                try {
-                    MusicUtils.sService.setQueuePosition(position);
-                    return;
-                } catch (RemoteException ex) {
-                }
-            }
-        }
-        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(R.drawable.ic_menu_play_clip);
-        }
-        menu.add(0, PARTY_SHUFFLE, 0,
-                R.string.party_shuffle); // icon will be set in onPrepareOptionsMenu()
-        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(R.drawable.ic_menu_clear_playlist);
-            }
-        }
-        return true;
-    }
-
-    @Override
-    public boolean onPrepareOptionsMenu(Menu menu) {
-        MusicUtils.setPartyShuffleMenuIcon(menu);
-        return super.onPrepareOptionsMenu(menu);
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        Intent intent;
-        Cursor cursor;
-        switch (item.getItemId()) {
-            case PLAY_ALL: {
-                MusicUtils.playAll(this, mTrackCursor);
-                return true;
-            }
-
-            case PARTY_SHUFFLE:
-                MusicUtils.togglePartyShuffle();
-                break;
-
-            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, true);
-                }
-                break;
-
-            case NEW_PLAYLIST:
-                if (resultCode == RESULT_OK) {
-                    Uri uri = intent.getData();
-                    if (uri != null) {
-                        long[] list = new long[] {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) {
-                        long[] list = MusicUtils.getSongListForCursor(mTrackCursor);
-                        int plid = Integer.parseInt(uri.getLastPathSegment());
-                        MusicUtils.addToPlaylist(this, list, plid);
-                    }
-                }
-                break;
-        }
-    }
-
-    private Cursor getTrackCursor(
-            TrackListAdapter.TrackQueryHandler queryhandler, String filter, boolean async) {
-        if (queryhandler == null) {
-            throw new IllegalArgumentException();
-        }
-
-        Cursor ret = null;
-        mSortOrder = MediaStore.Audio.Media.TITLE_KEY;
-        StringBuilder where = new StringBuilder();
-        where.append(MediaStore.Audio.Media.TITLE + " != ''");
-
-        if (mGenre != null) {
-            Uri uri = MediaStore.Audio.Genres.Members.getContentUri(
-                    "external", Integer.valueOf(mGenre));
-            if (!TextUtils.isEmpty(filter)) {
-                uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filter)).build();
-            }
-            mSortOrder = MediaStore.Audio.Genres.Members.DEFAULT_SORT_ORDER;
-            ret = queryhandler.doQuery(uri, mCursorCols, where.toString(), null, mSortOrder, async);
-        } 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");
-                Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
-                if (!TextUtils.isEmpty(filter)) {
-                    uri = uri.buildUpon()
-                                  .appendQueryParameter("filter", Uri.encode(filter))
-                                  .build();
-                }
-                ret = queryhandler.doQuery(uri, mCursorCols, where.toString(), null,
-                        MediaStore.Audio.Media.DEFAULT_SORT_ORDER, async);
-            } else if (mPlaylist.equals("recentlyadded")) {
-                // do a query for all songs added in the last X weeks
-                Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
-                if (!TextUtils.isEmpty(filter)) {
-                    uri = uri.buildUpon()
-                                  .appendQueryParameter("filter", Uri.encode(filter))
-                                  .build();
-                }
-                int X = MusicUtils.getIntPref(this, "numweeks", 2) * (3600 * 24 * 7);
-                where.append(" AND " + MediaStore.MediaColumns.DATE_ADDED + ">");
-                where.append(System.currentTimeMillis() / 1000 - X);
-                ret = queryhandler.doQuery(uri, mCursorCols, where.toString(), null,
-                        MediaStore.Audio.Media.DEFAULT_SORT_ORDER, async);
-            } else {
-                Uri uri = MediaStore.Audio.Playlists.Members.getContentUri(
-                        "external", Long.valueOf(mPlaylist));
-                if (!TextUtils.isEmpty(filter)) {
-                    uri = uri.buildUpon()
-                                  .appendQueryParameter("filter", Uri.encode(filter))
-                                  .build();
-                }
-                mSortOrder = MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER;
-                ret = queryhandler.doQuery(
-                        uri, mPlaylistMemberCols, where.toString(), null, mSortOrder, async);
-            }
-        } 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");
-            Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
-            if (!TextUtils.isEmpty(filter)) {
-                uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filter)).build();
-            }
-            ret = queryhandler.doQuery(uri, mCursorCols, where.toString(), null, mSortOrder, async);
-        }
-
-        // 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) {
-            init(ret, false);
-            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 long[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 long[size];
-            mCurrentPlaylistCursor.moveToFirst();
-            int colidx = mCurrentPlaylistCursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);
-            for (int i = 0; i < size; i++) {
-                mCursorIdxs[i] = mCurrentPlaylistCursor.getLong(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--) {
-                    long 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 long[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 || newPosition >= mNowPlaying.length) {
-                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.
-
-            long 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 int getType(int column) {
-            return mCurrentPlaylistCursor.getType(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 long[] mNowPlaying;
-        private long[] mCursorIdxs;
-        private int mCurPos;
-        private IMediaPlaybackService mService;
-    }
-
-    static class TrackListAdapter extends SimpleCursorAdapter implements SectionIndexer {
-        boolean mIsNowPlaying;
-        boolean mDisableNowPlayingIndicator;
-
-        int mTitleIdx;
-        int mArtistIdx;
-        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 TrackQueryHandler mQueryHandler;
-        private String mConstraint = null;
-        private boolean mConstraintIsValid = false;
+    // An adapter for showing the list of browsed MediaItem's
+    private static class TrackBrowseAdapter extends ArrayAdapter<MediaBrowser.MediaItem> {
+        private int mLayoutId;
+        private final Drawable mNowPlayingOverlay;
+        private Activity mActivity;
 
         static class ViewHolder {
             TextView line1;
             TextView line2;
             TextView duration;
             ImageView play_indicator;
-            CharArrayBuffer buffer1;
-            char[] buffer2;
         }
 
-        class TrackQueryHandler extends AsyncQueryHandler {
-            class QueryArgs {
-                public Uri uri;
-                public String[] projection;
-                public String selection;
-                public String[] selectionArgs;
-                public String orderBy;
-            }
-
-            TrackQueryHandler(ContentResolver res) {
-                super(res);
-            }
-
-            public Cursor doQuery(Uri uri, String[] projection, String selection,
-                    String[] selectionArgs, String orderBy, boolean async) {
-                if (async) {
-                    // Get 100 results first, which is enough to allow the user to start scrolling,
-                    // while still being very fast.
-                    Uri limituri = uri.buildUpon().appendQueryParameter("limit", "100").build();
-                    QueryArgs args = new QueryArgs();
-                    args.uri = uri;
-                    args.projection = projection;
-                    args.selection = selection;
-                    args.selectionArgs = selectionArgs;
-                    args.orderBy = orderBy;
-
-                    startQuery(0, args, limituri, projection, selection, selectionArgs, orderBy);
-                    return null;
-                }
-                return MusicUtils.query(
-                        mActivity, uri, projection, selection, selectionArgs, orderBy);
-            }
-
-            @Override
-            protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
-                // Log.i("@@@", "query complete: " + cursor.getCount() + "   " + mActivity);
-                mActivity.init(cursor, cookie != null);
-                if (token == 0 && cookie != null && cursor != null && !cursor.isClosed()
-                        && cursor.getCount() >= 100) {
-                    QueryArgs args = (QueryArgs) cookie;
-                    startQuery(1, null, args.uri, args.projection, args.selection,
-                            args.selectionArgs, args.orderBy);
-                }
-            }
-        }
-
-        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 TrackQueryHandler(context.getContentResolver());
-        }
-
-        public void setActivity(TrackBrowserActivity newactivity) {
-            mActivity = newactivity;
-        }
-
-        public TrackQueryHandler 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);
-                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 && mActivity.mAlbumId == null) {
-                    String alpha = mActivity.getString(R.string.fast_scroll_alphabet);
-
-                    mIndexer = new MusicAlphabetIndexer(cursor, mTitleIdx, alpha);
-                }
-            }
+        TrackBrowseAdapter(Activity activity, int layout) {
+            super(activity, layout, new ArrayList<>());
+            mLayoutId = layout;
+            mNowPlayingOverlay = activity.getResources().getDrawable(
+                    R.drawable.indicator_ic_mp_playing_list, activity.getTheme());
+            mActivity = activity;
         }
 
         @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.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(MediaStore.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;
-            long 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.getLong(mAudioIdIdx) == id)) {
-                iv.setImageResource(R.drawable.indicator_ic_mp_playing_list);
-                iv.setVisibility(View.VISIBLE);
-            } else {
+        public View getView(int position, View convertView, ViewGroup parent) {
+            Log.d(TAG, "getView()");
+            if (convertView == null) {
+                convertView = LayoutInflater.from(getContext()).inflate(mLayoutId, parent, false);
+                ImageView iv = (ImageView) convertView.findViewById(R.id.icon);
                 iv.setVisibility(View.GONE);
+                ViewHolder vh = new ViewHolder();
+                vh.line1 = (TextView) convertView.findViewById(R.id.line1);
+                vh.line2 = (TextView) convertView.findViewById(R.id.line2);
+                vh.duration = (TextView) convertView.findViewById(R.id.duration);
+                vh.play_indicator = (ImageView) convertView.findViewById(R.id.play_indicator);
+                convertView.setTag(vh);
             }
-        }
-
-        @Override
-        public void changeCursor(Cursor cursor) {
-            if (mActivity.isFinishing() && cursor != null) {
-                cursor.close();
-                cursor = null;
+            ViewHolder holder = (ViewHolder) convertView.getTag();
+            MediaBrowser.MediaItem item = getItem(position);
+            Log.d(TAG, "title: " + item.getDescription().getTitle());
+            holder.line1.setText(item.getDescription().getTitle());
+            Log.d(TAG, "artist: " + item.getDescription().getSubtitle());
+            holder.line2.setText(item.getDescription().getSubtitle());
+            long duration =
+                    item.getDescription().getExtras().getLong(MediaMetadata.METADATA_KEY_DURATION);
+            LogHelper.d(TAG, "duration: ", duration);
+            holder.duration.setText(MusicUtils.makeTimeString(getContext(), duration / 1000));
+            MediaController mediaController = mActivity.getMediaController();
+            if (mediaController == null) {
+                holder.play_indicator.setImageDrawable(null);
+                return convertView;
             }
-            if (cursor != mActivity.mTrackCursor) {
-                mActivity.mTrackCursor = cursor;
-                super.changeCursor(cursor);
-                getColumnIndices(cursor);
+            MediaMetadata metadata = mediaController.getMetadata();
+            if (metadata == null) {
+                holder.play_indicator.setImageDrawable(null);
+                return convertView;
             }
-        }
-
-        @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(mQueryHandler, s, false);
-            mConstraint = s;
-            mConstraintIsValid = true;
-            return c;
-        }
-
-        // SectionIndexer methods
-
-        public Object[] getSections() {
-            if (mIndexer != null) {
-                return mIndexer.getSections();
+            if (item.getDescription().getMediaId().endsWith(
+                        metadata.getString(MediaMetadata.METADATA_KEY_MEDIA_ID))) {
+                holder.play_indicator.setImageDrawable(mNowPlayingOverlay);
             } else {
-                return new String[] {" "};
+                holder.play_indicator.setImageDrawable(null);
             }
+            return convertView;
         }
+    }
 
-        public int getPositionForSection(int section) {
-            if (mIndexer != null) {
-                return mIndexer.getPositionForSection(section);
-            }
-            return 0;
-        }
+    @Override
+    public Object onRetainNonConfigurationInstance() {
+        TrackBrowseAdapter a = mBrowseListAdapter;
+        return a;
+    }
 
-        public int getSectionForPosition(int position) {
-            return 0;
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id) {
+        Log.d(TAG, "onListItemClick at position " + position + ", id " + id);
+        MediaBrowser.MediaItem item = mBrowseListAdapter.getItem(position);
+        if (item.isPlayable()) {
+            getMediaController().getTransportControls().playFromMediaId(item.getMediaId(), null);
         }
     }
 }
diff --git a/src/com/android/music/VideoBrowserActivity.java b/src/com/android/music/VideoBrowserActivity.java
deleted file mode 100644
index 16abe72..0000000
--- a/src/com/android/music/VideoBrowserActivity.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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);
-        }
-    }
-
-    @Override
-    public void onDestroy() {
-        if (mCursor != null) {
-            mCursor.close();
-        }
-        super.onDestroy();
-    }
-
-    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
deleted file mode 100644
index cfe90a3..0000000
--- a/src/com/android/music/WeekSelector.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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.media.AudioManager;
-import android.os.Bundle;
-import android.view.View;
-import android.view.Window;
-import android.view.WindowManager;
-import android.widget.Button;
-
-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.MATCH_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/src/com/android/music/utils/AlbumArtCache.java b/src/com/android/music/utils/AlbumArtCache.java
new file mode 100644
index 0000000..7021021
--- /dev/null
+++ b/src/com/android/music/utils/AlbumArtCache.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2014 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.utils;
+
+import android.graphics.Bitmap;
+import android.os.AsyncTask;
+import android.util.LruCache;
+
+import java.io.IOException;
+
+/**
+ * Implements a basic cache of album arts, with async loading support.
+ */
+public final class AlbumArtCache {
+    private static final String TAG = LogHelper.makeLogTag(AlbumArtCache.class);
+
+    private static final int MAX_ALBUM_ART_CACHE_SIZE = 12 * 1024 * 1024; // 12 MB
+    private static final int MAX_ART_WIDTH = 800; // pixels
+    private static final int MAX_ART_HEIGHT = 480; // pixels
+
+    // Resolution reasonable for carrying around as an icon (generally in
+    // MediaDescription.getIconBitmap). This should not be bigger than necessary, because
+    // the MediaDescription object should be lightweight. If you set it too high and try to
+    // serialize the MediaDescription, you may get FAILED BINDER TRANSACTION errors.
+    private static final int MAX_ART_WIDTH_ICON = 128; // pixels
+    private static final int MAX_ART_HEIGHT_ICON = 128; // pixels
+
+    private static final int BIG_BITMAP_INDEX = 0;
+    private static final int ICON_BITMAP_INDEX = 1;
+
+    private final LruCache<String, Bitmap[]> mCache;
+
+    private static final AlbumArtCache sInstance = new AlbumArtCache();
+
+    public static AlbumArtCache getInstance() {
+        return sInstance;
+    }
+
+    private AlbumArtCache() {
+        // Holds no more than MAX_ALBUM_ART_CACHE_SIZE bytes, bounded by maxmemory/4 and
+        // Integer.MAX_VALUE:
+        int maxSize = Math.min(MAX_ALBUM_ART_CACHE_SIZE,
+                (int) (Math.min(Integer.MAX_VALUE, Runtime.getRuntime().maxMemory() / 4)));
+        mCache = new LruCache<String, Bitmap[]>(maxSize) {
+            @Override
+            protected int sizeOf(String key, Bitmap[] value) {
+                return value[BIG_BITMAP_INDEX].getByteCount()
+                        + value[ICON_BITMAP_INDEX].getByteCount();
+            }
+        };
+    }
+
+    public Bitmap getBigImage(String artUrl) {
+        Bitmap[] result = mCache.get(artUrl);
+        return result == null ? null : result[BIG_BITMAP_INDEX];
+    }
+
+    public Bitmap getIconImage(String artUrl) {
+        Bitmap[] result = mCache.get(artUrl);
+        return result == null ? null : result[ICON_BITMAP_INDEX];
+    }
+
+    public void fetch(final String artUrl, final FetchListener listener) {
+        // WARNING: for the sake of simplicity, simultaneous multi-thread fetch requests
+        // are not handled properly: they may cause redundant costly operations, like HTTP
+        // requests and bitmap rescales. For production-level apps, we recommend you use
+        // a proper image loading library, like Glide.
+        Bitmap[] bitmap = mCache.get(artUrl);
+        if (bitmap != null) {
+            LogHelper.d(TAG, "getOrFetch: album art is in cache, using it", artUrl);
+            listener.onFetched(artUrl, bitmap[BIG_BITMAP_INDEX], bitmap[ICON_BITMAP_INDEX]);
+            return;
+        }
+        LogHelper.d(TAG, "getOrFetch: starting asynctask to fetch ", artUrl);
+
+        new AsyncTask<Void, Void, Bitmap[]>() {
+            @Override
+            protected Bitmap[] doInBackground(Void[] objects) {
+                Bitmap[] bitmaps;
+                try {
+                    Bitmap bitmap = BitmapHelper.fetchAndRescaleBitmap(
+                            artUrl, MAX_ART_WIDTH, MAX_ART_HEIGHT);
+                    Bitmap icon = BitmapHelper.scaleBitmap(
+                            bitmap, MAX_ART_WIDTH_ICON, MAX_ART_HEIGHT_ICON);
+                    bitmaps = new Bitmap[] {bitmap, icon};
+                    mCache.put(artUrl, bitmaps);
+                } catch (IOException e) {
+                    return null;
+                }
+                LogHelper.d(TAG,
+                        "doInBackground: putting bitmap in cache. cache size=" + mCache.size());
+                return bitmaps;
+            }
+
+            @Override
+            protected void onPostExecute(Bitmap[] bitmaps) {
+                if (bitmaps == null) {
+                    listener.onError(artUrl, new IllegalArgumentException("got null bitmaps"));
+                } else {
+                    listener.onFetched(
+                            artUrl, bitmaps[BIG_BITMAP_INDEX], bitmaps[ICON_BITMAP_INDEX]);
+                }
+            }
+        }
+                .execute();
+    }
+
+    public static abstract class FetchListener {
+        public abstract void onFetched(String artUrl, Bitmap bigImage, Bitmap iconImage);
+        public void onError(String artUrl, Exception e) {
+            LogHelper.e(TAG, e, "AlbumArtFetchListener: error while downloading " + artUrl);
+        }
+    }
+}
diff --git a/src/com/android/music/utils/BitmapHelper.java b/src/com/android/music/utils/BitmapHelper.java
new file mode 100644
index 0000000..5746233
--- /dev/null
+++ b/src/com/android/music/utils/BitmapHelper.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2014 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.utils;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+public class BitmapHelper {
+    private static final String TAG = LogHelper.makeLogTag(BitmapHelper.class);
+
+    // Max read limit that we allow our input stream to mark/reset.
+    private static final int MAX_READ_LIMIT_PER_IMG = 1024 * 1024;
+
+    public static Bitmap scaleBitmap(Bitmap src, int maxWidth, int maxHeight) {
+        double scaleFactor = Math.min(
+                ((double) maxWidth) / src.getWidth(), ((double) maxHeight) / src.getHeight());
+        return Bitmap.createScaledBitmap(src, (int) (src.getWidth() * scaleFactor),
+                (int) (src.getHeight() * scaleFactor), false);
+    }
+
+    public static Bitmap scaleBitmap(int scaleFactor, InputStream is) {
+        // Get the dimensions of the bitmap
+        BitmapFactory.Options bmOptions = new BitmapFactory.Options();
+
+        // Decode the image file into a Bitmap sized to fill the View
+        bmOptions.inJustDecodeBounds = false;
+        bmOptions.inSampleSize = scaleFactor;
+
+        return BitmapFactory.decodeStream(is, null, bmOptions);
+    }
+
+    public static int findScaleFactor(int targetW, int targetH, InputStream is) {
+        // Get the dimensions of the bitmap
+        BitmapFactory.Options bmOptions = new BitmapFactory.Options();
+        bmOptions.inJustDecodeBounds = true;
+        BitmapFactory.decodeStream(is, null, bmOptions);
+        int actualW = bmOptions.outWidth;
+        int actualH = bmOptions.outHeight;
+
+        // Determine how much to scale down the image
+        return Math.min(actualW / targetW, actualH / targetH);
+    }
+
+    @SuppressWarnings("SameParameterValue")
+    public static Bitmap fetchAndRescaleBitmap(String uri, int width, int height)
+            throws IOException {
+        URL url = new URL(uri);
+        BufferedInputStream is = null;
+        try {
+            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
+            is = new BufferedInputStream(urlConnection.getInputStream());
+            is.mark(MAX_READ_LIMIT_PER_IMG);
+            int scaleFactor = findScaleFactor(width, height, is);
+            LogHelper.d(TAG, "Scaling bitmap ", uri, " by factor ", scaleFactor, " to support ",
+                    width, "x", height, "requested dimension");
+            is.reset();
+            return scaleBitmap(scaleFactor, is);
+        } finally {
+            if (is != null) {
+                is.close();
+            }
+        }
+    }
+}
diff --git a/src/com/android/music/CheckableRelativeLayout.java b/src/com/android/music/utils/CheckableRelativeLayout.java
similarity index 98%
rename from src/com/android/music/CheckableRelativeLayout.java
rename to src/com/android/music/utils/CheckableRelativeLayout.java
index b2eccea..125fecb 100644
--- a/src/com/android/music/CheckableRelativeLayout.java
+++ b/src/com/android/music/utils/CheckableRelativeLayout.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.music;
+package com.android.music.utils;
 
 import android.content.Context;
 import android.util.AttributeSet;
diff --git a/src/com/android/music/utils/LogHelper.java b/src/com/android/music/utils/LogHelper.java
new file mode 100644
index 0000000..3a0d2ee
--- /dev/null
+++ b/src/com/android/music/utils/LogHelper.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2014 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.utils;
+
+import android.util.Log;
+
+public class LogHelper {
+    private static final String LOG_PREFIX = "music_";
+    private static final int LOG_PREFIX_LENGTH = LOG_PREFIX.length();
+    private static final int MAX_LOG_TAG_LENGTH = 23;
+    private static final boolean DEBUG = true;
+
+    public static String makeLogTag(String str) {
+        if (str.length() > MAX_LOG_TAG_LENGTH - LOG_PREFIX_LENGTH) {
+            return LOG_PREFIX + str.substring(0, MAX_LOG_TAG_LENGTH - LOG_PREFIX_LENGTH - 1);
+        }
+
+        return LOG_PREFIX + str;
+    }
+
+    /**
+     * Don't use this when obfuscating class names!
+     */
+    public static String makeLogTag(Class cls) {
+        return makeLogTag(cls.getSimpleName());
+    }
+
+    public static void v(String tag, Object... messages) {
+        // Only log VERBOSE if build type is DEBUG
+        if (DEBUG) {
+            log(tag, Log.VERBOSE, null, messages);
+        }
+    }
+
+    public static void d(String tag, Object... messages) {
+        // Only log DEBUG if build type is DEBUG
+        if (DEBUG) {
+            log(tag, Log.DEBUG, null, messages);
+        }
+    }
+
+    public static void i(String tag, Object... messages) {
+        log(tag, Log.INFO, null, messages);
+    }
+
+    public static void w(String tag, Object... messages) {
+        log(tag, Log.WARN, null, messages);
+    }
+
+    public static void w(String tag, Throwable t, Object... messages) {
+        log(tag, Log.WARN, t, messages);
+    }
+
+    public static void e(String tag, Object... messages) {
+        log(tag, Log.ERROR, null, messages);
+    }
+
+    public static void e(String tag, Throwable t, Object... messages) {
+        log(tag, Log.ERROR, t, messages);
+    }
+
+    public static void log(String tag, int level, Throwable t, Object... messages) {
+        String message;
+        if (t == null && messages != null && messages.length == 1) {
+            // handle this common case without the extra cost of creating a stringbuffer:
+            message = messages[0].toString();
+        } else {
+            StringBuilder sb = new StringBuilder();
+            if (messages != null)
+                for (Object m : messages) {
+                    sb.append(m);
+                }
+            if (t != null) {
+                sb.append("\n").append(Log.getStackTraceString(t));
+            }
+            message = sb.toString();
+        }
+        Log.println(level, tag, message);
+    }
+}
diff --git a/src/com/android/music/utils/MediaIDHelper.java b/src/com/android/music/utils/MediaIDHelper.java
new file mode 100644
index 0000000..e991e4a
--- /dev/null
+++ b/src/com/android/music/utils/MediaIDHelper.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2014 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.utils;
+
+import java.util.Arrays;
+
+/**
+ * Utility class to help on queue related tasks.
+ */
+public class MediaIDHelper {
+    private static final String TAG = "MediaIDHelper";
+
+    // Media IDs used on browseable items of MediaBrowser
+    public static final String MEDIA_ID_ROOT = "__ROOT__";
+    public static final String MEDIA_ID_MUSICS_BY_ARTIST = "__BY_ARTIST__";
+    public static final String MEDIA_ID_MUSICS_BY_ALBUM = "__BY_ALBUM__";
+    public static final String MEDIA_ID_MUSICS_BY_SONG = "__BY_SONG__";
+    public static final String MEDIA_ID_MUSICS_BY_PLAYLIST = "__BY_PLAYLIST__";
+    public static final String MEDIA_ID_MUSICS_BY_SEARCH = "__BY_SEARCH__";
+    public static final String MEDIA_ID_NOW_PLAYING = "__NOW_PLAYING__";
+
+    private static final char CATEGORY_SEPARATOR = 31;
+    private static final char LEAF_SEPARATOR = 30;
+
+    public static String createMediaID(String musicID, String... categories) {
+        // MediaIDs are of the form <categoryType>/<categoryValue>|<musicUniqueId>, to make it easy
+        // to find the category (like genre) that a music was selected from, so we
+        // can correctly build the playing queue. This is specially useful when
+        // one music can appear in more than one list, like "by genre -> genre_1"
+        // and "by artist -> artist_1".
+        StringBuilder sb = new StringBuilder();
+        if (categories != null && categories.length > 0) {
+            sb.append(categories[0]);
+            for (int i = 1; i < categories.length; i++) {
+                sb.append(CATEGORY_SEPARATOR).append(categories[i]);
+            }
+        }
+        if (musicID != null) {
+            sb.append(LEAF_SEPARATOR).append(musicID);
+        }
+        return sb.toString();
+    }
+
+    public static String createBrowseCategoryMediaID(String categoryType, String categoryValue) {
+        return categoryType + CATEGORY_SEPARATOR + categoryValue;
+    }
+
+    /**
+     * Extracts unique musicID from the mediaID. mediaID is, by this sample's convention, a
+     * concatenation of category (eg "by_genre"), categoryValue (eg "Classical") and unique
+     * musicID. This is necessary so we know where the user selected the music from, when the music
+     * exists in more than one music list, and thus we are able to correctly build the playing
+     * queue.
+     *
+     * @param mediaID that contains the musicID
+     * @return musicID
+     */
+    public static String extractMusicIDFromMediaID(String mediaID) {
+        int pos = mediaID.indexOf(LEAF_SEPARATOR);
+        if (pos >= 0) {
+            return mediaID.substring(pos + 1);
+        }
+        return null;
+    }
+
+    /**
+     * Extracts category and categoryValue from the mediaID. mediaID is, by this sample's
+     * convention, a concatenation of category (eg "by_genre"), categoryValue (eg "Classical") and
+     * mediaID. This is necessary so we know where the user selected the music from, when the music
+     * exists in more than one music list, and thus we are able to correctly build the playing
+     * queue.
+     *
+     * @param mediaID that contains a category and categoryValue.
+     */
+    public static String[] getHierarchy(String mediaID) {
+        int pos = mediaID.indexOf(LEAF_SEPARATOR);
+        if (pos >= 0) {
+            mediaID = mediaID.substring(0, pos);
+        }
+        return mediaID.split(String.valueOf(CATEGORY_SEPARATOR));
+    }
+
+    public static String extractBrowseCategoryValueFromMediaID(String mediaID) {
+        String[] hierarchy = getHierarchy(mediaID);
+        if (hierarchy != null && hierarchy.length == 2) {
+            return hierarchy[1];
+        }
+        return null;
+    }
+
+    private static boolean isBrowseable(String mediaID) {
+        return mediaID.indexOf(LEAF_SEPARATOR) < 0;
+    }
+
+    public static String getParentMediaID(String mediaID) {
+        String[] hierarchy = getHierarchy(mediaID);
+        if (!isBrowseable(mediaID)) {
+            return createMediaID(null, hierarchy);
+        }
+        if (hierarchy == null || hierarchy.length <= 1) {
+            return MEDIA_ID_ROOT;
+        }
+        String[] parentHierarchy = Arrays.copyOf(hierarchy, hierarchy.length - 1);
+        return createMediaID(null, parentHierarchy);
+    }
+}
diff --git a/src/com/android/music/utils/MediaNotificationManager.java b/src/com/android/music/utils/MediaNotificationManager.java
new file mode 100644
index 0000000..65b9797
--- /dev/null
+++ b/src/com/android/music/utils/MediaNotificationManager.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2014 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.utils;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.service.media.MediaBrowserService;
+import android.util.Log;
+import com.android.music.MediaPlaybackService;
+import com.android.music.R;
+
+/**
+ * Keeps track of a notification and updates it automatically for a given
+ * MediaSession. Maintaining a visible notification (usually) guarantees that the music service
+ * won't be killed during playback.
+ */
+public class MediaNotificationManager extends BroadcastReceiver {
+    private static final String TAG = LogHelper.makeLogTag(MediaNotificationManager.class);
+
+    private static final int NOTIFICATION_ID = 412;
+    private static final int REQUEST_CODE = 100;
+
+    public static final String ACTION_PAUSE = "com.android.music.pause";
+    public static final String ACTION_PLAY = "com.android.music.play";
+    public static final String ACTION_PREV = "com.android.music.prev";
+    public static final String ACTION_NEXT = "com.android.music.next";
+
+    private final MediaPlaybackService mService;
+    private MediaSession.Token mSessionToken;
+    private MediaController mController;
+    private MediaController.TransportControls mTransportControls;
+
+    private PlaybackState mPlaybackState;
+    private MediaMetadata mMetadata;
+
+    private NotificationManager mNotificationManager;
+
+    private PendingIntent mPauseIntent;
+    private PendingIntent mPlayIntent;
+    private PendingIntent mPreviousIntent;
+    private PendingIntent mNextIntent;
+
+    private int mNotificationColor;
+
+    private boolean mStarted = false;
+
+    public MediaNotificationManager(MediaPlaybackService service) {
+        mService = service;
+        updateSessionToken();
+
+        mNotificationColor =
+                ResourceHelper.getThemeColor(mService, android.R.attr.colorPrimary, Color.DKGRAY);
+
+        mNotificationManager =
+                (NotificationManager) mService.getSystemService(Context.NOTIFICATION_SERVICE);
+
+        String pkg = mService.getPackageName();
+        mPauseIntent = PendingIntent.getBroadcast(mService, REQUEST_CODE,
+                new Intent(ACTION_PAUSE).setPackage(pkg), PendingIntent.FLAG_CANCEL_CURRENT);
+        mPlayIntent = PendingIntent.getBroadcast(mService, REQUEST_CODE,
+                new Intent(ACTION_PLAY).setPackage(pkg), PendingIntent.FLAG_CANCEL_CURRENT);
+        mPreviousIntent = PendingIntent.getBroadcast(mService, REQUEST_CODE,
+                new Intent(ACTION_PREV).setPackage(pkg), PendingIntent.FLAG_CANCEL_CURRENT);
+        mNextIntent = PendingIntent.getBroadcast(mService, REQUEST_CODE,
+                new Intent(ACTION_NEXT).setPackage(pkg), PendingIntent.FLAG_CANCEL_CURRENT);
+
+        // Cancel all notifications to handle the case where the Service was killed and
+        // restarted by the system.
+        mNotificationManager.cancelAll();
+    }
+
+    /**
+     * Posts the notification and starts tracking the session to keep it
+     * updated. The notification will automatically be removed if the session is
+     * destroyed before {@link #stopNotification} is called.
+     */
+    public void startNotification() {
+        if (!mStarted) {
+            mMetadata = mController.getMetadata();
+            mPlaybackState = mController.getPlaybackState();
+
+            // The notification must be updated after setting started to true
+            Notification notification = createNotification();
+            if (notification != null) {
+                mController.registerCallback(mCb);
+                IntentFilter filter = new IntentFilter();
+                filter.addAction(ACTION_NEXT);
+                filter.addAction(ACTION_PAUSE);
+                filter.addAction(ACTION_PLAY);
+                filter.addAction(ACTION_PREV);
+                mService.registerReceiver(this, filter);
+
+                mService.startForeground(NOTIFICATION_ID, notification);
+                mStarted = true;
+            }
+        }
+    }
+
+    /**
+     * Removes the notification and stops tracking the session. If the session
+     * was destroyed this has no effect.
+     */
+    public void stopNotification() {
+        if (mStarted) {
+            mStarted = false;
+            mController.unregisterCallback(mCb);
+            try {
+                mNotificationManager.cancel(NOTIFICATION_ID);
+                mService.unregisterReceiver(this);
+            } catch (IllegalArgumentException ex) {
+                // ignore if the receiver is not registered.
+            }
+            mService.stopForeground(true);
+        }
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final String action = intent.getAction();
+        LogHelper.d(TAG, "Received intent with action " + action);
+        switch (action) {
+            case ACTION_PAUSE:
+                mTransportControls.pause();
+                break;
+            case ACTION_PLAY:
+                mTransportControls.play();
+                break;
+            case ACTION_NEXT:
+                mTransportControls.skipToNext();
+                break;
+            case ACTION_PREV:
+                mTransportControls.skipToPrevious();
+                break;
+            default:
+                LogHelper.w(TAG, "Unknown intent ignored. Action=", action);
+        }
+    }
+
+    /**
+     * Update the state based on a change on the session token. Called either when
+     * we are running for the first time or when the media session owner has destroyed the session
+     * (see {@link android.media.session.MediaController.Callback#onSessionDestroyed()})
+     */
+    private void updateSessionToken() {
+        MediaSession.Token freshToken = mService.getSessionToken();
+        if (mSessionToken == null || !mSessionToken.equals(freshToken)) {
+            if (mController != null) {
+                mController.unregisterCallback(mCb);
+            }
+            mSessionToken = freshToken;
+            mController = new MediaController(mService, mSessionToken);
+            mTransportControls = mController.getTransportControls();
+            if (mStarted) {
+                mController.registerCallback(mCb);
+            }
+        }
+    }
+
+    private PendingIntent createContentIntent() {
+        Intent openUI = new Intent(mService, MediaBrowserService.class);
+        openUI.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        return PendingIntent.getActivity(
+                mService, REQUEST_CODE, openUI, PendingIntent.FLAG_CANCEL_CURRENT);
+    }
+
+    private final MediaController.Callback mCb = new MediaController.Callback() {
+        @Override
+        public void onPlaybackStateChanged(PlaybackState state) {
+            mPlaybackState = state;
+            LogHelper.d(TAG, "Received new playback state", state);
+            if (state != null
+                    && (state.getState() == PlaybackState.STATE_STOPPED
+                               || state.getState() == PlaybackState.STATE_NONE)) {
+                stopNotification();
+            } else {
+                Notification notification = createNotification();
+                if (notification != null) {
+                    mNotificationManager.notify(NOTIFICATION_ID, notification);
+                }
+            }
+        }
+
+        @Override
+        public void onMetadataChanged(MediaMetadata metadata) {
+            mMetadata = metadata;
+            LogHelper.d(TAG, "Received new metadata ", metadata);
+            Notification notification = createNotification();
+            if (notification != null) {
+                mNotificationManager.notify(NOTIFICATION_ID, notification);
+            }
+        }
+
+        @Override
+        public void onSessionDestroyed() {
+            super.onSessionDestroyed();
+            LogHelper.d(TAG, "Session was destroyed, resetting to the new session token");
+            updateSessionToken();
+        }
+    };
+
+    private Notification createNotification() {
+        LogHelper.d(TAG, "updateNotificationMetadata. mMetadata=" + mMetadata);
+        if (mMetadata == null || mPlaybackState == null) {
+            return null;
+        }
+
+        Notification.Builder notificationBuilder = new Notification.Builder(mService);
+        int playPauseButtonPosition = 0;
+
+        // If skip to previous action is enabled
+        if ((mPlaybackState.getActions() & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) {
+            notificationBuilder.addAction(R.drawable.ic_skip_previous_white_24dp,
+                    mService.getString(R.string.skip_previous), mPreviousIntent);
+
+            // If there is a "skip to previous" button, the play/pause button will
+            // be the second one. We need to keep track of it, because the MediaStyle notification
+            // requires to specify the index of the buttons (actions) that should be visible
+            // when in compact view.
+            playPauseButtonPosition = 1;
+        }
+
+        addPlayPauseAction(notificationBuilder);
+
+        // If skip to next action is enabled
+        if ((mPlaybackState.getActions() & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
+            notificationBuilder.addAction(R.drawable.ic_skip_next_white_24dp,
+                    mService.getString(R.string.skip_next), mNextIntent);
+        }
+
+        MediaDescription description = mMetadata.getDescription();
+
+        String fetchArtUrl = null;
+        Bitmap art = null;
+        if (description.getIconUri() != null) {
+            // This sample assumes the iconUri will be a valid URL formatted String, but
+            // it can actually be any valid Android Uri formatted String.
+            // async fetch the album art icon
+            String artUrl = description.getIconUri().toString();
+            art = AlbumArtCache.getInstance().getBigImage(artUrl);
+            if (art == null) {
+                fetchArtUrl = artUrl;
+                // use a placeholder art while the remote art is being downloaded
+                art = BitmapFactory.decodeResource(
+                        mService.getResources(), R.drawable.ic_default_art);
+            }
+        }
+
+        notificationBuilder
+                .setStyle(new Notification.MediaStyle()
+                                  .setShowActionsInCompactView(
+                                          playPauseButtonPosition) // show only play/pause in
+                                  // compact view
+                                  .setMediaSession(mSessionToken))
+                .setColor(mNotificationColor)
+                .setSmallIcon(R.drawable.ic_notification)
+                .setVisibility(Notification.VISIBILITY_PUBLIC)
+                .setUsesChronometer(true)
+                .setContentIntent(createContentIntent())
+                .setContentTitle(description.getTitle())
+                .setContentText(description.getSubtitle())
+                .setLargeIcon(art);
+
+        setNotificationPlaybackState(notificationBuilder);
+        if (fetchArtUrl != null) {
+            fetchBitmapFromURLAsync(fetchArtUrl, notificationBuilder);
+        }
+
+        return notificationBuilder.build();
+    }
+
+    private void addPlayPauseAction(Notification.Builder builder) {
+        LogHelper.d(TAG, "updatePlayPauseAction");
+        String label;
+        int icon;
+        PendingIntent intent;
+        if (mPlaybackState.getState() == PlaybackState.STATE_PLAYING) {
+            label = mService.getString(R.string.play_pause);
+            icon = R.drawable.ic_pause_white_24dp;
+            intent = mPauseIntent;
+        } else {
+            label = mService.getString(R.string.play_item);
+            icon = R.drawable.ic_play_arrow_white_24dp;
+            intent = mPlayIntent;
+        }
+        builder.addAction(new Notification.Action(icon, label, intent));
+    }
+
+    private void setNotificationPlaybackState(Notification.Builder builder) {
+        LogHelper.d(TAG, "updateNotificationPlaybackState. mPlaybackState=" + mPlaybackState);
+        if (mPlaybackState == null || !mStarted) {
+            LogHelper.d(TAG, "updateNotificationPlaybackState. cancelling notification!");
+            mService.stopForeground(true);
+            return;
+        }
+        if (mPlaybackState.getState() == PlaybackState.STATE_PLAYING
+                && mPlaybackState.getPosition() >= 0) {
+            LogHelper.d(TAG, "updateNotificationPlaybackState. updating playback position to ",
+                    (System.currentTimeMillis() - mPlaybackState.getPosition()) / 1000, " seconds");
+            builder.setWhen(System.currentTimeMillis() - mPlaybackState.getPosition())
+                    .setShowWhen(true)
+                    .setUsesChronometer(true);
+        } else {
+            LogHelper.d(TAG, "updateNotificationPlaybackState. hiding playback position");
+            builder.setWhen(0).setShowWhen(false).setUsesChronometer(false);
+        }
+
+        // Make sure that the notification can be dismissed by the user when we are not playing:
+        builder.setOngoing(mPlaybackState.getState() == PlaybackState.STATE_PLAYING);
+    }
+
+    private void fetchBitmapFromURLAsync(
+            final String bitmapUrl, final Notification.Builder builder) {
+        AlbumArtCache.getInstance().fetch(bitmapUrl, new AlbumArtCache.FetchListener() {
+            @Override
+            public void onFetched(String artUrl, Bitmap bitmap, Bitmap icon) {
+                if (mMetadata != null && mMetadata.getDescription() != null
+                        && artUrl.equals(mMetadata.getDescription().getIconUri().toString())) {
+                    // If the media is still the same, update the notification:
+                    LogHelper.d(TAG, "fetchBitmapFromURLAsync: set bitmap to ", artUrl);
+                    builder.setLargeIcon(bitmap);
+                    mNotificationManager.notify(NOTIFICATION_ID, builder.build());
+                }
+            }
+        });
+    }
+}
diff --git a/src/com/android/music/utils/MusicProvider.java b/src/com/android/music/utils/MusicProvider.java
new file mode 100644
index 0000000..bda92ea
--- /dev/null
+++ b/src/com/android/music/utils/MusicProvider.java
@@ -0,0 +1,480 @@
+/*
+ * Copyright (C) 2017 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.utils;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.media.MediaActionSound;
+import android.media.MediaMetadata;
+import android.media.MediaMetadataRetriever;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.provider.MediaStore;
+import android.util.Log;
+import com.android.music.MediaPlaybackService;
+import com.android.music.MusicUtils;
+import com.android.music.R;
+
+import java.io.File;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/*
+A provider of music contents to the music application, it reads external storage for any music
+files, parse them and
+store them in this class for future use.
+ */
+public class MusicProvider {
+    private static final String TAG = "MusicProvider";
+
+    // Public constants
+    public static final String UNKOWN = "UNKNOWN";
+    // Uri source of this track
+    public static final String CUSTOM_METADATA_TRACK_SOURCE = "__SOURCE__";
+    // Sort key for this tack
+    public static final String CUSTOM_METADATA_SORT_KEY = "__SORT_KEY__";
+
+    // Content select criteria
+    private static final String MUSIC_SELECT_FILTER = MediaStore.Audio.Media.IS_MUSIC + " != 0";
+    private static final String MUSIC_SORT_ORDER = MediaStore.Audio.Media.TITLE + " ASC";
+
+    // Categorized caches for music track data:
+    private Context mContext;
+    // Album Name --> list of Metadata
+    private ConcurrentMap<String, List<MediaMetadata>> mMusicListByAlbum;
+    // Playlist Name --> list of Metadata
+    private ConcurrentMap<String, List<MediaMetadata>> mMusicListByPlaylist;
+    // Artist Name --> Map of (album name --> album metadata)
+    private ConcurrentMap<String, Map<String, MediaMetadata>> mArtistAlbumDb;
+    private List<MediaMetadata> mMusicList;
+    private final ConcurrentMap<Long, Song> mMusicListById;
+    private final ConcurrentMap<String, Song> mMusicListByMediaId;
+
+    enum State { NON_INITIALIZED, INITIALIZING, INITIALIZED }
+
+    private volatile State mCurrentState = State.NON_INITIALIZED;
+
+    public MusicProvider(Context context) {
+        mContext = context;
+        mArtistAlbumDb = new ConcurrentHashMap<>();
+        mMusicListByAlbum = new ConcurrentHashMap<>();
+        mMusicListByPlaylist = new ConcurrentHashMap<>();
+        mMusicListById = new ConcurrentHashMap<>();
+        mMusicList = new ArrayList<>();
+        mMusicListByMediaId = new ConcurrentHashMap<>();
+        mMusicListByPlaylist.put(MediaIDHelper.MEDIA_ID_NOW_PLAYING, new ArrayList<>());
+    }
+
+    public boolean isInitialized() {
+        return mCurrentState == State.INITIALIZED;
+    }
+
+    /**
+     * Get an iterator over the list of artists
+     *
+     * @return list of artists
+     */
+    public Iterable<String> getArtists() {
+        if (mCurrentState != State.INITIALIZED) {
+            return Collections.emptyList();
+        }
+        return mArtistAlbumDb.keySet();
+    }
+
+    /**
+     * Get an iterator over the list of albums
+     *
+     * @return list of albums
+     */
+    public Iterable<MediaMetadata> getAlbums() {
+        if (mCurrentState != State.INITIALIZED) {
+            return Collections.emptyList();
+        }
+        ArrayList<MediaMetadata> albumList = new ArrayList<>();
+        for (Map<String, MediaMetadata> artist_albums : mArtistAlbumDb.values()) {
+            albumList.addAll(artist_albums.values());
+        }
+        return albumList;
+    }
+
+    /**
+     * Get an iterator over the list of playlists
+     *
+     * @return list of playlists
+     */
+    public Iterable<String> getPlaylists() {
+        if (mCurrentState != State.INITIALIZED) {
+            return Collections.emptyList();
+        }
+        return mMusicListByPlaylist.keySet();
+    }
+
+    public Iterable<MediaMetadata> getMusicList() {
+        return mMusicList;
+    }
+
+    /**
+     * Get albums of a certain artist
+     *
+     */
+    public Iterable<MediaMetadata> getAlbumByArtist(String artist) {
+        if (mCurrentState != State.INITIALIZED || !mArtistAlbumDb.containsKey(artist)) {
+            return Collections.emptyList();
+        }
+        return mArtistAlbumDb.get(artist).values();
+    }
+
+    /**
+     * Get music tracks of the given album
+     *
+     */
+    public Iterable<MediaMetadata> getMusicsByAlbum(String album) {
+        if (mCurrentState != State.INITIALIZED || !mMusicListByAlbum.containsKey(album)) {
+            return Collections.emptyList();
+        }
+        return mMusicListByAlbum.get(album);
+    }
+
+    /**
+     * Get music tracks of the given playlist
+     *
+     */
+    public Iterable<MediaMetadata> getMusicsByPlaylist(String playlist) {
+        if (mCurrentState != State.INITIALIZED || !mMusicListByPlaylist.containsKey(playlist)) {
+            return Collections.emptyList();
+        }
+        return mMusicListByPlaylist.get(playlist);
+    }
+
+    /**
+     * Return the MediaMetadata for the given musicID.
+     *
+     * @param musicId The unique, non-hierarchical music ID.
+     */
+    public Song getMusicById(long musicId) {
+        return mMusicListById.containsKey(musicId) ? mMusicListById.get(musicId) : null;
+    }
+
+    /**
+     * Return the MediaMetadata for the given musicID.
+     *
+     * @param musicId The unique, non-hierarchical music ID.
+     */
+    public Song getMusicByMediaId(String musicId) {
+        return mMusicListByMediaId.containsKey(musicId) ? mMusicListByMediaId.get(musicId) : null;
+    }
+
+    /**
+     * Very basic implementation of a search that filter music tracks which title containing
+     * the given query.
+     *
+     */
+    public Iterable<MediaMetadata> searchMusic(String titleQuery) {
+        if (mCurrentState != State.INITIALIZED) {
+            return Collections.emptyList();
+        }
+        ArrayList<MediaMetadata> result = new ArrayList<>();
+        titleQuery = titleQuery.toLowerCase();
+        for (Song song : mMusicListByMediaId.values()) {
+            if (song.getMetadata()
+                            .getString(MediaMetadata.METADATA_KEY_TITLE)
+                            .toLowerCase()
+                            .contains(titleQuery)) {
+                result.add(song.getMetadata());
+            }
+        }
+        return result;
+    }
+
+    public interface MusicProviderCallback { void onMusicCatalogReady(boolean success); }
+
+    /**
+     * Get the list of music tracks from disk and caches the track information
+     * for future reference, keying tracks by musicId and grouping by genre.
+     */
+    public void retrieveMediaAsync(final MusicProviderCallback callback) {
+        Log.d(TAG, "retrieveMediaAsync called");
+        if (mCurrentState == State.INITIALIZED) {
+            // Nothing to do, execute callback immediately
+            callback.onMusicCatalogReady(true);
+            return;
+        }
+
+        // Asynchronously load the music catalog in a separate thread
+        new AsyncTask<Void, Void, State>() {
+            @Override
+            protected State doInBackground(Void... params) {
+                mCurrentState = State.INITIALIZING;
+                if (retrieveMedia()) {
+                    mCurrentState = State.INITIALIZED;
+                } else {
+                    mCurrentState = State.NON_INITIALIZED;
+                }
+                return mCurrentState;
+            }
+
+            @Override
+            protected void onPostExecute(State current) {
+                if (callback != null) {
+                    callback.onMusicCatalogReady(current == State.INITIALIZED);
+                }
+            }
+        }
+                .execute();
+    }
+
+    public synchronized boolean retrieveAllPlayLists() {
+        Cursor cursor = mContext.getContentResolver().query(
+                MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, null, null, null, null);
+        if (cursor == null) {
+            Log.e(TAG, "Failed to retreive playlist: cursor is null");
+            return false;
+        }
+        if (!cursor.moveToFirst()) {
+            Log.d(TAG, "Failed to move cursor to first row (no query result)");
+            cursor.close();
+            return true;
+        }
+        int idColumn = cursor.getColumnIndex(MediaStore.Audio.Playlists._ID);
+        int nameColumn = cursor.getColumnIndex(MediaStore.Audio.Playlists.NAME);
+        int pathColumn = cursor.getColumnIndex(MediaStore.Audio.Playlists.DATA);
+        do {
+            long thisId = cursor.getLong(idColumn);
+            String thisPath = cursor.getString(pathColumn);
+            String thisName = cursor.getString(nameColumn);
+            Log.i(TAG, "PlayList ID: " + thisId + " Name: " + thisName);
+            List<MediaMetadata> songList = retreivePlaylistMetadata(thisId, thisPath);
+            LogHelper.i(TAG, "Found ", songList.size(), " items for playlist name: ", thisName);
+            mMusicListByPlaylist.put(thisName, songList);
+        } while (cursor.moveToNext());
+        cursor.close();
+        return true;
+    }
+
+    public synchronized List<MediaMetadata> retreivePlaylistMetadata(
+            long playlistId, String playlistPath) {
+        Cursor cursor = mContext.getContentResolver().query(Uri.parse(playlistPath), null,
+                MediaStore.Audio.Playlists.Members.PLAYLIST_ID + " == " + playlistId, null, null);
+        if (cursor == null) {
+            Log.e(TAG, "Failed to retreive individual playlist: cursor is null");
+            return null;
+        }
+        if (!cursor.moveToFirst()) {
+            Log.d(TAG, "Failed to move cursor to first row (no query result for playlist)");
+            cursor.close();
+            return null;
+        }
+        List<Song> songList = new ArrayList<>();
+        int idColumn = cursor.getColumnIndex(MediaStore.Audio.Playlists.Members._ID);
+        int audioIdColumn = cursor.getColumnIndex(MediaStore.Audio.Playlists.Members.AUDIO_ID);
+        int orderColumn = cursor.getColumnIndex(MediaStore.Audio.Playlists.Members.PLAY_ORDER);
+        int audioPathColumn = cursor.getColumnIndex(MediaStore.Audio.Playlists.Members.DATA);
+        int audioNameColumn = cursor.getColumnIndex(MediaStore.Audio.Playlists.Members.TITLE);
+        do {
+            long thisId = cursor.getLong(idColumn);
+            long thisAudioId = cursor.getLong(audioIdColumn);
+            long thisOrder = cursor.getLong(orderColumn);
+            String thisAudioPath = cursor.getString(audioPathColumn);
+            Log.i(TAG,
+                    "Playlist ID: " + playlistId + " Music ID: " + thisAudioId
+                            + " Name: " + audioNameColumn);
+            if (!mMusicListById.containsKey(thisAudioId)) {
+                LogHelper.d(TAG, "Music does not exist");
+                continue;
+            }
+            Song song = mMusicListById.get(thisAudioId);
+            song.setSortKey(thisOrder);
+            songList.add(song);
+        } while (cursor.moveToNext());
+        cursor.close();
+        songList.sort(new Comparator<Song>() {
+            @Override
+            public int compare(Song s1, Song s2) {
+                long key1 = s1.getSortKey();
+                long key2 = s2.getSortKey();
+                if (key1 < key2) {
+                    return -1;
+                } else if (key1 == key2) {
+                    return 0;
+                } else {
+                    return 1;
+                }
+            }
+        });
+        List<MediaMetadata> metadataList = new ArrayList<>();
+        for (Song song : songList) {
+            metadataList.add(song.getMetadata());
+        }
+        return metadataList;
+    }
+
+    private synchronized boolean retrieveMedia() {
+        Cursor cursor =
+                mContext.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                        null, MUSIC_SELECT_FILTER, null, MUSIC_SORT_ORDER);
+        if (cursor == null) {
+            Log.e(TAG, "Failed to retreive music: cursor is null");
+            mCurrentState = State.NON_INITIALIZED;
+            return false;
+        }
+        if (!cursor.moveToFirst()) {
+            Log.d(TAG, "Failed to move cursor to first row (no query result)");
+            cursor.close();
+            return true;
+        }
+        int idColumn = cursor.getColumnIndex(MediaStore.Audio.Media._ID);
+        int titleColumn = cursor.getColumnIndex(MediaStore.Audio.Media.TITLE);
+        int pathColumn = cursor.getColumnIndex(MediaStore.Audio.Media.DATA);
+        do {
+            Log.i(TAG,
+                    "Music ID: " + cursor.getString(idColumn)
+                            + " Title: " + cursor.getString(titleColumn));
+            long thisId = cursor.getLong(idColumn);
+            String thisPath = cursor.getString(pathColumn);
+            MediaMetadata metadata = retrievMediaMetadata(thisId, thisPath);
+            Log.i(TAG, "MediaMetadata: " + metadata);
+            if (metadata == null) {
+                continue;
+            }
+            Song thisSong = new Song(thisId, metadata, null);
+            // Construct per feature database
+            mMusicList.add(metadata);
+            mMusicListById.put(thisId, thisSong);
+            mMusicListByMediaId.put(String.valueOf(thisId), thisSong);
+            addMusicToAlbumList(metadata);
+            addMusicToArtistList(metadata);
+        } while (cursor.moveToNext());
+        cursor.close();
+        return true;
+    }
+
+    private synchronized MediaMetadata retrievMediaMetadata(long musicId, String musicPath) {
+        LogHelper.d(TAG, "getting metadata for music: ", musicPath);
+        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+        Uri contentUri = ContentUris.withAppendedId(
+                android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, musicId);
+        if (!(new File(musicPath).exists())) {
+            LogHelper.d(TAG, "Does not exist, deleting item");
+            mContext.getContentResolver().delete(contentUri, null, null);
+            return null;
+        }
+        retriever.setDataSource(mContext, contentUri);
+        String title = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE);
+        String album = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM);
+        String artist = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST);
+        String durationString =
+                retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
+        long duration = durationString != null ? Long.parseLong(durationString) : 0;
+        MediaMetadata.Builder metadataBuilder =
+                new MediaMetadata.Builder()
+                        .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, String.valueOf(musicId))
+                        .putString(CUSTOM_METADATA_TRACK_SOURCE, musicPath)
+                        .putString(MediaMetadata.METADATA_KEY_TITLE, title != null ? title : UNKOWN)
+                        .putString(MediaMetadata.METADATA_KEY_ALBUM, album != null ? album : UNKOWN)
+                        .putString(
+                                MediaMetadata.METADATA_KEY_ARTIST, artist != null ? artist : UNKOWN)
+                        .putLong(MediaMetadata.METADATA_KEY_DURATION, duration);
+        byte[] albumArtData = retriever.getEmbeddedPicture();
+        Bitmap bitmap;
+        if (albumArtData != null) {
+            bitmap = BitmapFactory.decodeByteArray(albumArtData, 0, albumArtData.length);
+            bitmap = MusicUtils.resizeBitmap(bitmap, getDefaultAlbumArt());
+            metadataBuilder.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, bitmap);
+        }
+        retriever.release();
+        return metadataBuilder.build();
+    }
+
+    private Bitmap getDefaultAlbumArt() {
+        BitmapFactory.Options opts = new BitmapFactory.Options();
+        opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
+        return BitmapFactory.decodeStream(
+                mContext.getResources().openRawResource(R.drawable.albumart_mp_unknown), null,
+                opts);
+    }
+
+    private void addMusicToAlbumList(MediaMetadata metadata) {
+        String thisAlbum = metadata.getString(MediaMetadata.METADATA_KEY_ALBUM);
+        if (thisAlbum == null) {
+            thisAlbum = UNKOWN;
+        }
+        if (!mMusicListByAlbum.containsKey(thisAlbum)) {
+            mMusicListByAlbum.put(thisAlbum, new ArrayList<>());
+        }
+        mMusicListByAlbum.get(thisAlbum).add(metadata);
+    }
+
+    private void addMusicToArtistList(MediaMetadata metadata) {
+        String thisArtist = metadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
+        if (thisArtist == null) {
+            thisArtist = UNKOWN;
+        }
+        String thisAlbum = metadata.getString(MediaMetadata.METADATA_KEY_ALBUM);
+        if (thisAlbum == null) {
+            thisAlbum = UNKOWN;
+        }
+        if (!mArtistAlbumDb.containsKey(thisArtist)) {
+            mArtistAlbumDb.put(thisArtist, new ConcurrentHashMap<>());
+        }
+        Map<String, MediaMetadata> albumsMap = mArtistAlbumDb.get(thisArtist);
+        MediaMetadata.Builder builder;
+        long count = 0;
+        Bitmap thisAlbumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+        if (albumsMap.containsKey(thisAlbum)) {
+            MediaMetadata album_metadata = albumsMap.get(thisAlbum);
+            count = album_metadata.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS);
+            Bitmap nAlbumArt = album_metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+            builder = new MediaMetadata.Builder(album_metadata);
+            if (nAlbumArt != null) {
+                thisAlbumArt = null;
+            }
+        } else {
+            builder = new MediaMetadata.Builder();
+            builder.putString(MediaMetadata.METADATA_KEY_ALBUM, thisAlbum)
+                    .putString(MediaMetadata.METADATA_KEY_ARTIST, thisArtist);
+        }
+        if (thisAlbumArt != null) {
+            builder.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, thisAlbumArt);
+        }
+        builder.putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, count + 1);
+        albumsMap.put(thisAlbum, builder.build());
+    }
+
+    public synchronized void updateMusic(String musicId, MediaMetadata metadata) {
+        Song song = mMusicListByMediaId.get(musicId);
+        if (song == null) {
+            return;
+        }
+
+        String oldGenre = song.getMetadata().getString(MediaMetadata.METADATA_KEY_GENRE);
+        String newGenre = metadata.getString(MediaMetadata.METADATA_KEY_GENRE);
+
+        song.setMetadata(metadata);
+
+        // if genre has changed, we need to rebuild the list by genre
+        if (!oldGenre.equals(newGenre)) {
+            //            buildListsByGenre();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/music/utils/Playback.java b/src/com/android/music/utils/Playback.java
new file mode 100644
index 0000000..02114bd
--- /dev/null
+++ b/src/com/android/music/utils/Playback.java
@@ -0,0 +1,486 @@
+/*
+ * Copyright (C) 2014 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.utils;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.media.MediaMetadata;
+import android.media.MediaPlayer;
+import android.media.session.PlaybackState;
+import android.net.wifi.WifiManager;
+import android.os.PowerManager;
+import android.text.TextUtils;
+import android.util.Log;
+import com.android.music.MediaPlaybackService;
+
+import java.io.IOException;
+
+import static android.media.MediaPlayer.OnCompletionListener;
+import static android.media.MediaPlayer.OnErrorListener;
+import static android.media.MediaPlayer.OnPreparedListener;
+import static android.media.MediaPlayer.OnSeekCompleteListener;
+import static android.media.session.MediaSession.QueueItem;
+
+/**
+ * A class that implements local media playback using {@link android.media.MediaPlayer}
+ */
+public class Playback implements AudioManager.OnAudioFocusChangeListener, OnCompletionListener,
+                                 OnErrorListener, OnPreparedListener, OnSeekCompleteListener {
+    private static final String TAG = "Playback";
+
+    // The volume we set the media player to when we lose audio focus, but are
+    // allowed to reduce the volume instead of stopping playback.
+    public static final float VOLUME_DUCK = 0.2f;
+    // The volume we set the media player when we have audio focus.
+    public static final float VOLUME_NORMAL = 1.0f;
+
+    // we don't have audio focus, and can't duck (play at a low volume)
+    private static final int AUDIO_NO_FOCUS_NO_DUCK = 0;
+    // we don't have focus, but can duck (play at a low volume)
+    private static final int AUDIO_NO_FOCUS_CAN_DUCK = 1;
+    // we have full audio focus
+    private static final int AUDIO_FOCUSED = 2;
+
+    private final MediaPlaybackService mService;
+    private final WifiManager.WifiLock mWifiLock;
+    private int mState;
+    private boolean mPlayOnFocusGain;
+    private Callback mCallback;
+    private MusicProvider mMusicProvider;
+    private volatile boolean mAudioNoisyReceiverRegistered;
+    private volatile int mCurrentPosition;
+    private volatile String mCurrentMediaId;
+
+    // Type of audio focus we have:
+    private int mAudioFocus = AUDIO_NO_FOCUS_NO_DUCK;
+    private AudioManager mAudioManager;
+    private MediaPlayer mMediaPlayer;
+
+    private IntentFilter mAudioNoisyIntentFilter =
+            new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
+
+    private BroadcastReceiver mAudioNoisyReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
+                Log.d(TAG, "Headphones disconnected.");
+                if (isPlaying()) {
+                    Intent i = new Intent(context, MediaPlaybackService.class);
+                    i.setAction(MediaPlaybackService.ACTION_CMD);
+                    i.putExtra(MediaPlaybackService.CMD_NAME, MediaPlaybackService.CMD_PAUSE);
+                    mService.startService(i);
+                }
+            }
+        }
+    };
+
+    public Playback(MediaPlaybackService service, MusicProvider musicProvider) {
+        this.mService = service;
+        this.mMusicProvider = musicProvider;
+        this.mAudioManager = (AudioManager) service.getSystemService(Context.AUDIO_SERVICE);
+        // Create the Wifi lock (this does not acquire the lock, this just creates it)
+        this.mWifiLock = ((WifiManager) service.getSystemService(Context.WIFI_SERVICE))
+                                 .createWifiLock(WifiManager.WIFI_MODE_FULL, "sample_lock");
+    }
+
+    public void start() {}
+
+    public void stop(boolean notifyListeners) {
+        mState = PlaybackState.STATE_STOPPED;
+        if (notifyListeners && mCallback != null) {
+            mCallback.onPlaybackStatusChanged(mState);
+        }
+        mCurrentPosition = getCurrentStreamPosition();
+        // Give up Audio focus
+        giveUpAudioFocus();
+        unregisterAudioNoisyReceiver();
+        // Relax all resources
+        relaxResources(true);
+        if (mWifiLock.isHeld()) {
+            mWifiLock.release();
+        }
+    }
+
+    public void setState(int state) {
+        this.mState = state;
+    }
+
+    public int getState() {
+        return mState;
+    }
+
+    public boolean isConnected() {
+        return true;
+    }
+
+    public boolean isPlaying() {
+        return mPlayOnFocusGain || (mMediaPlayer != null && mMediaPlayer.isPlaying());
+    }
+
+    public int getCurrentStreamPosition() {
+        return mMediaPlayer != null ? mMediaPlayer.getCurrentPosition() : mCurrentPosition;
+    }
+
+    public void play(QueueItem item) {
+        mPlayOnFocusGain = true;
+        tryToGetAudioFocus();
+        registerAudioNoisyReceiver();
+        String mediaId = item.getDescription().getMediaId();
+        boolean mediaHasChanged = !TextUtils.equals(mediaId, mCurrentMediaId);
+        if (mediaHasChanged) {
+            mCurrentPosition = 0;
+            mCurrentMediaId = mediaId;
+        }
+
+        if (mState == PlaybackState.STATE_PAUSED && !mediaHasChanged && mMediaPlayer != null) {
+            configMediaPlayerState();
+        } else {
+            mState = PlaybackState.STATE_STOPPED;
+            relaxResources(false); // release everything except MediaPlayer
+            MediaMetadata track =
+                    mMusicProvider
+                            .getMusicByMediaId(MediaIDHelper.extractMusicIDFromMediaID(
+                                    item.getDescription().getMediaId()))
+                            .getMetadata();
+
+            String source = track.getString(MusicProvider.CUSTOM_METADATA_TRACK_SOURCE);
+
+            try {
+                createMediaPlayerIfNeeded();
+
+                mState = PlaybackState.STATE_BUFFERING;
+
+                mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+                mMediaPlayer.setDataSource(source);
+
+                // Starts preparing the media player in the background. When
+                // it's done, it will call our OnPreparedListener (that is,
+                // the onPrepared() method on this class, since we set the
+                // listener to 'this'). Until the media player is prepared,
+                // we *cannot* call start() on it!
+                mMediaPlayer.prepareAsync();
+
+                // If we are streaming from the internet, we want to hold a
+                // Wifi lock, which prevents the Wifi radio from going to
+                // sleep while the song is playing.
+                //                mWifiLock.acquire();
+
+                if (mCallback != null) {
+                    mCallback.onPlaybackStatusChanged(mState);
+                }
+
+            } catch (IOException ex) {
+                Log.e(TAG, ex + "Exception playing song");
+                if (mCallback != null) {
+                    mCallback.onError(ex.getMessage());
+                }
+            }
+        }
+    }
+
+    public void pause() {
+        if (mState == PlaybackState.STATE_PLAYING) {
+            // Pause media player and cancel the 'foreground service' state.
+            if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
+                mMediaPlayer.pause();
+                mCurrentPosition = mMediaPlayer.getCurrentPosition();
+            }
+            // while paused, retain the MediaPlayer but give up audio focus
+            relaxResources(false);
+            giveUpAudioFocus();
+        }
+        mState = PlaybackState.STATE_PAUSED;
+        if (mCallback != null) {
+            mCallback.onPlaybackStatusChanged(mState);
+        }
+        unregisterAudioNoisyReceiver();
+    }
+
+    public void seekTo(int position) {
+        Log.d(TAG, "seekTo called with " + position);
+
+        if (mMediaPlayer == null) {
+            // If we do not have a current media player, simply update the current position
+            mCurrentPosition = position;
+        } else {
+            if (mMediaPlayer.isPlaying()) {
+                mState = PlaybackState.STATE_BUFFERING;
+            }
+            mMediaPlayer.seekTo(position);
+            if (mCallback != null) {
+                mCallback.onPlaybackStatusChanged(mState);
+            }
+        }
+    }
+
+    public void setCallback(Callback callback) {
+        this.mCallback = callback;
+    }
+
+    /**
+     * Try to get the system audio focus.
+     */
+    private void tryToGetAudioFocus() {
+        Log.d(TAG, "tryToGetAudioFocus");
+        if (mAudioFocus != AUDIO_FOCUSED) {
+            int result = mAudioManager.requestAudioFocus(
+                    this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
+            if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+                mAudioFocus = AUDIO_FOCUSED;
+            }
+        }
+    }
+
+    /**
+     * Give up the audio focus.
+     */
+    private void giveUpAudioFocus() {
+        Log.d(TAG, "giveUpAudioFocus");
+        if (mAudioFocus == AUDIO_FOCUSED) {
+            if (mAudioManager.abandonAudioFocus(this) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+                mAudioFocus = AUDIO_NO_FOCUS_NO_DUCK;
+            }
+        }
+    }
+
+    /**
+     * Reconfigures MediaPlayer according to audio focus settings and
+     * starts/restarts it. This method starts/restarts the MediaPlayer
+     * respecting the current audio focus state. So if we have focus, it will
+     * play normally; if we don't have focus, it will either leave the
+     * MediaPlayer paused or set it to a low volume, depending on what is
+     * allowed by the current focus settings. This method assumes mPlayer !=
+     * null, so if you are calling it, you have to do so from a context where
+     * you are sure this is the case.
+     */
+    private void configMediaPlayerState() {
+        Log.d(TAG, "configMediaPlayerState. mAudioFocus=" + mAudioFocus);
+        if (mAudioFocus == AUDIO_NO_FOCUS_NO_DUCK) {
+            // If we don't have audio focus and can't duck, we have to pause,
+            if (mState == PlaybackState.STATE_PLAYING) {
+                pause();
+            }
+        } else { // we have audio focus:
+            if (mAudioFocus == AUDIO_NO_FOCUS_CAN_DUCK) {
+                mMediaPlayer.setVolume(VOLUME_DUCK, VOLUME_DUCK); // we'll be relatively quiet
+            } else {
+                if (mMediaPlayer != null) {
+                    mMediaPlayer.setVolume(VOLUME_NORMAL, VOLUME_NORMAL); // we can be loud again
+                } // else do something for remote client.
+            }
+            // If we were playing when we lost focus, we need to resume playing.
+            if (mPlayOnFocusGain) {
+                if (mMediaPlayer != null && !mMediaPlayer.isPlaying()) {
+                    Log.d(TAG,
+                            "configMediaPlayerState startMediaPlayer. seeking to "
+                                    + mCurrentPosition);
+                    if (mCurrentPosition == mMediaPlayer.getCurrentPosition()) {
+                        mMediaPlayer.start();
+                        mState = PlaybackState.STATE_PLAYING;
+                    } else {
+                        mMediaPlayer.seekTo(mCurrentPosition);
+                        mState = PlaybackState.STATE_BUFFERING;
+                    }
+                }
+                mPlayOnFocusGain = false;
+            }
+        }
+        if (mCallback != null) {
+            mCallback.onPlaybackStatusChanged(mState);
+        }
+    }
+
+    /**
+     * Called by AudioManager on audio focus changes.
+     * Implementation of {@link android.media.AudioManager.OnAudioFocusChangeListener}
+     */
+    @Override
+    public void onAudioFocusChange(int focusChange) {
+        Log.d(TAG, "onAudioFocusChange. focusChange=" + focusChange);
+        if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
+            // We have gained focus:
+            mAudioFocus = AUDIO_FOCUSED;
+
+        } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS
+                || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT
+                || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
+            // We have lost focus. If we can duck (low playback volume), we can keep playing.
+            // Otherwise, we need to pause the playback.
+            boolean canDuck = focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK;
+            mAudioFocus = canDuck ? AUDIO_NO_FOCUS_CAN_DUCK : AUDIO_NO_FOCUS_NO_DUCK;
+
+            // If we are playing, we need to reset media player by calling configMediaPlayerState
+            // with mAudioFocus properly set.
+            if (mState == PlaybackState.STATE_PLAYING && !canDuck) {
+                // If we don't have audio focus and can't duck, we save the information that
+                // we were playing, so that we can resume playback once we get the focus back.
+                mPlayOnFocusGain = true;
+            }
+        } else {
+            Log.e(TAG, "onAudioFocusChange: Ignoring unsupported focusChange: " + focusChange);
+        }
+        configMediaPlayerState();
+    }
+
+    /**
+     * Called when MediaPlayer has completed a seek
+     *
+     * @see android.media.MediaPlayer.OnSeekCompleteListener
+     */
+    @Override
+    public void onSeekComplete(MediaPlayer mp) {
+        Log.d(TAG, "onSeekComplete from MediaPlayer:" + mp.getCurrentPosition());
+        mCurrentPosition = mp.getCurrentPosition();
+        if (mState == PlaybackState.STATE_BUFFERING) {
+            mMediaPlayer.start();
+            mState = PlaybackState.STATE_PLAYING;
+        }
+        if (mCallback != null) {
+            mCallback.onPlaybackStatusChanged(mState);
+        }
+    }
+
+    /**
+     * Called when media player is done playing current song.
+     *
+     * @see android.media.MediaPlayer.OnCompletionListener
+     */
+    @Override
+    public void onCompletion(MediaPlayer player) {
+        Log.d(TAG, "onCompletion from MediaPlayer");
+        // The media player finished playing the current song, so we go ahead
+        // and start the next.
+        if (mCallback != null) {
+            mCallback.onCompletion();
+        }
+    }
+
+    /**
+     * Called when media player is done preparing.
+     *
+     * @see android.media.MediaPlayer.OnPreparedListener
+     */
+    @Override
+    public void onPrepared(MediaPlayer player) {
+        Log.d(TAG, "onPrepared from MediaPlayer");
+        // The media player is done preparing. That means we can start playing if we
+        // have audio focus.
+        configMediaPlayerState();
+    }
+
+    /**
+     * Called when there's an error playing media. When this happens, the media
+     * player goes to the Error state. We warn the user about the error and
+     * reset the media player.
+     *
+     * @see android.media.MediaPlayer.OnErrorListener
+     */
+    @Override
+    public boolean onError(MediaPlayer mp, int what, int extra) {
+        Log.e(TAG, "Media player error: what=" + what + ", extra=" + extra);
+        if (mCallback != null) {
+            mCallback.onError("MediaPlayer error " + what + " (" + extra + ")");
+        }
+        return true; // true indicates we handled the error
+    }
+
+    /**
+     * Makes sure the media player exists and has been reset. This will create
+     * the media player if needed, or reset the existing media player if one
+     * already exists.
+     */
+    private void createMediaPlayerIfNeeded() {
+        Log.d(TAG, "createMediaPlayerIfNeeded. needed? " + (mMediaPlayer == null));
+        if (mMediaPlayer == null) {
+            mMediaPlayer = new MediaPlayer();
+
+            // Make sure the media player will acquire a wake-lock while
+            // playing. If we don't do that, the CPU might go to sleep while the
+            // song is playing, causing playback to stop.
+            mMediaPlayer.setWakeMode(
+                    mService.getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
+
+            // we want the media player to notify us when it's ready preparing,
+            // and when it's done playing:
+            mMediaPlayer.setOnPreparedListener(this);
+            mMediaPlayer.setOnCompletionListener(this);
+            mMediaPlayer.setOnErrorListener(this);
+            mMediaPlayer.setOnSeekCompleteListener(this);
+        } else {
+            mMediaPlayer.reset();
+        }
+    }
+
+    /**
+     * Releases resources used by the service for playback. This includes the
+     * "foreground service" status, the wake locks and possibly the MediaPlayer.
+     *
+     * @param releaseMediaPlayer Indicates whether the Media Player should also
+     *            be released or not
+     */
+    private void relaxResources(boolean releaseMediaPlayer) {
+        Log.d(TAG, "relaxResources. releaseMediaPlayer=" + releaseMediaPlayer);
+
+        mService.stopForeground(true);
+
+        // stop and release the Media Player, if it's available
+        if (releaseMediaPlayer && mMediaPlayer != null) {
+            mMediaPlayer.reset();
+            mMediaPlayer.release();
+            mMediaPlayer = null;
+        }
+
+        // we can also release the Wifi lock, if we're holding it
+        if (mWifiLock.isHeld()) {
+            mWifiLock.release();
+        }
+    }
+
+    private void registerAudioNoisyReceiver() {
+        if (!mAudioNoisyReceiverRegistered) {
+            mService.registerReceiver(mAudioNoisyReceiver, mAudioNoisyIntentFilter);
+            mAudioNoisyReceiverRegistered = true;
+        }
+    }
+
+    private void unregisterAudioNoisyReceiver() {
+        if (mAudioNoisyReceiverRegistered) {
+            mService.unregisterReceiver(mAudioNoisyReceiver);
+            mAudioNoisyReceiverRegistered = false;
+        }
+    }
+
+    public interface Callback {
+        /**
+         * On current music completed.
+         */
+        void onCompletion();
+        /**
+         * on Playback status changed
+         * Implementations can use this callback to update
+         * playback state on the media sessions.
+         */
+        void onPlaybackStatusChanged(int state);
+
+        /**
+         * @param error to be added to the PlaybackState
+         */
+        void onError(String error);
+    }
+}
diff --git a/src/com/android/music/utils/QueueHelper.java b/src/com/android/music/utils/QueueHelper.java
new file mode 100644
index 0000000..7e3c2fd
--- /dev/null
+++ b/src/com/android/music/utils/QueueHelper.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2014 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.utils;
+
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.session.MediaSession;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import static com.android.music.utils.MediaIDHelper.MEDIA_ID_MUSICS_BY_ARTIST;
+import static com.android.music.utils.MediaIDHelper.MEDIA_ID_MUSICS_BY_SEARCH;
+import static com.android.music.utils.MediaIDHelper.MEDIA_ID_MUSICS_BY_SONG;
+import static com.android.music.utils.MediaIDHelper.MEDIA_ID_MUSICS_BY_PLAYLIST;
+import static com.android.music.utils.MediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM;
+
+/**
+ * Utility class to help on queue related tasks.
+ */
+public class QueueHelper {
+    private static final String TAG = LogHelper.makeLogTag(QueueHelper.class);
+
+    public static List<MediaSession.QueueItem> getPlayingQueue(
+            String mediaId, MusicProvider musicProvider) {
+        // extract the browsing hierarchy from the media ID:
+        String[] hierarchy = MediaIDHelper.getHierarchy(mediaId);
+
+        if (hierarchy.length != 2) {
+            LogHelper.e(TAG, "Could not build a playing queue for this mediaId: ", mediaId);
+            return null;
+        }
+
+        String categoryType = hierarchy[0];
+        String categoryValue = hierarchy[1];
+        LogHelper.d(TAG, "Creating playing queue for ", categoryType, ",  ", categoryValue);
+
+        Iterable<MediaMetadata> tracks = null;
+        // This sample only supports genre and by_search category types.
+        switch (categoryType) {
+            case MEDIA_ID_MUSICS_BY_SONG:
+                tracks = musicProvider.getMusicList();
+                break;
+            case MEDIA_ID_MUSICS_BY_ALBUM:
+                tracks = musicProvider.getMusicsByAlbum(categoryValue);
+                break;
+            case MEDIA_ID_MUSICS_BY_ARTIST:
+                LogHelper.d(TAG, "Not supported");
+                break;
+            default:
+                break;
+        }
+
+        if (tracks == null) {
+            LogHelper.e(
+                    TAG, "Unrecognized category type: ", categoryType, " for mediaId ", mediaId);
+            return null;
+        }
+
+        return convertToQueue(tracks, hierarchy[0], hierarchy[1]);
+    }
+
+    public static List<MediaSession.QueueItem> getPlayingQueueFromSearch(
+            String query, MusicProvider musicProvider) {
+        LogHelper.d(TAG, "Creating playing queue for musics from search ", query);
+
+        return convertToQueue(musicProvider.searchMusic(query), MEDIA_ID_MUSICS_BY_SEARCH, query);
+    }
+
+    public static int getMusicIndexOnQueue(Iterable<MediaSession.QueueItem> queue, String mediaId) {
+        int index = 0;
+        for (MediaSession.QueueItem item : queue) {
+            if (mediaId.equals(item.getDescription().getMediaId())) {
+                return index;
+            }
+            index++;
+        }
+        return -1;
+    }
+
+    public static int getMusicIndexOnQueue(Iterable<MediaSession.QueueItem> queue, long queueId) {
+        int index = 0;
+        for (MediaSession.QueueItem item : queue) {
+            if (queueId == item.getQueueId()) {
+                return index;
+            }
+            index++;
+        }
+        return -1;
+    }
+
+    private static List<MediaSession.QueueItem> convertToQueue(
+            Iterable<MediaMetadata> tracks, String... categories) {
+        List<MediaSession.QueueItem> queue = new ArrayList<>();
+        int count = 0;
+        for (MediaMetadata track : tracks) {
+            // We create a hierarchy-aware mediaID, so we know what the queue is about by looking
+            // at the QueueItem media IDs.
+            String hierarchyAwareMediaID =
+                    MediaIDHelper.createMediaID(track.getDescription().getMediaId(), categories);
+            long duration = track.getLong(MediaMetadata.METADATA_KEY_DURATION);
+            MediaDescription.Builder descriptionBuilder = new MediaDescription.Builder();
+            MediaDescription description = track.getDescription();
+            Bundle extras = description.getExtras();
+            if (extras == null) {
+                extras = new Bundle();
+            }
+            extras.putLong(MediaMetadata.METADATA_KEY_DURATION, duration);
+            descriptionBuilder.setExtras(extras)
+                    .setMediaId(hierarchyAwareMediaID)
+                    .setTitle(description.getTitle())
+                    .setSubtitle(track.getString(MediaMetadata.METADATA_KEY_ARTIST))
+                    .setIconBitmap(description.getIconBitmap())
+                    .setIconUri(description.getIconUri())
+                    .setMediaUri(description.getMediaUri())
+                    .setDescription(description.getDescription());
+
+            // We don't expect queues to change after created, so we use the item index as the
+            // queueId. Any other number unique in the queue would work.
+            MediaSession.QueueItem item =
+                    new MediaSession.QueueItem(descriptionBuilder.build(), count++);
+            queue.add(item);
+        }
+        return queue;
+    }
+
+    /**
+     * Create a random queue. For simplicity sake, instead of a random queue, we create a
+     * queue using the first genre.
+     *
+     * @param musicProvider the provider used for fetching music.
+     * @return list containing {@link android.media.session.MediaSession.QueueItem}'s
+     */
+    public static List<MediaSession.QueueItem> getRandomQueue(MusicProvider musicProvider) {
+        Iterator<String> genres = musicProvider.getArtists().iterator();
+        if (!genres.hasNext()) {
+            return Collections.emptyList();
+        }
+        String genre = genres.next();
+        Iterable<MediaMetadata> tracks = musicProvider.getMusicsByAlbum(genre);
+
+        return convertToQueue(tracks, MEDIA_ID_MUSICS_BY_ARTIST, genre);
+    }
+
+    public static boolean isIndexPlayable(int index, List<MediaSession.QueueItem> queue) {
+        return (queue != null && index >= 0 && index < queue.size());
+    }
+}
diff --git a/src/com/android/music/utils/ResourceHelper.java b/src/com/android/music/utils/ResourceHelper.java
new file mode 100644
index 0000000..d7773ff
--- /dev/null
+++ b/src/com/android/music/utils/ResourceHelper.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2014 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.utils;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+
+/**
+ * Generic reusable methods to handle resources.
+ */
+public class ResourceHelper {
+    /**
+     * Get a color value from a theme attribute.
+     * @param context used for getting the color.
+     * @param attribute theme attribute.
+     * @param defaultColor default to use.
+     * @return color value
+     */
+    public static int getThemeColor(Context context, int attribute, int defaultColor) {
+        int themeColor = 0;
+        String packageName = context.getPackageName();
+        try {
+            Context packageContext = context.createPackageContext(packageName, 0);
+            ApplicationInfo applicationInfo =
+                    context.getPackageManager().getApplicationInfo(packageName, 0);
+            packageContext.setTheme(applicationInfo.theme);
+            Resources.Theme theme = packageContext.getTheme();
+            TypedArray ta = theme.obtainStyledAttributes(new int[] {attribute});
+            themeColor = ta.getColor(0, defaultColor);
+            ta.recycle();
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+        }
+        return themeColor;
+    }
+}
diff --git a/src/com/android/music/utils/Song.java b/src/com/android/music/utils/Song.java
new file mode 100644
index 0000000..1260f03
--- /dev/null
+++ b/src/com/android/music/utils/Song.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2014 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.utils;
+
+import android.media.MediaMetadata;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Holder class that encapsulates a MediaMetadata and allows the actual metadata to be modified
+ * without requiring to rebuild the collections the metadata is in.
+ */
+public class Song implements Parcelable {
+    private MediaMetadata mMetadata;
+    private long mSongId;
+    private long mSortKey;
+
+    public Song(long songId, MediaMetadata metadata, Long sortKey) {
+        mMetadata = metadata;
+        mSongId = songId;
+        if (sortKey != null) {
+            mSortKey = sortKey;
+        }
+    }
+
+    public long getSongId() {
+        return mSongId;
+    }
+    public long getSortKey() {
+        return mSortKey;
+    }
+    public void setSortKey(long sortKey) {
+        mSortKey = sortKey;
+    }
+
+    public MediaMetadata getMetadata() {
+        return mMetadata;
+    }
+    public void setMetadata(MediaMetadata metadata) {
+        mMetadata = metadata;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || o.getClass() != Song.class) {
+            return false;
+        }
+
+        Song that = (Song) o;
+
+        return mSongId == that.getSongId();
+    }
+
+    @Override
+    public int hashCode() {
+        return Long.hashCode(mSongId);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeLong(mSortKey);
+        out.writeLong(mSongId);
+        out.writeParcelable(mMetadata, flags);
+    }
+
+    public static final Parcelable.Creator<Song> CREATOR = new Parcelable.Creator<Song>() {
+        public Song createFromParcel(Parcel in) {
+            MediaMetadata metadata = in.readParcelable(null);
+            long songId = in.readLong();
+            long sortKey = in.readLong();
+            return new Song(songId, metadata, sortKey);
+        }
+
+        public Song[] newArray(int size) {
+            return new Song[size];
+        }
+    };
+}
diff --git a/tests/src/com/android/music/functional/TestPlaylist.java b/tests/src/com/android/music/functional/TestPlaylist.java
index 64432d7..e751eb1 100644
--- a/tests/src/com/android/music/functional/TestPlaylist.java
+++ b/tests/src/com/android/music/functional/TestPlaylist.java
@@ -28,7 +28,6 @@
 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;
@@ -39,60 +38,59 @@
 /**
  * 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>{
+public class TestPlaylist extends ActivityInstrumentationTestCase<PlaylistBrowserActivity> {
     private static String TAG = "musicplayertests";
-  
+
     public TestPlaylist() {
-        super("com.android.music",PlaylistBrowserActivity.class);
+        super("com.android.music", PlaylistBrowserActivity.class);
     }
 
-    @Override 
-    protected void setUp() throws Exception {   
-        super.setUp(); 
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
     }
-    
-    @Override 
-    protected void tearDown() throws Exception {   
-        super.tearDown();           
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
     }
-    
-    
-    private void clearSearchString(int length){
+
+    private void clearSearchString(int length) {
         Instrumentation inst = getInstrumentation();
-        for (int j=0; j< length; j++)
+        for (int j = 0; j < length; j++) {
             inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DEL);
+        }
     }
+
     /**
      * Remove playlist
      */
-    public void deletePlaylist(String playlistname) throws Exception{
+    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);
+        inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
+        inst.invokeContextMenuAction(getActivity(), 0 + 1, 0);
         Thread.sleep(MusicPlayerNames.WAIT_SHORT_TIME);
         clearSearchString(playlistname.length());
-        
     }
 
     /**
-     *  Start the trackBrowserActivity and add the new playlist
+     * Start the trackBrowserActivity and add the new playlist
      */
-    public void addNewPlaylist(String playListName) throws Exception{
+    public void addNewPlaylist(String playListName) throws Exception {
         Instrumentation inst = getInstrumentation();
         Activity trackBrowserActivity;
-        ActivityMonitor trackBrowserMon = inst.addMonitor("com.android.music.TrackBrowserActivity", 
-                null, false);
+        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);     
+        getActivity().startActivity(intent);
         Thread.sleep(MusicPlayerNames.WAIT_LONG_TIME);
         trackBrowserActivity = trackBrowserMon.waitForActivityWithTimeout(2000);
-        inst.invokeContextMenuAction(trackBrowserActivity, MusicUtils.Defs.NEW_PLAYLIST, 0);
+        inst.invokeContextMenuAction(trackBrowserActivity, 0, 0);
         Thread.sleep(MusicPlayerNames.WAIT_SHORT_TIME);
         //Remove the default playlist name
         clearSearchString(MusicPlayerNames.DEFAULT_PLAYLIST_LENGTH);
@@ -104,16 +102,16 @@
         clearSearchString(playListName.length());
 
     }
-    
+
     /**
      * Rename playlist
      */
-    public void renamePlaylist(String oldPlaylistName, String newPlaylistName) throws Exception{
+    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);
+        inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
+        inst.invokeContextMenuAction(getActivity(), 0 + 3, 0);
         Thread.sleep(MusicPlayerNames.WAIT_SHORT_TIME);
         //Remove the old playlist name
         clearSearchString(oldPlaylistName.length());
@@ -124,18 +122,16 @@
         clearSearchString(oldPlaylistName.length());
     }
 
-    public boolean verifyPlaylist(String playlistname) throws Exception{
+    public boolean verifyPlaylist(String playlistname) throws Exception {
         Cursor mCursor;
         boolean isEmptyPlaylist = true;
-        String[] cols = new String[] {
-                MediaStore.Audio.Playlists.NAME
-        };
+        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 +"'";
+            String whereclause = MediaStore.Audio.Playlists.NAME + " = '" + playlistname + "'";
             mCursor = resolver.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
                     cols, whereclause, null,
                     MediaStore.Audio.Playlists.NAME);
@@ -149,26 +145,26 @@
      * Verification: The mediastore playlist should be empty
      */
     @LargeTest
-    public void testDeletePlaylist() throws Exception{
+    public void testDeletePlaylist() throws Exception {
         boolean isEmptyPlaylist = true;
         addNewPlaylist(MusicPlayerNames.DELETE_PLAYLIST_NAME);
-        deletePlaylist(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{
+    public void testRenamePlaylist() throws Exception {
         boolean isEmptyPlaylist = true;
         addNewPlaylist(MusicPlayerNames.ORIGINAL_PLAYLIST_NAME);
-        renamePlaylist(MusicPlayerNames.ORIGINAL_PLAYLIST_NAME, MusicPlayerNames.RENAMED_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
index 488d691..8bb2875 100644
--- a/tests/src/com/android/music/functional/TestSongs.java
+++ b/tests/src/com/android/music/functional/TestSongs.java
@@ -34,7 +34,6 @@
 import android.content.BroadcastReceiver;
 import android.content.IntentFilter;
 
-import com.android.music.CreatePlaylist;
 import com.android.music.TrackBrowserActivity;
 import com.android.music.MusicUtils;
 
@@ -44,49 +43,49 @@
 
 /**
  * Junit / Instrumentation test case for the TrackBrowserActivity
- 
  */
-public class TestSongs extends ActivityInstrumentationTestCase <TrackBrowserActivity>{
+public class TestSongs extends ActivityInstrumentationTestCase<TrackBrowserActivity> {
     private static String TAG = "musicplayertests";
-    
+
     public TestSongs() {
-        super("com.android.music",TrackBrowserActivity.class);
+        super("com.android.music", TrackBrowserActivity.class);
     }
 
-    @Override 
-    protected void setUp() throws Exception {   
-        super.setUp(); 
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
     }
-    
-    @Override 
-    protected void tearDown() throws Exception {   
-        super.tearDown();           
+
+    @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);
-      }
+    public void addNewPlaylist() throws Exception {
+        Instrumentation inst = getInstrumentation();
+        for (int i = 0; i < MusicPlayerNames.NO_OF_PLAYLIST; i++) {
+            inst.invokeContextMenuAction(getActivity(), 0, 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;
@@ -96,123 +95,120 @@
         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");
+    }
+
+    // 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);
-      }
-      
- 
+        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();
+    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 
+     * 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. 
+     * 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);
-      }
+    public void testSetRingtone() throws Exception {
+        Cursor mCursor;
+        Instrumentation inst = getInstrumentation();
+        inst.invokeContextMenuAction(getActivity(), 0, 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);
-      }     
-    } 
+    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(), 0, 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/MusicPlaybackStress.java b/tests/src/com/android/music/stress/MusicPlaybackStress.java
index ea916f6..10afd9d 100644
--- a/tests/src/com/android/music/stress/MusicPlaybackStress.java
+++ b/tests/src/com/android/music/stress/MusicPlaybackStress.java
@@ -34,53 +34,54 @@
 import com.android.music.TrackBrowserActivity;
 import com.android.music.tests.MusicPlayerNames;
 
-public class MusicPlaybackStress extends ActivityInstrumentationTestCase <TrackBrowserActivity>{
+public class MusicPlaybackStress extends ActivityInstrumentationTestCase<TrackBrowserActivity> {
     private static String TAG = "mediaplayertests";
-  
+
     public MusicPlaybackStress() {
-      super("com.android.music",TrackBrowserActivity.class);
+        super("com.android.music", TrackBrowserActivity.class);
     }
-  
-    @Override 
-    protected void setUp() throws Exception { 
-      super.setUp(); 
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
     }
-  
-    @Override 
-    protected void tearDown() throws Exception {   
-      super.tearDown();           
+
+    @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(Context.ACTIVITY_SERVICE)).getMemoryInfo(mi);
-      assertFalse(TAG, mi.lowMemory);      
+        Activity mediaPlaybackActivity;
+        try {
+            Instrumentation inst = getInstrumentation();
+            ActivityMonitor mediaPlaybackMon =
+                    inst.addMonitor("com.android.music.MediaPlaybackActivity", null, false);
+            inst.invokeMenuActionSync(getActivity(), 0 + 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(Context.ACTIVITY_SERVICE))
+                .getMemoryInfo(mi);
+        assertFalse(TAG, mi.lowMemory);
     }
 }