--- /dev/null
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := user development
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ src/com/android/music/IMediaPlaybackService.aidl
+
+LOCAL_PACKAGE_NAME := Music
+
+include $(BUILD_PACKAGE)
+
+# Use the folloing include to make our test apk.
+include $(call all-makefiles-under,$(LOCAL_PATH))
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.music">
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+
+ <application android:icon="@drawable/app_music"
+ android:label="@string/musicbrowserlabel"
+ android:taskAffinity="android.task.music"
+ android:allowTaskReparenting="true">
+ <activity android:name="MusicBrowserActivity"
+ android:theme="@android:style/Theme.NoTitleBar">
+ <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>
+ <receiver android:name="MediaButtonIntentReceiver">
+ <intent-filter>
+ <action android:name="android.intent.action.MEDIA_BUTTON" />
+ </intent-filter>
+ </receiver>
+ <!-- This is the "current music playing" panel, which has special
+ launch behavior. We clear its task affinity, so it will not
+ be associated with the main media task and if launched
+ from a notification will not bring the rest of the media app
+ to the foreground. We make it singleTask so that when others
+ launch it (such as media) we will launch in to our own task.
+ We set clearTaskOnLaunch because the user
+ can go to a playlist from this activity, so if they later return
+ to it we want it back in its initial state. We exclude from
+ recents since this is accessible through a notification when
+ appropriate. -->
+ <activity android:name="MediaPlaybackActivity"
+ android:theme="@android:style/Theme.NoTitleBar"
+ android:label="@string/mediaplaybacklabel"
+ android:taskAffinity=""
+ android:launchMode="singleTask"
+ android:clearTaskOnLaunch="true"
+ android:excludeFromRecents="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:scheme="content"/>
+ <data android:scheme="file"/>
+ <data android:mimeType="audio/*"/>
+ <data android:mimeType="application/ogg"/>
+ <data android:mimeType="application/x-ogg"/>
+ </intent-filter>
+ <intent-filter>
+ <action android:name="com.android.music.PLAYBACK_VIEWER" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ <activity android:name="StreamStarter" android:theme="@android:style/Theme.Dialog" >
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http" />
+ <data android:mimeType="audio/mp3"/>
+ <data android:mimeType="audio/x-mp3"/>
+ <data android:mimeType="audio/mpeg"/>
+ <data android:mimeType="audio/mp4"/>
+ </intent-filter>
+ </activity>
+ <activity android:name="ArtistAlbumBrowserActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.PICK" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="vnd.android.cursor.dir/artistalbum"/>
+ </intent-filter>
+ </activity>
+ <activity android:name="AlbumBrowserActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.PICK" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="vnd.android.cursor.dir/album"/>
+ </intent-filter>
+ </activity>
+ <activity android:name="NowPlayingActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.PICK" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="vnd.android.cursor.dir/nowplaying"/>
+ </intent-filter>
+ </activity>
+ <activity android:name="TrackBrowserActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.EDIT" />
+ <action android:name="android.intent.action.PICK" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="vnd.android.cursor.dir/track"/>
+ </intent-filter>
+ </activity>
+ <activity android:name="QueryBrowserActivity"
+ android:theme="@android:style/Theme.NoTitleBar">
+ <intent-filter>
+ <action android:name="android.intent.action.SEARCH" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ <activity android:name="PlaylistBrowserActivity" android:label="@string/musicbrowserlabel">
+ <intent-filter>
+ <action android:name="android.intent.action.PICK" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="vnd.android.cursor.dir/playlist"/>
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="vnd.android.cursor.dir/playlist"/>
+ </intent-filter>
+ </activity>
+ <activity-alias android:name="PlaylistShortcutActivity"
+ android:targetActivity="PlaylistBrowserActivity"
+ android:label="@string/musicshortcutlabel">
+
+ <intent-filter>
+ <action android:name="android.intent.action.CREATE_SHORTCUT" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+
+ </activity-alias>
+ <activity android:name="VideoBrowserActivity"
+ android:taskAffinity="android.task.video"
+ android:label="@string/videobrowserlabel"
+ android:icon="@drawable/app_video">
+ <intent-filter>
+ <action android:name="android.intent.action.PICK" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="vnd.android.cursor.dir/video"/>
+ </intent-filter>
+<!--
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+-->
+ </activity>
+ <activity android:name="MediaPickerActivity" android:label="@string/mediapickerlabel">
+<!--
+ <intent-filter>
+ <action android:name="android.intent.action.PICK" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="media/*"/>
+ <data android:mimeType="audio/*"/>
+ <data android:mimeType="application/ogg"/>
+ <data android:mimeType="application/x-ogg"/>
+ <data android:mimeType="video/*"/>
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.GET_CONTENT" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.OPENABLE" />
+ <data android:mimeType="media/*"/>
+ <data android:mimeType="audio/*"/>
+ <data android:mimeType="application/ogg"/>
+ <data android:mimeType="application/x-ogg"/>
+ <data android:mimeType="video/*"/>
+ </intent-filter>
+-->
+ </activity>
+ <activity android:name="CreatePlaylist" android:theme="@android:style/Theme.Dialog" />
+ <activity android:name="RenamePlaylist" android:theme="@android:style/Theme.Dialog" />
+ <activity android:name="WeekSelector" android:theme="@android:style/Theme.Dialog" />
+ <activity android:name="DeleteItems" android:theme="@android:style/Theme.Dialog" />
+ <activity android:name="ScanningProgress" android:theme="@android:style/Theme.Dialog" />
+ <activity android:name="MovieView"
+ android:label="@string/movieviewlabel"
+ android:screenOrientation="sensor"
+ android:configChanges="orientation|keyboardHidden"
+ android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
+ android:taskAffinity="android.task.video"
+ android:process=":MovieView" >
+ <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="rtsp" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="video/*" />
+ </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="video/mp4" />
+ <data android:mimeType="video/3gp" />
+ <data android:mimeType="video/3gpp" />
+ <data android:mimeType="video/3gpp2" />
+ </intent-filter>
+ </activity>
+ <service android:name="MediaPlaybackService" android:exported="true" />
+ </application>
+</manifest>
--- /dev/null
+
+ Copyright (c) 2005-2008, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_selected="true"
+ android:drawable="@android:color/transparent" />
+ <item android:state_pressed="true" android:state_selected="false"
+ android:drawable="@android:color/transparent" />
+ <item android:state_selected="false"
+ android:drawable="@color/expanding_child_background" />
+</selector>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true" android:drawable="@drawable/btn_selection_pressed" />
+ <item android:state_focused="true" android:state_pressed="false" android:drawable="@drawable/btn_selection_focused" />
+ <item android:drawable="@android:drawable/btn_default" />
+</selector>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:orientation="horizontal"
+ android:gravity="center">
+
+ <com.android.music.AlbumView android:id="@+id/album"
+ android:layout_width="220px"
+ android:layout_height="fill_parent"
+ android:paddingLeft="12px"
+ android:paddingRight="6px"
+ android:paddingTop="16px" />
+
+ <LinearLayout
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:gravity="center_horizontal">
+
+ <ImageButton android:id="@+id/curplaylist"
+ android:src="@drawable/ic_mp_current_playlist_btn"
+ android:layout_width="85px"
+ android:layout_height="54px"
+ android:layout_marginTop="14px" />
+
+ <ImageButton android:id="@+id/shuffle"
+ android:layout_width="85px"
+ android:layout_height="54px"
+ android:layout_marginTop="20px" />
+
+ <ImageButton android:id="@+id/repeat"
+ android:layout_width="85px"
+ android:layout_height="54px"
+ android:layout_marginTop="20px" />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:baselineAligned="false"
+ android:paddingLeft="11dip"
+ android:paddingTop="4dip"
+ android:paddingBottom="8dip">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="4dip"
+ android:src="@drawable/ic_mp_artist_playback" />
+
+ <TextView android:id="@+id/artistname"
+ android:textSize="18sp"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textStyle="bold"
+ android:layout_gravity="center_vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:baselineAligned="false"
+ android:paddingLeft="11dip"
+ android:paddingTop="4dip"
+ android:paddingBottom="8dip">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="4dip"
+ android:src="@drawable/ic_mp_album_playback" />
+
+ <TextView android:id="@+id/albumname"
+ android:textSize="14sp"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:layout_gravity="center_vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:baselineAligned="false"
+ android:paddingLeft="11dip"
+ android:paddingTop="0dip"
+ android:paddingBottom="8dip">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="4dip"
+ android:src="@drawable/ic_mp_song_playback" />
+
+ <TextView android:id="@+id/trackname"
+ android:textSize="14sp"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:layout_gravity="center_vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+ <include layout="@layout/audio_player_common" />
+
+</LinearLayout>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <View
+ android:layout_width="fill_parent"
+ android:layout_height="1px"
+ android:background="#ffffffff" />
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:background="#ff5a5a5a"
+ android:paddingTop="1px"
+ android:paddingBottom="4px"
+ android:orientation="horizontal">
+
+ <TextView android:id="@+id/currenttime"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="14sp"
+ android:textStyle="bold"
+ android:shadowColor="#ff000000"
+ android:shadowDx="0"
+ android:shadowDy="0"
+ android:shadowRadius="3"
+ android:layout_gravity="bottom"
+ android:layout_weight="1"
+ android:layout_width="0dip"
+ android:paddingLeft="5px"
+ android:layout_height="wrap_content" />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_gravity="bottom"
+ android:layout_marginTop="1px"
+ android:layout_marginBottom="2px"
+ android:gravity="center">
+
+ <com.android.music.RepeatingImageButton android:id="@+id/prev" style="@android:style/MediaButton.Previous" />
+
+ <ImageButton android:id="@+id/pause" style="@android:style/MediaButton.Play" />
+
+ <com.android.music.RepeatingImageButton android:id="@+id/next" style="@android:style/MediaButton.Next" />
+
+ </LinearLayout>
+
+ <TextView android:id="@+id/totaltime"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="14sp"
+ android:textStyle="bold"
+ android:shadowColor="#ff000000"
+ android:shadowDx="0"
+ android:shadowDy="0"
+ android:shadowRadius="3"
+ android:gravity="right"
+ android:paddingRight="5px"
+ android:layout_gravity="bottom"
+ android:layout_weight="1"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+ <SeekBar android:id="@android:id/progress"
+ android:background="#ff5a5a5a"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="36px"
+ android:paddingLeft="5px"
+ android:paddingRight="5px"
+ android:paddingBottom="4px" />
+
+</merge>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <TextView android:id="@+id/prompt"
+ android:layout_width="fill_parent" android:layout_height="wrap_content"
+ android:text="@string/delete_confirm_prompt"
+ android:layout_marginLeft="8dip"
+ android:layout_marginTop="8dip"
+ android:layout_marginBottom="8dip"
+ android:drawableLeft="@drawable/ic_dialog_alert"
+ android:drawablePadding="8dip">
+ </TextView>
+
+ <RelativeLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:padding="6px"
+ android:background="#ffffff" >
+
+ <Button android:id="@+id/delete"
+ android:layout_width="120px" android:layout_height="wrap_content"
+ android:text="@string/delete_confirm_button_text"
+ android:layout_gravity="center_horizontal"
+ android:layout_alignParentLeft="true" />
+
+ <Button android:id="@+id/cancel"
+ android:layout_width="120px" android:layout_height="wrap_content"
+ android:text="@string/cancel"
+ android:layout_alignParentRight="true" />
+
+ </RelativeLayout>
+
+</LinearLayout>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="64dip"
+ android:gravity="bottom"
+ android:orientation="vertical"
+ android:baselineAligned="false">
+
+ <ImageView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:background="@android:drawable/divider_horizontal_dark" />
+
+ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="64dip"
+ android:gravity="bottom">
+
+ <include layout="@layout/track_list_item_common" />
+
+ </RelativeLayout>
+
+</LinearLayout>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical"
+ android:gravity="center_vertical" >
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:background="@drawable/ic_mp_sd_card" />
+
+ <TextView
+ android:id="@+id/sd_message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:padding="8dip"
+ android:text="@string/sdcard_missing_message"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <!-- Need a dummy ListView because this will be placed in a ListActivity -->
+ <ListView
+ android:id="@android:id/list"
+ android:layout_width="0px"
+ android:layout_height="0px"
+ android:visibility="gone" />
+
+</LinearLayout>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical"
+ android:gravity="center_vertical" >
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:background="@drawable/ic_mp_sd_card" />
+
+ <TextView
+ android:id="@+id/sd_message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:padding="8dip"
+ android:text="@string/sdcard_missing_message"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <!-- Need a dummy ListView because this will be placed in a ListActivity -->
+ <ExpandableListView
+ android:id="@android:id/list"
+ android:layout_width="0px"
+ android:layout_height="0px"
+ android:visibility="gone" />
+
+</LinearLayout>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:padding="10dip">
+
+ <ProgressBar android:id="@android:id/progress"
+ style="?android:attr/progressBarStyleLarge"
+ android:layout_gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView android:id="@+id/streamloading"
+ android:paddingTop="5dip"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:textSize="14sp"
+ android:textColor="#ffffffff" />
+
+</LinearLayout>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="64dip"
+ android:gravity="center_vertical"
+ android:ignoreGravity="@+id/icon">
+
+ <include layout="@layout/track_list_item_common" />
+
+</RelativeLayout>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="64dip"
+ android:background="@drawable/list_selector"
+ android:paddingLeft="24px">
+
+ <include layout="@layout/track_list_item_common" />
+
+</RelativeLayout>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <ImageView android:id="@+id/icon"
+ android:layout_alignParentLeft="true"
+ android:layout_centerVertical="true"
+ android:layout_marginLeft="4dip"
+ android:layout_width="60px"
+ android:layout_height="60px"/>
+
+ <TextView android:id="@+id/duration" android:textSize="14sp" android:textColor="#ffe0d090"
+ android:paddingLeft="4dip"
+ android:paddingRight="5dip"
+ android:layout_alignParentRight="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignBaseline="@+id/line1"
+ android:singleLine="true" />
+
+ <TextView android:id="@+id/line1"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:paddingLeft="4dip"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_alignWithParentIfMissing="true"
+ android:layout_toRightOf="@id/icon"
+ android:layout_toLeftOf="@id/duration"
+ android:ellipsize="end"
+ android:singleLine="true" />
+
+ <TextView android:id="@+id/line2" android:visibility="visible"
+ android:maxLines="2"
+ android:ellipsize="end"
+ android:paddingLeft="4dip"
+ android:textSize="14sp"
+ android:layout_below="@id/line1"
+ android:layout_alignWithParentIfMissing="true"
+ android:layout_toRightOf="@id/icon"
+ android:layout_toLeftOf="@id/duration"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <ImageView android:id="@+id/play_indicator"
+ android:layout_alignParentRight="true"
+ android:layout_alignBottom="@id/line2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="6dip" />
+
+</merge>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="64dip"
+ android:gravity="center_vertical"
+ android:ignoreGravity="@+id/icon"
+ android:paddingLeft="30px">
+
+ <include layout="@layout/track_list_item_common" />
+
+</RelativeLayout>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- Layout of time picker-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:text="@string/weekpicker_title"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_gravity="center_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <!-- weeks -->
+ <com.android.internal.widget.VerticalTextSpinner
+ android:id="@+id/weeks"
+ android:layout_width="120dip"
+ android:layout_height="120dip"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginTop="6px"
+ android:layout_marginBottom="6px"
+ />
+
+ <!-- Set button -->
+ <RelativeLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:padding="6px"
+ android:background="#ffffff" >
+
+ <Button android:id="@+id/set"
+ android:layout_width="120px"
+ android:layout_height="wrap_content"
+ android:text="@string/weekpicker_set"
+ android:layout_alignParentRight="true" />
+ </RelativeLayout>
+
+</LinearLayout>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <TextView android:id="@+id/prompt"
+ android:layout_width="fill_parent" android:layout_height="wrap_content"
+ android:text="@string/create_playlist_create_text_prompt"
+ android:layout_marginTop="8dip"
+ android:layout_marginBottom="8dip"
+ android:layout_marginLeft="8dip"
+ android:layout_marginRight="8dip">
+ </TextView>
+
+ <EditText android:id="@+id/playlist"
+ android:layout_width="fill_parent" android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:layout_marginBottom="8dip"
+ android:layout_marginLeft="8dip"
+ android:layout_marginRight="8dip">
+ <requestFocus />
+ </EditText>
+
+ <RelativeLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:padding="6px"
+ android:background="#ffffff" >
+
+ <Button android:id="@+id/create"
+ android:layout_width="120px" android:layout_height="wrap_content"
+ android:text="@string/create_playlist_create_text"
+ android:layout_alignParentLeft="true" />
+
+ <Button android:id="@+id/cancel"
+ android:layout_width="120px" android:layout_height="wrap_content"
+ android:text="@string/cancel"
+ android:layout_alignParentRight="true" />
+
+ </RelativeLayout>
+
+</LinearLayout>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="8dip"
+ android:layout_marginTop="8dip"
+ android:layout_marginLeft="8dip"
+ android:layout_marginRight="8dip">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_slide_keyboard"/>
+
+ <TextView android:id="@+id/prompt"
+ android:layout_width="fill_parent" android:layout_height="wrap_content"
+ android:text="@string/create_playlist_create_text_prompt"
+ android:layout_marginLeft="8dip">
+ </TextView>
+ </LinearLayout>
+
+ <EditText android:id="@+id/playlist"
+ android:layout_width="fill_parent" android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:visibility="gone"
+ android:layout_marginBottom="8dip">
+ <requestFocus />
+ </EditText>
+
+ <RelativeLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:padding="6px"
+ android:background="#ffffff" >
+
+ <Button android:id="@+id/create"
+ android:layout_width="120px" android:layout_height="wrap_content"
+ android:text="@string/create_playlist_create_text"
+ android:layout_alignParentLeft="true" />
+
+ <Button android:id="@+id/cancel"
+ android:layout_width="120px" android:layout_height="wrap_content"
+ android:text="@string/cancel"
+ android:layout_alignParentRight="true" />
+
+ </RelativeLayout>
+
+</LinearLayout>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical">
+
+
+ <!-- This is the LinearLayout that contains the album art, function buttons and album/artist/track info -->
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:orientation="horizontal">
+
+ <com.android.music.AlbumView android:id="@+id/album"
+ android:layout_width="200px"
+ android:layout_height="204px"
+ android:paddingLeft="20px"
+ android:paddingRight="15px"
+ android:paddingTop="20px" />
+
+ <!-- This is the LinearLayout that contains function buttons and album/artist/track info -->
+ <LinearLayout
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingTop="20px" >
+
+ <ImageButton android:id="@+id/curplaylist"
+ android:src="@drawable/ic_mp_current_playlist_btn"
+ android:layout_width="82px"
+ android:layout_height="45px"
+ android:layout_marginRight="8px" />
+
+ <ImageButton android:id="@+id/shuffle"
+ android:layout_width="82px"
+ android:layout_height="45px"
+ android:layout_marginRight="8px" />
+
+ <ImageButton android:id="@+id/repeat"
+ android:layout_width="82px"
+ android:layout_height="45px" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:baselineAligned="false"
+ android:paddingTop="8dip"
+ android:paddingBottom="2dip">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="4dip"
+ android:src="@drawable/ic_mp_artist_playback" />
+
+ <TextView android:id="@+id/artistname"
+ android:textSize="18sp"
+ android:maxLines="1"
+ android:textStyle="bold"
+ android:layout_gravity="center_vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:baselineAligned="false"
+ android:paddingTop="8dip"
+ android:paddingBottom="2dip">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="4dip"
+ android:src="@drawable/ic_mp_album_playback" />
+
+ <TextView android:id="@+id/albumname"
+ android:textSize="14sp"
+ android:maxLines="1"
+ android:textStyle="bold"
+ android:layout_gravity="center_vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:baselineAligned="false"
+ android:paddingTop="4dip"
+ android:paddingBottom="2dip">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="4dip"
+ android:src="@drawable/ic_mp_song_playback" />
+
+ <TextView android:id="@+id/trackname"
+ android:textSize="14sp"
+ android:maxLines="1"
+ android:textStyle="bold"
+ android:layout_gravity="center_vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <include layout="@layout/audio_player_common" />
+
+</LinearLayout>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:paddingTop="12px"
+ android:paddingLeft="12px"
+ android:paddingRight="12px"
+ android:orientation="horizontal"
+ >
+
+ <LinearLayout
+ android:layout_weight="1"
+ android:layout_width="0dip"
+ android:layout_height="fill_parent"
+ android:orientation="vertical"
+ >
+
+ <ImageButton
+ android:background="@drawable/main_menu_button"
+ android:id="@+id/browse_button"
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:src="@drawable/ic_mp_screen_artists"
+ />
+
+ <TextView
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:layout_gravity="center_horizontal"
+ android:gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="1" android:scrollHorizontally="true"
+ android:text="@string/browse_menu"
+ android:layout_marginRight="8px"
+ />
+
+ <ImageButton
+ android:background="@drawable/main_menu_button"
+ android:id="@+id/tracks_button"
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:src="@drawable/ic_mp_screen_tracks"
+ />
+
+ <TextView
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:layout_gravity="center_horizontal"
+ android:gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="1" android:scrollHorizontally="true"
+ android:text="@string/tracks_menu"
+ android:layout_marginRight="8px"
+ />
+ </LinearLayout>
+
+ <View
+ android:layout_width="12px"
+ android:layout_height="10px" />
+
+ <LinearLayout
+ android:layout_weight="1"
+ android:layout_width="0dip"
+ android:layout_height="fill_parent"
+ android:orientation="vertical"
+ >
+
+ <ImageButton
+ android:background="@drawable/main_menu_button"
+ android:id="@+id/albums_button"
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:src="@drawable/ic_mp_screen_albums"
+ />
+
+ <TextView
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:layout_gravity="center_horizontal"
+ android:gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="1" android:scrollHorizontally="true"
+ android:text="@string/albums_menu"
+ android:layout_marginLeft="8px"
+ />
+
+ <ImageButton
+ android:background="@drawable/main_menu_button"
+ android:id="@+id/playlists_button"
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:src="@drawable/ic_mp_screen_playlists"
+ />
+
+ <TextView
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:layout_gravity="center_horizontal"
+ android:gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="1" android:scrollHorizontally="true"
+ android:text="@string/playlists_menu"
+ android:layout_marginLeft="8px"
+ />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/nowplaying"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:focusable="true"
+ android:background="#ff404040"
+ android:visibility="invisible"
+ android:orientation="horizontal">
+
+ <ImageView android:id="@+id/icon"
+ style="@android:style/MediaButton.Play"
+ android:layout_marginLeft="8dip"
+ android:layout_marginTop="4dip"
+ android:layout_marginRight="6dip"
+ android:layout_marginBottom="4dip" />
+
+ <LinearLayout
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:orientation="vertical">
+
+ <TextView android:id="@+id/title" android:textAppearance="?android:attr/textAppearanceLarge"
+ android:singleLine="true" android:ellipsize="end"
+ android:layout_marginLeft="3dip"
+ android:layout_width="fill_parent" android:layout_height="wrap_content" />
+ <TextView android:id="@+id/artist" android:textAppearance="?android:attr/textAppearanceMedium"
+ android:singleLine="true" android:ellipsize="end"
+ android:layout_marginLeft="3dip"
+ android:layout_width="fill_parent" android:layout_height="wrap_content" />
+ </LinearLayout>
+ </LinearLayout>
+</LinearLayout>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <LinearLayout android:id="@+id/buttoncontainer"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+
+ </LinearLayout>
+</FrameLayout>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<com.android.music.TouchInterceptor xmlns:android="http://schemas.android.com/apk/res/android"
+
+ android:id="@android:id/list"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:textSize="18sp"
+ android:drawSelectorOnTop="false"
+
+/>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <VideoView
+ android:id="@+id/surface_view"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_centerInParent="true"
+ />
+
+ <LinearLayout android:id="@+id/progress_indicator"
+ android:orientation="vertical"
+ android:layout_centerInParent="true"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <ProgressBar android:id="@android:id/progress"
+ style="?android:attr/progressBarStyleLarge"
+ android:layout_gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView android:paddingTop="5dip"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:text="@string/loading_video" android:textSize="14sp"
+ android:textColor="#ffffffff" />
+ </LinearLayout>
+
+</RelativeLayout>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:paddingTop="17px"
+ android:paddingLeft="14px"
+ android:paddingRight="14px"
+ android:orientation="horizontal"
+ >
+
+ <LinearLayout
+ android:layout_weight="1"
+ android:layout_width="0dip"
+ android:layout_height="fill_parent"
+ android:orientation="vertical"
+ >
+
+ <ImageButton
+ android:background="@drawable/main_menu_button"
+ android:id="@+id/browse_button"
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:src="@drawable/ic_mp_screen_artists"
+ />
+
+ <TextView
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:layout_gravity="center_horizontal"
+ android:gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="1" android:scrollHorizontally="true"
+ android:text="@string/browse_menu"
+ android:layout_marginBottom="24px"
+ />
+
+ <ImageButton
+ android:background="@drawable/main_menu_button"
+ android:id="@+id/tracks_button"
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:src="@drawable/ic_mp_screen_tracks"
+ />
+
+ <TextView
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:layout_gravity="center_horizontal"
+ android:gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="1" android:scrollHorizontally="true"
+ android:text="@string/tracks_menu"
+ android:layout_marginBottom="24px"
+ />
+ </LinearLayout>
+
+ <View
+ android:layout_width="18px"
+ android:layout_height="10px" />
+
+ <LinearLayout
+ android:layout_weight="1"
+ android:layout_width="0dip"
+ android:layout_height="fill_parent"
+ android:orientation="vertical"
+ >
+
+ <ImageButton
+ android:background="@drawable/main_menu_button"
+ android:id="@+id/albums_button"
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:src="@drawable/ic_mp_screen_albums"
+ />
+
+ <TextView
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:layout_gravity="center_horizontal"
+ android:gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="1" android:scrollHorizontally="true"
+ android:text="@string/albums_menu"
+ android:layout_marginBottom="24px"
+ />
+
+ <ImageButton
+ android:background="@drawable/main_menu_button"
+ android:id="@+id/playlists_button"
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:src="@drawable/ic_mp_screen_playlists"
+ />
+
+ <TextView
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:layout_gravity="center_horizontal"
+ android:gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="1" android:scrollHorizontally="true"
+ android:text="@string/playlists_menu"
+ android:layout_marginBottom="24px"
+ />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/nowplaying"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:focusable="true"
+ android:background="#ff404040"
+ android:visibility="invisible"
+ android:orientation="horizontal">
+
+ <ImageView android:id="@+id/icon"
+ style="@android:style/MediaButton.Play"
+ android:layout_marginLeft="8dip"
+ android:layout_marginTop="4dip"
+ android:layout_marginRight="6dip"
+ android:layout_marginBottom="4dip" />
+
+ <LinearLayout
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:orientation="vertical">
+
+ <TextView android:id="@+id/title" android:textAppearance="?android:attr/textAppearanceLarge"
+ android:singleLine="true" android:ellipsize="end"
+ android:layout_marginLeft="3dip"
+ android:layout_width="fill_parent" android:layout_height="wrap_content" />
+ <TextView android:id="@+id/artist" android:textAppearance="?android:attr/textAppearanceMedium"
+ android:singleLine="true" android:ellipsize="end"
+ android:layout_marginLeft="3dip"
+ android:layout_width="fill_parent" android:layout_height="wrap_content" />
+ </LinearLayout>
+ </LinearLayout>
+</LinearLayout>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical">
+
+ <ListView android:id="@android:id/list"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:drawSelectorOnTop="false">
+ </ListView>
+</LinearLayout>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="12px">
+
+ <ProgressBar android:id="@+id/spinner"
+ style="?android:attr/progressBarStyleLarge"
+ android:layout_gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView android:id="@+id/message"
+ android:layout_gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:text="@string/scanning">
+ </TextView>
+
+</LinearLayout>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="horizontal">
+
+ <ImageView android:id="@+id/icon"
+ android:padding="4dip"
+ android:gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ </ImageView>
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <TextView android:id="@+id/trackname"
+ android:textAppearance="?android:attr/textAppearanceMediumInverse"
+ android:layout_gravity="left"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView android:id="@+id/artistalbum"
+ android:textAppearance="?android:attr/textAppearanceSmallInverse"
+ android:layout_gravity="left"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+</LinearLayout>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="NNNtracksdeleted">Bylo odstraněno <xliff:g id="songs_to_delete">%s</xliff:g> skladeb</string>
+ <string name="NNNtrackstoplaylist">Do seznamu stop bylo přidáno NNN skladeb</string>
+ <string name="Nalbums"><xliff:g id="count">%d</xliff:g> alb</string>
+ <string name="Nsongs"><xliff:g id="count">%d</xliff:g> skladeb</string>
+ <string name="add_to_playlist">Přidat do seznamu stop</string>
+ <string name="albums_menu">Alba</string>
+ <string name="albums_title">Alba</string>
+ <string name="all_title">Všechna média</string>
+ <string name="artists_title">Interpreti</string>
+ <string name="browse_menu">Interpreti</string>
+ <string name="create_playlist_create_text">Uložit</string>
+ <string name="create_playlist_overwrite_text">Přepsat</string>
+ <string name="delete_album_desc">celé album \"<xliff:g id="album">%s</xliff:g>\"</string>
+ <string name="delete_artist_desc">všechny skladby od interpreta <xliff:g id="artist">%s</xliff:g></string>
+ <string name="delete_confirm_button_text">Odstranit</string>
+ <string name="delete_confirm_prompt">Trvale odstranit <xliff:g id="to_delete">%s</xliff:g> z karty SD?</string>
+ <string name="delete_item">Odstranit</string>
+ <string name="delete_playlist_menu">Odstranit</string>
+ <string name="durationformat"><xliff:g id="format">%2$d:%5$02d</xliff:g></string>
+ <string name="edit_playlist_menu">Upravit</string>
+ <string name="emptyplaylist">Vybraný seznam stop je prázdný</string>
+ <string name="goto_playback">Přehrávání</string>
+ <string name="goto_start">Knihovna</string>
+ <string name="mediapickerlabel">Hudba</string>
+ <string name="mediaplaybacklabel">Hudba</string>
+ <string name="movieviewlabel">Filmy</string>
+ <string name="musicbrowserlabel">Hudba</string>
+ <string name="musicsearch">Hudba</string>
+ <string name="musicsearchhint">Interpret, album nebo název skladby</string>
+ <string name="new_playlist">Nový\u2026</string>
+ <string name="new_playlist_name_template">Nový seznam stop <xliff:g id="number">%d</xliff:g></string>
+ <string name="no_albums_title">Žádná alba</string>
+ <string name="no_playlists_title">Žádné seznamy stop</string>
+ <string name="no_tracks_title">Žádné skladby</string>
+ <string name="no_videos_title">Žádná videa</string>
+ <string name="nowplaying_title">Přehrávání</string>
+ <string name="onealbum">1 album</string>
+ <string name="onesong">1 skladba</string>
+ <string name="party_shuffle">Náhodné přehrávání</string>
+ <string name="party_shuffle_off">Náhodné přehrávání vypnuto</string>
+ <string name="partyshuffle_title">Náhodné přehrávání</string>
+ <string name="play_all">Přehrát vše</string>
+ <string name="play_selection">Přehrát</string>
+ <string name="playlists_menu">Seznamy stop</string>
+ <string name="playlists_title">Seznamy stop</string>
+ <string name="recentlyadded">Naposledy přidané</string>
+ <string name="rename_playlist_menu">Přejmenovat</string>
+ <string name="repeat_all_notif">Opakování všech skladeb</string>
+ <string name="repeat_current_notif">Opakování aktuální skladby</string>
+ <string name="repeat_off_notif">Opakování je vypnuto</string>
+ <string name="ringtone_menu">Použít jako vyzv. tón</string>
+ <string name="save_as_playlist">Uložit jako seznam stop</string>
+ <string name="scanning">Prohledávání karty SD\u2026</string>
+ <string name="sdcard_busy_title">Karta SD je zaneprázdněna</string>
+ <string name="sdcard_error_title">Chyba karty SD</string>
+ <string name="sdcard_missing_title">Karta SD chybí</string>
+ <string name="search_title">Hledat</string>
+ <string name="service_start_error_button">OK</string>
+ <string name="service_start_error_msg">Nelze spustit službu přehrávání médií</string>
+ <string name="service_start_error_title">Chyba</string>
+ <string name="shuffle_all">Náhodně vše</string>
+ <string name="shuffle_off_notif">Náhodné přehrávání je vypnuto</string>
+ <string name="shuffle_on_notif">Náhodné přehrávání je zapnuto</string>
+ <string name="tracks_menu">Skladby</string>
+ <string name="tracks_title">Skladby</string>
+ <string name="unknown_album_name">Neznámé album</string>
+ <string name="unknown_artist_name">Neznámý interpret</string>
+ <string name="videobrowserlabel">Videa</string>
+ <string name="videos_title">Videa</string>
+ <string name="weekpicker_set">Hotovo</string>
+ <string name="weekpicker_title">Nastavit čas</string>
+</resources>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="add_to_playlist">Zur Wiedergabeliste hinzufügen</string>
+ <string name="albums_menu">Alben</string>
+ <string name="albums_title">Alben</string>
+ <string name="all_title">Alle Medien</string>
+ <string name="artists_title">Künstler</string>
+ <string name="browse_menu">Interpreten</string>
+ <string name="cancel">Abbrechen</string>
+ <string name="clear_playlist">Wiedergabeliste löschen</string>
+ <string name="create_playlist_create_text">Speichern</string>
+ <string name="create_playlist_overwrite_text">Überschreiben</string>
+ <string name="delete_album_desc">das gesamte Album \"<xliff:g id="album">%s</xliff:g>\"</string>
+ <string name="delete_artist_desc">alle Lieder von <xliff:g id="artist">%s</xliff:g></string>
+ <string name="delete_confirm_button_text">OK</string>
+ <string name="delete_confirm_prompt">\"<xliff:g id="to_delete">%s</xliff:g>\" wird permanent aus der SD-Karte entfernt.</string>
+ <string name="delete_item">Löschen</string>
+ <string name="delete_playlist_menu">Löschen</string>
+ <string name="durationformat">
+ <xliff:g id="format">%2$d:%5$02d</xliff:g>
+ </string>
+ <string name="edit_playlist_menu">Bearbeiten</string>
+ <string name="emptyplaylist">Die ausgewählte Wiedergabeliste ist leer.</string>
+ <string name="goto_playback">Wiedergabe</string>
+ <string name="goto_start">Bibliothek</string>
+ <string name="loading_video">Lädt Video\u2026</string>
+ <string name="mediapickerlabel">Musik</string>
+ <string name="mediaplaybacklabel">Musik</string>
+ <string name="mediasearch">Suche nach %s mit:</string>
+ <string name="mediasearch_amazon">Amazon MP3</string>
+ <string name="mediasearch_google">Google</string>
+ <string name="mediasearch_youtube">YouTube</string>
+ <string name="movieviewlabel">Filme</string>
+ <string name="musicbrowserlabel">Musik</string>
+ <string name="musicsearch">Musik</string>
+ <string name="musicsearchhint">Künstler, Album oder Liedtitel</string>
+ <string name="musicshortcutlabel">Musikwiedergabeliste</string>
+ <string name="new_playlist">Neu</string>
+ <string name="new_playlist_name_template">Neue Wiedergabeliste <xliff:g id="number">%d</xliff:g></string>
+ <string name="no_albums_title">Keine Alben</string>
+ <string name="no_playlists_title">Keine Wiedergabelisten</string>
+ <string name="no_tracks_title">Keine Lieder</string>
+ <string name="no_videos_title">Keine Videos</string>
+ <string name="nowplaying_title">Aktuelle Wiedergabe</string>
+ <string name="onesong">1 Lied</string>
+ <string name="party_shuffle">Partymix</string>
+ <string name="party_shuffle_off">Partymix aus</string>
+ <string name="partyshuffle_title">Partymix</string>
+ <string name="play_all">Alle wiedergeben</string>
+ <string name="play_selection">Wiedergabe</string>
+ <string name="playback_failed">Der Player unterstützt diese Art von Audiodatei nicht."</string>
+ <string name="playlist_deleted_message">Wiedergabeliste gelöscht.</string>
+ <string name="playlist_renamed_message">Wiedergabeliste umbenannt.</string>
+ <string name="playlists_menu">Wiedergabelisten</string>
+ <string name="playlists_title">Wiedergabelisten</string>
+ <string name="queue">Aktuelle Wiedergabeliste</string>
+ <string name="recentlyadded">Zuletzt hinzugefügt</string>
+ <string name="remove_from_playlist">Aus Wiedergabeliste entfernen</string>
+ <string name="rename_playlist_menu">Umbenennen</string>
+ <string name="repeat_all_notif">Alle Lieder wiederholen.</string>
+ <string name="repeat_current_notif">Aktuelles Lied wiederholen.</string>
+ <string name="repeat_off_notif">Wiederholung aus.</string>
+ <string name="ringtone_menu">Als Telefonklingelton verwenden</string>
+ <string name="ringtone_menu_short">Als Klingelton verwenden</string>
+ <string name="ringtone_set">\"%s\" als Klingelton einrichten.</string>
+ <string name="save_as_playlist">Als Wiedergabeliste speichern</string>
+ <string name="scanning">SD-Karte durchsuchen\u2026</string>
+ <string name="sdcard_busy_message">Die SD-Karte wird derzeit benutzt. Um auf die Musik zuzugreifen, gehen Sie nach Startseite > Einstellungen > SD-Karte & Telefonspeicher und heben Sie die Auswahl von \"Für USB-Speicher verwenden\" auf.</string>
+ <string name="sdcard_busy_title">SD-Karte wird benutzt</string>
+ <string name="sdcard_error_message">SD-Karten-Fehler.</string>
+ <string name="sdcard_error_title">SD-Karten-Fehler</string>
+ <string name="sdcard_missing_message">Keine SD-Karte.</string>
+ <string name="sdcard_missing_title">Keine SD-Karte</string>
+ <string name="search_title">Suchen</string>
+ <string name="service_start_error_button">OK</string>
+ <string name="service_start_error_msg">Das Lied konnte leider nicht wiedergegeben werden.</string>
+ <string name="service_start_error_title">Wiedergabeproblem</string>
+ <string name="shuffle_all">Alle zufällig wiedergeben</string>
+ <string name="shuffle_off_notif">Zufällige Wiedergabe aus.</string>
+ <string name="shuffle_on_notif">Zufällige Wiedergabe ein.</string>
+ <string name="streamloadingtext">Verbindet mit <xliff:g id="host">%s</xliff:g></string>
+ <string name="tracks_menu">Lieder</string>
+ <string name="tracks_title">Lieder</string>
+ <string name="unknown_album_name">Unbekanntes Album</string>
+ <string name="unknown_artist_name">Unbekannter Künstler</string>
+ <string name="videobrowserlabel">Videos</string>
+ <string name="videos_title">Videos</string>
+ <string name="weekpicker_set">Fertig</string>
+ <string name="weekpicker_title">Zeit einstellen</string>
+</resources>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="add_to_playlist">Add to playlist</string>
+ <string name="albums_menu">Albums</string>
+ <string name="albums_title">Albums</string>
+ <string name="all_title">All media</string>
+ <string name="artists_title">Artists</string>
+ <string name="browse_menu">Artists</string>
+ <string name="cancel">Cancel</string>
+ <string name="clear_playlist">Clear playlist</string>
+ <string name="create_playlist_create_text">Save</string>
+ <string name="create_playlist_overwrite_text">Overwrite</string>
+ <string name="delete_album_desc">the entire album \"<xliff:g id="album">%s</xliff:g>\"</string>
+ <string name="delete_artist_desc">all songs by <xliff:g id="artist">%s</xliff:g></string>
+ <string name="delete_confirm_button_text">OK</string>
+ <string name="delete_confirm_prompt">\"<xliff:g id="to_delete">%s</xliff:g>\" will be permanently deleted from the SD card.</string>
+ <string name="delete_item">Delete</string>
+ <string name="delete_playlist_menu">Delete</string>
+ <string name="durationformat"><xliff:g id="format">%2$d:%5$02d</xliff:g></string>
+ <string name="edit_playlist_menu">Edit</string>
+ <string name="emptyplaylist">Selected playlist is empty.</string>
+ <string name="goto_playback">Playback</string>
+ <string name="goto_start">Library</string>
+ <string name="loading_video">Loading video\u2026</string>
+ <string name="mediapickerlabel">Music</string>
+ <string name="mediaplaybacklabel">Music</string>
+ <string name="movieviewlabel">Movies</string>
+ <string name="musicbrowserlabel">Music</string>
+ <string name="musicsearch">Music</string>
+ <string name="musicsearchhint">Artist, album, or song name</string>
+ <string name="musicshortcutlabel">Music playlist</string>
+ <string name="new_playlist">New\u2026</string>
+ <string name="new_playlist_name_template">New playlist <xliff:g id="number">%d</xliff:g></string>
+ <string name="no_albums_title">No albums</string>
+ <string name="no_playlists_title">No playlists</string>
+ <string name="no_tracks_title">No songs</string>
+ <string name="no_videos_title">No videos</string>
+ <string name="nowplaying_title">Now playing</string>
+ <string name="onesong">1 song</string>
+ <string name="party_shuffle">Party shuffle</string>
+ <string name="party_shuffle_off">Party shuffle off</string>
+ <string name="partyshuffle_title">Party shuffle</string>
+ <string name="play_all">Play all</string>
+ <string name="play_selection">Play</string>
+ <string name="playback_failed">Sorry, the player does not support this type of audio file."</string>
+ <string name="playlist_deleted_message">Playlist deleted.</string>
+ <string name="playlist_renamed_message">Playlist renamed.</string>
+ <string name="playlists_menu">Playlists</string>
+ <string name="playlists_title">Playlists</string>
+ <string name="queue">Current playlist</string>
+ <string name="recentlyadded">Recently added</string>
+ <string name="remove_from_playlist">Remove from playlist</string>
+ <string name="rename_playlist_menu">Rename</string>
+ <string name="repeat_all_notif">Repeating all songs.</string>
+ <string name="repeat_current_notif">Repeating current song.</string>
+ <string name="repeat_off_notif">Repeat is off.</string>
+ <string name="ringtone_menu">Use as phone ringtone</string>
+ <string name="ringtone_menu_short">Use as ringtone</string>
+ <string name="ringtone_set">\"%s\" set as phone ringtone.</string>
+ <string name="save_as_playlist">Save as playlist</string>
+ <string name="scanning">Scanning SD card\u2026</string>
+ <string name="sdcard_busy_title">SD card is busy</string>
+ <string name="sdcard_error_title">SD card error</string>
+ <string name="sdcard_missing_title">No SD card</string>
+ <string name="search_title">Search</string>
+ <string name="service_start_error_button">OK</string>
+ <string name="service_start_error_msg">Sorry, the song could not be played.</string>
+ <string name="service_start_error_title">Playback problem</string>
+ <string name="shuffle_all">Shuffle all</string>
+ <string name="shuffle_off_notif">Shuffle is off.</string>
+ <string name="shuffle_on_notif">Shuffle is on.</string>
+ <string name="tracks_menu">Songs</string>
+ <string name="tracks_title">Songs</string>
+ <string name="unknown_album_name">Unknown album</string>
+ <string name="unknown_artist_name">Unknown artist</string>
+ <string name="videobrowserlabel">Videos</string>
+ <string name="videos_title">Videos</string>
+ <string name="weekpicker_set">Done</string>
+ <string name="weekpicker_title">Set time</string>
+</resources>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="add_to_playlist">Añadir a Lista de reproducción</string>
+ <string name="albums_menu">Álbumes</string>
+ <string name="albums_title">Álbumes</string>
+ <string name="all_title">Todos los medios</string>
+ <string name="artists_title">Artistas</string>
+ <string name="browse_menu">Artistas</string>
+ <string name="cancel">Cancelar</string>
+ <string name="clear_playlist">Borrar lista de reproducción</string>
+ <string name="create_playlist_create_text">Guardar</string>
+ <string name="create_playlist_overwrite_text">Sobrescribir</string>
+ <string name="delete_album_desc">todo el álbum \"<xliff:g id="album">%s</xliff:g>\"</string>
+ <string name="delete_artist_desc">todas las canciones de <xliff:g id="artist">%s</xliff:g></string>
+ <string name="delete_confirm_button_text">Aceptar</string>
+ <string name="delete_confirm_prompt">\"<xliff:g id="to_delete">%s</xliff:g>\" se eliminará de forma permanente de la tarjeta SD. </string>
+ <string name="delete_item">Eliminar</string>
+ <string name="delete_playlist_menu">Eliminar</string>
+ <string name="durationformat">
+ <xliff:g id="format">%2$d:%5$02d</xliff:g>
+ </string>
+ <string name="edit_playlist_menu">Editar</string>
+ <string name="emptyplaylist">La lista de reproducción seleccionada está vacía. </string>
+ <string name="goto_playback">Reproducción</string>
+ <string name="goto_start">Biblioteca</string>
+ <string name="loading_video">Cargando vídeo\u2026</string>
+ <string name="mediapickerlabel">Música</string>
+ <string name="mediaplaybacklabel">Música</string>
+ <string name="mediasearch">Buscar %s usando:</string>
+ <string name="mediasearch_amazon">Amazon MP3</string>
+ <string name="mediasearch_google">Google</string>
+ <string name="mediasearch_youtube">YouTube</string>
+ <string name="movieviewlabel">Películas</string>
+ <string name="musicbrowserlabel">Música</string>
+ <string name="musicsearch">Música</string>
+ <string name="musicsearchhint">Artista, álbum o nombre de canción</string>
+ <string name="musicshortcutlabel">Lista de música</string>
+ <string name="new_playlist">Nuevo</string>
+ <string name="new_playlist_name_template">Nueva lista de reproducción <xliff:g id="number">%d</xliff:g></string>
+ <string name="no_albums_title">No hay álbumes</string>
+ <string name="no_playlists_title">No hay listas de reproducción</string>
+ <string name="no_tracks_title">No hay canciones</string>
+ <string name="no_videos_title">No hay vídeos</string>
+ <string name="nowplaying_title">Reproduciendo</string>
+ <string name="onesong">1 canción</string>
+ <string name="party_shuffle">Lista aleatoria</string>
+ <string name="party_shuffle_off">Lista aleatoria desactivada</string>
+ <string name="partyshuffle_title">Lista aleatoria</string>
+ <string name="play_all">Reproducir todo</string>
+ <string name="play_selection">Reproducir</string>
+ <string name="playback_failed">Lo siento, el reproductor no admite este tipo de archivo de audio. "</string>
+ <string name="playlist_deleted_message">Lista de reproducción eliminada.</string>
+ <string name="playlist_renamed_message">Lista de reproducción con un nombre diferente.</string>
+ <string name="playlists_menu">Listas de reproducción</string>
+ <string name="playlists_title">Listas de reproducción</string>
+ <string name="queue">Lista de reproducción actual</string>
+ <string name="recentlyadded">Agregado recientemente</string>
+ <string name="remove_from_playlist">Eliminar de la lista de reproducción</string>
+ <string name="rename_playlist_menu">Cambiar nombre</string>
+ <string name="repeat_all_notif">Repitiendo todas las canciones.</string>
+ <string name="repeat_current_notif">Repitiendo la canción actual</string>
+ <string name="repeat_off_notif">La repetición está desactivada.</string>
+ <string name="ringtone_menu">Utilizar como tono de timbre.</string>
+ <string name="ringtone_menu_short">Utilizar como tono de timbre</string>
+ <string name="ringtone_set">\"%s\" configurado como tono de timbre del teléfono.</string>
+ <string name="save_as_playlist">Guardar como lista de reproducción</string>
+ <string name="scanning">Explorando tarjeta SD\u2026</string>
+ <string name="sdcard_busy_message">La tarjeta SD está ocupada. Para acceder a su música, vaya a Inicio > Configuración > Tarjeta SD y memoria de almacenamiento y desactive la casilla de verificación \"Utilizar para almacenamiento USB\".</string>
+ <string name="sdcard_busy_title">La tarjeta SD está ocupada</string>
+ <string name="sdcard_error_message">Error de la tarjeta SD.</string>
+ <string name="sdcard_error_title">Error de la tarjeta SD</string>
+ <string name="sdcard_missing_message">Ninguna tarjeta SD.</string>
+ <string name="sdcard_missing_title">Ninguna tarjeta SD</string>
+ <string name="search_title">Buscar</string>
+ <string name="service_start_error_button">Aceptar</string>
+ <string name="service_start_error_msg">Lo sentimos, no se ha podido reproducir la canción.</string>
+ <string name="service_start_error_title">Problema de reproducción</string>
+ <string name="shuffle_all">Todo en orden aleatorio</string>
+ <string name="shuffle_off_notif">La opción orden aleatorio está desactivada.</string>
+ <string name="shuffle_on_notif">La opción orden aleatorio está activada.</string>
+ <string name="streamloadingtext">Conectando con <xliff:g id="host">%s</xliff:g></string>
+ <string name="tracks_menu">Canciones</string>
+ <string name="tracks_title">Canciones</string>
+ <string name="unknown_album_name">Álbum desconocido</string>
+ <string name="unknown_artist_name">Artista desconocido</string>
+ <string name="videobrowserlabel">Vídeos</string>
+ <string name="videos_title">Vídeos</string>
+ <string name="weekpicker_set">Listo</string>
+ <string name="weekpicker_title">Establecer hora</string>
+</resources>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="albumsongseparator">\n</string>
+
+ <string name="notification_artist_album"><xliff:g id="album">%2$s</xliff:g>\n<xliff:g id="artist">%1$s</xliff:g></string>
+</resources>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="add_to_playlist">Ajouter à sélection</string>
+ <string name="albums_menu">Albums</string>
+ <string name="albums_title">Albums</string>
+ <string name="all_title">Tous les médias</string>
+ <string name="artists_title">Artistes</string>
+ <string name="browse_menu">Artistes</string>
+ <string name="cancel">Annuler</string>
+ <string name="clear_playlist">Effacer la sélection</string>
+ <string name="create_playlist_create_text">Enregistrer</string>
+ <string name="create_playlist_overwrite_text">Écraser</string>
+ <string name="delete_album_desc">l\'album entier \"<xliff:g id="album">%s</xliff:g>\"</string>
+ <string name="delete_artist_desc">toutes les chansons par <xliff:g id="artist">%s</xliff:g></string>
+ <string name="delete_confirm_button_text">OK</string>
+ <string name="delete_confirm_prompt">\"<xliff:g id="to_delete">%s</xliff:g>\" seront supprimés de manière permanente de la carte SD.</string>
+ <string name="delete_item">Supprimer</string>
+ <string name="delete_playlist_menu">Supprimer</string>
+ <string name="durationformat">
+ <xliff:g id="format">%2$d:%5$02d</xliff:g>
+ </string>
+ <string name="edit_playlist_menu">Modifier</string>
+ <string name="emptyplaylist">La sélection choisie est vide.</string>
+ <string name="goto_playback">Lecture</string>
+ <string name="goto_start">Bibliothèque</string>
+ <string name="loading_video">Chargement de la vidéo\u2026</string>
+ <string name="mediapickerlabel">Musique</string>
+ <string name="mediaplaybacklabel">Musique</string>
+ <string name="mediasearch">Rechercher %s en utilisant :</string>
+ <string name="mediasearch_amazon">Amazon MP3</string>
+ <string name="mediasearch_google">Google</string>
+ <string name="mediasearch_youtube">YouTube</string>
+ <string name="movieviewlabel">Films</string>
+ <string name="musicbrowserlabel">Musique</string>
+ <string name="musicsearch">Musique</string>
+ <string name="musicsearchhint">Artiste, album ou nom de chanson</string>
+ <string name="musicshortcutlabel">Sélection musicale</string>
+ <string name="new_playlist">Nouveau</string>
+ <string name="new_playlist_name_template">Nouvelle sélection <xliff:g id="number">%d</xliff:g></string>
+ <string name="no_albums_title">Aucun album</string>
+ <string name="no_playlists_title">Aucune sélection</string>
+ <string name="no_tracks_title">Aucune chanson</string>
+ <string name="no_videos_title">Aucune vidéo</string>
+ <string name="nowplaying_title">Lecture en cours</string>
+ <string name="onesong">1 chanson</string>
+ <string name="party_shuffle">Lecture aléatoire de fête</string>
+ <string name="party_shuffle_off">Lecture aléatoire de fête désactivée</string>
+ <string name="partyshuffle_title">Lecture aléatoire de fête</string>
+ <string name="play_all">Lire tout</string>
+ <string name="play_selection">Lecture</string>
+ <string name="playback_failed">Désolé, le lecteur ne prend pas en charge ce type de fichier audio."</string>
+ <string name="playlist_deleted_message">Sélection supprimée.</string>
+ <string name="playlist_renamed_message">Sélection renommée.</string>
+ <string name="playlists_menu">Sélections</string>
+ <string name="playlists_title">Sélections</string>
+ <string name="queue">Sélection actuelle</string>
+ <string name="recentlyadded">Récemment ajoutée</string>
+ <string name="remove_from_playlist">Supprimer de la sélection</string>
+ <string name="rename_playlist_menu">Renommer</string>
+ <string name="repeat_all_notif">Répéter toutes les chansons.</string>
+ <string name="repeat_current_notif">Répéter la chanson actuelle.</string>
+ <string name="repeat_off_notif">Répétition désactivée.</string>
+ <string name="ringtone_menu">Utiliser comme sonnerie du téléphone</string>
+ <string name="ringtone_menu_short">Utiliser comme sonnerie</string>
+ <string name="ringtone_set">\"%s\" défini comme sonnerie du téléphone.</string>
+ <string name="save_as_playlist">Enregistrer comme sélection</string>
+ <string name="scanning">Recherche de la carte SD\u2026</string>
+ <string name="sdcard_busy_message">Carte SD occupée. Pour accéder à votre musique, allez à Accueil > Paramètres > Stockage du téléphone et carte SD et désélectionnez la case \"Utiliser pour le stockage USB\".</string>
+ <string name="sdcard_busy_title">Carte SD occupée</string>
+ <string name="sdcard_error_message">Erreur de carte SD.</string>
+ <string name="sdcard_error_title">Erreur de carte SD</string>
+ <string name="sdcard_missing_message">Pas de carte SD.</string>
+ <string name="sdcard_missing_title">Pas de carte SD</string>
+ <string name="search_title">Recherche</string>
+ <string name="service_start_error_button">OK</string>
+ <string name="service_start_error_msg">Désolé, la chanson n\'a pas pu être lue.</string>
+ <string name="service_start_error_title">Problème de lecture</string>
+ <string name="shuffle_all">Lecture aléatoire de tout</string>
+ <string name="shuffle_off_notif">Lecture aléatoire désactivée.</string>
+ <string name="shuffle_on_notif">Lecture aléatoire activée.</string>
+ <string name="streamloadingtext">Connexion à <xliff:g id="host">%s</xliff:g></string>
+ <string name="tracks_menu">Chansons</string>
+ <string name="tracks_title">Chansons</string>
+ <string name="unknown_album_name">Album inconnu</string>
+ <string name="unknown_artist_name">Artiste inconnu</string>
+ <string name="videobrowserlabel">Vidéos</string>
+ <string name="videos_title">Vidéos</string>
+ <string name="weekpicker_set">Terminé</string>
+ <string name="weekpicker_title">Définir l\'heure</string>
+</resources>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="add_to_playlist">Aggiungi a elenco di riproduzione</string>
+ <string name="albums_menu">Album</string>
+ <string name="albums_title">Album</string>
+ <string name="all_title">Tutti i supporti</string>
+ <string name="artists_title">Artisti</string>
+ <string name="browse_menu">Artisti</string>
+ <string name="cancel">Annulla</string>
+ <string name="clear_playlist">Cancella elenco di riproduzione</string>
+ <string name="create_playlist_create_text">Salva</string>
+ <string name="create_playlist_overwrite_text">Sovrascrivi</string>
+ <string name="delete_album_desc">l'intero album \"<xliff:g id="album">%s</xliff:g>\"</string>
+ <string name="delete_artist_desc">tutti i brani di <xliff:g id="artist">%s</xliff:g></string>
+ <string name="delete_confirm_button_text">OK</string>
+ <string name="delete_confirm_prompt">\"<xliff:g id="to_delete">%s</xliff:g>\" verrà eliminato in maniera permanente dalla scheda SD.</string>
+ <string name="delete_item">Elimina</string>
+ <string name="delete_playlist_menu">Elimina</string>
+ <string name="durationformat">
+ <xliff:g id="format">%2$d:%5$02d</xliff:g>
+ </string>
+ <string name="edit_playlist_menu">Modifica</string>
+ <string name="emptyplaylist">L'elenco di riproduzione selezionato è vuoto.</string>
+ <string name="goto_playback">Riproduzione</string>
+ <string name="goto_start">Catalogo</string>
+ <string name="loading_video">Caricamento del video in corso\u2026</string>
+ <string name="mediapickerlabel">Musica</string>
+ <string name="mediaplaybacklabel">Musica</string>
+ <string name="mediasearch">Ricerca %s utilizzando:</string>
+ <string name="mediasearch_amazon">Amazon MP3</string>
+ <string name="mediasearch_google">Google</string>
+ <string name="mediasearch_youtube">YouTube</string>
+ <string name="movieviewlabel">Filmati</string>
+ <string name="musicbrowserlabel">Musica</string>
+ <string name="musicsearch">Musica</string>
+ <string name="musicsearchhint">Nome artista, album o brano</string>
+ <string name="musicshortcutlabel">Elenco di riproduzione musicale</string>
+ <string name="new_playlist">Nuovo</string>
+ <string name="new_playlist_name_template">Nuovo elenco di riproduzione <xliff:g id="number">%d</xliff:g></string>
+ <string name="no_albums_title">Nessun album</string>
+ <string name="no_playlists_title">Nessun elenco di riproduzione</string>
+ <string name="no_tracks_title">Nessun brano</string>
+ <string name="no_videos_title">Nessun video</string>
+ <string name="nowplaying_title">In esecuzione</string>
+ <string name="onesong">Brano 1</string>
+ <string name="party_shuffle">Riproduzione casuale parziale</string>
+ <string name="party_shuffle_off">Riproduzione casuale parziale disattivata</string>
+ <string name="partyshuffle_title">Riproduzione casuale parziale</string>
+ <string name="play_all">Riproduci tutto</string>
+ <string name="play_selection">Riproduci</string>
+ <string name="playback_failed">Il lettore non supporta questo tipo di file audio."</string>
+ <string name="playlist_deleted_message">Elenco di riproduzione eliminato.</string>
+ <string name="playlist_renamed_message">Elenco di riproduzione rinominato.</string>
+ <string name="playlists_menu">Elenchi di riproduzione</string>
+ <string name="playlists_title">Elenchi di riproduzione</string>
+ <string name="queue">Elenco di riproduzione corrente</string>
+ <string name="recentlyadded">Aggiunto recentemente</string>
+ <string name="remove_from_playlist">Rimuovi da elenco di riproduzione</string>
+ <string name="rename_playlist_menu">Rinomina</string>
+ <string name="repeat_all_notif">Ripetizione di tutti i brani.</string>
+ <string name="repeat_current_notif">Ripetizione del bano corrente.</string>
+ <string name="repeat_off_notif">Ripetizione disattivata.</string>
+ <string name="ringtone_menu">Utilizzare come suoneria telefonica</string>
+ <string name="ringtone_menu_short">Utilizzare come suoneria</string>
+ <string name="ringtone_set">\"%s\" impostato come suoneria telefonica.</string>
+ <string name="save_as_playlist">Salva come elenco di riproduzione</string>
+ <string name="scanning">Scansione scheda SD\u2026</string>
+ <string name="sdcard_busy_message">Scheda SD occupata. Per accedere alla musica, andare su Home > Impostazioni> Scheda SD e memoria telefono e deselezionare la casella di controllo \"Usa per dispositivo di memorizzazione USB\".</string>
+ <string name="sdcard_busy_title">La scheda SD è occupata</string>
+ <string name="sdcard_error_message">Errore scheda SD.</string>
+ <string name="sdcard_error_title">Errore scheda SD</string>
+ <string name="sdcard_missing_message">Nessuna scheda SD.</string>
+ <string name="sdcard_missing_title">Nessuna scheda SD</string>
+ <string name="search_title">Cerca</string>
+ <string name="service_start_error_button">OK</string>
+ <string name="service_start_error_msg">Impossibile riprodurre il brano.</string>
+ <string name="service_start_error_title">Problema di riproduzione</string>
+ <string name="shuffle_all">Riproduzione casuale di tutto</string>
+ <string name="shuffle_off_notif">La riproduzione casuale è disattivata.</string>
+ <string name="shuffle_on_notif">La riproduzione casuale è attivata.</string>
+ <string name="streamloadingtext">Collegamento a <xliff:g id="host">%s</xliff:g></string>
+ <string name="tracks_menu">Brani</string>
+ <string name="tracks_title">Brani</string>
+ <string name="unknown_album_name">Album sconosciuto</string>
+ <string name="unknown_artist_name">Artista sconosciuto</string>
+ <string name="videobrowserlabel">Video</string>
+ <string name="videos_title">Video</string>
+ <string name="weekpicker_set">Completato</string>
+ <string name="weekpicker_title">Imposta ora</string>
+</resources>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<resources>
+ <string name="create_playlist_create_text_prompt">Playlist name</string>
+ <string name="rename_playlist_same_prompt">Rename \"%s\" to</string>
+ <string name="rename_playlist_diff_prompt">Rename \"%s\" to</string>
+</resources>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<resources>
+ <string name="create_playlist_create_text_prompt">Open the keyboard to give your new Playlist a name, or select Save to name it \"%s\".</string>
+
+ <!-- When the entered name is the same as the old one, show this -->
+ <string name="rename_playlist_same_prompt">Open the keyboard to give playlist \"%s\" a new name.</string>
+ <!-- When the entered name is different, show this -->
+ <string name="rename_playlist_diff_prompt">Open the keyboard to give playlist \"%s\" a new name, or select Save to name it \"%s\".</string>
+</resources>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="NNNtracksdeleted"><xliff:g id="songs_to_delete">%s</xliff:g> nummers verwijderd</string>
+ <string name="NNNtrackstoplaylist">NNN nummers toegevoegd aan afspeellijst</string>
+ <string name="Nalbums"><xliff:g id="count">%d</xliff:g> albums</string>
+ <string name="Nsongs"><xliff:g id="count">%d</xliff:g> nummers</string>
+ <string name="add_to_playlist">Toevoegen aan speellijst</string>
+ <string name="albums_menu">Albums</string>
+ <string name="albums_title">Albums</string>
+ <string name="all_title">Alle media</string>
+ <string name="artists_title">Artiesten</string>
+ <string name="browse_menu">Artiesten</string>
+ <string name="create_playlist_create_text">Opslaan</string>
+ <string name="create_playlist_overwrite_text">Overschrijven</string>
+ <string name="delete_album_desc">het hele album \"<xliff:g id="album">%s</xliff:g>\"</string>
+ <string name="delete_artist_desc">alle nummers van <xliff:g id="artist">%s</xliff:g></string>
+ <string name="delete_confirm_button_text">Verwijderen</string>
+ <string name="delete_confirm_prompt"><xliff:g id="to_delete">%s</xliff:g> permanent van sd-kaart verwijderen?</string>
+ <string name="delete_item">Verwijderen</string>
+ <string name="delete_playlist_menu">Verwijderen</string>
+ <string name="durationformat"><xliff:g id="format">%2$d:%5$02d</xliff:g></string>
+ <string name="edit_playlist_menu">Bewerken</string>
+ <string name="emptyplaylist">De geselecteerde afspeellijst is leeg</string>
+ <string name="goto_playback">Afspelen</string>
+ <string name="goto_start">Bibliotheek</string>
+ <string name="mediapickerlabel">Muziek</string>
+ <string name="mediaplaybacklabel">Muziek</string>
+ <string name="movieviewlabel">Films</string>
+ <string name="musicbrowserlabel">Muziek</string>
+ <string name="musicsearch">Muziek</string>
+ <string name="musicsearchhint">Artiest, album of naam</string>
+ <string name="new_playlist">Nieuw\u2026</string>
+ <string name="new_playlist_name_template">Nieuwe afspeellijst <xliff:g id="number">%d</xliff:g></string>
+ <string name="no_albums_title">Geen albums</string>
+ <string name="no_playlists_title">Geen afspeellijsten</string>
+ <string name="no_tracks_title">Geen nummers</string>
+ <string name="no_videos_title">Geen video's</string>
+ <string name="nowplaying_title">Afspelen</string>
+ <string name="onealbum">1 album</string>
+ <string name="onesong">1 nummer</string>
+ <string name="party_shuffle">Feestvolgorde</string>
+ <string name="party_shuffle_off">Feestvolgorde uit</string>
+ <string name="partyshuffle_title">Feestvolgorde</string>
+ <string name="play_all">Alles afspelen</string>
+ <string name="play_selection">Afspelen</string>
+ <string name="playlists_menu">Afspeellijst</string>
+ <string name="playlists_title">Afspeellijst</string>
+ <string name="recentlyadded">Recent toegevoegd</string>
+ <string name="rename_playlist_menu">Naam wijzigen</string>
+ <string name="repeat_all_notif">Alle nummers herhalen</string>
+ <string name="repeat_current_notif">Huidig nummer herhalen</string>
+ <string name="repeat_off_notif">Herhalen staat uit</string>
+ <string name="ringtone_menu">Gebruiken als beltoon</string>
+ <string name="save_as_playlist">Opslaan als afspeellijst</string>
+ <string name="scanning">Sd-kaart wordt gescand\u2026</string>
+ <string name="sdcard_busy_title">Sd-kaart is bezet</string>
+ <string name="sdcard_error_title">Sd-kaartfout</string>
+ <string name="sdcard_missing_title">Geen sd-kaart</string>
+ <string name="search_title">Zoeken</string>
+ <string name="service_start_error_button">OK</string>
+ <string name="service_start_error_msg">Kan media-afspeelservice niet starten</string>
+ <string name="service_start_error_title">Fout</string>
+ <string name="shuffle_all">Alles willekeurig afspelen</string>
+ <string name="shuffle_off_notif">Willekeurig afspelen staat uit</string>
+ <string name="shuffle_on_notif">Willekeurig afspelen staat aan</string>
+ <string name="tracks_menu">Nummers</string>
+ <string name="tracks_title">Nummers</string>
+ <string name="unknown_album_name">Onbekend album</string>
+ <string name="unknown_artist_name">Onbekende artiest</string>
+ <string name="videobrowserlabel">Video's</string>
+ <string name="videos_title">Video's</string>
+ <string name="weekpicker_set">Gereed</string>
+ <string name="weekpicker_title">Tijd instellen</string>
+</resources>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="add_to_playlist">新增到播放清單</string>
+ <string name="albums_menu">專輯</string>
+ <string name="albums_title">專輯</string>
+ <string name="all_title">所有媒體</string>
+ <string name="artists_title">演出者</string>
+ <string name="browse_menu">演出者</string>
+ <string name="cancel">取消</string>
+ <string name="clear_playlist">清除播放清單</string>
+ <string name="create_playlist_create_text">儲存</string>
+ <string name="create_playlist_overwrite_text">覆寫</string>
+ <string name="delete_album_desc">整張專輯 \"<xliff:g id="album">%s</xliff:g>\"</string>
+ <string name="delete_artist_desc"><xliff:g id="artist">%s</xliff:g> 的所有歌曲</string>
+ <string name="delete_confirm_button_text">確定</string>
+ <string name="delete_confirm_prompt">將會永久從 SD 卡中刪除 \"<xliff:g id="to_delete">%s</xliff:g>\"。</string>
+ <string name="delete_item">刪除</string>
+ <string name="delete_playlist_menu">刪除</string>
+ <string name="durationformat">
+ <xliff:g id="format">%2$d:%5$02d</xliff:g>
+ </string>
+ <string name="edit_playlist_menu">編輯</string>
+ <string name="emptyplaylist">選取的播放清單空白。</string>
+ <string name="goto_playback">播放</string>
+ <string name="goto_start">媒體櫃</string>
+ <string name="loading_video">正在載入影片\u2026</string>
+ <string name="mediapickerlabel">音樂</string>
+ <string name="mediaplaybacklabel">音樂</string>
+ <string name="mediasearch">搜尋 %s 的工具:</string>
+ <string name="mediasearch_amazon">Amazon MP3</string>
+ <string name="mediasearch_google">Google</string>
+ <string name="mediasearch_youtube">YouTube</string>
+ <string name="movieviewlabel">電影</string>
+ <string name="musicbrowserlabel">音樂</string>
+ <string name="musicsearch">音樂</string>
+ <string name="musicsearchhint">演出者、專輯或歌曲名稱</string>
+ <string name="musicshortcutlabel">音樂播放清單</string>
+ <string name="new_playlist">新增</string>
+ <string name="new_playlist_name_template">新增播放清單 <xliff:g id="number">%d</xliff:g></string>
+ <string name="no_albums_title">無專輯</string>
+ <string name="no_playlists_title">無播放清單</string>
+ <string name="no_tracks_title">無歌曲</string>
+ <string name="no_videos_title">無影片</string>
+ <string name="nowplaying_title">現正播放</string>
+ <string name="onesong">1 首歌曲</string>
+ <string name="party_shuffle">派對隨機播放</string>
+ <string name="party_shuffle_off">關閉派對隨機播放</string>
+ <string name="partyshuffle_title">派對隨機播放</string>
+ <string name="play_all">全部播放</string>
+ <string name="play_selection">播放</string>
+ <string name="playback_failed">抱歉,播放器不支援此類型的音訊檔。"</string>
+ <string name="playlist_deleted_message">已經刪除播放清單。</string>
+ <string name="playlist_renamed_message">已經重新命名播放清單。</string>
+ <string name="playlists_menu">播放清單</string>
+ <string name="playlists_title">播放清單</string>
+ <string name="queue">目前的播放清單</string>
+ <string name="recentlyadded">最近新增</string>
+ <string name="remove_from_playlist">從播放清單移除</string>
+ <string name="rename_playlist_menu">重新命名</string>
+ <string name="repeat_all_notif">重複所有歌曲。</string>
+ <string name="repeat_current_notif">重複目前歌曲。</string>
+ <string name="repeat_off_notif">關閉重複。</string>
+ <string name="ringtone_menu">設為電話響鈴音調</string>
+ <string name="ringtone_menu_short">設為響鈴音調</string>
+ <string name="ringtone_set">\"%s\" 已經設為響鈴音調。</string>
+ <string name="save_as_playlist">儲存為播放清單</string>
+ <string name="scanning">正在掃描 SD 卡\u2026</string>
+ <string name="sdcard_busy_message">SD 卡忙碌中。若要存取音樂,請移至首頁 > 設定 > SD 卡與電話儲存空間,並清除 \"用於 USB 儲存\" 核取方塊。</string>
+ <string name="sdcard_busy_title">SD 卡忙碌中</string>
+ <string name="sdcard_error_message">SD 卡錯誤。</string>
+ <string name="sdcard_error_title">SD 卡錯誤</string>
+ <string name="sdcard_missing_message">無 SD 卡。</string>
+ <string name="sdcard_missing_title">無 SD 卡</string>
+ <string name="search_title">搜尋</string>
+ <string name="service_start_error_button">確定</string>
+ <string name="service_start_error_msg">抱歉,無法播放此歌曲。</string>
+ <string name="service_start_error_title">播放發生問題</string>
+ <string name="shuffle_all">全部隨機播放</string>
+ <string name="shuffle_off_notif">關閉隨機播放。</string>
+ <string name="shuffle_on_notif">開啟隨機播放。</string>
+ <string name="streamloadingtext">正在連線到 <xliff:g id="host">%s</xliff:g></string>
+ <string name="tracks_menu">歌曲</string>
+ <string name="tracks_title">歌曲</string>
+ <string name="unknown_album_name">無法辨識的專輯</string>
+ <string name="unknown_artist_name">無法辨識的演出者</string>
+ <string name="videobrowserlabel">影片</string>
+ <string name="videos_title">影片</string>
+ <string name="weekpicker_set">完成</string>
+ <string name="weekpicker_title">設定時間</string>
+</resources>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <string name="onesong">1 song</string>
+ <plurals name="Nsongs">
+ <item quantity="other"><xliff:g id="count">%d</xliff:g> songs</item>
+ </plurals>
+
+ <plurals name="Nalbums">
+ <item quantity="one">1 album</item>
+ <item quantity="other"><xliff:g id="count">%d</xliff:g> albums</item>
+ </plurals>
+
+ <!--
+ This string is used as the format string in a String.format call,
+ and 5 additional arguments are available for printing:
+ 1 - hours
+ 2 - minutes
+ 3 - minutes%60
+ 4 - seconds
+ 5 - second%60
+ -->
+ <string name="durationformat"><xliff:g id="format">%2$d:%5$02d</xliff:g></string>
+
+ <!-- Search -->
+ <string name="musicsearch">Music</string>
+ <string name="musicsearchhint">Artist, album, or song name</string>
+
+ <string name="goto_start">Library</string>
+ <string name="goto_playback">Playback</string>
+ <string name="party_shuffle">Party shuffle</string>
+ <string name="party_shuffle_off">Party shuffle off</string>
+ <string name="delete_item">Delete</string>
+ <string name="shuffle_all">Shuffle all</string>
+ <string name="play_all">Play all</string>
+
+ <string name="delete_artist_desc">all songs by <xliff:g id="artist">%s</xliff:g></string>
+ <string name="delete_album_desc">the entire album \"<xliff:g id="album">%s</xliff:g>\"</string>
+ <string name="delete_confirm_prompt">\"<xliff:g id="to_delete">%s</xliff:g>\" will be permanently deleted from the SD card.</string>
+ <string name="delete_confirm_button_text">OK</string>
+ <plurals name="NNNtracksdeleted">
+ <item quantity="one">1 song was deleted.</item>
+ <item quantity="other"><xliff:g id="songs_to_delete">%d</xliff:g> songs were deleted.</item>
+ </plurals>
+
+ <string name="scanning">Scanning SD card\u2026</string>
+
+ <string name="nowplaying_title">Now playing</string>
+ <string name="partyshuffle_title">Party shuffle</string>
+ <string name="artists_title">Artists</string>
+ <string name="albums_menu">Albums</string>
+ <string name="albums_title">Albums</string>
+ <string name="tracks_menu">Songs</string>
+ <string name="tracks_title">Songs</string>
+ <string name="playlists_menu">Playlists</string>
+ <string name="playlists_title">Playlists</string>
+ <string name="videos_title">Videos</string>
+ <string name="all_title">All media</string>
+ <string name="browse_menu">Artists</string>
+ <string name="search_title">Search</string>
+
+ <string name="no_albums_title">No albums</string>
+ <string name="no_tracks_title">No songs</string>
+ <string name="no_videos_title">No videos</string>
+ <string name="no_playlists_title">No playlists</string>
+ <string name="delete_playlist_menu">Delete</string>
+ <string name="edit_playlist_menu">Edit</string>
+ <string name="rename_playlist_menu">Rename</string>
+ <string name="playlist_deleted_message">Playlist deleted.</string>
+ <string name="playlist_renamed_message">Playlist renamed.</string>
+ <string name="recentlyadded">Recently added</string>
+ <string name="sdcard_missing_title">No SD card</string>
+ <string name="sdcard_missing_message">No SD card.</string>
+ <string name="sdcard_busy_title">SD card is busy</string>
+ <string name="sdcard_busy_message">Sorry, your SD card is busy.</string>
+ <string name="sdcard_error_title">SD card error</string>
+ <string name="sdcard_error_message">SD card error.</string>
+ <string name="unknown_artist_name">Unknown artist</string>
+ <string name="unknown_album_name">Unknown album</string>
+
+ <string name="shuffle_on_notif">Shuffle is on.</string>
+ <string name="shuffle_off_notif">Shuffle is off.</string>
+
+ <string name="repeat_off_notif">Repeat is off.</string>
+ <string name="repeat_current_notif">Repeating current song.</string>
+ <string name="repeat_all_notif">Repeating all songs.</string>
+
+ <string name="ringtone_menu">Use as phone ringtone</string>
+ <string name="ringtone_menu_short">Use as ringtone</string>
+ <string name="ringtone_set">\"%s\" set as phone ringtone.</string>
+
+ <string name="play_selection">Play</string>
+ <string name="add_to_playlist">Add to playlist</string>
+ <string name="queue">Current playlist</string>
+ <string name="new_playlist">New</string>
+ <string name="new_playlist_name_template">New playlist <xliff:g id="number">%d</xliff:g></string>
+ <plurals name="NNNtrackstoplaylist">
+ <item quantity="one">1 song added to playlist.</item>
+ <item quantity="other">%d songs added to playlist.</item>
+ </plurals>
+ <string name="emptyplaylist">Selected playlist is empty.</string>
+
+ <string name="create_playlist_create_text">Save</string>
+ <string name="create_playlist_overwrite_text">Overwrite</string>
+
+ <string name="service_start_error_title">Playback problem</string>
+ <string name="service_start_error_msg">Sorry, the song could not be played.</string>
+ <string name="service_start_error_button">OK</string>
+
+ <string-array name="weeklist">
+ <item>"1 week"</item>
+ <item>"2 weeks"</item>
+ <item>"3 weeks"</item>
+ <item>"4 weeks"</item>
+ <item>"5 weeks"</item>
+ <item>"6 weeks"</item>
+ <item>"7 weeks"</item>
+ <item>"8 weeks"</item>
+ <item>"9 weeks"</item>
+ <item>"10 weeks"</item>
+ <item>"11 weeks"</item>
+ <item>"12 weeks"</item>
+ </string-array>
+ <string name="weekpicker_set">Done</string>
+ <string name="weekpicker_title">Set time</string>
+
+ <color name="dragndrop_background">#e0103010</color>
+ <color name="expanding_child_background">#ff404040</color>
+
+ <string name="save_as_playlist">Save as playlist</string>
+ <string name="clear_playlist">Clear playlist</string>
+
+ <!-- Activity labels. These might show up in an activity-picker -->
+ <string name="musicbrowserlabel">Music</string>
+ <string name="musicshortcutlabel">Music playlist</string>
+ <string name="mediaplaybacklabel">Music</string>
+ <string name="videobrowserlabel">Videos</string>
+ <string name="mediapickerlabel">Music</string>
+ <string name="movieviewlabel">Movies</string>
+
+ <string name="playback_failed">Sorry, the player does not support this type of audio file."</string>
+
+ <string name="cancel">Cancel</string>
+
+ <string name="remove_from_playlist">Remove from playlist</string>
+
+ <string name="loading_video">Loading video\u2026</string>
+
+ <string name="streamloadingtext">Connecting to <xliff:g id="host">%s</xliff:g></string>
+
+ <string name="mediasearch">Search for %s using:</string>
+ <string name="mediasearch_amazon">Amazon MP3</string>
+ <string name="mediasearch_google">Google</string>
+ <string name="mediasearch_youtube">YouTube</string>
+</resources>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<resources>
+ <string name="albumsongseparator">, </string>
+</resources>
+
--- /dev/null
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import java.text.Collator;
+
+import android.app.ListActivity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.BroadcastReceiver;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.media.AudioManager;
+import android.media.MediaFile;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.MediaStore;
+import android.view.ContextMenu;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.SubMenu;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+import android.widget.TextView;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+
+public class AlbumBrowserActivity extends ListActivity
+ implements View.OnCreateContextMenuListener, MusicUtils.Defs
+{
+ private String mCurrentAlbumId;
+ private String mCurrentAlbumName;
+
+ public AlbumBrowserActivity()
+ {
+ }
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle icicle)
+ {
+ if (icicle != null) {
+ mCurrentAlbumId = icicle.getString("selectedalbum");
+ mArtistId = icicle.getString("artist");
+ } else {
+ mArtistId = getIntent().getStringExtra("artist");
+ }
+ super.onCreate(icicle);
+ requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+ MusicUtils.bindToService(this);
+
+ IntentFilter f = new IntentFilter();
+ f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
+ f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
+ f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
+ f.addDataScheme("file");
+ registerReceiver(mScanListener, f);
+
+ init();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outcicle) {
+ // need to store the selected item so we don't lose it in case
+ // of an orientation switch. Otherwise we could lose it while
+ // in the middle of specifying a playlist to add the item to.
+ outcicle.putString("selectedalbum", mCurrentAlbumId);
+ outcicle.putString("artist", mArtistId);
+ super.onSaveInstanceState(outcicle);
+ }
+
+ @Override
+ public void onDestroy() {
+ MusicUtils.unbindFromService(this);
+ if (mAlbumCursor != null) {
+ mAlbumCursor.close();
+ }
+ unregisterReceiver(mScanListener);
+ super.onDestroy();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ IntentFilter f = new IntentFilter();
+ f.addAction(MediaPlaybackService.META_CHANGED);
+ f.addAction(MediaPlaybackService.QUEUE_CHANGED);
+ registerReceiver(mTrackListListener, f);
+ mTrackListListener.onReceive(null, null);
+
+ MusicUtils.setSpinnerState(this);
+ }
+
+ private BroadcastReceiver mTrackListListener = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ getListView().invalidateViews();
+ }
+ };
+ private BroadcastReceiver mScanListener = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ MusicUtils.setSpinnerState(AlbumBrowserActivity.this);
+ mReScanHandler.sendEmptyMessage(0);
+ if (intent.getAction().equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
+ MusicUtils.clearAlbumArtCache();
+ }
+ }
+ };
+
+ private Handler mReScanHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ init();
+ if (mAlbumCursor == null) {
+ sendEmptyMessageDelayed(0, 1000);
+ }
+ }
+ };
+
+ @Override
+ public void onPause() {
+ unregisterReceiver(mTrackListListener);
+ mReScanHandler.removeCallbacksAndMessages(null);
+ super.onPause();
+ }
+
+ public void init() {
+
+ mAlbumCursor = getAlbumCursor(null);
+
+ /* The UI spec does not call for icons in the title bar
+ if (mArtistId != null) {
+ requestWindowFeature(Window.FEATURE_LEFT_ICON);
+ }
+ */
+ setContentView(R.layout.media_picker_activity);
+ /* The UI spec does not call for icons in the title bar
+ if (mArtistId != null) {
+ // this call needs to happen after setContentView
+ setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, R.drawable.person_title_icon);
+ }
+ */
+
+ if (mAlbumCursor == null) {
+ MusicUtils.displayDatabaseError(this);
+ return;
+ }
+
+ CharSequence fancyName = "";
+ if (mAlbumCursor.getCount() > 0) {
+ mAlbumCursor.moveToFirst();
+ fancyName = mAlbumCursor.getString(3);
+ if (MediaFile.UNKNOWN_STRING.equals(fancyName))
+ fancyName = getText(R.string.unknown_artist_name);
+
+ if (mArtistId != null && fancyName != null)
+ setTitle(fancyName);
+ else
+ setTitle(R.string.albums_title);
+ } else {
+ setTitle(R.string.no_albums_title);
+ }
+
+ // Map Cursor columns to views defined in media_list_item.xml
+ AlbumListAdapter adapter = new AlbumListAdapter(
+ this,
+ R.layout.track_list_item,
+ mAlbumCursor,
+ new String[] {},
+ new int[] {});
+
+ setListAdapter(adapter);
+ ListView lv = getListView();
+ lv.setOnCreateContextMenuListener(this);
+ lv.setTextFilterEnabled(true);
+ }
+
+ @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);
+
+ 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));
+ menu.setHeaderTitle(mCurrentAlbumName);
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case PLAY_SELECTION: {
+ // play the selected album
+ int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
+ MusicUtils.playAll(this, list, 0);
+ return true;
+ }
+
+ case QUEUE: {
+ int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
+ MusicUtils.addToCurrentPlaylist(this, list);
+ return true;
+ }
+
+ case NEW_PLAYLIST: {
+ Intent intent = new Intent();
+ intent.setClass(this, CreatePlaylist.class);
+ startActivityForResult(intent, NEW_PLAYLIST);
+ return true;
+ }
+
+ case PLAYLIST_SELECTED: {
+ int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
+ int playlist = item.getIntent().getIntExtra("playlist", 0);
+ MusicUtils.addToPlaylist(this, list, playlist);
+ return true;
+ }
+ case DELETE_ITEM: {
+ int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
+ String f = getString(R.string.delete_album_desc);
+ String desc = String.format(f, mCurrentAlbumName);
+ Bundle b = new Bundle();
+ b.putString("description", desc);
+ b.putIntArray("items", list);
+ Intent intent = new Intent();
+ intent.setClass(this, DeleteItems.class);
+ intent.putExtras(b);
+ startActivityForResult(intent, -1);
+ return true;
+ }
+
+ }
+ return super.onContextItemSelected(item);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ switch (requestCode) {
+ case SCAN_DONE:
+ if (resultCode == RESULT_CANCELED) {
+ finish();
+ } else {
+ init();
+ }
+ break;
+
+ case NEW_PLAYLIST:
+ if (resultCode == RESULT_OK) {
+ Uri uri = Uri.parse(intent.getAction());
+ if (uri != null) {
+ int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
+ MusicUtils.addToPlaylist(this, list, Integer.parseInt(uri.getLastPathSegment()));
+ }
+ }
+ break;
+ }
+ }
+
+ @Override
+ protected void onListItemClick(ListView l, View v, int position, long id)
+ {
+ if (mHasHeader) {
+ position --;
+ }
+ Intent intent = new Intent(Intent.ACTION_PICK);
+ intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
+ intent.putExtra("album", Long.valueOf(id).toString());
+ intent.putExtra("artist", mArtistId);
+ startActivity(intent);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ menu.add(0, GOTO_START, 0, R.string.goto_start).setIcon(R.drawable.ic_menu_music_library);
+ menu.add(0, GOTO_PLAYBACK, 0, R.string.goto_playback).setIcon(R.drawable.ic_menu_playback);
+ menu.add(0, SHUFFLE_ALL, 0, R.string.shuffle_all).setIcon(R.drawable.ic_menu_shuffle);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ menu.findItem(GOTO_PLAYBACK).setVisible(MusicUtils.isMusicLoaded());
+ return super.onPrepareOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ Intent intent;
+ Cursor cursor;
+ switch (item.getItemId()) {
+ case GOTO_START:
+ intent = new Intent();
+ intent.setClass(this, MusicBrowserActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ return true;
+
+ case GOTO_PLAYBACK:
+ intent = new Intent("com.android.music.PLAYBACK_VIEWER");
+ startActivity(intent);
+ return true;
+
+ case SHUFFLE_ALL:
+ cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+ new String [] { MediaStore.Audio.Media._ID},
+ MediaStore.Audio.Media.IS_MUSIC + "=1", null,
+ MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
+ if (cursor != null) {
+ MusicUtils.shuffleAll(this, cursor);
+ cursor.close();
+ }
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private Cursor getAlbumCursor(String filterstring) {
+ StringBuilder where = new StringBuilder();
+ where.append(MediaStore.Audio.Albums.ALBUM + " != ''");
+
+ // Add in the filtering constraints
+ String [] keywords = null;
+ if (filterstring != null) {
+ String [] searchWords = filterstring.split(" ");
+ keywords = new String[searchWords.length];
+ Collator col = Collator.getInstance();
+ col.setStrength(Collator.PRIMARY);
+ for (int i = 0; i < searchWords.length; i++) {
+ keywords[i] = '%' + MediaStore.Audio.keyFor(searchWords[i]) + '%';
+ }
+ for (int i = 0; i < searchWords.length; i++) {
+ where.append(" AND ");
+ where.append(MediaStore.Audio.Media.ARTIST_KEY + "||");
+ where.append(MediaStore.Audio.Media.ALBUM_KEY + " LIKE ?");
+ }
+ }
+
+ String whereclause = where.toString();
+
+ String[] cols = new String[] {
+ MediaStore.Audio.Albums._ID,
+ MediaStore.Audio.Albums.ALBUM,
+ MediaStore.Audio.Albums.ALBUM_KEY,
+ MediaStore.Audio.Albums.ARTIST,
+ MediaStore.Audio.Albums.NUMBER_OF_SONGS,
+ MediaStore.Audio.Albums.ALBUM_ART
+ };
+ Cursor ret;
+ if (mArtistId != null) {
+ ret = MusicUtils.query(this,
+ MediaStore.Audio.Artists.Albums.getContentUri("external", Long.valueOf(mArtistId)),
+ cols, whereclause, keywords, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
+ } else {
+ ret = MusicUtils.query(this, MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
+ cols, whereclause, keywords, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
+ }
+ return ret;
+ }
+
+ class AlbumListAdapter extends SimpleCursorAdapter {
+
+ private final Drawable mNowPlayingOverlay;
+ private final BitmapDrawable mDefaultAlbumIcon;
+ private final int mAlbumIdx;
+ private final int mArtistIdx;
+ private final int mNumSongsIdx;
+ private final 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];
+
+ class ViewHolder {
+ TextView line1;
+ TextView line2;
+ TextView duration;
+ ImageView play_indicator;
+ ImageView icon;
+ }
+
+ AlbumListAdapter(Context context, int layout, Cursor cursor, String[] from, int[] to) {
+ super(context, layout, cursor, from, to);
+
+ mUnknownAlbum = context.getString(R.string.unknown_album_name);
+ mUnknownArtist = context.getString(R.string.unknown_artist_name);
+ mAlbumSongSeparator = context.getString(R.string.albumsongseparator);
+
+ Resources r = getResources();
+ mNowPlayingOverlay = r.getDrawable(R.drawable.indicator_ic_mp_playing_list);
+
+ Bitmap b = BitmapFactory.decodeResource(r, R.drawable.albumart_mp_unknown_list);
+ mDefaultAlbumIcon = new BitmapDrawable(b);
+ // no filter or dither, it's a lot faster and we can't tell the difference
+ mDefaultAlbumIcon.setFilterBitmap(false);
+ mDefaultAlbumIcon.setDither(false);
+ mAlbumIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM);
+ mArtistIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ARTIST);
+ mNumSongsIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.NUMBER_OF_SONGS);
+ mAlbumArtIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM_ART);
+
+ mResources = context.getResources();
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ View v = super.newView(context, cursor, parent);
+ ViewHolder vh = new ViewHolder();
+ vh.line1 = (TextView) v.findViewById(R.id.line1);
+ vh.line2 = (TextView) v.findViewById(R.id.line2);
+ vh.duration = (TextView) v.findViewById(R.id.duration);
+ vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator);
+ vh.icon = (ImageView) v.findViewById(R.id.icon);
+ vh.icon.setBackgroundDrawable(mDefaultAlbumIcon);
+ vh.icon.setPadding(1, 1, 1, 1);
+ 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;
+ if (MediaFile.UNKNOWN_STRING.equals(name)) {
+ displayname = mUnknownAlbum;
+ }
+ vh.line1.setText(displayname);
+
+ name = cursor.getString(mArtistIdx);
+ displayname = name;
+ if (MediaFile.UNKNOWN_STRING.equals(name)) {
+ displayname = mUnknownArtist;
+ }
+ StringBuilder builder = mStringBuilder;
+ builder.delete(0, builder.length());
+ builder.append(displayname);
+ builder.append(mAlbumSongSeparator);
+
+ int numsongs = cursor.getInt(mNumSongsIdx);
+ if (numsongs == 1) {
+ builder.append(context.getString(R.string.onesong));
+ } else {
+ final Object[] args = mFormatArgs;
+ args[0] = numsongs;
+ builder.append(mResources.getQuantityString(R.plurals.Nsongs, numsongs, args));
+ }
+ vh.line2.setText(builder.toString());
+
+ ImageView iv = vh.icon;
+ // We don't actually need the path to the thumbnail file,
+ // we just use it to see if there is album art or not
+ String art = cursor.getString(mAlbumArtIndex);
+ if (art == null || art.length() == 0) {
+ iv.setImageDrawable(null);
+ } else {
+ int artIndex = cursor.getInt(0);
+ Drawable d = MusicUtils.getCachedArtwork(context, artIndex, mDefaultAlbumIcon);
+ iv.setImageDrawable(d);
+ }
+
+ int currentalbumid = MusicUtils.getCurrentAlbumId();
+ int aid = cursor.getInt(0);
+ iv = vh.play_indicator;
+ if (currentalbumid == aid) {
+ iv.setImageDrawable(mNowPlayingOverlay);
+ } else {
+ iv.setImageDrawable(null);
+ }
+ }
+ @Override
+ public void changeCursor(Cursor cursor) {
+ super.changeCursor(cursor);
+ mAlbumCursor = cursor;
+ }
+ @Override
+ public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
+ return getAlbumCursor(constraint.toString());
+ }
+ }
+
+ private Cursor mAlbumCursor;
+ private String mArtistId;
+ private boolean mHasHeader = false;
+}
+
--- /dev/null
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapShader;
+import android.graphics.Camera;
+import android.graphics.Canvas;
+import android.graphics.ComposeShader;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.graphics.Xfermode;
+import android.util.AttributeSet;
+import android.widget.RelativeLayout;
+
+
+public class AlbumView extends RelativeLayout
+{
+ public AlbumView(Context context)
+ {
+ super(context);
+ setWillNotDraw(false);
+ }
+
+ public AlbumView(Context context, AttributeSet attrs)
+ {
+ super(context, attrs);
+ setWillNotDraw(false);
+ }
+
+ public void setArtwork(Bitmap art)
+ {
+ mScale = 1.00f;
+ cX = 0f;
+ cY = 0f;
+ cR = 0f; // 24f;
+
+ if (art == null) {
+ BitmapFactory.Options opts = new BitmapFactory.Options();
+ opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ art = BitmapFactory.decodeResource(getResources(), R.drawable.albumart_mp_unknown, opts);
+ }
+
+ if (art != null) {
+ mBit = art; // Bitmap.createBitmap(art, 2, 2, art.width()-4, art.height()-4);
+ mCoverPaint = new Paint();
+ //mCoverPaint.setAntiAlias(true);
+ mCoverPaint.setFilterBitmap(true);
+ mCoverPaint.setDither(true);
+
+ BitmapShader sh1 = new BitmapShader(mBit, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
+ LinearGradient sh2 = new LinearGradient(0f, mBit.getHeight(),0f, mBit.getHeight()/3, 0x7f000000, 0x00000000, Shader.TileMode.CLAMP);
+ Xfermode mode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
+ mReflectionShader = new ComposeShader(sh1, sh2, mode );
+
+ mCamera = new Camera();
+ mCamera.rotateY(cR);
+ mCamera.translate(cX,cY,0);
+ }
+ }
+
+ private void drawArtwork(Canvas canvas)
+ {
+ if (mBit == null)
+ return;
+
+ canvas.save();
+
+ mCamera.applyToCanvas(canvas);
+
+ int mywidth = getWidth();
+ float artwidth = mBit.getWidth();
+ float scale = ((float)(mywidth - mPaddingLeft - mPaddingRight))/artwidth * mScale;
+
+ canvas.translate(mPaddingLeft, mPaddingTop);
+
+ canvas.scale(scale, scale);
+ mCoverPaint.setAlpha(255);
+ canvas.drawBitmap(mBit,0f,0f, mCoverPaint);
+
+ if (false) {
+ // draw the reflection
+ canvas.scale(1, -1, 0, mBit.getHeight());
+ mCoverPaint.setAlpha(64);
+ mCoverPaint.setShader(mReflectionShader);
+ canvas.drawRect(new Rect(0,0,mBit.getWidth(),mBit.getHeight()), mCoverPaint);
+ mCoverPaint.setShader(null);
+ }
+
+ canvas.restore();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ drawArtwork(canvas);
+ }
+
+ void adjustParams(double scale, double ix, double iy, double cx, double cy, double cr)
+ {
+ mScale += scale;
+ mPaddingLeft += ix;
+ mPaddingTop += iy;
+ cX += cx;
+ cY += cy;
+ cR += cr;
+
+ mCamera.rotateY((float)cr); // little r
+ mCamera.translate((float)cx,(float)cy,0); // little too
+
+ System.out.println("parameters: "
+ + mScale + " "
+ + mPaddingLeft + " "
+ + mPaddingTop + " "
+ + cX + " "
+ + cY + " "
+ + cR + " ");
+ invalidate();
+ }
+
+ private Bitmap mBit;
+ private Paint mCoverPaint;
+ private Camera mCamera;
+ private ComposeShader mReflectionShader;
+
+ float mScale;
+ float cX;
+ float cY;
+ float cR;
+}
--- /dev/null
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import java.text.Collator;
+
+import android.app.ExpandableListActivity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.BroadcastReceiver;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.media.MediaFile;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.SubMenu;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
+import android.widget.ExpandableListView;
+import android.widget.ImageView;
+import android.widget.SimpleCursorTreeAdapter;
+import android.widget.TextView;
+
+
+public class ArtistAlbumBrowserActivity extends ExpandableListActivity
+ implements View.OnCreateContextMenuListener, MusicUtils.Defs
+{
+ private String mCurrentArtistId;
+ private String mCurrentArtistName;
+ private String mCurrentAlbumId;
+ private String mCurrentAlbumName;
+
+ public ArtistAlbumBrowserActivity()
+ {
+ }
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+ if (icicle != null) {
+ mCurrentAlbumId = icicle.getString("selectedalbum");
+ mCurrentAlbumName = icicle.getString("selectedalbumname");
+ mCurrentArtistId = icicle.getString("selectedartist");
+ mCurrentArtistName = icicle.getString("selectedartistname");
+ }
+ MusicUtils.bindToService(this);
+
+ IntentFilter f = new IntentFilter();
+ f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
+ f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
+ f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
+ f.addDataScheme("file");
+ registerReceiver(mScanListener, f);
+
+ init();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outcicle) {
+ // need to store the selected item so we don't lose it in case
+ // of an orientation switch. Otherwise we could lose it while
+ // in the middle of specifying a playlist to add the item to.
+ outcicle.putString("selectedalbum", mCurrentAlbumId);
+ outcicle.putString("selectedalbumname", mCurrentAlbumName);
+ outcicle.putString("selectedartist", mCurrentArtistId);
+ outcicle.putString("selectedartistname", mCurrentArtistName);
+ super.onSaveInstanceState(outcicle);
+ }
+
+ @Override
+ public void onDestroy() {
+ MusicUtils.unbindFromService(this);
+ if (mArtistCursor != null) {
+ mArtistCursor.close();
+ }
+ unregisterReceiver(mScanListener);
+ super.onDestroy();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ IntentFilter f = new IntentFilter();
+ f.addAction(MediaPlaybackService.META_CHANGED);
+ f.addAction(MediaPlaybackService.QUEUE_CHANGED);
+ registerReceiver(mTrackListListener, f);
+ mTrackListListener.onReceive(null, null);
+
+ MusicUtils.setSpinnerState(this);
+ }
+
+ private BroadcastReceiver mTrackListListener = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ getExpandableListView().invalidateViews();
+ }
+ };
+ private BroadcastReceiver mScanListener = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ MusicUtils.setSpinnerState(ArtistAlbumBrowserActivity.this);
+ mReScanHandler.sendEmptyMessage(0);
+ if (intent.getAction().equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
+ MusicUtils.clearAlbumArtCache();
+ }
+ }
+ };
+
+ private Handler mReScanHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ init();
+ if (mArtistCursor == null) {
+ sendEmptyMessageDelayed(0, 1000);
+ }
+ }
+ };
+
+ @Override
+ public void onPause() {
+ unregisterReceiver(mTrackListListener);
+ mReScanHandler.removeCallbacksAndMessages(null);
+ super.onPause();
+ }
+
+ public void init() {
+
+ mArtistCursor = getArtistCursor(null);
+
+ if (mArtistCursor == null) {
+ MusicUtils.displayDatabaseError(this);
+ return;
+ }
+
+ setContentView(android.R.layout.expandable_list_content);
+
+ // Map Cursor columns to views defined in media_list_item.xml
+ ArtistAlbumListAdapter adapter = new ArtistAlbumListAdapter(
+ this,
+ mArtistCursor,
+ R.layout.track_list_item_group,
+ new String[] {},
+ new int[] {},
+ R.layout.track_list_item_child,
+ new String[] {},
+ new int[] {});
+
+ setTitle(R.string.artists_title);
+ setListAdapter(adapter);
+ ExpandableListView lv = getExpandableListView();
+ lv.setOnCreateContextMenuListener(this);
+ lv.setTextFilterEnabled(true);
+ }
+
+ @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);
+ mArtistCursor.moveToPosition(groupPosition);
+ mCurrentArtistId = Long.valueOf(id).toString();
+ intent.putExtra("artist", mCurrentArtistId);
+ startActivity(intent);
+ return true;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ menu.add(0, GOTO_START, 0, R.string.goto_start).setIcon(R.drawable.ic_menu_music_library);
+ menu.add(0, GOTO_PLAYBACK, 0, R.string.goto_playback).setIcon(R.drawable.ic_menu_playback);
+ menu.add(0, SHUFFLE_ALL, 0, R.string.shuffle_all).setIcon(R.drawable.ic_menu_shuffle);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ menu.findItem(GOTO_PLAYBACK).setVisible(MusicUtils.isMusicLoaded());
+ return super.onPrepareOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ Intent intent;
+ Cursor cursor;
+ switch (item.getItemId()) {
+ case GOTO_START:
+ intent = new Intent();
+ intent.setClass(this, MusicBrowserActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ return true;
+
+ case GOTO_PLAYBACK:
+ intent = new Intent("com.android.music.PLAYBACK_VIEWER");
+ startActivity(intent);
+ return true;
+
+ case SHUFFLE_ALL:
+ cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+ new String [] { MediaStore.Audio.Media._ID},
+ MediaStore.Audio.Media.IS_MUSIC + "=1", null,
+ MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
+ if (cursor != null) {
+ MusicUtils.shuffleAll(this, cursor);
+ cursor.close();
+ }
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
+ menu.add(0, PLAY_SELECTION, 0, R.string.play_selection);
+ SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0, R.string.add_to_playlist);
+ MusicUtils.makePlaylistMenu(this, sub);
+ menu.add(0, DELETE_ITEM, 0, R.string.delete_item);
+
+ ExpandableListContextMenuInfo mi = (ExpandableListContextMenuInfo) menuInfoIn;
+
+ int itemtype = ExpandableListView.getPackedPositionType(mi.packedPosition);
+ int gpos = ExpandableListView.getPackedPositionGroup(mi.packedPosition);
+ int cpos = ExpandableListView.getPackedPositionChild(mi.packedPosition);
+ if (itemtype == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
+ if (gpos == -1) {
+ // this shouldn't happen
+ Log.d("Artist/Album", "no group");
+ return;
+ }
+ gpos = gpos - getExpandableListView().getHeaderViewsCount();
+ mArtistCursor.moveToPosition(gpos);
+ mCurrentArtistId = mArtistCursor.getString(mArtistCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists._ID));
+ mCurrentArtistName = mArtistCursor.getString(mArtistCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST));
+ mCurrentAlbumId = null;
+ menu.setHeaderTitle(mCurrentArtistName);
+ return;
+ } else if (itemtype == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
+ if (cpos == -1) {
+ // this shouldn't happen
+ Log.d("Artist/Album", "no child");
+ return;
+ }
+ Cursor c = (Cursor) getExpandableListAdapter().getChild(gpos, cpos);
+ c.moveToPosition(cpos);
+ mCurrentArtistId = null;
+ mCurrentAlbumId = Long.valueOf(mi.id).toString();
+ mCurrentAlbumName = c.getString(c.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));
+ menu.setHeaderTitle(mCurrentAlbumName);
+ }
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case PLAY_SELECTION: {
+ // play everything by the selected artist
+ int [] list =
+ mCurrentArtistId != null ?
+ MusicUtils.getSongListForArtist(this, Integer.parseInt(mCurrentArtistId))
+ : MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
+
+ MusicUtils.playAll(this, list, 0);
+ return true;
+ }
+
+ case QUEUE: {
+ int [] list =
+ mCurrentArtistId != null ?
+ MusicUtils.getSongListForArtist(this, Integer.parseInt(mCurrentArtistId))
+ : MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
+ MusicUtils.addToCurrentPlaylist(this, list);
+ return true;
+ }
+
+ case NEW_PLAYLIST: {
+ Intent intent = new Intent();
+ intent.setClass(this, CreatePlaylist.class);
+ startActivityForResult(intent, NEW_PLAYLIST);
+ return true;
+ }
+
+ case PLAYLIST_SELECTED: {
+ int [] list =
+ mCurrentArtistId != null ?
+ MusicUtils.getSongListForArtist(this, Integer.parseInt(mCurrentArtistId))
+ : MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
+ int playlist = item.getIntent().getIntExtra("playlist", 0);
+ MusicUtils.addToPlaylist(this, list, playlist);
+ return true;
+ }
+
+ case DELETE_ITEM: {
+ int [] list;
+ String desc;
+ if (mCurrentArtistId != null) {
+ list = MusicUtils.getSongListForArtist(this, Integer.parseInt(mCurrentArtistId));
+ String f = getString(R.string.delete_artist_desc);
+ desc = String.format(f, mCurrentArtistName);
+ } else {
+ list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
+ String f = getString(R.string.delete_album_desc);
+ desc = String.format(f, mCurrentAlbumName);
+ }
+ Bundle b = new Bundle();
+ b.putString("description", desc);
+ b.putIntArray("items", list);
+ Intent intent = new Intent();
+ intent.setClass(this, DeleteItems.class);
+ intent.putExtras(b);
+ startActivityForResult(intent, -1);
+ return true;
+ }
+ }
+ return super.onContextItemSelected(item);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ switch (requestCode) {
+ case SCAN_DONE:
+ if (resultCode == RESULT_CANCELED) {
+ finish();
+ } else {
+ init();
+ }
+ break;
+
+ case NEW_PLAYLIST:
+ if (resultCode == RESULT_OK) {
+ Uri uri = Uri.parse(intent.getAction());
+ if (uri != null) {
+ int [] list = null;
+ if (mCurrentArtistId != null) {
+ list = MusicUtils.getSongListForArtist(this, Integer.parseInt(mCurrentArtistId));
+ } else if (mCurrentAlbumId != null) {
+ list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
+ }
+ MusicUtils.addToPlaylist(this, list, Integer.parseInt(uri.getLastPathSegment()));
+ }
+ }
+ break;
+ }
+ }
+
+ private Cursor getArtistCursor(String filterstring) {
+
+ StringBuilder where = new StringBuilder();
+ where.append(MediaStore.Audio.Artists.ARTIST + " != ''");
+
+ // Add in the filtering constraints
+ String [] keywords = null;
+ if (filterstring != null) {
+ String [] searchWords = filterstring.split(" ");
+ keywords = new String[searchWords.length];
+ Collator col = Collator.getInstance();
+ col.setStrength(Collator.PRIMARY);
+ for (int i = 0; i < searchWords.length; i++) {
+ keywords[i] = '%' + MediaStore.Audio.keyFor(searchWords[i]) + '%';
+ }
+ for (int i = 0; i < searchWords.length; i++) {
+ where.append(" AND ");
+ where.append(MediaStore.Audio.Media.ARTIST_KEY + " LIKE ?");
+ }
+ }
+
+ String whereclause = where.toString();
+ String[] cols = new String[] {
+ MediaStore.Audio.Artists._ID,
+ MediaStore.Audio.Artists.ARTIST,
+ MediaStore.Audio.Artists.NUMBER_OF_ALBUMS,
+ MediaStore.Audio.Artists.NUMBER_OF_TRACKS
+ };
+ Cursor ret;
+ ret = MusicUtils.query(this, MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI,
+ cols, whereclause , keywords, MediaStore.Audio.Artists.ARTIST_KEY);
+ return ret;
+ }
+
+ class ArtistAlbumListAdapter extends SimpleCursorTreeAdapter {
+
+ private final Drawable mNowPlayingOverlay;
+ private final BitmapDrawable mDefaultAlbumIcon;
+ private final int mGroupArtistIdIdx;
+ private final int mGroupArtistIdx;
+ private final int mGroupAlbumIdx;
+ private final int mGroupSongIdx;
+ 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];
+
+ class ViewHolder {
+ TextView line1;
+ TextView line2;
+ ImageView play_indicator;
+ ImageView icon;
+ }
+
+ ArtistAlbumListAdapter(Context context, Cursor cursor,
+ int glayout, String[] gfrom, int[] gto,
+ int clayout, String[] cfrom, int[] cto) {
+ super(context, cursor, glayout, gfrom, gto, clayout, cfrom, cto);
+
+ Resources r = getResources();
+ mNowPlayingOverlay = r.getDrawable(R.drawable.indicator_ic_mp_playing_list);
+ mDefaultAlbumIcon = (BitmapDrawable) r.getDrawable(R.drawable.albumart_mp_unknown_list);
+ // no filter or dither, it's a lot faster and we can't tell the difference
+ mDefaultAlbumIcon.setFilterBitmap(false);
+ mDefaultAlbumIcon.setDither(false);
+ 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);
+
+ 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);
+ }
+
+ @Override
+ public View newGroupView(Context context, Cursor cursor, boolean isExpanded, ViewGroup parent) {
+ View v = super.newGroupView(context, cursor, isExpanded, parent);
+ ImageView iv = (ImageView) v.findViewById(R.id.icon);
+ ViewGroup.LayoutParams p = iv.getLayoutParams();
+ p.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+ p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ ViewHolder vh = new ViewHolder();
+ vh.line1 = (TextView) v.findViewById(R.id.line1);
+ vh.line2 = (TextView) v.findViewById(R.id.line2);
+ vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator);
+ vh.icon = (ImageView) v.findViewById(R.id.icon);
+ vh.icon.setPadding(1, 1, 1, 1);
+ v.setTag(vh);
+ return v;
+ }
+
+ @Override
+ public View newChildView(Context context, Cursor cursor, boolean isLastChild,
+ ViewGroup parent) {
+ View v = super.newChildView(context, cursor, isLastChild, parent);
+ ViewHolder vh = new ViewHolder();
+ vh.line1 = (TextView) v.findViewById(R.id.line1);
+ vh.line2 = (TextView) v.findViewById(R.id.line2);
+ vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator);
+ vh.icon = (ImageView) v.findViewById(R.id.icon);
+ vh.icon.setBackgroundDrawable(mDefaultAlbumIcon);
+ vh.icon.setPadding(1, 1, 1, 1);
+ v.setTag(vh);
+ return v;
+ }
+
+ @Override
+ public void bindGroupView(View view, Context context, Cursor cursor, boolean isexpanded) {
+
+ ViewHolder vh = (ViewHolder) view.getTag();
+
+ String artist = cursor.getString(mGroupArtistIdx);
+ String displayartist = artist;
+ boolean unknown = MediaFile.UNKNOWN_STRING.equals(artist);
+ if (unknown) {
+ displayartist = mUnknownArtist;
+ }
+ vh.line1.setText(displayartist);
+
+ int numalbums = cursor.getInt(mGroupAlbumIdx);
+ int numsongs = cursor.getInt(mGroupSongIdx);
+
+ String songs_albums = MusicUtils.makeAlbumsSongsLabel(context,
+ numalbums, numsongs, unknown);
+
+ vh.line2.setText(songs_albums);
+
+ int currentartistid = MusicUtils.getCurrentArtistId();
+ int artistid = cursor.getInt(mGroupArtistIdIdx);
+ if (currentartistid == artistid && !isexpanded) {
+ vh.play_indicator.setImageDrawable(mNowPlayingOverlay);
+ } else {
+ vh.play_indicator.setImageDrawable(null);
+ }
+ }
+
+ @Override
+ public void bindChildView(View view, Context context, Cursor cursor, boolean islast) {
+
+ ViewHolder vh = (ViewHolder) view.getTag();
+
+ String name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));
+ String displayname = name;
+ if (name.equals(MediaFile.UNKNOWN_STRING)) {
+ displayname = mUnknownAlbum;
+ }
+ vh.line1.setText(displayname);
+
+ int numsongs = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.NUMBER_OF_SONGS));
+ int first = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.FIRST_YEAR));
+ int last = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.LAST_YEAR));
+
+ if (first == 0) {
+ first = last;
+ }
+
+ final StringBuilder builder = mBuffer;
+ builder.delete(0, builder.length());
+ if (numsongs == 1) {
+ builder.append(context.getString(R.string.onesong));
+ } else {
+ final Object[] args = mFormatArgs;
+ args[0] = numsongs;
+ builder.append(mResources.getQuantityString(R.plurals.Nsongs, numsongs, args));
+ }
+ if (first != 0 && last != 0) {
+ builder.append(mAlbumSongSeparator);
+
+ builder.append(first);
+ if (first != last) {
+ builder.append('-');
+ builder.append(last);
+ } else {
+ }
+ }
+ 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.getColumnIndex(MediaStore.Audio.Albums.ALBUM_ART));
+ if (art == null || art.length() == 0) {
+ iv.setBackgroundDrawable(mDefaultAlbumIcon);
+ iv.setImageDrawable(null);
+ } else {
+ int artIndex = cursor.getInt(0);
+ Drawable d = MusicUtils.getCachedArtwork(context, artIndex, mDefaultAlbumIcon);
+ iv.setImageDrawable(d);
+ }
+
+ int currentalbumid = MusicUtils.getCurrentAlbumId();
+ int aid = cursor.getInt(0);
+ iv = vh.play_indicator;
+ if (currentalbumid == aid) {
+ iv.setImageDrawable(mNowPlayingOverlay);
+ } else {
+ iv.setImageDrawable(null);
+ }
+ }
+
+
+ @Override
+ protected Cursor getChildrenCursor(Cursor groupCursor) {
+
+ int id = groupCursor.getInt(groupCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists._ID));
+
+ String[] cols = new String[] {
+ MediaStore.Audio.Albums._ID,
+ MediaStore.Audio.Albums.ALBUM,
+ MediaStore.Audio.Albums.ARTIST,
+ MediaStore.Audio.Albums.NUMBER_OF_SONGS,
+ MediaStore.Audio.Albums.FIRST_YEAR,
+ MediaStore.Audio.Albums.LAST_YEAR,
+ MediaStore.Audio.Albums.ALBUM_ART
+ };
+ return MusicUtils.query(ArtistAlbumBrowserActivity.this,
+ MediaStore.Audio.Artists.Albums.getContentUri("external", id),
+ cols, null, null, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
+ }
+
+ @Override
+ public void changeCursor(Cursor cursor) {
+ super.changeCursor(cursor);
+ mArtistCursor = cursor;
+ }
+ @Override
+ public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
+ return getArtistCursor(constraint.toString());
+ }
+
+ }
+
+ private Cursor mArtistCursor;
+}
+
--- /dev/null
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.database.Cursor;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+public class CreatePlaylist extends Activity
+{
+ private EditText mPlaylist;
+ private TextView mPrompt;
+ private Button mSaveButton;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(R.layout.create_playlist);
+ getWindow().setLayout(WindowManager.LayoutParams.FILL_PARENT,
+ WindowManager.LayoutParams.WRAP_CONTENT);
+
+ mPrompt = (TextView)findViewById(R.id.prompt);
+ mPlaylist = (EditText)findViewById(R.id.playlist);
+ mSaveButton = (Button) findViewById(R.id.create);
+ mSaveButton.setOnClickListener(mOpenClicked);
+
+ ((Button)findViewById(R.id.cancel)).setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ finish();
+ }
+ });
+
+ String defaultname = icicle != null ? icicle.getString("defaultname") : makePlaylistName();
+ String promptformat = getString(R.string.create_playlist_create_text_prompt);
+ String prompt = String.format(promptformat, defaultname);
+ mPrompt.setText(prompt);
+ mPlaylist.setText(defaultname);
+ mPlaylist.setSelection(defaultname.length());
+ mPlaylist.addTextChangedListener(mTextWatcher);
+ }
+
+ TextWatcher mTextWatcher = new TextWatcher() {
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ // don't care about this one
+ }
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ // check if playlist with current name exists already, and warn the user if so.
+ if (idForplaylist(mPlaylist.getText().toString()) >= 0) {
+ mSaveButton.setText(R.string.create_playlist_overwrite_text);
+ } else {
+ mSaveButton.setText(R.string.create_playlist_create_text);
+ }
+ };
+ public void afterTextChanged(Editable s) {
+ // don't care about this one
+ }
+ };
+
+ private int idForplaylist(String name) {
+ Cursor c = MusicUtils.query(this, MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+ new String[] { MediaStore.Audio.Playlists._ID },
+ MediaStore.Audio.Playlists.NAME + "=?",
+ new String[] { name },
+ MediaStore.Audio.Playlists.NAME);
+ int id = -1;
+ if (c != null) {
+ c.moveToFirst();
+ if (!c.isAfterLast()) {
+ id = c.getInt(0);
+ }
+ }
+ c.close();
+ return id;
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outcicle) {
+ outcicle.putString("defaultname", mPlaylist.getText().toString());
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ }
+
+ private String makePlaylistName() {
+
+ String template = getString(R.string.new_playlist_name_template);
+ int num = 1;
+
+ String[] cols = new String[] {
+ MediaStore.Audio.Playlists.NAME
+ };
+ ContentResolver resolver = getContentResolver();
+ String whereclause = MediaStore.Audio.Playlists.NAME + " != ''";
+ Cursor c = resolver.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+ cols, whereclause, null,
+ MediaStore.Audio.Playlists.NAME);
+
+ 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()).setAction(uri.toString()));
+ finish();
+ }
+ }
+ };
+}
--- /dev/null
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.database.Cursor;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+public class DeleteItems extends Activity
+{
+ private TextView mPrompt;
+ private Button mButton;
+ private int [] mItemList;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(R.layout.confirm_delete);
+ getWindow().setLayout(WindowManager.LayoutParams.FILL_PARENT,
+ WindowManager.LayoutParams.WRAP_CONTENT);
+
+ mPrompt = (TextView)findViewById(R.id.prompt);
+ mButton = (Button) findViewById(R.id.delete);
+ mButton.setOnClickListener(mButtonClicked);
+
+ ((Button)findViewById(R.id.cancel)).setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ finish();
+ }
+ });
+
+ Bundle b = getIntent().getExtras();
+ String desc = b.getString("description");
+ mItemList = b.getIntArray("items");
+
+ String promptformat = getString(R.string.delete_confirm_prompt);
+ String prompt = String.format(promptformat, desc);
+ mPrompt.setText(prompt);
+ }
+
+ private View.OnClickListener mButtonClicked = new View.OnClickListener() {
+ public void onClick(View v) {
+ // delete the selected item(s)
+ MusicUtils.deleteTracks(DeleteItems.this, mItemList);
+ finish();
+ }
+ };
+}
--- /dev/null
+/* //device/samples/SampleCode/src/com/android/samples/app/RemoteServiceInterface.java
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package com.android.music;
+
+import android.graphics.Bitmap;
+
+interface IMediaPlaybackService
+{
+ void openfile(String path);
+ void openfileAsync(String path);
+ void open(in int [] list, int position);
+ int getQueuePosition();
+ boolean isPlaying();
+ void stop();
+ void pause();
+ void play();
+ void prev();
+ void next();
+ long duration();
+ long position();
+ long seek(long pos);
+ String getTrackName();
+ String getAlbumName();
+ int getAlbumId();
+ String getArtistName();
+ int getArtistId();
+ void enqueue(in int [] list, int action);
+ int [] getQueue();
+ void moveQueueItem(int from, int to);
+ void setQueuePosition(int index);
+ String getPath();
+ int getAudioId();
+ void setShuffleMode(int shufflemode);
+ int getShuffleMode();
+ int removeTracks(int first, int last);
+ int removeTrack(int id);
+ void setRepeatMode(int repeatmode);
+ int getRepeatMode();
+ int getMediaMountedCount();
+}
+
--- /dev/null
+/*
+ * 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.Context;
+import android.content.Intent;
+import android.content.BroadcastReceiver;
+import android.content.SharedPreferences;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.os.Handler;
+import android.os.Message;
+
+/**
+ *
+ */
+public class MediaButtonIntentReceiver extends BroadcastReceiver {
+
+ private static final int MSG_LONGPRESS_TIMEOUT = 1;
+ private static final int LONG_PRESS_DELAY = 1000;
+
+ private static long mLastClickTime = 0;
+ private static boolean mDown = false;
+ private static boolean mLaunched = false;
+
+ private static Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_LONGPRESS_TIMEOUT:
+ if (!mLaunched) {
+ Context context = (Context)msg.obj;
+ Intent i = new Intent();
+ i.putExtra("autoshuffle", "true");
+ i.setClass(context, MusicBrowserActivity.class);
+ i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(i);
+ mLaunched = true;
+ }
+ break;
+ }
+ }
+ };
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ 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.
+
+ if (keycode == KeyEvent.KEYCODE_HEADSETHOOK) {
+ if (action == KeyEvent.ACTION_DOWN) {
+ if (!mDown) {
+ // only if this isn't a repeat event
+
+ // We're not using the original time of the event as the
+ // base here, because in some cases it can take more than
+ // one second for us to receive the event, in which case
+ // we would go immediately to auto shuffle mode, even if
+ // the user didn't long press.
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(MSG_LONGPRESS_TIMEOUT, context),
+ LONG_PRESS_DELAY);
+
+
+ SharedPreferences pref = context.getSharedPreferences("Music",
+ Context.MODE_WORLD_READABLE | Context.MODE_WORLD_WRITEABLE);
+ String q = pref.getString("queue", "");
+ // The service may or may not be running, but we need to send it
+ // a command.
+ Intent i = new Intent(context, MediaPlaybackService.class);
+ i.setAction(MediaPlaybackService.SERVICECMD);
+ if (eventtime - mLastClickTime < 300) {
+ i.putExtra(MediaPlaybackService.CMDNAME, MediaPlaybackService.CMDNEXT);
+ context.startService(i);
+ mLastClickTime = 0;
+ } else {
+ i.putExtra(MediaPlaybackService.CMDNAME,
+ MediaPlaybackService.CMDTOGGLEPAUSE);
+ context.startService(i);
+ mLastClickTime = eventtime;
+ }
+
+ mLaunched = false;
+ mDown = true;
+ }
+ } else {
+ mHandler.removeMessages(MSG_LONGPRESS_TIMEOUT);
+ mDown = false;
+ }
+ abortBroadcast();
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import com.android.internal.database.SortCursor;
+
+import android.app.ListActivity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ContentUris;
+import android.database.Cursor;
+import android.media.MediaFile;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.provider.MediaStore;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+public class MediaPickerActivity extends ListActivity implements MusicUtils.Defs
+{
+
+ public MediaPickerActivity()
+ {
+ }
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle icicle)
+ {
+ super.onCreate(icicle);
+
+ mFirstYear = getIntent().getStringExtra("firstyear");
+ mLastYear = getIntent().getStringExtra("lastyear");
+
+ if (mFirstYear == null) {
+ setTitle(R.string.all_title);
+ } else if (mFirstYear.equals(mLastYear)) {
+ setTitle(mFirstYear);
+ } else {
+ setTitle(mFirstYear + "-" + mLastYear);
+ }
+ MusicUtils.bindToService(this);
+ init();
+ }
+
+ @Override
+ public void onDestroy() {
+ MusicUtils.unbindFromService(this);
+ super.onDestroy();
+ }
+
+ 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.getColumnIndex(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.getColumnIndex(MediaStore.Video.Media._ID));
+ } else {
+ uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+ mediaId = mCursor.getLong(mCursor.getColumnIndex(MediaStore.Audio.Media._ID));
+ }
+
+ setResult(RESULT_OK, new Intent().setData(ContentUris.withAppendedId(uri, mediaId)));
+ finish();
+ return;
+ }
+
+ // Need to stop the playbackservice, in case it is busy playing audio
+ // and the user selected a video.
+ if (MusicUtils.sService != null) {
+ try {
+ MusicUtils.sService.stop();
+ } catch (RemoteException ex) {
+ }
+ }
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setDataAndType(ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id), type);
+
+ startActivity(intent);
+ }
+
+ private void MakeCursor() {
+ String[] audiocols = new String[] {
+ MediaStore.Audio.Media._ID,
+ MediaStore.Audio.Media.ARTIST,
+ MediaStore.Audio.Media.ALBUM,
+ MediaStore.Audio.Media.TITLE,
+ MediaStore.Audio.Media.DATA,
+ MediaStore.Audio.Media.MIME_TYPE,
+ MediaStore.Audio.Media.YEAR
+ };
+ String[] videocols = new String[] {
+ MediaStore.Audio.Media._ID,
+ MediaStore.Audio.Media.TITLE,
+ MediaStore.Audio.Media.ARTIST,
+ MediaStore.Audio.Media.ALBUM,
+ MediaStore.Audio.Media.TITLE,
+ MediaStore.Audio.Media.DATA,
+ MediaStore.Audio.Media.MIME_TYPE
+ };
+
+ Cursor[] cs;
+ // Use ArrayList for the moment, since we don't know the size of
+ // Cursor[]. If the length of Corsor[] larger than really used,
+ // a NPE will come up when access the content of Corsor[].
+ ArrayList<Cursor> cList = new ArrayList<Cursor>();
+ Intent intent = getIntent();
+ String type = intent.getType();
+
+ if (mFirstYear != null) {
+ // If mFirstYear is not null, the picker only for audio because
+ // video has no year column.
+ if(type.equals("video/*")) {
+ mCursor = null;
+ return;
+ }
+
+ mWhereClause = MediaStore.Audio.Media.YEAR + ">=" + mFirstYear + " AND " +
+ MediaStore.Audio.Media.YEAR + "<=" + mLastYear;
+ }
+
+ // If use Cursor[] as before, the Cursor[i] could be null when there is
+ // no video/audio/sdcard. Then a NPE will come up when access the content of the
+ // Array.
+
+ Cursor c;
+ if (type.equals("video/*")) {
+ // Only video.
+ c = MusicUtils.query(this, MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
+ videocols, null , null, mSortOrder);
+ if (c != null) {
+ cList.add(c);
+ }
+ } else {
+ c = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+ audiocols, mWhereClause , null, mSortOrder);
+
+ if (c != null) {
+ cList.add(c);
+ }
+
+ if (mFirstYear == null && intent.getType().equals("media/*")) {
+ // video has no year column
+ c = MusicUtils.query(this, MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
+ videocols, null , null, mSortOrder);
+ if (c != null) {
+ cList.add(c);
+ }
+ }
+ }
+
+ // Get the ArrayList size.
+ int size = cList.size();
+ if (0 == size) {
+ // If no video/audio/SDCard exist, return.
+ mCursor = null;
+ return;
+ }
+
+ // The size is known now, we're sure each item of Cursor[] is not null.
+ cs = new Cursor[size];
+ cs = cList.toArray(cs);
+ mCursor = new SortCursor(cs, MediaStore.Audio.Media.TITLE);
+ }
+
+ private Cursor mCursor;
+ private String mSortOrder = MediaStore.Audio.Media.TITLE + " COLLATE UNICODE";
+ private String mFirstYear;
+ private String mLastYear;
+ private String mWhereClause;
+
+ class PickListAdapter extends SimpleCursorAdapter {
+ int mTitleIdx;
+ int mArtistIdx;
+ int mAlbumIdx;
+ int mMimeIdx;
+
+ PickListAdapter(Context context, int layout, Cursor cursor, String[] from, int[] to) {
+ super(context, layout, cursor, from, to);
+
+ mTitleIdx = cursor.getColumnIndex(MediaStore.Audio.Media.TITLE);
+ mArtistIdx = cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST);
+ mAlbumIdx = cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM);
+ mMimeIdx = cursor.getColumnIndex(MediaStore.Audio.Media.MIME_TYPE);
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ View v = super.newView(context, cursor, parent);
+ ImageView iv = (ImageView) v.findViewById(R.id.icon);
+ iv.setVisibility(View.VISIBLE);
+ ViewGroup.LayoutParams p = iv.getLayoutParams();
+ p.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+ p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+
+ TextView tv = (TextView) v.findViewById(R.id.duration);
+ tv.setVisibility(View.GONE);
+ iv = (ImageView) v.findViewById(R.id.play_indicator);
+ iv.setVisibility(View.GONE);
+
+ return v;
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+
+ TextView tv = (TextView) view.findViewById(R.id.line1);
+ String name = cursor.getString(mTitleIdx);
+ tv.setText(name);
+
+ tv = (TextView) view.findViewById(R.id.line2);
+ name = cursor.getString(mAlbumIdx);
+ StringBuilder builder = new StringBuilder();
+ if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
+ builder.append(context.getString(R.string.unknown_album_name));
+ } else {
+ builder.append(name);
+ }
+ builder.append("\n");
+ name = cursor.getString(mArtistIdx);
+ if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
+ builder.append(context.getString(R.string.unknown_artist_name));
+ } else {
+ builder.append(name);
+ }
+ tv.setText(builder.toString());
+
+ String text = cursor.getString(mMimeIdx);
+ ImageView iv = (ImageView) view.findViewById(R.id.icon);;
+ if("audio/midi".equals(text)) {
+ iv.setImageResource(R.drawable.midi);
+ } else if(text != null && (text.startsWith("audio") ||
+ text.equals("application/ogg") ||
+ text.equals("application/x-ogg"))) {
+ iv.setImageResource(R.drawable.ic_search_category_music_song);
+ } else if(text != null && text.startsWith("video")) {
+ iv.setImageResource(R.drawable.movie);
+ } else {
+ iv.setImageResource(0);
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.graphics.Bitmap;
+import android.media.AudioManager;
+import android.media.MediaFile;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.ContextMenu;
+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.Window;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.ImageButton;
+import android.widget.ProgressBar;
+import android.widget.SeekBar;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+
+
+public class MediaPlaybackActivity extends Activity implements MusicUtils.Defs, View.OnTouchListener
+{
+ private static final int USE_AS_RINGTONE = CHILD_MENU_BASE;
+
+ private boolean mOneShot = false;
+ private boolean mSeeking = false;
+ private boolean mTrackball;
+ private long mStartSeekPos = 0;
+ private long mLastSeekEventTime;
+ private IMediaPlaybackService mService = null;
+ private RepeatingImageButton mPrevButton;
+ private ImageButton mPauseButton;
+ private RepeatingImageButton mNextButton;
+ private ImageButton mRepeatButton;
+ private ImageButton mShuffleButton;
+ private ImageButton mQueueButton;
+ private Worker mAlbumArtWorker;
+ private AlbumArtHandler mAlbumArtHandler;
+ private Toast mToast;
+ private boolean mRelaunchAfterConfigChange;
+
+ public MediaPlaybackActivity()
+ {
+ }
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle icicle)
+ {
+ super.onCreate(icicle);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+ mAlbumArtWorker = new Worker("album art worker");
+ mAlbumArtHandler = new AlbumArtHandler(mAlbumArtWorker.getLooper());
+
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(R.layout.audio_player);
+
+ mCurrentTime = (TextView) findViewById(R.id.currenttime);
+ mTotalTime = (TextView) findViewById(R.id.totaltime);
+ mProgress = (ProgressBar) findViewById(android.R.id.progress);
+ mAlbum = (AlbumView) findViewById(R.id.album);
+ mArtistName = (TextView) findViewById(R.id.artistname);
+ mAlbumName = (TextView) findViewById(R.id.albumname);
+ mTrackName = (TextView) findViewById(R.id.trackname);
+
+ View v = (View)mArtistName.getParent();
+ v.setOnTouchListener(this);
+ registerForContextMenu(v);
+
+ v = (View)mAlbumName.getParent();
+ v.setOnTouchListener(this);
+ registerForContextMenu(v);
+
+ v = (View)mTrackName.getParent();
+ v.setOnTouchListener(this);
+ registerForContextMenu(v);
+
+ mPrevButton = (RepeatingImageButton) findViewById(R.id.prev);
+ mPrevButton.setOnClickListener(mPrevListener);
+ mPrevButton.setRepeatListener(mRewListener, 260);
+ mPauseButton = (ImageButton) findViewById(R.id.pause);
+ mPauseButton.requestFocus();
+ mPauseButton.setOnClickListener(mPauseListener);
+ mNextButton = (RepeatingImageButton) findViewById(R.id.next);
+ mNextButton.setOnClickListener(mNextListener);
+ mNextButton.setRepeatListener(mFfwdListener, 260);
+ seekmethod = 1;
+
+ mTrackball = true; /* (See bug 1044348) (getResources().getConfiguration().navigation ==
+ Resources.Configuration.NAVIGATION_TRACKBALL);*/
+
+ mQueueButton = (ImageButton) findViewById(R.id.curplaylist);
+ mQueueButton.setOnClickListener(mQueueListener);
+ mShuffleButton = ((ImageButton) findViewById(R.id.shuffle));
+ mShuffleButton.setOnClickListener(mShuffleListener);
+ mRepeatButton = ((ImageButton) findViewById(R.id.repeat));
+ mRepeatButton.setOnClickListener(mRepeatListener);
+
+ if (mProgress instanceof SeekBar) {
+ SeekBar seeker = (SeekBar) mProgress;
+ seeker.setOnSeekBarChangeListener(mSeekListener);
+ }
+ mProgress.setMax(1000);
+
+ if (icicle != null) {
+ mRelaunchAfterConfigChange = icicle.getBoolean("configchange");
+ mOneShot = icicle.getBoolean("oneshot");
+ } else {
+ mOneShot = getIntent().getBooleanExtra("oneshot", false);
+ }
+ }
+
+ public boolean onTouch(View v, MotionEvent event) {
+ int action = event.getAction();
+ if (action == MotionEvent.ACTION_DOWN) {
+ v.setBackgroundColor(0xff606060);
+ } else if (action == MotionEvent.ACTION_UP ||
+ action == MotionEvent.ACTION_CANCEL) {
+ v.setBackgroundColor(0);
+ }
+ return false;
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
+
+ /*
+ * A better way to do this would be to define a new "media search" intent (which
+ * would behave similar to a regular search intent), and have amazon, youtube, the
+ * browser and other suitable apps support it. Then we could just fire off the
+ * intent and let the user choose from the activity picker.
+ */
+ CharSequence title = null;
+ String query = null;
+ CharSequence artist = mArtistName.getText();
+ CharSequence album = mAlbumName.getText();
+ CharSequence song = mTrackName.getText();
+ if (view.equals(mArtistName.getParent()) && artist.length() > 0) {
+ title = artist;
+ query = artist.toString();
+ } else if (view.equals(mAlbumName.getParent()) &&
+ artist.length() > 0 && album.length() > 0) {
+ title = album ;
+ query = artist.toString() + " " + album.toString();
+ } else if (view.equals(mTrackName.getParent()) &&
+ artist.length() > 0 && song.length() > 0) {
+ title = song;
+ query = artist.toString() + " " + song.toString();
+ } else {
+ return;
+ }
+
+ title = getString(R.string.mediasearch, title);
+ TextView tv = new TextView(this);
+ tv.setText(title);
+ tv.setTextSize(18);
+ tv.setPadding(8, 8, 8, 8);
+ menu.setHeaderView(tv);
+ //menu.setHeaderTitle(title);
+
+ Intent i = new Intent();
+ i.setAction(Intent.ACTION_SEARCH);
+ i.setClassName("com.amazon.mp3", "com.amazon.mp3.android.client.SearchActivity");
+ i.putExtra("query", query);
+ PackageManager pm = getPackageManager();
+ ActivityInfo ai = i.resolveActivityInfo(pm, 0);
+ if ( ai != null) {
+ menu.add(R.string.mediasearch_amazon).setIntent(i);
+ }
+
+ i = new Intent();
+ i.setAction(Intent.ACTION_WEB_SEARCH);
+ i.putExtra("query", query);
+ menu.add(R.string.mediasearch_google).setIntent(i);
+
+ i = new Intent();
+ i.setAction(Intent.ACTION_SEARCH);
+ i.setClassName("com.google.android.youtube", "com.google.android.youtube.QueryActivity");
+ i.putExtra("query", query);
+ menu.add(R.string.mediasearch_youtube).setIntent(i);
+ return;
+ }
+
+ private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
+ public void onStartTrackingTouch(SeekBar bar) {
+ mLastSeekEventTime = 0;
+ }
+ public void onProgressChanged(SeekBar bar, int progress, boolean fromtouch) {
+ if (mService == null) return;
+ if (fromtouch) {
+ long now = SystemClock.elapsedRealtime();
+ if ((now - mLastSeekEventTime) > 250) {
+ mLastSeekEventTime = now;
+ mPosOverride = mDuration * progress / 1000;
+ try {
+ mService.seek(mPosOverride);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+ }
+ public void onStopTrackingTouch(SeekBar bar) {
+ mPosOverride = -1;
+ }
+ };
+
+ private View.OnClickListener mQueueListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ startActivity(
+ new Intent(Intent.ACTION_EDIT)
+ .setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track")
+ .putExtra("playlist", "nowplaying")
+ );
+ }
+ };
+
+ private View.OnClickListener mShuffleListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ toggleShuffle();
+ }
+ };
+
+ private View.OnClickListener mRepeatListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ cycleRepeat();
+ }
+ };
+
+ private View.OnClickListener mPauseListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ doPauseResume();
+ }
+ };
+
+ private View.OnClickListener mPrevListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ if (mService == null) return;
+ try {
+ if (mService.position() < 2000) {
+ mService.prev();
+ } else {
+ mService.seek(0);
+ mService.play();
+ }
+ } catch (RemoteException ex) {
+ }
+ }
+ };
+
+ private View.OnClickListener mNextListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ if (mService == null) return;
+ try {
+ mService.next();
+ } catch (RemoteException ex) {
+ }
+ }
+ };
+
+ private RepeatingImageButton.RepeatListener mRewListener =
+ new RepeatingImageButton.RepeatListener() {
+ public void onRepeat(View v, long howlong, int repcnt) {
+ scanBackward(repcnt, howlong);
+ }
+ };
+
+ private RepeatingImageButton.RepeatListener mFfwdListener =
+ new RepeatingImageButton.RepeatListener() {
+ public void onRepeat(View v, long howlong, int repcnt) {
+ scanForward(repcnt, howlong);
+ }
+ };
+
+ @Override
+ public void onStop() {
+ paused = true;
+ if (mService != null && mOneShot && getChangingConfigurations() == 0) {
+ try {
+ mService.stop();
+ } catch (RemoteException ex) {
+ }
+ }
+ mHandler.removeMessages(REFRESH);
+ unregisterReceiver(mStatusListener);
+ MusicUtils.unbindFromService(this);
+ super.onStop();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ outState.putBoolean("configchange", getChangingConfigurations() != 0);
+ outState.putBoolean("oneshot", mOneShot);
+ super.onSaveInstanceState(outState);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ paused = false;
+
+ if (false == MusicUtils.bindToService(this, osc)) {
+ // something went wrong
+ mHandler.sendEmptyMessage(QUIT);
+ }
+
+ IntentFilter f = new IntentFilter();
+ f.addAction(MediaPlaybackService.PLAYSTATE_CHANGED);
+ f.addAction(MediaPlaybackService.META_CHANGED);
+ f.addAction(MediaPlaybackService.PLAYBACK_COMPLETE);
+ registerReceiver(mStatusListener, new IntentFilter(f));
+ updateTrackInfo();
+ long next = refreshNow();
+ queueNextRefresh(next);
+ }
+
+ @Override
+ public void onNewIntent(Intent intent) {
+ setIntent(intent);
+ mOneShot = intent.getBooleanExtra("oneshot", false);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ updateTrackInfo();
+ setPauseButtonImage();
+ }
+
+ @Override
+ public void onDestroy()
+ {
+ mAlbumArtWorker.quit();
+ super.onDestroy();
+ //System.out.println("***************** playback activity onDestroy\n");
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ // Don't show the menu items if we got launched by path/filedescriptor, since
+ // those tend to not be in the media database.
+ if (MusicUtils.getCurrentAudioId() >= 0) {
+ if (!mOneShot) {
+ menu.add(0, GOTO_START, 0, R.string.goto_start).setIcon(R.drawable.ic_menu_music_library);
+ menu.add(0, PARTY_SHUFFLE, 0, R.string.party_shuffle); // icon will be set in onPrepareOptionsMenu()
+ }
+ SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0,
+ R.string.add_to_playlist).setIcon(R.drawable.ic_menu_add);
+ MusicUtils.makePlaylistMenu(this, sub);
+ menu.add(0, USE_AS_RINGTONE, 0, R.string.ringtone_menu_short).setIcon(R.drawable.ic_menu_set_as_ringtone);
+ menu.add(0, DELETE_ITEM, 0, R.string.delete_item).setIcon(R.drawable.ic_menu_delete);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ MenuItem item = menu.findItem(PARTY_SHUFFLE);
+ if (item != null) {
+ int shuffle = MusicUtils.getCurrentShuffleMode();
+ if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
+ item.setIcon(R.drawable.ic_menu_party_shuffle);
+ item.setTitle(R.string.party_shuffle_off);
+ } else {
+ item.setIcon(R.drawable.ic_menu_party_shuffle);
+ item.setTitle(R.string.party_shuffle);
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ Intent intent;
+ try {
+ switch (item.getItemId()) {
+ case GOTO_START:
+ intent = new Intent();
+ intent.setClass(this, MusicBrowserActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ break;
+ case USE_AS_RINGTONE: {
+ // Set the system setting to make this the current ringtone
+ if (mService != null) {
+ MusicUtils.setRingtone(this, mService.getAudioId());
+ }
+ return true;
+ }
+ case PARTY_SHUFFLE:
+ if (mService != null) {
+ int shuffle = mService.getShuffleMode();
+ if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
+ mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
+ } else {
+ mService.setShuffleMode(MediaPlaybackService.SHUFFLE_AUTO);
+ }
+ }
+ setShuffleButtonImage();
+ break;
+
+ case NEW_PLAYLIST: {
+ intent = new Intent();
+ intent.setClass(this, CreatePlaylist.class);
+ startActivityForResult(intent, NEW_PLAYLIST);
+ return true;
+ }
+
+ case PLAYLIST_SELECTED: {
+ int [] list = new int[1];
+ list[0] = MusicUtils.getCurrentAudioId();
+ int playlist = item.getIntent().getIntExtra("playlist", 0);
+ MusicUtils.addToPlaylist(this, list, playlist);
+ return true;
+ }
+
+ case DELETE_ITEM: {
+ if (mService != null) {
+ int [] list = new int[1];
+ list[0] = MusicUtils.getCurrentAudioId();
+ Bundle b = new Bundle();
+ b.putString("description", mService.getTrackName());
+ b.putIntArray("items", list);
+ intent = new Intent();
+ intent.setClass(this, DeleteItems.class);
+ intent.putExtras(b);
+ startActivityForResult(intent, -1);
+ }
+ return true;
+ }
+ }
+ } catch (RemoteException ex) {
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ if (resultCode != RESULT_OK) {
+ return;
+ }
+ switch (requestCode) {
+ case NEW_PLAYLIST:
+ Uri uri = Uri.parse(intent.getAction());
+ if (uri != null) {
+ int [] list = new int[1];
+ list[0] = MusicUtils.getCurrentAudioId();
+ int playlist = Integer.parseInt(uri.getLastPathSegment());
+ MusicUtils.addToPlaylist(this, list, playlist);
+ }
+ break;
+ }
+ }
+ private final int keyboard[][] = {
+ {
+ KeyEvent.KEYCODE_Q,
+ KeyEvent.KEYCODE_W,
+ KeyEvent.KEYCODE_E,
+ KeyEvent.KEYCODE_R,
+ KeyEvent.KEYCODE_T,
+ KeyEvent.KEYCODE_Y,
+ KeyEvent.KEYCODE_U,
+ KeyEvent.KEYCODE_I,
+ KeyEvent.KEYCODE_O,
+ KeyEvent.KEYCODE_P,
+ },
+ {
+ KeyEvent.KEYCODE_A,
+ KeyEvent.KEYCODE_S,
+ KeyEvent.KEYCODE_D,
+ KeyEvent.KEYCODE_F,
+ KeyEvent.KEYCODE_G,
+ KeyEvent.KEYCODE_H,
+ KeyEvent.KEYCODE_J,
+ KeyEvent.KEYCODE_K,
+ KeyEvent.KEYCODE_L,
+ KeyEvent.KEYCODE_DEL,
+ },
+ {
+ KeyEvent.KEYCODE_Z,
+ KeyEvent.KEYCODE_X,
+ KeyEvent.KEYCODE_C,
+ KeyEvent.KEYCODE_V,
+ KeyEvent.KEYCODE_B,
+ KeyEvent.KEYCODE_N,
+ KeyEvent.KEYCODE_M,
+ KeyEvent.KEYCODE_COMMA,
+ KeyEvent.KEYCODE_PERIOD,
+ KeyEvent.KEYCODE_ENTER
+ }
+
+ };
+
+ private int lastX;
+ private int lastY;
+
+ private boolean seekMethod1(int keyCode)
+ {
+ for(int x=0;x<10;x++) {
+ for(int y=0;y<3;y++) {
+ if(keyboard[y][x] == keyCode) {
+ int dir = 0;
+ // top row
+ if(x == lastX && y == lastY) dir = 0;
+ else if (y == 0 && lastY == 0 && x > lastX) dir = 1;
+ else if (y == 0 && lastY == 0 && x < lastX) dir = -1;
+ // bottom row
+ else if (y == 2 && lastY == 2 && x > lastX) dir = -1;
+ else if (y == 2 && lastY == 2 && x < lastX) dir = 1;
+ // moving up
+ else if (y < lastY && x <= 4) dir = 1;
+ else if (y < lastY && x >= 5) dir = -1;
+ // moving down
+ else if (y > lastY && x <= 4) dir = -1;
+ else if (y > lastY && x >= 5) dir = 1;
+ lastX = x;
+ lastY = y;
+ try {
+ mService.seek(mService.position() + dir * 5);
+ } catch (RemoteException ex) {
+ }
+ refreshNow();
+ return true;
+ }
+ }
+ }
+ lastX = -1;
+ lastY = -1;
+ return false;
+ }
+
+ private boolean seekMethod2(int keyCode)
+ {
+ if (mService == null) return false;
+ for(int i=0;i<10;i++) {
+ if(keyboard[0][i] == keyCode) {
+ int seekpercentage = 100*i/10;
+ try {
+ mService.seek(mService.duration() * seekpercentage / 100);
+ } catch (RemoteException ex) {
+ }
+ refreshNow();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ try {
+ switch(keyCode)
+ {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (mTrackball) {
+ break;
+ }
+ if (mService != null) {
+ if (!mSeeking && mStartSeekPos >= 0) {
+ mPauseButton.requestFocus();
+ if (mStartSeekPos < 1000) {
+ mService.prev();
+ } else {
+ mService.seek(0);
+ }
+ } else {
+ scanBackward(-1, event.getEventTime() - event.getDownTime());
+ mPauseButton.requestFocus();
+ mStartSeekPos = -1;
+ }
+ }
+ mSeeking = false;
+ mPosOverride = -1;
+ return true;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (mTrackball) {
+ break;
+ }
+ if (mService != null) {
+ if (!mSeeking && mStartSeekPos >= 0) {
+ mPauseButton.requestFocus();
+ mService.next();
+ } else {
+ scanForward(-1, event.getEventTime() - event.getDownTime());
+ mPauseButton.requestFocus();
+ mStartSeekPos = -1;
+ }
+ }
+ mSeeking = false;
+ mPosOverride = -1;
+ return true;
+ }
+ } catch (RemoteException ex) {
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event)
+ {
+ int direction = -1;
+ int repcnt = event.getRepeatCount();
+
+ if((seekmethod==0)?seekMethod1(keyCode):seekMethod2(keyCode))
+ return true;
+
+ switch(keyCode)
+ {
+/*
+ // image scale
+ case KeyEvent.KEYCODE_Q: av.adjustParams(-0.05, 0.0, 0.0, 0.0, 0.0,-1.0); break;
+ case KeyEvent.KEYCODE_E: av.adjustParams( 0.05, 0.0, 0.0, 0.0, 0.0, 1.0); break;
+ // image translate
+ case KeyEvent.KEYCODE_W: av.adjustParams( 0.0, 0.0,-1.0, 0.0, 0.0, 0.0); break;
+ case KeyEvent.KEYCODE_X: av.adjustParams( 0.0, 0.0, 1.0, 0.0, 0.0, 0.0); break;
+ case KeyEvent.KEYCODE_A: av.adjustParams( 0.0,-1.0, 0.0, 0.0, 0.0, 0.0); break;
+ case KeyEvent.KEYCODE_D: av.adjustParams( 0.0, 1.0, 0.0, 0.0, 0.0, 0.0); break;
+ // camera rotation
+ case KeyEvent.KEYCODE_R: av.adjustParams( 0.0, 0.0, 0.0, 0.0, 0.0,-1.0); break;
+ case KeyEvent.KEYCODE_U: av.adjustParams( 0.0, 0.0, 0.0, 0.0, 0.0, 1.0); break;
+ // camera translate
+ case KeyEvent.KEYCODE_Y: av.adjustParams( 0.0, 0.0, 0.0, 0.0,-1.0, 0.0); break;
+ case KeyEvent.KEYCODE_N: av.adjustParams( 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); break;
+ case KeyEvent.KEYCODE_G: av.adjustParams( 0.0, 0.0, 0.0,-1.0, 0.0, 0.0); break;
+ case KeyEvent.KEYCODE_J: av.adjustParams( 0.0, 0.0, 0.0, 1.0, 0.0, 0.0); break;
+
+*/
+
+ case KeyEvent.KEYCODE_SLASH:
+ seekmethod = 1 - seekmethod;
+ return true;
+
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (mTrackball) {
+ break;
+ }
+ if (!mPrevButton.hasFocus()) {
+ mPrevButton.requestFocus();
+ }
+ scanBackward(repcnt, event.getEventTime() - event.getDownTime());
+ return true;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (mTrackball) {
+ break;
+ }
+ if (!mNextButton.hasFocus()) {
+ mNextButton.requestFocus();
+ }
+ scanForward(repcnt, event.getEventTime() - event.getDownTime());
+ return true;
+
+ case KeyEvent.KEYCODE_S:
+ toggleShuffle();
+ return true;
+
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ case KeyEvent.KEYCODE_SPACE:
+ doPauseResume();
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ private void scanBackward(int repcnt, long delta) {
+ if(mService == null) return;
+ try {
+ if(repcnt == 0) {
+ mStartSeekPos = mService.position();
+ mLastSeekEventTime = 0;
+ mSeeking = false;
+ } else {
+ mSeeking = true;
+ if (delta < 5000) {
+ // seek at 10x speed for the first 5 seconds
+ delta = delta * 10;
+ } else {
+ // seek at 40x after that
+ delta = 50000 + (delta - 5000) * 40;
+ }
+ long newpos = mStartSeekPos - delta;
+ if (newpos < 0) {
+ // move to previous track
+ mService.prev();
+ long duration = mService.duration();
+ mStartSeekPos += duration;
+ newpos += duration;
+ }
+ if (((delta - mLastSeekEventTime) > 250) || repcnt < 0){
+ mService.seek(newpos);
+ mLastSeekEventTime = delta;
+ }
+ if (repcnt >= 0) {
+ mPosOverride = newpos;
+ } else {
+ mPosOverride = -1;
+ }
+ refreshNow();
+ }
+ } catch (RemoteException ex) {
+ }
+ }
+
+ private void scanForward(int repcnt, long delta) {
+ if(mService == null) return;
+ try {
+ if(repcnt == 0) {
+ mStartSeekPos = mService.position();
+ mLastSeekEventTime = 0;
+ mSeeking = false;
+ } else {
+ mSeeking = true;
+ if (delta < 5000) {
+ // seek at 10x speed for the first 5 seconds
+ delta = delta * 10;
+ } else {
+ // seek at 40x after that
+ delta = 50000 + (delta - 5000) * 40;
+ }
+ long newpos = mStartSeekPos + delta;
+ long duration = mService.duration();
+ if (newpos >= duration) {
+ // move to next track
+ mService.next();
+ mStartSeekPos -= duration; // is OK to go negative
+ newpos -= duration;
+ }
+ if (((delta - mLastSeekEventTime) > 250) || repcnt < 0){
+ mService.seek(newpos);
+ mLastSeekEventTime = delta;
+ }
+ if (repcnt >= 0) {
+ mPosOverride = newpos;
+ } else {
+ mPosOverride = -1;
+ }
+ refreshNow();
+ }
+ } catch (RemoteException ex) {
+ }
+ }
+
+ private void doPauseResume() {
+ try {
+ if(mService != null) {
+ if (mService.isPlaying()) {
+ mService.pause();
+ } else {
+ mService.play();
+ }
+ refreshNow();
+ setPauseButtonImage();
+ }
+ } catch (RemoteException ex) {
+ }
+ }
+
+ private void toggleShuffle() {
+ if (mService == null) {
+ return;
+ }
+ try {
+ int shuffle = mService.getShuffleMode();
+ if (shuffle == MediaPlaybackService.SHUFFLE_NONE) {
+ mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NORMAL);
+ if (mService.getRepeatMode() == MediaPlaybackService.REPEAT_CURRENT) {
+ mService.setRepeatMode(MediaPlaybackService.REPEAT_ALL);
+ setRepeatButtonImage();
+ }
+ showToast(R.string.shuffle_on_notif);
+ } else if (shuffle == MediaPlaybackService.SHUFFLE_NORMAL ||
+ shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
+ mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
+ showToast(R.string.shuffle_off_notif);
+ } else {
+ Log.e("MediaPlaybackActivity", "Invalid shuffle mode: " + shuffle);
+ }
+ setShuffleButtonImage();
+ } catch (RemoteException ex) {
+ }
+ }
+
+ private void cycleRepeat() {
+ if (mService == null) {
+ return;
+ }
+ try {
+ int mode = mService.getRepeatMode();
+ if (mode == MediaPlaybackService.REPEAT_NONE) {
+ mService.setRepeatMode(MediaPlaybackService.REPEAT_ALL);
+ showToast(R.string.repeat_all_notif);
+ } else if (mode == MediaPlaybackService.REPEAT_ALL) {
+ mService.setRepeatMode(MediaPlaybackService.REPEAT_CURRENT);
+ if (mService.getShuffleMode() != MediaPlaybackService.SHUFFLE_NONE) {
+ mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
+ setShuffleButtonImage();
+ }
+ showToast(R.string.repeat_current_notif);
+ } else {
+ mService.setRepeatMode(MediaPlaybackService.REPEAT_NONE);
+ showToast(R.string.repeat_off_notif);
+ }
+ setRepeatButtonImage();
+ } catch (RemoteException ex) {
+ }
+
+ }
+
+ private void showToast(int resid) {
+ if (mToast == null) {
+ mToast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
+ }
+ mToast.setText(resid);
+ mToast.show();
+ }
+
+ private void startPlayback() {
+
+ if(mService == null)
+ return;
+ Intent intent = getIntent();
+ String filename = "";
+ Uri uri = intent.getData();
+ if (uri != null && uri.toString().length() > 0) {
+ // If this is a file:// URI, just use the path directly instead
+ // of going through the open-from-filedescriptor codepath.
+ String scheme = uri.getScheme();
+ if ("file".equals(scheme)) {
+ filename = uri.getPath();
+ } else {
+ filename = uri.toString();
+ }
+ try {
+ mOneShot = true;
+ if (! mRelaunchAfterConfigChange) {
+ mService.stop();
+ mService.openfile(filename);
+ mService.play();
+ }
+ } catch (Exception ex) {
+ Log.d("MediaPlaybackActivity", "couldn't start playback: " + ex);
+ }
+ }
+
+ updateTrackInfo();
+ long next = refreshNow();
+ queueNextRefresh(next);
+ }
+
+ private ServiceConnection osc = new ServiceConnection() {
+ public void onServiceConnected(ComponentName classname, IBinder obj) {
+ mService = IMediaPlaybackService.Stub.asInterface(obj);
+ if (MusicUtils.sService == null) {
+ MusicUtils.sService = mService;
+ }
+ startPlayback();
+ try {
+ // Assume something is playing when the service says it is,
+ // but also if the audio ID is valid but the service is paused.
+ if (mService.getAudioId() >= 0 || mService.isPlaying() ||
+ mService.getPath() != null) {
+ // something is playing now, we're done
+ if (mOneShot) {
+ mRepeatButton.setVisibility(View.INVISIBLE);
+ mShuffleButton.setVisibility(View.INVISIBLE);
+ mQueueButton.setVisibility(View.INVISIBLE);
+ } else {
+ mRepeatButton.setVisibility(View.VISIBLE);
+ mShuffleButton.setVisibility(View.VISIBLE);
+ mQueueButton.setVisibility(View.VISIBLE);
+ setRepeatButtonImage();
+ setShuffleButtonImage();
+ }
+ setPauseButtonImage();
+ return;
+ }
+ } catch (RemoteException ex) {
+ }
+ // Service is dead or not playing anything. If we got here as part
+ // of a "play this file" Intent, exit. Otherwise go to the Music
+ // app start screen.
+ if (getIntent().getData() == null) {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setClass(MediaPlaybackActivity.this, MusicBrowserActivity.class);
+ startActivity(intent);
+ }
+ finish();
+ }
+ public void onServiceDisconnected(ComponentName classname) {
+ }
+ };
+
+ private void setRepeatButtonImage() {
+ try {
+ switch (mService.getRepeatMode()) {
+ case MediaPlaybackService.REPEAT_ALL:
+ mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_all_btn);
+ break;
+ case MediaPlaybackService.REPEAT_CURRENT:
+ mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_once_btn);
+ break;
+ default:
+ mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_off_btn);
+ break;
+ }
+ } catch (RemoteException ex) {
+ }
+ }
+
+ private void setShuffleButtonImage() {
+ try {
+ switch (mService.getShuffleMode()) {
+ case MediaPlaybackService.SHUFFLE_NONE:
+ mShuffleButton.setImageResource(R.drawable.ic_mp_shuffle_off_btn);
+ break;
+ case MediaPlaybackService.SHUFFLE_AUTO:
+ mShuffleButton.setImageResource(R.drawable.ic_mp_partyshuffle_on_btn);
+ break;
+ default:
+ mShuffleButton.setImageResource(R.drawable.ic_mp_shuffle_on_btn);
+ break;
+ }
+ } catch (RemoteException ex) {
+ }
+ }
+
+ private void setPauseButtonImage() {
+ try {
+ if (mService != null && mService.isPlaying()) {
+ mPauseButton.setImageResource(android.R.drawable.ic_media_pause);
+ } else {
+ mPauseButton.setImageResource(android.R.drawable.ic_media_play);
+ }
+ } catch (RemoteException ex) {
+ }
+ }
+
+ private AlbumView mAlbum;
+ private TextView mCurrentTime;
+ private TextView mTotalTime;
+ private TextView mArtistName;
+ private TextView mAlbumName;
+ private TextView mTrackName;
+ private ProgressBar mProgress;
+ private long mPosOverride = -1;
+ private long mDuration;
+ private int seekmethod;
+ private boolean paused;
+
+ private static final int REFRESH = 1;
+ private static final int QUIT = 2;
+ private static final int GET_ALBUM_ART = 3;
+ private static final int ALBUM_ART_DECODED = 4;
+
+ private void queueNextRefresh(long delay) {
+ if (!paused) {
+ Message msg = mHandler.obtainMessage(REFRESH);
+ mHandler.removeMessages(REFRESH);
+ mHandler.sendMessageDelayed(msg, delay);
+ }
+ }
+
+ private long refreshNow() {
+ if(mService == null)
+ return 500;
+ try {
+ long pos = mPosOverride < 0 ? mService.position() : mPosOverride;
+ long remaining = 1000 - (pos % 1000);
+ if ((pos >= 0) && (mDuration > 0)) {
+ mCurrentTime.setText(MusicUtils.makeTimeString(this, pos / 1000));
+
+ if (mService.isPlaying()) {
+ mCurrentTime.setVisibility(View.VISIBLE);
+ } else {
+ // blink the counter
+ int vis = mCurrentTime.getVisibility();
+ mCurrentTime.setVisibility(vis == View.INVISIBLE ? View.VISIBLE : View.INVISIBLE);
+ remaining = 500;
+ }
+
+ mProgress.setProgress((int) (1000 * pos / mDuration));
+ } else {
+ mCurrentTime.setText("--:--");
+ mProgress.setProgress(1000);
+ }
+ // return the number of milliseconds until the next full second, so
+ // the counter can be updated at just the right time
+ return remaining;
+ } catch (RemoteException ex) {
+ }
+ return 500;
+ }
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case ALBUM_ART_DECODED:
+ mAlbum.setArtwork((Bitmap)msg.obj);
+ mAlbum.invalidate();
+ break;
+
+ case REFRESH:
+ long next = refreshNow();
+ queueNextRefresh(next);
+ break;
+
+ case QUIT:
+ // This can be moved back to onCreate once the bug that prevents
+ // Dialogs from being started from onCreate/onResume is fixed.
+ new AlertDialog.Builder(MediaPlaybackActivity.this)
+ .setTitle(R.string.service_start_error_title)
+ .setMessage(R.string.service_start_error_msg)
+ .setPositiveButton(R.string.service_start_error_button,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ finish();
+ }
+ })
+ .setCancelable(false)
+ .show();
+ break;
+
+ default:
+ break;
+ }
+ }
+ };
+
+ private BroadcastReceiver mStatusListener = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(MediaPlaybackService.META_CHANGED)) {
+ // redraw the artist/title info and
+ // set new max for progress bar
+ updateTrackInfo();
+ setPauseButtonImage();
+ queueNextRefresh(1);
+ } else if (action.equals(MediaPlaybackService.PLAYBACK_COMPLETE)) {
+ if (mOneShot) {
+ finish();
+ } else {
+ setPauseButtonImage();
+ }
+ } else if (action.equals(MediaPlaybackService.PLAYSTATE_CHANGED)) {
+ setPauseButtonImage();
+ }
+ }
+ };
+
+ private void updateTrackInfo() {
+ if (mService == null) {
+ return;
+ }
+ try {
+ if (mService.getPath() == null) {
+ finish();
+ return;
+ }
+ String artistName = mService.getArtistName();
+ if (MediaFile.UNKNOWN_STRING.equals(artistName)) {
+ artistName = getString(R.string.unknown_artist_name);
+ }
+ mArtistName.setText(artistName);
+ String albumName = mService.getAlbumName();
+ int albumid = mService.getAlbumId();
+ if (MediaFile.UNKNOWN_STRING.equals(albumName)) {
+ albumName = getString(R.string.unknown_album_name);
+ albumid = -1;
+ }
+ mAlbumName.setText(albumName);
+ mTrackName.setText(mService.getTrackName());
+ mAlbumArtHandler.removeMessages(GET_ALBUM_ART);
+ mAlbumArtHandler.obtainMessage(GET_ALBUM_ART, albumid, 0).sendToTarget();
+ mDuration = mService.duration();
+ mTotalTime.setText(MusicUtils.makeTimeString(this, mDuration / 1000));
+ } catch (RemoteException ex) {
+ finish();
+ }
+ }
+
+ public class AlbumArtHandler extends Handler {
+ private int mAlbumId = -1;
+
+ public AlbumArtHandler(Looper looper) {
+ super(looper);
+ }
+ public void handleMessage(Message msg)
+ {
+ int albumid = msg.arg1;
+ if (msg.what == GET_ALBUM_ART && (mAlbumId != albumid || albumid < 0)) {
+ // while decoding the new image, show the default album art
+ Message numsg = mHandler.obtainMessage(ALBUM_ART_DECODED, null);
+ mHandler.removeMessages(ALBUM_ART_DECODED);
+ mHandler.sendMessageDelayed(numsg, 300);
+ Bitmap bm = MusicUtils.getArtwork(MediaPlaybackActivity.this, albumid);
+ if (bm == null) {
+ bm = MusicUtils.getArtwork(MediaPlaybackActivity.this, -1);
+ albumid = -1;
+ }
+ if (bm != null) {
+ numsg = mHandler.obtainMessage(ALBUM_ART_DECODED, bm);
+ mHandler.removeMessages(ALBUM_ART_DECODED);
+ mHandler.sendMessage(numsg);
+ }
+ mAlbumId = albumid;
+ }
+ }
+ }
+
+ private class Worker implements Runnable {
+ private final Object mLock = new Object();
+ private Looper mLooper;
+
+ /**
+ * Creates a worker thread with the given name. The thread
+ * then runs a {@link android.os.Looper}.
+ * @param name A name for the new thread
+ */
+ Worker(String name) {
+ Thread t = new Thread(null, this, name);
+ t.setPriority(Thread.MIN_PRIORITY);
+ t.start();
+ synchronized (mLock) {
+ while (mLooper == null) {
+ try {
+ mLock.wait();
+ } catch (InterruptedException ex) {
+ }
+ }
+ }
+ }
+
+ public Looper getLooper() {
+ return mLooper;
+ }
+
+ public void run() {
+ synchronized (mLock) {
+ Looper.prepare();
+ mLooper = Looper.myLooper();
+ mLock.notifyAll();
+ }
+ Looper.loop();
+ }
+
+ public void quit() {
+ mLooper.quit();
+ }
+ }
+}
+
--- /dev/null
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.ContentResolver;
+import android.content.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.media.AudioManager;
+import android.media.MediaFile;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.os.PowerManager.WakeLock;
+import android.provider.MediaStore;
+import android.provider.Settings;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneStateIntentReceiver;
+import android.util.Log;
+import android.widget.RemoteViews;
+import android.widget.Toast;
+
+import java.io.IOException;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ * Provides "background" audio playback capabilities, allowing the
+ * user to switch between activities without stopping playback.
+ */
+public class MediaPlaybackService extends Service {
+ /** used to specify whether enqueue() should start playing
+ * the new list of files right away, next or once all the currently
+ * queued files have been played
+ */
+ public static final int NOW = 1;
+ public static final int NEXT = 2;
+ public static final int LAST = 3;
+ public static final int PLAYBACKSERVICE_STATUS = 1;
+
+ public static final int SHUFFLE_NONE = 0;
+ public static final int SHUFFLE_NORMAL = 1;
+ public static final int SHUFFLE_AUTO = 2;
+
+ public static final int REPEAT_NONE = 0;
+ public static final int REPEAT_CURRENT = 1;
+ public static final int REPEAT_ALL = 2;
+
+ public static final String PLAYSTATE_CHANGED = "com.android.music.playstatechanged";
+ public static final String META_CHANGED = "com.android.music.metachanged";
+ public static final String QUEUE_CHANGED = "com.android.music.queuechanged";
+ public static final String PLAYBACK_COMPLETE = "com.android.music.playbackcomplete";
+ public static final String ASYNC_OPEN_COMPLETE = "com.android.music.asyncopencomplete";
+
+ public static final String SERVICECMD = "com.android.music.musicservicecommand";
+ public static final String CMDNAME = "command";
+ public static final String CMDTOGGLEPAUSE = "togglepause";
+ public static final String CMDPAUSE = "pause";
+ public static final String CMDNEXT = "next";
+
+ private static final int PHONE_CHANGED = 1;
+ private static final int TRACK_ENDED = 1;
+ private static final int RELEASE_WAKELOCK = 2;
+ private static final int SERVER_DIED = 3;
+ private static final int MAX_HISTORY_SIZE = 10;
+
+ private MultiPlayer mPlayer;
+ private String mFileToPlay;
+ private PhoneStateIntentReceiver mPsir;
+ private int mShuffleMode = SHUFFLE_NONE;
+ private int mRepeatMode = REPEAT_NONE;
+ private int mMediaMountedCount = 0;
+ private int [] mAutoShuffleList = null;
+ private boolean mOneShot;
+ private int [] mPlayList = null;
+ private int mPlayListLen = 0;
+ private Vector<Integer> mHistory = new Vector<Integer>(MAX_HISTORY_SIZE);
+ private Cursor mCursor;
+ private int mPlayPos = -1;
+ private static final String LOGTAG = "MediaPlaybackService";
+ private final Shuffler mRand = new Shuffler();
+ private int mOpenFailedCounter = 0;
+ String[] mCursorCols = new String[] {
+ "audio._id AS _id",
+ 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
+ };
+ private BroadcastReceiver mUnmountReceiver = null;
+ private WakeLock mWakeLock;
+ private int mServiceStartId = -1;
+ private boolean mServiceInUse = false;
+ private boolean mResumeAfterCall = false;
+ private boolean mWasPlaying = 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;
+
+ // interval after which we stop the service when idle
+ private static final int IDLE_DELAY = 60000;
+
+ private Handler mPhoneHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case PHONE_CHANGED:
+ Phone.State state = mPsir.getPhoneState();
+ if (state == Phone.State.RINGING) {
+ AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ int ringvolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
+ if (ringvolume > 0) {
+ mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);
+ pause();
+ }
+ } else if (state == Phone.State.OFFHOOK) {
+ // pause the music while a conversation is in progress
+ mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);
+ pause();
+ } else if (state == Phone.State.IDLE) {
+ // start playing again
+ if (mResumeAfterCall) {
+ // resume playback only if music was playing
+ // when the call was answered
+ play();
+ mResumeAfterCall = false;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ private Handler mMediaplayerHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case SERVER_DIED:
+ if (mWasPlaying) {
+ next(true);
+ } else {
+ // the server died when we were idle, so just
+ // reopen the same song (it will start again
+ // from the beginning though when the user
+ // restarts)
+ openCurrent();
+ }
+ break;
+ case TRACK_ENDED:
+ if (mRepeatMode == REPEAT_CURRENT) {
+ seek(0);
+ play();
+ } else if (!mOneShot) {
+ next(false);
+ } else {
+ notifyChange(PLAYBACK_COMPLETE);
+ }
+ break;
+ case RELEASE_WAKELOCK:
+ mWakeLock.release();
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String cmd = intent.getStringExtra("command");
+ if (CMDNEXT.equals(cmd)) {
+ next(true);
+ } else if (CMDTOGGLEPAUSE.equals(cmd)) {
+ if (isPlaying()) {
+ pause();
+ } else {
+ play();
+ }
+ } else if (CMDPAUSE.equals(cmd)) {
+ pause();
+ }
+ }
+ };
+
+ public MediaPlaybackService() {
+ mPsir = new PhoneStateIntentReceiver(this, mPhoneHandler);
+ mPsir.notifyPhoneCallState(PHONE_CHANGED);
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ mPreferences = getSharedPreferences("Music", MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
+ mCardId = FileUtils.getFatVolumeId(Environment.getExternalStorageDirectory().getPath());
+
+ registerExternalStorageListener();
+
+ // Needs to be done in this thread, since otherwise ApplicationContext.getPowerManager() crashes.
+ mPlayer = new MultiPlayer();
+ mPlayer.setHandler(mMediaplayerHandler);
+
+ // Clear leftover notification in case this service previously got killed while playing
+ NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.cancel(PLAYBACKSERVICE_STATUS);
+
+ reloadQueue();
+
+ registerReceiver(mIntentReceiver, new IntentFilter(SERVICECMD));
+ mPsir.registerIntent();
+ PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
+ mWakeLock.setReferenceCounted(false);
+ }
+
+ @Override
+ public void onDestroy() {
+ unregisterReceiver(mIntentReceiver);
+ if (mUnmountReceiver != null) {
+ unregisterReceiver(mUnmountReceiver);
+ mUnmountReceiver = null;
+ }
+ mPsir.unregisterIntent();
+ mWakeLock.release();
+ super.onDestroy();
+ }
+
+ private final char hexdigits [] = new char [] {
+ '0', '1', '2', '3',
+ '4', '5', '6', '7',
+ '8', '9', 'a', 'b',
+ 'c', 'd', 'e', 'f'
+ };
+
+ private void saveQueue(boolean full) {
+ if (mOneShot) {
+ return;
+ }
+ Editor ed = mPreferences.edit();
+ //long start = System.currentTimeMillis();
+ if (full) {
+ StringBuilder q = new StringBuilder();
+
+ // The current playlist is saved as a list of "reverse hexadecimal"
+ // numbers, which we can generate faster than normal decimal or
+ // hexadecimal numbers, which in turn allows us to save the playlist
+ // more often without worrying too much about performance.
+ // (saving the full state takes about 40 ms under no-load conditions
+ // on the phone)
+ int len = mPlayListLen;
+ for (int i = 0; i < len; i++) {
+ int n = mPlayList[i];
+ if (n == 0) {
+ q.append("0;");
+ } else {
+ while (n != 0) {
+ int digit = n & 0xf;
+ n >>= 4;
+ q.append(hexdigits[digit]);
+ }
+ q.append(";");
+ }
+ }
+ //Log.i("@@@@ service", "created queue string in " + (System.currentTimeMillis() - start) + " ms");
+ ed.putString("queue", q.toString());
+ ed.putInt("cardid", mCardId);
+ }
+ ed.putInt("curpos", mPlayPos);
+ if (mPlayer.isInitialized()) {
+ ed.putLong("seekpos", mPlayer.position());
+ }
+ ed.putInt("repeatmode", mRepeatMode);
+ ed.putInt("shufflemode", mShuffleMode);
+ ed.commit();
+
+ //Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis() - start) + " ms");
+ }
+
+ private void reloadQueue() {
+ String q = null;
+
+ boolean newstyle = false;
+ int id = mCardId;
+ if (mPreferences.contains("cardid")) {
+ newstyle = true;
+ id = mPreferences.getInt("cardid", ~mCardId);
+ }
+ if (id == mCardId) {
+ // Only restore the saved playlist if the card is still
+ // the same one as when the playlist was saved
+ q = mPreferences.getString("queue", "");
+ }
+ if (q != null && q.length() > 1) {
+ //Log.i("@@@@ service", "loaded queue: " + q);
+ String [] entries = q.split(";");
+ int len = entries.length;
+ ensurePlayListCapacity(len);
+ for (int i = 0; i < len; i++) {
+ if (newstyle) {
+ String revhex = entries[i];
+ int n = 0;
+ for (int j = revhex.length() - 1; j >= 0 ; j--) {
+ n <<= 4;
+ char c = revhex.charAt(j);
+ if (c >= '0' && c <= '9') {
+ n += (c - '0');
+ } else if (c >= 'a' && c <= 'f') {
+ n += (10 + c - 'a');
+ } else {
+ // bogus playlist data
+ len = 0;
+ break;
+ }
+ }
+ mPlayList[i] = n;
+ } else {
+ mPlayList[i] = Integer.parseInt(entries[i]);
+ }
+ }
+ mPlayListLen = len;
+
+ int pos = mPreferences.getInt("curpos", 0);
+ if (pos < 0 || pos >= len) {
+ // The saved playlist is bogus, discard it
+ mPlayListLen = 0;
+ return;
+ }
+ mPlayPos = pos;
+
+ // When reloadQueue is called in response to a card-insertion,
+ // we might not be able to query the media provider right away.
+ // To deal with this, try querying for the current file, and if
+ // that fails, wait a while and try again. If that too fails,
+ // assume there is a problem and don't restore the state.
+ Cursor c = MusicUtils.query(this,
+ MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+ new String [] {"_id"}, "_id=" + mPlayList[mPlayPos] , null, null);
+ if (c == null || c.getCount() == 0) {
+ // wait a bit and try again
+ SystemClock.sleep(3000);
+ c = getContentResolver().query(
+ MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+ mCursorCols, "_id=" + mPlayList[mPlayPos] , null, null);
+ }
+ if (c != null) {
+ c.close();
+ }
+
+ // Make sure we don't auto-skip to the next song, since that
+ // also starts playback. What could happen in that case is:
+ // - music is paused
+ // - go to UMS and delete some files, including the currently playing one
+ // - come back from UMS
+ // (time passes)
+ // - music app is killed for some reason (out of memory)
+ // - music service is restarted, service restores state, doesn't find
+ // the "current" file, goes to the next and: playback starts on its
+ // own, potentially at some random inconvenient time.
+ mOpenFailedCounter = 20;
+ openCurrent();
+ if (!mPlayer.isInitialized()) {
+ // couldn't restore the saved state
+ mPlayListLen = 0;
+ return;
+ }
+
+ long seekpos = mPreferences.getLong("seekpos", 0);
+ seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
+
+ int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
+ if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
+ repmode = REPEAT_NONE;
+ }
+ mRepeatMode = repmode;
+
+ int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
+ if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
+ shufmode = SHUFFLE_NONE;
+ }
+ if (shufmode == SHUFFLE_AUTO) {
+ if (! makeAutoShuffleList()) {
+ shufmode = SHUFFLE_NONE;
+ }
+ }
+ mShuffleMode = shufmode;
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ mDelayedStopHandler.removeCallbacksAndMessages(null);
+ mServiceInUse = true;
+ return mBinder;
+ }
+
+ @Override
+ public void onRebind(Intent intent) {
+ mDelayedStopHandler.removeCallbacksAndMessages(null);
+ mServiceInUse = true;
+ }
+
+ @Override
+ public void onStart(Intent intent, int startId) {
+ mServiceStartId = startId;
+ mDelayedStopHandler.removeCallbacksAndMessages(null);
+ String cmd = intent.getStringExtra("command");
+ if (CMDNEXT.equals(cmd)) {
+ next(true);
+ } else if (CMDTOGGLEPAUSE.equals(cmd)) {
+ if (isPlaying()) {
+ pause();
+ } else {
+ play();
+ }
+ } else if (CMDPAUSE.equals(cmd)) {
+ pause();
+ }
+ // make sure the service will shut down on its own if it was
+ // just started but not bound to and nothing is playing
+ mDelayedStopHandler.removeCallbacksAndMessages(null);
+ Message msg = mDelayedStopHandler.obtainMessage();
+ mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ mServiceInUse = false;
+
+ // Take a snapshot of the current playlist
+ saveQueue(true);
+
+ if (isPlaying() || mResumeAfterCall) {
+ // something is currently playing, or will be playing once
+ // an in-progress call ends, so don't stop the service now.
+ return true;
+ }
+
+ // If there is a playlist but playback is paused, then wait a while
+ // before stopping the service, so that pause/resume isn't slow.
+ // Also delay stopping the service if we're transitioning between tracks.
+ if (mPlayListLen > 0 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
+ Message msg = mDelayedStopHandler.obtainMessage();
+ mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
+ return true;
+ }
+
+ // No active playlist, OK to stop the service right now
+ stopSelf(mServiceStartId);
+ return true;
+ }
+
+ private Handler mDelayedStopHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ // Check again to make sure nothing is playing right now
+ if (isPlaying() || mResumeAfterCall || mServiceInUse
+ || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
+ return;
+ }
+ // save the queue again, because it might have change
+ // since the user exited the music app (because of
+ // party-shuffle or because the play-position changed)
+ saveQueue(true);
+ stopSelf(mServiceStartId);
+ }
+ };
+
+ /**
+ * Called when we receive a ACTION_MEDIA_EJECT notification.
+ *
+ * @param storagePath path to mount point for the removed media
+ */
+ public void closeExternalStorageFiles(String storagePath) {
+ // stop playback and clean up if the SD card is going to be unmounted.
+ stop(true);
+ notifyChange(QUEUE_CHANGED);
+ notifyChange(META_CHANGED);
+ }
+
+ /**
+ * Registers an intent to listen for ACTION_MEDIA_EJECT notifications.
+ * The intent will call closeExternalStorageFiles() if the external media
+ * is going to be ejected, so applications can clean up any files they have open.
+ */
+ public void registerExternalStorageListener() {
+ if (mUnmountReceiver == null) {
+ mUnmountReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
+ saveQueue(true);
+ mOneShot = true; // This makes us not save the state again later,
+ // which would be wrong because the song ids and
+ // card id might not match.
+ closeExternalStorageFiles(intent.getData().getPath());
+ } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
+ mMediaMountedCount++;
+ mCardId = FileUtils.getFatVolumeId(intent.getData().getPath());
+ reloadQueue();
+ notifyChange(QUEUE_CHANGED);
+ notifyChange(META_CHANGED);
+ }
+ }
+ };
+ IntentFilter iFilter = new IntentFilter();
+ iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
+ iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
+ iFilter.addDataScheme("file");
+ registerReceiver(mUnmountReceiver, iFilter);
+ }
+ }
+
+ /**
+ * Notify the change-receivers that something has changed.
+ * The intent that is sent contains the following data
+ * for the currently playing track:
+ * "id" - Integer: the database row ID
+ * "artist" - String: the name of the artist
+ * "album" - String: the name of the album
+ * "track" - String: the name of the track
+ * The intent has an action that is one of
+ * "com.android.music.metachanged"
+ * "com.android.music.queuechanged",
+ * "com.android.music.playbackcomplete"
+ * "com.android.music.playstatechanged"
+ * respectively indicating that a new track has
+ * started playing, that the playback queue has
+ * changed, that playback has stopped because
+ * the last file in the list has been played,
+ * or that the play-state changed (paused/resumed).
+ */
+ private void notifyChange(String what) {
+
+ Intent i = new Intent(what);
+ i.putExtra("id", Integer.valueOf(getAudioId()));
+ i.putExtra("artist", getArtistName());
+ i.putExtra("album",getAlbumName());
+ i.putExtra("track", getTrackName());
+ sendBroadcast(i);
+
+ if (what.equals(QUEUE_CHANGED)) {
+ saveQueue(true);
+ } else {
+ saveQueue(false);
+ }
+ }
+
+ private void ensurePlayListCapacity(int size) {
+ if (mPlayList == null || size > mPlayList.length) {
+ // reallocate at 2x requested size so we don't
+ // need to grow and copy the array for every
+ // insert
+ int [] newlist = new int[size * 2];
+ int len = mPlayListLen;
+ for (int i = 0; i < len; i++) {
+ newlist[i] = mPlayList[i];
+ }
+ mPlayList = newlist;
+ }
+ // FIXME: shrink the array when the needed size is much smaller
+ // than the allocated size
+ }
+
+ private void addToPlayList(int id) {
+ synchronized(this) {
+ ensurePlayListCapacity(mPlayListLen + 1);
+ mPlayList[mPlayListLen++] = id;
+ }
+ }
+
+ private void addToPlayList(int [] list, int position) {
+ int addlen = list.length;
+ if (position < 0) { // overwrite
+ mPlayListLen = 0;
+ position = 0;
+ }
+ ensurePlayListCapacity(mPlayListLen + addlen);
+ if (position > mPlayListLen) {
+ position = mPlayListLen;
+ }
+
+ // move part of list after insertion point
+ int tailsize = mPlayListLen - position;
+ for (int i = tailsize ; i > 0 ; i--) {
+ mPlayList[position + i] = mPlayList[position + i - addlen];
+ }
+
+ // copy list into playlist
+ for (int i = 0; i < addlen; i++) {
+ mPlayList[position + i] = list[i];
+ }
+ mPlayListLen += addlen;
+ }
+
+ /**
+ * Appends a list of tracks to the current playlist.
+ * If nothing is playing currently, playback will be started at
+ * the first track.
+ * If the action is NOW, playback will switch to the first of
+ * the new tracks immediately.
+ * @param list The list of tracks to append.
+ * @param action NOW, NEXT or LAST
+ */
+ public void enqueue(int [] list, int action) {
+ synchronized(this) {
+ if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
+ addToPlayList(list, mPlayPos + 1);
+ notifyChange(QUEUE_CHANGED);
+ } else {
+ // action == LAST || action == NOW || mPlayPos + 1 == mPlayListLen
+ addToPlayList(list, Integer.MAX_VALUE);
+ notifyChange(QUEUE_CHANGED);
+ if (action == NOW) {
+ mPlayPos = mPlayListLen - list.length;
+ openCurrent();
+ play();
+ notifyChange(META_CHANGED);
+ return;
+ }
+ }
+ if (mPlayPos < 0) {
+ mPlayPos = 0;
+ openCurrent();
+ play();
+ notifyChange(META_CHANGED);
+ }
+ }
+ }
+
+ /**
+ * Replaces the current playlist with a new list,
+ * and prepares for starting playback at the specified
+ * position in the list.
+ * @param list The new list of tracks.
+ */
+ public void open(int [] list, int position) {
+ synchronized (this) {
+ if (mShuffleMode == SHUFFLE_AUTO) {
+ mShuffleMode = SHUFFLE_NORMAL;
+ }
+ addToPlayList(list, -1);
+ mPlayPos = position;
+ mHistory.clear();
+
+ openCurrent();
+ }
+ }
+
+ /**
+ * Moves the item at index1 to index2.
+ * @param index1
+ * @param index2
+ */
+ public void moveQueueItem(int index1, int index2) {
+ synchronized (this) {
+ if (index1 >= mPlayListLen) {
+ index1 = mPlayListLen - 1;
+ }
+ if (index2 >= mPlayListLen) {
+ index2 = mPlayListLen - 1;
+ }
+ if (index1 < index2) {
+ int tmp = mPlayList[index1];
+ for (int i = index1; i < index2; i++) {
+ mPlayList[i] = mPlayList[i+1];
+ }
+ mPlayList[index2] = tmp;
+ if (mPlayPos == index1) {
+ mPlayPos = index2;
+ } else if (mPlayPos >= index1 && mPlayPos <= index2) {
+ mPlayPos--;
+ }
+ } else if (index2 < index1) {
+ int tmp = mPlayList[index1];
+ for (int i = index1; i > index2; i--) {
+ mPlayList[i] = mPlayList[i-1];
+ }
+ mPlayList[index2] = tmp;
+ if (mPlayPos == index1) {
+ mPlayPos = index2;
+ } else if (mPlayPos >= index2 && mPlayPos <= index1) {
+ mPlayPos++;
+ }
+ }
+ notifyChange(QUEUE_CHANGED);
+ }
+ }
+
+ /**
+ * Returns the current play list
+ * @return An array of integers containing the IDs of the tracks in the play list
+ */
+ public int [] getQueue() {
+ synchronized (this) {
+ int len = mPlayListLen;
+ int [] list = new int[len];
+ for (int i = 0; i < len; i++) {
+ list[i] = mPlayList[i];
+ }
+ return list;
+ }
+ }
+
+ private void openCurrent() {
+ synchronized (this) {
+ if (mCursor != null) {
+ mCursor.close();
+ mCursor = null;
+ }
+ if (mPlayListLen == 0) {
+ return;
+ }
+ stop(false);
+
+ String id = String.valueOf(mPlayList[mPlayPos]);
+
+ mCursor = getContentResolver().query(
+ MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+ mCursorCols, "_id=" + id , null, null);
+ if (mCursor != null) {
+ mCursor.moveToFirst();
+ open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id, false);
+ }
+ }
+ }
+
+ public void openAsync(String path) {
+ synchronized (this) {
+ if (path == null) {
+ return;
+ }
+
+ mRepeatMode = REPEAT_NONE;
+ ensurePlayListCapacity(1);
+ mPlayListLen = 1;
+ mPlayPos = -1;
+
+ mFileToPlay = path;
+ mCursor = null;
+ mPlayer.setDataSourceAsync(mFileToPlay);
+ mOneShot = true;
+ }
+ }
+
+ /**
+ * Opens the specified file and readies it for playback.
+ *
+ * @param path The full path of the file to be opened.
+ * @param oneshot when set to true, playback will stop after this file completes, instead
+ * of moving on to the next track in the list
+ */
+ public void open(String path, boolean oneshot) {
+ synchronized (this) {
+ if (path == null) {
+ return;
+ }
+
+ if (oneshot) {
+ mRepeatMode = REPEAT_NONE;
+ ensurePlayListCapacity(1);
+ mPlayListLen = 1;
+ mPlayPos = -1;
+ }
+
+ // if mCursor is null, try to associate path with a database cursor
+ if (mCursor == null) {
+
+ ContentResolver resolver = getContentResolver();
+ Uri uri;
+ String where;
+ String selectionArgs[];
+ if (path.startsWith("content://media/")) {
+ uri = Uri.parse(path);
+ where = null;
+ selectionArgs = null;
+ } else {
+ uri = MediaStore.Audio.Media.getContentUriForPath(path);
+ where = MediaStore.Audio.Media.DATA + "=?";
+ selectionArgs = new String[] { path };
+ }
+
+ try {
+ mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
+ if (mCursor != null) {
+ if (mCursor.getCount() == 0) {
+ mCursor.close();
+ mCursor = null;
+ } else {
+ mCursor.moveToNext();
+ ensurePlayListCapacity(1);
+ mPlayListLen = 1;
+ mPlayList[0] = mCursor.getInt(0);
+ mPlayPos = 0;
+ }
+ }
+ } catch (UnsupportedOperationException ex) {
+ }
+ }
+ mFileToPlay = path;
+ mPlayer.setDataSource(mFileToPlay);
+ mOneShot = oneshot;
+ if (! mPlayer.isInitialized()) {
+ stop(true);
+ if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) {
+ // beware: this ends up being recursive because next() calls open() again.
+ next(false);
+ }
+ if (! mPlayer.isInitialized() && mOpenFailedCounter != 0) {
+ // need to make sure we only shows this once
+ mOpenFailedCounter = 0;
+ Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
+ }
+ } else {
+ mOpenFailedCounter = 0;
+ }
+ }
+ }
+
+ /**
+ * Starts playback of a previously opened file.
+ */
+ public void play() {
+ if (mPlayer.isInitialized()) {
+ mPlayer.start();
+ setForeground(true);
+ mWasPlaying = true;
+
+ NotificationManager nm = (NotificationManager)
+ getSystemService(Context.NOTIFICATION_SERVICE);
+
+ RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
+ views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
+ views.setTextViewText(R.id.trackname, getTrackName());
+ String artist = getArtistName();
+ if (artist == null || artist.equals(MediaFile.UNKNOWN_STRING)) {
+ artist = getString(R.string.unknown_artist_name);
+ }
+ String album = getAlbumName();
+ if (album == null || album.equals(MediaFile.UNKNOWN_STRING)) {
+ album = getString(R.string.unknown_album_name);
+ }
+
+ views.setTextViewText(R.id.artistalbum,
+ getString(R.string.notification_artist_album, artist, album)
+ );
+
+ Intent statusintent = new Intent("com.android.music.PLAYBACK_VIEWER");
+ statusintent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ Notification status = new Notification();
+ status.contentView = views;
+ status.flags |= Notification.FLAG_ONGOING_EVENT;
+ status.icon = R.drawable.stat_notify_musicplayer;
+ status.contentIntent = PendingIntent.getActivity(this, 0,
+ new Intent("com.android.music.PLAYBACK_VIEWER"), 0);
+ nm.notify(PLAYBACKSERVICE_STATUS, status);
+ notifyChange(PLAYSTATE_CHANGED);
+ }
+ }
+
+ private void stop(boolean remove_status_icon) {
+ if (mPlayer.isInitialized()) {
+ mPlayer.stop();
+ }
+ mFileToPlay = null;
+ if (mCursor != null) {
+ mCursor.close();
+ mCursor = null;
+ }
+ if (remove_status_icon) {
+ gotoIdleState();
+ }
+ setForeground(false);
+ mWasPlaying = false;
+ }
+
+ /**
+ * Stops playback.
+ */
+ public void stop() {
+ stop(true);
+ }
+
+ /**
+ * Pauses playback (call play() to resume)
+ */
+ public void pause() {
+ if (isPlaying()) {
+ mPlayer.pause();
+ gotoIdleState();
+ setForeground(false);
+ mWasPlaying = false;
+ notifyChange(PLAYSTATE_CHANGED);
+ }
+ }
+
+ /** Returns whether playback is currently paused
+ *
+ * @return true if playback is paused, false if not
+ */
+ public boolean isPlaying() {
+ if (mPlayer.isInitialized()) {
+ return mPlayer.isPlaying();
+ }
+ return false;
+ }
+
+ /*
+ Desired behavior for prev/next/shuffle:
+
+ - NEXT will move to the next track in the list when not shuffling, and to
+ a track randomly picked from the not-yet-played tracks when shuffling.
+ If all tracks have already been played, pick from the full set, but
+ avoid picking the previously played track if possible.
+ - when shuffling, PREV will go to the previously played track. Hitting PREV
+ again will go to the track played before that, etc. When the start of the
+ history has been reached, PREV is a no-op.
+ When not shuffling, PREV will go to the sequentially previous track (the
+ difference with the shuffle-case is mainly that when not shuffling, the
+ user can back up to tracks that are not in the history).
+
+ Example:
+ When playing an album with 10 tracks from the start, and enabling shuffle
+ while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.
+ the final play order might be 1-2-3-4-5-8-10-6-9-7.
+ When hitting 'prev' 8 times while playing track 7 in this example, the
+ user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next',
+ a random track will be picked again. If at any time user disables shuffling
+ the next/previous track will be picked in sequential order again.
+ */
+
+ public void prev() {
+ synchronized (this) {
+ if (mOneShot) {
+ // we were playing a specific file not part of a playlist, so there is no 'previous'
+ seek(0);
+ play();
+ return;
+ }
+ if (mShuffleMode == SHUFFLE_NORMAL) {
+ // go to previously-played track and remove it from the history
+ int histsize = mHistory.size();
+ if (histsize == 0) {
+ // prev is a no-op
+ return;
+ }
+ Integer pos = mHistory.remove(histsize - 1);
+ mPlayPos = pos.intValue();
+ } else {
+ if (mPlayPos > 0) {
+ mPlayPos--;
+ } else {
+ mPlayPos = mPlayListLen - 1;
+ }
+ }
+ stop(false);
+ openCurrent();
+ play();
+ notifyChange(META_CHANGED);
+ }
+ }
+
+ public void next(boolean force) {
+ synchronized (this) {
+ if (mOneShot) {
+ // we were playing a specific file not part of a playlist, so there is no 'next'
+ seek(0);
+ play();
+ return;
+ }
+
+ // Store the current file in the history, but keep the history at a
+ // reasonable size
+ mHistory.add(Integer.valueOf(mPlayPos));
+ if (mHistory.size() > MAX_HISTORY_SIZE) {
+ mHistory.removeElementAt(0);
+ }
+
+ if (mShuffleMode == SHUFFLE_NORMAL) {
+ // Pick random next track from the not-yet-played ones
+ // TODO: make it work right after adding/removing items in the queue.
+
+ int numTracks = mPlayListLen;
+ int[] tracks = new int[numTracks];
+ for (int i=0;i < numTracks; i++) {
+ tracks[i] = i;
+ }
+
+ int numHistory = mHistory.size();
+ int numUnplayed = numTracks;
+ for (int i=0;i < numHistory; i++) {
+ int idx = mHistory.get(i).intValue();
+ if (idx < numTracks && tracks[idx] >= 0) {
+ numUnplayed--;
+ tracks[idx] = -1;
+ }
+ }
+
+ // 'numUnplayed' now indicates how many tracks have not yet
+ // been played, and 'tracks' contains the indices of those
+ // tracks.
+ if (numUnplayed <=0) {
+ // everything's already been played
+ if (mRepeatMode == REPEAT_ALL || force) {
+ //pick from full set
+ numUnplayed = numTracks;
+ for (int i=0;i < numTracks; i++) {
+ tracks[i] = i;
+ }
+ } else {
+ // all done
+ gotoIdleState();
+ return;
+ }
+ }
+ int skip = mRand.nextInt(numUnplayed);
+ int cnt = -1;
+ while (true) {
+ while (tracks[++cnt] < 0)
+ ;
+ skip--;
+ if (skip < 0) {
+ break;
+ }
+ }
+ mPlayPos = cnt;
+ } else if (mShuffleMode == SHUFFLE_AUTO) {
+ doAutoShuffleUpdate();
+ mPlayPos++;
+ } else {
+ if (mPlayPos >= mPlayListLen - 1) {
+ // we're at the end of the list
+ if (mRepeatMode == REPEAT_NONE && !force) {
+ // all done
+ gotoIdleState();
+ notifyChange(PLAYBACK_COMPLETE);
+ return;
+ } else if (mRepeatMode == REPEAT_ALL || force) {
+ mPlayPos = 0;
+ }
+ } else {
+ mPlayPos++;
+ }
+ }
+ stop(false);
+ openCurrent();
+ play();
+ notifyChange(META_CHANGED);
+ }
+ }
+
+ private void gotoIdleState() {
+ NotificationManager nm =
+ (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.cancel(PLAYBACKSERVICE_STATUS);
+ mDelayedStopHandler.removeCallbacksAndMessages(null);
+ Message msg = mDelayedStopHandler.obtainMessage();
+ mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
+ }
+
+ // Make sure there are at least 5 items after the currently playing item
+ // and no more than 10 items before.
+ private void doAutoShuffleUpdate() {
+ // remove old entries
+ if (mPlayPos > 10) {
+ removeTracks(0, mPlayPos - 9);
+ }
+ // add new entries if needed
+ int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
+ if (to_add > 0) {
+ for (int i = 0; i < to_add; i++) {
+ // pick something at random from the list
+ int idx = mRand.nextInt(mAutoShuffleList.length);
+ Integer which = mAutoShuffleList[idx];
+ addToPlayList(which);
+ }
+ notifyChange(QUEUE_CHANGED);
+ }
+ }
+
+ // A simple variation of Random that makes sure that the
+ // value it returns is not equal to the value it returned
+ // previously, unless the interval is 1.
+ private class Shuffler {
+ private int mPrevious;
+ private Random mRandom = new Random();
+ public int nextInt(int interval) {
+ int ret;
+ do {
+ ret = mRandom.nextInt(interval);
+ } while (ret == mPrevious && interval > 1);
+ mPrevious = ret;
+ return ret;
+ }
+ };
+
+ private boolean makeAutoShuffleList() {
+ ContentResolver res = getContentResolver();
+ Cursor c = null;
+ try {
+ c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+ new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
+ null, null);
+ if (c == null || c.getCount() == 0) {
+ return false;
+ }
+ int len = c.getCount();
+ int[] list = new int[len];
+ for (int i = 0; i < len; i++) {
+ c.moveToNext();
+ list[i] = c.getInt(0);
+ }
+ mAutoShuffleList = list;
+ return true;
+ } catch (RuntimeException ex) {
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Removes the range of tracks specified from the play list. If a file within the range is
+ * the file currently being played, playback will move to the next file after the
+ * range.
+ * @param first The first file to be removed
+ * @param last The last file to be removed
+ * @return the number of tracks deleted
+ */
+ public int removeTracks(int first, int last) {
+ synchronized (this) {
+ if (last < first) return 0;
+ if (first < 0) first = 0;
+ if (last >= mPlayListLen) last = mPlayListLen - 1;
+
+ boolean gotonext = false;
+ if (first <= mPlayPos && mPlayPos <= last) {
+ mPlayPos = first;
+ gotonext = true;
+ } else if (mPlayPos > last) {
+ mPlayPos -= (last - first + 1);
+ }
+ int num = mPlayListLen - last - 1;
+ for (int i = 0; i < num; i++) {
+ mPlayList[first + i] = mPlayList[last + 1 + i];
+ }
+ mPlayListLen -= last - first + 1;
+
+ if (gotonext) {
+ if (mPlayListLen == 0) {
+ stop(true);
+ mPlayPos = -1;
+ } else {
+ if (mPlayPos >= mPlayListLen) {
+ mPlayPos = 0;
+ }
+ stop(false);
+ openCurrent();
+ play();
+ }
+ }
+ notifyChange(QUEUE_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(int id) {
+ int numremoved = 0;
+ synchronized (this) {
+ for (int i = 0; i < mPlayListLen; i++) {
+ if (mPlayList[i] == id) {
+ numremoved += removeTracks(i, i);
+ i--;
+ }
+ }
+ }
+ return numremoved;
+ }
+
+ public void setShuffleMode(int shufflemode) {
+ synchronized(this) {
+ if (mShuffleMode == shufflemode) {
+ return;
+ }
+ mShuffleMode = shufflemode;
+ if (mShuffleMode == SHUFFLE_AUTO) {
+ if (makeAutoShuffleList()) {
+ mPlayListLen = 0;
+ doAutoShuffleUpdate();
+ mPlayPos = 0;
+ openCurrent();
+ play();
+ notifyChange(META_CHANGED);
+ } else {
+ // failed to build a list of files to shuffle
+ mShuffleMode = SHUFFLE_NONE;
+ }
+ }
+ }
+ }
+ public int getShuffleMode() {
+ return mShuffleMode;
+ }
+
+ public void setRepeatMode(int repeatmode) {
+ synchronized(this) {
+ mRepeatMode = repeatmode;
+ }
+ }
+ public int getRepeatMode() {
+ return mRepeatMode;
+ }
+
+ public int getMediaMountedCount() {
+ return mMediaMountedCount;
+ }
+
+ /**
+ * Returns the path of the currently playing file, or null if
+ * no file is currently playing.
+ */
+ public String getPath() {
+ return mFileToPlay;
+ }
+
+ /**
+ * Returns the rowid of the currently playing file, or -1 if
+ * no file is currently playing.
+ */
+ public int getAudioId() {
+ synchronized (this) {
+ if (mPlayPos >= 0 && mPlayer.isInitialized()) {
+ return mPlayList[mPlayPos];
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the position in the queue
+ * @return the position in the queue
+ */
+ public int getQueuePosition() {
+ synchronized(this) {
+ return mPlayPos;
+ }
+ }
+
+ /**
+ * Starts playing the track at the given position in the queue.
+ * @param pos The position in the queue of the track that will be played.
+ */
+ public void setQueuePosition(int pos) {
+ synchronized(this) {
+ stop(false);
+ mPlayPos = pos;
+ openCurrent();
+ play();
+ notifyChange(META_CHANGED);
+ }
+ }
+
+ public String getArtistName() {
+ if (mCursor == null) {
+ return null;
+ }
+ return mCursor.getString(mCursor.getColumnIndex(MediaStore.Audio.Media.ARTIST));
+ }
+
+ public int getArtistId() {
+ if (mCursor == null) {
+ return -1;
+ }
+ return mCursor.getInt(mCursor.getColumnIndex(MediaStore.Audio.Media.ARTIST_ID));
+ }
+
+ public String getAlbumName() {
+ if (mCursor == null) {
+ return null;
+ }
+ return mCursor.getString(mCursor.getColumnIndex(MediaStore.Audio.Media.ALBUM));
+ }
+
+ public int getAlbumId() {
+ if (mCursor == null) {
+ return -1;
+ }
+ return mCursor.getInt(mCursor.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID));
+ }
+
+ public String getTrackName() {
+ if (mCursor == null) {
+ return null;
+ }
+ return mCursor.getString(mCursor.getColumnIndex(MediaStore.Audio.Media.TITLE));
+ }
+
+
+
+ /**
+ * 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();
+ }
+ // TODO: when the MIDI engine supports it, return MIDI duration.
+ return -1;
+ }
+
+ /**
+ * Returns the current playback position in milliseconds
+ */
+ public long position() {
+ if (mPlayer.isInitialized()) {
+ return mPlayer.position();
+ }
+ return -1;
+ }
+
+ /**
+ * Seeks to the position specified.
+ *
+ * @param pos The position to seek to, in milliseconds
+ */
+ public long seek(long pos) {
+ if (mPlayer.isInitialized()) {
+ if (pos < 0) pos = 0;
+ if (pos > mPlayer.duration()) pos = mPlayer.duration();
+ return mPlayer.seek(pos);
+ }
+ return -1;
+ }
+
+ /**
+ * Provides a unified interface for dealing with midi files and
+ * other media files.
+ */
+ private class MultiPlayer {
+ private MediaPlayer mMediaPlayer = new MediaPlayer();
+ private Handler mHandler;
+ private boolean mIsInitialized = false;
+
+ public MultiPlayer() {
+ mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
+ }
+
+ public void setDataSourceAsync(String path) {
+ try {
+ mMediaPlayer.reset();
+ mMediaPlayer.setDataSource(path);
+ mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+ mMediaPlayer.setOnPreparedListener(preparedlistener);
+ mMediaPlayer.prepareAsync();
+ } catch (IOException ex) {
+ // TODO: notify the user why the file couldn't be opened
+ mIsInitialized = false;
+ return;
+ } catch (IllegalArgumentException ex) {
+ // TODO: notify the user why the file couldn't be opened
+ mIsInitialized = false;
+ return;
+ }
+ mMediaPlayer.setOnCompletionListener(listener);
+ mMediaPlayer.setOnErrorListener(errorListener);
+
+ mIsInitialized = true;
+ }
+
+ public void setDataSource(String path) {
+ try {
+ mMediaPlayer.reset();
+ mMediaPlayer.setOnPreparedListener(null);
+ if (path.startsWith("content://")) {
+ mMediaPlayer.setDataSource(MediaPlaybackService.this, Uri.parse(path));
+ } else {
+ mMediaPlayer.setDataSource(path);
+ }
+ mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+ mMediaPlayer.prepare();
+ } catch (IOException ex) {
+ // TODO: notify the user why the file couldn't be opened
+ mIsInitialized = false;
+ return;
+ } catch (IllegalArgumentException ex) {
+ // TODO: notify the user why the file couldn't be opened
+ mIsInitialized = false;
+ return;
+ }
+ mMediaPlayer.setOnCompletionListener(listener);
+ mMediaPlayer.setOnErrorListener(errorListener);
+
+ mIsInitialized = true;
+ }
+
+ public boolean isInitialized() {
+ return mIsInitialized;
+ }
+
+ public void start() {
+ mMediaPlayer.start();
+ }
+
+ public void stop() {
+ mMediaPlayer.reset();
+ mIsInitialized = false;
+ }
+
+ public void pause() {
+ mMediaPlayer.pause();
+ }
+
+ public boolean isPlaying() {
+ return mMediaPlayer.isPlaying();
+ }
+
+ public void setHandler(Handler handler) {
+ mHandler = handler;
+ }
+
+ MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
+ public void onCompletion(MediaPlayer mp) {
+ // Acquire a temporary wakelock, since when we return from
+ // this callback the MediaPlayer will release its wakelock
+ // and allow the device to go to sleep.
+ // This temporary wakelock is released when the RELEASE_WAKELOCK
+ // message is processed, but just in case, put a timeout on it.
+ mWakeLock.acquire(30000);
+ mHandler.sendEmptyMessage(TRACK_ENDED);
+ mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
+ }
+ };
+
+ MediaPlayer.OnPreparedListener preparedlistener = new MediaPlayer.OnPreparedListener() {
+ public void onPrepared(MediaPlayer mp) {
+ notifyChange(ASYNC_OPEN_COMPLETE);
+ }
+ };
+
+ MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
+ public boolean onError(MediaPlayer mp, int what, int extra) {
+ switch (what) {
+ case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
+ mIsInitialized = false;
+ mMediaPlayer.release();
+ // Creating a new MediaPlayer and settings its wakemode does not
+ // require the media service, so it's OK to do this now, while the
+ // service is still being restarted
+ mMediaPlayer = new MediaPlayer();
+ mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
+ return true;
+ default:
+ break;
+ }
+ return false;
+ }
+ };
+
+ public long duration() {
+ return mMediaPlayer.getDuration();
+ }
+
+ public long position() {
+ return mMediaPlayer.getCurrentPosition();
+ }
+
+ public long seek(long whereto) {
+ mMediaPlayer.seekTo((int) whereto);
+ return whereto;
+ }
+ }
+
+ private final IMediaPlaybackService.Stub mBinder = new IMediaPlaybackService.Stub()
+ {
+ public void openfileAsync(String path)
+ {
+ MediaPlaybackService.this.openAsync(path);
+ }
+ public void openfile(String path)
+ {
+ MediaPlaybackService.this.open(path, true);
+ }
+ public void open(int [] list, int position) {
+ MediaPlaybackService.this.open(list, position);
+ }
+ public int getQueuePosition() {
+ return MediaPlaybackService.this.getQueuePosition();
+ }
+ public void setQueuePosition(int index) {
+ MediaPlaybackService.this.setQueuePosition(index);
+ }
+ public boolean isPlaying() {
+ return MediaPlaybackService.this.isPlaying();
+ }
+ public void stop() {
+ MediaPlaybackService.this.stop();
+ }
+ public void pause() {
+ MediaPlaybackService.this.pause();
+ }
+ public void play() {
+ MediaPlaybackService.this.play();
+ }
+ public void prev() {
+ MediaPlaybackService.this.prev();
+ }
+ public void next() {
+ MediaPlaybackService.this.next(true);
+ }
+ public String getTrackName() {
+ return MediaPlaybackService.this.getTrackName();
+ }
+ public String getAlbumName() {
+ return MediaPlaybackService.this.getAlbumName();
+ }
+ public int getAlbumId() {
+ return MediaPlaybackService.this.getAlbumId();
+ }
+ public String getArtistName() {
+ return MediaPlaybackService.this.getArtistName();
+ }
+ public int getArtistId() {
+ return MediaPlaybackService.this.getArtistId();
+ }
+ public void enqueue(int [] list , int action) {
+ MediaPlaybackService.this.enqueue(list, action);
+ }
+ public int [] getQueue() {
+ return MediaPlaybackService.this.getQueue();
+ }
+ public void moveQueueItem(int from, int to) {
+ MediaPlaybackService.this.moveQueueItem(from, to);
+ }
+ public String getPath() {
+ return MediaPlaybackService.this.getPath();
+ }
+ public int getAudioId() {
+ return MediaPlaybackService.this.getAudioId();
+ }
+ public long position() {
+ return MediaPlaybackService.this.position();
+ }
+ public long duration() {
+ return MediaPlaybackService.this.duration();
+ }
+ public long seek(long pos) {
+ return MediaPlaybackService.this.seek(pos);
+ }
+ public void setShuffleMode(int shufflemode) {
+ MediaPlaybackService.this.setShuffleMode(shufflemode);
+ }
+ public int getShuffleMode() {
+ return MediaPlaybackService.this.getShuffleMode();
+ }
+ public int removeTracks(int first, int last) {
+ return MediaPlaybackService.this.removeTracks(first, last);
+ }
+ public int removeTrack(int id) {
+ return MediaPlaybackService.this.removeTrack(id);
+ }
+ public void setRepeatMode(int repeatmode) {
+ MediaPlaybackService.this.setRepeatMode(repeatmode);
+ }
+ public int getRepeatMode() {
+ return MediaPlaybackService.this.getRepeatMode();
+ }
+ public int getMediaMountedCount() {
+ return MediaPlaybackService.this.getMediaMountedCount();
+ }
+ };
+}
+
--- /dev/null
+/*
+ * 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.Intent;
+import android.graphics.PixelFormat;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Config;
+import android.util.Log;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.MediaController;
+import android.widget.VideoView;
+
+public class MovieView extends Activity implements MediaPlayer.OnErrorListener
+{
+ private static final String TAG = "MovieView";
+
+ private VideoView mVideoView;
+ private View mProgressView;
+ public MovieView()
+ {
+ }
+
+ @Override
+ public void onCreate(Bundle icicle)
+ {
+ super.onCreate(icicle);
+
+ setContentView(R.layout.movie_view);
+
+ mVideoView = (VideoView) findViewById(R.id.surface_view);
+ mProgressView = findViewById(R.id.progress_indicator);
+ Uri uri = getIntent().getData();
+
+ // For streams that we expect to be slow to start up, show a
+ // progress spinner until playback starts.
+ String scheme = uri.getScheme();
+ if ("http".equalsIgnoreCase(scheme) ||
+ "rtsp".equalsIgnoreCase(scheme)) {
+ mHandler.postDelayed(mPlayingChecker, 250);
+ } else {
+ mProgressView.setVisibility(View.GONE);
+ }
+
+ mVideoView.setOnErrorListener(this);
+ mVideoView.setVideoURI(uri);
+ mVideoView.setMediaController(new MediaController(this));
+ mVideoView.requestFocus(); // make the video view handle keys for seeking and pausing
+
+ Intent i = new Intent(MediaPlaybackService.SERVICECMD);
+ i.putExtra(MediaPlaybackService.CMDNAME, MediaPlaybackService.CMDPAUSE);
+ sendBroadcast(i);
+
+ mVideoView.start();
+ }
+
+ @Override
+ public void onPause() {
+ mHandler.removeCallbacksAndMessages(null);
+ super.onPause();
+ }
+
+ Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ }
+ };
+
+ Runnable mPlayingChecker = new Runnable() {
+ public void run() {
+ if (mVideoView.isPlaying()) {
+ mProgressView.setVisibility(View.GONE);
+ } else {
+ mHandler.postDelayed(mPlayingChecker, 250);
+ }
+ }
+ };
+
+ public boolean onError(MediaPlayer player, int arg1, int arg2) {
+ mHandler.removeCallbacksAndMessages(null);
+ mProgressView.setVisibility(View.GONE);
+ return false;
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.app.Activity;
+import android.app.SearchManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.media.MediaFile;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.provider.MediaStore;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.Window;
+import android.widget.TextView;
+
+public class MusicBrowserActivity extends Activity
+ implements MusicUtils.Defs, View.OnClickListener {
+ private View mNowPlayingView;
+ private TextView mTitle;
+ private TextView mArtist;
+ private boolean mAutoShuffle = false;
+ private static final int SEARCH_MUSIC = CHILD_MENU_BASE;
+
+ public MusicBrowserActivity() {
+ }
+
+ /**
+ * Called when the activity is first created.
+ */
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+ String shuf = getIntent().getStringExtra("autoshuffle");
+ if ("true".equals(shuf)) {
+ mAutoShuffle = true;
+ }
+ MusicUtils.bindToService(this, new ServiceConnection() {
+ public void onServiceConnected(ComponentName classname, IBinder obj) {
+ updateMenu();
+ }
+
+ public void onServiceDisconnected(ComponentName classname) {
+ updateMenu();
+ }
+
+ });
+ setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
+ init();
+ }
+
+ @Override
+ public void onDestroy() {
+ MusicUtils.unbindFromService(this);
+ super.onDestroy();
+ }
+
+ public void init() {
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(R.layout.music_library);
+ mNowPlayingView = findViewById(R.id.nowplaying);
+ mTitle = (TextView) mNowPlayingView.findViewById(R.id.title);
+ mArtist = (TextView) mNowPlayingView.findViewById(R.id.artist);
+
+ findViewById(R.id.browse_button).setOnClickListener(this);
+ findViewById(R.id.albums_button).setOnClickListener(this);
+ findViewById(R.id.tracks_button).setOnClickListener(this);
+ findViewById(R.id.playlists_button).setOnClickListener(this);
+ }
+
+ private void updateMenu() {
+ try {
+ if (MusicUtils.sService != null && MusicUtils.sService.getAudioId() != -1) {
+ makeNowPlayingView();
+ mNowPlayingView.setVisibility(View.VISIBLE);
+ return;
+ }
+ } catch (RemoteException ex) {
+ }
+ mNowPlayingView.setVisibility(View.INVISIBLE);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ IntentFilter f = new IntentFilter();
+ f.addAction(MediaPlaybackService.META_CHANGED);
+ registerReceiver(mStatusListener, new IntentFilter(f));
+ updateMenu();
+ if (mAutoShuffle) {
+ mAutoShuffle = false;
+ doAutoShuffle();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ unregisterReceiver(mStatusListener);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ menu.add(0, PARTY_SHUFFLE, 0, R.string.party_shuffle); // icon will be set in onPrepareOptionsMenu()
+ menu.add(0, SEARCH_MUSIC, 0, R.string.search_title).setIcon(R.drawable.ic_menu_search);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ MenuItem item = menu.findItem(PARTY_SHUFFLE);
+ if (item != null) {
+ int shuffle = MusicUtils.getCurrentShuffleMode();
+ if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
+ item.setIcon(R.drawable.ic_menu_party_shuffle);
+ item.setTitle(R.string.party_shuffle_off);
+ } else {
+ item.setIcon(R.drawable.ic_menu_party_shuffle);
+ item.setTitle(R.string.party_shuffle);
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ Intent intent;
+ try {
+ switch (item.getItemId()) {
+ case PARTY_SHUFFLE:
+ int shuffle = MusicUtils.sService.getShuffleMode();
+ if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
+ MusicUtils.sService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
+ } else {
+ MusicUtils.sService.setShuffleMode(MediaPlaybackService.SHUFFLE_AUTO);
+ }
+ break;
+
+ case SEARCH_MUSIC: {
+ intent = new Intent(Intent.ACTION_PICK);
+ intent.setClass(this, QueryBrowserActivity.class);
+ intent.putExtra(SearchManager.QUERY, "");
+ startActivity(intent);
+ return true;
+ }
+ }
+ } catch (RemoteException ex) {
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+
+ // TODO: Activities are requested to call onSearchRequested, and to override
+ // that function in order to insert custom fields (e.g. the search bundle). startSearch
+ // was not intended to be overridden.
+ @Override
+ public void startSearch(String initialQuery, boolean selectQuery, Bundle appSearchData,
+ boolean globalSearch) {
+ Intent intent = new Intent(Intent.ACTION_PICK);
+ intent.setClass(this, QueryBrowserActivity.class);
+ intent.putExtra(SearchManager.QUERY, initialQuery);
+ startActivity(intent);
+ }
+
+ public void onClick(View v) {
+ Intent intent;
+ switch (v.getId()) {
+ case R.id.browse_button:
+ intent = new Intent(Intent.ACTION_PICK);
+ intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/artistalbum");
+ startActivity(intent);
+ break;
+ case R.id.albums_button:
+ intent = new Intent(Intent.ACTION_PICK);
+ intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album");
+ startActivity(intent);
+ break;
+ case R.id.tracks_button:
+ intent = new Intent(Intent.ACTION_PICK);
+ intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
+ startActivity(intent);
+ break;
+ case R.id.playlists_button:
+ intent = new Intent(Intent.ACTION_PICK);
+ intent.setDataAndType(Uri.EMPTY, MediaStore.Audio.Playlists.CONTENT_TYPE);
+ startActivity(intent);
+ break;
+ case R.id.nowplaying:
+ intent = new Intent("com.android.music.PLAYBACK_VIEWER");
+ startActivity(intent);
+ break;
+ }
+ }
+
+ private void doAutoShuffle() {
+ bindService((new Intent()).setClass(this, MediaPlaybackService.class), autoshuffle, 0);
+ }
+
+ private ServiceConnection autoshuffle = new ServiceConnection() {
+ public void onServiceConnected(ComponentName classname, IBinder obj) {
+ // we need to be able to bind again, so unbind
+ unbindService(this);
+ IMediaPlaybackService serv = IMediaPlaybackService.Stub.asInterface(obj);
+ if (serv != null) {
+ try {
+ serv.setShuffleMode(MediaPlaybackService.SHUFFLE_AUTO);
+ updateMenu();
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName classname) {
+ }
+ };
+
+ private void makeNowPlayingView() {
+ try {
+ mTitle.setText(MusicUtils.sService.getTrackName());
+ String artistName = MusicUtils.sService.getArtistName();
+ if (MediaFile.UNKNOWN_STRING.equals(artistName)) {
+ artistName = getString(R.string.unknown_artist_name);
+ }
+ mArtist.setText(artistName);
+ mNowPlayingView.setOnFocusChangeListener(mFocuser);
+ mNowPlayingView.setOnClickListener(this);
+ } catch (RemoteException ex) {
+
+ }
+ }
+
+ View.OnFocusChangeListener mFocuser = new View.OnFocusChangeListener() {
+ Drawable mBack;
+
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (hasFocus) {
+ if (mBack == null) {
+ mBack = mNowPlayingView.getBackground();
+ }
+ Drawable dr = getResources().getDrawable(android.R.drawable.menuitem_background);
+ dr.setState(new int[] { android.R.attr.state_focused});
+ mNowPlayingView.setBackgroundDrawable(dr);
+ } else {
+ mNowPlayingView.setBackgroundDrawable(mBack);
+ }
+ }
+ };
+
+ private BroadcastReceiver mStatusListener = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // this receiver is only used for META_CHANGED events
+ updateMenu();
+ }
+ };
+}
+
--- /dev/null
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.Locale;
+
+import android.app.Activity;
+import android.app.ExpandableListActivity;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.media.MediaFile;
+import android.media.MediaScanner;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.provider.MediaStore;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.SubMenu;
+import android.view.Window;
+import android.widget.TextView;
+import android.widget.Toast;
+
+public class MusicUtils {
+
+ private static final String TAG = "MusicUtils";
+
+ public interface Defs {
+ public final static int OPEN_URL = 0;
+ public final static int ADD_TO_PLAYLIST = 1;
+ public final static int USE_AS_RINGTONE = 2;
+ public final static int PLAYLIST_SELECTED = 3;
+ public final static int NEW_PLAYLIST = 4;
+ public final static int PLAY_SELECTION = 5;
+ public final static int GOTO_START = 6;
+ public final static int GOTO_PLAYBACK = 7;
+ public final static int PARTY_SHUFFLE = 8;
+ public final static int SHUFFLE_ALL = 9;
+ public final static int DELETE_ITEM = 10;
+ public final static int SCAN_DONE = 11;
+ public final static int CHILD_MENU_BASE = 12;
+ public final static int QUEUE = 13;
+ }
+
+ public static String makeAlbumsSongsLabel(Context context, int numalbums, int numsongs, boolean isUnknown) {
+ // There are several formats for the albums/songs information:
+ // "1 Song" - used if there is only 1 song
+ // "N Songs" - used for the "unknown artist" item
+ // "1 Album"/"N Songs"
+ // "N Album"/"M Songs"
+ // Depending on locale, these may need to be further subdivided
+
+ StringBuilder songs_albums = new StringBuilder();
+
+ if (numsongs == 1) {
+ songs_albums.append(context.getString(R.string.onesong));
+ } else {
+ Resources r = context.getResources();
+ if (! isUnknown) {
+ String f = r.getQuantityText(R.plurals.Nalbums, numalbums).toString();
+ sFormatBuilder.setLength(0);
+ sFormatter.format(f, Integer.valueOf(numalbums));
+ songs_albums.append(sFormatBuilder);
+ songs_albums.append(context.getString(R.string.albumsongseparator));
+ }
+ String f = r.getQuantityText(R.plurals.Nsongs, numsongs).toString();
+ sFormatBuilder.setLength(0);
+ sFormatter.format(f, Integer.valueOf(numsongs));
+ songs_albums.append(sFormatBuilder);
+ }
+ return songs_albums.toString();
+ }
+
+ public static IMediaPlaybackService sService = null;
+ private static HashMap<Context, ServiceBinder> sConnectionMap = new HashMap<Context, ServiceBinder>();
+
+ public static boolean bindToService(Context context) {
+ return bindToService(context, null);
+ }
+
+ public static boolean bindToService(Context context, ServiceConnection callback) {
+ context.startService(new Intent(context, MediaPlaybackService.class));
+ ServiceBinder sb = new ServiceBinder(callback);
+ sConnectionMap.put(context, sb);
+ return context.bindService((new Intent()).setClass(context,
+ MediaPlaybackService.class), sb, 0);
+ }
+
+ public static void unbindFromService(Context context) {
+ ServiceBinder sb = (ServiceBinder) sConnectionMap.remove(context);
+ if (sb == null) {
+ Log.e("MusicUtils", "Trying to unbind for unknown Context");
+ return;
+ }
+ context.unbindService(sb);
+ }
+
+ private static class ServiceBinder implements ServiceConnection {
+ ServiceConnection mCallback;
+ ServiceBinder(ServiceConnection callback) {
+ mCallback = callback;
+ }
+
+ public void onServiceConnected(ComponentName className, android.os.IBinder service) {
+ sService = IMediaPlaybackService.Stub.asInterface(service);
+ initAlbumArtCache();
+ if (mCallback != null) {
+ mCallback.onServiceConnected(className, service);
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ if (mCallback != null) {
+ mCallback.onServiceDisconnected(className);
+ }
+ sService = null;
+ }
+ }
+
+ public static int getCurrentAlbumId() {
+ if (sService != null) {
+ try {
+ return sService.getAlbumId();
+ } catch (RemoteException ex) {
+ }
+ }
+ return -1;
+ }
+
+ public static int getCurrentArtistId() {
+ if (MusicUtils.sService != null) {
+ try {
+ return sService.getArtistId();
+ } catch (RemoteException ex) {
+ }
+ }
+ return -1;
+ }
+
+ public static int getCurrentAudioId() {
+ if (MusicUtils.sService != null) {
+ try {
+ return sService.getAudioId();
+ } catch (RemoteException ex) {
+ }
+ }
+ return -1;
+ }
+
+ public static int getCurrentShuffleMode() {
+ int mode = MediaPlaybackService.SHUFFLE_NONE;
+ if (sService != null) {
+ try {
+ mode = sService.getShuffleMode();
+ } catch (RemoteException ex) {
+ }
+ }
+ return mode;
+ }
+
+ /*
+ * Returns true if a file is currently opened for playback (regardless
+ * of whether it's playing or paused).
+ */
+ public static boolean isMusicLoaded() {
+ if (MusicUtils.sService != null) {
+ try {
+ return sService.getPath() != null;
+ } catch (RemoteException ex) {
+ }
+ }
+ return false;
+ }
+
+ private final static int [] sEmptyList = new int[0];
+
+ public static int [] getSongListForCursor(Cursor cursor) {
+ if (cursor == null) {
+ return sEmptyList;
+ }
+ int len = cursor.getCount();
+ int [] list = new int[len];
+ cursor.moveToFirst();
+ int colidx = cursor.getColumnIndex(MediaStore.Audio.Playlists.Members.AUDIO_ID);
+ if (colidx < 0) {
+ colidx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);
+ }
+ for (int i = 0; i < len; i++) {
+ list[i] = cursor.getInt(colidx);
+ cursor.moveToNext();
+ }
+ return list;
+ }
+
+ public static int [] getSongListForArtist(Context context, int id) {
+ final String[] ccols = new String[] { MediaStore.Audio.Media._ID };
+ String where = MediaStore.Audio.Media.ARTIST_ID + "=" + id + " AND " +
+ MediaStore.Audio.Media.IS_MUSIC + "=1";
+ Cursor cursor = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+ ccols, where, null,
+ MediaStore.Audio.Media.ALBUM_KEY + "," + MediaStore.Audio.Media.TRACK);
+
+ if (cursor != null) {
+ int [] list = getSongListForCursor(cursor);
+ cursor.close();
+ return list;
+ }
+ return sEmptyList;
+ }
+
+ public static int [] getSongListForAlbum(Context context, int id) {
+ final String[] ccols = new String[] { MediaStore.Audio.Media._ID };
+ String where = MediaStore.Audio.Media.ALBUM_ID + "=" + id + " AND " +
+ MediaStore.Audio.Media.IS_MUSIC + "=1";
+ Cursor cursor = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+ ccols, where, null, MediaStore.Audio.Media.TRACK);
+
+ if (cursor != null) {
+ int [] list = getSongListForCursor(cursor);
+ cursor.close();
+ return list;
+ }
+ return sEmptyList;
+ }
+
+ public static int [] getSongListForPlaylist(Context context, long plid) {
+ final String[] ccols = new String[] { MediaStore.Audio.Playlists.Members.AUDIO_ID };
+ Cursor cursor = query(context, MediaStore.Audio.Playlists.Members.getContentUri("external", plid),
+ ccols, null, null, MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER);
+
+ if (cursor != null) {
+ int [] list = getSongListForCursor(cursor);
+ cursor.close();
+ return list;
+ }
+ return sEmptyList;
+ }
+
+ public static void playPlaylist(Context context, long plid) {
+ int [] list = getSongListForPlaylist(context, plid);
+ if (list != null) {
+ playAll(context, list, -1, false);
+ }
+ }
+
+ public static int [] getAllSongs(Context context) {
+ Cursor c = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+ new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
+ null, null);
+ try {
+ if (c == null || c.getCount() == 0) {
+ return null;
+ }
+ int len = c.getCount();
+ int[] list = new int[len];
+ for (int i = 0; i < len; i++) {
+ c.moveToNext();
+ list[i] = c.getInt(0);
+ }
+
+ return list;
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ }
+
+ /**
+ * Fills out the given submenu with items for "new playlist" and
+ * any existing playlists. When the user selects an item, the
+ * application will receive PLAYLIST_SELECTED with the Uri of
+ * the selected playlist, NEW_PLAYLIST if a new playlist
+ * should be created, and QUEUE if the "current playlist" was
+ * selected.
+ * @param context The context to use for creating the menu items
+ * @param sub The submenu to add the items to.
+ */
+ public static void makePlaylistMenu(Context context, SubMenu sub) {
+ String[] cols = new String[] {
+ MediaStore.Audio.Playlists._ID,
+ MediaStore.Audio.Playlists.NAME
+ };
+ ContentResolver resolver = context.getContentResolver();
+ if (resolver == null) {
+ System.out.println("resolver = null");
+ } else {
+ String whereclause = MediaStore.Audio.Playlists.NAME + " != ''";
+ Cursor cur = resolver.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+ cols, whereclause, null,
+ MediaStore.Audio.Playlists.NAME);
+ sub.clear();
+ sub.add(1, Defs.QUEUE, 0, R.string.queue);
+ sub.add(1, Defs.NEW_PLAYLIST, 0, R.string.new_playlist);
+ if (cur != null && cur.getCount() > 0) {
+ //sub.addSeparator(1, 0);
+ cur.moveToFirst();
+ while (! cur.isAfterLast()) {
+ Intent intent = new Intent();
+ intent.putExtra("playlist", cur.getInt(0));
+// if (cur.getInt(0) == mLastPlaylistSelected) {
+// sub.add(0, MusicBaseActivity.PLAYLIST_SELECTED, cur.getString(1)).setIntent(intent);
+// } else {
+ sub.add(1, Defs.PLAYLIST_SELECTED, 0, cur.getString(1)).setIntent(intent);
+// }
+ cur.moveToNext();
+ }
+ }
+ if (cur != null) {
+ cur.close();
+ }
+ }
+ }
+
+ public static void clearPlaylist(Context context, int plid) {
+ final String[] ccols = new String[] { MediaStore.Audio.Playlists.Members._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) {
+ return;
+ }
+ cursor.moveToFirst();
+ while (!cursor.isAfterLast()) {
+ cursor.deleteRow();
+ }
+ cursor.commitUpdates();
+ cursor.close();
+ return;
+ }
+
+ public static void deleteTracks(Context context, int [] list) {
+
+ String [] cols = new String [] { MediaStore.Audio.Media._ID,
+ MediaStore.Audio.Media.DATA, MediaStore.Audio.Media.ALBUM_ID };
+ StringBuilder where = new StringBuilder();
+ where.append(MediaStore.Audio.Media._ID + " IN (");
+ for (int i = 0; i < list.length; i++) {
+ where.append(list[i]);
+ if (i < list.length - 1) {
+ where.append(",");
+ }
+ }
+ where.append(")");
+ Cursor c = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, cols,
+ where.toString(), null, null);
+
+ if (c != null) {
+
+ // step 1: remove selected tracks from the current playlist, as well
+ // as from the album art cache
+ try {
+ c.moveToFirst();
+ while (! c.isAfterLast()) {
+ // remove from current playlist
+ int id = c.getInt(0);
+ sService.removeTrack(id);
+ // remove from album art cache
+ int artIndex = c.getInt(2);
+ synchronized(sArtCache) {
+ sArtCache.remove(artIndex);
+ }
+ c.moveToNext();
+ }
+ } catch (RemoteException ex) {
+ }
+
+ // step 2: remove selected tracks from the database
+ context.getContentResolver().delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, where.toString(), null);
+
+ // step 3: remove files from card
+ c.moveToFirst();
+ while (! c.isAfterLast()) {
+ String name = c.getString(1);
+ File f = new File(name);
+ try { // File.delete can throw a security exception
+ if (!f.delete()) {
+ // I'm not sure if we'd ever get here (deletion would
+ // have to fail, but no exception thrown)
+ Log.e("MusicUtils", "Failed to delete file " + name);
+ }
+ c.moveToNext();
+ } catch (SecurityException ex) {
+ c.moveToNext();
+ }
+ }
+ c.commitUpdates();
+ c.close();
+ }
+
+ String message = context.getResources().getQuantityString(
+ R.plurals.NNNtracksdeleted, list.length, Integer.valueOf(list.length));
+
+ Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
+ // We deleted a number of tracks, which could affect any number of things
+ // in the media content domain, so update everything.
+ context.getContentResolver().notifyChange(Uri.parse("content://media"), null);
+ }
+
+ public static void addToCurrentPlaylist(Context context, int [] list) {
+ if (sService == null) {
+ return;
+ }
+ try {
+ sService.enqueue(list, MediaPlaybackService.LAST);
+ String message = context.getResources().getQuantityString(
+ R.plurals.NNNtrackstoplaylist, list.length, Integer.valueOf(list.length));
+ Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
+ } catch (RemoteException ex) {
+ }
+ }
+
+ public static void addToPlaylist(Context context, int [] ids, long playlistid) {
+ if (ids == null) {
+ // this shouldn't happen (the menuitems shouldn't be visible
+ // unless the selected item represents something playable
+ Log.e("MusicBase", "ListSelection null");
+ } else {
+ int size = ids.length;
+ ContentValues values [] = new ContentValues[size];
+ ContentResolver resolver = context.getContentResolver();
+ // need to determine the number of items currently in the playlist,
+ // so the play_order field can be maintained.
+ String[] cols = new String[] {
+ "count(*)"
+ };
+ Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistid);
+ Cursor cur = resolver.query(uri, cols, null, null, null);
+ cur.moveToFirst();
+ int base = cur.getInt(0);
+ cur.close();
+
+ for (int i = 0; i < size; i++) {
+ values[i] = new ContentValues();
+ values[i].put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, Integer.valueOf(base + i));
+ values[i].put(MediaStore.Audio.Playlists.Members.AUDIO_ID, ids[i]);
+ }
+ resolver.bulkInsert(uri, values);
+ String message = context.getResources().getQuantityString(
+ R.plurals.NNNtrackstoplaylist, size, Integer.valueOf(size));
+ Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
+ //mLastPlaylistSelected = playlistid;
+ }
+ }
+
+ public static Cursor query(Context context, Uri uri, String[] projection,
+ String selection, String[] selectionArgs, String sortOrder) {
+ try {
+ ContentResolver resolver = context.getContentResolver();
+ if (resolver == null) {
+ return null;
+ }
+ return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
+ } catch (UnsupportedOperationException ex) {
+ return null;
+ }
+
+ }
+
+ public static boolean isMediaScannerScanning(Context context) {
+ boolean result = false;
+ Uri uri = MediaStore.getMediaScannerUri();
+ Cursor cursor = query(context, MediaStore.getMediaScannerUri(),
+ new String [] { MediaStore.MEDIA_SCANNER_VOLUME }, null, null, null);
+ if (cursor != null) {
+ if (cursor.getCount() == 1) {
+ cursor.moveToFirst();
+ result = "external".equals(cursor.getString(0));
+ }
+ cursor.close();
+ }
+
+ return result;
+ }
+
+ public static void setSpinnerState(Activity a) {
+ if (isMediaScannerScanning(a)) {
+ // start the progress spinner
+ a.getWindow().setFeatureInt(
+ Window.FEATURE_INDETERMINATE_PROGRESS,
+ Window.PROGRESS_INDETERMINATE_ON);
+
+ a.getWindow().setFeatureInt(
+ Window.FEATURE_INDETERMINATE_PROGRESS,
+ Window.PROGRESS_VISIBILITY_ON);
+ } else {
+ // stop the progress spinner
+ a.getWindow().setFeatureInt(
+ Window.FEATURE_INDETERMINATE_PROGRESS,
+ Window.PROGRESS_VISIBILITY_OFF);
+ }
+ }
+
+ public static void displayDatabaseError(Activity a) {
+ String status = Environment.getExternalStorageState();
+ int title = R.string.sdcard_error_title;
+ int message = R.string.sdcard_error_message;
+
+ if (status.equals(Environment.MEDIA_SHARED)) {
+ title = R.string.sdcard_busy_title;
+ message = R.string.sdcard_busy_message;
+ } else if (status.equals(Environment.MEDIA_REMOVED)) {
+ title = R.string.sdcard_missing_title;
+ message = R.string.sdcard_missing_message;
+ } else if (status.equals(Environment.MEDIA_MOUNTED)){
+ // The card is mounted, but we didn't get a valid cursor.
+ // This probably means the mediascanner hasn't started scanning the
+ // card yet (there is a small window of time during boot where this
+ // will happen).
+ a.setTitle("");
+ Intent intent = new Intent();
+ intent.setClass(a, ScanningProgress.class);
+ a.startActivityForResult(intent, Defs.SCAN_DONE);
+ } else {
+ Log.d(TAG, "sd card: " + status);
+ }
+
+ a.setTitle(title);
+ if (a instanceof ExpandableListActivity) {
+ a.setContentView(R.layout.no_sd_card_expanding);
+ } else {
+ a.setContentView(R.layout.no_sd_card);
+ }
+ TextView tv = (TextView) a.findViewById(R.id.sd_message);
+ tv.setText(message);
+ }
+
+ static protected Uri getContentURIForPath(String path) {
+ return Uri.fromFile(new File(path));
+ }
+
+
+ /* Try to use String.format() as little as possible, because it creates a
+ * new Formatter every time you call it, which is very inefficient.
+ * Reusing an existing Formatter more than tripled the speed of
+ * makeTimeString().
+ * This Formatter/StringBuilder are also used by makeAlbumSongsLabel()
+ */
+ private static StringBuilder sFormatBuilder = new StringBuilder();
+ private static Formatter sFormatter = new Formatter(sFormatBuilder, Locale.getDefault());
+ private static final Object[] sTimeArgs = new Object[5];
+
+ public static String makeTimeString(Context context, long secs) {
+ String durationformat = context.getString(R.string.durationformat);
+
+ /* Provide multiple arguments so the format can be changed easily
+ * by modifying the xml.
+ */
+ sFormatBuilder.setLength(0);
+
+ final Object[] timeArgs = sTimeArgs;
+ timeArgs[0] = secs / 3600;
+ timeArgs[1] = secs / 60;
+ timeArgs[2] = (secs / 60) % 60;
+ timeArgs[3] = secs;
+ timeArgs[4] = secs % 60;
+
+ return sFormatter.format(durationformat, timeArgs).toString();
+ }
+
+ public static void shuffleAll(Context context, Cursor cursor) {
+ playAll(context, cursor, 0, true);
+ }
+
+ public static void playAll(Context context, Cursor cursor) {
+ playAll(context, cursor, 0, false);
+ }
+
+ public static void playAll(Context context, Cursor cursor, int position) {
+ playAll(context, cursor, position, false);
+ }
+
+ public static void playAll(Context context, int [] list, int position) {
+ playAll(context, list, position, false);
+ }
+
+ private static void playAll(Context context, Cursor cursor, int position, boolean force_shuffle) {
+
+ int [] list = getSongListForCursor(cursor);
+ playAll(context, list, position, force_shuffle);
+ }
+
+ private static void playAll(Context context, int [] list, int position, boolean force_shuffle) {
+ if (list.length == 0 || sService == null) {
+ Log.d("MusicUtils", "attempt to play empty song list");
+ // Don't try to play empty playlists. Nothing good will come of it.
+ String message = context.getString(R.string.emptyplaylist, list.length);
+ Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
+ return;
+ }
+ try {
+ if (force_shuffle) {
+ sService.setShuffleMode(MediaPlaybackService.SHUFFLE_NORMAL);
+ }
+ int curid = sService.getAudioId();
+ int curpos = sService.getQueuePosition();
+ if (position != -1 && curpos == position && curid == list[position]) {
+ // The selected file is the file that's currently playing;
+ // figure out if we need to restart with a new playlist,
+ // or just launch the playback activity.
+ int [] playlist = sService.getQueue();
+ if (Arrays.equals(list, playlist)) {
+ // we don't need to set a new list, but we should resume playback if needed
+ sService.play();
+ return; // the 'finally' block will still run
+ }
+ }
+ if (position < 0) {
+ position = 0;
+ }
+ sService.open(list, position);
+ sService.play();
+ } catch (RemoteException ex) {
+ } finally {
+ Intent intent = new Intent("com.android.music.PLAYBACK_VIEWER")
+ .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ context.startActivity(intent);
+ }
+ }
+
+ public static void clearQueue() {
+ try {
+ sService.removeTracks(0, Integer.MAX_VALUE);
+ } catch (RemoteException ex) {
+ }
+ }
+
+ // A really simple BitmapDrawable-like class, that doesn't do
+ // scaling, dithering or filtering.
+ private static class FastBitmapDrawable extends Drawable {
+ private Bitmap mBitmap;
+ public FastBitmapDrawable(Bitmap b) {
+ mBitmap = b;
+ }
+ @Override
+ public void draw(Canvas canvas) {
+ canvas.drawBitmap(mBitmap, 0, 0, null);
+ }
+ @Override
+ public int getOpacity() {
+ return PixelFormat.OPAQUE;
+ }
+ @Override
+ public void setAlpha(int alpha) {
+ }
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ }
+ }
+
+ private static int sArtId = -2;
+ private static byte [] mCachedArt;
+ private static Bitmap mCachedBit = null;
+ private static final BitmapFactory.Options sBitmapOptionsCache = new BitmapFactory.Options();
+ private static final BitmapFactory.Options sBitmapOptions = new BitmapFactory.Options();
+ private static final Uri sArtworkUri = Uri.parse("content://media/external/audio/albumart");
+ private static final HashMap<Integer, Drawable> sArtCache = new HashMap<Integer, Drawable>();
+ private static int sArtCacheId = -1;
+
+ static {
+ // for the cache,
+ // 565 is faster to decode and display
+ // and we don't want to dither here because the image will be scaled down later
+ sBitmapOptionsCache.inPreferredConfig = Bitmap.Config.RGB_565;
+ sBitmapOptionsCache.inDither = false;
+
+ sBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565;
+ sBitmapOptions.inDither = false;
+ }
+
+ public static void initAlbumArtCache() {
+ try {
+ int id = sService.getMediaMountedCount();
+ if (id != sArtCacheId) {
+ clearAlbumArtCache();
+ sArtCacheId = id;
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void clearAlbumArtCache() {
+ synchronized(sArtCache) {
+ sArtCache.clear();
+ }
+ }
+
+ public static Drawable getCachedArtwork(Context context, int artIndex, BitmapDrawable defaultArtwork) {
+ Drawable d = null;
+ synchronized(sArtCache) {
+ d = sArtCache.get(artIndex);
+ }
+ if (d == null) {
+ d = defaultArtwork;
+ final Bitmap icon = defaultArtwork.getBitmap();
+ int w = icon.getWidth();
+ int h = icon.getHeight();
+ Bitmap b = MusicUtils.getArtworkQuick(context, artIndex, w, h);
+ if (b != null) {
+ d = new FastBitmapDrawable(b);
+ synchronized(sArtCache) {
+ // the cache may have changed since we checked
+ Drawable value = sArtCache.get(artIndex);
+ if (value == null) {
+ sArtCache.put(artIndex, d);
+ } else {
+ d = value;
+ }
+ }
+ }
+ }
+ return d;
+ }
+
+ // Get album art for specified album. This method will not try to
+ // fall back to getting artwork directly from the file, nor will
+ // it attempt to repair the database.
+ private static Bitmap getArtworkQuick(Context context, int album_id, int w, int h) {
+ // NOTE: There is in fact a 1 pixel frame in the ImageView used to
+ // display this drawable. Take it into account now, so we don't have to
+ // scale later.
+ w -= 2;
+ h -= 2;
+ 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);
+ b.recycle();
+ b = tmp;
+ }
+ }
+
+ return b;
+ } catch (FileNotFoundException e) {
+ } finally {
+ try {
+ if (fd != null)
+ fd.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ return null;
+ }
+
+ // Get album art for specified album. You should not pass in the album id
+ // for the "unknown" album here (use -1 instead)
+ public static Bitmap getArtwork(Context context, int album_id) {
+
+ if (album_id < 0) {
+ // This is something that is not in the database, so get the album art directly
+ // from the file.
+ Bitmap bm = getArtworkFromFile(context, null, -1);
+ if (bm != null) {
+ return bm;
+ }
+ return getDefaultArtwork(context);
+ }
+
+ ContentResolver res = context.getContentResolver();
+ Uri uri = ContentUris.withAppendedId(sArtworkUri, album_id);
+ if (uri != null) {
+ InputStream in = null;
+ try {
+ in = res.openInputStream(uri);
+ return BitmapFactory.decodeStream(in, null, sBitmapOptions);
+ } catch (FileNotFoundException ex) {
+ // The album art thumbnail does not actually exist. Maybe the user deleted it, or
+ // maybe it never existed to begin with.
+ Bitmap bm = getArtworkFromFile(context, null, album_id);
+ if (bm != null) {
+ // Put the newly found artwork in the database.
+ // Note that this shouldn't be done for the "unknown" album,
+ // but if this method is called correctly, that won't happen.
+
+ // first write it somewhere
+ String file = Environment.getExternalStorageDirectory()
+ + "/albumthumbs/" + String.valueOf(System.currentTimeMillis());
+ if (ensureFileExists(file)) {
+ try {
+ OutputStream outstream = new FileOutputStream(file);
+ if (bm.getConfig() == null) {
+ bm = bm.copy(Bitmap.Config.RGB_565, false);
+ if (bm == null) {
+ return getDefaultArtwork(context);
+ }
+ }
+ boolean success = bm.compress(Bitmap.CompressFormat.JPEG, 75, outstream);
+ outstream.close();
+ if (success) {
+ ContentValues values = new ContentValues();
+ values.put("album_id", album_id);
+ values.put("_data", file);
+ Uri newuri = res.insert(sArtworkUri, values);
+ if (newuri == null) {
+ // Failed to insert in to the database. The most likely
+ // cause of this is that the item already existed in the
+ // database, and the most likely cause of that is that
+ // the album was scanned before, but the user deleted the
+ // album art from the sd card.
+ // We can ignore that case here, since the media provider
+ // will regenerate the album art for those entries when
+ // it detects this.
+ success = false;
+ }
+ }
+ if (!success) {
+ File f = new File(file);
+ f.delete();
+ }
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "error creating file", e);
+ } catch (IOException e) {
+ Log.e(TAG, "error creating file", e);
+ }
+ }
+ } else {
+ bm = getDefaultArtwork(context);
+ }
+ return bm;
+ } finally {
+ try {
+ if (in != null) {
+ in.close();
+ }
+ } catch (IOException ex) {
+ }
+ }
+ }
+
+ return null;
+ }
+
+ // copied from MediaProvider
+ private static boolean ensureFileExists(String path) {
+ File file = new File(path);
+ if (file.exists()) {
+ return true;
+ } else {
+ // we will not attempt to create the first directory in the path
+ // (for example, do not create /sdcard if the SD card is not mounted)
+ int secondSlash = path.indexOf('/', 1);
+ if (secondSlash < 1) return false;
+ String directoryPath = path.substring(0, secondSlash);
+ File directory = new File(directoryPath);
+ if (!directory.exists())
+ return false;
+ file.getParentFile().mkdirs();
+ try {
+ return file.createNewFile();
+ } catch(IOException ioe) {
+ Log.e(TAG, "File creation failed", ioe);
+ }
+ return false;
+ }
+ }
+
+ // get album art for specified file
+ private static final String sExternalMediaUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString();
+ private static Bitmap getArtworkFromFile(Context context, Uri uri, int albumid) {
+ Bitmap bm = null;
+ byte [] art = null;
+ String path = null;
+
+ if (sArtId == albumid) {
+ //Log.i("@@@@@@ ", "reusing cached data", new Exception());
+ if (mCachedBit != null) {
+ return mCachedBit;
+ }
+ art = mCachedArt;
+ } else {
+ // try reading embedded artwork
+ if (uri == null) {
+ try {
+ int curalbum = sService.getAlbumId();
+ if (curalbum == albumid || albumid < 0) {
+ path = sService.getPath();
+ if (path != null) {
+ uri = Uri.parse(path);
+ }
+ }
+ } catch (RemoteException ex) {
+ }
+ }
+ if (uri == null) {
+ if (albumid >= 0) {
+ Cursor c = query(context,MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+ new String[] { MediaStore.Audio.Media._ID, MediaStore.Audio.Media.ALBUM },
+ MediaStore.Audio.Media.ALBUM_ID + "=?", new String [] {String.valueOf(albumid)},
+ null);
+ if (c != null) {
+ c.moveToFirst();
+ if (!c.isAfterLast()) {
+ int trackid = c.getInt(0);
+ uri = ContentUris.withAppendedId(
+ MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, trackid);
+ }
+ if (c.getString(1).equals(MediaFile.UNKNOWN_STRING)) {
+ albumid = -1;
+ }
+ c.close();
+ }
+ }
+ }
+ if (uri != null) {
+ MediaScanner scanner = new MediaScanner(context);
+ ParcelFileDescriptor pfd = null;
+ try {
+ pfd = context.getContentResolver().openFileDescriptor(uri, "r");
+ if (pfd != null) {
+ FileDescriptor fd = pfd.getFileDescriptor();
+ art = scanner.extractAlbumArt(fd);
+ }
+ } catch (IOException ex) {
+ } catch (SecurityException ex) {
+ } finally {
+ try {
+ if (pfd != null) {
+ pfd.close();
+ }
+ } catch (IOException ex) {
+ }
+ }
+ }
+ }
+ // if no embedded art exists, look for AlbumArt.jpg in same directory as the media file
+ if (art == null && path != null) {
+ if (path.startsWith(sExternalMediaUri)) {
+ // get the real path
+ Cursor c = query(context,Uri.parse(path),
+ new String[] { MediaStore.Audio.Media.DATA},
+ null, null, null);
+ if (c != null) {
+ c.moveToFirst();
+ if (!c.isAfterLast()) {
+ path = c.getString(0);
+ }
+ c.close();
+ }
+ }
+ int lastSlash = path.lastIndexOf('/');
+ if (lastSlash > 0) {
+ String artPath = path.substring(0, lastSlash + 1) + "AlbumArt.jpg";
+ File file = new File(artPath);
+ if (file.exists()) {
+ art = new byte[(int)file.length()];
+ FileInputStream stream = null;
+ try {
+ stream = new FileInputStream(file);
+ stream.read(art);
+ } catch (IOException ex) {
+ art = null;
+ } finally {
+ try {
+ if (stream != null) {
+ stream.close();
+ }
+ } catch (IOException ex) {
+ }
+ }
+ } else {
+ // TODO: try getting album art from the web
+ }
+ }
+ }
+
+ if (art != null) {
+ try {
+ // get the size of the bitmap
+ BitmapFactory.Options opts = new BitmapFactory.Options();
+ opts.inJustDecodeBounds = true;
+ opts.inSampleSize = 1;
+ BitmapFactory.decodeByteArray(art, 0, art.length, opts);
+
+ // request a reasonably sized output image
+ // TODO: don't hardcode the size
+ while (opts.outHeight > 320 || opts.outWidth > 320) {
+ opts.outHeight /= 2;
+ opts.outWidth /= 2;
+ opts.inSampleSize *= 2;
+ }
+
+ // get the image for real now
+ opts.inJustDecodeBounds = false;
+ bm = BitmapFactory.decodeByteArray(art, 0, art.length, opts);
+ if (albumid != -1) {
+ sArtId = albumid;
+ }
+ mCachedArt = art;
+ mCachedBit = bm;
+ } catch (Exception e) {
+ }
+ }
+ return bm;
+ }
+
+ private static Bitmap getDefaultArtwork(Context context) {
+ BitmapFactory.Options opts = new BitmapFactory.Options();
+ opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ return BitmapFactory.decodeStream(
+ context.getResources().openRawResource(R.drawable.albumart_mp_unknown), null, opts);
+ }
+
+ static int getIntPref(Context context, String name, int def) {
+ SharedPreferences prefs =
+ context.getSharedPreferences("com.android.music", Context.MODE_PRIVATE);
+ return prefs.getInt(name, def);
+ }
+
+ static void setIntPref(Context context, String name, int value) {
+ SharedPreferences prefs =
+ context.getSharedPreferences("com.android.music", Context.MODE_PRIVATE);
+ Editor ed = prefs.edit();
+ ed.putInt(name, value);
+ ed.commit();
+ }
+
+ static void setRingtone(Context context, long id) {
+ ContentResolver resolver = context.getContentResolver();
+ // Set the flag in the database to mark this as a ringtone
+ Uri ringUri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
+ try {
+ ContentValues values = new ContentValues(2);
+ values.put(MediaStore.Audio.Media.IS_RINGTONE, "1");
+ values.put(MediaStore.Audio.Media.IS_ALARM, "1");
+ resolver.update(ringUri, values, null, null);
+ } catch (UnsupportedOperationException ex) {
+ // most likely the card just got unmounted
+ Log.e(TAG, "couldn't set ringtone flag for id " + id);
+ return;
+ }
+
+ String[] cols = new String[] {
+ MediaStore.Audio.Media._ID,
+ MediaStore.Audio.Media.DATA,
+ MediaStore.Audio.Media.TITLE
+ };
+
+ String where = MediaStore.Audio.Media._ID + "=" + id;
+ Cursor cursor = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+ cols, where , null, null);
+ try {
+ if (cursor != null && cursor.getCount() == 1) {
+ // Set the system setting to make this the current ringtone
+ cursor.moveToFirst();
+ Settings.System.putString(resolver, Settings.System.RINGTONE, ringUri.toString());
+ String message = context.getString(R.string.ringtone_set, cursor.getString(2));
+ Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import java.text.Collator;
+import java.util.ArrayList;
+
+import android.app.ListActivity;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+
+import com.android.internal.database.ArrayListCursor;
+import android.database.Cursor;
+import android.database.MergeCursor;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+
+public class PlaylistBrowserActivity extends ListActivity
+ implements View.OnCreateContextMenuListener, MusicUtils.Defs
+{
+ private static final 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 boolean mCreateShortcut;
+
+ public PlaylistBrowserActivity()
+ {
+ }
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle icicle)
+ {
+ super.onCreate(icicle);
+
+ final Intent intent = getIntent();
+ final String action = intent.getAction();
+ if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) {
+ mCreateShortcut = true;
+ }
+
+ requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+ MusicUtils.bindToService(this, new ServiceConnection() {
+ public void onServiceConnected(ComponentName classname, IBinder obj) {
+ if (Intent.ACTION_VIEW.equals(action)) {
+ long id = Long.parseLong(intent.getExtras().getString("playlist"));
+ if (id == RECENTLY_ADDED_PLAYLIST) {
+ playRecentlyAdded();
+ } else if (id == ALL_SONGS_PLAYLIST) {
+ int [] list = MusicUtils.getAllSongs(PlaylistBrowserActivity.this);
+ if (list != null) {
+ MusicUtils.playAll(PlaylistBrowserActivity.this, list, 0);
+ }
+ } else {
+ MusicUtils.playPlaylist(PlaylistBrowserActivity.this, id);
+ }
+ finish();
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName classname) {
+ }
+
+ });
+ IntentFilter f = new IntentFilter();
+ f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
+ f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
+ f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
+ f.addDataScheme("file");
+ registerReceiver(mScanListener, f);
+
+ init();
+ }
+
+
+
+ @Override
+ public void onDestroy() {
+ MusicUtils.unbindFromService(this);
+ if (mPlaylistCursor != null) {
+ mPlaylistCursor.close();
+ }
+ unregisterReceiver(mScanListener);
+ super.onDestroy();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ MusicUtils.setSpinnerState(this);
+ }
+ @Override
+ public void onPause() {
+ mReScanHandler.removeCallbacksAndMessages(null);
+ super.onPause();
+ }
+ private BroadcastReceiver mScanListener = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ MusicUtils.setSpinnerState(PlaylistBrowserActivity.this);
+ mReScanHandler.sendEmptyMessage(0);
+ }
+ };
+
+ private Handler mReScanHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ init();
+ if (mPlaylistCursor == null) {
+ sendEmptyMessageDelayed(0, 1000);
+ }
+ }
+ };
+ public void init() {
+
+ mPlaylistCursor = getPlaylistCursor(null);
+
+ if (mPlaylistCursor == null) {
+ MusicUtils.displayDatabaseError(this);
+ setContentView(R.layout.no_sd_card);
+ return;
+ }
+
+ setContentView(R.layout.media_picker_activity);
+
+ if (mPlaylistCursor.getCount() > 0) {
+ mPlaylistCursor.moveToFirst();
+
+ setTitle(R.string.playlists_title);
+ } else {
+ setTitle(R.string.no_playlists_title);
+ }
+
+ // Map Cursor columns to views defined in media_list_item.xml
+ PlaylistListAdapter adapter = new PlaylistListAdapter(
+ this,
+ R.layout.track_list_item,
+ mPlaylistCursor,
+ new String[] { MediaStore.Audio.Playlists.NAME},
+ new int[] { android.R.id.text1 });
+
+ setListAdapter(adapter);
+ ListView lv = getListView();
+ lv.setOnCreateContextMenuListener(this);
+ lv.setTextFilterEnabled(true);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ if (!mCreateShortcut) {
+ menu.add(0, GOTO_START, 0, R.string.goto_start).setIcon(
+ R.drawable.ic_menu_music_library);
+ menu.add(0, GOTO_PLAYBACK, 0, R.string.goto_playback).setIcon(
+ R.drawable.ic_menu_playback).setVisible(MusicUtils.isMusicLoaded());
+ }
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ Intent intent;
+ switch (item.getItemId()) {
+ case GOTO_START:
+ intent = new Intent();
+ intent.setClass(this, MusicBrowserActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ return true;
+
+ case GOTO_PLAYBACK:
+ intent = new Intent("com.android.music.PLAYBACK_VIEWER");
+ startActivity(intent);
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
+ if (mCreateShortcut) {
+ return;
+ }
+
+ AdapterContextMenuInfo mi = (AdapterContextMenuInfo) menuInfoIn;
+
+ if (mi.id < 0) {
+ menu.add(0, PLAY_SELECTION, 0, R.string.play_selection);
+ menu.add(0, EDIT_PLAYLIST, 0, R.string.edit_playlist_menu);
+ mPlaylistCursor.moveToPosition(mi.position);
+ menu.setHeaderTitle(mPlaylistCursor.getString(mPlaylistCursor.getColumnIndex(MediaStore.Audio.Playlists.NAME)));
+ } else {
+ menu.add(0, PLAY_SELECTION, 0, R.string.play_selection);
+ menu.add(0, DELETE_PLAYLIST, 0, R.string.delete_playlist_menu);
+ menu.add(0, EDIT_PLAYLIST, 0, R.string.edit_playlist_menu);
+ menu.add(0, RENAME_PLAYLIST, 0, R.string.rename_playlist_menu);
+ mPlaylistCursor.moveToPosition(mi.position);
+ menu.setHeaderTitle(mPlaylistCursor.getString(mPlaylistCursor.getColumnIndex(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 {
+ MusicUtils.playPlaylist(this, mi.id);
+ }
+ break;
+ case DELETE_PLAYLIST:
+ mPlaylistCursor.moveToPosition(mi.position);
+ mPlaylistCursor.deleteRow();
+ mPlaylistCursor.commitUpdates();
+ 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 {
+ Intent intent = new Intent(Intent.ACTION_EDIT);
+ intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
+ intent.putExtra("playlist", Long.valueOf(mi.id).toString());
+ startActivity(intent);
+ }
+ 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 {
+ init();
+ }
+ break;
+ }
+ }
+
+ @Override
+ protected void onListItemClick(ListView l, View v, int position, long id)
+ {
+ if (mCreateShortcut) {
+ final Intent shortcut = new Intent();
+ shortcut.setAction(Intent.ACTION_VIEW);
+ shortcut.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/playlist");
+ shortcut.putExtra("playlist", String.valueOf(id));
+
+ final Intent intent = new Intent();
+ intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcut);
+ intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, ((TextView) v.findViewById(R.id.line1)).getText());
+ intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(
+ this, R.drawable.app_music));
+
+ setResult(RESULT_OK, intent);
+ finish();
+ return;
+ }
+ if (id == RECENTLY_ADDED_PLAYLIST) {
+ playRecentlyAdded();
+ } else {
+ MusicUtils.playPlaylist(this, id);
+ }
+ }
+
+ private void playRecentlyAdded() {
+ // do a query for all playlists 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;
+ }
+ int len = cursor.getCount();
+ int [] list = new int[len];
+ for (int i = 0; i < len; i++) {
+ cursor.moveToNext();
+ list[i] = cursor.getInt(0);
+ }
+ cursor.close();
+ MusicUtils.playAll(this, list, 0);
+ }
+
+ private Cursor getPlaylistCursor(String filterstring) {
+ String[] cols = new String[] {
+ MediaStore.Audio.Playlists._ID,
+ MediaStore.Audio.Playlists.NAME
+ };
+
+ 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();
+
+ Cursor results = MusicUtils.query(this, MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+ cols, whereclause, keywords,
+ MediaStore.Audio.Playlists.NAME);
+
+ if (results == null) {
+ return null;
+ }
+ ArrayList<ArrayList> autoplaylists = new ArrayList<ArrayList>();
+ if (mCreateShortcut) {
+ ArrayList<Object> all = new ArrayList<Object>(2);
+ all.add(ALL_SONGS_PLAYLIST);
+ all.add(getString(R.string.play_all));
+ autoplaylists.add(all);
+ }
+ ArrayList<Object> recent = new ArrayList<Object>(2);
+ recent.add(RECENTLY_ADDED_PLAYLIST);
+ recent.add(getString(R.string.recentlyadded));
+ autoplaylists.add(recent);
+
+ ArrayListCursor autoplaylistscursor = new ArrayListCursor(cols, autoplaylists);
+
+ Cursor c = new MergeCursor(new Cursor [] {autoplaylistscursor, results});
+
+ return c;
+ }
+
+ class PlaylistListAdapter extends SimpleCursorAdapter {
+ int mTitleIdx;
+
+ PlaylistListAdapter(Context context, int layout, Cursor cursor, String[] from, int[] to) {
+ super(context, layout, cursor, from, to);
+
+ mTitleIdx = cursor.getColumnIndex(MediaStore.Audio.Playlists.NAME);
+ }
+
+ @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);
+
+ ImageView iv = (ImageView) view.findViewById(R.id.icon);
+ iv.setImageResource(R.drawable.ic_mp_playlist_list);
+ ViewGroup.LayoutParams p = iv.getLayoutParams();
+ p.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+ p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+
+ iv = (ImageView) view.findViewById(R.id.play_indicator);
+ iv.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void changeCursor(Cursor cursor) {
+ super.changeCursor(cursor);
+ mPlaylistCursor = cursor;
+ }
+ @Override
+ public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
+ return getPlaylistCursor(constraint.toString());
+ }
+ }
+
+ private Cursor mPlaylistCursor;
+}
+
--- /dev/null
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.app.ListActivity;
+import android.app.SearchManager;
+import android.content.Context;
+import android.content.Intent;
+import com.android.internal.database.ArrayListCursor;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.media.AudioManager;
+import android.media.MediaFile;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.ViewGroup.OnHierarchyChangeListener;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+public class QueryBrowserActivity extends ListActivity implements MusicUtils.Defs
+{
+ private final static int PLAY_NOW = 0;
+ private final static int ADD_TO_QUEUE = 1;
+ private final static int PLAY_NEXT = 2;
+ private final static int PLAY_ARTIST = 3;
+ private final static int EXPLORE_ARTIST = 4;
+ private final static int PLAY_ALBUM = 5;
+ private final static int EXPLORE_ALBUM = 6;
+ private final static int REQUERY = 3;
+ private String mSearchString = null;
+
+ public QueryBrowserActivity()
+ {
+ }
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle icicle)
+ {
+ super.onCreate(icicle);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+ MusicUtils.bindToService(this);
+
+ if (icicle == null) {
+ Intent intent = getIntent();
+ mSearchString = intent.getStringExtra(SearchManager.QUERY);
+ }
+ if (mSearchString == null) {
+ mSearchString = "";
+ }
+ init();
+ }
+
+ @Override
+ public void onDestroy() {
+ MusicUtils.unbindFromService(this);
+ super.onDestroy();
+ }
+
+ public void init() {
+ // Set the layout for this activity. You can find it
+ // in assets/res/any/layout/media_picker_activity.xml
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(R.layout.query_activity);
+ setTitle(R.string.search_title);
+ mTrackList = (ListView) findViewById(android.R.id.list);
+
+ mQueryCursor = getQueryCursor(mSearchString.length() == 0 ? "" : null);
+
+ ListView lv = getListView();
+ lv.setTextFilterEnabled(true);
+
+ if (mQueryCursor == null) {
+ MusicUtils.displayDatabaseError(this);
+ setListAdapter(null);
+ lv.setFocusable(false);
+ return;
+ }
+
+ // Map Cursor columns to views defined in media_list_item.xml
+ QueryListAdapter adapter = new QueryListAdapter(
+ this,
+ R.layout.track_list_item,
+ mQueryCursor,
+ new String[] {},
+ new int[] {});
+
+ setListAdapter(adapter);
+
+ if (mSearchString.length() != 0) {
+ // Hack. Can be removed once ListView supports starting filtering with a given string
+ lv.setOnHierarchyChangeListener(mHierarchyListener);
+ }
+ }
+
+ OnHierarchyChangeListener mHierarchyListener = new OnHierarchyChangeListener() {
+ public void onChildViewAdded(View parent, View child) {
+ ((ListView)parent).setOnHierarchyChangeListener(null);
+ // need to do this here to be sure all the views have been initialized
+ startFilteringWithString(mSearchString);
+ }
+
+ public void onChildViewRemoved(View parent, View child) {
+ }
+ };
+
+ private KeyEvent eventForChar(char c) {
+ int code = -1;
+ if (c >= 'a' && c <= 'z') {
+ code = KeyEvent.KEYCODE_A + (c - 'a');
+ } else if (c >= 'A' && c <= 'Z') {
+ code = KeyEvent.KEYCODE_A + (c - 'A');
+ } else if (c >= '0' && c <= '9') {
+ code = KeyEvent.KEYCODE_0 + (c - '0');
+ }
+ if (code != -1) {
+ return new KeyEvent(KeyEvent.ACTION_DOWN, code);
+ }
+ return null;
+ }
+ private void startFilteringWithString(String filterstring) {
+ ListView lv = getListView();
+ for (int i = 0; i < filterstring.length(); i++) {
+ KeyEvent event = eventForChar(filterstring.charAt(i));
+ if (event != null) {
+ lv.onKeyDown(event.getKeyCode(), event);
+ }
+ }
+ }
+
+ @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.getColumnIndex(MediaStore.Audio.Media.MIME_TYPE));
+
+ if ("artist".equals(selectedType)) {
+ Intent intent = new Intent(Intent.ACTION_PICK);
+ intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album");
+ intent.putExtra("artist", Long.valueOf(id).toString());
+ startActivity(intent);
+ } else if ("album".equals(selectedType)) {
+ Intent intent = new Intent(Intent.ACTION_PICK);
+ intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
+ intent.putExtra("album", Long.valueOf(id).toString());
+ startActivity(intent);
+ } else if (position >= 0 && id >= 0){
+ int [] list = new int[] { (int) id };
+ MusicUtils.playAll(this, list, 0);
+ } else {
+ Log.e("QueryBrowser", "invalid position/id: " + position + "/" + id);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case USE_AS_RINGTONE: {
+ // Set the system setting to make this the current ringtone
+ MusicUtils.setRingtone(this, mTrackList.getSelectedItemId());
+ return true;
+ }
+
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private Cursor getQueryCursor(String filterstring) {
+ String[] ccols = new String[] {
+ "_id", // this will be the artist, album or track ID
+ MediaStore.Audio.Media.MIME_TYPE, // mimetype of audio file, or "artist" or "album"
+ SearchManager.SUGGEST_COLUMN_TEXT_1,
+ "data1",
+ "data2"
+ };
+ if (filterstring == null) {
+ ArrayList<ArrayList> placeholder = new ArrayList<ArrayList>();
+ ArrayList<Object> row = new ArrayList<Object>(5);
+ row.add(-1);
+ row.add("");
+ row.add("");
+ row.add("");
+ row.add("");
+ placeholder.add(row);
+ return new ArrayListCursor(ccols, placeholder);
+ }
+ Uri search = Uri.parse("content://media/external/audio/" +
+ SearchManager.SUGGEST_URI_PATH_QUERY + "/" + Uri.encode(filterstring));
+
+ return MusicUtils.query(this, search, ccols, null, null, null);
+
+ }
+
+ class QueryListAdapter extends SimpleCursorAdapter {
+ QueryListAdapter(Context context, int layout, Cursor cursor, String[] from, int[] to) {
+ super(context, layout, cursor, from, to);
+ }
+
+ @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.getColumnIndex(MediaStore.Audio.Media.MIME_TYPE));
+
+ if (mimetype == null) {
+ mimetype = "audio/";
+ }
+ if (mimetype.equals("artist")) {
+ iv.setImageResource(R.drawable.ic_search_category_music_artist);
+ String name = cursor.getString(cursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1));
+ String displayname = name;
+ if (name.equals(MediaFile.UNKNOWN_STRING)) {
+ displayname = context.getString(R.string.unknown_artist_name);
+ }
+ tv1.setText(displayname);
+
+ int numalbums = cursor.getInt(cursor.getColumnIndex("data1"));
+ int numsongs = cursor.getInt(cursor.getColumnIndex("data2"));
+
+ String songs_albums = MusicUtils.makeAlbumsSongsLabel(context,
+ numalbums, numsongs, name.equals(MediaFile.UNKNOWN_STRING));
+
+ tv2.setText(songs_albums);
+
+ } else if (mimetype.equals("album")) {
+ iv.setImageResource(R.drawable.ic_search_category_music_album);
+ String name = cursor.getString(cursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1));
+ String displayname = name;
+ if (name.equals(MediaFile.UNKNOWN_STRING)) {
+ displayname = context.getString(R.string.unknown_album_name);
+ }
+ tv1.setText(displayname);
+
+ name = cursor.getString(cursor.getColumnIndex("data1"));
+ displayname = name;
+ if (name.equals(MediaFile.UNKNOWN_STRING)) {
+ displayname = context.getString(R.string.unknown_artist_name);
+ }
+ tv2.setText(displayname);
+
+ } else if(mimetype.startsWith("audio/") ||
+ mimetype.equals("application/ogg") ||
+ mimetype.equals("application/x-ogg")) {
+ iv.setImageResource(R.drawable.ic_search_category_music_song);
+ String name = cursor.getString(cursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1));
+ tv1.setText(name);
+
+ String displayname = cursor.getString(cursor.getColumnIndex("data1"));
+ if (name.equals(MediaFile.UNKNOWN_STRING)) {
+ displayname = context.getString(R.string.unknown_artist_name);
+ }
+ name = cursor.getString(cursor.getColumnIndex("data2"));
+ if (name.equals(MediaFile.UNKNOWN_STRING)) {
+ name = context.getString(R.string.unknown_artist_name);
+ }
+ tv2.setText(displayname + " - " + name);
+ }
+ }
+ @Override
+ public void changeCursor(Cursor cursor) {
+ super.changeCursor(cursor);
+ mQueryCursor = cursor;
+ }
+ @Override
+ public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
+ return getQueryCursor(constraint.toString());
+ }
+ }
+
+ private ListView mTrackList;
+ private Cursor mQueryCursor;
+}
+
--- /dev/null
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+public class RenamePlaylist extends Activity
+{
+ private EditText mPlaylist;
+ private TextView mPrompt;
+ private Button mSaveButton;
+ private long mRenameId;
+ private String mOriginalName;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(R.layout.create_playlist);
+ getWindow().setLayout(WindowManager.LayoutParams.FILL_PARENT,
+ WindowManager.LayoutParams.WRAP_CONTENT);
+
+ mPrompt = (TextView)findViewById(R.id.prompt);
+ mPlaylist = (EditText)findViewById(R.id.playlist);
+ mSaveButton = (Button) findViewById(R.id.create);
+ mSaveButton.setOnClickListener(mOpenClicked);
+
+ ((Button)findViewById(R.id.cancel)).setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ finish();
+ }
+ });
+
+ mRenameId = icicle != null ? icicle.getLong("rename")
+ : getIntent().getLongExtra("rename", -1);
+ mOriginalName = nameForId(mRenameId);
+ String defaultname = icicle != null ? icicle.getString("defaultname") : mOriginalName;
+
+ if (mRenameId < 0 || mOriginalName == null || defaultname == null) {
+ Log.i("@@@@", "Rename failed: " + mRenameId + "/" + defaultname);
+ finish();
+ return;
+ }
+
+ String promptformat;
+ if (mOriginalName.equals(defaultname)) {
+ promptformat = getString(R.string.rename_playlist_same_prompt);
+ } else {
+ promptformat = getString(R.string.rename_playlist_diff_prompt);
+ }
+
+ String prompt = String.format(promptformat, mOriginalName, defaultname);
+ mPrompt.setText(prompt);
+ mPlaylist.setText(defaultname);
+ mPlaylist.setSelection(defaultname.length());
+ mPlaylist.addTextChangedListener(mTextWatcher);
+ setSaveButton();
+ }
+
+ TextWatcher mTextWatcher = new TextWatcher() {
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ // don't care about this one
+ }
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ // check if playlist with current name exists already, and warn the user if so.
+ setSaveButton();
+ };
+ public void afterTextChanged(Editable s) {
+ // don't care about this one
+ }
+ };
+
+ private void setSaveButton() {
+ String typedname = mPlaylist.getText().toString();
+ if (idForplaylist(typedname) >= 0
+ && ! mOriginalName.equals(typedname)) {
+ mSaveButton.setText(R.string.create_playlist_overwrite_text);
+ } else {
+ mSaveButton.setText(R.string.create_playlist_create_text);
+ }
+ }
+
+ private int idForplaylist(String name) {
+ Cursor c = MusicUtils.query(this, MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+ new String[] { MediaStore.Audio.Playlists._ID },
+ MediaStore.Audio.Playlists.NAME + "=?",
+ new String[] { name },
+ MediaStore.Audio.Playlists.NAME);
+ int id = -1;
+ if (c != null) {
+ c.moveToFirst();
+ if (!c.isAfterLast()) {
+ id = c.getInt(0);
+ }
+ }
+ c.close();
+ return id;
+ }
+
+ private String nameForId(long id) {
+ Cursor c = MusicUtils.query(this, MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+ new String[] { MediaStore.Audio.Playlists.NAME },
+ MediaStore.Audio.Playlists._ID + "=?",
+ new String[] { Long.valueOf(id).toString() },
+ MediaStore.Audio.Playlists.NAME);
+ String name = null;
+ if (c != null) {
+ c.moveToFirst();
+ if (!c.isAfterLast()) {
+ name = c.getString(0);
+ }
+ }
+ c.close();
+ return name;
+ }
+
+
+ @Override
+ public void onSaveInstanceState(Bundle outcicle) {
+ outcicle.putString("defaultname", mPlaylist.getText().toString());
+ outcicle.putLong("rename", mRenameId);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ }
+
+ private View.OnClickListener mOpenClicked = new View.OnClickListener() {
+ public void onClick(View v) {
+ String name = mPlaylist.getText().toString();
+ if (name != null && name.length() > 0) {
+ ContentResolver resolver = getContentResolver();
+ ContentValues values = new ContentValues(1);
+ values.put(MediaStore.Audio.Playlists.NAME, name);
+ resolver.update(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+ values,
+ MediaStore.Audio.Playlists._ID + "=?",
+ new String[] { Long.valueOf(mRenameId).toString()});
+
+ setResult(RESULT_OK);
+ Toast.makeText(RenamePlaylist.this, R.string.playlist_renamed_message, Toast.LENGTH_SHORT).show();
+ finish();
+ }
+ }
+ };
+}
--- /dev/null
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.content.Context;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.ImageButton;
+
+/**
+ * A button that will repeatedly call a 'listener' method
+ * as long as the button is pressed.
+ */
+public class RepeatingImageButton extends ImageButton {
+
+ private long mStartTime;
+ private int mRepeatCount;
+ private RepeatListener mListener;
+ private long mInterval = 500;
+
+ public RepeatingImageButton(Context context) {
+ this(context, null);
+ }
+
+ public RepeatingImageButton(Context context, AttributeSet attrs) {
+ this(context, attrs, android.R.attr.imageButtonStyle);
+ }
+
+ public RepeatingImageButton(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ setFocusable(true);
+ setLongClickable(true);
+ }
+
+ /**
+ * Sets the listener to be called while the button is pressed and
+ * the interval in milliseconds with which it will be called.
+ * @param l The listener that will be called
+ * @param interval The interval in milliseconds for calls
+ */
+ public void setRepeatListener(RepeatListener l, long interval) {
+ mListener = l;
+ mInterval = interval;
+ }
+
+ @Override
+ public boolean performLongClick() {
+ mStartTime = SystemClock.elapsedRealtime();
+ mRepeatCount = 0;
+ post(mRepeater);
+ return true;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ // remove the repeater, but call the hook one more time
+ removeCallbacks(mRepeater);
+ if (mStartTime != 0) {
+ doRepeat(true);
+ mStartTime = 0;
+ }
+ }
+ return super.onTouchEvent(event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ case KeyEvent.KEYCODE_ENTER:
+ // remove the repeater, but call the hook one more time
+ removeCallbacks(mRepeater);
+ if (mStartTime != 0) {
+ doRepeat(true);
+ mStartTime = 0;
+ }
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ private Runnable mRepeater = new Runnable() {
+ public void run() {
+ doRepeat(false);
+ if (isPressed()) {
+ postDelayed(this, mInterval);
+ }
+ }
+ };
+
+ private void doRepeat(boolean last) {
+ long now = SystemClock.elapsedRealtime();
+ if (mListener != null) {
+ mListener.onRepeat(this, now - mStartTime, last ? -1 : mRepeatCount++);
+ }
+ }
+
+ public interface RepeatListener {
+ /**
+ * This method will be called repeatedly at roughly the interval
+ * specified in setRepeatListener(), for as long as the button
+ * is pressed.
+ * @param v The button as a View.
+ * @param duration The number of milliseconds the button has been pressed so far.
+ * @param repeatcount The number of previous calls in this sequence.
+ * If this is going to be the last call in this sequence (i.e. the user
+ * just stopped pressing the button), the value will be -1.
+ */
+ void onRepeat(View v, long duration, int repeatcount);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.app.Activity;
+import android.database.Cursor;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.MediaStore;
+import android.view.Window;
+import android.view.WindowManager;
+
+public class ScanningProgress extends Activity
+{
+ private final static int CHECK = 0;
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg)
+ {
+ if (msg.what == CHECK) {
+ String status = Environment.getExternalStorageState();
+ if (!status.equals(Environment.MEDIA_MOUNTED)) {
+ // If the card suddenly got unmounted again, there's
+ // really no need to keep waiting for the media scanner.
+ finish();
+ return;
+ }
+ Cursor c = MusicUtils.query(ScanningProgress.this,
+ MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+ null, null, null, null);
+ if (c != null) {
+ // The external media database is now ready for querying
+ // (though it may still be in the process of being filled).
+ c.close();
+ setResult(RESULT_OK);
+ finish();
+ return;
+ }
+ Message next = obtainMessage(CHECK);
+ sendMessageDelayed(next, 3000);
+ }
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(R.layout.scanning);
+ getWindow().setLayout(WindowManager.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.WRAP_CONTENT);
+ setResult(RESULT_CANCELED);
+
+ Message msg = mHandler.obtainMessage(CHECK);
+ mHandler.sendMessageDelayed(msg, 1000);
+ }
+
+ @Override
+ public void onDestroy() {
+ mHandler.removeMessages(CHECK);
+ super.onDestroy();
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.Window;
+import android.widget.TextView;
+
+public class StreamStarter extends Activity
+{
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(R.layout.streamstarter);
+
+ TextView tv = (TextView) findViewById(R.id.streamloading);
+
+ Uri uri = getIntent().getData();
+ String msg = getString(R.string.streamloadingtext, uri.getHost());
+ tv.setText(msg);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ MusicUtils.bindToService(this, new ServiceConnection() {
+ public void onServiceConnected(ComponentName classname, IBinder obj) {
+ try {
+ IntentFilter f = new IntentFilter();
+ f.addAction(MediaPlaybackService.ASYNC_OPEN_COMPLETE);
+ registerReceiver(mStatusListener, new IntentFilter(f));
+ MusicUtils.sService.openfileAsync(getIntent().getData().toString());
+ } catch (RemoteException ex) {
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName classname) {
+ }
+ });
+ }
+
+ private BroadcastReceiver mStatusListener = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ try {
+ MusicUtils.sService.play();
+ intent = new Intent("com.android.music.PLAYBACK_VIEWER");
+ intent.putExtra("oneshot", true);
+ startActivity(intent);
+ } catch (RemoteException ex) {
+ }
+ finish();
+ }
+ };
+
+ @Override
+ public void onPause() {
+ if (MusicUtils.sService != null) {
+ try {
+ // This looks a little weird (when it's not playing, stop playing)
+ // but it is correct. When nothing is playing, it means that this
+ // was paused before a connection was established, in which case
+ // we stop trying to connect/play.
+ // Otherwise, this call to onPause() was a result of the call to
+ // finish() above, and we should let playback continue.
+ if (! MusicUtils.sService.isPlaying()) {
+ MusicUtils.sService.stop();
+ }
+ } catch (RemoteException ex) {
+ }
+ }
+ unregisterReceiver(mStatusListener);
+ MusicUtils.unbindFromService(this);
+ super.onPause();
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.GestureDetector.SimpleOnGestureListener;
+import android.widget.AdapterView;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+public class TouchInterceptor extends ListView {
+
+ private View mDragView;
+ private WindowManager mWindowManager;
+ private WindowManager.LayoutParams mWindowParams;
+ private int mDragPos; // which item is being dragged
+ private int mFirstDragPos; // where was the dragged item originally
+ private int mDragPoint; // at what offset inset the item did the user grab it
+ private int mCoordOffset; // the difference between screen coordinates and coordinates in this view
+ private DragListener mDragListener;
+ private DropListener mDropListener;
+ private RemoveListener mRemoveListener;
+ private int mUpperBound;
+ private int mLowerBound;
+ private int mHeight;
+ private int mTouchSlop;
+ private int mBackGroundColor;
+ private GestureDetector mGestureDetector;
+ private static final int FLING = 0;
+ private static final int SLIDE = 1;
+ private int mRemoveMode = -1;
+ private Rect mTempRect = new Rect();
+
+ public TouchInterceptor(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ SharedPreferences pref = context.getSharedPreferences("Music", 3);
+ mRemoveMode = pref.getInt("deletemode", -1);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (mRemoveListener != null && mGestureDetector == null) {
+ if (mRemoveMode == FLING) {
+ mGestureDetector = new GestureDetector(new SimpleOnGestureListener() {
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+ float velocityY) {
+ if (mDragView != null) {
+ if (velocityX > 1000) {
+ Rect r = mTempRect;
+ mDragView.getDrawingRect(r);
+ if ( e2.getX() > r.right * 2 / 3) {
+ // fast fling right with release near the right edge of the screen
+ stopDragging();
+ mRemoveListener.remove(mFirstDragPos);
+ unExpandViews(true);
+ }
+ }
+ // flinging while dragging should have no effect
+ return true;
+ }
+ return false;
+ }
+ });
+ }
+ }
+ if (mDragListener != null || mDropListener != null) {
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ int x = (int) ev.getX();
+ int y = (int) ev.getY();
+ int itemnum = pointToPosition(x, y);
+ if (itemnum == AdapterView.INVALID_POSITION) {
+ break;
+ }
+ ViewGroup item = (ViewGroup) getChildAt(itemnum - getFirstVisiblePosition());
+ mDragPoint = y - item.getTop();
+ mCoordOffset = ((int)ev.getRawY()) - y;
+ View dragger = item.findViewById(R.id.icon);
+ Rect r = mTempRect;
+ dragger.getDrawingRect(r);
+ if (x < r.right) {
+ item.setDrawingCacheEnabled(true);
+ Bitmap bitmap = item.getDrawingCache();
+ startDragging(bitmap, y);
+ mDragPos = itemnum;
+ mFirstDragPos = mDragPos;
+ mHeight = getHeight();
+ mTouchSlop = ViewConfiguration.getTouchSlop();
+ mUpperBound = Math.min(y - mTouchSlop, mHeight / 3);
+ mLowerBound = Math.max(y + mTouchSlop, mHeight * 2 /3);
+ return false;
+ }
+ mDragView = null;
+ break;
+ }
+ }
+ return super.onInterceptTouchEvent(ev);
+ }
+
+ /*
+ * pointToPosition() doesn't consider invisible views, but we
+ * need to, so implement a slightly different version.
+ */
+ private int myPointToPosition(int x, int y) {
+ Rect frame = mTempRect;
+ final int count = getChildCount();
+ for (int i = count - 1; i >= 0; i--) {
+ final View child = getChildAt(i);
+ child.getHitRect(frame);
+ if (frame.contains(x, y)) {
+ return getFirstVisiblePosition() + i;
+ }
+ }
+ return INVALID_POSITION;
+ }
+
+ private int getItemForPosition(int y) {
+ int pos = myPointToPosition(0, y - mDragPoint - 32);
+ if (pos >= 0) {
+ if (pos <= mFirstDragPos) {
+ pos += 1;
+ }
+ } else if ((y - mDragPoint) < 0) {
+ pos = 0;
+ }
+ return pos;
+ }
+
+ private void adjustScrollBounds(int y) {
+ if (y >= mHeight / 3) {
+ mUpperBound = mHeight / 3;
+ }
+ if (y <= mHeight * 2 / 3) {
+ mLowerBound = mHeight * 2 / 3;
+ }
+ }
+
+ /*
+ * Restore size and visibility for all listitems
+ */
+ private void unExpandViews(boolean deletion) {
+ for (int i = 0;; i++) {
+ View v = getChildAt(i);
+ if (v == null) {
+ if (deletion) {
+ // HACK force update of mItemCount
+ int position = getFirstVisiblePosition();
+ int y = getChildAt(0).getTop();
+ setAdapter(getAdapter());
+ setSelectionFromTop(position, y);
+ // end hack
+ }
+ layoutChildren(); // force children to be recreated where needed
+ v = getChildAt(i);
+ if (v == null) {
+ break;
+ }
+ }
+ ViewGroup.LayoutParams params = v.getLayoutParams();
+ params.height = 64;
+ v.setLayoutParams(params);
+ v.setVisibility(View.VISIBLE);
+ }
+ }
+
+ /* Adjust visibility and size to make it appear as though
+ * an item is being dragged around and other items are making
+ * room for it:
+ * If dropping the item would result in it still being in the
+ * same place, then make the dragged listitem's size normal,
+ * but make the item invisible.
+ * Otherwise, if the dragged listitem is still on screen, make
+ * it as small as possible and expand the item below the insert
+ * point.
+ * If the dragged item is not on screen, only expand the item
+ * below the current insertpoint.
+ */
+ private void doExpansion() {
+ int childnum = mDragPos - getFirstVisiblePosition();
+ if (mDragPos > mFirstDragPos) {
+ childnum++;
+ }
+ View v = getChildAt(childnum);
+ if (v== null) {
+ return;
+ }
+ View first = getChildAt(mFirstDragPos - getFirstVisiblePosition());
+
+ for (int i = 0;; i++) {
+ View vv = getChildAt(i);
+ if (vv == null) {
+ break;
+ }
+ int height = 64;
+ int visibility = View.VISIBLE;
+ if (vv.equals(first)) {
+ // processing the item that is being dragged
+ if (mDragPos == mFirstDragPos) {
+ // hovering over the original location
+ visibility = View.INVISIBLE;
+ } else {
+ // not hovering over it
+ height = 1;
+ }
+ } else if (i == (mDragPos - getFirstVisiblePosition() + (mDragPos > mFirstDragPos ? 1 : 0))) {
+ height = 128;
+ }
+ ViewGroup.LayoutParams params = vv.getLayoutParams();
+ params.height = height;
+ vv.setLayoutParams(params);
+ vv.setVisibility(visibility);
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (mGestureDetector != null) {
+ mGestureDetector.onTouchEvent(ev);
+ }
+ if ((mDragListener != null || mDropListener != null) && mDragView != null) {
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ Rect r = mTempRect;
+ mDragView.getDrawingRect(r);
+ stopDragging();
+ if (mRemoveMode == SLIDE && ev.getX() > r.right * 3 / 4) {
+ if (mRemoveListener != null) {
+ mRemoveListener.remove(mFirstDragPos);
+ }
+ unExpandViews(true);
+ } else {
+ if (mDropListener != null) {
+ mDropListener.drop(mFirstDragPos, mDragPos);
+ }
+ unExpandViews(false);
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ int x = (int) ev.getX();
+ int y = (int) ev.getY();
+ dragView(x, y);
+ int itemnum = getItemForPosition(y);
+ if (itemnum >= 0) {
+ if (true || itemnum != mDragPos) {
+ if (mDragListener != null) {
+ mDragListener.drag(mDragPos, itemnum);
+ }
+ mDragPos = itemnum;
+ doExpansion();
+ }
+ int speed = 0;
+ adjustScrollBounds(y);
+ if (y > mLowerBound) {
+ // scroll the list up a bit
+ speed = y > (mHeight + mLowerBound) / 2 ? 16 : 4;
+ } else if (y < mUpperBound) {
+ // scroll the list down a bit
+ speed = y < mUpperBound / 2 ? -16 : -4;
+ }
+ if (speed != 0) {
+ int ref = pointToPosition(0, mHeight / 2);
+ if (ref == AdapterView.INVALID_POSITION) {
+ //we hit a divider or an invisible view, check somewhere else
+ ref = pointToPosition(0, mHeight / 2 + getDividerHeight() + 64);
+ }
+ View v = getChildAt(ref - getFirstVisiblePosition());
+ if (v!= null) {
+ int pos = v.getTop();
+ setSelectionFromTop(ref, pos - speed);
+ }
+ }
+ }
+ break;
+ }
+ return true;
+ }
+ return super.onTouchEvent(ev);
+ }
+
+ private void startDragging(Bitmap bm, int y) {
+ mWindowParams = new WindowManager.LayoutParams();
+ mWindowParams.gravity = Gravity.TOP;
+ mWindowParams.x = 0;
+ mWindowParams.y = y - mDragPoint + mCoordOffset;
+
+ mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
+ mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+ mWindowParams.format = PixelFormat.TRANSLUCENT;
+ mWindowParams.windowAnimations = 0;
+
+ ImageView v = new ImageView(mContext);
+ mBackGroundColor = mContext.getResources().getColor(R.color.dragndrop_background);
+ v.setBackgroundColor(mBackGroundColor);
+ v.setImageBitmap(bm);
+ mWindowManager = (WindowManager)mContext.getSystemService("window");
+ mWindowManager.addView(v, mWindowParams);
+ mDragView = v;
+ }
+
+ private void dragView(int x, int y) {
+ if (mRemoveMode == SLIDE) {
+ float alpha = 1.0f;
+ int width = mDragView.getWidth();
+ if (x > width / 2) {
+ alpha = ((float)(width - x)) / (width / 2);
+ }
+ mWindowParams.alpha = alpha;
+ }
+ mWindowParams.y = y - mDragPoint + mCoordOffset;
+ mWindowManager.updateViewLayout(mDragView, mWindowParams);
+ }
+
+ private void stopDragging() {
+ WindowManager wm = (WindowManager)mContext.getSystemService("window");
+ wm.removeView(mDragView);
+ mDragView = null;
+ }
+
+ public void setDragListener(DragListener l) {
+ mDragListener = l;
+ }
+
+ public void setDropListener(DropListener l) {
+ mDropListener = l;
+ }
+
+ public void setRemoveListener(RemoveListener l) {
+ mRemoveListener = l;
+ }
+
+ public interface DragListener {
+ void drag(int from, int to);
+ }
+ public interface DropListener {
+ void drop(int from, int to);
+ }
+ public interface RemoveListener {
+ void remove(int which);
+ }
+};
--- /dev/null
+/*
+ * 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.*;
+import android.database.AbstractCursor;
+import android.database.CharArrayBuffer;
+import android.database.Cursor;
+import android.media.AudioManager;
+import android.media.MediaFile;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.RemoteException;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Audio.Playlists;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.SubMenu;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.text.Collator;
+import java.util.Arrays;
+import java.util.Map;
+
+public class TrackBrowserActivity extends ListActivity
+ implements View.OnCreateContextMenuListener, MusicUtils.Defs
+{
+ private final int Q_SELECTED = CHILD_MENU_BASE;
+ private final int Q_ALL = CHILD_MENU_BASE + 1;
+ private final int SAVE_AS_PLAYLIST = CHILD_MENU_BASE + 2;
+ private final int PLAY_ALL = CHILD_MENU_BASE + 3;
+ private final int CLEAR_PLAYLIST = CHILD_MENU_BASE + 4;
+ private final int REMOVE = CHILD_MENU_BASE + 5;
+
+ private final int PLAY_NOW = 0;
+ private final int ADD_TO_QUEUE = 1;
+ private final int PLAY_NEXT = 2;
+ private final int JUMP_TO = 3;
+ private final int CLEAR_HISTORY = 4;
+ private final int CLEAR_ALL = 5;
+
+ private static final String LOGTAG = "TrackBrowser";
+
+ private String[] mCursorCols;
+ private String[] mPlaylistMemberCols;
+ private boolean mDeletedOneRow = false;
+ private boolean mEditMode = false;
+ private String mCurrentTrackName;
+
+ public TrackBrowserActivity()
+ {
+ }
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle icicle)
+ {
+ super.onCreate(icicle);
+ requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+ if (icicle != null) {
+ mSelectedId = icicle.getLong("selectedtrack");
+ mAlbumId = icicle.getString("album");
+ mArtistId = icicle.getString("artist");
+ mPlaylist = icicle.getString("playlist");
+ mGenre = icicle.getString("genre");
+ mEditMode = icicle.getBoolean("editmode", false);
+ } else {
+ mAlbumId = getIntent().getStringExtra("album");
+ // If we have an album, show everything on the album, not just stuff
+ // by a particular artist.
+ Intent intent = getIntent();
+ mArtistId = intent.getStringExtra("artist");
+ mPlaylist = intent.getStringExtra("playlist");
+ mGenre = intent.getStringExtra("genre");
+ mEditMode = intent.getAction().equals(Intent.ACTION_EDIT);
+ }
+
+ mCursorCols = new String[] {
+ MediaStore.Audio.Media._ID,
+ MediaStore.Audio.Media.TITLE,
+ MediaStore.Audio.Media.TITLE_KEY,
+ MediaStore.Audio.Media.DATA,
+ MediaStore.Audio.Media.ALBUM,
+ MediaStore.Audio.Media.ARTIST,
+ MediaStore.Audio.Media.ARTIST_ID,
+ MediaStore.Audio.Media.DURATION
+ };
+ mPlaylistMemberCols = new String[] {
+ MediaStore.Audio.Playlists.Members._ID,
+ MediaStore.Audio.Media.TITLE,
+ MediaStore.Audio.Media.TITLE_KEY,
+ MediaStore.Audio.Media.DATA,
+ MediaStore.Audio.Media.ALBUM,
+ MediaStore.Audio.Media.ARTIST,
+ MediaStore.Audio.Media.ARTIST_ID,
+ MediaStore.Audio.Media.DURATION,
+ MediaStore.Audio.Playlists.Members.PLAY_ORDER,
+ MediaStore.Audio.Playlists.Members.AUDIO_ID
+ };
+
+ MusicUtils.bindToService(this);
+
+ IntentFilter f = new IntentFilter();
+ f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
+ f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
+ f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
+ f.addDataScheme("file");
+ registerReceiver(mScanListener, f);
+
+ init();
+ //Debug.startMethodTracing();
+ }
+
+ @Override
+ public void onDestroy() {
+ //Debug.stopMethodTracing();
+ MusicUtils.unbindFromService(this);
+ try {
+ if ("nowplaying".equals(mPlaylist)) {
+ unregisterReceiver(mNowPlayingListener);
+ } else {
+ unregisterReceiver(mTrackListListener);
+ }
+ } catch (IllegalArgumentException ex) {
+ // we end up here in case we never registered the listeners
+ }
+ if (mTrackCursor != null) {
+ mTrackCursor.close();
+ }
+ unregisterReceiver(mScanListener);
+ super.onDestroy();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (mTrackCursor != null) {
+ getListView().invalidateViews();
+ }
+ MusicUtils.setSpinnerState(this);
+ }
+ @Override
+ public void onPause() {
+ mReScanHandler.removeCallbacksAndMessages(null);
+ super.onPause();
+ }
+ private BroadcastReceiver mScanListener = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ MusicUtils.setSpinnerState(TrackBrowserActivity.this);
+ mReScanHandler.sendEmptyMessage(0);
+ }
+ };
+
+ private Handler mReScanHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ init();
+ if (mTrackCursor == null) {
+ sendEmptyMessageDelayed(0, 1000);
+ }
+ }
+ };
+
+ public void onSaveInstanceState(Bundle outcicle) {
+ // need to store the selected item so we don't lose it in case
+ // of an orientation switch. Otherwise we could lose it while
+ // in the middle of specifying a playlist to add the item to.
+ outcicle.putLong("selectedtrack", mSelectedId);
+ outcicle.putString("artist", mArtistId);
+ outcicle.putString("album", mAlbumId);
+ outcicle.putString("playlist", mPlaylist);
+ outcicle.putString("genre", mGenre);
+ outcicle.putBoolean("editmode", mEditMode);
+ super.onSaveInstanceState(outcicle);
+ }
+
+ public void init() {
+
+ mTrackCursor = getTrackCursor(null);
+
+ setContentView(R.layout.media_picker_activity);
+ mTrackList = (ListView) findViewById(android.R.id.list);
+ if (mEditMode) {
+ //((TouchInterceptor) mTrackList).setDragListener(mDragListener);
+ ((TouchInterceptor) mTrackList).setDropListener(mDropListener);
+ ((TouchInterceptor) mTrackList).setRemoveListener(mRemoveListener);
+ }
+
+ if (mTrackCursor == null) {
+ MusicUtils.displayDatabaseError(this);
+ return;
+ }
+
+ int numresults = mTrackCursor.getCount();
+ if (numresults > 0) {
+ mTrackCursor.moveToFirst();
+
+ CharSequence fancyName = null;
+ if (mAlbumId != null) {
+ fancyName = mTrackCursor.getString(mTrackCursor.getColumnIndex(MediaStore.Audio.Media.ALBUM));
+ // 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.getString(mTrackCursor.getColumnIndex(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(mTrackCursor.getColumnIndex(MediaStore.Audio.Media.ALBUM));
+ }
+ cursor.deactivate();
+ }
+ } 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 {
+ 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);
+ }
+ } else {
+ setTitle(R.string.no_tracks_title);
+ }
+
+ TrackListAdapter adapter = new TrackListAdapter(
+ this,
+ mEditMode ? R.layout.edit_track_list_item : R.layout.track_list_item,
+ mTrackCursor,
+ new String[] {},
+ new int[] {},
+ "nowplaying".equals(mPlaylist));
+
+ setListAdapter(adapter);
+ ListView lv = getListView();
+ lv.setOnCreateContextMenuListener(this);
+ lv.setCacheColorHint(0);
+ if (!mEditMode) {
+ lv.setTextFilterEnabled(true);
+ }
+
+ // 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.getColumnIndex(MediaStore.Audio.Media.ARTIST_ID);
+ mTrackCursor.moveToFirst();
+ while (! mTrackCursor.isAfterLast()) {
+ String artist = mTrackCursor.getString(keyidx);
+ if (artist.equals(key)) {
+ setSelection(mTrackCursor.getPosition());
+ break;
+ }
+ mTrackCursor.moveToNext();
+ }
+ }
+ registerReceiver(mTrackListListener, new IntentFilter(f));
+ mTrackListListener.onReceive(this, new Intent(MediaPlaybackService.META_CHANGED));
+ }
+ }
+
+ private TouchInterceptor.DragListener mDragListener =
+ new TouchInterceptor.DragListener() {
+ public void drag(int from, int to) {
+ if (mTrackCursor instanceof NowPlayingCursor) {
+ NowPlayingCursor c = (NowPlayingCursor) mTrackCursor;
+ c.moveItem(from, to);
+ ((TrackListAdapter)getListAdapter()).notifyDataSetChanged();
+ getListView().invalidateViews();
+ mDeletedOneRow = true;
+ }
+ }
+ };
+ private TouchInterceptor.DropListener mDropListener =
+ new TouchInterceptor.DropListener() {
+ public void drop(int from, int to) {
+ if (mTrackCursor instanceof NowPlayingCursor) {
+ // update the currently playing list
+ NowPlayingCursor c = (NowPlayingCursor) mTrackCursor;
+ c.moveItem(from, to);
+ ((TrackListAdapter)getListAdapter()).notifyDataSetChanged();
+ getListView().invalidateViews();
+ mDeletedOneRow = true;
+ } else {
+ // update a saved playlist
+ int colidx = mTrackCursor.getColumnIndex(MediaStore.Audio.Playlists.Members.PLAY_ORDER);
+ if (from < to) {
+ // move the item to somewhere later in the list
+ mTrackCursor.moveToPosition(to);
+ int toidx = mTrackCursor.getInt(colidx);
+ mTrackCursor.moveToPosition(from);
+ mTrackCursor.updateInt(colidx, toidx);
+ for (int i = from + 1; i <= to; i++) {
+ mTrackCursor.moveToPosition(i);
+ mTrackCursor.updateInt(colidx, i - 1);
+ }
+ mTrackCursor.commitUpdates();
+ } else if (from > to) {
+ // move the item to somewhere earlier in the list
+ mTrackCursor.moveToPosition(to);
+ int toidx = mTrackCursor.getInt(colidx);
+ mTrackCursor.moveToPosition(from);
+ mTrackCursor.updateInt(colidx, toidx);
+ for (int i = from - 1; i >= to; i--) {
+ mTrackCursor.moveToPosition(i);
+ mTrackCursor.updateInt(colidx, i + 1);
+ }
+ mTrackCursor.commitUpdates();
+ }
+ }
+ }
+ };
+
+ private TouchInterceptor.RemoveListener mRemoveListener =
+ new TouchInterceptor.RemoveListener() {
+ public void remove(int which) {
+ removePlaylistItem(which);
+ }
+ };
+
+ private void removePlaylistItem(int which) {
+ View v = mTrackList.getChildAt(which - mTrackList.getFirstVisiblePosition());
+ try {
+ if (MusicUtils.sService != null
+ && which != MusicUtils.sService.getQueuePosition()) {
+ mDeletedOneRow = true;
+ }
+ } catch (RemoteException e) {
+ // Service died, so nothing playing.
+ mDeletedOneRow = true;
+ }
+ v.setVisibility(View.GONE);
+ mTrackList.invalidateViews();
+ mTrackCursor.moveToPosition(which);
+ mTrackCursor.deleteRow();
+ mTrackCursor.commitUpdates();
+ v.setVisibility(View.VISIBLE);
+ mTrackList.invalidateViews();
+ }
+
+ private BroadcastReceiver mTrackListListener = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ getListView().invalidateViews();
+ }
+ };
+
+ private BroadcastReceiver mNowPlayingListener = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(MediaPlaybackService.META_CHANGED)) {
+ getListView().invalidateViews();
+ } else if (intent.getAction().equals(MediaPlaybackService.QUEUE_CHANGED)) {
+ if (mDeletedOneRow) {
+ // This is the notification for a single row that was
+ // deleted previously, which is already reflected in
+ // the UI.
+ mDeletedOneRow = false;
+ return;
+ }
+ mTrackCursor.close();
+ mTrackCursor = new NowPlayingCursor(MusicUtils.sService, mCursorCols);
+ if (mTrackCursor.getCount() == 0) {
+ finish();
+ return;
+ }
+ ((TrackListAdapter)getListAdapter()).changeCursor(mTrackCursor);
+ getListView().invalidateViews();
+ }
+ }
+ };
+
+ 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);
+ int id_idx = mTrackCursor.getColumnIndex(MediaStore.Audio.Playlists.Members.AUDIO_ID);
+ if (id_idx < 0 ) {
+ mSelectedId = mi.id;
+ } else {
+ mSelectedId = mTrackCursor.getInt(id_idx);
+ }
+ mCurrentTrackName = mTrackCursor.getString(mTrackCursor.getColumnIndex(MediaStore.Audio.Media.TITLE));
+ menu.setHeaderTitle(mCurrentTrackName);
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case PLAY_SELECTION: {
+ // play the track
+ int position = mSelectedPosition;
+ MusicUtils.playAll(this, mTrackCursor, position);
+ return true;
+ }
+
+ case QUEUE: {
+ int [] list = new int[] { (int) mSelectedId };
+ MusicUtils.addToCurrentPlaylist(this, list);
+ return true;
+ }
+
+ case NEW_PLAYLIST: {
+ Intent intent = new Intent();
+ intent.setClass(this, CreatePlaylist.class);
+ startActivityForResult(intent, NEW_PLAYLIST);
+ return true;
+ }
+
+ case PLAYLIST_SELECTED: {
+ int [] list = new int[] { (int) mSelectedId };
+ int playlist = item.getIntent().getIntExtra("playlist", 0);
+ MusicUtils.addToPlaylist(this, list, playlist);
+ return true;
+ }
+
+ case USE_AS_RINGTONE:
+ // Set the system setting to make this the current ringtone
+ MusicUtils.setRingtone(this, mSelectedId);
+ return true;
+
+ case DELETE_ITEM: {
+ int [] list = new int[1];
+ list[0] = (int) mSelectedId;
+ Bundle b = new Bundle();
+ b.putString("description", mCurrentTrackName);
+ b.putIntArray("items", list);
+ Intent intent = new Intent();
+ intent.setClass(this, DeleteItems.class);
+ intent.putExtras(b);
+ startActivityForResult(intent, -1);
+ return true;
+ }
+
+ case REMOVE:
+ removePlaylistItem(mSelectedPosition);
+ return true;
+ }
+ return super.onContextItemSelected(item);
+ }
+
+ // In order to use alt-up/down as a shortcut for moving the selected item
+ // in the list, we need to override dispatchKeyEvent, not onKeyDown.
+ // (onKeyDown never sees these events, since they are handled by the list)
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (mPlaylist != null && event.getMetaState() != 0 && event.isDown()) {
+ 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();
+ mTrackCursor.moveToPosition(curpos);
+ mTrackCursor.deleteRow();
+ mTrackCursor.commitUpdates();
+ v.setVisibility(View.VISIBLE);
+ mTrackList.invalidateViews();
+ } else {
+ // remove track from playlist
+ mTrackCursor.moveToPosition(curpos);
+ mTrackCursor.deleteRow();
+ mTrackCursor.commitUpdates();
+ 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.getColumnIndex(MediaStore.Audio.Playlists.Members.PLAY_ORDER);
+ mTrackCursor.moveToPosition(curpos);
+ int currentplayidx = mTrackCursor.getInt(colidx);
+ if (up) {
+ mTrackCursor.updateInt(colidx, currentplayidx - 1);
+ mTrackCursor.moveToPrevious();
+ } else {
+ mTrackCursor.updateInt(colidx, currentplayidx + 1);
+ mTrackCursor.moveToNext();
+ }
+ mTrackCursor.updateInt(colidx, currentplayidx);
+ mTrackCursor.commitUpdates();
+ }
+ }
+
+ @Override
+ protected void onListItemClick(ListView l, View v, int position, long id)
+ {
+ if (mTrackCursor.getCount() == 0) {
+ return;
+ }
+ MusicUtils.playAll(this, mTrackCursor, position);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ /* This activity is used for a number of different browsing modes, and the menu can
+ * be different for each of them:
+ * - all tracks, optionally restricted to an album, artist or playlist
+ * - the list of currently playing songs
+ */
+ super.onCreateOptionsMenu(menu);
+ if (mPlaylist == null) {
+ menu.add(0, PLAY_ALL, 0, R.string.play_all).setIcon(R.drawable.ic_menu_play_clip);
+ }
+ menu.add(0, GOTO_START, 0, R.string.goto_start).setIcon(R.drawable.ic_menu_music_library);
+ menu.add(0, GOTO_PLAYBACK, 0, R.string.goto_playback).setIcon(R.drawable.ic_menu_playback)
+ .setVisible(MusicUtils.isMusicLoaded());
+ menu.add(0, SHUFFLE_ALL, 0, R.string.shuffle_all).setIcon(R.drawable.ic_menu_shuffle);
+ if (mPlaylist != null) {
+ menu.add(0, SAVE_AS_PLAYLIST, 0, R.string.save_as_playlist).setIcon(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 onOptionsItemSelected(MenuItem item) {
+ Intent intent;
+ Cursor cursor;
+ switch (item.getItemId()) {
+ case PLAY_ALL: {
+ MusicUtils.playAll(this, mTrackCursor);
+ return true;
+ }
+
+ case GOTO_START:
+ intent = new Intent();
+ intent.setClass(this, MusicBrowserActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ return true;
+
+ case GOTO_PLAYBACK:
+ intent = new Intent("com.android.music.PLAYBACK_VIEWER");
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ return true;
+
+ case SHUFFLE_ALL:
+ // Should 'shuffle all' shuffle ALL, or only the tracks shown?
+ cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+ new String [] { MediaStore.Audio.Media._ID},
+ MediaStore.Audio.Media.IS_MUSIC + "=1", null,
+ MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
+ if (cursor != null) {
+ MusicUtils.shuffleAll(this, cursor);
+ cursor.close();
+ }
+ return true;
+
+ case SAVE_AS_PLAYLIST:
+ intent = new Intent();
+ intent.setClass(this, CreatePlaylist.class);
+ startActivityForResult(intent, SAVE_AS_PLAYLIST);
+ return true;
+
+ case CLEAR_PLAYLIST:
+ // We only clear the current playlist
+ MusicUtils.clearQueue();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ switch (requestCode) {
+ case SCAN_DONE:
+ if (resultCode == RESULT_CANCELED) {
+ finish();
+ } else {
+ init();
+ }
+ break;
+
+ case NEW_PLAYLIST:
+ if (resultCode == RESULT_OK) {
+ Uri uri = Uri.parse(intent.getAction());
+ if (uri != null) {
+ int [] list = new int[] { (int) mSelectedId };
+ MusicUtils.addToPlaylist(this, list, Integer.valueOf(uri.getLastPathSegment()));
+ }
+ }
+ break;
+
+ case SAVE_AS_PLAYLIST:
+ if (resultCode == RESULT_OK) {
+ Uri uri = Uri.parse(intent.getAction());
+ if (uri != null) {
+ int [] list = MusicUtils.getSongListForCursor(mTrackCursor);
+ int plid = Integer.parseInt(uri.getLastPathSegment());
+ MusicUtils.addToPlaylist(this, list, plid);
+ }
+ }
+ break;
+ }
+ }
+
+ private Cursor getTrackCursor(String filterstring) {
+ Cursor ret = null;
+ mSortOrder = MediaStore.Audio.Media.TITLE_KEY;
+ StringBuilder where = new StringBuilder();
+ where.append(MediaStore.Audio.Media.TITLE + " != ''");
+
+ // Add in the filtering constraints
+ String [] keywords = null;
+ if (filterstring != null) {
+ String [] searchWords = filterstring.split(" ");
+ keywords = new String[searchWords.length];
+ Collator col = Collator.getInstance();
+ col.setStrength(Collator.PRIMARY);
+ for (int i = 0; i < searchWords.length; i++) {
+ keywords[i] = '%' + MediaStore.Audio.keyFor(searchWords[i]) + '%';
+ }
+ for (int i = 0; i < searchWords.length; i++) {
+ where.append(" AND ");
+ where.append(MediaStore.Audio.Media.ARTIST_KEY + "||");
+ where.append(MediaStore.Audio.Media.ALBUM_KEY + "||");
+ where.append(MediaStore.Audio.Media.TITLE_KEY + " LIKE ?");
+ }
+ }
+
+ if (mGenre != null) {
+ mSortOrder = MediaStore.Audio.Genres.Members.DEFAULT_SORT_ORDER;
+ ret = MusicUtils.query(this,
+ MediaStore.Audio.Genres.Members.getContentUri("external", Integer.valueOf(mGenre)),
+ mCursorCols, where.toString(), keywords, mSortOrder);
+ } else if (mPlaylist != null) {
+ if (mPlaylist.equals("nowplaying")) {
+ if (MusicUtils.sService != null) {
+ ret = new NowPlayingCursor(MusicUtils.sService, mCursorCols);
+ if (ret.getCount() == 0) {
+ finish();
+ }
+ } else {
+ // Nothing is playing.
+ }
+ } else {
+ mSortOrder = MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER;
+ ret = MusicUtils.query(this,
+ MediaStore.Audio.Playlists.Members.getContentUri("external", Long.valueOf(mPlaylist)),
+ mPlaylistMemberCols, where.toString(), keywords, mSortOrder);
+ }
+ } else {
+ if (mAlbumId != null) {
+ where.append(" AND " + MediaStore.Audio.Media.ALBUM_ID + "='" + mAlbumId + "'");
+ mSortOrder = MediaStore.Audio.Media.TRACK + ", " + mSortOrder;
+ }
+ where.append(" AND " + MediaStore.Audio.Media.IS_MUSIC + "=1");
+ ret = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+ mCursorCols, where.toString() , keywords, mSortOrder);
+ }
+ return ret;
+ }
+
+ private class NowPlayingCursor extends AbstractCursor
+ {
+ public NowPlayingCursor(IMediaPlaybackService service, String [] cols)
+ {
+ mCols = cols;
+ mService = service;
+ makeNowPlayingCursor();
+ }
+ private void makeNowPlayingCursor() {
+ mCurrentPlaylistCursor = null;
+ try {
+ mNowPlaying = mService.getQueue();
+ } catch (RemoteException ex) {
+ mNowPlaying = new int[0];
+ }
+ mSize = mNowPlaying.length;
+ if (mSize == 0) {
+ return;
+ }
+
+ StringBuilder where = new StringBuilder();
+ where.append(MediaStore.Audio.Media._ID + " IN (");
+ for (int i = 0; i < mSize; i++) {
+ where.append(mNowPlaying[i]);
+ if (i < mSize - 1) {
+ where.append(",");
+ }
+ }
+ where.append(")");
+
+ mCurrentPlaylistCursor = MusicUtils.query(TrackBrowserActivity.this,
+ MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+ mCols, where.toString(), null, MediaStore.Audio.Media._ID);
+
+ if (mCurrentPlaylistCursor == null) {
+ mSize = 0;
+ return;
+ }
+
+ int size = mCurrentPlaylistCursor.getCount();
+ mCursorIdxs = new int[size];
+ mCurrentPlaylistCursor.moveToFirst();
+ int colidx = mCurrentPlaylistCursor.getColumnIndex(MediaStore.Audio.Media._ID);
+ for (int i = 0; i < size; i++) {
+ mCursorIdxs[i] = mCurrentPlaylistCursor.getInt(colidx);
+ mCurrentPlaylistCursor.moveToNext();
+ }
+ mCurrentPlaylistCursor.moveToFirst();
+ mCurPos = -1;
+ }
+
+ @Override
+ public int getCount()
+ {
+ return mSize;
+ }
+
+ @Override
+ public boolean onMove(int oldPosition, int newPosition)
+ {
+ if (oldPosition == newPosition)
+ return true;
+
+ if (mNowPlaying == null || mCursorIdxs == null) {
+ return false;
+ }
+
+ // The cursor doesn't have any duplicates in it, and is not ordered
+ // in queue-order, so we need to figure out where in the cursor we
+ // should be.
+
+ int newid = mNowPlaying[newPosition];
+ int crsridx = Arrays.binarySearch(mCursorIdxs, newid);
+ mCurrentPlaylistCursor.moveToPosition(crsridx);
+ mCurPos = newPosition;
+
+ return true;
+ }
+
+ @Override
+ public boolean deleteRow()
+ {
+ try {
+ if (mService.removeTracks((int)mCurPos, (int)mCurPos) == 0) {
+ return false; // delete failed
+ }
+ int i = (int) mCurPos;
+ 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)
+ {
+ return mCurrentPlaylistCursor.getLong(column);
+ }
+
+ @Override
+ public float getFloat(int column)
+ {
+ return mCurrentPlaylistCursor.getFloat(column);
+ }
+
+ @Override
+ public double getDouble(int column)
+ {
+ return mCurrentPlaylistCursor.getDouble(column);
+ }
+
+ @Override
+ public boolean isNull(int column)
+ {
+ return mCurrentPlaylistCursor.isNull(column);
+ }
+
+ @Override
+ public String[] getColumnNames()
+ {
+ return mCols;
+ }
+
+ @Override
+ public void deactivate()
+ {
+ if (mCurrentPlaylistCursor != null)
+ mCurrentPlaylistCursor.deactivate();
+ }
+
+ @Override
+ public boolean requery()
+ {
+ makeNowPlayingCursor();
+ return true;
+ }
+
+ private String [] mCols;
+ private Cursor mCurrentPlaylistCursor; // updated in onMove
+ private int mSize; // size of the queue
+ private int[] mNowPlaying;
+ private int[] mCursorIdxs;
+ private int mCurPos;
+ private IMediaPlaybackService mService;
+ }
+
+ class TrackListAdapter extends SimpleCursorAdapter {
+ boolean mIsNowPlaying;
+
+ final int mTitleIdx;
+ final int mArtistIdx;
+ final int mAlbumIdx;
+ final int mDurationIdx;
+ int mAudioIdIdx;
+
+ private final StringBuilder mBuilder = new StringBuilder();
+ private final String mUnknownArtist;
+ private final String mUnknownAlbum;
+
+ class ViewHolder {
+ TextView line1;
+ TextView line2;
+ TextView duration;
+ ImageView play_indicator;
+ CharArrayBuffer buffer1;
+ char [] buffer2;
+ }
+
+ TrackListAdapter(Context context, int layout, Cursor cursor, String[] from, int[] to,
+ boolean isnowplaying) {
+ super(context, layout, cursor, from, to);
+ mIsNowPlaying = isnowplaying;
+ mUnknownArtist = context.getString(R.string.unknown_artist_name);
+ mUnknownAlbum = context.getString(R.string.unknown_album_name);
+
+ 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);
+ mAudioIdIdx = cursor.getColumnIndex(MediaStore.Audio.Playlists.Members.AUDIO_ID);
+ if (mAudioIdIdx < 0) {
+ mAudioIdIdx = cursor.getColumnIndex(MediaStore.Audio.Media._ID);
+ }
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ View v = super.newView(context, cursor, parent);
+ ImageView iv = (ImageView) v.findViewById(R.id.icon);
+ if (mEditMode) {
+ iv.setVisibility(View.VISIBLE);
+ iv.setImageResource(R.drawable.ic_mp_move);
+ ViewGroup.LayoutParams p = iv.getLayoutParams();
+ p.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+ p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ } else {
+ iv.setVisibility(View.GONE);
+ }
+
+ ViewHolder vh = new ViewHolder();
+ vh.line1 = (TextView) v.findViewById(R.id.line1);
+ vh.line2 = (TextView) v.findViewById(R.id.line2);
+ vh.duration = (TextView) v.findViewById(R.id.duration);
+ vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator);
+ vh.buffer1 = new CharArrayBuffer(100);
+ vh.buffer2 = new char[200];
+ v.setTag(vh);
+ return v;
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+
+ ViewHolder vh = (ViewHolder) view.getTag();
+
+ cursor.copyStringToBuffer(mTitleIdx, vh.buffer1);
+ vh.line1.setText(vh.buffer1.data, 0, vh.buffer1.sizeCopied);
+
+ int secs = cursor.getInt(mDurationIdx) / 1000;
+ if (secs == 0) {
+ vh.duration.setText("");
+ } else {
+ vh.duration.setText(MusicUtils.makeTimeString(context, secs));
+ }
+
+ final StringBuilder builder = mBuilder;
+ builder.delete(0, builder.length());
+
+ String name = cursor.getString(mAlbumIdx);
+ if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
+ builder.append(mUnknownAlbum);
+ } else {
+ builder.append(name);
+ }
+ builder.append('\n');
+ name = cursor.getString(mArtistIdx);
+ if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
+ builder.append(mUnknownArtist);
+ } else {
+ builder.append(name);
+ }
+ int len = builder.length();
+ if (vh.buffer2.length < len) {
+ vh.buffer2 = new char[len];
+ }
+ builder.getChars(0, len, vh.buffer2, 0);
+ vh.line2.setText(vh.buffer2, 0, len);
+
+ ImageView iv = vh.play_indicator;
+ int id = -1;
+ if (MusicUtils.sService != null) {
+ // TODO: IPC call on each bind??
+ try {
+ if (mIsNowPlaying) {
+ id = MusicUtils.sService.getQueuePosition();
+ } else {
+ id = MusicUtils.sService.getAudioId();
+ }
+ } catch (RemoteException ex) {
+ }
+ }
+ if ( (mIsNowPlaying && cursor.getPosition() == id) ||
+ (!mIsNowPlaying && cursor.getInt(mAudioIdIdx) == id)) {
+ iv.setImageResource(R.drawable.indicator_ic_mp_playing_list);
+ iv.setVisibility(View.VISIBLE);
+ } else {
+ iv.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public void changeCursor(Cursor cursor) {
+ super.changeCursor(cursor);
+ mTrackCursor = cursor;
+ }
+ @Override
+ public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
+ return getTrackCursor(constraint.toString());
+ }
+ }
+
+ private ListView mTrackList;
+ private Cursor mTrackCursor;
+ private String mAlbumId;
+ private String mArtistId;
+ private String mPlaylist;
+ private String mGenre;
+ private String mSortOrder;
+ private int mSelectedPosition;
+ private long mSelectedId;
+}
+
--- /dev/null
+/*
+ * 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);
+
+ mTrackList = (ListView) findViewById(android.R.id.list);
+
+ 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.getColumnIndex(MediaStore.Video.Media.MIME_TYPE));
+ intent.setDataAndType(ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id), type);
+
+ startActivity(intent);
+ }
+
+ private void MakeCursor() {
+ String[] cols = new String[] {
+ MediaStore.Video.Media._ID,
+ MediaStore.Video.Media.TITLE,
+ MediaStore.Video.Media.DATA,
+ MediaStore.Video.Media.MIME_TYPE,
+ MediaStore.Video.Media.ARTIST
+ };
+ ContentResolver resolver = getContentResolver();
+ if (resolver == null) {
+ System.out.println("resolver = null");
+ } else {
+ mSortOrder = MediaStore.Video.Media.TITLE + " COLLATE UNICODE";
+ mWhereClause = MediaStore.Video.Media.TITLE + " != ''";
+ mCursor = resolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
+ cols, mWhereClause , null, mSortOrder);
+ }
+ }
+
+ private ListView mTrackList;
+ private Cursor mCursor;
+ private String mWhereClause;
+ private String mSortOrder;
+}
+
--- /dev/null
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import com.android.internal.widget.VerticalTextSpinner;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+public class WeekSelector extends Activity
+{
+ VerticalTextSpinner mWeeks;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(R.layout.weekpicker);
+ getWindow().setLayout(WindowManager.LayoutParams.FILL_PARENT,
+ WindowManager.LayoutParams.WRAP_CONTENT);
+
+ mWeeks = (VerticalTextSpinner)findViewById(R.id.weeks);
+ mWeeks.setItems(getResources().getStringArray(R.array.weeklist));
+ mWeeks.setWrapAround(false);
+ mWeeks.setScrollInterval(200);
+
+ int def = MusicUtils.getIntPref(this, "numweeks", 2);
+ int pos = icicle != null ? icicle.getInt("numweeks", def - 1) : def - 1;
+ mWeeks.setSelectedPos(pos);
+
+ ((Button) findViewById(R.id.set)).setOnClickListener(mListener);
+ }
+
+ @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();
+ }
+ };
+}