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);
}
}