OSDN Git Service

overhaul
authorAndrew <andrew.d.neal@gmail.com>
Sun, 20 May 2012 23:07:41 +0000 (18:07 -0500)
committerAndrew <andrew.d.neal@gmail.com>
Sun, 20 May 2012 23:07:41 +0000 (18:07 -0500)
236 files changed:
.gitignore [new file with mode: 0644]
.project
AndroidManifest.xml
assets/Frame0.png [new file with mode: 0644]
assets/Frame1.png [new file with mode: 0644]
assets/Frame10.png [new file with mode: 0644]
assets/Frame11.png [new file with mode: 0644]
assets/Frame2.png [new file with mode: 0644]
assets/Frame3.png [new file with mode: 0644]
assets/Frame4.png [new file with mode: 0644]
assets/Frame5.png [new file with mode: 0644]
assets/Frame6.png [new file with mode: 0644]
assets/Frame7.png [new file with mode: 0644]
assets/Frame8.png [new file with mode: 0644]
assets/Frame9.png [new file with mode: 0644]
libs/android-query-0.21.7.jar [new file with mode: 0644]
libs/android-support-v4.jar
proguard.cfg [new file with mode: 0644]
project.properties
res/anim/peak_meter_1.xml [new file with mode: 0644]
res/anim/peak_meter_2.xml [new file with mode: 0644]
res/color/tab_text_color.xml [new file with mode: 0644]
res/drawable-hdpi/apollo_holo_dark_next.png [new file with mode: 0644]
res/drawable-hdpi/apollo_holo_dark_notifiation_bar_collapse.png [new file with mode: 0644]
res/drawable-hdpi/apollo_holo_dark_overflow.png [new file with mode: 0644]
res/drawable-hdpi/apollo_holo_dark_pause.png [new file with mode: 0644]
res/drawable-hdpi/apollo_holo_dark_play.png [new file with mode: 0644]
res/drawable-hdpi/apollo_holo_light_favorite_normal.png [new file with mode: 0644]
res/drawable-hdpi/apollo_holo_light_favorite_selected.png [new file with mode: 0644]
res/drawable-hdpi/apollo_holo_light_next.png [new file with mode: 0644]
res/drawable-hdpi/apollo_holo_light_overflow.png [new file with mode: 0644]
res/drawable-hdpi/apollo_holo_light_pause.png [new file with mode: 0644]
res/drawable-hdpi/apollo_holo_light_play.png [new file with mode: 0644]
res/drawable-hdpi/apollo_holo_light_previous.png [new file with mode: 0644]
res/drawable-hdpi/apollo_holo_light_repeat_all.png [new file with mode: 0644]
res/drawable-hdpi/apollo_holo_light_repeat_normal.png [new file with mode: 0644]
res/drawable-hdpi/apollo_holo_light_repeat_one.png [new file with mode: 0644]
res/drawable-hdpi/apollo_holo_light_search.png [new file with mode: 0644]
res/drawable-hdpi/apollo_holo_light_shuffle_normal.png [new file with mode: 0644]
res/drawable-hdpi/apollo_holo_light_shuffle_on.png [new file with mode: 0644]
res/drawable-hdpi/apollo_settings_about.png [new file with mode: 0644]
res/drawable-hdpi/apollo_settings_themes.png [new file with mode: 0644]
res/drawable-hdpi/appwidget_bg.9.png
res/drawable-hdpi/colorstrip_shadow.9.png [new file with mode: 0644]
res/drawable-hdpi/ic_launcher.png [new file with mode: 0644]
res/drawable-hdpi/title_bar_shadow.9.png [new file with mode: 0644]
res/drawable-mdpi/apollo_holo_dark_next.png [new file with mode: 0644]
res/drawable-mdpi/apollo_holo_dark_notifiation_bar_collapse.png [new file with mode: 0644]
res/drawable-mdpi/apollo_holo_dark_overflow.png [new file with mode: 0644]
res/drawable-mdpi/apollo_holo_dark_pause.png [new file with mode: 0644]
res/drawable-mdpi/apollo_holo_dark_play.png [new file with mode: 0644]
res/drawable-mdpi/apollo_holo_light_favorite_normal.png [new file with mode: 0644]
res/drawable-mdpi/apollo_holo_light_favorite_selected.png [new file with mode: 0644]
res/drawable-mdpi/apollo_holo_light_next.png [new file with mode: 0644]
res/drawable-mdpi/apollo_holo_light_overflow.png [new file with mode: 0644]
res/drawable-mdpi/apollo_holo_light_pause.png [new file with mode: 0644]
res/drawable-mdpi/apollo_holo_light_play.png [new file with mode: 0644]
res/drawable-mdpi/apollo_holo_light_previous.png [new file with mode: 0644]
res/drawable-mdpi/apollo_holo_light_repeat_all.png [new file with mode: 0644]
res/drawable-mdpi/apollo_holo_light_repeat_normal.png [new file with mode: 0644]
res/drawable-mdpi/apollo_holo_light_repeat_one.png [new file with mode: 0644]
res/drawable-mdpi/apollo_holo_light_search.png [new file with mode: 0644]
res/drawable-mdpi/apollo_holo_light_shuffle_normal.png [new file with mode: 0644]
res/drawable-mdpi/apollo_holo_light_shuffle_on.png [new file with mode: 0644]
res/drawable-mdpi/apollo_settings_about.png [new file with mode: 0644]
res/drawable-mdpi/apollo_settings_themes.png [new file with mode: 0644]
res/drawable-mdpi/appwidget_bg.9.png
res/drawable-mdpi/ic_launcher.png [new file with mode: 0644]
res/drawable-mdpi/notify_panel_notification_icon_bg.png [new file with mode: 0644]
res/drawable-nodpi/promo.png [new file with mode: 0644]
res/drawable-xhdpi/apollo_holo_dark_next.png [new file with mode: 0644]
res/drawable-xhdpi/apollo_holo_dark_notifiation_bar_collapse.png [new file with mode: 0644]
res/drawable-xhdpi/apollo_holo_dark_overflow.png [new file with mode: 0644]
res/drawable-xhdpi/apollo_holo_dark_pause.png [new file with mode: 0644]
res/drawable-xhdpi/apollo_holo_dark_play.png [new file with mode: 0644]
res/drawable-xhdpi/apollo_holo_light_favorite_normal.png [new file with mode: 0644]
res/drawable-xhdpi/apollo_holo_light_favorite_selected.png [new file with mode: 0644]
res/drawable-xhdpi/apollo_holo_light_next.png [new file with mode: 0644]
res/drawable-xhdpi/apollo_holo_light_overflow.png [new file with mode: 0644]
res/drawable-xhdpi/apollo_holo_light_pause.png [new file with mode: 0644]
res/drawable-xhdpi/apollo_holo_light_play.png [new file with mode: 0644]
res/drawable-xhdpi/apollo_holo_light_previous.png [new file with mode: 0644]
res/drawable-xhdpi/apollo_holo_light_repeat_all.png [new file with mode: 0644]
res/drawable-xhdpi/apollo_holo_light_repeat_normal.png [new file with mode: 0644]
res/drawable-xhdpi/apollo_holo_light_repeat_one.png [new file with mode: 0644]
res/drawable-xhdpi/apollo_holo_light_search.png [new file with mode: 0644]
res/drawable-xhdpi/apollo_holo_light_shuffle_normal.png [new file with mode: 0644]
res/drawable-xhdpi/apollo_holo_light_shuffle_on.png [new file with mode: 0644]
res/drawable-xhdpi/apollo_settings_about.png [new file with mode: 0644]
res/drawable-xhdpi/apollo_settings_themes.png [new file with mode: 0644]
res/drawable-xhdpi/appwidget_bg.9.png
res/drawable-xhdpi/dropdown_ic_arrow_normal_holo_light.png [new file with mode: 0644]
res/drawable-xhdpi/ic_launcher.png [new file with mode: 0644]
res/drawable-xhdpi/indicator_playing_peak_meter_1.png [new file with mode: 0644]
res/drawable-xhdpi/indicator_playing_peak_meter_2.png [new file with mode: 0644]
res/drawable-xhdpi/indicator_playing_peak_meter_3.png [new file with mode: 0644]
res/drawable-xhdpi/indicator_playing_peak_meter_4.png [new file with mode: 0644]
res/drawable-xhdpi/indicator_playing_peak_meter_5.png [new file with mode: 0644]
res/drawable-xhdpi/list_section_divider_holo_custom.9.png [new file with mode: 0644]
res/drawable-xhdpi/notify_panel_notification_icon_bg.png [new file with mode: 0644]
res/drawable-xhdpi/queue_thumbnail_bg.9.png [new file with mode: 0644]
res/drawable-xhdpi/recents_thumbnail_bg_press.9.png [new file with mode: 0644]
res/drawable-xhdpi/stat_notify_music.png [new file with mode: 0644]
res/drawable-xhdpi/tab_selected_holo.9.png [new file with mode: 0644]
res/drawable-xhdpi/tab_selected_pressed_focused_holo.9.png [new file with mode: 0644]
res/drawable-xhdpi/tab_selected_pressed_holo.9.png [new file with mode: 0644]
res/drawable-xhdpi/tab_unselected_focused_holo.9.png [new file with mode: 0644]
res/drawable-xhdpi/tab_unselected_holo.9.png [new file with mode: 0644]
res/drawable-xhdpi/tab_unselected_pressed_holo.9.png [new file with mode: 0644]
res/drawable/holo_selector.xml [new file with mode: 0644]
res/drawable/queue_thumbnail_fg.xml [new file with mode: 0644]
res/drawable/tab.xml [new file with mode: 0644]
res/drawable/viewpager_margin.xml [new file with mode: 0644]
res/layout/audio_controls.xml [new file with mode: 0644]
res/layout/audio_player.xml
res/layout/audio_player_browser.xml [new file with mode: 0644]
res/layout/bottom_action_bar.xml [new file with mode: 0644]
res/layout/bottom_action_bar_controls.xml [new file with mode: 0644]
res/layout/colorstrip.xml [new file with mode: 0644]
res/layout/context_menu.xml [new file with mode: 0644]
res/layout/context_menu_header.xml [new file with mode: 0644]
res/layout/custom_action_bar.xml [new file with mode: 0644]
res/layout/empty_view.xml [new file with mode: 0644]
res/layout/fourbyone_app_widget.xml [new file with mode: 0644]
res/layout/fourbytwo_app_widget.xml [new file with mode: 0644]
res/layout/gridview.xml [new file with mode: 0644]
res/layout/gridview_items.xml [new file with mode: 0644]
res/layout/half_and_half.xml [new file with mode: 0644]
res/layout/library_browser.xml [new file with mode: 0644]
res/layout/list_separator.xml [new file with mode: 0644]
res/layout/listview.xml [new file with mode: 0644]
res/layout/listview_items.xml [new file with mode: 0644]
res/layout/onebyone_app_widget.xml [new file with mode: 0644]
res/layout/quick_queue.xml [new file with mode: 0644]
res/layout/quick_queue_items.xml [new file with mode: 0644]
res/layout/shadow.xml [new file with mode: 0644]
res/layout/status_bar.xml [new file with mode: 0644]
res/layout/tabs.xml [new file with mode: 0644]
res/layout/theme_preview.xml
res/layout/track_browser.xml [new file with mode: 0644]
res/menu/overflow_library.xml [new file with mode: 0644]
res/menu/overflow_now_playing.xml [new file with mode: 0644]
res/values-hdpi/config.xml [new file with mode: 0644]
res/values-hdpi/dimens.xml [new file with mode: 0644]
res/values-xhdpi/dimens.xml [new file with mode: 0644]
res/values/colors.xml
res/values/config.xml [new file with mode: 0644]
res/values/dimens.xml
res/values/strings.xml
res/values/styles.xml [new file with mode: 0644]
res/xml/appwidget1x1_info.xml
res/xml/appwidget4x1_info.xml
res/xml/appwidget4x2_info.xml
res/xml/searchable.xml
res/xml/settings.xml
src/com/andrew/apollo/AudioPlayerFragment.java [new file with mode: 0644]
src/com/andrew/apollo/BottomActionBarControlsFragment.java [new file with mode: 0644]
src/com/andrew/apollo/BottomActionBarFragment.java [new file with mode: 0644]
src/com/andrew/apollo/Constants.java [new file with mode: 0644]
src/com/andrew/apollo/IApolloService.aidl [new file with mode: 0644]
src/com/andrew/apollo/NowPlayingCursor.java [new file with mode: 0644]
src/com/andrew/apollo/activities/AudioPlayerHolder.java [new file with mode: 0644]
src/com/andrew/apollo/activities/MusicLibrary.java [new file with mode: 0644]
src/com/andrew/apollo/activities/QueryBrowserActivity.java [new file with mode: 0644]
src/com/andrew/apollo/activities/QuickQueue.java [new file with mode: 0644]
src/com/andrew/apollo/activities/ScanningProgress.java [new file with mode: 0644]
src/com/andrew/apollo/activities/TracksBrowser.java [new file with mode: 0644]
src/com/andrew/apollo/adapters/AlbumAdapter.java [new file with mode: 0644]
src/com/andrew/apollo/adapters/ArtistAdapter.java [new file with mode: 0644]
src/com/andrew/apollo/adapters/ArtistAlbumAdapter.java [new file with mode: 0644]
src/com/andrew/apollo/adapters/GenreAdapter.java [new file with mode: 0644]
src/com/andrew/apollo/adapters/PagerAdapter.java [new file with mode: 0644]
src/com/andrew/apollo/adapters/PlaylistAdapter.java [new file with mode: 0644]
src/com/andrew/apollo/adapters/QuickQueueAdapter.java [new file with mode: 0644]
src/com/andrew/apollo/adapters/RecentlyAddedAdapter.java [new file with mode: 0644]
src/com/andrew/apollo/adapters/ScrollingTabsAdapter.java [new file with mode: 0644]
src/com/andrew/apollo/adapters/TabAdapter.java [new file with mode: 0644]
src/com/andrew/apollo/adapters/TrackAdapter.java [new file with mode: 0644]
src/com/andrew/apollo/app/widgets/AppWidget11.java [new file with mode: 0644]
src/com/andrew/apollo/app/widgets/AppWidget41.java [new file with mode: 0644]
src/com/andrew/apollo/app/widgets/AppWidget42.java [new file with mode: 0644]
src/com/andrew/apollo/grid/fragments/AlbumsFragment.java [new file with mode: 0644]
src/com/andrew/apollo/grid/fragments/ArtistsFragment.java [new file with mode: 0644]
src/com/andrew/apollo/grid/fragments/QuickQueueFragment.java [new file with mode: 0644]
src/com/andrew/apollo/lastfm/api/Album.java [new file with mode: 0644]
src/com/andrew/apollo/lastfm/api/Artist.java [new file with mode: 0644]
src/com/andrew/apollo/lastfm/api/CallException.java [new file with mode: 0644]
src/com/andrew/apollo/lastfm/api/Caller.java [new file with mode: 0644]
src/com/andrew/apollo/lastfm/api/Image.java [new file with mode: 0644]
src/com/andrew/apollo/lastfm/api/ImageHolder.java [new file with mode: 0644]
src/com/andrew/apollo/lastfm/api/ImageSize.java [new file with mode: 0644]
src/com/andrew/apollo/lastfm/api/ItemFactory.java [new file with mode: 0644]
src/com/andrew/apollo/lastfm/api/ItemFactoryBuilder.java [new file with mode: 0644]
src/com/andrew/apollo/lastfm/api/MusicEntry.java [new file with mode: 0644]
src/com/andrew/apollo/lastfm/api/PaginatedResult.java [new file with mode: 0644]
src/com/andrew/apollo/lastfm/api/ResponseBuilder.java [new file with mode: 0644]
src/com/andrew/apollo/lastfm/api/Result.java [new file with mode: 0644]
src/com/andrew/apollo/lastfm/api/Session.java [new file with mode: 0644]
src/com/andrew/apollo/list/fragments/ArtistAlbumsFragment.java [new file with mode: 0644]
src/com/andrew/apollo/list/fragments/GenresFragment.java [new file with mode: 0644]
src/com/andrew/apollo/list/fragments/PlaylistsFragment.java [new file with mode: 0644]
src/com/andrew/apollo/list/fragments/RecentlyAddedFragment.java [new file with mode: 0644]
src/com/andrew/apollo/list/fragments/TracksFragment.java [new file with mode: 0644]
src/com/andrew/apollo/menu/PlaylistDialog.java [new file with mode: 0644]
src/com/andrew/apollo/menu/PlaylistPicker.java [new file with mode: 0644]
src/com/andrew/apollo/menu/PlaylistPickerDialog.java [new file with mode: 0644]
src/com/andrew/apollo/preferences/SettingsFragment.java [new file with mode: 0644]
src/com/andrew/apollo/preferences/SettingsHolder.java [new file with mode: 0644]
src/com/andrew/apollo/preferences/ThemePreview.java [new file with mode: 0644]
src/com/andrew/apollo/service/ApolloService.java [new file with mode: 0644]
src/com/andrew/apollo/service/MediaButtonIntentReceiver.java [new file with mode: 0644]
src/com/andrew/apollo/service/ServiceBinder.java [new file with mode: 0644]
src/com/andrew/apollo/service/ServiceToken.java [new file with mode: 0644]
src/com/andrew/apollo/tasks/BitmapFromURL.java [new file with mode: 0644]
src/com/andrew/apollo/tasks/FetchAlbumImages.java [new file with mode: 0644]
src/com/andrew/apollo/tasks/FetchArtistImages.java [new file with mode: 0644]
src/com/andrew/apollo/tasks/GetCachedImages.java [new file with mode: 0644]
src/com/andrew/apollo/tasks/LastfmGetAlbumImages.java [new file with mode: 0644]
src/com/andrew/apollo/tasks/LastfmGetArtistImages.java [new file with mode: 0644]
src/com/andrew/apollo/tasks/LastfmGetArtistImagesOriginal.java [new file with mode: 0644]
src/com/andrew/apollo/tasks/ViewHolderQueueTask.java [new file with mode: 0644]
src/com/andrew/apollo/tasks/ViewHolderTask.java [new file with mode: 0644]
src/com/andrew/apollo/ui/widgets/BottomActionBar.java [new file with mode: 0644]
src/com/andrew/apollo/ui/widgets/BottomActionBarItem.java [new file with mode: 0644]
src/com/andrew/apollo/ui/widgets/RepeatingImageButton.java [new file with mode: 0644]
src/com/andrew/apollo/ui/widgets/ScrollableTabView.java [new file with mode: 0644]
src/com/andrew/apollo/utils/ApolloUtils.java [new file with mode: 0644]
src/com/andrew/apollo/utils/DomElement.java [new file with mode: 0644]
src/com/andrew/apollo/utils/MapUtilities.java [new file with mode: 0644]
src/com/andrew/apollo/utils/MusicUtils.java [new file with mode: 0644]
src/com/andrew/apollo/utils/SharedPreferencesCompat.java [new file with mode: 0644]
src/com/andrew/apollo/utils/StringUtilities.java [new file with mode: 0644]
src/com/andrew/apollo/utils/ThemeUtils.java [new file with mode: 0644]
src/com/andrew/apollo/views/ViewHolderGrid.java [new file with mode: 0644]
src/com/andrew/apollo/views/ViewHolderList.java [new file with mode: 0644]
src/com/andrew/apollo/views/ViewHolderQueue.java [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..521cb53
--- /dev/null
@@ -0,0 +1,2 @@
+git rm -r --cached bin\r
+git rm -r --cached gen
\ No newline at end of file
index 25e9b4c..1fe2ecf 100644 (file)
--- a/.project
+++ b/.project
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>\r
 <projectDescription>\r
-       <name>Music</name>\r
+       <name>Apollo</name>\r
        <comment></comment>\r
        <projects>\r
        </projects>\r
index 26505ef..630b90c 100644 (file)
-<?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"
-    android:versionCode="1"
-    android:versionName="1.0" >
-
-    <original-package android:name="com.android.music" />
-
-    <uses-sdk
-        android:maxSdkVersion="15"
-        android:minSdkVersion="14" />
-
-    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
-    <uses-permission android:name="android.permission.WAKE_LOCK" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.BROADCAST_STICKY" />
-    <uses-permission android:name="android.permission.VIBRATE" />
-    <uses-permission android:name="android.permission.SET_WALLPAPER" />
-
-    <application
-        android:allowTaskReparenting="true"
-        android:hardwareAccelerated="true"
-        android:icon="@drawable/app_music"
-        android:label="@string/musicbrowserlabel"
-        android:taskAffinity="android.task.music"
-        android:theme="@android:style/Theme.Holo" >
-        <meta-data
-            android:name="android.app.default_searchable"
-            android:value="com.android.music.QueryBrowserActivity" />
-
-        <activity
-            android:name="com.android.music.MusicBrowserActivity"
-            android:exported="true"
-            android:theme="@android:style/Theme.NoTitleBar" >
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <action android:name="android.intent.action.MUSIC_PLAYER" />
-
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
-                <category android:name="android.intent.category.APP_MUSIC" />
-            </intent-filter>
-        </activity>
-
-        <receiver android:name="com.android.music.MediaButtonIntentReceiver" >
-            <intent-filter>
-                <action android:name="android.intent.action.MEDIA_BUTTON" />
-                <action android:name="android.media.AUDIO_BECOMING_NOISY" />
-            </intent-filter>
-        </receiver>
-        <!--
-             This is the "current music playing" panel, which has special
-             launch behavior.  We clear its task affinity, so it will not
-             be associated with the main media task and if launched
-             from a notification will not bring the rest of the media app
-             to the foreground.  We make it singleTask so that when others
-             launch it (such as media) we will launch in to our own task.
-             We set clearTaskOnLaunch because the user
-             can go to a playlist from this activity, so if they later return
-             to it we want it back in its initial state.  We exclude from
-             recents since this is accessible through a notification when
-             appropriate.
-        -->
-        <activity
-            android:name="com.android.music.ColorPicker"
-            android:excludeFromRecents="true"
-            android:taskAffinity=""
-            android:theme="@android:style/Theme.Holo.Dialog" >
-            <intent-filter>
-                <action android:name="com.android.music.ColorPicker" />
-
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-        </activity>
-        <activity
-            android:name="com.android.music.Sensitivity"
-            android:label="Sensitivity"
-            android:excludeFromRecents="true"
-            android:taskAffinity=""
-            android:theme="@android:style/Theme.Holo.Dialog" >
-            <intent-filter>
-                <action android:name="com.android.music.Sensitivity" />
-
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-        </activity>
-        <activity
-            android:name="com.android.music.MediaPlaybackActivity"
-            android:clearTaskOnLaunch="true"
-            android:excludeFromRecents="true"
-            android:exported="true"
-            android:label="@string/mediaplaybacklabel"
-            android:launchMode="singleTask"
-            android:taskAffinity="" >
-            <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-
-                <category android:name="android.intent.category.DEFAULT" />
-
-                <data android:scheme="content" />
-                <data android:host="media" />
-                <data android:mimeType="audio/*" />
-                <data android:mimeType="application/ogg" />
-                <data android:mimeType="application/x-ogg" />
-                <data android:mimeType="application/itunes" />
-            </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="AudioPreview"
-            android:excludeFromRecents="true"
-            android:exported="true"
-            android:taskAffinity=""
-            android:theme="@android:style/Theme.Dialog" >
-            <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-
-                <category android:name="android.intent.category.DEFAULT" />
-
-                <data android:scheme="file" />
-                <data android:mimeType="audio/*" />
-                <data android:mimeType="application/ogg" />
-                <data android:mimeType="application/x-ogg" />
-                <data android:mimeType="application/itunes" />
-            </intent-filter>
-            <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-
-                <data android:scheme="http" />
-                <data android:mimeType="audio/*" />
-                <data android:mimeType="application/ogg" />
-                <data android:mimeType="application/x-ogg" />
-                <data android:mimeType="application/itunes" />
-            </intent-filter>
-            <intent-filter android:priority="-1" >
-                <action android:name="android.intent.action.VIEW" />
-
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-
-                <data android:scheme="content" />
-                <data android:mimeType="audio/*" />
-                <data android:mimeType="application/ogg" />
-                <data android:mimeType="application/x-ogg" />
-                <data android:mimeType="application/itunes" />
-            </intent-filter>
-        </activity>
-        <activity
-            android:name="com.android.music.ArtistAlbumBrowserActivity"
-            android:exported="false" >
-            <intent-filter>
-                <action android:name="android.intent.action.PICK" />
-
-                <category android:name="android.intent.category.DEFAULT" />
-
-                <data android:mimeType="vnd.android.cursor.dir/artistalbum" />
-            </intent-filter>
-        </activity>
-        <activity
-            android:name="com.android.music.AlbumBrowserActivity"
-            android:exported="false" >
-            <intent-filter>
-                <action android:name="android.intent.action.PICK" />
-
-                <category android:name="android.intent.category.DEFAULT" />
-
-                <data android:mimeType="vnd.android.cursor.dir/album" />
-            </intent-filter>
-        </activity>
-        <activity
-            android:name="com.android.music.NowPlayingActivity"
-            android:exported="false" >
-            <intent-filter>
-                <action android:name="android.intent.action.PICK" />
-
-                <category android:name="android.intent.category.DEFAULT" />
-
-                <data android:mimeType="vnd.android.cursor.dir/nowplaying" />
-            </intent-filter>
-        </activity>
-        <activity
-            android:name="com.android.music.TrackBrowserActivity"
-            android:exported="false" >
-            <intent-filter>
-                <action android:name="android.intent.action.EDIT" />
-                <action android:name="android.intent.action.PICK" />
-
-                <category android:name="android.intent.category.DEFAULT" />
-
-                <data android:mimeType="vnd.android.cursor.dir/track" />
-            </intent-filter>
-        </activity>
-        <activity
-            android:name="com.android.music.QueryBrowserActivity"
-            android:exported="true"
-            android:theme="@android:style/Theme.NoTitleBar" >
-            <intent-filter>
-                <action android:name="android.intent.action.SEARCH" />
-                <action android:name="android.intent.action.MEDIA_SEARCH" />
-
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-
-            <meta-data
-                android:name="android.app.searchable"
-                android:resource="@xml/searchable" />
-        </activity>
-        <activity
-            android:name="com.android.music.PlaylistBrowserActivity"
-            android:exported="true"
-            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="com.android.music.PlaylistShortcutActivity"
-            android:exported="true"
-            android:icon="@drawable/ic_launcher_shortcut_music_playlist"
-            android:label="@string/musicshortcutlabel"
-            android:targetActivity="com.android.music.PlaylistBrowserActivity" >
-            <intent-filter>
-                <action android:name="android.intent.action.CREATE_SHORTCUT" />
-
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-        </activity-alias>
-
-        <activity
-            android:name="com.android.music.VideoBrowserActivity"
-            android:exported="false"
-            android:icon="@drawable/app_video"
-            android:label="@string/videobrowserlabel"
-            android:taskAffinity="android.task.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="com.android.music.MediaPickerActivity"
-            android:exported="false"
-            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="com.android.music.MusicPicker"
-            android:exported="true"
-            android:label="@string/music_picker_title" >
-
-            <!--
-                 First way to invoke us: someone asks to get content of
-                 any of the audio types we support.
-            -->
-            <intent-filter>
-                <action android:name="android.intent.action.GET_CONTENT" />
-
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.OPENABLE" />
-
-                <data android:mimeType="audio/*" />
-                <data android:mimeType="application/ogg" />
-                <data android:mimeType="application/x-ogg" />
-            </intent-filter>
-            <!--
-                 Second way to invoke us: someone asks to pick an item from
-                 some media Uri.
-            -->
-            <intent-filter>
-                <action android:name="android.intent.action.PICK" />
-
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.OPENABLE" />
-
-                <data android:mimeType="vnd.android.cursor.dir/audio" />
-            </intent-filter>
-        </activity>
-        <activity
-            android:name="com.android.music.CreatePlaylist"
-            android:exported="false"
-            android:theme="@android:style/Theme.Holo.Dialog" />
-        <activity
-            android:name="com.android.music.RenamePlaylist"
-            android:exported="false"
-            android:theme="@android:style/Theme.Holo.Dialog" />
-        <activity
-            android:name="com.android.music.WeekSelector"
-            android:exported="false"
-            android:theme="@android:style/Theme.Holo.Dialog" />
-        <activity
-            android:name="com.android.music.DeleteItems"
-            android:exported="false"
-            android:theme="@android:style/Theme.Holo.Dialog" />
-        <activity
-            android:name="com.android.music.ScanningProgress"
-            android:exported="false"
-            android:theme="@android:style/Theme.Holo.Dialog" />
-        <activity
-            android:name="com.android.music.MusicSettingsActivity"
-            android:label="@string/settings" />
-        <activity
-            android:name="com.android.music.EditGesturesActivity"
-            android:label="@string/edit_gestures_title" />
-        <activity
-            android:name="com.android.music.CustomizeGestureActivity"
-            android:label="@string/customize_gesture_title" />
-
-        <service
-            android:name="com.android.music.MediaPlaybackService"
-            android:exported="true" />
-
-        <receiver
-            android:name="com.android.music.MediaAppWidgetProvider4x1"
-            android:label="Music (4x1)" >
-            <intent-filter>
-                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
-            </intent-filter>
-
-            <meta-data
-                android:name="android.appwidget.provider"
-                android:resource="@xml/appwidget4x1_info" />
-        </receiver>
-        <receiver
-            android:name="com.android.music.MediaAppWidgetProvider4x2"
-            android:label="Music (4x2)" >
-            <intent-filter>
-                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
-            </intent-filter>
-
-            <meta-data
-                android:name="android.appwidget.provider"
-                android:resource="@xml/appwidget4x2_info" />
-        </receiver>
-        <receiver
-            android:name="com.android.music.MediaAppWidgetProvider1x1"
-            android:label="Music (1x1)" >
-            <intent-filter>
-                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
-            </intent-filter>
-
-            <meta-data
-                android:name="android.appwidget.provider"
-                android:resource="@xml/appwidget1x1_info" />
-        </receiver>
-        <receiver
-            android:name="com.android.music.MediaAppWidgetProvider3x1"
-            android:label="Music (3x1)" >
-            <intent-filter>
-                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
-            </intent-filter>
-
-            <meta-data
-                android:name="android.appwidget.provider"
-                android:resource="@xml/appwidget3x1_info" />
-        </receiver>
-    </application>
-
+<?xml version="1.0" encoding="utf-8"?>\r
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"\r
+    package="com.andrew.apollo"\r
+    android:versionCode="1"\r
+    android:versionName="1.0" >\r
+\r
+    <uses-sdk\r
+        android:minSdkVersion="14"\r
+        android:targetSdkVersion="15" />\r
+\r
+    <!-- This is used for Last.fm and Google Music -->\r
+    <uses-permission android:name="android.permission.INTERNET" />\r
+    <!-- Used to check for a data connection -->\r
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />\r
+    <!-- Used to keep the service running when the phone sleeps -->\r
+    <uses-permission android:name="android.permission.WAKE_LOCK" />\r
+    <!-- Stick Broadcast -->\r
+    <uses-permission android:name="android.permission.BROADCAST_STICKY" />\r
+    <!-- Incoming calls -->\r
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />\r
+    <!-- Used to set ringtone -->\r
+    <uses-permission android:name="android.permission.WRITE_SETTINGS" />\r
+\r
+    <application\r
+        android:allowTaskReparenting="true"\r
+        android:hardwareAccelerated="true"\r
+        android:icon="@drawable/ic_launcher"\r
+        android:label="@string/app_name"\r
+        android:largeHeap="true"\r
+        android:process=":main"\r
+        android:taskAffinity="apollo.task.music"\r
+        android:theme="@android:style/Theme.Holo.Light" >\r
+        <meta-data\r
+            android:name="android.app.default_searchable"\r
+            android:value=".activities.QueryBrowserActivity" />\r
+        <!-- Serach -->\r
+        <activity\r
+            android:name=".activities.QueryBrowserActivity"\r
+            android:exported="true"\r
+            android:theme="@android:style/Theme.Holo.Light" >\r
+            <intent-filter>\r
+                <action android:name="android.intent.action.SEARCH" />\r
+                <action android:name="android.intent.action.MEDIA_SEARCH" />\r
+                <action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />\r
+\r
+                <category android:name="android.intent.category.DEFAULT" />\r
+            </intent-filter>\r
+\r
+            <meta-data\r
+                android:name="android.app.searchable"\r
+                android:resource="@xml/searchable" />\r
+        </activity>\r
+\r
+        <!-- Main Activity -->\r
+        <activity\r
+            android:name=".activities.MusicLibrary"\r
+            android:label="@string/app_name"\r
+            android:windowSoftInputMode="adjustPan" >\r
+            <intent-filter>\r
+                <action android:name="android.intent.action.MAIN" />\r
+                <action android:name="android.intent.action.MUSIC_PLAYER" />\r
+\r
+                <category android:name="android.intent.category.DEFAULT" />\r
+                <category android:name="android.intent.category.LAUNCHER" />\r
+                <category android:name="android.intent.category.APP_MUSIC" />\r
+            </intent-filter>\r
+        </activity>\r
+        <!-- Now Playing -->\r
+        <activity\r
+            android:name=".activities.AudioPlayerHolder"\r
+            android:clearTaskOnLaunch="true"\r
+            android:excludeFromRecents="true"\r
+            android:label="@string/nowplaying"\r
+            android:launchMode="singleTask"\r
+            android:windowSoftInputMode="adjustPan" >\r
+            <intent-filter>\r
+                <action android:name="android.intent.action.VIEW" />\r
+\r
+                <category android:name="android.intent.category.DEFAULT" />\r
+\r
+                <data android:scheme="content" />\r
+                <data android:host="media" />\r
+                <data android:mimeType="audio/*" />\r
+                <data android:mimeType="application/ogg" />\r
+                <data android:mimeType="application/x-ogg" />\r
+                <data android:mimeType="application/itunes" />\r
+            </intent-filter>\r
+            <intent-filter>\r
+                <action android:name="com.andrew.apollo.PLAYBACK_VIEWER" />\r
+\r
+                <category android:name="android.intent.category.DEFAULT" />\r
+            </intent-filter>\r
+        </activity>\r
+        <!-- Track browser -->\r
+        <activity\r
+            android:name=".activities.TracksBrowser"\r
+            android:label="@string/app_name"\r
+            android:windowSoftInputMode="adjustPan" >\r
+            <intent-filter>\r
+                <action android:name="android.intent.action.EDIT" />\r
+                <action android:name="android.intent.action.VIEW" />\r
+\r
+                <category android:name="android.intent.category.DEFAULT" />\r
+            </intent-filter>\r
+        </activity>\r
+\r
+        <!-- Quickly show the queue -->\r
+        <activity\r
+            android:name=".activities.QuickQueue"\r
+            android:excludeFromRecents="true"\r
+            android:launchMode="singleTop"\r
+            android:noHistory="true"\r
+            android:theme="@style/Theme.QuickQueue"\r
+            android:windowSoftInputMode="stateUnchanged" />\r
+        <!-- Settings -->\r
+        <activity\r
+            android:name=".preferences.SettingsHolder"\r
+            android:label="@string/settings" />\r
+        <!-- Mediascanner -->\r
+        <activity\r
+            android:name=".activities.ScanningProgress"\r
+            android:exported="false"\r
+            android:theme="@android:style/Theme.Holo.Light.Dialog" />\r
+        <activity\r
+            android:name=".menu.PlaylistDialog"\r
+            android:label="@string/rename_playlist"\r
+            android:theme="@android:style/Theme.Holo.Light.Dialog.NoActionBar" >\r
+            <intent-filter>\r
+                <action android:name="com.andrew.apollo.CREATE_PLAYLIST" />\r
+\r
+                <category android:name="android.intent.category.DEFAULT" />\r
+            </intent-filter>\r
+            <intent-filter>\r
+                <action android:name="com.andrew.apollo.RENAME_PLAYLIST" />\r
+\r
+                <category android:name="android.intent.category.DEFAULT" />\r
+            </intent-filter>\r
+        </activity>\r
+        <activity\r
+            android:name=".menu.PlaylistPicker"\r
+            android:icon="@drawable/ic_launcher"\r
+            android:theme="@android:style/Theme.Holo.Light.Dialog.NoActionBar" >\r
+            <intent-filter>\r
+                <action android:name="com.andrew.apollo.ADD_TO_PLAYLIST" />\r
+\r
+                <category android:name="android.intent.category.DEFAULT" />\r
+            </intent-filter>\r
+        </activity>\r
+        <!-- 1x1 App Widget -->\r
+        <receiver\r
+            android:name="com.andrew.apollo.app.widgets.AppWidget11"\r
+            android:label="@string/apollo_1x1" >\r
+            <intent-filter>\r
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />\r
+            </intent-filter>\r
+\r
+            <meta-data\r
+                android:name="android.appwidget.provider"\r
+                android:resource="@xml/appwidget1x1_info" />\r
+        </receiver>\r
+        <!-- 4x1 App Widget -->\r
+        <receiver\r
+            android:name="com.andrew.apollo.app.widgets.AppWidget41"\r
+            android:label="@string/apollo_4x1" >\r
+            <intent-filter>\r
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />\r
+            </intent-filter>\r
+\r
+            <meta-data\r
+                android:name="android.appwidget.provider"\r
+                android:resource="@xml/appwidget4x1_info" />\r
+        </receiver>\r
+        <!-- 4x2 App Widget -->\r
+        <receiver\r
+            android:name="com.andrew.apollo.app.widgets.AppWidget42"\r
+            android:label="@string/apollo_4x2" >\r
+            <intent-filter>\r
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />\r
+            </intent-filter>\r
+\r
+            <meta-data\r
+                android:name="android.appwidget.provider"\r
+                android:resource="@xml/appwidget4x2_info" />\r
+        </receiver>\r
+        <!-- Media button receiver -->\r
+        <receiver android:name=".service.MediaButtonIntentReceiver" >\r
+            <intent-filter>\r
+                <action android:name="android.intent.action.MEDIA_BUTTON" />\r
+                <action android:name="android.media.AUDIO_BECOMING_NOISY" />\r
+            </intent-filter>\r
+        </receiver>\r
+        <!-- Music service -->\r
+        <service\r
+            android:name=".service.ApolloService"\r
+            android:label="@string/app_name"\r
+            android:process=":main" />\r
+    </application>\r
+\r
 </manifest>
\ No newline at end of file
diff --git a/assets/Frame0.png b/assets/Frame0.png
new file mode 100644 (file)
index 0000000..5a977d8
Binary files /dev/null and b/assets/Frame0.png differ
diff --git a/assets/Frame1.png b/assets/Frame1.png
new file mode 100644 (file)
index 0000000..a377d76
Binary files /dev/null and b/assets/Frame1.png differ
diff --git a/assets/Frame10.png b/assets/Frame10.png
new file mode 100644 (file)
index 0000000..a189e9d
Binary files /dev/null and b/assets/Frame10.png differ
diff --git a/assets/Frame11.png b/assets/Frame11.png
new file mode 100644 (file)
index 0000000..fed6906
Binary files /dev/null and b/assets/Frame11.png differ
diff --git a/assets/Frame2.png b/assets/Frame2.png
new file mode 100644 (file)
index 0000000..84e2a7a
Binary files /dev/null and b/assets/Frame2.png differ
diff --git a/assets/Frame3.png b/assets/Frame3.png
new file mode 100644 (file)
index 0000000..c221e28
Binary files /dev/null and b/assets/Frame3.png differ
diff --git a/assets/Frame4.png b/assets/Frame4.png
new file mode 100644 (file)
index 0000000..6654af8
Binary files /dev/null and b/assets/Frame4.png differ
diff --git a/assets/Frame5.png b/assets/Frame5.png
new file mode 100644 (file)
index 0000000..92731d9
Binary files /dev/null and b/assets/Frame5.png differ
diff --git a/assets/Frame6.png b/assets/Frame6.png
new file mode 100644 (file)
index 0000000..473f1c1
Binary files /dev/null and b/assets/Frame6.png differ
diff --git a/assets/Frame7.png b/assets/Frame7.png
new file mode 100644 (file)
index 0000000..df20376
Binary files /dev/null and b/assets/Frame7.png differ
diff --git a/assets/Frame8.png b/assets/Frame8.png
new file mode 100644 (file)
index 0000000..7cebd07
Binary files /dev/null and b/assets/Frame8.png differ
diff --git a/assets/Frame9.png b/assets/Frame9.png
new file mode 100644 (file)
index 0000000..bde4b9d
Binary files /dev/null and b/assets/Frame9.png differ
diff --git a/libs/android-query-0.21.7.jar b/libs/android-query-0.21.7.jar
new file mode 100644 (file)
index 0000000..f2dd72a
Binary files /dev/null and b/libs/android-query-0.21.7.jar differ
index d006198..1fbeba0 100644 (file)
Binary files a/libs/android-support-v4.jar and b/libs/android-support-v4.jar differ
diff --git a/proguard.cfg b/proguard.cfg
new file mode 100644 (file)
index 0000000..01888ae
--- /dev/null
@@ -0,0 +1,55 @@
+-optimizationpasses 5
+-dontusemixedcaseclassnames
+-dontskipnonpubliclibraryclasses
+-dontpreverify
+-verbose
+-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
+
+-keep public class * extends android.app.Activity
+-keep public class * extends android.app.Application
+-keep public class * extends android.app.Service
+-keep public class * extends android.content.BroadcastReceiver
+-keep public class * extends android.content.ContentProvider
+-keep public class * extends android.app.backup.BackupAgentHelper
+-keep public class * extends android.preference.Preference
+-keep public class com.android.vending.licensing.ILicensingService
+
+#keep all classes that might be used in XML layouts
+-keep public class * extends android.view.View
+-keep public class * extends android.app.Fragment
+-keep public class * extends android.support.v4.Fragment
+
+#keep all classes
+-keep public class *{
+    public protected *;
+}
+
+#keep all public and protected methods that could be used by java reflection
+-keepclassmembernames class * {
+       public protected <methods>;
+}
+
+-keepclasseswithmembers  class * {
+    native <methods>;
+}
+
+-keepclasseswithmembers  class * {
+    public <init>(android.content.Context, android.util.AttributeSet);
+}
+
+-keepclasseswithmembers  class * {
+    public <init>(android.content.Context, android.util.AttributeSet, int);
+}
+
+-keepclassmembers enum * {
+    public static **[] values();
+    public static ** valueOf(java.lang.String);
+}
+
+-keep class * implements android.os.Parcelable {
+  public static final android.os.Parcelable$Creator *;
+}
+
+-dontwarn **CompatHoneycomb
+-dontwarn org.htmlcleaner.*
+#-keep class android.support.v4.** { *; }
\ No newline at end of file
index 8da376a..746a18f 100644 (file)
@@ -3,9 +3,13 @@
 #
 # This file must be checked in Version Control Systems.
 #
-# To customize properties used by the Ant build system use,
+# To customize properties used by the Ant build system edit
 # "ant.properties", and override values to adapt the script to your
 # project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}\tools\proguard\proguard-android.txt:proguard-project.txt
 
 # Project target.
 target=android-15
+proguard.config=proguard.cfg
diff --git a/res/anim/peak_meter_1.xml b/res/anim/peak_meter_1.xml
new file mode 100644 (file)
index 0000000..c9fbfde
--- /dev/null
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<animation-list android:oneshot="false"\r
+  xmlns:android="http://schemas.android.com/apk/res/android">\r
+    <item android:duration="@integer/peak" android:drawable="@drawable/indicator_playing_peak_meter_1" />\r
+    <item android:duration="@integer/peak" android:drawable="@drawable/indicator_playing_peak_meter_2" />\r
+    <item android:duration="@integer/peak" android:drawable="@drawable/indicator_playing_peak_meter_4" />\r
+    <item android:duration="@integer/peak" android:drawable="@drawable/indicator_playing_peak_meter_3" />\r
+    <item android:duration="@integer/peak" android:drawable="@drawable/indicator_playing_peak_meter_4" />\r
+    <item android:duration="@integer/peak" android:drawable="@drawable/indicator_playing_peak_meter_3" />\r
+    <item android:duration="@integer/peak" android:drawable="@drawable/indicator_playing_peak_meter_2" />\r
+    <item android:duration="@integer/peak" android:drawable="@drawable/indicator_playing_peak_meter_4" />\r
+    <item android:duration="@integer/peak" android:drawable="@drawable/indicator_playing_peak_meter_5" />\r
+    <item android:duration="@integer/peak" android:drawable="@drawable/indicator_playing_peak_meter_4" />\r
+    <item android:duration="@integer/peak" android:drawable="@drawable/indicator_playing_peak_meter_3" />\r
+    <item android:duration="@integer/peak" android:drawable="@drawable/indicator_playing_peak_meter_2" />\r
+    <item android:duration="@integer/peak" android:drawable="@drawable/indicator_playing_peak_meter_1" />\r
+    <item android:duration="@integer/peak" android:drawable="@drawable/indicator_playing_peak_meter_3" />\r
+</animation-list>\r
diff --git a/res/anim/peak_meter_2.xml b/res/anim/peak_meter_2.xml
new file mode 100644 (file)
index 0000000..5601a21
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<animation-list android:oneshot="false"\r
+  xmlns:android="http://schemas.android.com/apk/res/android">\r
+    <item android:duration="@integer/peak" android:drawable="@drawable/indicator_playing_peak_meter_1" />\r
+    <item android:duration="@integer/peak" android:drawable="@drawable/indicator_playing_peak_meter_4" />\r
+    <item android:duration="@integer/peak" android:drawable="@drawable/indicator_playing_peak_meter_3" />\r
+    <item android:duration="@integer/peak" android:drawable="@drawable/indicator_playing_peak_meter_2" />\r
+    <item android:duration="@integer/peak" android:drawable="@drawable/indicator_playing_peak_meter_1" />\r
+    <item android:duration="@integer/peak" android:drawable="@drawable/indicator_playing_peak_meter_3" />\r
+    <item android:duration="@integer/peak" android:drawable="@drawable/indicator_playing_peak_meter_2" />\r
+    <item android:duration="@integer/peak" android:drawable="@drawable/indicator_playing_peak_meter_3" />\r
+    <item android:duration="@integer/peak" android:drawable="@drawable/indicator_playing_peak_meter_4" />\r
+    <item android:duration="@integer/peak" android:drawable="@drawable/indicator_playing_peak_meter_3" />\r
+    <item android:duration="@integer/peak" android:drawable="@drawable/indicator_playing_peak_meter_2" />\r
+    <item android:duration="@integer/peak" android:drawable="@drawable/indicator_playing_peak_meter_4" />\r
+    <item android:duration="@integer/peak" android:drawable="@drawable/indicator_playing_peak_meter_5" />\r
+</animation-list>\r
diff --git a/res/color/tab_text_color.xml b/res/color/tab_text_color.xml
new file mode 100644 (file)
index 0000000..569f1c3
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<selector\r
+  xmlns:android="http://schemas.android.com/apk/res/android">\r
+    <item android:state_selected="true" android:color="@color/black" />\r
+    <item android:color="@color/transparent_black" />\r
+</selector>\r
diff --git a/res/drawable-hdpi/apollo_holo_dark_next.png b/res/drawable-hdpi/apollo_holo_dark_next.png
new file mode 100644 (file)
index 0000000..738aae1
Binary files /dev/null and b/res/drawable-hdpi/apollo_holo_dark_next.png differ
diff --git a/res/drawable-hdpi/apollo_holo_dark_notifiation_bar_collapse.png b/res/drawable-hdpi/apollo_holo_dark_notifiation_bar_collapse.png
new file mode 100644 (file)
index 0000000..5b04d33
Binary files /dev/null and b/res/drawable-hdpi/apollo_holo_dark_notifiation_bar_collapse.png differ
diff --git a/res/drawable-hdpi/apollo_holo_dark_overflow.png b/res/drawable-hdpi/apollo_holo_dark_overflow.png
new file mode 100644 (file)
index 0000000..38aadc6
Binary files /dev/null and b/res/drawable-hdpi/apollo_holo_dark_overflow.png differ
diff --git a/res/drawable-hdpi/apollo_holo_dark_pause.png b/res/drawable-hdpi/apollo_holo_dark_pause.png
new file mode 100644 (file)
index 0000000..6b435bb
Binary files /dev/null and b/res/drawable-hdpi/apollo_holo_dark_pause.png differ
diff --git a/res/drawable-hdpi/apollo_holo_dark_play.png b/res/drawable-hdpi/apollo_holo_dark_play.png
new file mode 100644 (file)
index 0000000..df8a2ca
Binary files /dev/null and b/res/drawable-hdpi/apollo_holo_dark_play.png differ
diff --git a/res/drawable-hdpi/apollo_holo_light_favorite_normal.png b/res/drawable-hdpi/apollo_holo_light_favorite_normal.png
new file mode 100644 (file)
index 0000000..98dd2ca
Binary files /dev/null and b/res/drawable-hdpi/apollo_holo_light_favorite_normal.png differ
diff --git a/res/drawable-hdpi/apollo_holo_light_favorite_selected.png b/res/drawable-hdpi/apollo_holo_light_favorite_selected.png
new file mode 100644 (file)
index 0000000..861b898
Binary files /dev/null and b/res/drawable-hdpi/apollo_holo_light_favorite_selected.png differ
diff --git a/res/drawable-hdpi/apollo_holo_light_next.png b/res/drawable-hdpi/apollo_holo_light_next.png
new file mode 100644 (file)
index 0000000..b4f692f
Binary files /dev/null and b/res/drawable-hdpi/apollo_holo_light_next.png differ
diff --git a/res/drawable-hdpi/apollo_holo_light_overflow.png b/res/drawable-hdpi/apollo_holo_light_overflow.png
new file mode 100644 (file)
index 0000000..0c844f3
Binary files /dev/null and b/res/drawable-hdpi/apollo_holo_light_overflow.png differ
diff --git a/res/drawable-hdpi/apollo_holo_light_pause.png b/res/drawable-hdpi/apollo_holo_light_pause.png
new file mode 100644 (file)
index 0000000..9661cfb
Binary files /dev/null and b/res/drawable-hdpi/apollo_holo_light_pause.png differ
diff --git a/res/drawable-hdpi/apollo_holo_light_play.png b/res/drawable-hdpi/apollo_holo_light_play.png
new file mode 100644 (file)
index 0000000..e70f041
Binary files /dev/null and b/res/drawable-hdpi/apollo_holo_light_play.png differ
diff --git a/res/drawable-hdpi/apollo_holo_light_previous.png b/res/drawable-hdpi/apollo_holo_light_previous.png
new file mode 100644 (file)
index 0000000..ba9d60c
Binary files /dev/null and b/res/drawable-hdpi/apollo_holo_light_previous.png differ
diff --git a/res/drawable-hdpi/apollo_holo_light_repeat_all.png b/res/drawable-hdpi/apollo_holo_light_repeat_all.png
new file mode 100644 (file)
index 0000000..bc4c95a
Binary files /dev/null and b/res/drawable-hdpi/apollo_holo_light_repeat_all.png differ
diff --git a/res/drawable-hdpi/apollo_holo_light_repeat_normal.png b/res/drawable-hdpi/apollo_holo_light_repeat_normal.png
new file mode 100644 (file)
index 0000000..8fc95ba
Binary files /dev/null and b/res/drawable-hdpi/apollo_holo_light_repeat_normal.png differ
diff --git a/res/drawable-hdpi/apollo_holo_light_repeat_one.png b/res/drawable-hdpi/apollo_holo_light_repeat_one.png
new file mode 100644 (file)
index 0000000..4656cc0
Binary files /dev/null and b/res/drawable-hdpi/apollo_holo_light_repeat_one.png differ
diff --git a/res/drawable-hdpi/apollo_holo_light_search.png b/res/drawable-hdpi/apollo_holo_light_search.png
new file mode 100644 (file)
index 0000000..e6b7045
Binary files /dev/null and b/res/drawable-hdpi/apollo_holo_light_search.png differ
diff --git a/res/drawable-hdpi/apollo_holo_light_shuffle_normal.png b/res/drawable-hdpi/apollo_holo_light_shuffle_normal.png
new file mode 100644 (file)
index 0000000..7397176
Binary files /dev/null and b/res/drawable-hdpi/apollo_holo_light_shuffle_normal.png differ
diff --git a/res/drawable-hdpi/apollo_holo_light_shuffle_on.png b/res/drawable-hdpi/apollo_holo_light_shuffle_on.png
new file mode 100644 (file)
index 0000000..1095fcc
Binary files /dev/null and b/res/drawable-hdpi/apollo_holo_light_shuffle_on.png differ
diff --git a/res/drawable-hdpi/apollo_settings_about.png b/res/drawable-hdpi/apollo_settings_about.png
new file mode 100644 (file)
index 0000000..2c5aee8
Binary files /dev/null and b/res/drawable-hdpi/apollo_settings_about.png differ
diff --git a/res/drawable-hdpi/apollo_settings_themes.png b/res/drawable-hdpi/apollo_settings_themes.png
new file mode 100644 (file)
index 0000000..a646ad5
Binary files /dev/null and b/res/drawable-hdpi/apollo_settings_themes.png differ
index 02ee440..1783677 100644 (file)
Binary files a/res/drawable-hdpi/appwidget_bg.9.png and b/res/drawable-hdpi/appwidget_bg.9.png differ
diff --git a/res/drawable-hdpi/colorstrip_shadow.9.png b/res/drawable-hdpi/colorstrip_shadow.9.png
new file mode 100644 (file)
index 0000000..285f123
Binary files /dev/null and b/res/drawable-hdpi/colorstrip_shadow.9.png differ
diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..b0006c9
Binary files /dev/null and b/res/drawable-hdpi/ic_launcher.png differ
diff --git a/res/drawable-hdpi/title_bar_shadow.9.png b/res/drawable-hdpi/title_bar_shadow.9.png
new file mode 100644 (file)
index 0000000..e106a4c
Binary files /dev/null and b/res/drawable-hdpi/title_bar_shadow.9.png differ
diff --git a/res/drawable-mdpi/apollo_holo_dark_next.png b/res/drawable-mdpi/apollo_holo_dark_next.png
new file mode 100644 (file)
index 0000000..28e8137
Binary files /dev/null and b/res/drawable-mdpi/apollo_holo_dark_next.png differ
diff --git a/res/drawable-mdpi/apollo_holo_dark_notifiation_bar_collapse.png b/res/drawable-mdpi/apollo_holo_dark_notifiation_bar_collapse.png
new file mode 100644 (file)
index 0000000..1b4c46f
Binary files /dev/null and b/res/drawable-mdpi/apollo_holo_dark_notifiation_bar_collapse.png differ
diff --git a/res/drawable-mdpi/apollo_holo_dark_overflow.png b/res/drawable-mdpi/apollo_holo_dark_overflow.png
new file mode 100644 (file)
index 0000000..c37420e
Binary files /dev/null and b/res/drawable-mdpi/apollo_holo_dark_overflow.png differ
diff --git a/res/drawable-mdpi/apollo_holo_dark_pause.png b/res/drawable-mdpi/apollo_holo_dark_pause.png
new file mode 100644 (file)
index 0000000..a5aee6f
Binary files /dev/null and b/res/drawable-mdpi/apollo_holo_dark_pause.png differ
diff --git a/res/drawable-mdpi/apollo_holo_dark_play.png b/res/drawable-mdpi/apollo_holo_dark_play.png
new file mode 100644 (file)
index 0000000..6a40cd5
Binary files /dev/null and b/res/drawable-mdpi/apollo_holo_dark_play.png differ
diff --git a/res/drawable-mdpi/apollo_holo_light_favorite_normal.png b/res/drawable-mdpi/apollo_holo_light_favorite_normal.png
new file mode 100644 (file)
index 0000000..f4838d4
Binary files /dev/null and b/res/drawable-mdpi/apollo_holo_light_favorite_normal.png differ
diff --git a/res/drawable-mdpi/apollo_holo_light_favorite_selected.png b/res/drawable-mdpi/apollo_holo_light_favorite_selected.png
new file mode 100644 (file)
index 0000000..55e1d3b
Binary files /dev/null and b/res/drawable-mdpi/apollo_holo_light_favorite_selected.png differ
diff --git a/res/drawable-mdpi/apollo_holo_light_next.png b/res/drawable-mdpi/apollo_holo_light_next.png
new file mode 100644 (file)
index 0000000..937e029
Binary files /dev/null and b/res/drawable-mdpi/apollo_holo_light_next.png differ
diff --git a/res/drawable-mdpi/apollo_holo_light_overflow.png b/res/drawable-mdpi/apollo_holo_light_overflow.png
new file mode 100644 (file)
index 0000000..493e1f1
Binary files /dev/null and b/res/drawable-mdpi/apollo_holo_light_overflow.png differ
diff --git a/res/drawable-mdpi/apollo_holo_light_pause.png b/res/drawable-mdpi/apollo_holo_light_pause.png
new file mode 100644 (file)
index 0000000..01858e3
Binary files /dev/null and b/res/drawable-mdpi/apollo_holo_light_pause.png differ
diff --git a/res/drawable-mdpi/apollo_holo_light_play.png b/res/drawable-mdpi/apollo_holo_light_play.png
new file mode 100644 (file)
index 0000000..1e3bc97
Binary files /dev/null and b/res/drawable-mdpi/apollo_holo_light_play.png differ
diff --git a/res/drawable-mdpi/apollo_holo_light_previous.png b/res/drawable-mdpi/apollo_holo_light_previous.png
new file mode 100644 (file)
index 0000000..4e2b588
Binary files /dev/null and b/res/drawable-mdpi/apollo_holo_light_previous.png differ
diff --git a/res/drawable-mdpi/apollo_holo_light_repeat_all.png b/res/drawable-mdpi/apollo_holo_light_repeat_all.png
new file mode 100644 (file)
index 0000000..4880369
Binary files /dev/null and b/res/drawable-mdpi/apollo_holo_light_repeat_all.png differ
diff --git a/res/drawable-mdpi/apollo_holo_light_repeat_normal.png b/res/drawable-mdpi/apollo_holo_light_repeat_normal.png
new file mode 100644 (file)
index 0000000..a6e8935
Binary files /dev/null and b/res/drawable-mdpi/apollo_holo_light_repeat_normal.png differ
diff --git a/res/drawable-mdpi/apollo_holo_light_repeat_one.png b/res/drawable-mdpi/apollo_holo_light_repeat_one.png
new file mode 100644 (file)
index 0000000..8889d93
Binary files /dev/null and b/res/drawable-mdpi/apollo_holo_light_repeat_one.png differ
diff --git a/res/drawable-mdpi/apollo_holo_light_search.png b/res/drawable-mdpi/apollo_holo_light_search.png
new file mode 100644 (file)
index 0000000..3aa6440
Binary files /dev/null and b/res/drawable-mdpi/apollo_holo_light_search.png differ
diff --git a/res/drawable-mdpi/apollo_holo_light_shuffle_normal.png b/res/drawable-mdpi/apollo_holo_light_shuffle_normal.png
new file mode 100644 (file)
index 0000000..5fd81e5
Binary files /dev/null and b/res/drawable-mdpi/apollo_holo_light_shuffle_normal.png differ
diff --git a/res/drawable-mdpi/apollo_holo_light_shuffle_on.png b/res/drawable-mdpi/apollo_holo_light_shuffle_on.png
new file mode 100644 (file)
index 0000000..86c608a
Binary files /dev/null and b/res/drawable-mdpi/apollo_holo_light_shuffle_on.png differ
diff --git a/res/drawable-mdpi/apollo_settings_about.png b/res/drawable-mdpi/apollo_settings_about.png
new file mode 100644 (file)
index 0000000..5b26d8c
Binary files /dev/null and b/res/drawable-mdpi/apollo_settings_about.png differ
diff --git a/res/drawable-mdpi/apollo_settings_themes.png b/res/drawable-mdpi/apollo_settings_themes.png
new file mode 100644 (file)
index 0000000..a5ae584
Binary files /dev/null and b/res/drawable-mdpi/apollo_settings_themes.png differ
index a245d91..2ae3070 100644 (file)
Binary files a/res/drawable-mdpi/appwidget_bg.9.png and b/res/drawable-mdpi/appwidget_bg.9.png differ
diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..34ebfd0
Binary files /dev/null and b/res/drawable-mdpi/ic_launcher.png differ
diff --git a/res/drawable-mdpi/notify_panel_notification_icon_bg.png b/res/drawable-mdpi/notify_panel_notification_icon_bg.png
new file mode 100644 (file)
index 0000000..8fbf4bb
Binary files /dev/null and b/res/drawable-mdpi/notify_panel_notification_icon_bg.png differ
diff --git a/res/drawable-nodpi/promo.png b/res/drawable-nodpi/promo.png
new file mode 100644 (file)
index 0000000..651f5eb
Binary files /dev/null and b/res/drawable-nodpi/promo.png differ
diff --git a/res/drawable-xhdpi/apollo_holo_dark_next.png b/res/drawable-xhdpi/apollo_holo_dark_next.png
new file mode 100644 (file)
index 0000000..fe6b558
Binary files /dev/null and b/res/drawable-xhdpi/apollo_holo_dark_next.png differ
diff --git a/res/drawable-xhdpi/apollo_holo_dark_notifiation_bar_collapse.png b/res/drawable-xhdpi/apollo_holo_dark_notifiation_bar_collapse.png
new file mode 100644 (file)
index 0000000..5dfcf1c
Binary files /dev/null and b/res/drawable-xhdpi/apollo_holo_dark_notifiation_bar_collapse.png differ
diff --git a/res/drawable-xhdpi/apollo_holo_dark_overflow.png b/res/drawable-xhdpi/apollo_holo_dark_overflow.png
new file mode 100644 (file)
index 0000000..3a48be2
Binary files /dev/null and b/res/drawable-xhdpi/apollo_holo_dark_overflow.png differ
diff --git a/res/drawable-xhdpi/apollo_holo_dark_pause.png b/res/drawable-xhdpi/apollo_holo_dark_pause.png
new file mode 100644 (file)
index 0000000..333c1b2
Binary files /dev/null and b/res/drawable-xhdpi/apollo_holo_dark_pause.png differ
diff --git a/res/drawable-xhdpi/apollo_holo_dark_play.png b/res/drawable-xhdpi/apollo_holo_dark_play.png
new file mode 100644 (file)
index 0000000..5112499
Binary files /dev/null and b/res/drawable-xhdpi/apollo_holo_dark_play.png differ
diff --git a/res/drawable-xhdpi/apollo_holo_light_favorite_normal.png b/res/drawable-xhdpi/apollo_holo_light_favorite_normal.png
new file mode 100644 (file)
index 0000000..e6acafd
Binary files /dev/null and b/res/drawable-xhdpi/apollo_holo_light_favorite_normal.png differ
diff --git a/res/drawable-xhdpi/apollo_holo_light_favorite_selected.png b/res/drawable-xhdpi/apollo_holo_light_favorite_selected.png
new file mode 100644 (file)
index 0000000..767bf0d
Binary files /dev/null and b/res/drawable-xhdpi/apollo_holo_light_favorite_selected.png differ
diff --git a/res/drawable-xhdpi/apollo_holo_light_next.png b/res/drawable-xhdpi/apollo_holo_light_next.png
new file mode 100644 (file)
index 0000000..61b8d59
Binary files /dev/null and b/res/drawable-xhdpi/apollo_holo_light_next.png differ
diff --git a/res/drawable-xhdpi/apollo_holo_light_overflow.png b/res/drawable-xhdpi/apollo_holo_light_overflow.png
new file mode 100644 (file)
index 0000000..9a62ae0
Binary files /dev/null and b/res/drawable-xhdpi/apollo_holo_light_overflow.png differ
diff --git a/res/drawable-xhdpi/apollo_holo_light_pause.png b/res/drawable-xhdpi/apollo_holo_light_pause.png
new file mode 100644 (file)
index 0000000..97d6f91
Binary files /dev/null and b/res/drawable-xhdpi/apollo_holo_light_pause.png differ
diff --git a/res/drawable-xhdpi/apollo_holo_light_play.png b/res/drawable-xhdpi/apollo_holo_light_play.png
new file mode 100644 (file)
index 0000000..2d67d31
Binary files /dev/null and b/res/drawable-xhdpi/apollo_holo_light_play.png differ
diff --git a/res/drawable-xhdpi/apollo_holo_light_previous.png b/res/drawable-xhdpi/apollo_holo_light_previous.png
new file mode 100644 (file)
index 0000000..5ba8441
Binary files /dev/null and b/res/drawable-xhdpi/apollo_holo_light_previous.png differ
diff --git a/res/drawable-xhdpi/apollo_holo_light_repeat_all.png b/res/drawable-xhdpi/apollo_holo_light_repeat_all.png
new file mode 100644 (file)
index 0000000..1cc1063
Binary files /dev/null and b/res/drawable-xhdpi/apollo_holo_light_repeat_all.png differ
diff --git a/res/drawable-xhdpi/apollo_holo_light_repeat_normal.png b/res/drawable-xhdpi/apollo_holo_light_repeat_normal.png
new file mode 100644 (file)
index 0000000..468415a
Binary files /dev/null and b/res/drawable-xhdpi/apollo_holo_light_repeat_normal.png differ
diff --git a/res/drawable-xhdpi/apollo_holo_light_repeat_one.png b/res/drawable-xhdpi/apollo_holo_light_repeat_one.png
new file mode 100644 (file)
index 0000000..d9d4c20
Binary files /dev/null and b/res/drawable-xhdpi/apollo_holo_light_repeat_one.png differ
diff --git a/res/drawable-xhdpi/apollo_holo_light_search.png b/res/drawable-xhdpi/apollo_holo_light_search.png
new file mode 100644 (file)
index 0000000..804420a
Binary files /dev/null and b/res/drawable-xhdpi/apollo_holo_light_search.png differ
diff --git a/res/drawable-xhdpi/apollo_holo_light_shuffle_normal.png b/res/drawable-xhdpi/apollo_holo_light_shuffle_normal.png
new file mode 100644 (file)
index 0000000..eee9d97
Binary files /dev/null and b/res/drawable-xhdpi/apollo_holo_light_shuffle_normal.png differ
diff --git a/res/drawable-xhdpi/apollo_holo_light_shuffle_on.png b/res/drawable-xhdpi/apollo_holo_light_shuffle_on.png
new file mode 100644 (file)
index 0000000..6b7468d
Binary files /dev/null and b/res/drawable-xhdpi/apollo_holo_light_shuffle_on.png differ
diff --git a/res/drawable-xhdpi/apollo_settings_about.png b/res/drawable-xhdpi/apollo_settings_about.png
new file mode 100644 (file)
index 0000000..3b09a5d
Binary files /dev/null and b/res/drawable-xhdpi/apollo_settings_about.png differ
diff --git a/res/drawable-xhdpi/apollo_settings_themes.png b/res/drawable-xhdpi/apollo_settings_themes.png
new file mode 100644 (file)
index 0000000..57dd2a5
Binary files /dev/null and b/res/drawable-xhdpi/apollo_settings_themes.png differ
index 7ccb762..909f498 100644 (file)
Binary files a/res/drawable-xhdpi/appwidget_bg.9.png and b/res/drawable-xhdpi/appwidget_bg.9.png differ
diff --git a/res/drawable-xhdpi/dropdown_ic_arrow_normal_holo_light.png b/res/drawable-xhdpi/dropdown_ic_arrow_normal_holo_light.png
new file mode 100644 (file)
index 0000000..36d8cf4
Binary files /dev/null and b/res/drawable-xhdpi/dropdown_ic_arrow_normal_holo_light.png differ
diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/drawable-xhdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..d22f8f3
Binary files /dev/null and b/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/res/drawable-xhdpi/indicator_playing_peak_meter_1.png b/res/drawable-xhdpi/indicator_playing_peak_meter_1.png
new file mode 100644 (file)
index 0000000..b5c524e
Binary files /dev/null and b/res/drawable-xhdpi/indicator_playing_peak_meter_1.png differ
diff --git a/res/drawable-xhdpi/indicator_playing_peak_meter_2.png b/res/drawable-xhdpi/indicator_playing_peak_meter_2.png
new file mode 100644 (file)
index 0000000..6f48de3
Binary files /dev/null and b/res/drawable-xhdpi/indicator_playing_peak_meter_2.png differ
diff --git a/res/drawable-xhdpi/indicator_playing_peak_meter_3.png b/res/drawable-xhdpi/indicator_playing_peak_meter_3.png
new file mode 100644 (file)
index 0000000..485f52c
Binary files /dev/null and b/res/drawable-xhdpi/indicator_playing_peak_meter_3.png differ
diff --git a/res/drawable-xhdpi/indicator_playing_peak_meter_4.png b/res/drawable-xhdpi/indicator_playing_peak_meter_4.png
new file mode 100644 (file)
index 0000000..a148d0e
Binary files /dev/null and b/res/drawable-xhdpi/indicator_playing_peak_meter_4.png differ
diff --git a/res/drawable-xhdpi/indicator_playing_peak_meter_5.png b/res/drawable-xhdpi/indicator_playing_peak_meter_5.png
new file mode 100644 (file)
index 0000000..e85552c
Binary files /dev/null and b/res/drawable-xhdpi/indicator_playing_peak_meter_5.png differ
diff --git a/res/drawable-xhdpi/list_section_divider_holo_custom.9.png b/res/drawable-xhdpi/list_section_divider_holo_custom.9.png
new file mode 100644 (file)
index 0000000..0bd8a0f
Binary files /dev/null and b/res/drawable-xhdpi/list_section_divider_holo_custom.9.png differ
diff --git a/res/drawable-xhdpi/notify_panel_notification_icon_bg.png b/res/drawable-xhdpi/notify_panel_notification_icon_bg.png
new file mode 100644 (file)
index 0000000..adbe4d2
Binary files /dev/null and b/res/drawable-xhdpi/notify_panel_notification_icon_bg.png differ
diff --git a/res/drawable-xhdpi/queue_thumbnail_bg.9.png b/res/drawable-xhdpi/queue_thumbnail_bg.9.png
new file mode 100644 (file)
index 0000000..80fc849
Binary files /dev/null and b/res/drawable-xhdpi/queue_thumbnail_bg.9.png differ
diff --git a/res/drawable-xhdpi/recents_thumbnail_bg_press.9.png b/res/drawable-xhdpi/recents_thumbnail_bg_press.9.png
new file mode 100644 (file)
index 0000000..5bae56d
Binary files /dev/null and b/res/drawable-xhdpi/recents_thumbnail_bg_press.9.png differ
diff --git a/res/drawable-xhdpi/stat_notify_music.png b/res/drawable-xhdpi/stat_notify_music.png
new file mode 100644 (file)
index 0000000..e62aa01
Binary files /dev/null and b/res/drawable-xhdpi/stat_notify_music.png differ
diff --git a/res/drawable-xhdpi/tab_selected_holo.9.png b/res/drawable-xhdpi/tab_selected_holo.9.png
new file mode 100644 (file)
index 0000000..1d66449
Binary files /dev/null and b/res/drawable-xhdpi/tab_selected_holo.9.png differ
diff --git a/res/drawable-xhdpi/tab_selected_pressed_focused_holo.9.png b/res/drawable-xhdpi/tab_selected_pressed_focused_holo.9.png
new file mode 100644 (file)
index 0000000..e9f327f
Binary files /dev/null and b/res/drawable-xhdpi/tab_selected_pressed_focused_holo.9.png differ
diff --git a/res/drawable-xhdpi/tab_selected_pressed_holo.9.png b/res/drawable-xhdpi/tab_selected_pressed_holo.9.png
new file mode 100644 (file)
index 0000000..79a1e0a
Binary files /dev/null and b/res/drawable-xhdpi/tab_selected_pressed_holo.9.png differ
diff --git a/res/drawable-xhdpi/tab_unselected_focused_holo.9.png b/res/drawable-xhdpi/tab_unselected_focused_holo.9.png
new file mode 100644 (file)
index 0000000..823638f
Binary files /dev/null and b/res/drawable-xhdpi/tab_unselected_focused_holo.9.png differ
diff --git a/res/drawable-xhdpi/tab_unselected_holo.9.png b/res/drawable-xhdpi/tab_unselected_holo.9.png
new file mode 100644 (file)
index 0000000..244f04b
Binary files /dev/null and b/res/drawable-xhdpi/tab_unselected_holo.9.png differ
diff --git a/res/drawable-xhdpi/tab_unselected_pressed_holo.9.png b/res/drawable-xhdpi/tab_unselected_pressed_holo.9.png
new file mode 100644 (file)
index 0000000..a75e182
Binary files /dev/null and b/res/drawable-xhdpi/tab_unselected_pressed_holo.9.png differ
diff --git a/res/drawable/holo_selector.xml b/res/drawable/holo_selector.xml
new file mode 100644 (file)
index 0000000..46330f1
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<selector xmlns:android="http://schemas.android.com/apk/res/android" android:exitFadeDuration="@android:integer/config_mediumAnimTime">\r
+\r
+    <item android:drawable="@color/holo_blue_dark" android:state_pressed="true"/>\r
+    <item android:drawable="@color/holo_blue_dark" android:state_enabled="true" android:state_focused="true"/>\r
+    <item android:drawable="@color/transparent"/>\r
+\r
+</selector>
\ No newline at end of file
diff --git a/res/drawable/queue_thumbnail_fg.xml b/res/drawable/queue_thumbnail_fg.xml
new file mode 100644 (file)
index 0000000..d1201c9
--- /dev/null
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2011 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:drawable="@drawable/recents_thumbnail_bg_press" android:state_selected="true"/>
+    <item android:drawable="@drawable/recents_thumbnail_bg_press" android:state_pressed="true"/>
+    <item android:drawable="@color/transparent"/>
+
+</selector>
\ No newline at end of file
diff --git a/res/drawable/tab.xml b/res/drawable/tab.xml
new file mode 100644 (file)
index 0000000..a6e1b55
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<selector xmlns:android="http://schemas.android.com/apk/res/android">\r
+\r
+    <item android:drawable="@drawable/tab_unselected_holo" android:state_focused="false" android:state_pressed="false" android:state_selected="false"/>\r
+    <item android:drawable="@drawable/tab_selected_holo" android:state_focused="false" android:state_pressed="false" android:state_selected="true"/>\r
+    <item android:drawable="@drawable/tab_unselected_focused_holo" android:state_focused="true" android:state_pressed="false" android:state_selected="false"/>\r
+    <item android:drawable="@drawable/tab_unselected_pressed_holo" android:state_focused="false" android:state_pressed="true" android:state_selected="false"/>\r
+    <item android:drawable="@drawable/tab_selected_pressed_holo" android:state_focused="false" android:state_pressed="true" android:state_selected="true"/>\r
+    <item android:drawable="@drawable/tab_selected_pressed_focused_holo" android:state_focused="true" android:state_pressed="true" android:state_selected="true"/>\r
+\r
+</selector>
\ No newline at end of file
diff --git a/res/drawable/viewpager_margin.xml b/res/drawable/viewpager_margin.xml
new file mode 100644 (file)
index 0000000..850607c
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<shape xmlns:android="http://schemas.android.com/apk/res/android"\r
+    android:shape="rectangle" >\r
+\r
+    <solid android:color="@color/transparent" />\r
+\r
+    <stroke\r
+        android:width="@dimen/viewpager_margin_stroke_width"\r
+        android:color="@color/transparent_black" />\r
+\r
+</shape>
\ No newline at end of file
diff --git a/res/layout/audio_controls.xml b/res/layout/audio_controls.xml
new file mode 100644 (file)
index 0000000..e9602b0
--- /dev/null
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<merge xmlns:android="http://schemas.android.com/apk/res/android" >\r
+\r
+    <RelativeLayout\r
+        android:layout_width="match_parent"\r
+        android:layout_height="wrap_content" >\r
+\r
+        <SeekBar\r
+            android:id="@android:id/progress"\r
+            style="?android:attr/progressBarStyleHorizontal"\r
+            android:layout_width="match_parent"\r
+            android:layout_height="wrap_content"\r
+            android:paddingTop="@dimen/audio_player_seek_bar_padding"\r
+            android:thumb="@null" />\r
+\r
+        <TextView\r
+            android:id="@+id/audio_player_current_time"\r
+            style="@style/AudioPlayerText"\r
+            android:layout_alignParentLeft="true"\r
+            android:layout_below="@android:id/progress"\r
+            android:paddingLeft="@dimen/audio_player_info_container_padding"\r
+            android:paddingRight="@dimen/audio_player_info_container_padding"\r
+            android:textColor="@color/transparent_black"\r
+            android:textSize="@dimen/text_size_small"\r
+            android:textStyle="bold" />\r
+\r
+        <TextView\r
+            android:id="@+id/audio_player_total_time"\r
+            style="@style/AudioPlayerText"\r
+            android:layout_alignParentRight="true"\r
+            android:layout_below="@android:id/progress"\r
+            android:paddingLeft="@dimen/audio_player_info_container_padding"\r
+            android:paddingRight="@dimen/audio_player_info_container_padding"\r
+            android:textColor="@color/transparent_black"\r
+            android:textSize="@dimen/text_size_small"\r
+            android:textStyle="bold" />\r
+    </RelativeLayout>\r
+\r
+    <LinearLayout\r
+        android:layout_width="match_parent"\r
+        android:layout_height="@dimen/audio_player_controls_height"\r
+        android:layout_gravity="bottom"\r
+        android:orientation="horizontal"\r
+        android:paddingBottom="@dimen/audio_player_button_container_padding" >\r
+\r
+        <ImageButton\r
+            android:id="@+id/audio_player_repeat"\r
+            style="@style/AudioPlayerButton"\r
+            android:contentDescription="@string/cd_repeat"\r
+            android:src="@drawable/apollo_holo_light_repeat_normal" />\r
+\r
+        <com.andrew.apollo.ui.widgets.RepeatingImageButton\r
+            android:id="@+id/audio_player_prev"\r
+            style="@style/AudioPlayerButton"\r
+            android:src="@drawable/apollo_holo_light_previous" />\r
+\r
+        <ImageButton\r
+            android:id="@+id/audio_player_play"\r
+            style="@style/AudioPlayerButton"\r
+            android:contentDescription="@string/cd_play"\r
+            android:src="@drawable/apollo_holo_light_pause" />\r
+\r
+        <com.andrew.apollo.ui.widgets.RepeatingImageButton\r
+            android:id="@+id/audio_player_next"\r
+            style="@style/AudioPlayerButton"\r
+            android:src="@drawable/apollo_holo_light_next" />\r
+\r
+        <ImageButton\r
+            android:id="@+id/audio_player_shuffle"\r
+            style="@style/AudioPlayerButton"\r
+            android:contentDescription="@string/cd_shuffle"\r
+            android:src="@drawable/apollo_holo_light_shuffle_normal" />\r
+    </LinearLayout>\r
+\r
+</merge>
\ No newline at end of file
index b86cf90..fa55223 100644 (file)
-<?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:id="@+id/now_playing_bg"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:background="@drawable/player_background"
-    android:orientation="vertical" >
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="0dip"
-        android:layout_weight="1" >
-
-        <ImageView
-            android:id="@+id/album"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:scaleType="fitXY" />
-
-        <ImageView
-            android:id="@+id/album_dummy"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:scaleType="fitXY"
-            android:visibility="gone" />
-    </LinearLayout>
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:baselineAligned="false"
-        android:orientation="horizontal"
-        android:paddingBottom="6dip"
-        android:paddingLeft="11dip"
-        android:paddingTop="4dip" >
-
-        <TextView
-            android:id="@+id/trackname"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical"
-            android:ellipsize="end"
-            android:shadowColor="#ff000000"
-            android:shadowDx="0"
-            android:shadowDy="0"
-            android:shadowRadius="3"
-            android:singleLine="true"
-            android:textColor="#ffffffff"
-            android:textSize="19sp"
-            android:textStyle="bold" />
-    </LinearLayout>
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:baselineAligned="false"
-        android:orientation="horizontal"
-        android:paddingBottom="10dip"
-        android:paddingLeft="13dip"
-        android:paddingTop="4dip" >
-
-        <TextView
-            android:id="@+id/albumname"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical"
-            android:ellipsize="end"
-            android:shadowColor="#ff000000"
-            android:shadowDx="0"
-            android:shadowDy="0"
-            android:shadowRadius="3"
-            android:singleLine="true"
-            android:textColor="#ff999999"
-            android:textSize="16sp" />
-    </LinearLayout>
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:baselineAligned="false"
-        android:orientation="horizontal"
-        android:paddingBottom="2dip"
-        android:paddingTop="4dip"
-        android:visibility="gone" >
-
-        <TextView
-            android:id="@+id/artistname"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical"
-            android:ellipsize="end"
-            android:singleLine="true"
-            android:textSize="14sp"
-            android:textStyle="bold" />
-    </LinearLayout>
-
-    <include layout="@layout/audio_player_common" />
-
+<?xml version="1.0" encoding="utf-8"?>\r
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    xmlns:tools="http://schemas.android.com/tools"\r
+    android:layout_width="match_parent"\r
+    android:layout_height="match_parent"\r
+    android:orientation="vertical"\r
+    tools:ignore="ContentDescription" >\r
+\r
+    <LinearLayout\r
+        android:layout_width="match_parent"\r
+        android:layout_height="0dp"\r
+        android:layout_weight="1"\r
+        android:background="@color/black" >\r
+\r
+        <ImageView\r
+            android:id="@+id/audio_player_album_art"\r
+            android:layout_width="match_parent"\r
+            android:layout_height="match_parent"\r
+            android:scaleType="fitXY" />\r
+    </LinearLayout>\r
+\r
+    <RelativeLayout\r
+        android:layout_width="wrap_content"\r
+        android:layout_height="wrap_content"\r
+        android:paddingLeft="@dimen/audio_player_info_container_padding"\r
+        android:paddingRight="@dimen/audio_player_info_container_padding"\r
+        android:paddingTop="@dimen/audio_player_artwork_padding" >\r
+\r
+        <TextView\r
+            android:id="@+id/audio_player_track"\r
+            style="@style/AudioPlayerText"\r
+            android:textColor="@color/black"\r
+            android:textSize="@dimen/text_size_large"\r
+            android:textStyle="bold" />\r
+\r
+        <TextView\r
+            android:id="@+id/audio_player_album_artist"\r
+            style="@style/AudioPlayerText"\r
+            android:layout_below="@+id/audio_player_track"\r
+            android:textColor="@color/transparent_black"\r
+            android:textSize="@dimen/text_size_small" />\r
+    </RelativeLayout>\r
+\r
+    <include layout="@layout/audio_controls" />\r
+\r
 </LinearLayout>
\ No newline at end of file
diff --git a/res/layout/audio_player_browser.xml b/res/layout/audio_player_browser.xml
new file mode 100644 (file)
index 0000000..9c57385
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    android:layout_width="match_parent"\r
+    android:layout_height="match_parent"\r
+    android:orientation="vertical" >\r
+\r
+    <include layout="@layout/colorstrip" />\r
+\r
+    <android.support.v4.view.ViewPager\r
+        android:id="@+id/viewPager"\r
+        android:layout_width="match_parent"\r
+        android:layout_height="match_parent" />\r
+\r
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/bottom_action_bar.xml b/res/layout/bottom_action_bar.xml
new file mode 100644 (file)
index 0000000..2994335
--- /dev/null
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    android:id="@+id/bottom_action_bar_container"\r
+    android:layout_width="match_parent"\r
+    android:layout_height="@dimen/bottom_action_bar_height"\r
+    android:layout_gravity="bottom"\r
+    android:orientation="vertical" >\r
+\r
+    <ImageView\r
+        android:id="@+id/bottom_action_bar_info_divider"\r
+        android:layout_width="match_parent"\r
+        android:layout_height="@dimen/bottom_action_bar_divider_height"\r
+        android:background="@color/holo_blue_dark" />\r
+\r
+    <com.andrew.apollo.ui.widgets.BottomActionBar\r
+        android:id="@+id/bottom_action_bar"\r
+        android:layout_width="match_parent"\r
+        android:layout_height="match_parent"\r
+        android:background="@drawable/holo_selector"\r
+        android:orientation="horizontal" >\r
+\r
+        <ImageView\r
+            android:id="@+id/bottom_action_bar_album_art"\r
+            android:layout_width="@dimen/bottom_action_bar_album_art_width_height"\r
+            android:layout_height="@dimen/bottom_action_bar_album_art_width_height"\r
+            android:layout_gravity="left|center"\r
+            android:contentDescription="@string/cd_bottom_action_bar_album_art"\r
+            android:scaleType="fitXY" />\r
+\r
+        <LinearLayout\r
+            android:layout_width="0dp"\r
+            android:layout_height="wrap_content"\r
+            android:layout_gravity="center_vertical"\r
+            android:layout_weight="1"\r
+            android:orientation="vertical"\r
+            android:paddingLeft="@dimen/bottom_action_bar_info_padding_left" >\r
+\r
+            <TextView\r
+                android:id="@+id/bottom_action_bar_track_name"\r
+                style="@style/BottomActionBarText"\r
+                android:textColor="@color/transparent_black"\r
+                android:textStyle="bold" />\r
+\r
+            <TextView\r
+                android:id="@+id/bottom_action_bar_artist_name"\r
+                style="@style/BottomActionBarText"\r
+                android:textColor="@color/transparent_black" />\r
+        </LinearLayout>\r
+\r
+        <com.andrew.apollo.ui.widgets.BottomActionBarItem\r
+            android:id="@+id/bottom_action_bar_item_one"\r
+            style="@style/BottomActionBarItem"\r
+            android:contentDescription="@string/cd_favorite"\r
+            android:src="@drawable/apollo_holo_light_favorite_normal" />\r
+\r
+        <com.andrew.apollo.ui.widgets.BottomActionBarItem\r
+            android:id="@+id/bottom_action_bar_item_two"\r
+            style="@style/BottomActionBarItem"\r
+            android:contentDescription="@string/cd_search"\r
+            android:src="@drawable/apollo_holo_light_search" />\r
+\r
+        <com.andrew.apollo.ui.widgets.BottomActionBarItem\r
+            android:id="@+id/bottom_action_bar_item_three"\r
+            style="@style/BottomActionBarItem"\r
+            android:contentDescription="@string/cd_overflow"\r
+            android:src="@drawable/apollo_holo_light_overflow" />\r
+    </com.andrew.apollo.ui.widgets.BottomActionBar>\r
+\r
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/bottom_action_bar_controls.xml b/res/layout/bottom_action_bar_controls.xml
new file mode 100644 (file)
index 0000000..26678b6
--- /dev/null
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    android:layout_width="match_parent"\r
+    android:layout_height="@dimen/bottom_action_bar_height"\r
+    android:layout_gravity="bottom"\r
+    android:orientation="vertical" >\r
+\r
+    <ImageView\r
+        android:layout_width="match_parent"\r
+        android:layout_height="@dimen/bottom_action_bar_divider_height"\r
+        android:background="@color/holo_blue_dark" />\r
+\r
+    <LinearLayout\r
+        android:layout_width="match_parent"\r
+        android:layout_height="match_parent"\r
+        android:orientation="horizontal" >\r
+\r
+        <ImageButton\r
+            android:id="@+id/bottom_action_bar_repeat"\r
+            style="@style/BottomActionBarItem"\r
+            android:layout_weight="1"\r
+            android:contentDescription="@string/cd_repeat"\r
+            android:src="@drawable/apollo_holo_light_repeat_normal" />\r
+\r
+        <ImageButton\r
+            android:id="@+id/bottom_action_bar_previous"\r
+            style="@style/BottomActionBarItem"\r
+            android:layout_weight="1"\r
+            android:contentDescription="@string/cd_previous"\r
+            android:src="@drawable/apollo_holo_light_previous" />\r
+\r
+        <ImageButton\r
+            android:id="@+id/bottom_action_bar_play"\r
+            style="@style/BottomActionBarItem"\r
+            android:layout_weight="1"\r
+            android:contentDescription="@string/cd_play"\r
+            android:src="@drawable/apollo_holo_light_play" />\r
+\r
+        <ImageButton\r
+            android:id="@+id/bottom_action_bar_next"\r
+            style="@style/BottomActionBarItem"\r
+            android:layout_weight="1"\r
+            android:contentDescription="@string/cd_next"\r
+            android:src="@drawable/apollo_holo_light_next" />\r
+\r
+        <ImageButton\r
+            android:id="@+id/bottom_action_bar_shuffle"\r
+            style="@style/BottomActionBarItem"\r
+            android:layout_weight="1"\r
+            android:contentDescription="@string/cd_shuffle"\r
+            android:src="@drawable/apollo_holo_light_shuffle_normal" />\r
+    </LinearLayout>\r
+\r
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/colorstrip.xml b/res/layout/colorstrip.xml
new file mode 100644 (file)
index 0000000..b04eb8c
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    xmlns:tools="http://schemas.android.com/tools"\r
+    android:id="@+id/colorstrip"\r
+    android:layout_width="match_parent"\r
+    android:layout_height="@dimen/colorstrip_height"\r
+    android:foreground="@drawable/colorstrip_shadow"\r
+    tools:ignore="Overdraw" />\r
diff --git a/res/layout/context_menu.xml b/res/layout/context_menu.xml
new file mode 100644 (file)
index 0000000..9802971
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    android:id="@+id/track_list_context_frame"\r
+    android:layout_width="wrap_content"\r
+    android:layout_height="@dimen/listview_item_height"\r
+    android:layout_alignParentRight="true"\r
+    android:background="@drawable/holo_selector"\r
+    android:clickable="true"\r
+    android:paddingRight="@dimen/quick_context_padding_right" >\r
+\r
+    <ImageView\r
+        android:id="@+id/quick_context_line"\r
+        android:layout_width="@dimen/quick_context_line_width"\r
+        android:layout_height="@dimen/quick_context_line_height"\r
+        android:layout_gravity="center|left"\r
+        android:background="@color/transparent_black" />\r
+\r
+    <ImageView\r
+        android:id="@+id/quick_context_tip"\r
+        android:layout_width="wrap_content"\r
+        android:layout_height="match_parent"\r
+        android:layout_gravity="center"\r
+        android:layout_marginRight="@dimen/quick_context_margin_right"\r
+        android:scaleType="centerInside"\r
+        android:src="@drawable/dropdown_ic_arrow_normal_holo_light" />\r
+\r
+</FrameLayout>
\ No newline at end of file
diff --git a/res/layout/context_menu_header.xml b/res/layout/context_menu_header.xml
new file mode 100644 (file)
index 0000000..ba41e56
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    android:layout_width="match_parent"\r
+    android:layout_height="wrap_content"\r
+    android:background="@color/black" >\r
+\r
+    <ImageView\r
+        android:id="@+id/header_image"\r
+        android:layout_width="match_parent"\r
+        android:layout_height="@dimen/half_and_half_image_height" />\r
+\r
+    <TextView\r
+        android:id="@+id/header_text"\r
+        style="@style/HeaderText"\r
+        android:layout_alignBottom="@+id/header_image" />\r
+\r
+</RelativeLayout>
\ No newline at end of file
diff --git a/res/layout/custom_action_bar.xml b/res/layout/custom_action_bar.xml
new file mode 100644 (file)
index 0000000..090bb1c
--- /dev/null
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    android:id="@+id/action_bar_layout"\r
+    android:layout_width="96dp"\r
+    android:layout_height="wrap_content"\r
+    android:layout_gravity="right|center"\r
+    android:background="@drawable/holo_selector"\r
+    android:clickable="true"\r
+    android:focusable="true" >\r
+\r
+    <ImageView\r
+        android:id="@+id/action_bar_album_art"\r
+        android:layout_width="48dp"\r
+        android:layout_height="48dp"\r
+        android:layout_alignParentBottom="true"\r
+        android:layout_alignParentRight="true"\r
+        android:layout_alignParentTop="true" />\r
+\r
+    <TextView\r
+        android:id="@+id/action_bar_track_name"\r
+        android:layout_width="wrap_content"\r
+        android:layout_height="wrap_content"\r
+        android:layout_alignParentTop="true"\r
+        android:layout_toLeftOf="@+id/action_bar_album_art"\r
+        android:ellipsize="end"\r
+        android:paddingRight="5dp"\r
+        android:paddingTop="10dp"\r
+        android:singleLine="true"\r
+        android:textColor="@color/transparent_black"\r
+        android:textSize="@dimen/text_size_micro"\r
+        android:textStyle="bold" />\r
+\r
+    <TextView\r
+        android:id="@+id/action_bar_album_name"\r
+        android:layout_width="wrap_content"\r
+        android:layout_height="wrap_content"\r
+        android:layout_below="@+id/action_bar_track_name"\r
+        android:layout_toLeftOf="@+id/action_bar_album_art"\r
+        android:ellipsize="end"\r
+        android:paddingRight="5dp"\r
+        android:singleLine="true"\r
+        android:textColor="@color/transparent_black"\r
+        android:textSize="@dimen/text_size_micro" />\r
+\r
+</RelativeLayout>
\ No newline at end of file
diff --git a/res/layout/empty_view.xml b/res/layout/empty_view.xml
new file mode 100644 (file)
index 0000000..aa29c7a
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    android:layout_width="wrap_content"\r
+    android:layout_height="@dimen/bottom_action_bar_height" />
\ No newline at end of file
diff --git a/res/layout/fourbyone_app_widget.xml b/res/layout/fourbyone_app_widget.xml
new file mode 100644 (file)
index 0000000..8e0cf1c
--- /dev/null
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    xmlns:tools="http://schemas.android.com/tools"\r
+    android:layout_width="match_parent"\r
+    android:layout_height="match_parent"\r
+    android:background="@drawable/appwidget_bg"\r
+    android:orientation="horizontal"\r
+    tools:ignore="Overdraw" >\r
+\r
+    <ImageView\r
+        android:id="@+id/four_by_one_albumart"\r
+        android:layout_width="@dimen/four_by_one_album_art_width"\r
+        android:layout_height="match_parent"\r
+        android:scaleType="centerCrop" />\r
+\r
+    <LinearLayout\r
+        android:id="@+id/four_by_one_album_appwidget"\r
+        android:layout_width="0dp"\r
+        android:layout_height="match_parent"\r
+        android:layout_weight="2"\r
+        android:background="@drawable/holo_selector"\r
+        android:clickable="true"\r
+        android:focusable="true"\r
+        android:gravity="center"\r
+        android:orientation="vertical"\r
+        android:paddingLeft="4dp" >\r
+\r
+        <TextView\r
+            android:id="@+id/four_by_one_title"\r
+            android:layout_width="match_parent"\r
+            android:layout_height="wrap_content"\r
+            android:ellipsize="end"\r
+            android:singleLine="true"\r
+            android:textColor="@color/transparent_black"\r
+            android:textSize="@dimen/text_size_small"\r
+            android:textStyle="bold" />\r
+\r
+        <TextView\r
+            android:id="@+id/four_by_one_artist"\r
+            android:layout_width="match_parent"\r
+            android:layout_height="wrap_content"\r
+            android:ellipsize="end"\r
+            android:singleLine="true"\r
+            android:textColor="@color/transparent_black"\r
+            android:textSize="@dimen/text_size_small" />\r
+    </LinearLayout>\r
+\r
+    <ImageButton\r
+        android:id="@+id/four_by_one_control_prev"\r
+        android:layout_width="0dp"\r
+        android:layout_height="match_parent"\r
+        android:layout_weight="1"\r
+        android:background="@drawable/holo_selector"\r
+        android:scaleType="center"\r
+        android:src="@drawable/apollo_holo_light_previous"\r
+        android:visibility="gone" />\r
+\r
+    <ImageView\r
+        android:layout_width="0.2dp"\r
+        android:layout_height="match_parent"\r
+        android:background="@color/transparent_black" />\r
+\r
+    <ImageButton\r
+        android:id="@+id/four_by_one_control_play"\r
+        android:layout_width="0dp"\r
+        android:layout_height="match_parent"\r
+        android:layout_weight="1"\r
+        android:background="@drawable/holo_selector"\r
+        android:scaleType="center"\r
+        android:src="@drawable/apollo_holo_light_play" />\r
+\r
+    <ImageView\r
+        android:layout_width="0.2dp"\r
+        android:layout_height="match_parent"\r
+        android:background="@color/transparent_black" />\r
+\r
+    <ImageButton\r
+        android:id="@+id/four_by_one_control_next"\r
+        android:layout_width="0dp"\r
+        android:layout_height="match_parent"\r
+        android:layout_weight="1"\r
+        android:background="@drawable/holo_selector"\r
+        android:scaleType="center"\r
+        android:src="@drawable/apollo_holo_light_next" />\r
+\r
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/fourbytwo_app_widget.xml b/res/layout/fourbytwo_app_widget.xml
new file mode 100644 (file)
index 0000000..05fda67
--- /dev/null
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    android:id="@+id/four_by_two"\r
+    android:layout_width="match_parent"\r
+    android:layout_height="@dimen/four_by_two_height"\r
+    android:background="@drawable/appwidget_bg"\r
+    android:gravity="center"\r
+    android:orientation="horizontal" >\r
+\r
+    <LinearLayout\r
+        android:id="@+id/four_by_two_controls"\r
+        android:layout_width="match_parent"\r
+        android:layout_height="@dimen/four_by_two_control_height"\r
+        android:layout_alignParentBottom="true"\r
+        android:orientation="horizontal" >\r
+\r
+        <ImageButton\r
+            android:id="@+id/four_by_two_control_prev"\r
+            style="@style/FourByTwoMediaButton"\r
+            android:src="@drawable/apollo_holo_light_previous" />\r
+\r
+        <ImageButton\r
+            android:id="@+id/four_by_two_control_play"\r
+            style="@style/FourByTwoMediaButton"\r
+            android:src="@drawable/apollo_holo_light_play" />\r
+\r
+        <ImageButton\r
+            android:id="@+id/four_by_two_control_next"\r
+            style="@style/FourByTwoMediaButton"\r
+            android:src="@drawable/apollo_holo_light_next" />\r
+\r
+        <ImageButton\r
+            android:id="@+id/four_by_two_control_shuffle"\r
+            style="@style/FourByTwoMediaButton"\r
+            android:src="@drawable/apollo_holo_light_shuffle_normal" />\r
+\r
+        <ImageButton\r
+            android:id="@+id/four_by_two_control_repeat"\r
+            style="@style/FourByTwoMediaButton"\r
+            android:src="@drawable/apollo_holo_light_repeat_normal" />\r
+    </LinearLayout>\r
+\r
+    <ImageView\r
+        android:id="@+id/four_by_two_controls_info_divider"\r
+        android:layout_width="match_parent"\r
+        android:layout_height="1dp"\r
+        android:layout_above="@id/four_by_two_controls"\r
+        android:scaleType="fitXY" />\r
+\r
+    <ImageView\r
+        android:id="@+id/four_by_two_albumart"\r
+        android:layout_width="@dimen/four_by_two_album_art_width"\r
+        android:layout_height="match_parent"\r
+        android:layout_above="@id/four_by_two_controls_info_divider"\r
+        android:adjustViewBounds="true"\r
+        android:scaleType="fitXY" />\r
+\r
+    <ImageView\r
+        android:layout_width="match_parent"\r
+        android:layout_height="1dp"\r
+        android:layout_above="@id/four_by_two_controls"\r
+        android:background="@color/holo_blue_dark" />\r
+\r
+    <LinearLayout\r
+        android:id="@+id/four_by_two_info"\r
+        android:layout_width="match_parent"\r
+        android:layout_height="match_parent"\r
+        android:layout_above="@id/four_by_two_controls"\r
+        android:layout_toRightOf="@+id/four_by_two_albumart"\r
+        android:background="@drawable/holo_selector"\r
+        android:clickable="true"\r
+        android:focusable="true"\r
+        android:gravity="center"\r
+        android:orientation="vertical" >\r
+\r
+        <TextView\r
+            android:id="@+id/four_by_two_trackname"\r
+            android:layout_width="wrap_content"\r
+            android:layout_height="wrap_content"\r
+            android:ellipsize="end"\r
+            android:singleLine="true"\r
+            android:textColor="@color/transparent_black"\r
+            android:textSize="@dimen/text_size_small"\r
+            android:textStyle="bold" />\r
+\r
+        <TextView\r
+            android:id="@+id/four_by_two_albumname"\r
+            android:layout_width="wrap_content"\r
+            android:layout_height="wrap_content"\r
+            android:ellipsize="end"\r
+            android:singleLine="true"\r
+            android:textColor="@color/transparent_black"\r
+            android:textSize="@dimen/text_size_small" />\r
+\r
+        <TextView\r
+            android:id="@+id/four_by_two_artistname"\r
+            android:layout_width="wrap_content"\r
+            android:layout_height="wrap_content"\r
+            android:ellipsize="end"\r
+            android:singleLine="true"\r
+            android:textColor="@color/transparent_black"\r
+            android:textSize="@dimen/text_size_small" />\r
+    </LinearLayout>\r
+\r
+</RelativeLayout>
\ No newline at end of file
diff --git a/res/layout/gridview.xml b/res/layout/gridview.xml
new file mode 100644 (file)
index 0000000..5e3e940
--- /dev/null
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    android:layout_width="match_parent"\r
+    android:layout_height="wrap_content"\r
+    android:gravity="center_vertical"\r
+    android:orientation="vertical" >\r
+\r
+    <include layout="@layout/shadow" />\r
+\r
+    <GridView\r
+        android:id="@+id/gridview"\r
+        android:layout_width="match_parent"\r
+        android:layout_height="0dp"\r
+        android:layout_weight="1"\r
+        android:cacheColorHint="@color/transparent"\r
+        android:drawSelectorOnTop="true"\r
+        android:numColumns="@integer/gridview_columns"\r
+        android:scrollbars="none" />\r
+\r
+    <include layout="@layout/empty_view" />\r
+\r
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/gridview_items.xml b/res/layout/gridview_items.xml
new file mode 100644 (file)
index 0000000..2c6462f
--- /dev/null
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    android:layout_width="match_parent"\r
+    android:layout_height="wrap_content" >\r
+\r
+    <ImageView\r
+        android:id="@+id/gridview_image"\r
+        android:layout_width="@dimen/gridview_image_width"\r
+        android:layout_height="@dimen/gridview_image_height"\r
+        android:scaleType="centerCrop" />\r
+\r
+    <LinearLayout\r
+        android:id="@+id/gridview_info_holder"\r
+        android:layout_width="match_parent"\r
+        android:layout_height="@dimen/gridview_item_ccontainer_height"\r
+        android:layout_alignParentBottom="true"\r
+        android:background="@color/transparent_black"\r
+        android:gravity="center_vertical"\r
+        android:orientation="vertical"\r
+        android:paddingLeft="@dimen/gridview_item_ccontainer_padding_left"\r
+        android:paddingRight="@dimen/gridview_item_ccontainer_padding_right" >\r
+\r
+        <TextView\r
+            android:id="@+id/gridview_line_one"\r
+            style="@style/GridViewTextItem"\r
+            android:textStyle="bold" />\r
+\r
+        <TextView\r
+            android:id="@+id/gridview_line_two"\r
+            style="@style/GridViewTextItem" />\r
+    </LinearLayout>\r
+\r
+    <ImageView\r
+        android:id="@+id/peak_one"\r
+        style="@style/PeakMeter"\r
+        android:layout_alignParentBottom="true"\r
+        android:paddingBottom="@dimen/peak_meter_padding_bottom"\r
+        android:paddingRight="@dimen/peak_meter_one_padding_right" />\r
+\r
+    <ImageView\r
+        android:id="@+id/peak_two"\r
+        style="@style/PeakMeter"\r
+        android:layout_alignParentBottom="true"\r
+        android:paddingBottom="@dimen/peak_meter_padding_bottom"\r
+        android:paddingRight="@dimen/peak_meter_two_padding_right" />\r
+\r
+</RelativeLayout>
\ No newline at end of file
diff --git a/res/layout/half_and_half.xml b/res/layout/half_and_half.xml
new file mode 100644 (file)
index 0000000..e9a43d0
--- /dev/null
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    xmlns:tools="http://schemas.android.com/tools"\r
+    android:layout_width="match_parent"\r
+    android:layout_height="wrap_content"\r
+    tools:ignore="UnknownIdInLayout" >\r
+\r
+    <include layout="@layout/colorstrip" />\r
+\r
+    <LinearLayout\r
+        android:layout_width="match_parent"\r
+        android:layout_height="wrap_content"\r
+        android:layout_below="@+id/colorstrip"\r
+        android:baselineAligned="false"\r
+        android:orientation="horizontal" >\r
+\r
+        <RelativeLayout\r
+            android:id="@+id/artist_half_container"\r
+            android:layout_width="match_parent"\r
+            android:layout_height="wrap_content"\r
+            android:layout_weight="1"\r
+            android:background="@color/black"\r
+            android:padding="@dimen/half_and_half_container_padding" >\r
+\r
+            <ImageView\r
+                android:id="@+id/half_artist_image"\r
+                android:layout_width="match_parent"\r
+                android:layout_height="@dimen/half_and_half_image_height"\r
+                android:scaleType="centerCrop" />\r
+\r
+            <TextView\r
+                android:id="@+id/half_artist_image_text"\r
+                style="@style/HalfText"\r
+                android:layout_alignBottom="@+id/half_artist_image"\r
+                android:visibility="gone" />\r
+        </RelativeLayout>\r
+\r
+        <RelativeLayout\r
+            android:id="@+id/album_half_container"\r
+            android:layout_width="match_parent"\r
+            android:layout_height="wrap_content"\r
+            android:layout_weight="1"\r
+            android:background="@color/black"\r
+            android:padding="@dimen/half_and_half_container_padding"\r
+            android:visibility="gone" >\r
+\r
+            <ImageView\r
+                android:id="@+id/half_album_image"\r
+                android:layout_width="match_parent"\r
+                android:layout_height="@dimen/half_and_half_image_height"\r
+                android:scaleType="centerCrop" />\r
+\r
+            <TextView\r
+                android:id="@+id/half_album_image_text"\r
+                style="@style/HalfText"\r
+                android:layout_alignBottom="@+id/half_album_image" />\r
+        </RelativeLayout>\r
+    </LinearLayout>\r
+\r
+</RelativeLayout>
\ No newline at end of file
diff --git a/res/layout/library_browser.xml b/res/layout/library_browser.xml
new file mode 100644 (file)
index 0000000..383694e
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    android:layout_width="match_parent"\r
+    android:layout_height="wrap_content" >\r
+\r
+    <com.andrew.apollo.ui.widgets.ScrollableTabView\r
+        android:id="@+id/scrollingTabs"\r
+        android:layout_width="match_parent"\r
+        android:layout_height="wrap_content" />\r
+\r
+    <android.support.v4.view.ViewPager\r
+        android:id="@+id/viewPager"\r
+        android:layout_width="match_parent"\r
+        android:layout_height="wrap_content"\r
+        android:layout_below="@+id/scrollingTabs" />\r
+\r
+    <LinearLayout\r
+        android:layout_width="match_parent"\r
+        android:layout_height="@dimen/bottom_action_bar_height"\r
+        android:layout_alignParentBottom="true" >\r
+\r
+        <android.support.v4.view.ViewPager\r
+            android:id="@+id/bottomActionBarPager"\r
+            android:layout_width="match_parent"\r
+            android:layout_height="match_parent"/>\r
+    </LinearLayout>\r
+\r
+</RelativeLayout>
\ No newline at end of file
diff --git a/res/layout/list_separator.xml b/res/layout/list_separator.xml
new file mode 100644 (file)
index 0000000..b8717df
--- /dev/null
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    android:layout_width="match_parent"\r
+    android:layout_height="wrap_content"\r
+    android:focusable="false"\r
+    android:paddingLeft="@dimen/list_separator_container_padding_left"\r
+    android:paddingRight="@dimen/fast_scroll_padding_right" >\r
+\r
+    <TextView\r
+        android:id="@+id/title"\r
+        style="@style/SeparatorTextViewStyle" />\r
+\r
+</FrameLayout>
\ No newline at end of file
diff --git a/res/layout/listview.xml b/res/layout/listview.xml
new file mode 100644 (file)
index 0000000..7e4283a
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    android:layout_width="match_parent"\r
+    android:layout_height="match_parent"\r
+    android:gravity="center_vertical"\r
+    android:orientation="vertical" >\r
+\r
+    <include layout="@layout/shadow" />\r
+\r
+    <include layout="@layout/list_separator" />\r
+\r
+    <ListView\r
+        android:id="@android:id/list"\r
+        android:layout_width="match_parent"\r
+        android:layout_height="0dp"\r
+        android:layout_weight="1"\r
+        android:cacheColorHint="@color/transparent"\r
+        android:drawSelectorOnTop="false"\r
+        android:fastScrollAlwaysVisible="true"\r
+        android:fastScrollEnabled="true"\r
+        android:listSelector="@drawable/holo_selector"\r
+        android:paddingRight="@dimen/fast_scroll_padding_right" />\r
+\r
+    <include\r
+        android:id="@+id/empty_view"\r
+        layout="@layout/empty_view" />\r
+\r
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/listview_items.xml b/res/layout/listview_items.xml
new file mode 100644 (file)
index 0000000..d589ae2
--- /dev/null
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    android:layout_width="match_parent"\r
+    android:layout_height="@dimen/listview_item_height"\r
+    android:layout_gravity="center_vertical" >\r
+\r
+    <ImageView\r
+        android:id="@+id/listview_item_image"\r
+        android:layout_width="@dimen/listview_album_art"\r
+        android:layout_height="@dimen/listview_album_art"\r
+        android:layout_alignParentBottom="true"\r
+        android:layout_alignParentLeft="true"\r
+        android:layout_alignParentTop="true"\r
+        android:scaleType="centerCrop" />\r
+\r
+    <!-- Padding may be set on via code for some tabs -->\r
+\r
+    <TextView\r
+        android:id="@+id/listview_item_line_one"\r
+        android:layout_width="match_parent"\r
+        android:layout_height="wrap_content"\r
+        android:layout_toRightOf="@+id/listview_item_image"\r
+        android:ellipsize="end"\r
+        android:maxLength="29"\r
+        android:paddingLeft="@dimen/listview_items_padding_left_top"\r
+        android:paddingRight="@dimen/listview_items_padding_right"\r
+        android:paddingTop="@dimen/listview_items_padding_left_top"\r
+        android:shadowColor="@color/black"\r
+        android:shadowRadius="0.5"\r
+        android:singleLine="true"\r
+        android:textSize="@dimen/text_size_medium" />\r
+\r
+    <TextView\r
+        android:id="@+id/listview_item_line_two"\r
+        android:layout_width="match_parent"\r
+        android:layout_height="wrap_content"\r
+        android:layout_below="@+id/listview_item_line_one"\r
+        android:layout_toRightOf="@+id/listview_item_image"\r
+        android:ellipsize="end"\r
+        android:paddingLeft="@dimen/listview_items_padding_left_top"\r
+        android:paddingRight="@dimen/listview_items_padding_right"\r
+        android:paddingTop="@dimen/listview_items_padding_left_top"\r
+        android:shadowColor="@color/black"\r
+        android:shadowRadius="0.2"\r
+        android:singleLine="true"\r
+        android:textSize="@dimen/text_size_small" />\r
+\r
+    <include layout="@layout/context_menu" />\r
+\r
+    <ImageView\r
+        android:id="@+id/peak_two"\r
+        style="@style/PeakMeter"\r
+        android:layout_centerVertical="true"\r
+        android:paddingRight="@dimen/listview_peak_meter_two_padding_right"\r
+        android:paddingTop="@dimen/peak_meter_padding_top" />\r
+\r
+    <ImageView\r
+        android:id="@+id/peak_one"\r
+        style="@style/PeakMeter"\r
+        android:layout_centerVertical="true"\r
+        android:paddingRight="@dimen/listview_peak_meter_one_padding_right"\r
+        android:paddingTop="@dimen/peak_meter_padding_top" />\r
+\r
+</RelativeLayout>
\ No newline at end of file
diff --git a/res/layout/onebyone_app_widget.xml b/res/layout/onebyone_app_widget.xml
new file mode 100644 (file)
index 0000000..b770eec
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    android:layout_width="@dimen/one_by_one_width"\r
+    android:layout_height="@dimen/one_by_one_height" >\r
+\r
+    <ImageView\r
+        android:id="@+id/one_by_one_albumart"\r
+        android:layout_width="match_parent"\r
+        android:layout_height="match_parent" />\r
+\r
+</FrameLayout>
\ No newline at end of file
diff --git a/res/layout/quick_queue.xml b/res/layout/quick_queue.xml
new file mode 100644 (file)
index 0000000..4ee60f5
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    android:id="@+id/quick_queue_holder"\r
+    android:layout_width="match_parent"\r
+    android:layout_height="wrap_content"\r
+    android:gravity="center_vertical"\r
+    android:orientation="vertical" >\r
+\r
+    <GridView\r
+        android:id="@+id/gridview"\r
+        android:layout_width="match_parent"\r
+        android:layout_height="0dp"\r
+        android:layout_weight="1"\r
+        android:cacheColorHint="@color/transparent"\r
+        android:drawSelectorOnTop="false"\r
+        android:listSelector="@color/transparent"\r
+        android:scrollbars="none" />\r
+\r
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/quick_queue_items.xml b/res/layout/quick_queue_items.xml
new file mode 100644 (file)
index 0000000..afcb7ee
--- /dev/null
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    android:layout_width="match_parent"\r
+    android:layout_height="wrap_content"\r
+    android:paddingBottom="@dimen/status_bar_recents_item_padding"\r
+    android:paddingTop="@dimen/status_bar_recents_item_padding" >\r
+\r
+    <RelativeLayout\r
+        android:id="@+id/recent_item"\r
+        android:layout_width="wrap_content"\r
+        android:layout_height="wrap_content"\r
+        android:layout_gravity="center_horizontal" >\r
+\r
+        <TextView\r
+            android:id="@+id/queue_track_name"\r
+            android:layout_width="@dimen/status_bar_recents_app_label_width"\r
+            android:layout_height="wrap_content"\r
+            android:layout_alignParentLeft="true"\r
+            android:layout_alignTop="@+id/queue_album_art"\r
+            android:layout_marginLeft="@dimen/status_bar_recents_app_label_left_margin"\r
+            android:ellipsize="marquee"\r
+            android:fadingEdge="horizontal"\r
+            android:fadingEdgeLength="@dimen/status_bar_recents_fading_edge_length"\r
+            android:paddingTop="2dp"\r
+            android:scrollHorizontally="true"\r
+            android:singleLine="true"\r
+            android:textColor="@color/white"\r
+            android:textSize="@dimen/status_bar_recents_app_label_text_size" />\r
+\r
+        <FrameLayout\r
+            android:id="@+id/app_thumbnail"\r
+            android:layout_width="wrap_content"\r
+            android:layout_height="wrap_content"\r
+            android:layout_alignParentTop="true"\r
+            android:layout_marginLeft="@dimen/status_bar_recents_thumbnail_left_margin"\r
+            android:layout_toRightOf="@+id/queue_track_name"\r
+            android:background="@drawable/queue_thumbnail_bg"\r
+            android:foreground="@drawable/queue_thumbnail_fg" >\r
+\r
+            <ImageView\r
+                android:id="@+id/queue_artist_image"\r
+                android:layout_width="@dimen/status_bar_recents_thumbnail_width"\r
+                android:layout_height="@dimen/status_bar_recents_thumbnail_height"\r
+                android:scaleType="fitXY" />\r
+        </FrameLayout>\r
+\r
+        <View\r
+            android:id="@+id/recents_callout_line"\r
+            android:layout_width="@dimen/status_bar_recents_app_label_width"\r
+            android:layout_height="1dip"\r
+            android:layout_alignParentLeft="true"\r
+            android:layout_below="@+id/queue_track_name"\r
+            android:layout_marginLeft="@dimen/status_bar_recents_app_label_left_margin"\r
+            android:layout_marginRight="3dip"\r
+            android:layout_marginTop="3dip"\r
+            android:layout_toLeftOf="@id/app_thumbnail"\r
+            android:background="@color/queue_callout_line" />\r
+\r
+        <ImageView\r
+            android:id="@+id/queue_album_art"\r
+            android:layout_width="wrap_content"\r
+            android:layout_height="wrap_content"\r
+            android:layout_marginLeft="@dimen/status_bar_recents_app_icon_left_margin"\r
+            android:layout_marginTop="@dimen/status_bar_recents_app_icon_top_margin"\r
+            android:layout_toRightOf="@+id/queue_track_name"\r
+            android:adjustViewBounds="true"\r
+            android:maxHeight="@dimen/status_bar_recents_app_icon_max_height"\r
+            android:maxWidth="@dimen/status_bar_recents_app_icon_max_width"\r
+            android:scaleType="centerInside" />\r
+\r
+        <TextView\r
+            android:id="@+id/app_description"\r
+            android:layout_width="@dimen/status_bar_recents_app_label_width"\r
+            android:layout_height="wrap_content"\r
+            android:layout_alignParentLeft="true"\r
+            android:layout_below="@id/recents_callout_line"\r
+            android:layout_marginLeft="@dimen/status_bar_recents_app_label_left_margin"\r
+            android:layout_marginTop="3dip"\r
+            android:ellipsize="marquee"\r
+            android:fadingEdge="horizontal"\r
+            android:fadingEdgeLength="@dimen/status_bar_recents_fading_edge_length"\r
+            android:scrollHorizontally="true"\r
+            android:singleLine="true"\r
+            android:textSize="@dimen/status_bar_recents_app_description_text_size" />\r
+    </RelativeLayout>\r
+\r
+</FrameLayout>
\ No newline at end of file
diff --git a/res/layout/shadow.xml b/res/layout/shadow.xml
new file mode 100644 (file)
index 0000000..37f28bb
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    android:id="@+id/shadow"\r
+    android:layout_width="match_parent"\r
+    android:layout_height="wrap_content"\r
+    android:foreground="@drawable/title_bar_shadow"\r
+    android:foregroundGravity="fill_horizontal|top|center" />\r
diff --git a/res/layout/status_bar.xml b/res/layout/status_bar.xml
new file mode 100644 (file)
index 0000000..34385e7
--- /dev/null
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    android:layout_width="match_parent"\r
+    android:layout_height="match_parent"\r
+    android:orientation="horizontal" >\r
+\r
+    <ImageView\r
+        android:id="@+id/status_bar_album_art"\r
+        android:layout_width="@dimen/status_bar_album_art"\r
+        android:layout_height="@dimen/status_bar_album_art"\r
+        android:gravity="center" />\r
+\r
+    <ImageView\r
+        android:id="@+id/status_bar_icon"\r
+        android:layout_width="@dimen/status_bar_album_art"\r
+        android:layout_height="@dimen/status_bar_album_art"\r
+        android:background="@drawable/status_bg"\r
+        android:scaleType="center"\r
+        android:src="@drawable/stat_notify_music"\r
+        android:visibility="gone" />\r
+\r
+    <LinearLayout\r
+        android:layout_width="0dp"\r
+        android:layout_height="wrap_content"\r
+        android:layout_gravity="center_vertical"\r
+        android:layout_weight="1"\r
+        android:orientation="vertical"\r
+        android:paddingLeft="@dimen/status_bar_button_info_container_padding_left" >\r
+\r
+        <TextView\r
+            android:id="@+id/status_bar_track_name"\r
+            style="@style/StatusBarText"\r
+            android:textColor="@color/white"\r
+            android:textSize="@dimen/text_size_medium"\r
+            android:textStyle="bold" />\r
+\r
+        <TextView\r
+            android:id="@+id/status_bar_artist_name"\r
+            style="@style/StatusBarText" />\r
+    </LinearLayout>\r
+\r
+    <ImageButton\r
+        android:id="@+id/status_bar_play"\r
+        style="@style/StatusBarButton" />\r
+\r
+    <ImageButton\r
+        android:id="@+id/status_bar_next"\r
+        style="@style/StatusBarButton"\r
+        android:src="@drawable/apollo_holo_dark_next" />\r
+\r
+    <ImageButton\r
+        android:id="@+id/status_bar_collapse"\r
+        style="@style/StatusBarButton"\r
+        android:src="@drawable/apollo_holo_dark_notifiation_bar_collapse" />\r
+\r
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/tabs.xml b/res/layout/tabs.xml
new file mode 100644 (file)
index 0000000..0bfd77b
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<Button xmlns:android="http://schemas.android.com/apk/res/android"\r
+    android:id="@+id/tabs"\r
+    style="@style/Tabs" />\r
index 5a85225..0563b44 100644 (file)
@@ -1,39 +1,36 @@
 <?xml version="1.0" encoding="utf-8"?>
-<LinearLayout
-  xmlns:android="http://schemas.android.com/apk/res/android"
-  android:orientation="vertical"
-  android:layout_width="fill_parent"
-  android:layout_height="fill_parent">
-       <TextView
-               android:id="@+id/ThemeTitle"
-               android:layout_width="fill_parent"
-               android:layout_height="wrap_content"
-               android:textAppearance="?android:attr/textAppearanceLarge"
-       />
-       <TextView
-               android:id="@+id/ThemeDescription"
-               android:layout_width="fill_parent"
-               android:layout_height="wrap_content"
-               android:textAppearance="?android:attr/textAppearanceSmall"
-       />
-       <ImageView
-               android:id="@+id/ThemeIcon"
-               android:src="@drawable/app_music"
-               android:layout_width="wrap_content"
-               android:layout_height="wrap_content"
-       />
-       <Button
-               android:id="@+id/ThemeApply"
-               android:layout_width="fill_parent"
-               android:layout_height="wrap_content"
-               android:text="@string/pref_themes_apply_theme"
-               android:onClick="applyTheme"
-       />
-       <Button
-               android:id="@+id/ThemeSearch"
-               android:layout_width="fill_parent"
-               android:layout_height="wrap_content"
-               android:text="@string/pref_themes_get_themes"
-               android:onClick="getThemes"
-       />
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+
+    <TextView
+        android:id="@+id/themeTitle"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+    <TextView
+        android:id="@+id/themeDescription"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+    <ImageView
+        android:id="@+id/themeIcon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <Button
+        android:id="@+id/themeApply"
+        android:onClick="applyTheme"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/apply_theme" />
+
+    <Button
+        android:id="@+id/themeSearch"
+        android:onClick="getThemes"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/get_more_themes" />
+
 </LinearLayout>
\ No newline at end of file
diff --git a/res/layout/track_browser.xml b/res/layout/track_browser.xml
new file mode 100644 (file)
index 0000000..4559091
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    android:layout_width="match_parent"\r
+    android:layout_height="wrap_content" >\r
+\r
+    <include\r
+        android:id="@+id/half"\r
+        layout="@layout/half_and_half" />\r
+\r
+    <android.support.v4.view.ViewPager\r
+        android:id="@+id/viewPager"\r
+        android:layout_width="match_parent"\r
+        android:layout_height="match_parent"\r
+        android:layout_below="@+id/half" />\r
+\r
+    <LinearLayout\r
+        android:layout_width="match_parent"\r
+        android:layout_height="@dimen/bottom_action_bar_height"\r
+        android:layout_alignParentBottom="true" >\r
+\r
+        <android.support.v4.view.ViewPager\r
+            android:id="@+id/bottomActionBarPager"\r
+            android:layout_width="match_parent"\r
+            android:layout_height="match_parent"/>\r
+    </LinearLayout>\r
+\r
+</RelativeLayout>
\ No newline at end of file
diff --git a/res/menu/overflow_library.xml b/res/menu/overflow_library.xml
new file mode 100644 (file)
index 0000000..7243c8a
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >\r
+\r
+    <item\r
+        android:id="@+id/settings"\r
+        android:title="@string/settings"/>\r
+    <item\r
+        android:id="@+id/equalizer"\r
+        android:title="@string/eqalizer"/>\r
+    <item\r
+        android:id="@+id/shuffle_all"\r
+        android:title="@string/shuffle_all"/>\r
+    <!--\r
+    <item\r
+        android:id="@+id/help"\r
+        android:title="@string/help"/>\r
+    <item\r
+        android:id="@+id/fetch_artwork"\r
+        android:title="Fetch album art"/>\r
+    <item\r
+        android:id="@+id/fetch_artist_images"\r
+        android:title="Fetch artist images"/>\r\r
+    -->\r
+\r
+</menu>
\ No newline at end of file
diff --git a/res/menu/overflow_now_playing.xml b/res/menu/overflow_now_playing.xml
new file mode 100644 (file)
index 0000000..554c6bc
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >\r
+\r
+    <item\r
+        android:id="@+id/add_to_playlist"\r
+        android:showAsAction="never"\r
+        android:title="@string/add_to_playlist"/>\r
+    <item\r
+        android:id="@+id/eq"\r
+        android:showAsAction="never"\r
+        android:title="@string/eqalizer"/>\r
+    <item\r
+        android:id="@+id/play_store"\r
+        android:showAsAction="never"\r
+        android:title="@string/play_store"/>\r
+    <item\r
+        android:id="@+id/share"\r
+        android:showAsAction="never"\r
+        android:title="@string/share"/>\r
+    <item\r
+        android:id="@+id/settings"\r
+        android:showAsAction="never"\r
+        android:title="@string/settings"/>\r
+\r
+</menu>
\ No newline at end of file
diff --git a/res/values-hdpi/config.xml b/res/values-hdpi/config.xml
new file mode 100644 (file)
index 0000000..b727fb7
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<resources>\r
+\r
+    <!-- ListView album art size -->\r
+    <integer name="listview_album_art">100</integer>\r
+\r
+</resources>
\ No newline at end of file
diff --git a/res/values-hdpi/dimens.xml b/res/values-hdpi/dimens.xml
new file mode 100644 (file)
index 0000000..4aae5b1
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<resources>\r
+\r
+    <!-- Half and half layout -->\r
+    <dimen name="half_and_half_image_height">130dp</dimen>\r
+\r
+    <!-- GridView items -->\r
+    <dimen name="gridview_image_height">148dp</dimen>\r
+    <dimen name="gridview_item_ccontainer_height">54dp</dimen>\r
+\r
+</resources>
\ No newline at end of file
diff --git a/res/values-xhdpi/dimens.xml b/res/values-xhdpi/dimens.xml
new file mode 100644 (file)
index 0000000..af3eaf7
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<resources>\r
+\r
+    <!-- Half and half layout -->\r
+    <dimen name="half_and_half_image_height">150dp</dimen>\r
+\r
+    <!-- GridView items -->\r
+    <dimen name="gridview_image_height">180dp</dimen>\r
+    <dimen name="gridview_item_ccontainer_height">64dp</dimen>\r
+\r
+</resources>
\ No newline at end of file
index 05e3233..959d021 100644 (file)
@@ -1,28 +1,22 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2009 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resources>
-
-    <color name="appwidget_text">#ffffffff</color>
-    <color name="transparent">#00000000</color>
-    <color name="trans_black">#80000000</color>
-    <color name="ics">#33b5e5</color>
-    <color name="android">#A4C639</color>
-    <color name="ics_opaque">#8033b5e5</color>
-    <color name="np_background">#111111</color>
-
+<?xml version="1.0" encoding="utf-8"?>\r
+<resources>\r
+\r
+    <!-- A transparent black -->\r
+    <color name="transparent_black">#99000000</color>\r
+\r
+    <!-- A semi-transparent dark Holo shade of blue -->\r
+    <color name="holo_blue_dark">#ff0099cc</color>\r
+\r
+    <!-- Transparent -->\r
+    <color name="transparent">#00000000</color>\r
+\r
+    <!-- Black -->\r
+    <color name="black">#ff000000</color>\r
+\r
+    <!-- White -->\r
+    <color name="white">#ffffffff</color>\r
+\r
+    <!-- Quick Queue line seperator -->\r
+    <color name="queue_callout_line">#99ffffff</color>\r
+\r
 </resources>
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
new file mode 100644 (file)
index 0000000..c3e8295
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<resources>\r
+\r
+    <!-- ViewPager margin width -->\r
+    <integer name="viewpager_margin_width">30</integer>\r
+\r
+    <!-- ListView album art size -->\r
+    <integer name="listview_album_art">133</integer>\r
+\r
+    <!-- Now playing indicator animation time -->\r
+    <integer name="peak">200</integer>\r
+\r
+    <!-- Number of GridView coulumns -->\r
+    <integer name="gridview_columns">2</integer>\r
+\r
+    <!-- ListView padding when header is applied -->\r
+    <integer name="listview_padding_left">16</integer>\r
+    <integer name="listview_padding_right">32</integer>\r
+\r
+</resources>
\ No newline at end of file
index 76e33d7..aeacf75 100644 (file)
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2009 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resources>
-
-    <!-- height of a normal list item in edit playlist mode -->
-    <dimen name="normal_height">66dip</dimen>
-    <!-- height of an expanded list item in edit playlist mode -->
-    <dimen name="expanded_height">132dip</dimen>
-
+<?xml version="1.0" encoding="utf-8"?>\r
+<resources>\r
+\r
+    <!-- Text sizes -->\r
+    <dimen name="text_size_extra_micro">10sp</dimen>\r
+    <dimen name="text_size_micro">12sp</dimen>\r
+    <dimen name="text_size_small">14sp</dimen>\r
+    <dimen name="text_size_medium">16sp</dimen>\r
+    <dimen name="text_size_large">18sp</dimen>\r
+\r
+    <!-- Tabs -->\r
+    <dimen name="tab_padding_top_bottom">15dp</dimen>\r
+    <dimen name="tab_padding_left_right">40dp</dimen>\r
+\r
+    <!-- Bottom ActionBar -->\r
+    <dimen name="bottom_action_bar_height">48dp</dimen>\r
+    <dimen name="bottom_action_bar_item_width">56dp</dimen>\r
+    <dimen name="bottom_action_bar_item_padding_left_right">12dp</dimen>\r
+    <dimen name="bottom_action_bar_album_art_width_height">30dp</dimen>\r
+    <dimen name="bottom_action_bar_divider_height">2dp</dimen>\r
+    <dimen name="bottom_action_bar_info_padding_left">5dp</dimen>\r
+\r
+    <!-- ViewPager margin stroke width -->\r
+    <dimen name="viewpager_margin_stroke_width">0.5dp</dimen>\r
+\r
+    <!-- FastScroll padding -->\r
+    <dimen name="fast_scroll_padding_right">32dp</dimen>\r
+\r
+    <!-- List separator -->\r
+    <dimen name="list_separator_padding_left_right">8dp</dimen>\r
+    <dimen name="list_separator_min_height">32dp</dimen>\r
+    <dimen name="list_separator_container_padding_left">16dp</dimen>\r
+\r
+    <!-- ListView items -->\r
+    <dimen name="listview_item_height">64dp</dimen>\r
+    <dimen name="listview_items_padding_left_top">9dp</dimen>\r
+    <dimen name="listview_items_padding_right">85dp</dimen>\r
+    <dimen name="listview_peak_meter_one_padding_right">80dp</dimen>\r
+    <dimen name="listview_peak_meter_two_padding_right">70dp</dimen>\r
+    <dimen name="listview_album_art">64dp</dimen>\r
+\r
+    <!-- Quick Context Menu -->\r
+    <dimen name="quick_context_padding_right">4dp</dimen>\r
+    <dimen name="quick_context_line_height">30dp</dimen>\r
+    <dimen name="quick_context_line_width">1dp</dimen>\r
+    <dimen name="quick_context_margin_right">5dp</dimen>\r
+\r
+    <!-- Nofication bar button -->\r
+    <dimen name="status_bar_button_width_height">48dp</dimen>\r
+    <dimen name="status_bar_album_art">64dp</dimen>\r
+    <dimen name="status_bar_button_info_container_padding_left">11dp</dimen>\r
+\r
+    <!-- Colorstrip -->\r
+    <dimen name="colorstrip_height">4dp</dimen>\r
+\r
+    <!-- Half and half layout -->\r
+    <dimen name="half_and_half_text_padding">5dp</dimen>\r
+    <dimen name="half_and_half_image_height">150dp</dimen>\r
+    <dimen name="half_and_half_container_padding">3dp</dimen>\r
+\r
+    <!-- ContextMenu header text padding -->\r
+    <dimen name="header_text_padding">5dp</dimen>\r
+    <dimen name="header_text_padding_left">15dp</dimen>\r
+\r
+    <!-- GridView items -->\r
+    <dimen name="gridview_image_width">180dp</dimen>\r
+    <dimen name="gridview_image_height">180dp</dimen>\r
+    <dimen name="gridview_item_ccontainer_height">64dp</dimen>\r
+    <dimen name="gridview_item_ccontainer_padding_left">8dp</dimen>\r
+    <dimen name="gridview_item_ccontainer_padding_right">80dp</dimen>\r
+    <dimen name="peak_meter_one_padding_right">15dp</dimen>\r
+    <dimen name="peak_meter_two_padding_right">5dp</dimen>\r
+    <dimen name="peak_meter_padding_bottom">10dp</dimen>\r
+    <dimen name="peak_meter_padding_top">8dp</dimen>\r
+\r
+    <!-- Audio player -->\r
+    <dimen name="audio_player_info_container_padding">16dp</dimen>\r
+    <dimen name="audio_player_artwork_padding">20dp</dimen>\r
+    <dimen name="audio_player_controls_height">56dp</dimen>\r
+    <dimen name="audio_player_seek_bar_padding">10dp</dimen>\r
+    <dimen name="audio_player_button_container_padding">2dp</dimen>\r
+\r
+    <!-- Recent Applications parameters -->\r
+    <!-- How far the thumbnail for a recent app appears from left edge -->\r
+    <dimen name="status_bar_recents_thumbnail_left_margin">20dp</dimen>\r
+    <!-- Width of application label text -->\r
+    <dimen name="status_bar_recents_app_label_width">88dp</dimen>\r
+    <!-- Left margin of application label text -->\r
+    <dimen name="status_bar_recents_app_label_left_margin">0dp</dimen>\r
+    <!-- Padding between recents items -->\r
+    <dimen name="status_bar_recents_item_padding">0dp</dimen>\r
+    <!-- Where to place the app icon over the thumbnail -->\r
+    <dimen name="status_bar_recents_app_icon_left_margin">0dp</dimen>\r
+    <dimen name="status_bar_recents_app_icon_top_margin">8dp</dimen>\r
+    <!-- Recent Applications parameters -->\r
+    <!-- Upper width limit for application icon -->\r
+    <dimen name="status_bar_recents_app_icon_max_width">48dp</dimen>\r
+    <!-- Upper height limit for application icon -->\r
+    <dimen name="status_bar_recents_app_icon_max_height">48dp</dimen>\r
+\r
+    <!-- Size of application thumbnail -->\r
+    <dimen name="status_bar_recents_thumbnail_width">164dp</dimen>\r
+    <dimen name="status_bar_recents_thumbnail_height">145dp</dimen>\r
+\r
+    <!-- Size of application label text -->\r
+    <dimen name="status_bar_recents_app_label_text_size">14dp</dimen>\r
+    <!-- Size of application description text -->\r
+    <dimen name="status_bar_recents_app_description_text_size">14dp</dimen>\r
+    <!-- Size of fading edge for scroll effect -->\r
+    <dimen name="status_bar_recents_fading_edge_length">20dp</dimen>\r
+\r
+    <!-- AppWidgdt 1x1 -->\r
+    <dimen name="one_by_one_height">62dp</dimen>\r
+    <dimen name="one_by_one_width">72dp</dimen>\r
+\r
+    <!-- AppWidgdt 4x1 -->\r
+    <dimen name="four_by_one_album_art_width">90dp</dimen>\r
+    \r
+    <!-- AppWidget 4x2 -->\r
+    <dimen name="four_by_two_height">180dp</dimen>\r
+    <dimen name="four_by_two_control_height">55dp</dimen>\r
+    <dimen name="four_by_two_album_art_width">135dp</dimen>\r
+\r
 </resources>
\ No newline at end of file
index 4077794..2f25c63 100644 (file)
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2007, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
-**
-**     http://www.apache.org/licenses/LICENSE-2.0 
-**
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
-** limitations under the License.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- used in various places to indicate there is one song for a given artist or album -->
-    <string name="onesong">1 song</string>
-    <!-- used in various places to indicate there is some number other than one songs for a given artist or album. -->
-    <plurals name="Nsongs">
-        <item quantity="other"><xliff:g id="count">%d</xliff:g> songs</item>
-    </plurals>
-
-    <!-- shows how many songs on the album are by the selected artist, out of how many total, if those two numbers are different -->
-    <plurals name="Nsongscomp">
-        <item quantity="other"><xliff:g id="count_for_artist">%2$d</xliff:g> of <xliff:g id="total_count">%1$d</xliff:g> songs</item>
-    </plurals>
-
-    <!-- Used in artists list view, indicates how many albums exist for a given artist. -->
-    <plurals name="Nalbums">
-
-        <!-- number of albums is one -->
-        <item quantity="one">1 album</item>
-        <!-- number of albums is not equal to one -->
-        <item quantity="other"><xliff:g id="count">%d</xliff:g> albums</item>
-    </plurals>
-
-    <!--
-    This string is used as the format string in a String.format call,
-    and 5 additional arguments are available for printing:
-    1 - hours
-    2 - minutes
-    3 - minutes%60
-    4 - seconds
-    5 - second%60
-    -->
-    <skip />
-    <!-- Do not translate. Duration format for duration < 1 hour -->
-    <string name="durationformatshort" translatable="false"><xliff:g id="format">%2$d:%5$02d</xliff:g></string>
-    <!-- Do not translate. Duration format for duration >= 1 hour -->
-    <string name="durationformatlong" translatable="false"><xliff:g id="format">%1$d:%3$02d:%5$02d</xliff:g></string>
-
-    <!-- Menu item that takes the user back to the top level screen of the music player -->
-    <string name="goto_start">Library</string>
-    <!-- Menu item that switches the music player in to party shuffle mode -->
-    <string name="party_shuffle">Party shuffle</string>
-    <!-- Menu item that switches the music player out of party shuffle mode -->
-    <string name="party_shuffle_off">Party shuffle off</string>
-    <string name="party_shuffle_on">Party shuffle on</string>
-
-    <!--
-         Menu item that deletes the currently selected item, which might be a single song, or a collection of songs.
-         The user will be prompted to confirm before deletion actually takes place
-    -->
-    <string name="delete_item">Delete</string>
-    <!-- Menu item to play back all the songs in the currently showing list in shuffle mode -->
-    <string name="shuffle_all">Shuffle all</string>
-    <!-- Menu item to play back all the songs in the currently showing list in list order -->
-    <string name="play_all">Play all</string>
-
-    <!-- Delete confirmation dialog when deleting an entire artist -->
-    <string name="delete_artist_desc">All songs by <xliff:g id="artist">%s</xliff:g> will be permanently deleted from the SD card.</string>
-    <!-- Delete confirmation dialog when deleting an entire artist -->
-    <string name="delete_artist_desc_nosdcard">All songs by <xliff:g id="artist">%s</xliff:g> will be permanently deleted from USB storage.</string>
-
-    <!-- Delete confirmation dialog when deleting an entire album -->
-    <string name="delete_album_desc">The entire album \"<xliff:g id="album">%s</xliff:g>\" will be permanently deleted from the SD card.</string>
-    <!-- Delete confirmation dialog when deleting an entire album -->
-    <string name="delete_album_desc_nosdcard">The entire album \"<xliff:g id="album">%s</xliff:g>\" will be permanently deleted from USB storage.</string>
-
-    <!-- Delete confirmation dialog when deleting a single song -->
-    <string name="delete_song_desc">\"<xliff:g id="song">%s</xliff:g>\" will be permanently deleted from the SD card.</string>
-    <!-- Delete confirmation dialog when deleting a single song -->
-    <string name="delete_song_desc_nosdcard">\"<xliff:g id="song">%s</xliff:g>\" will be permanently deleted from USB storage.</string>
-
-    <!-- Delete confirmation dialog, confirmation button text -->
-    <string name="delete_confirm_button_text">OK</string>
-    <!-- Toast confirming that song(s) was/were deleted. -->
-    <plurals name="NNNtracksdeleted">
-
-        <!-- delete confirmation message for 1 song -->
-        <item quantity="one">1 song was deleted.</item>
-        <!-- delete confirmation message for 0 or more than 1 songs -->
-        <item quantity="other"><xliff:g id="songs_to_delete">%d</xliff:g> songs were deleted.</item>
-    </plurals>
-
-    <!-- shown in dialog while the media scanner is starting up -->
-    <string name="scanning">Scanning SD card\u2026</string>
-    <!-- shown in dialog while the media scanner is starting up -->
-    <string name="scanning_nosdcard">Scanning USB storage\u2026</string>
-
-    <!-- title of the "current playlist" screen when not in party shuffle mode -->
-    <string name="nowplaying_title">Now playing</string>
-    <!-- Artist screen title -->
-    <string name="artists_title">Artists</string>
-    <!-- Category label on Library screen -->
-    <string name="albums_menu">Albums</string>
-    <!-- Albums screen title -->
-    <string name="albums_title">Albums</string>
-    <!-- Category label on Library screen -->
-    <string name="tracks_menu">Songs</string>
-    <!-- Songs screen title -->
-    <string name="tracks_title">Songs</string>
-    <!-- Category label on Library screen -->
-    <string name="playlists_menu">Playlists</string>
-    <!-- Playlists screen title -->
-    <string name="playlists_title">Playlists</string>
-    <!-- Videos screen title -->
-    <string name="videos_title">Videos</string>
-    <!-- All media screen title -->
-    <string name="all_title">All media</string>
-    <!-- Category label on Library screen -->
-    <string name="browse_menu">Artists</string>
-    <!-- Library screen, menu item -->
-    <string name="search_title">Search</string>
-    <!-- Title of screen when there are no songs, or if the SD card is busy -->
-    <string name="no_tracks_title">No songs</string>
-    <!-- Title of screen when there are no videos, or if the SD card is busy -->
-    <string name="no_videos_title">No videos</string>
-    <!-- Title of screen when there are no playlists, or if the SD card is busy -->
-    <string name="no_playlists_title">No playlists</string>
-    <!-- Playlist context menu item to delete the selected playlist. -->
-    <string name="delete_playlist_menu">Delete</string>
-    <!-- Playlist context menu item to edit the selected playlist -->
-    <string name="edit_playlist_menu">Edit</string>
-    <!-- Playlist context menu item to rename the selected playlist -->
-    <string name="rename_playlist_menu">Rename</string>
-    <!-- Transient popup message shown after deleting a playlist -->
-    <string name="playlist_deleted_message">Playlist deleted.</string>
-    <!-- Transient popup message shown after renaming a playlist -->
-    <string name="playlist_renamed_message">Playlist renamed.</string>
-    <!-- The name of the pseudo-playlist that holds all the recently added files, shown in list view -->
-    <string name="recentlyadded">Recently added</string>
-    <!-- The name of the pseudo-playlist that holds all the recently added files, shown in title bar of songs list -->
-    <string name="recentlyadded_title">Recently added</string>
-    <!-- The name of the pseudo-playlist that holds all the podcasts, shown in list view -->
-    <string name="podcasts_listitem">Podcasts</string>
-    <!-- The name of the pseudo-playlist that holds all the podcasts, shown in title bar of songs list -->
-    <string name="podcasts_title">Podcasts</string>
-    <!-- Title of screen when no sd card is present -->
-    <string name="sdcard_missing_title">No SD card</string>
-    <!-- Title of screen when no sd card is present -->
-    <string name="sdcard_missing_title_nosdcard">No USB storage</string>
-    <!-- label underneath icon used to indicate that no sd card is present -->
-    <string name="sdcard_missing_message">Your phone does not have an SD card inserted.</string>
-    <!-- label underneath icon used to indicate that no sd card is present -->
-    <string name="sdcard_missing_message_nosdcard">Your phone does not have USB storage.</string>
-    <!-- label underneath icon used to indicate that the sd card is present, but currently unavailable -->
-    <string name="sdcard_busy_title">SD card unavailable</string>
-    <!-- label underneath icon used to indicate that the sd card is present, but currently unavailable -->
-    <string name="sdcard_busy_title_nosdcard">USB storage unavailable</string>
-    <!-- label underneath icon used to indicate sd card is mounted to your computer via USB -->
-    <string name="sdcard_busy_message">Sorry, your SD card is busy.</string>
-    <!-- label underneath icon used to indicate sd card is mounted to your computer via USB -->
-    <string name="sdcard_busy_message_nosdcard">Sorry, your USB storage is busy.</string>
-    <!-- Title of screen when there was an error accessing the sd card -->
-    <string name="sdcard_error_title">SD card error</string>
-    <!-- Title of screen when there was an error accessing the sd card -->
-    <string name="sdcard_error_title_nosdcard">USB storage error</string>
-    <!-- label underneath icon used to indicate there was an error accessing the sd card -->
-    <string name="sdcard_error_message">An error was encountered on your SD card.</string>
-    <!-- label underneath icon used to indicate there was an error accessing the sd card -->
-    <string name="sdcard_error_message_nosdcard">An error was encountered on your USB storage.</string>
-    <!-- Default name of artist that doesn't have a name in the metadata -->
-    <string name="unknown_artist_name">Unknown artist</string>
-    <!-- Default name of album that doesn't have a name in the metadata -->
-    <string name="unknown_album_name">Unknown album</string>
-    <!-- Toast after turning shuffle on -->
-    <string name="shuffle_on_notif">Shuffle is on</string>
-    <!-- Toast after turning shuffle off -->
-    <string name="shuffle_off_notif">Shuffle is off</string>
-    <!-- Toast after turning repeat off -->
-    <string name="repeat_off_notif">Repeat is off</string>
-    <!-- Toast after turning single repeat on -->
-    <string name="repeat_current_notif">Repeating current song</string>
-    <!-- Toast after turning repeat all on -->
-    <string name="repeat_all_notif">Repeating all songs</string>
-    <!-- Individual song context menu item -->
-    <string name="ringtone_menu">Use as phone ringtone</string>
-    <!-- Menu item -->
-    <string name="ringtone_menu_short">Use as ringtone</string>
-    <!-- Toast after setting a song as phone ringtone -->
-    <string name="ringtone_set">\"<xliff:g id="name" example="Alarm Bell">%s</xliff:g>\" set as phone ringtone.</string>
-    <!-- Context menu item -->
-    <string name="play_selection">Play</string>
-    <!-- Context menu item -->
-    <string name="add_to_playlist">Add to playlist</string>
-    <!-- Context menu item -->
-    <string name="queue">Queue</string>
-    <!-- Context menu item -->
-    <string name="new_playlist">New playlist</string>
-    <!-- Context menu item -->
-    <string name="show_all">Show All</string>
-    <!-- Template for newly created playlist name -->
-    <string name="new_playlist_name_template">New playlist <xliff:g id="number">%d</xliff:g></string>
-    <!-- Toasts after adding song(s) to playlists -->
-    <plurals name="NNNtrackstoplaylist">
-
-        <!-- message shown when one song was added -->
-        <item quantity="one">1 song added to playlist.</item>
-        <!-- message shown when zero or more than one song was added -->
-        <item quantity="other"><xliff:g id="number" example="27">%d</xliff:g> songs added to playlist.</item>
-    </plurals>
-    <!-- Toast after selecting an empty playlist -->
-    <string name="emptyplaylist">Selected playlist is empty.</string>
-    <!-- Button name when saving a playlist -->
-    <string name="create_playlist_create_text">Save</string>
-    <!-- Button name when saving a playlist and the new playlist will overwrite an existing one -->
-    <string name="create_playlist_overwrite_text">Overwrite</string>
-    <!-- Dialog box title -->
-    <string name="service_start_error_title">Playback problem</string>
-    <!-- Dialog box message -->
-    <string name="service_start_error_msg">Sorry, the song could not be played.</string>
-    <!-- Dialog box button -->
-    <string name="service_start_error_button">OK</string>
-    <!-- Time span edit options that appear when editing system playlist "Recently added" -->
-    <!-- Used to indicate the number of weeks the "recently added" playlist covers in a selector widget -->
-    <string-array name="weeklist">
-        <item>"1 week"</item>
-        <item>"2 weeks"</item>
-        <item>"3 weeks"</item>
-        <item>"4 weeks"</item>
-        <item>"5 weeks"</item>
-        <item>"6 weeks"</item>
-        <item>"7 weeks"</item>
-        <item>"8 weeks"</item>
-        <item>"9 weeks"</item>
-        <item>"10 weeks"</item>
-        <item>"11 weeks"</item>
-        <item>"12 weeks"</item>
-    </string-array>
-    <!-- Button name in time span picker -->
-    <string name="weekpicker_set">Done</string>
-    <!-- Title of time span picker -->
-    <string name="weekpicker_title">Set time</string>
-    <!-- Do not translate. Background color for currently dragged item in playlist edit mode. -->
-    <color name="dragndrop_background">#e0103010</color>
-    <!-- Do not translate. Background color for albums in the artists list view. -->
-    <color name="expanding_child_background">#ff404040</color>
-
-    <!-- menu item to save the current list as a new playlist -->
-    <string name="save_as_playlist">Save as playlist</string>
-    <!-- menu item to clear the current playlist -->
-    <string name="clear_playlist">Clear playlist</string>
-
-    <!-- Activity label. This might show up in the activity-picker -->
-    <string name="musicbrowserlabel">Music</string>
-    <!-- Activity label. This might show up in the activity-picker -->
-    <string name="musicshortcutlabel">Music playlist</string>
-    <!-- Activity label. This might show up in the activity-picker -->
-    <string name="mediaplaybacklabel">Music</string>
-    <!-- Activity label. This might show up in the activity-picker -->
-    <string name="videobrowserlabel">Videos</string>
-    <!-- Activity label. This might show up in the activity-picker -->
-    <string name="mediapickerlabel">Music</string>
-
-    <!-- Shown as a transient message whenever a file fails to play -->
-    <string name="playback_failed">Sorry, the player does not support this type of audio file.</string>
-
-    <!-- Text for the "cancel" button in the "delete" and "create playlist" confirmation dialogs -->
-    <string name="cancel">Cancel</string>
-
-    <!-- context menu item to remove the selected item from the playlist -->
-    <string name="remove_from_playlist">Remove from playlist</string>
-
-    <!-- shown when connecting to a music stream, before it starts playing -->
-    <string name="streamloadingtext">Connecting to <xliff:g id="host">%s</xliff:g></string>
-
-    <!-- title of contextual music search menu -->
-    <string name="mediasearch">Search for <xliff:g id="term" example="Beethoven">%s</xliff:g> using:</string>
-
-    <!-- Shown in the title bar while the list of artists is being retrieved in the background -->
-    <string name="working_artists">Artists\u2026</string>
-    <!-- Shown in the title bar while the list of albums is being retrieved in the background -->
-    <string name="working_albums">Albums\u2026</string>
-    <!-- Shown in the title bar while the list of songs is being retrieved in the background -->
-    <string name="working_songs">Songs\u2026</string>
-    <!-- Shown in the title bar while the list of playlists is being retrieved in the background -->
-    <string name="working_playlists">Playlists\u2026</string>
-
-    <!-- Shown in the music picker while loading the music database. -->
-    <string name="loading">Loading</string>
-    <!-- Menu in music picker to sort the list by track/song name. -->
-    <string name="sort_by_track">Tracks</string>
-    <!-- Menu in music picker to sort the list by album name. -->
-    <string name="sort_by_album">Albums</string>
-    <!-- Menu in music picker to sort the list by artist name. -->
-    <string name="sort_by_artist">Artists</string>
-    <!-- Title of the music picker activity. -->
-    <string name="music_picker_title">Select music track</string>
-
-    <!-- The string used to describe Music as a searchable item within system search settings. -->
-    <string name="search_settings_description">Artists, albums, and tracks</string>
-
-    <!-- Shown in the search box as a hint -->
-    <string name="search_hint">Search Music</string>
-
-    <!-- The fast scroll thumb will show one character from this string depending on the position in the list -->
-    <string name="fast_scroll_alphabet">\u0020ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
-
-    <!-- Text shown on widget when first loaded -->
-    <string name="widget_initial_text">Touch to select music.</string>
-
-    <!-- Menu text for the item controlling the audio effects panel [CHAR LIMIT=15] -->
-    <!-- Extra Controls -->
-    <string name="shuffle_extra">Shuffle</string>
-    <string name="repeat_extra_one">Repeating one</string>
-    <string name="repeat_extra_all">Repeating all</string>
-    <string name="repeat_extra">Repeat</string>
-    <string name="share">Share track</string>
-    <string name="eq">Equalizer</string>
-    <string name="shop">Shop the Market</string>
-    <string name="shop_min">Shop Market</string>
-    <string name="set_ring">Set ringtone</string>
-    <string name="delete_track">Delete track</string>
-    <string name="audio_cat">Audio</string>
-    <string name="ui_cat">UI</string>
-    <string name="edit_gesture_summary">Haptic feedback, color picker, define gestures</string>
-    <string name="gesture_color_picker_title">Gesture Color Picker</string>
-    <string name="gesture_color_picker_summary">Change the color of the gesture overlay</string>
-    <string name="define_gestures">Define Gestures</string>
-    <string name="shake_action_title_bg">Send to Background</string>
-    <string name="shake_action_summary_bg">The Tab action will continue working outside the app</string>
-    <string name="customizations_title">Customization</string>
-    <string name="customizations_summary">Heavy modification</string>
-    <string name="small_np_window_title">Small Now Playing Window</string>
-    <string name="small_np_window_summary">Customize smaller now playing window</string>
-    <string name="small_np_ui">Small Window UI</string>
-    <string name="small_np_ui_summary">Media buttons, text views, font size</string>
-    <string name="np_media_play_title">Play and Pause</string>
-    <string name="np_media_next_title">Next Track</string>
-    <string name="np_media_prev_title">Previous Track</string>
-    <string name="np_media_search_title">Search Music</string>
-    <string name="np_media_playlist_title">Quick Playlist</string>
-    <string name="np_media_market_title">Market Search</string>
-    <string name="np_media_share_title">Share Track</string>
-    <string name="np_media_album_art_title">Album Art</string>
-    <string name="np_media_play_summary">Play and Pause music</string>
-    <string name="np_media_next_summary">Play next track</string>
-    <string name="np_media_prev_summary">Play previous track</string>
-    <string name="np_media_search_summary">Search your music</string>
-    <string name="np_media_playlist_summary">Create a Quick Playlist (Playlist tab only)</string>
-    <string name="np_media_market_summary">Search Market for currently playing artist</string>
-    <string name="np_media_share_summary">Show your current track</string>
-    <string name="np_media_album_art_summary">Show album art</string>
-    <string name="np_tv">      Small Now Playing Window Text Views</string>
-    <string name="np_tv_artist">Artist Name</string>
-    <string name="np_tv_album">Album Name</string>
-    <string name="np_tv_track">Track Name</string>
-    <string name="np_tv_artist_summary">Show artist name</string>
-    <string name="np_tv_album_summary">Show album name</string>
-    <string name="np_tv_track_summary">Show track name</string>
-    <string name="np_font_title">Font Size</string>
-    <string name="np_font_dialog_title">Now Playing Window Font Size</string>
-    <string name="np_font_summary">Choose your font size for artist, album, and track</string>
-    <string name="np_progress_title">Progress bar</string>
-    <string name="np_progress_summary">Show a progress bar above your controls</string>
-    <string name="swipe_up_gesture">Swipe Up Gesture</string>
-    <string name="status_bar_ui_title">Status Bar UI</string>
-    <string name="status_bar_ui_summary">Customize the status bar</string>
-    <string name="status_bar_close">Stop Playback</string>
-    <string name="status_bar_close_summary">Cease all music</string>
-    <string name="status_bar_text_views">Status Bar Text Views</string>
-    <string name="status_bar_color_picker">Status Bar Text Color Picker</string>
-    <string name="status_bar_color_picker_summary">Change the color for album, artist, and track in
-               the status bar</string>
-    <string name="full_np_title">Enter Full Now Playing Screen</string>
-    <string name="full_np_summary">Enter into the full "Now Playing" screen when your
-               music starts</string>
-    <string name="charging_title">Screen On While Charging</string>
-    <string name="charging_summary">Keep the screen on while playing music and charging
-               your phone</string>
-    <string name="fullscreen_np_title">Now Playing Full Screen</string>
-    <string name="fullscreen_np_summary">Hide the status bar in the "Now Playing" screen</string>
-    <string name="lockscreen_controls_title">Use Lockscreen Controls</string>
-    <string name="lockscreen_controls_summary">Display lockscreen controls when your music is
-               playing</string>
-    <string name="album_art_anim_title">Now Playing Animations</string>
-    <string name="album_art_anim_summary">Animate the album art in the "Now Playing" screen</string>
-    <string name="tab_anim_title">Tab Animations</string>
-    <string name="tab_anim_summary">Select animation when swiping tabs</string>
-    <string name="status_bar_nonya_title">Minimal</string>
-    <string name="status_bar_nonya_summary">Remove the album art/status icon when the status bar is extended</string>
-    <string name="build_version">Build Version</string>
-    <string name="build_version_summary">Unknown</string>
-    <string name="flip_to_pause">Flip To Pause</string>
-    <string name="flip_to_pause_summary">Flip your phone over to quickly pause your music then back up to resume</string>
-    <string name="home_art">Album Art As Wallpaper</string>
-    <string name="home_art_summary">Set your home screen wallpaper to the current album art while playing</string>
-    <string name="lock">Lock Screen Controls</string>
-    <string name="lock_summary">Enables lock screen controls</string>
-    <string name="flow">Menu Button</string>
-    <string name="flow_summary">Courtesy for Galaxy Nexus users</string>
-    <string name="tick">Ticker Text</string>
-    <string name="tick_summary">Display a short preview of your notification\'s content </string>
-    <!-- Menu text for the item controlling the audio effects panel [CHAR LIMIT=15] -->
-    <string name="effectspanel">Sound effects</string>
-    <string name="effectspanel_summary">FX Booster, Bass boost, 3D effect, etc</string>
-
-    <!-- Menu text for the music settings item [CHAR LIMIT=15] -->
-    <string name="settings">Settings</string>
-
-    <!-- Title, summary, and entries for 'enable gestures' preference list -->
-    <string name="duck_attenuation_db_title">Duck attentuation</string>
-    <string name="duck_attenuation_db_summary">Select the amount by which to attenuate the volume
-               during focus loss</string>
-
-    <string-array name="duck_attenuation_db_entries">
-        <item>None</item>
-        <item>4 dB</item>
-        <item>8 dB</item>
-        <item>12 dB</item>
-    </string-array>
-    <!-- Do not translate. Entry values for 'enable gestures' preference list -->
-    <string-array name="duck_attenuation_db_entryvalues" translatable="false">
-        <item>0</item>
-        <item>4</item>
-        <item>8</item>
-        <item>12</item>
-    </string-array>
-
-    <string name="back_button_action_title">Long Press Back Button Action</string>
-    <string name="back_button_action">Long Press Back Button</string>
-    <string name="back_button_action_summary">Select long-press action while in tabs</string>
-    <string name="np_swipe_gesture_title">Now Playing Gesture</string>
-    <string name="np_swipe_gesture_title_summary">Small now playing window gesture</string>
-
-    <string-array name="np_font_size_entries">
-        <item>Extra Small</item>
-        <item>Small</item>
-        <item>Normal</item>
-        <item>Large</item>
-        <item>Extra Large</item>
-    </string-array>
-    <string-array name="np_font_size_entrievalues">
-        <item>0</item>
-        <item>1</item>
-        <item>2</item>
-        <item>3</item>
-        <item>4</item>
-    </string-array>
-    <string-array name="np_swipe_gesture_entries">
-        <item>None</item>
-        <item>Play and Pause</item>
-        <item>Next</item>
-        <item>Prev</item>
-    </string-array>
-    <string-array name="np_swipe_gesture_entrievalues">
-        <item>0</item>
-        <item>1</item>
-        <item>2</item>
-        <item>3</item>
-    </string-array>
-    <string-array name="back_button_db_entries">
-        <item>None</item>
-        <item>Party Shuffle</item>
-        <item>Recently Added</item>
-        <item>Shuffle All</item>
-    </string-array>
-    <string-array name="back_button_db_entrievalues">
-        <item>3</item>
-        <item>0</item>
-        <item>1</item>
-        <item>2</item>
-    </string-array>
-
-    <string name="artist_ui_title">Artist Animations</string>
-    <string name="artist_ui_summary">Select animation for the Artist tab</string>
-    <string name="album_ui_title">Album Animations</string>
-    <string name="album_ui_summary">Select animation for the Album tab</string>
-    <string name="song_ui_title">Song Animations</string>
-    <string name="song_ui_summary">Select animation for the Song tab</string>
-    <string name="playlist_ui_title">Playlist Animations</string>
-    <string name="playlist_ui_summary">Select animation for the Playlists tab</string>
-
-    <string-array name="animation_ui_db_entries">
-        <item>None</item>
-        <item>Eye Opener</item>
-        <item>Corkscrew</item>
-        <item>Diagonally</item>
-        <item>Expand</item>
-        <item>Stacked</item>
-        <item>Slide Right</item>
-        <item>Fade</item>
-    </string-array>
-    <string-array name="animation_ui_db_entrievalues">
-        <item>7</item>
-        <item>0</item>
-        <item>1</item>
-        <item>2</item>
-        <item>3</item>
-        <item>4</item>
-        <item>5</item>
-        <item>6</item>
-    </string-array>
-
-    <string name="shaker_title_artist">Tab Shake Actions</string>
-    <string name="shaker_summary_artist">Shake action for each tab</string>
-    <string name="shaker_summary_playlist">Shake action for the Playlist tab</string>
-    <string name="shaker_title_nowplaying">Shake Actions</string>
-    <string name="shaker_summary_nowplaying">Play, next, prev, etc</string>
-    <string name="shaker_title">Shake &amp; Flip Actions</string>
-    <string name="shaker_summary">Select your "shake" and "flip" actions</string>
-    <string name="sensitivity">Sensitivity</string>
-    <string name="sensitivity_summary">Shake &amp; Flip sensitivity</string>
-
-    <string-array name="shaker_db_entries">
-        <item>None</item>
-        <item>Play and Pause</item>
-        <item>Next</item>
-        <item>Prev</item>
-        <item>Shuffle</item>
-        <item>Party Shuffle</item>
-    </string-array>
-    <string-array name="shaker_db_entrievalues">
-        <item>0</item>
-        <item>1</item>
-        <item>2</item>
-        <item>3</item>
-        <item>4</item>
-        <item>5</item>
-    </string-array>
-
-    <string name="animation_ui_title">Animations</string>
-    <string name="animation_ui_summary">Customize your animations</string>
-
-    <!-- Category title for gestures in music settings -->
-    <string name="gestures_category_title">Gestures</string>
-
-    <!-- Title and summary for 'enable gestures' preference checkbox -->
-    <string name="enable_gestures_summary">Control music playback via gestures on the \"Now
-               playing\" screen</string>
-
-    <!-- Title and summary for 'enable haptic feedback' preference checkbox -->
-    <string name="enable_haptic_feedback_title">Haptic feedback</string>
-    <string name="enable_haptic_feedback_summary">Enable phone vibration in response to completed
-               gestures</string>
-
-    <!-- Title for 'edit gestures' preference screen and subsequent activity -->
-    <string name="edit_gestures_title">Edit gestures</string>
-
-    <!-- Titles and summaries for gestures when listed in 'edit gestures' activity -->
-    <string-array name="gesture_titles">
-        <item>Pause/Resume</item>
-        <item>Next</item>
-        <item>Previous</item>
-        <item>Shuffle</item>
-        <item>Repeat</item>
-    </string-array>
-    <string-array name="gesture_summaries">
-        <item>Pause current song or resume playback</item>
-        <item>Skip to next song on playlist</item>
-        <item>Restart current song or skip to previous song on playlist</item>
-        <item>Toggle shuffle on and off</item>
-        <item>Cycle through repeat modes</item>
-    </string-array>
-
-    <!-- Loading text for 'edit gestures' activity -->
-    <string name="gestures_loading">Loading gestures</string>
-
-    <!-- Context menu entries for 'edit gestures' activity -->
-    <string name="gestures_customize">Customize gesture</string>
-    <string name="gestures_reset">Reset to default</string>
-
-    <!-- Button text for 'edit gestures' activity -->
-    <string name="button_reload_gestures">Reload gestures</string>
-    <string name="button_reset_all_gestures">Default</string>
-
-    <!-- Alert dialog text to confirm desire to reset all gestures to default -->
-    <string name="gestures_reset_all_alert">Are you sure you want to reset all gestures to
-               default?</string>
-
-    <!-- Alert dialog 'yes' and 'no' button text -->
-    <string name="button_yes">Yes</string>
-    <string name="button_no">No</string>
-
-    <!-- Error messages for 'edit gestures' activity -->
-    <string name="gestures_error_loading">Could not load gesture library.</string>
-    <string name="gestures_error_library">Your custom gesture library is corrupted. Try
-               resetting all gestures to default.</string>
-
-    <!-- Title for 'customize gesture' activity -->
-    <string name="customize_gesture_title">Customize gesture</string>
-
-    <!-- Prompt and instructions for 'customize gesture' activity -->
-    <string name="custimize_gesture_prompt">Draw your desired gesture below. For compatibility
-               with playback controls, gestures cannot consist solely of a horizontal
-               swipe.</string>
-
-    <!-- Button text for 'customize gesture' activity -->
-    <string name="button_done">Done</string>
-    <string name="button_cancel">Cancel</string>
-
-    <!-- Theme preferences -->
-    <string name="pref_title_theme_settings">Themes Chooser</string>
-    <string name="pref_summary_theme_settings">Select themes for Music</string>
-    <string name="pref_title_theme_package">Select your theme</string>
-    <string name="pref_summary_theme_package">Choose your theme for fLockScreen</string>
-    <string name="pref_title_theme_preview">Theme Preview</string>
-    <string name="pref_summary_theme_preview">Preview of the selected theme</string>
-    <string name="pref_themes_apply_theme">Apply theme!</string>
-    <string name="pref_themes_get_themes">Get themes!</string>
-    <string name="activity_not_found">Android Market can\'t be found!</string>
-
-    <!-- Color Picker Dialog -->
-    <string name="settings_bg_color_confirm">Tap to confirm</string>
-    <string name="settings_default_color_confirm">Default Color</string>
-    <string name="settings_bg_color_dialog">Color Picker</string>
-    <!-- Sample time used in color picker -->
-    <string name="color_picker_sample">Music</string>
-
-    <!-- Labels for colors in color picker -->
-    <string name="color_picker_alpha">Transparency</string>
-    <string name="color_picker_red">Red</string>
-    <string name="color_picker_green">Green</string>
-    <string name="color_picker_blue">Blue</string>
-
-    <!-- Do not show again -->
-    <string name="do_not_show">Do not show again</string>
-
-    <!-- Shake and Flip sensitivity -->
-    <string name="tv_shake_sens">Shake action sensitivity</string>
-    <string name="tv_flip_sens">Flip action sensitivity</string>
-
-</resources>
+<?xml version="1.0" encoding="utf-8"?>\r
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">\r
+\r
+    <!-- App name -->\r
+    <string name="app_name">Apollo</string>\r
+\r
+    <!-- Content descriptions for the Bottom Action Bar -->\r
+    <string name="cd_favorite">Make this a favorite song</string>\r
+    <string name="cd_search">Search through your music</string>\r
+    <string name="cd_overflow">View more options</string>\r
+    <string name="cd_bottom_action_bar_album_art">Album art for this song</string>\r
+    <string name="cd_repeat">Repeat one or all</string>\r
+    <string name="cd_previous">Skip backwards</string>\r
+    <string name="cd_play">Play and pause</string>\r
+    <string name="cd_next">Skip forwards</string>\r
+    <string name="cd_shuffle">Shuffle tracks</string>\r
+\r
+    <!-- AudioPlayer title -->\r
+    <string name="nowplaying">Now Playing</string>\r
+\r
+    <!-- Used to indicate the number of albums for an artist -->\r
+    <plurals name="Nalbums">\r
+\r
+        <!-- Number of albums is one -->\r
+        <item quantity="one">1 album</item>\r
+        <!-- Number of albums is more than one -->\r
+        <item quantity="other"><xliff:g id="count">%d</xliff:g> albums</item>\r
+    </plurals>\r
+\r
+    <!-- Used to indicate the number of songs for an album -->\r
+\r
+    <plurals name="Nsongs">\r
+\r
+        <!-- Number of songs is one -->\r
+        <item quantity="one">1 song</item>\r
+        <item quantity="other"><xliff:g id="count">%d</xliff:g> songs</item>\r
+    </plurals>\r
+\r
+    <!-- Toasts after adding song(s) to playlists -->\r
+    <plurals name="NNNtrackstoplaylist">\r
+\r
+        <!-- message shown when one song was added -->\r
+        <item quantity="one">1 song added to playlist</item>\r
+        <!-- message shown when zero or more than one song was added -->\r
+        <item quantity="other"><xliff:g id="number" example="27">%d</xliff:g> songs added to playlis.</item>\r
+    </plurals>\r
+\r
+    <!-- Headers -->\r
+    <string name="album_header">ALBUM LIST</string>\r
+    <string name="track_header">TRACK LIST</string>\r
+\r
+    <!-- Options MenuItems -->\r
+    <string name="settings">Settings</string>\r
+    <string name="shuffle_all">Shuffle all</string>\r
+    <string name="share">Share</string>\r
+    <string name="play_store">Play Store</string>\r
+    <string name="help">Help</string>\r
+\r
+    <!-- Set track as ringtone -->\r
+    <string name="set_as_ringtone">\"<xliff:g id="name" example="Alarm Bell">%s</xliff:g>\" set as ringtone</string>\r
+\r
+    <!-- Do not translate. Duration format for duration < 1 hour -->\r
+    <string name="durationformatshort" translatable="false"><xliff:g id="format">%2$d:%5$02d</xliff:g></string>\r
+    <!-- Do not translate. Duration format for duration >= 1 hour -->\r
+    <string name="durationformatlong" translatable="false"><xliff:g id="format">%1$d:%3$02d:%5$02d</xliff:g></string>\r
+\r
+    <!-- Transient popup message shown after renaming a playlist -->\r
+    <string name="rename_playlist">Rename playlist</string>\r
+\r
+    <!-- Shuffle and repeat messages -->\r
+    <string name="repeat_one">Repeat one</string>\r
+    <string name="repeat_all">Repeat all</string>\r
+    <string name="repeat_off">Repeat off</string>\r
+    <string name="shuffle_off">Shuffle off</string>\r
+    <string name="shuffle_on">Shuffle on</string>\r
+\r
+    <!-- Share Intent -->\r
+    <string name="now_listening_to">Now listening to:</string>\r
+    <string name="by">by</string>\r
+    <string name="share_track_using">Share track using</string>\r
+\r
+    <!-- ContextMenu items -->\r
+    <string name="play_all">Play all</string>\r
+    <string name="add_to_playlist">Add to playlist</string>\r
+    <string name="use_as_ringtone">Use as ringtone</string>\r
+    <string name="delete_playlist">Delete playlist</string>\r
+    <string name="search">Search</string>\r
+    <string name="remove">Remove from playlist</string>\r
+\r
+    <!-- App Widgets -->\r
+    <string name="apollo_1x1">Apollo (1x1)</string>\r
+    <string name="apollo_4x1">Apollo (4x1)</string>\r
+    <string name="apollo_4x2">Apollo (4x2)</string>\r
+\r
+    <!-- Unknown genre name -->\r
+    <string name="unknown">Unknown</string>\r
+\r
+    <!-- Settings -->\r
+    <string name="about">About Apollo</string>\r
+    <string name="eqalizer">Equalizer</string>\r
+    <string name="header_interface">Interface</string>\r
+    <string name="themes">Themes</string>\r
+    <string name="apollo_themes">Select theme for Apollo</string>\r
+    <string name="select_theme">Select your theme</string>\r
+    <string name="version">Apollo Version</string>\r
+    <string name="author_title">Author</string>\r
+    <string name="author_summary">Andrew Neal</string>\r
+    <string name="credits_title">Credits</string>\r
+    <string name="credits_summary">AOSP, CyanogenMod, A.J Lopez, and all others</string>\r
+\r
+    <!-- Settings keys -->\r
+    <string name="key_themes">themepreview</string>\r
+    <string name="key_themes_package">themePackageName</string>\r
+    <string name="key_themes_preferences">themePrefences</string>\r
+    <string name="key_build_version">build_version</string>\r
+\r
+    <!-- Theme layout Buttons -->\r
+    <string name="apply_theme">Apply theme</string>\r
+    <string name="get_more_themes">Get more themes</string>\r
+\r
+    <!-- Dialog while the media scanner is starting up -->\r
+    <string name="scanning">Scanning SD card\u2026</string>\r
+    <!-- Dialog while the media scanner is starting up -->\r
+    <string name="scanning_nosdcard">Scanning USB storage\u2026</string>\r
+\r
+    <!-- Playlists menu -->\r
+    <string name="favorite">Favorites</string>\r
+    <string name="queue">Queue</string>\r
+    <string name="new_playlist">New</string>\r
+    <string name="new_playlist_name_template">Playlist <xliff:g id="number">%d</xliff:g></string>\r
+    <string name="save">Save</string>\r
+    <string name="overwrite">Overwrite</string>\r
+\r
+    <!-- Something went wrong -->\r
+    <string name="error">Error</string>\r
+\r
+</resources>
\ No newline at end of file
diff --git a/res/values/styles.xml b/res/values/styles.xml
new file mode 100644 (file)
index 0000000..6ac2f46
--- /dev/null
@@ -0,0 +1,186 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<resources>\r
+\r
+    <!-- Custom tabs -->\r
+    <style name="Tabs">\r
+        <item name="android:layout_width">wrap_content</item>\r
+        <item name="android:layout_height">@dimen/bottom_action_bar_height</item>\r
+        <item name="android:background">@drawable/tab</item>\r
+        <item name="android:gravity">center</item>\r
+        <item name="android:paddingBottom">@dimen/tab_padding_top_bottom</item>\r
+        <item name="android:paddingLeft">@dimen/tab_padding_left_right</item>\r
+        <item name="android:paddingRight">@dimen/tab_padding_left_right</item>\r
+        <item name="android:paddingTop">@dimen/tab_padding_top_bottom</item>\r
+        <item name="android:textColor">@color/tab_text_color</item>\r
+        <item name="android:textSize">@dimen/text_size_micro</item>\r
+        <item name="android:textStyle">bold</item>\r
+        <item name="android:focusable">true</item>\r
+        <item name="android:focusableInTouchMode">false</item>\r
+        <item name="android:selectAllOnFocus">false</item>\r
+    </style>\r
+\r
+    <!-- ImageButton in the bottom bar -->\r
+    <style name="BottomActionBarItem">\r
+        <item name="android:layout_width">@dimen/bottom_action_bar_item_width</item>\r
+        <item name="android:layout_height">match_parent</item>\r
+        <item name="android:paddingLeft">@dimen/bottom_action_bar_item_padding_left_right</item>\r
+        <item name="android:paddingRight">@dimen/bottom_action_bar_item_padding_left_right</item>\r
+        <item name="android:background">@drawable/holo_selector</item>\r
+        <item name="android:gravity">center|right</item>\r
+    </style>\r
+\r
+    <!-- TextView in the bottom bar -->\r
+    <style name="BottomActionBarText">\r
+        <item name="android:layout_width">wrap_content</item>\r
+        <item name="android:layout_height">wrap_content</item>\r
+        <item name="android:ellipsize">end</item>\r
+        <item name="android:gravity">top|left|center</item>\r
+        <item name="android:singleLine">true</item>\r
+        <item name="android:textSize">@dimen/text_size_extra_micro</item>\r
+        <item name="android:textAllCaps">true</item>\r
+    </style>\r
+\r
+    <!-- List separator with a blue underline -->\r
+    <style name="SeparatorTextViewStyle">\r
+        <item name="android:layout_width">match_parent</item>\r
+        <item name="android:layout_height">wrap_content</item>\r
+        <item name="android:minHeight">@dimen/list_separator_min_height</item>\r
+        <item name="android:background">@drawable/list_section_divider_holo_custom</item>\r
+        <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>\r
+        <item name="android:textStyle">bold</item>\r
+        <item name="android:textColor">@color/holo_blue_dark</item>\r
+        <item name="android:gravity">center_vertical</item>\r
+        <item name="android:paddingRight">@dimen/list_separator_padding_left_right</item>\r
+        <item name="android:visibility">gone</item>\r
+        <item name="android:ellipsize">end</item>\r
+        <item name="android:singleLine">true</item>\r
+        <item name="android:textAllCaps">true</item>\r
+    </style>\r
+    <!-- Notification bar button -->\r
+    <style name="StatusBarButton">\r
+        <item name="android:layout_width">@dimen/status_bar_button_width_height</item>\r
+        <item name="android:layout_height">@dimen/status_bar_button_width_height</item>\r
+        <item name="android:layout_gravity">center|right</item>\r
+        <item name="android:background">?android:listChoiceBackgroundIndicator</item>\r
+    </style>\r
+\r
+    <!-- Notification bar text -->\r
+    <style name="StatusBarText">\r
+        <item name="android:layout_width">wrap_content</item>\r
+        <item name="android:layout_height">wrap_content</item>\r
+        <item name="android:layout_gravity">left</item>\r
+        <item name="android:ellipsize">marquee</item>\r
+        <item name="android:scrollHorizontally">true</item>\r
+        <item name="android:singleLine">true</item>\r
+    </style>\r
+\r
+    <!-- Half and half layout -->\r
+    <style name="HalfText">\r
+        <item name="android:layout_width">match_parent</item>\r
+        <item name="android:layout_height">wrap_content</item>\r
+        <item name="android:background">@color/transparent_black</item>\r
+        <item name="android:ellipsize">end</item>\r
+        <item name="android:gravity">center</item>\r
+        <item name="android:padding">@dimen/half_and_half_text_padding</item>\r
+        <item name="android:singleLine">true</item>\r
+        <item name="android:textColor">@color/white</item>\r
+        <item name="android:textSize">@dimen/text_size_small</item>\r
+    </style>\r
+\r
+    <!-- ContextMenu header text -->\r
+    <style name="HeaderText">\r
+        <item name="android:layout_width">match_parent</item>\r
+        <item name="android:layout_height">wrap_content</item>\r
+        <item name="android:ellipsize">end</item>\r
+        <item name="android:gravity">center|left</item>\r
+        <item name="android:paddingTop">@dimen/header_text_padding</item>\r
+        <item name="android:paddingLeft">@dimen/header_text_padding_left</item>\r
+        <item name="android:paddingBottom">@dimen/header_text_padding</item>\r
+        <item name="android:paddingRight">@dimen/header_text_padding</item>\r
+        <item name="android:singleLine">true</item>\r
+        <item name="android:textColor">@color/white</item>\r
+        <item name="android:textSize">@dimen/text_size_large</item>\r
+    </style>\r
+\r
+    <!-- TextView in shown over the images in the GridView -->\r
+    <style name="GridViewTextItem">\r
+        <item name="android:layout_width">wrap_content</item>\r
+        <item name="android:layout_height">wrap_content</item>\r
+        <item name="android:singleLine">true</item>\r
+        <item name="android:ellipsize">end</item>\r
+        <item name="android:shadowColor">@color/white</item>\r
+        <item name="android:shadowRadius">1</item>\r
+        <item name="android:textColor">@color/white</item>\r
+        <item name="android:textSize">@dimen/text_size_medium</item>\r
+    </style>\r
+\r
+    <!-- Now playing indicator -->\r
+    <style name="PeakMeter">\r
+        <item name="android:layout_width">wrap_content</item>\r
+        <item name="android:layout_height">wrap_content</item>\r
+        <item name="android:layout_alignParentRight">true</item>\r
+    </style>\r
+\r
+    <!-- TextView in the audio player -->\r
+    <style name="AudioPlayerText">\r
+        <item name="android:layout_width">wrap_content</item>\r
+        <item name="android:layout_height">wrap_content</item>\r
+        <item name="android:ellipsize">marquee</item>\r
+        <item name="android:singleLine">true</item>\r
+        <item name="android:focusable">true</item>\r
+        <item name="android:focusableInTouchMode">true</item>\r
+        <item name="android:lineSpacingMultiplier">1.2</item>\r
+        <item name="android:scrollHorizontally">true</item>\r
+    </style>\r
+\r
+    <!-- ImageButton in the audio player controls -->\r
+    <style name="AudioPlayerButton">\r
+        <item name="android:layout_width">0dp</item>\r
+        <item name="android:layout_height">match_parent</item>\r
+        <item name="android:layout_weight">1</item>\r
+        <item name="android:background">@drawable/holo_selector</item>\r
+    </style>\r
+\r
+    <!-- QuickQueue -->\r
+    <style name="Theme.QuickQueue" parent="@android:style/Theme.Holo.Light">\r
+        <item name="android:windowBackground">@color/transparent</item>\r
+        <item name="android:colorBackgroundCacheHint">@null</item>\r
+        <item name="android:windowFrame">@null</item>\r
+        <item name="android:windowContentOverlay">@null</item>\r
+        <item name="android:windowAnimationStyle">@null</item>\r
+        <item name="android:windowIsFloating">false</item>\r
+        <item name="android:backgroundDimEnabled">true</item>\r
+        <item name="android:windowIsTranslucent">true</item>\r
+        <item name="android:windowNoTitle">true</item>\r
+    </style>\r
+\r
+    <!-- App Widget 4x2 -->\r
+    <style name="FourByTwoMediaButton">\r
+        <item name="android:layout_width">0dp</item>\r
+        <item name="android:layout_height">match_parent</item>\r
+        <item name="android:layout_weight">1</item>\r
+        <item name="android:background">@drawable/holo_selector</item>\r
+        <item name="android:scaleType">center</item>\r
+    </style>\r
+\r
+    <!-- Overflow Holo theme -->\r
+    <style name="Apollo.Holo" parent="@android:style/Theme.Holo.Light">\r
+        <item name="android:actionOverflowButtonStyle">@style/OverFlowHolo</item>\r
+    </style>\r
+\r
+    <!-- Overflow Holo.Light theme -->\r
+    <style name="Apollo.Holo.Light" parent="@android:style/Theme.Holo.Light">\r
+        <item name="android:actionOverflowButtonStyle">@style/OverFlowHolo.Light</item>\r
+    </style>\r
+\r
+    <!-- Overflow Holo.Dark -->\r
+    <style name="OverFlowHolo" parent="@android:style/Widget.Holo.ActionButton.Overflow">\r
+        <item name="android:src">@drawable/apollo_holo_dark_overflow</item>\r
+    </style>\r
+\r
+    <!-- Overflow Holo.Dark -->\r
+    <style name="OverFlowHolo.Light" parent="@android:style/Widget.Holo.ActionButton.Overflow">\r
+        <item name="android:src">@drawable/apollo_holo_light_overflow</item>\r
+    </style>\r
+\r
+</resources>
\ No newline at end of file
index 00df66f..680c1ef 100644 (file)
@@ -1,24 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>\r
-<!--\r
-     Copyright (C) 2009 The Android Open Source Project\r
-\r
-     Licensed under the Apache License, Version 2.0 (the "License");\r
-     you may not use this file except in compliance with the License.\r
-     You may obtain a copy of the License at\r
-  \r
-          http://www.apache.org/licenses/LICENSE-2.0\r
-  \r
-     Unless required by applicable law or agreed to in writing, software\r
-     distributed under the License is distributed on an "AS IS" BASIS,\r
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
-     See the License for the specific language governing permissions and\r
-     limitations under the License.\r\r\r\r
--->\r
-\r
 <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"\r
-    android:initialLayout="@layout/album_appwidget1x1"\r
-    android:minHeight="40dip"\r
-    android:minWidth="40dip"\r
-    android:updatePeriodMillis="0" >\r
-\r
-</appwidget-provider>
\ No newline at end of file
+    android:initialLayout="@layout/onebyone_app_widget"\r
+    android:minHeight="40dp"\r
+    android:minWidth="40dp"\r
+    android:updatePeriodMillis="0" />
\ No newline at end of file
index cc665a1..aad2bb0 100644 (file)
@@ -1,24 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2009 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-  
-          http://www.apache.org/licenses/LICENSE-2.0
-  
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
 <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
-    android:initialLayout="@layout/album_appwidget4x1"
-    android:minHeight="40dip"
-    android:minWidth="260dip"
-    android:updatePeriodMillis="0" >
-
-</appwidget-provider>
\ No newline at end of file
+    android:initialLayout="@layout/fourbyone_app_widget"
+    android:minHeight="40dp"
+    android:minWidth="260dp"
+    android:updatePeriodMillis="0" />
index 50094a3..0605762 100644 (file)
@@ -1,23 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>\r
-<!-- Copyright (C) 2009 The Android Open Source Project\r
-\r
-     Licensed under the Apache License, Version 2.0 (the "License");\r
-     you may not use this file except in compliance with the License.\r
-     You may obtain a copy of the License at\r
-  \r
-          http://www.apache.org/licenses/LICENSE-2.0\r
-  \r
-     Unless required by applicable law or agreed to in writing, software\r
-     distributed under the License is distributed on an "AS IS" BASIS,\r
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
-     See the License for the specific language governing permissions and\r
-     limitations under the License.\r
--->\r
-\r
 <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"\r
-    android:minWidth="250dp"\r
+    android:initialLayout="@layout/fourbytwo_app_widget"\r
     android:minHeight="110dp"\r
-    android:updatePeriodMillis="0"\r
-    android:initialLayout="@layout/album_appwidget4x2"\r
-    >\r
-</appwidget-provider>\r
+    android:minWidth="250dp"\r
+    android:updatePeriodMillis="0" />\r
index b00f540..c4f8174 100644 (file)
 -->
 
 <searchable xmlns:android="http://schemas.android.com/apk/res/android"
-    android:hint="@string/search_hint"
     android:includeInGlobalSearch="true"
-    android:label="@string/search_title"
-    android:searchSettingsDescription="@string/search_settings_description"
-    android:searchSuggestAuthority="media"
+    android:label="@string/search"
     android:searchSuggestIntentAction="android.intent.action.VIEW"
-    android:searchSuggestPath="external/audio/search"
-    android:searchSuggestThreshold="3"
     android:voiceSearchMode="showVoiceSearchButton|launchRecognizer" />
index cd2fde6..fbd15fc 100644 (file)
 <?xml version="1.0" encoding="utf-8"?>\r
-<!--\r
-     /* * Copyright 2011, The CyanogenMod Project * * Licensed under the \r
-       Apache License, Version 2.0 (the "License"); * you may not use this file \r
-       except in compliance with the License. * You may obtain a copy of the License \r
-       at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by \r
-       applicable law or agreed to in writing, software * distributed under the \r
-       License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS \r
-       OF ANY KIND, either express or implied. * See the License for the specific \r
-       language governing permissions and * limitations under the License. */\r\r\r\r\r\r\r\r\r
--->\r
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >\r
 \r
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"\r
-    android:key="settings"\r
-    android:title="@string/settings" >\r
-\r
-    <PreferenceCategory android:title="@string/audio_cat" >\r
-        <ListPreference\r
-            android:defaultValue="8"\r
-            android:dialogTitle="@string/duck_attenuation_db_title"\r
-            android:entries="@array/duck_attenuation_db_entries"\r
-            android:entryValues="@array/duck_attenuation_db_entryvalues"\r
-            android:key="duck_attenuation_db"\r
-            android:summary="@string/duck_attenuation_db_summary"\r
-            android:title="@string/duck_attenuation_db_title" />\r
-\r
-        <Preference\r
-            android:enabled="true"\r
-            android:key="eqEffects"\r
-            android:summary="@string/effectspanel_summary"\r
-            android:title="@string/effectspanel" />\r
-    </PreferenceCategory>\r
-    <PreferenceCategory android:title="@string/ui_cat" >\r
+    <PreferenceCategory android:title="@string/header_interface" >\r
         <PreferenceScreen\r
-            android:summary="@string/shaker_summary"\r
-            android:title="@string/shaker_title" >\r
-            <PreferenceCategory android:title="Action Control" >\r
-                <ListPreference\r
-                    android:defaultValue="0"\r
-                    android:dialogTitle="@string/shaker_title_nowplaying"\r
-                    android:entries="@array/shaker_db_entries"\r
-                    android:entryValues="@array/shaker_db_entrievalues"\r
-                    android:key="shake_actions_db"\r
-                    android:summary="@string/shaker_summary_nowplaying"\r
-                    android:title="@string/shaker_title_nowplaying" />\r
-\r
-                <CheckBoxPreference\r
-                    android:defaultValue="true"\r
-                    android:key="cbFlip"\r
-                    android:summary="@string/flip_to_pause_summary"\r
-                    android:title="@string/flip_to_pause" />\r
-\r
-                <PreferenceScreen\r
-                    android:key="sens_picker"\r
-                    android:title="@string/sensitivity" \r
-                    android:summary="@string/sensitivity_summary">\r
-                    <intent\r
-                        android:action="com.android.music.Sensitivity"\r
-                        android.targetClass="com.android.music.Sensitivity"\r
-                        android:targetPackage="com.android.music" />\r
-                </PreferenceScreen>\r
-            </PreferenceCategory>\r
-        </PreferenceScreen>\r
-        <PreferenceScreen\r
-            android:summary="@string/customizations_summary"\r
-            android:title="@string/customizations_title" >\r
-            <PreferenceScreen\r
-                android:summary="@string/animation_ui_summary"\r
-                android:title="@string/animation_ui_title" >\r
-                <ListPreference\r
-                    android:defaultValue="7"\r
-                    android:dialogTitle="Now Playing Animations"\r
-                    android:entries="@array/animation_ui_db_entries"\r
-                    android:entryValues="@array/animation_ui_db_entrievalues"\r
-                    android:key="np_animation_ui_db"\r
-                    android:summary="@string/album_art_anim_summary"\r
-                    android:title="@string/album_art_anim_title" />\r
-            </PreferenceScreen>\r
-            <PreferenceScreen\r
-                android:summary="@string/small_np_window_summary"\r
-                android:title="@string/small_np_window_title" >\r
-                <PreferenceScreen\r
-                    android:summary="@string/small_np_ui_summary"\r
-                    android:title="@string/small_np_ui" >\r
-                    <PreferenceCategory android:title="@string/small_np_ui" >\r
-                        <CheckBoxPreference\r
-                            android:defaultValue="false"\r
-                            android:key="cbPlay"\r
-                            android:summary="@string/np_media_play_summary"\r
-                            android:title="@string/np_media_play_title" />\r
-                        <CheckBoxPreference\r
-                            android:defaultValue="false"\r
-                            android:key="cbNext"\r
-                            android:summary="@string/np_media_next_summary"\r
-                            android:title="@string/np_media_next_title" />\r
-                        <CheckBoxPreference\r
-                            android:defaultValue="false"\r
-                            android:key="cbPrev"\r
-                            android:summary="@string/np_media_prev_summary"\r
-                            android:title="@string/np_media_prev_title" />\r
-                        <CheckBoxPreference\r
-                            android:defaultValue="true"\r
-                            android:key="cbSearch"\r
-                            android:summary="@string/np_media_search_summary"\r
-                            android:title="@string/np_media_search_title" />\r
-                        <CheckBoxPreference\r
-                            android:defaultValue="true"\r
-                            android:key="cbPlaylist"\r
-                            android:summary="@string/np_media_playlist_summary"\r
-                            android:title="@string/np_media_playlist_title" />\r
-                        <CheckBoxPreference\r
-                            android:defaultValue="false"\r
-                            android:key="cbMarket"\r
-                            android:summary="@string/np_media_market_summary"\r
-                            android:title="@string/np_media_market_title" />\r
-                        <CheckBoxPreference\r
-                            android:defaultValue="false"\r
-                            android:key="cbShare"\r
-                            android:summary="@string/np_media_share_summary"\r
-                            android:title="@string/np_media_share_title" />\r
-                        <CheckBoxPreference\r
-                            android:defaultValue="true"\r
-                            android:key="cbArt"\r
-                            android:summary="@string/np_media_album_art_summary"\r
-                            android:title="@string/np_media_album_art_title" />\r
-                        <CheckBoxPreference\r
-                            android:defaultValue="false"\r
-                            android:key="cbProgress"\r
-                            android:summary="@string/np_progress_summary"\r
-                            android:title="@string/np_progress_title" />\r
-                        <CheckBoxPreference\r
-                            android:defaultValue="false"\r
-                            android:key="cbFlow"\r
-                            android:summary="@string/flow_summary"\r
-                            android:title="@string/flow" />\r
-                    </PreferenceCategory>\r
-                    <PreferenceCategory android:title="@string/np_tv" >\r
-                        <CheckBoxPreference\r
-                            android:defaultValue="true"\r
-                            android:key="tvLine1"\r
-                            android:summary="@string/np_tv_track_summary"\r
-                            android:title="@string/np_tv_track" />\r
-                        <CheckBoxPreference\r
-                            android:defaultValue="true"\r
-                            android:key="tvLine2"\r
-                            android:summary="@string/np_tv_artist_summary"\r
-                            android:title="@string/np_tv_artist" />\r
-                        <CheckBoxPreference\r
-                            android:defaultValue="false"\r
-                            android:key="tvLine3"\r
-                            android:summary="@string/np_tv_album_summary"\r
-                            android:title="@string/np_tv_album" />\r
-\r
-                        <ListPreference\r
-                            android:defaultValue="2"\r
-                            android:dialogTitle="@string/np_font_dialog_title"\r
-                            android:entries="@array/np_font_size_entries"\r
-                            android:entryValues="@array/np_font_size_entrievalues"\r
-                            android:key="np_font_size"\r
-                            android:summary="@string/np_font_summary"\r
-                            android:title="@string/np_font_title" />\r
-                    </PreferenceCategory>\r
-                </PreferenceScreen>\r
-\r
-                <ListPreference\r
-                    android:defaultValue="2"\r
-                    android:dialogTitle="@string/np_swipe_gesture_title"\r
-                    android:entries="@array/np_swipe_gesture_entries"\r
-                    android:entryValues="@array/np_swipe_gesture_entrievalues"\r
-                    android:key="np_swipe_gesture"\r
-                    android:summary="@string/np_swipe_gesture_title_summary"\r
-                    android:title="@string/swipe_up_gesture" />\r
-            </PreferenceScreen>\r
-            <PreferenceScreen\r
-                android:summary="@string/status_bar_ui_summary"\r
-                android:title="@string/status_bar_ui_title" >\r
-                <PreferenceCategory android:title="@string/status_bar_ui_title" >\r
-                    <CheckBoxPreference\r
-                        android:defaultValue="false"\r
-                        android:key="cbStatusPlay"\r
-                        android:summary="@string/np_media_play_summary"\r
-                        android:title="@string/np_media_play_title" />\r
-                    <CheckBoxPreference\r
-                        android:defaultValue="false"\r
-                        android:key="cbStatusNext"\r
-                        android:summary="@string/np_media_next_summary"\r
-                        android:title="@string/np_media_next_title" />\r
-                    <CheckBoxPreference\r
-                        android:defaultValue="false"\r
-                        android:key="cbStatusPrev"\r
-                        android:summary="@string/np_media_prev_summary"\r
-                        android:title="@string/np_media_prev_title" />\r
-                    <CheckBoxPreference\r
-                        android:defaultValue="false"\r
-                        android:key="cbStatusCollapse"\r
-                        android:summary="@string/status_bar_close_summary"\r
-                        android:title="@string/status_bar_close" />\r
-                    <CheckBoxPreference\r
-                        android:defaultValue="true"\r
-                        android:key="cbStatusArt"\r
-                        android:summary="@string/np_media_album_art_summary"\r
-                        android:title="@string/np_media_album_art_title" />\r
-                    <CheckBoxPreference\r
-                        android:defaultValue="false"\r
-                        android:key="cbStatusNonya"\r
-                        android:summary="@string/status_bar_nonya_summary"\r
-                        android:title="@string/status_bar_nonya_title" />\r
-                    <CheckBoxPreference\r
-                        android:defaultValue="true"\r
-                        android:key="cbStatusTicker"\r
-                        android:summary="@string/tick_summary"\r
-                        android:title="@string/tick" />\r
-                </PreferenceCategory>\r
-                <PreferenceCategory android:title="@string/status_bar_text_views" >\r
-                    <CheckBoxPreference\r
-                        android:defaultValue="true"\r
-                        android:key="tvStatusLine1"\r
-                        android:summary="@string/np_tv_track_summary"\r
-                        android:title="@string/np_tv_track" />\r
-                    <CheckBoxPreference\r
-                        android:defaultValue="true"\r
-                        android:key="tvStatusLine2"\r
-                        android:summary="@string/np_tv_artist_summary"\r
-                        android:title="@string/np_tv_artist" />\r
-                    <CheckBoxPreference\r
-                        android:defaultValue="false"\r
-                        android:key="tvStatusLine3"\r
-                        android:summary="@string/np_tv_album_summary"\r
-                        android:title="@string/np_tv_album" />\r
-                    <CheckBoxPreference\r
-                        android:defaultValue="true"\r
-                        android:key="tvStatusColor"\r
-                        android:summary="Use the default text colors"\r
-                        android:title="Default Text Colors" />\r
-\r
-                    <PreferenceScreen\r
-                        android:key="color_picker"\r
-                        android:summary="@string/status_bar_color_picker_summary"\r
-                        android:title="@string/status_bar_color_picker" >\r
-                        <intent\r
-                            android:action="com.android.music.ColorPicker"\r
-                            android.targetClass="com.android.music.ColorPicker"\r
-                            android:targetPackage="com.android.music" />\r
-                    </PreferenceScreen>\r
-                </PreferenceCategory>\r
-            </PreferenceScreen>\r
-\r
+            android:icon="@drawable/apollo_settings_themes"\r
+            android:key="@string/key_themes_preferences"\r
+            android:title="@string/themes" >\r
             <ListPreference\r
-                android:defaultValue="1"\r
-                android:dialogTitle="@string/back_button_action_title"\r
-                android:entries="@array/back_button_db_entries"\r
-                android:entryValues="@array/back_button_db_entrievalues"\r
-                android:key="back_button_db"\r
-                android:summary="@string/back_button_action_summary"\r
-                android:title="@string/back_button_action" />\r
+                android:key="@string/key_themes_package"\r
+                android:summary="@string/apollo_themes"\r
+                android:title="@string/select_theme" />\r
 \r
-            <CheckBoxPreference\r
-                android:defaultValue="false"\r
-                android:key="cbEnterNowPlaying"\r
-                android:summary="@string/full_np_summary"\r
-                android:title="@string/full_np_title" />\r
-            <CheckBoxPreference\r
-                android:defaultValue="true"\r
-                android:key="cbLock"\r
-                android:summary="@string/lock_summary"\r
-                android:title="@string/lock" />\r
-            <CheckBoxPreference\r
-                android:defaultValue="false"\r
-                android:key="cbHomeAlbumArt"\r
-                android:summary="@string/home_art_summary"\r
-                android:title="@string/home_art" />\r
+            <com.andrew.apollo.preferences.ThemePreview\r
+                android:key="@string/key_themes"\r
+                android:layout="@layout/theme_preview" />\r
         </PreferenceScreen>\r
     </PreferenceCategory>\r
-\r
-    <PreferenceScreen\r
-        android:key="themePrefences"\r
-        android:summary="@string/pref_summary_theme_settings"\r
-        android:title="@string/pref_title_theme_settings" >\r
-        <ListPreference\r
-            android:key="themePackageName"\r
-            android:summary="@string/pref_summary_theme_settings"\r
-            android:title="@string/pref_title_theme_package" />\r
-\r
-        <com.android.music.PreviewPreference\r
-            android:key="themePreview"\r
-            android:layout="@layout/theme_preview" />\r
-    </PreferenceScreen>\r
-\r
-    <Preference\r
-        style="?android:preferenceInformationStyle"\r
-        android:enabled="true"\r
-        android:key="build"\r
-        android:summary="@string/build_version_summary"\r
-        android:title="@string/build_version" />\r
+    <PreferenceCategory android:title="@string/about" >\r
+        <Preference\r
+            style="?android:preferenceInformationStyle"\r
+            android:enabled="false"\r
+            android:key="@string/key_build_version"\r
+            android:summary="1.0"\r
+            android:title="@string/version" />\r
+    </PreferenceCategory>\r
 \r
 </PreferenceScreen>
\ No newline at end of file
diff --git a/src/com/andrew/apollo/AudioPlayerFragment.java b/src/com/andrew/apollo/AudioPlayerFragment.java
new file mode 100644 (file)
index 0000000..d61fb68
--- /dev/null
@@ -0,0 +1,638 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo;\r
+\r
+import android.content.BroadcastReceiver;\r
+import android.content.Context;\r
+import android.content.Intent;\r
+import android.content.IntentFilter;\r
+import android.os.AsyncTask;\r
+import android.os.Bundle;\r
+import android.os.Handler;\r
+import android.os.Message;\r
+import android.os.RemoteException;\r
+import android.os.SystemClock;\r
+import android.provider.BaseColumns;\r
+import android.provider.MediaStore.Audio;\r
+import android.support.v4.app.Fragment;\r
+import android.view.LayoutInflater;\r
+import android.view.View;\r
+import android.view.View.OnClickListener;\r
+import android.view.ViewGroup;\r
+import android.widget.ImageButton;\r
+import android.widget.ImageView;\r
+import android.widget.SeekBar;\r
+import android.widget.SeekBar.OnSeekBarChangeListener;\r
+import android.widget.TextView;\r
+import android.widget.Toast;\r
+\r
+import com.andrew.apollo.activities.TracksBrowser;\r
+import com.andrew.apollo.service.ApolloService;\r
+import com.andrew.apollo.tasks.GetCachedImages;\r
+import com.andrew.apollo.tasks.LastfmGetAlbumImages;\r
+import com.andrew.apollo.ui.widgets.RepeatingImageButton;\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+import com.andrew.apollo.utils.MusicUtils;\r
+import com.andrew.apollo.utils.ThemeUtils;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ */\r
+public class AudioPlayerFragment extends Fragment implements Constants {\r
+\r
+    // Track, album, and artist name\r
+    private TextView mTrackName, mAlbumArtistName;\r
+\r
+    // Total and current time\r
+    private TextView mTotalTime, mCurrentTime;\r
+\r
+    // Album art\r
+    private ImageView mAlbumArt;\r
+\r
+    // Controls\r
+    private ImageButton mRepeat, mPlay, mShuffle;\r
+\r
+    private RepeatingImageButton mPrev, mNext;\r
+\r
+    // Progress\r
+    private SeekBar mProgress;\r
+\r
+    // Where we are in the track\r
+    private long mDuration, mLastSeekEventTime, mPosOverride = -1, mStartSeekPos = 0;\r
+\r
+    private boolean mFromTouch, paused = false;\r
+\r
+    // Handler\r
+    private static final int REFRESH = 1, UPDATEINFO = 2;\r
+\r
+    // Notify if repeat or shuffle changes\r
+    private Toast mToast;\r
+\r
+    @Override\r
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\r
+        View root = inflater.inflate(R.layout.audio_player, container, false);\r
+\r
+        mTrackName = (TextView)root.findViewById(R.id.audio_player_track);\r
+        mTrackName.setOnClickListener(new OnClickListener() {\r
+\r
+            @Override\r
+            public void onClick(View v) {\r
+                tracksBrowser();\r
+            }\r
+        });\r
+        mAlbumArtistName = (TextView)root.findViewById(R.id.audio_player_album_artist);\r
+        mAlbumArtistName.setOnClickListener(new OnClickListener() {\r
+\r
+            @Override\r
+            public void onClick(View v) {\r
+                tracksBrowserArtist();\r
+            }\r
+        });\r
+\r
+        mTotalTime = (TextView)root.findViewById(R.id.audio_player_total_time);\r
+        mCurrentTime = (TextView)root.findViewById(R.id.audio_player_current_time);\r
+\r
+        mAlbumArt = (ImageView)root.findViewById(R.id.audio_player_album_art);\r
+\r
+        mRepeat = (ImageButton)root.findViewById(R.id.audio_player_repeat);\r
+        mPrev = (RepeatingImageButton)root.findViewById(R.id.audio_player_prev);\r
+        mPlay = (ImageButton)root.findViewById(R.id.audio_player_play);\r
+        mNext = (RepeatingImageButton)root.findViewById(R.id.audio_player_next);\r
+        mShuffle = (ImageButton)root.findViewById(R.id.audio_player_shuffle);\r
+\r
+        mRepeat.setOnClickListener(new OnClickListener() {\r
+\r
+            @Override\r
+            public void onClick(View v) {\r
+                cycleRepeat();\r
+            }\r
+        });\r
+\r
+        mPrev.setRepeatListener(mRewListener, 260);\r
+        mPrev.setOnClickListener(new OnClickListener() {\r
+\r
+            @Override\r
+            public void onClick(View v) {\r
+                if (MusicUtils.mService == null)\r
+                    return;\r
+                try {\r
+                    if (MusicUtils.mService.position() < 2000) {\r
+                        MusicUtils.mService.prev();\r
+                    } else {\r
+                        MusicUtils.mService.seek(0);\r
+                        MusicUtils.mService.play();\r
+                    }\r
+                } catch (RemoteException ex) {\r
+                    ex.printStackTrace();\r
+                }\r
+            }\r
+        });\r
+\r
+        mPlay.setOnClickListener(new OnClickListener() {\r
+\r
+            @Override\r
+            public void onClick(View v) {\r
+                doPauseResume();\r
+            }\r
+        });\r
+\r
+        mNext.setRepeatListener(mFfwdListener, 260);\r
+        mNext.setOnClickListener(new OnClickListener() {\r
+\r
+            @Override\r
+            public void onClick(View v) {\r
+                if (MusicUtils.mService == null)\r
+                    return;\r
+                try {\r
+                    MusicUtils.mService.next();\r
+                } catch (RemoteException ex) {\r
+                    ex.printStackTrace();\r
+                }\r
+            }\r
+        });\r
+\r
+        mShuffle.setOnClickListener(new OnClickListener() {\r
+\r
+            @Override\r
+            public void onClick(View v) {\r
+                toggleShuffle();\r
+            }\r
+        });\r
+\r
+        mProgress = (SeekBar)root.findViewById(android.R.id.progress);\r
+        if (mProgress instanceof SeekBar) {\r
+            SeekBar seeker = mProgress;\r
+            seeker.setOnSeekBarChangeListener(mSeekListener);\r
+        }\r
+        mProgress.setMax(1000);\r
+\r
+        // Theme chooser\r
+        ThemeUtils.setImageButton(getActivity(), mPrev, "apollo_previous");\r
+        ThemeUtils.setImageButton(getActivity(), mNext, "apollo_next");\r
+        ThemeUtils.setProgessDrawable(getActivity(), mProgress, "apollo_seekbar_background");\r
+        return root;\r
+    }\r
+\r
+    /**\r
+     * Update everything as the meta or playstate changes\r
+     */\r
+    private final BroadcastReceiver mStatusListener = new BroadcastReceiver() {\r
+        @Override\r
+        public void onReceive(Context context, Intent intent) {\r
+            if (intent.getAction().equals(ApolloService.META_CHANGED))\r
+                mHandler.sendMessage(mHandler.obtainMessage(UPDATEINFO));\r
+            setPauseButtonImage();\r
+            setShuffleButtonImage();\r
+            setRepeatButtonImage();\r
+        }\r
+    };\r
+\r
+    @Override\r
+    public void onStart() {\r
+        super.onStart();\r
+        IntentFilter f = new IntentFilter();\r
+        f.addAction(ApolloService.PLAYSTATE_CHANGED);\r
+        f.addAction(ApolloService.META_CHANGED);\r
+        getActivity().registerReceiver(mStatusListener, new IntentFilter(f));\r
+\r
+        long next = refreshNow();\r
+        queueNextRefresh(next);\r
+    }\r
+\r
+    @Override\r
+    public void onDestroy() {\r
+        super.onDestroy();\r
+        paused = true;\r
+        mHandler.removeMessages(REFRESH);\r
+        getActivity().unregisterReceiver(mStatusListener);\r
+    }\r
+\r
+    /**\r
+     * Cycle repeat states\r
+     */\r
+    private void cycleRepeat() {\r
+        if (MusicUtils.mService == null) {\r
+            return;\r
+        }\r
+        try {\r
+            int mode = MusicUtils.mService.getRepeatMode();\r
+            if (mode == ApolloService.REPEAT_NONE) {\r
+                MusicUtils.mService.setRepeatMode(ApolloService.REPEAT_ALL);\r
+                ApolloUtils.showToast(R.string.repeat_all, mToast, getActivity());\r
+            } else if (mode == ApolloService.REPEAT_ALL) {\r
+                MusicUtils.mService.setRepeatMode(ApolloService.REPEAT_CURRENT);\r
+                if (MusicUtils.mService.getShuffleMode() != ApolloService.SHUFFLE_NONE) {\r
+                    MusicUtils.mService.setShuffleMode(ApolloService.SHUFFLE_NONE);\r
+                    setShuffleButtonImage();\r
+                }\r
+                ApolloUtils.showToast(R.string.repeat_one, mToast, getActivity());\r
+            } else {\r
+                MusicUtils.mService.setRepeatMode(ApolloService.REPEAT_NONE);\r
+                ApolloUtils.showToast(R.string.repeat_off, mToast, getActivity());\r
+            }\r
+            setRepeatButtonImage();\r
+        } catch (RemoteException ex) {\r
+            ex.printStackTrace();\r
+        }\r
+\r
+    }\r
+\r
+    /**\r
+     * Scan backwards\r
+     */\r
+    private final RepeatingImageButton.RepeatListener mRewListener = new RepeatingImageButton.RepeatListener() {\r
+        @Override\r
+        public void onRepeat(View v, long howlong, int repcnt) {\r
+            scanBackward(repcnt, howlong);\r
+        }\r
+    };\r
+\r
+    /**\r
+     * Play and pause music\r
+     */\r
+    private void doPauseResume() {\r
+        try {\r
+            if (MusicUtils.mService != null) {\r
+                if (MusicUtils.mService.isPlaying()) {\r
+                    MusicUtils.mService.pause();\r
+                } else {\r
+                    MusicUtils.mService.play();\r
+                }\r
+            }\r
+            refreshNow();\r
+            setPauseButtonImage();\r
+        } catch (RemoteException ex) {\r
+            ex.printStackTrace();\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Scan forwards\r
+     */\r
+    private final RepeatingImageButton.RepeatListener mFfwdListener = new RepeatingImageButton.RepeatListener() {\r
+        @Override\r
+        public void onRepeat(View v, long howlong, int repcnt) {\r
+            scanForward(repcnt, howlong);\r
+        }\r
+    };\r
+\r
+    /**\r
+     * Set the shuffle mode\r
+     */\r
+    private void toggleShuffle() {\r
+        if (MusicUtils.mService == null) {\r
+            return;\r
+        }\r
+        try {\r
+            int shuffle = MusicUtils.mService.getShuffleMode();\r
+            if (shuffle == ApolloService.SHUFFLE_NONE) {\r
+                MusicUtils.mService.setShuffleMode(ApolloService.SHUFFLE_NORMAL);\r
+                if (MusicUtils.mService.getRepeatMode() == ApolloService.REPEAT_CURRENT) {\r
+                    MusicUtils.mService.setRepeatMode(ApolloService.REPEAT_ALL);\r
+                    setRepeatButtonImage();\r
+                }\r
+                ApolloUtils.showToast(R.string.shuffle_on, mToast, getActivity());\r
+            } else if (shuffle == ApolloService.SHUFFLE_NORMAL\r
+                    || shuffle == ApolloService.SHUFFLE_AUTO) {\r
+                MusicUtils.mService.setShuffleMode(ApolloService.SHUFFLE_NONE);\r
+                ApolloUtils.showToast(R.string.shuffle_off, mToast, getActivity());\r
+            }\r
+            setShuffleButtonImage();\r
+        } catch (RemoteException ex) {\r
+            ex.printStackTrace();\r
+        }\r
+    }\r
+\r
+    private void scanBackward(int repcnt, long delta) {\r
+        if (MusicUtils.mService == null)\r
+            return;\r
+        try {\r
+            if (repcnt == 0) {\r
+                mStartSeekPos = MusicUtils.mService.position();\r
+                mLastSeekEventTime = 0;\r
+            } else {\r
+                if (delta < 5000) {\r
+                    // seek at 10x speed for the first 5 seconds\r
+                    delta = delta * 10;\r
+                } else {\r
+                    // seek at 40x after that\r
+                    delta = 50000 + (delta - 5000) * 40;\r
+                }\r
+                long newpos = mStartSeekPos - delta;\r
+                if (newpos < 0) {\r
+                    // move to previous track\r
+                    MusicUtils.mService.prev();\r
+                    long duration = MusicUtils.mService.duration();\r
+                    mStartSeekPos += duration;\r
+                    newpos += duration;\r
+                }\r
+                if (((delta - mLastSeekEventTime) > 250) || repcnt < 0) {\r
+                    MusicUtils.mService.seek(newpos);\r
+                    mLastSeekEventTime = delta;\r
+                }\r
+                if (repcnt >= 0) {\r
+                    mPosOverride = newpos;\r
+                } else {\r
+                    mPosOverride = -1;\r
+                }\r
+                refreshNow();\r
+            }\r
+        } catch (RemoteException ex) {\r
+            ex.printStackTrace();\r
+        }\r
+    }\r
+\r
+    private void scanForward(int repcnt, long delta) {\r
+        if (MusicUtils.mService == null)\r
+            return;\r
+        try {\r
+            if (repcnt == 0) {\r
+                mStartSeekPos = MusicUtils.mService.position();\r
+                mLastSeekEventTime = 0;\r
+            } else {\r
+                if (delta < 5000) {\r
+                    // seek at 10x speed for the first 5 seconds\r
+                    delta = delta * 10;\r
+                } else {\r
+                    // seek at 40x after that\r
+                    delta = 50000 + (delta - 5000) * 40;\r
+                }\r
+                long newpos = mStartSeekPos + delta;\r
+                long duration = MusicUtils.mService.duration();\r
+                if (newpos >= duration) {\r
+                    // move to next track\r
+                    MusicUtils.mService.next();\r
+                    mStartSeekPos -= duration; // is OK to go negative\r
+                    newpos -= duration;\r
+                }\r
+                if (((delta - mLastSeekEventTime) > 250) || repcnt < 0) {\r
+                    MusicUtils.mService.seek(newpos);\r
+                    mLastSeekEventTime = delta;\r
+                }\r
+                if (repcnt >= 0) {\r
+                    mPosOverride = newpos;\r
+                } else {\r
+                    mPosOverride = -1;\r
+                }\r
+                refreshNow();\r
+            }\r
+        } catch (RemoteException ex) {\r
+            ex.printStackTrace();\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Set the repeat images\r
+     */\r
+    private void setRepeatButtonImage() {\r
+        if (MusicUtils.mService == null)\r
+            return;\r
+        try {\r
+            switch (MusicUtils.mService.getRepeatMode()) {\r
+                case ApolloService.REPEAT_ALL:\r
+                    mRepeat.setImageResource(R.drawable.apollo_holo_light_repeat_all);\r
+                    break;\r
+                case ApolloService.REPEAT_CURRENT:\r
+                    mRepeat.setImageResource(R.drawable.apollo_holo_light_repeat_one);\r
+                    break;\r
+                default:\r
+                    mRepeat.setImageResource(R.drawable.apollo_holo_light_repeat_normal);\r
+                    // Theme chooser\r
+                    ThemeUtils.setImageButton(getActivity(), mRepeat, "apollo_repeat_normal");\r
+                    break;\r
+            }\r
+        } catch (RemoteException ex) {\r
+            ex.printStackTrace();\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Set the shuffle images\r
+     */\r
+    private void setShuffleButtonImage() {\r
+        if (MusicUtils.mService == null)\r
+            return;\r
+        try {\r
+            switch (MusicUtils.mService.getShuffleMode()) {\r
+                case ApolloService.SHUFFLE_NONE:\r
+                    mShuffle.setImageResource(R.drawable.apollo_holo_light_shuffle_normal);\r
+                    // Theme chooser\r
+                    ThemeUtils.setImageButton(getActivity(), mShuffle, "apollo_shuffle_normal");\r
+                    break;\r
+                case ApolloService.SHUFFLE_AUTO:\r
+                    mShuffle.setImageResource(R.drawable.apollo_holo_light_shuffle_on);\r
+                    break;\r
+                default:\r
+                    mShuffle.setImageResource(R.drawable.apollo_holo_light_shuffle_on);\r
+                    break;\r
+            }\r
+        } catch (RemoteException ex) {\r
+            ex.printStackTrace();\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Set the play and pause image\r
+     */\r
+    private void setPauseButtonImage() {\r
+        try {\r
+            if (MusicUtils.mService != null && MusicUtils.mService.isPlaying()) {\r
+                mPlay.setImageResource(R.drawable.apollo_holo_light_pause);\r
+                // Theme chooser\r
+                ThemeUtils.setImageButton(getActivity(), mPlay, "apollo_pause");\r
+            } else {\r
+                mPlay.setImageResource(R.drawable.apollo_holo_light_play);\r
+                // Theme chooser\r
+                ThemeUtils.setImageButton(getActivity(), mPlay, "apollo_play");\r
+            }\r
+        } catch (RemoteException ex) {\r
+            ex.printStackTrace();\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @param delay\r
+     */\r
+    private void queueNextRefresh(long delay) {\r
+        if (!paused) {\r
+            Message msg = mHandler.obtainMessage(REFRESH);\r
+            mHandler.removeMessages(REFRESH);\r
+            mHandler.sendMessageDelayed(msg, delay);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * We need to refresh the time via a Handler\r
+     */\r
+    private final Handler mHandler = new Handler() {\r
+        @Override\r
+        public void handleMessage(Message msg) {\r
+            switch (msg.what) {\r
+                case REFRESH:\r
+                    long next = refreshNow();\r
+                    queueNextRefresh(next);\r
+                    break;\r
+                case UPDATEINFO:\r
+                    updateMusicInfo();\r
+                    break;\r
+                default:\r
+                    break;\r
+            }\r
+        }\r
+    };\r
+\r
+    /**\r
+     * Drag to a specfic duration\r
+     */\r
+    private final OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {\r
+        @Override\r
+        public void onStartTrackingTouch(SeekBar bar) {\r
+            mLastSeekEventTime = 0;\r
+            mFromTouch = true;\r
+        }\r
+\r
+        @Override\r
+        public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {\r
+            if (!fromuser || (MusicUtils.mService == null))\r
+                return;\r
+            long now = SystemClock.elapsedRealtime();\r
+            if ((now - mLastSeekEventTime) > 250) {\r
+                mLastSeekEventTime = now;\r
+                mPosOverride = mDuration * progress / 1000;\r
+                try {\r
+                    MusicUtils.mService.seek(mPosOverride);\r
+                } catch (RemoteException ex) {\r
+                    ex.printStackTrace();\r
+                }\r
+\r
+                if (!mFromTouch) {\r
+                    refreshNow();\r
+                    mPosOverride = -1;\r
+                }\r
+            }\r
+        }\r
+\r
+        @Override\r
+        public void onStopTrackingTouch(SeekBar bar) {\r
+            mPosOverride = -1;\r
+            mFromTouch = false;\r
+        }\r
+    };\r
+\r
+    /**\r
+     * @return current time\r
+     */\r
+    private long refreshNow() {\r
+        if (MusicUtils.mService == null)\r
+            return 500;\r
+        try {\r
+            long pos = mPosOverride < 0 ? MusicUtils.mService.position() : mPosOverride;\r
+            long remaining = 1000 - (pos % 1000);\r
+            if ((pos >= 0) && (mDuration > 0)) {\r
+                mCurrentTime.setText(MusicUtils.makeTimeString(getActivity(), pos / 1000));\r
+\r
+                if (MusicUtils.mService.isPlaying()) {\r
+                    mCurrentTime.setVisibility(View.VISIBLE);\r
+                    mCurrentTime.setTextColor(getResources().getColor(R.color.transparent_black));\r
+                    // Theme chooser\r
+                    ThemeUtils.setTextColor(getActivity(), mCurrentTime, "audio_player_text_color");\r
+                } else {\r
+                    // blink the counter\r
+                    int col = mCurrentTime.getCurrentTextColor();\r
+                    mCurrentTime.setTextColor(col == getResources().getColor(\r
+                            R.color.transparent_black) ? getResources().getColor(\r
+                            R.color.holo_blue_dark) : getResources().getColor(\r
+                            R.color.transparent_black));\r
+                    remaining = 500;\r
+                    // Theme chooser\r
+                    ThemeUtils.setTextColor(getActivity(), mCurrentTime, "audio_player_text_color");\r
+                }\r
+\r
+                mProgress.setProgress((int)(1000 * pos / mDuration));\r
+            } else {\r
+                mCurrentTime.setText("--:--");\r
+                mProgress.setProgress(1000);\r
+            }\r
+            return remaining;\r
+        } catch (RemoteException ex) {\r
+            ex.printStackTrace();\r
+        }\r
+        return 500;\r
+    }\r
+\r
+    /**\r
+     * Update what's playing\r
+     */\r
+    private void updateMusicInfo() {\r
+        if (MusicUtils.mService == null) {\r
+            return;\r
+        }\r
+\r
+        String artistName = MusicUtils.getArtistName();\r
+        String albumName = MusicUtils.getAlbumName();\r
+        String trackName = MusicUtils.getTrackName();\r
+        mTrackName.setText(trackName);\r
+        mAlbumArtistName.setText(albumName + " - " + artistName);\r
+        mDuration = MusicUtils.getDuration();\r
+        mTotalTime.setText(MusicUtils.makeTimeString(getActivity(), mDuration / 1000));\r
+\r
+        if (ApolloUtils.getImageURL(albumName, ALBUM_IMAGE, getActivity()) == null)\r
+            new LastfmGetAlbumImages(getActivity(), mAlbumArt, 1).executeOnExecutor(\r
+                    AsyncTask.THREAD_POOL_EXECUTOR, artistName, albumName);\r
+\r
+        new GetCachedImages(getActivity(), 1, mAlbumArt).executeOnExecutor(\r
+                AsyncTask.THREAD_POOL_EXECUTOR, albumName);\r
+\r
+        // Theme chooser\r
+        ThemeUtils.setTextColor(getActivity(), mTrackName, "audio_player_text_color");\r
+        ThemeUtils.setTextColor(getActivity(), mAlbumArtistName, "audio_player_text_color");\r
+        ThemeUtils.setTextColor(getActivity(), mTotalTime, "audio_player_text_color");\r
+\r
+    }\r
+\r
+    /**\r
+     * Takes you into the @TracksBrowser to view all of the tracks on the\r
+     * current album\r
+     */\r
+    private void tracksBrowser() {\r
+\r
+        String artistName = MusicUtils.getArtistName();\r
+        String albumName = MusicUtils.getAlbumName();\r
+        long id = MusicUtils.getCurrentAlbumId();\r
+\r
+        Bundle bundle = new Bundle();\r
+        bundle.putString(MIME_TYPE, Audio.Albums.CONTENT_TYPE);\r
+        bundle.putString(ARTIST_KEY, artistName);\r
+        bundle.putString(ALBUM_KEY, albumName);\r
+        bundle.putLong(BaseColumns._ID, id);\r
+\r
+        Intent intent = new Intent(Intent.ACTION_VIEW);\r
+        intent.setClass(getActivity(), TracksBrowser.class);\r
+        intent.putExtras(bundle);\r
+        getActivity().startActivity(intent);\r
+    }\r
+\r
+    /**\r
+     * Takes you into the @TracksBrowser to view all of the tracks and albums by\r
+     * the current artist\r
+     */\r
+    private void tracksBrowserArtist() {\r
+\r
+        String artistName = MusicUtils.getArtistName();\r
+        long id = MusicUtils.getCurrentArtistId();\r
+\r
+        Bundle bundle = new Bundle();\r
+        bundle.putString(MIME_TYPE, Audio.Artists.CONTENT_TYPE);\r
+        bundle.putString(ARTIST_KEY, artistName);\r
+        bundle.putLong(BaseColumns._ID, id);\r
+\r
+        ApolloUtils.setArtistId(artistName, id, ARTIST_ID, getActivity());\r
+\r
+        Intent intent = new Intent(Intent.ACTION_VIEW);\r
+        intent.setClass(getActivity(), TracksBrowser.class);\r
+        intent.putExtras(bundle);\r
+        getActivity().startActivity(intent);\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/BottomActionBarControlsFragment.java b/src/com/andrew/apollo/BottomActionBarControlsFragment.java
new file mode 100644 (file)
index 0000000..8a26d52
--- /dev/null
@@ -0,0 +1,277 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo;\r
+\r
+import android.content.BroadcastReceiver;\r
+import android.content.Context;\r
+import android.content.Intent;\r
+import android.content.IntentFilter;\r
+import android.os.Bundle;\r
+import android.os.RemoteException;\r
+import android.support.v4.app.Fragment;\r
+import android.view.LayoutInflater;\r
+import android.view.View;\r
+import android.view.View.OnClickListener;\r
+import android.view.ViewGroup;\r
+import android.widget.ImageButton;\r
+import android.widget.Toast;\r
+\r
+import com.andrew.apollo.service.ApolloService;\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+import com.andrew.apollo.utils.MusicUtils;\r
+import com.andrew.apollo.utils.ThemeUtils;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ */\r
+public class BottomActionBarControlsFragment extends Fragment {\r
+\r
+    private ImageButton mRepeat, mPrev, mPlay, mNext, mShuffle;\r
+\r
+    // Notify if repeat or shuffle changes\r
+    private Toast mToast;\r
+\r
+    @Override\r
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\r
+        View root = inflater.inflate(R.layout.bottom_action_bar_controls, container, false);\r
+\r
+        mRepeat = (ImageButton)root.findViewById(R.id.bottom_action_bar_repeat);\r
+        mRepeat.setOnClickListener(new OnClickListener() {\r
+\r
+            @Override\r
+            public void onClick(View v) {\r
+                cycleRepeat();\r
+            }\r
+        });\r
+\r
+        mPrev = (ImageButton)root.findViewById(R.id.bottom_action_bar_previous);\r
+        mPrev.setOnClickListener(new OnClickListener() {\r
+\r
+            @Override\r
+            public void onClick(View v) {\r
+                if (MusicUtils.mService == null)\r
+                    return;\r
+                try {\r
+                    if (MusicUtils.mService.position() < 2000) {\r
+                        MusicUtils.mService.prev();\r
+                    } else {\r
+                        MusicUtils.mService.seek(0);\r
+                        MusicUtils.mService.play();\r
+                    }\r
+                } catch (RemoteException ex) {\r
+                    ex.printStackTrace();\r
+                }\r
+            }\r
+        });\r
+\r
+        mPlay = (ImageButton)root.findViewById(R.id.bottom_action_bar_play);\r
+        mPlay.setOnClickListener(new OnClickListener() {\r
+\r
+            @Override\r
+            public void onClick(View v) {\r
+                doPauseResume();\r
+            }\r
+        });\r
+\r
+        mNext = (ImageButton)root.findViewById(R.id.bottom_action_bar_next);\r
+        mNext.setOnClickListener(new OnClickListener() {\r
+\r
+            @Override\r
+            public void onClick(View v) {\r
+                if (MusicUtils.mService == null)\r
+                    return;\r
+                try {\r
+                    MusicUtils.mService.next();\r
+                } catch (RemoteException ex) {\r
+                    ex.printStackTrace();\r
+                }\r
+            }\r
+        });\r
+\r
+        mShuffle = (ImageButton)root.findViewById(R.id.bottom_action_bar_shuffle);\r
+        mShuffle.setOnClickListener(new OnClickListener() {\r
+\r
+            @Override\r
+            public void onClick(View v) {\r
+                toggleShuffle();\r
+            }\r
+        });\r
+        // Theme chooser\r
+        ThemeUtils.setImageButton(getActivity(), mPrev, "apollo_previous");\r
+        ThemeUtils.setImageButton(getActivity(), mNext, "apollo_next");\r
+        return root;\r
+    }\r
+\r
+    /**\r
+     * Update everything as the meta or playstate changes\r
+     */\r
+    private final BroadcastReceiver mStatusListener = new BroadcastReceiver() {\r
+        @Override\r
+        public void onReceive(Context context, Intent intent) {\r
+            setPauseButtonImage();\r
+            setShuffleButtonImage();\r
+            setRepeatButtonImage();\r
+        }\r
+    };\r
+\r
+    @Override\r
+    public void onStart() {\r
+        super.onStart();\r
+        IntentFilter f = new IntentFilter();\r
+        f.addAction(ApolloService.PLAYSTATE_CHANGED);\r
+        getActivity().registerReceiver(mStatusListener, new IntentFilter(f));\r
+    }\r
+\r
+    @Override\r
+    public void onDestroy() {\r
+        super.onDestroy();\r
+        getActivity().unregisterReceiver(mStatusListener);\r
+    }\r
+\r
+    /**\r
+     * Cycle repeat states\r
+     */\r
+    private void cycleRepeat() {\r
+        if (MusicUtils.mService == null) {\r
+            return;\r
+        }\r
+        try {\r
+            int mode = MusicUtils.mService.getRepeatMode();\r
+            if (mode == ApolloService.REPEAT_NONE) {\r
+                MusicUtils.mService.setRepeatMode(ApolloService.REPEAT_ALL);\r
+                ApolloUtils.showToast(R.string.repeat_all, mToast, getActivity());\r
+            } else if (mode == ApolloService.REPEAT_ALL) {\r
+                MusicUtils.mService.setRepeatMode(ApolloService.REPEAT_CURRENT);\r
+                if (MusicUtils.mService.getShuffleMode() != ApolloService.SHUFFLE_NONE) {\r
+                    MusicUtils.mService.setShuffleMode(ApolloService.SHUFFLE_NONE);\r
+                    setShuffleButtonImage();\r
+                }\r
+                ApolloUtils.showToast(R.string.repeat_one, mToast, getActivity());\r
+            } else {\r
+                MusicUtils.mService.setRepeatMode(ApolloService.REPEAT_NONE);\r
+                ApolloUtils.showToast(R.string.repeat_off, mToast, getActivity());\r
+            }\r
+            setRepeatButtonImage();\r
+        } catch (RemoteException ex) {\r
+            ex.printStackTrace();\r
+        }\r
+\r
+    }\r
+\r
+    /**\r
+     * Play and pause music\r
+     */\r
+    private void doPauseResume() {\r
+        try {\r
+            if (MusicUtils.mService != null) {\r
+                if (MusicUtils.mService.isPlaying()) {\r
+                    MusicUtils.mService.pause();\r
+                } else {\r
+                    MusicUtils.mService.play();\r
+                }\r
+            }\r
+            setPauseButtonImage();\r
+        } catch (RemoteException ex) {\r
+            ex.printStackTrace();\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Set the shuffle mode\r
+     */\r
+    private void toggleShuffle() {\r
+        if (MusicUtils.mService == null) {\r
+            return;\r
+        }\r
+        try {\r
+            int shuffle = MusicUtils.mService.getShuffleMode();\r
+            if (shuffle == ApolloService.SHUFFLE_NONE) {\r
+                MusicUtils.mService.setShuffleMode(ApolloService.SHUFFLE_NORMAL);\r
+                if (MusicUtils.mService.getRepeatMode() == ApolloService.REPEAT_CURRENT) {\r
+                    MusicUtils.mService.setRepeatMode(ApolloService.REPEAT_ALL);\r
+                    setRepeatButtonImage();\r
+                }\r
+                ApolloUtils.showToast(R.string.shuffle_on, mToast, getActivity());\r
+            } else if (shuffle == ApolloService.SHUFFLE_NORMAL\r
+                    || shuffle == ApolloService.SHUFFLE_AUTO) {\r
+                MusicUtils.mService.setShuffleMode(ApolloService.SHUFFLE_NONE);\r
+                ApolloUtils.showToast(R.string.shuffle_off, mToast, getActivity());\r
+            }\r
+            setShuffleButtonImage();\r
+        } catch (RemoteException ex) {\r
+            ex.printStackTrace();\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Set the repeat images\r
+     */\r
+    private void setRepeatButtonImage() {\r
+        if (MusicUtils.mService == null)\r
+            return;\r
+        try {\r
+            switch (MusicUtils.mService.getRepeatMode()) {\r
+                case ApolloService.REPEAT_ALL:\r
+                    mRepeat.setImageResource(R.drawable.apollo_holo_light_repeat_all);\r
+                    break;\r
+                case ApolloService.REPEAT_CURRENT:\r
+                    mRepeat.setImageResource(R.drawable.apollo_holo_light_repeat_one);\r
+                    break;\r
+                default:\r
+                    mRepeat.setImageResource(R.drawable.apollo_holo_light_repeat_normal);\r
+                    // Theme chooser\r
+                    ThemeUtils.setImageButton(getActivity(), mRepeat, "apollo_repeat_normal");\r
+                    break;\r
+            }\r
+        } catch (RemoteException ex) {\r
+            ex.printStackTrace();\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Set the shuffle images\r
+     */\r
+    private void setShuffleButtonImage() {\r
+        if (MusicUtils.mService == null)\r
+            return;\r
+        try {\r
+            switch (MusicUtils.mService.getShuffleMode()) {\r
+                case ApolloService.SHUFFLE_NONE:\r
+                    mShuffle.setImageResource(R.drawable.apollo_holo_light_shuffle_normal);\r
+                    // Theme chooser\r
+                    ThemeUtils.setImageButton(getActivity(), mShuffle, "apollo_shuffle_normal");\r
+                    break;\r
+                case ApolloService.SHUFFLE_AUTO:\r
+                    mShuffle.setImageResource(R.drawable.apollo_holo_light_shuffle_on);\r
+                    break;\r
+                default:\r
+                    mShuffle.setImageResource(R.drawable.apollo_holo_light_shuffle_on);\r
+                    break;\r
+            }\r
+        } catch (RemoteException ex) {\r
+            ex.printStackTrace();\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Set the play and pause image\r
+     */\r
+    private void setPauseButtonImage() {\r
+        try {\r
+            if (MusicUtils.mService != null && MusicUtils.mService.isPlaying()) {\r
+                mPlay.setImageResource(R.drawable.apollo_holo_light_pause);\r
+                // Theme chooser\r
+                ThemeUtils.setImageButton(getActivity(), mPlay, "apollo_pause");\r
+            } else {\r
+                mPlay.setImageResource(R.drawable.apollo_holo_light_play);\r
+                // Theme chooser\r
+                ThemeUtils.setImageButton(getActivity(), mPlay, "apollo_play");\r
+            }\r
+        } catch (RemoteException ex) {\r
+            ex.printStackTrace();\r
+        }\r
+    }\r
+\r
+}\r
diff --git a/src/com/andrew/apollo/BottomActionBarFragment.java b/src/com/andrew/apollo/BottomActionBarFragment.java
new file mode 100644 (file)
index 0000000..a1f7136
--- /dev/null
@@ -0,0 +1,60 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo;\r
+\r
+import android.content.BroadcastReceiver;\r
+import android.content.Context;\r
+import android.content.Intent;\r
+import android.content.IntentFilter;\r
+import android.os.Bundle;\r
+import android.support.v4.app.Fragment;\r
+import android.view.LayoutInflater;\r
+import android.view.View;\r
+import android.view.ViewGroup;\r
+\r
+import com.andrew.apollo.service.ApolloService;\r
+import com.andrew.apollo.ui.widgets.BottomActionBar;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ */\r
+public class BottomActionBarFragment extends Fragment {\r
+\r
+    private BottomActionBar mBottomActionBar;\r
+\r
+    @Override\r
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\r
+        View root = inflater.inflate(R.layout.bottom_action_bar, container, false);\r
+        mBottomActionBar = new BottomActionBar(getActivity());\r
+        return root;\r
+    }\r
+\r
+    /**\r
+     * Update the list as needed\r
+     */\r
+    private final BroadcastReceiver mMediaStatusReceiver = new BroadcastReceiver() {\r
+\r
+        @Override\r
+        public void onReceive(Context context, Intent intent) {\r
+            if (mBottomActionBar != null) {\r
+                mBottomActionBar.updateBottomActionBar(getActivity());\r
+            }\r
+        }\r
+    };\r
+\r
+    @Override\r
+    public void onStart() {\r
+        super.onStart();\r
+        IntentFilter filter = new IntentFilter();\r
+        filter.addAction(ApolloService.META_CHANGED);\r
+        getActivity().registerReceiver(mMediaStatusReceiver, filter);\r
+    }\r
+\r
+    @Override\r
+    public void onStop() {\r
+        getActivity().unregisterReceiver(mMediaStatusReceiver);\r
+        super.onStop();\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/Constants.java b/src/com/andrew/apollo/Constants.java
new file mode 100644 (file)
index 0000000..9514080
--- /dev/null
@@ -0,0 +1,74 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ */\r
+public interface Constants {\r
+\r
+    // Last.fm API\r
+    public String LASTFM_API_KEY = "0bec3f7ec1f914d7c960c12a916c8fb3";\r
+\r
+    // Tab titles\r
+    public String[] mTitles = {\r
+            "RECENT", "ARTISTS", "ALBUMS", "SONGS", "PLAYLISTS", "GENRES"\r
+    };\r
+\r
+    // SharedPreferences\r
+    public String APOLLO = "Apollo", APOLLO_PREFERENCES = "apollopreferences",\r
+            ARTIST_IMAGE = "artistimage", ARTIST_IMAGE_ORIGINAL = "artistimageoriginal",\r
+            ALBUM_IMAGE = "albumimage", ARTIST_KEY = "artist", ALBUM_KEY = "album",\r
+            GENRE_KEY = "genres", ARTIST_ID = "artistid", NUMWEEKS = "numweeks",\r
+            PLAYLIST_NAME_FAVORITES = "Favorites", PLAYLIST_NAME = "playlist",\r
+            THEME_PACKAGE_NAME = "themePackageName", THEME_DESCRIPTION = "themeDescription",\r
+            THEME_PREVIEW = "themepreview", THEME_TITLE = "themeTitle";\r
+\r
+    // Bundle & Intent type\r
+    public String MIME_TYPE = "mimetype", INTENT_ACTION = "action", DATA_SCHEME = "file";\r
+\r
+    // Storage Volume\r
+    public String EXTERNAL = "external";\r
+\r
+    // Playlists\r
+    public final static long PLAYLIST_UNKNOWN = -1, PLAYLIST_ALL_SONGS = -2, PLAYLIST_QUEUE = -3,\r
+            PLAYLIST_NEW = -4, PLAYLIST_FAVORITES = -5, PLAYLIST_RECENTLY_ADDED = -6;\r
+\r
+    // Genres\r
+    public final static String[] GENRES_DB = {\r
+            "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", "Hip-Hop",\r
+            "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", "Rap", "Reggae", "Rock",\r
+            "Techno", "Industrial", "Alternative", "Ska", "Death Metal", "Pranks", "Soundtrack",\r
+            "Euro-Techno", "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance",\r
+            "Classical", "Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise",\r
+            "AlternRock", "Bass", "Soul", "Punk", "Space", "Meditative", "Instrumental Pop",\r
+            "Instrumental Rock", "Ethnic", "Gothic", "Darkwave", "Techno-Industrial", "Electronic",\r
+            "Pop-Folk", "Eurodance", "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta",\r
+            "Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American", "Cabaret",\r
+            "New Wave", "Psychedelic", "Rave", "Showtunes", "Trailer", "Lo-Fi", "Tribal",\r
+            "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock",\r
+            "Folk", "Folk-Rock", "National Folk", "Swing", "Fast Fusion", "Bebob", "Latin",\r
+            "Revival", "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", "Progressive Rock",\r
+            "Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band", "Chorus",\r
+            "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson", "Opera", "Chamber Music",\r
+            "Sonata", "Symphony", "Booty Bass", "Primus", "Porn Groove", "Satire", "Slow Jam",\r
+            "Club", "Tango", "Samba", "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul",\r
+            "Freestyle", "Duet", "Punk Rock", "Drum Solo", "A capella", "Euro-House", "Dance Hall",\r
+            "Goa", "Drum & Bass", "Club-House", "Hardcore", "Terror", "Indie", "Britpop",\r
+            "Negerpunk", "Polsk Punk", "Beat", "Christian Gangsta Rap", "Heavy Metal",\r
+            "Black Metal", "Crossover", "Contemporary Christian", "Christian Rock ", "Merengue",\r
+            "Salsa", "Thrash Metal", "Anime", "JPop", "Synthpop"\r
+    };\r
+\r
+    // Theme item type\r
+    public final static int THEME_ITEM_BACKGROUND = 0, THEME_ITEM_FOREGROUND = 1;\r
+\r
+    public final static String INTENT_ADD_TO_PLAYLIST = "com.andrew.apollo.ADD_TO_PLAYLIST",\r
+            INTENT_PLAYLIST_LIST = "playlistlist",\r
+            INTENT_CREATE_PLAYLIST = "com.andrew.apollo.CREATE_PLAYLIST",\r
+            INTENT_RENAME_PLAYLIST = "com.andrew.apollo.RENAME_PLAYLIST",\r
+            INTENT_KEY_RENAME = "rename", INTENT_KEY_DEFAULT_NAME = "default_name";\r
+\r
+}\r
diff --git a/src/com/andrew/apollo/IApolloService.aidl b/src/com/andrew/apollo/IApolloService.aidl
new file mode 100644 (file)
index 0000000..b44edef
--- /dev/null
@@ -0,0 +1,42 @@
+package com.andrew.apollo;\r
+\r
+import android.graphics.Bitmap;\r
+\r
+interface IApolloService\r
+{\r
+    void openFile(String path);\r
+    void open(in long [] list, int position);\r
+    int getQueuePosition();\r
+    boolean isPlaying();\r
+    void stop();\r
+    void pause();\r
+    void play();\r
+    void prev();\r
+    void next();\r
+    long duration();\r
+    long position();\r
+    long seek(long pos);\r
+    String getTrackName();\r
+    String getAlbumName();\r
+    long getAlbumId();\r
+    String getArtistName();\r
+    long getArtistId();\r
+    void enqueue(in long [] list, int action);\r
+    long [] getQueue();\r
+    void setQueuePosition(int index);\r
+    String getPath();\r
+    long getAudioId();\r
+    void setShuffleMode(int shufflemode);\r
+    int getShuffleMode();\r
+    int removeTracks(int first, int last);\r
+    int removeTrack(long id);\r
+    void setRepeatMode(int repeatmode);\r
+    int getRepeatMode();\r
+    int getMediaMountedCount();\r
+    int getAudioSessionId();\r
+       void addToFavorites(long id);\r
+       void removeFromFavorites(long id);\r
+       boolean isFavorite(long id);\r
+    void toggleFavorite();\r
+}\r
+\r
diff --git a/src/com/andrew/apollo/NowPlayingCursor.java b/src/com/andrew/apollo/NowPlayingCursor.java
new file mode 100644 (file)
index 0000000..d62190e
--- /dev/null
@@ -0,0 +1,194 @@
+\r
+package com.andrew.apollo;\r
+\r
+import java.util.Arrays;\r
+\r
+import android.content.Context;\r
+import android.database.AbstractCursor;\r
+import android.database.Cursor;\r
+import android.os.RemoteException;\r
+import android.provider.BaseColumns;\r
+import android.provider.MediaStore.Audio;\r
+\r
+import com.andrew.apollo.utils.MusicUtils;\r
+\r
+public class NowPlayingCursor extends AbstractCursor {\r
+\r
+    public NowPlayingCursor(IApolloService service, String[] projection, Context c) {\r
+        mProjection = projection;\r
+        mService = service;\r
+        makeNowPlayingCursor();\r
+        context = c;\r
+    }\r
+\r
+    private void makeNowPlayingCursor() {\r
+        mCurrentPlaylistCursor = null;\r
+        try {\r
+            mNowPlaying = mService.getQueue();\r
+        } catch (RemoteException ex) {\r
+            mNowPlaying = new long[0];\r
+        }\r
+        mSize = mNowPlaying.length;\r
+        if (mSize == 0) {\r
+            return;\r
+        }\r
+\r
+        StringBuilder where = new StringBuilder();\r
+        where.append(BaseColumns._ID + " IN (");\r
+        for (int i = 0; i < mSize; i++) {\r
+            where.append(mNowPlaying[i]);\r
+            if (i < mSize - 1) {\r
+                where.append(",");\r
+            }\r
+        }\r
+        where.append(")");\r
+\r
+        mCurrentPlaylistCursor = MusicUtils.query(context, Audio.Media.EXTERNAL_CONTENT_URI,\r
+                mProjection, where.toString(), null, BaseColumns._ID);\r
+\r
+        if (mCurrentPlaylistCursor == null) {\r
+            mSize = 0;\r
+            return;\r
+        }\r
+\r
+        int size = mCurrentPlaylistCursor.getCount();\r
+        mCursorIdxs = new long[size];\r
+        mCurrentPlaylistCursor.moveToFirst();\r
+        int colidx = mCurrentPlaylistCursor.getColumnIndexOrThrow(BaseColumns._ID);\r
+        for (int i = 0; i < size; i++) {\r
+            mCursorIdxs[i] = mCurrentPlaylistCursor.getLong(colidx);\r
+            mCurrentPlaylistCursor.moveToNext();\r
+        }\r
+        mCurrentPlaylistCursor.moveToFirst();\r
+        try {\r
+            int removed = 0;\r
+            for (int i = mNowPlaying.length - 1; i >= 0; i--) {\r
+                long trackid = mNowPlaying[i];\r
+                int crsridx = Arrays.binarySearch(mCursorIdxs, trackid);\r
+                if (crsridx < 0) {\r
+                    removed += mService.removeTrack(trackid);\r
+                }\r
+            }\r
+            if (removed > 0) {\r
+                mNowPlaying = mService.getQueue();\r
+                mSize = mNowPlaying.length;\r
+                if (mSize == 0) {\r
+                    mCursorIdxs = null;\r
+                    return;\r
+                }\r
+            }\r
+        } catch (RemoteException ex) {\r
+            mNowPlaying = new long[0];\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public int getCount() {\r
+        return mSize;\r
+    }\r
+\r
+    @Override\r
+    public boolean onMove(int oldPosition, int newPosition) {\r
+        if (oldPosition == newPosition)\r
+            return true;\r
+\r
+        if (mNowPlaying == null || mCursorIdxs == null || newPosition >= mNowPlaying.length) {\r
+            return false;\r
+        }\r
+\r
+        // The cursor doesn't have any duplicates in it, and is not ordered\r
+        // in queue-order, so we need to figure out where in the cursor we\r
+        // should be.\r
+\r
+        long newid = mNowPlaying[newPosition];\r
+        int crsridx = Arrays.binarySearch(mCursorIdxs, newid);\r
+        mCurrentPlaylistCursor.moveToPosition(crsridx);\r
+        return true;\r
+    }\r
+\r
+    @Override\r
+    public String getString(int column) {\r
+        try {\r
+            return mCurrentPlaylistCursor.getString(column);\r
+        } catch (Exception ex) {\r
+            onChange(true);\r
+            return "";\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public short getShort(int column) {\r
+        return mCurrentPlaylistCursor.getShort(column);\r
+    }\r
+\r
+    @Override\r
+    public int getInt(int column) {\r
+        try {\r
+            return mCurrentPlaylistCursor.getInt(column);\r
+        } catch (Exception ex) {\r
+            onChange(true);\r
+            return 0;\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public long getLong(int column) {\r
+        try {\r
+            return mCurrentPlaylistCursor.getLong(column);\r
+        } catch (Exception ex) {\r
+            onChange(true);\r
+            return 0;\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public float getFloat(int column) {\r
+        return mCurrentPlaylistCursor.getFloat(column);\r
+    }\r
+\r
+    @Override\r
+    public double getDouble(int column) {\r
+        return mCurrentPlaylistCursor.getDouble(column);\r
+    }\r
+\r
+    @Override\r
+    public int getType(int column) {\r
+        return mCurrentPlaylistCursor.getType(column);\r
+    }\r
+\r
+    @Override\r
+    public boolean isNull(int column) {\r
+        return mCurrentPlaylistCursor.isNull(column);\r
+    }\r
+\r
+    @Override\r
+    public String[] getColumnNames() {\r
+        return mProjection;\r
+    }\r
+\r
+    @Override\r
+    public void deactivate() {\r
+        if (mCurrentPlaylistCursor != null)\r
+            mCurrentPlaylistCursor.deactivate();\r
+    }\r
+\r
+    @Override\r
+    public boolean requery() {\r
+        makeNowPlayingCursor();\r
+        return true;\r
+    }\r
+\r
+    private final String[] mProjection;\r
+\r
+    private Cursor mCurrentPlaylistCursor;\r
+\r
+    private int mSize;\r
+\r
+    private long[] mNowPlaying;\r
+\r
+    private long[] mCursorIdxs;\r
+\r
+    private final Context context;\r
+\r
+    private final IApolloService mService;\r
+}\r
diff --git a/src/com/andrew/apollo/activities/AudioPlayerHolder.java b/src/com/andrew/apollo/activities/AudioPlayerHolder.java
new file mode 100644 (file)
index 0000000..a22c4c1
--- /dev/null
@@ -0,0 +1,291 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.activities;\r
+\r
+import android.content.BroadcastReceiver;\r
+import android.content.ComponentName;\r
+import android.content.Context;\r
+import android.content.Intent;\r
+import android.content.IntentFilter;\r
+import android.content.ServiceConnection;\r
+import android.content.pm.ActivityInfo;\r
+import android.content.res.Resources;\r
+import android.media.AudioManager;\r
+import android.media.audiofx.AudioEffect;\r
+import android.os.Bundle;\r
+import android.os.IBinder;\r
+import android.os.RemoteException;\r
+import android.provider.BaseColumns;\r
+import android.provider.MediaStore.Audio;\r
+import android.support.v4.app.FragmentActivity;\r
+import android.support.v4.view.ViewPager;\r
+import android.view.Menu;\r
+import android.view.MenuInflater;\r
+import android.view.MenuItem;\r
+import android.widget.FrameLayout;\r
+import android.widget.ImageView;\r
+import android.widget.TextView;\r
+\r
+import com.andrew.apollo.AudioPlayerFragment;\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.IApolloService;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.adapters.PagerAdapter;\r
+import com.andrew.apollo.list.fragments.TracksFragment;\r
+import com.andrew.apollo.preferences.SettingsHolder;\r
+import com.andrew.apollo.service.ApolloService;\r
+import com.andrew.apollo.service.ServiceToken;\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+import com.andrew.apollo.utils.MusicUtils;\r
+import com.andrew.apollo.utils.ThemeUtils;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ * @Note This is the "holder" for the @TracksFragment(queue) and @AudioPlayerFragment\r
+ */\r
+public class AudioPlayerHolder extends FragmentActivity implements ServiceConnection, Constants {\r
+\r
+    private ServiceToken mToken;\r
+\r
+    // Options\r
+    private static final int FAVORITE = 0;\r
+\r
+    private static final int SEARCH = 1;\r
+\r
+    private static final int EFFECTS_PANEL = 0;\r
+\r
+    @Override\r
+    protected void onCreate(Bundle icicle) {\r
+        // For the theme chooser and overflow MenuItem\r
+        if (ThemeUtils.overflowLight(this)) {\r
+            setTheme(R.style.Apollo_Holo);\r
+        } else {\r
+            setTheme(R.style.Apollo_Holo_Light);\r
+        }\r
+        // Landscape mode on phone isn't ready\r
+        if (!ApolloUtils.isTablet(this))\r
+            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);\r
+\r
+        // Control Media volume\r
+        setVolumeControlStream(AudioManager.STREAM_MUSIC);\r
+\r
+        // Layout\r
+        setContentView(R.layout.audio_player_browser);\r
+\r
+        // Set up the colorstrip\r
+        initColorstrip();\r
+\r
+        // Set up the ActionBar\r
+        initActionBar();\r
+\r
+        // Important!\r
+        initPager();\r
+        super.onCreate(icicle);\r
+    }\r
+\r
+    @Override\r
+    public void onServiceConnected(ComponentName name, IBinder obj) {\r
+        MusicUtils.mService = IApolloService.Stub.asInterface(obj);\r
+    }\r
+\r
+    @Override\r
+    public void onServiceDisconnected(ComponentName name) {\r
+        MusicUtils.mService = null;\r
+    }\r
+\r
+    /**\r
+     * Update the MenuItems in the ActionBar\r
+     */\r
+    private final BroadcastReceiver mMediaStatusReceiver = new BroadcastReceiver() {\r
+\r
+        @Override\r
+        public void onReceive(Context context, Intent intent) {\r
+            invalidateOptionsMenu();\r
+        }\r
+\r
+    };\r
+\r
+    @Override\r
+    protected void onStart() {\r
+        // Bind to Service\r
+        mToken = MusicUtils.bindToService(this, this);\r
+\r
+        IntentFilter filter = new IntentFilter();\r
+        filter.addAction(ApolloService.META_CHANGED);\r
+        filter.addAction(ApolloService.PLAYSTATE_CHANGED);\r
+\r
+        registerReceiver(mMediaStatusReceiver, filter);\r
+        super.onStart();\r
+    }\r
+\r
+    @Override\r
+    protected void onStop() {\r
+        // Unbind\r
+        if (MusicUtils.mService != null) {\r
+            MusicUtils.unbindFromService(mToken);\r
+            mToken = null;\r
+        }\r
+\r
+        unregisterReceiver(mMediaStatusReceiver);\r
+        super.onStop();\r
+    }\r
+\r
+    @Override\r
+    public boolean onCreateOptionsMenu(Menu menu) {\r
+        menu.add(0, FAVORITE, 0, R.string.cd_favorite).setShowAsAction(\r
+                MenuItem.SHOW_AS_ACTION_IF_ROOM);\r
+        menu.add(0, SEARCH, 0, R.string.cd_search).setIcon(R.drawable.apollo_holo_light_search)\r
+                .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);\r
+\r
+        MenuInflater inflater = getMenuInflater();\r
+        inflater.inflate(R.menu.overflow_now_playing, menu);\r
+        return super.onCreateOptionsMenu(menu);\r
+    }\r
+\r
+    @Override\r
+    public boolean onPrepareOptionsMenu(Menu menu) {\r
+        MenuItem favorite = menu.findItem(FAVORITE);\r
+        MenuItem search = menu.findItem(SEARCH);\r
+        if (MusicUtils.mService != null && MusicUtils.getCurrentAudioId() != -1) {\r
+            if (MusicUtils.isFavorite(this, MusicUtils.getCurrentAudioId())) {\r
+                favorite.setIcon(R.drawable.apollo_holo_light_favorite_selected);\r
+            } else {\r
+                favorite.setIcon(R.drawable.apollo_holo_light_favorite_normal);\r
+                // Theme chooser\r
+                ThemeUtils.setActionBarItem(this, favorite, "apollo_favorite_normal");\r
+            }\r
+        }\r
+        // Theme chooser\r
+        ThemeUtils.setActionBarItem(this, search, "apollo_search");\r
+        return super.onPrepareOptionsMenu(menu);\r
+    }\r
+\r
+    @Override\r
+    public boolean onOptionsItemSelected(MenuItem item) {\r
+\r
+        switch (item.getItemId()) {\r
+            case android.R.id.home: {\r
+                Intent intent = new Intent();\r
+                intent.setClass(this, MusicLibrary.class);\r
+                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);\r
+                startActivity(intent);\r
+                finish();\r
+                break;\r
+            }\r
+            case FAVORITE: {\r
+                MusicUtils.toggleFavorite();\r
+                invalidateOptionsMenu();\r
+                break;\r
+            }\r
+            case SEARCH: {\r
+                onSearchRequested();\r
+                break;\r
+            }\r
+            case R.id.add_to_playlist: {\r
+                Intent intent = new Intent(INTENT_ADD_TO_PLAYLIST);\r
+                long[] list = new long[1];\r
+                list[0] = MusicUtils.getCurrentAudioId();\r
+                intent.putExtra(INTENT_PLAYLIST_LIST, list);\r
+                startActivity(intent);\r
+                break;\r
+            }\r
+            case R.id.eq: {\r
+                Intent i = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);\r
+                i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, MusicUtils.getCurrentAudioId());\r
+                startActivityForResult(i, EFFECTS_PANEL);\r
+                break;\r
+            }\r
+            case R.id.play_store: {\r
+                ApolloUtils.shopFor(this, MusicUtils.getArtistName());\r
+                break;\r
+            }\r
+            case R.id.share: {\r
+                shareCurrentTrack();\r
+                break;\r
+            }\r
+            case R.id.settings: {\r
+                startActivity(new Intent(this, SettingsHolder.class));\r
+                break;\r
+            }\r
+            default:\r
+                break;\r
+        }\r
+        return super.onOptionsItemSelected(item);\r
+    }\r
+\r
+    private void initActionBar() {\r
+        ApolloUtils.showUpTitleOnly(getActionBar());\r
+\r
+        // The ActionBar Title and UP ids are hidden.\r
+        int titleId = Resources.getSystem().getIdentifier("action_bar_title", "id", "android");\r
+        int upId = Resources.getSystem().getIdentifier("up", "id", "android");\r
+\r
+        TextView actionBarTitle = (TextView)findViewById(titleId);\r
+        ImageView actionBarUp = (ImageView)findViewById(upId);\r
+\r
+        // Theme chooser\r
+        ThemeUtils.setActionBarBackground(this, getActionBar(), "action_bar_background");\r
+        ThemeUtils.setTextColor(this, actionBarTitle, "action_bar_title_color");\r
+        ThemeUtils.initThemeChooser(this, actionBarUp, "action_bar_up", THEME_ITEM_BACKGROUND);\r
+    }\r
+\r
+    /**\r
+     * @return Share intent\r
+     * @throws RemoteException\r
+     */\r
+    private String shareCurrentTrack() {\r
+        if (MusicUtils.getTrackName() == null || MusicUtils.getArtistName() == null) {\r
+\r
+        }\r
+\r
+        Intent shareIntent = new Intent();\r
+        String currentTrackMessage = getResources().getString(R.string.now_listening_to) + " "\r
+                + MusicUtils.getTrackName() + " " + getResources().getString(R.string.by) + " "\r
+                + MusicUtils.getArtistName();\r
+\r
+        shareIntent.setAction(Intent.ACTION_SEND);\r
+        shareIntent.setType("text/plain");\r
+        shareIntent.putExtra(Intent.EXTRA_TEXT, currentTrackMessage);\r
+\r
+        startActivity(Intent.createChooser(shareIntent,\r
+                getResources().getString(R.string.share_track_using)));\r
+        return currentTrackMessage;\r
+    }\r
+\r
+    /**\r
+     * Initiate ViewPager and PagerAdapter\r
+     */\r
+    public void initPager() {\r
+        // Initiate PagerAdapter\r
+        PagerAdapter mPagerAdapter = new PagerAdapter(getSupportFragmentManager());\r
+        Bundle bundle = new Bundle();\r
+        bundle.putString(MIME_TYPE, Audio.Playlists.CONTENT_TYPE);\r
+        bundle.putLong(BaseColumns._ID, PLAYLIST_QUEUE);\r
+        mPagerAdapter.addFragment(new TracksFragment(bundle));\r
+        // Artists\r
+        mPagerAdapter.addFragment(new AudioPlayerFragment());\r
+\r
+        // Initiate ViewPager\r
+        ViewPager mViewPager = (ViewPager)findViewById(R.id.viewPager);\r
+        mViewPager.setPageMargin(getResources().getInteger(R.integer.viewpager_margin_width));\r
+        mViewPager.setPageMarginDrawable(R.drawable.viewpager_margin);\r
+        mViewPager.setOffscreenPageLimit(mPagerAdapter.getCount());\r
+        mViewPager.setAdapter(mPagerAdapter);\r
+        mViewPager.setCurrentItem(1);\r
+\r
+        // Theme chooser\r
+        ThemeUtils.initThemeChooser(this, mViewPager, "viewpager", THEME_ITEM_BACKGROUND);\r
+        ThemeUtils.setMarginDrawable(this, mViewPager, "viewpager_margin");\r
+    }\r
+\r
+    /**\r
+     * For the theme chooser\r
+     */\r
+    private void initColorstrip() {\r
+        FrameLayout mColorstrip = (FrameLayout)findViewById(R.id.colorstrip);\r
+        mColorstrip.setBackgroundColor(getResources().getColor(R.color.holo_blue_dark));\r
+        ThemeUtils.setBackgroundColor(this, mColorstrip, "colorstrip");\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/activities/MusicLibrary.java b/src/com/andrew/apollo/activities/MusicLibrary.java
new file mode 100644 (file)
index 0000000..377799e
--- /dev/null
@@ -0,0 +1,193 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.activities;\r
+\r
+import android.content.BroadcastReceiver;\r
+import android.content.ComponentName;\r
+import android.content.Context;\r
+import android.content.Intent;\r
+import android.content.IntentFilter;\r
+import android.content.ServiceConnection;\r
+import android.content.pm.ActivityInfo;\r
+import android.media.AudioManager;\r
+import android.os.Bundle;\r
+import android.os.IBinder;\r
+import android.provider.BaseColumns;\r
+import android.provider.MediaStore.Audio;\r
+import android.support.v4.app.FragmentActivity;\r
+import android.support.v4.view.ViewPager;\r
+import android.view.Window;\r
+\r
+import com.andrew.apollo.BottomActionBarControlsFragment;\r
+import com.andrew.apollo.BottomActionBarFragment;\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.IApolloService;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.adapters.PagerAdapter;\r
+import com.andrew.apollo.adapters.ScrollingTabsAdapter;\r
+import com.andrew.apollo.grid.fragments.AlbumsFragment;\r
+import com.andrew.apollo.grid.fragments.ArtistsFragment;\r
+import com.andrew.apollo.list.fragments.GenresFragment;\r
+import com.andrew.apollo.list.fragments.PlaylistsFragment;\r
+import com.andrew.apollo.list.fragments.RecentlyAddedFragment;\r
+import com.andrew.apollo.list.fragments.TracksFragment;\r
+import com.andrew.apollo.service.ApolloService;\r
+import com.andrew.apollo.service.ServiceToken;\r
+import com.andrew.apollo.ui.widgets.ScrollableTabView;\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+import com.andrew.apollo.utils.MusicUtils;\r
+import com.andrew.apollo.utils.ThemeUtils;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ * @Note This is the "holder" for all of the tabs\r
+ */\r
+public class MusicLibrary extends FragmentActivity implements ServiceConnection, Constants {\r
+\r
+    private ServiceToken mToken;\r
+\r
+    @Override\r
+    protected void onCreate(Bundle icicle) {\r
+        // Landscape mode on phone isn't ready\r
+        if (!ApolloUtils.isTablet(this))\r
+            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);\r
+\r
+        // Scan for music\r
+        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);\r
+\r
+        // Control Media volume\r
+        setVolumeControlStream(AudioManager.STREAM_MUSIC);\r
+\r
+        // Layout\r
+        setContentView(R.layout.library_browser);\r
+\r
+        // Hide the ActionBar\r
+        getActionBar().hide();\r
+\r
+        // Important!\r
+        initPager();\r
+\r
+        // Update the BottomActionBar\r
+        initBottomActionBar();\r
+        super.onCreate(icicle);\r
+    }\r
+\r
+    @Override\r
+    public void onServiceConnected(ComponentName name, IBinder obj) {\r
+        MusicUtils.mService = IApolloService.Stub.asInterface(obj);\r
+    }\r
+\r
+    @Override\r
+    public void onServiceDisconnected(ComponentName name) {\r
+        MusicUtils.mService = null;\r
+    }\r
+\r
+    @Override\r
+    protected void onStart() {\r
+        // Bind to Service\r
+        mToken = MusicUtils.bindToService(this, this);\r
+\r
+        IntentFilter filter = new IntentFilter();\r
+        filter.addAction(ApolloService.META_CHANGED);\r
+\r
+        IntentFilter scannerFilter = new IntentFilter();\r
+        scannerFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);\r
+        scannerFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);\r
+        scannerFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);\r
+        scannerFilter.addDataScheme(DATA_SCHEME);\r
+\r
+        registerReceiver(mScanListener, scannerFilter);\r
+        super.onStart();\r
+    }\r
+\r
+    @Override\r
+    protected void onStop() {\r
+        // Unbind\r
+        if (MusicUtils.mService != null)\r
+            MusicUtils.unbindFromService(mToken);\r
+\r
+        unregisterReceiver(mScanListener);\r
+        super.onStop();\r
+    }\r
+\r
+    /**\r
+     * Initiate ViewPager and PagerAdapter\r
+     */\r
+    public void initPager() {\r
+        // Initiate PagerAdapter\r
+        PagerAdapter mPagerAdapter = new PagerAdapter(getSupportFragmentManager());\r
+\r
+        Bundle bundle = new Bundle();\r
+        bundle.putString(MIME_TYPE, Audio.Playlists.CONTENT_TYPE);\r
+        bundle.putLong(BaseColumns._ID, PLAYLIST_RECENTLY_ADDED);\r
+        // Recently added tracks\r
+        mPagerAdapter.addFragment(new RecentlyAddedFragment(bundle));\r
+        // Artists\r
+        mPagerAdapter.addFragment(new ArtistsFragment());\r
+        // Albums\r
+        mPagerAdapter.addFragment(new AlbumsFragment());\r
+        // // Tracks\r
+        mPagerAdapter.addFragment(new TracksFragment());\r
+        // // Playlists\r
+        mPagerAdapter.addFragment(new PlaylistsFragment());\r
+        // // Genres\r
+        mPagerAdapter.addFragment(new GenresFragment());\r
+\r
+        // Initiate ViewPager\r
+        ViewPager mViewPager = (ViewPager)findViewById(R.id.viewPager);\r
+        mViewPager.setPageMargin(getResources().getInteger(R.integer.viewpager_margin_width));\r
+        mViewPager.setPageMarginDrawable(R.drawable.viewpager_margin);\r
+        mViewPager.setOffscreenPageLimit(mPagerAdapter.getCount());\r
+        mViewPager.setAdapter(mPagerAdapter);\r
+        mViewPager.setCurrentItem(1);\r
+\r
+        // Tabs\r
+        initScrollableTabs(mViewPager);\r
+\r
+        // Theme chooser\r
+        ThemeUtils.initThemeChooser(this, mViewPager, "viewpager", THEME_ITEM_BACKGROUND);\r
+        ThemeUtils.setMarginDrawable(this, mViewPager, "viewpager_margin");\r
+    }\r
+\r
+    /**\r
+     * Initiate the tabs\r
+     */\r
+    public void initScrollableTabs(ViewPager mViewPager) {\r
+        ScrollableTabView mScrollingTabs = (ScrollableTabView)findViewById(R.id.scrollingTabs);\r
+        ScrollingTabsAdapter mScrollingTabsAdapter = new ScrollingTabsAdapter(this);\r
+        mScrollingTabs.setAdapter(mScrollingTabsAdapter);\r
+        mScrollingTabs.setViewPager(mViewPager);\r
+\r
+        // Theme chooser\r
+        ThemeUtils.initThemeChooser(this, mScrollingTabs, "scrollable_tab_background",\r
+                THEME_ITEM_BACKGROUND);\r
+    }\r
+\r
+    /**\r
+     * Initiate the BottomActionBar\r
+     */\r
+    private void initBottomActionBar() {\r
+        PagerAdapter pagerAdatper = new PagerAdapter(getSupportFragmentManager());\r
+        pagerAdatper.addFragment(new BottomActionBarFragment());\r
+        pagerAdatper.addFragment(new BottomActionBarControlsFragment());\r
+        ViewPager viewPager = (ViewPager)findViewById(R.id.bottomActionBarPager);\r
+        viewPager.setAdapter(pagerAdatper);\r
+    }\r
+\r
+    /*\r
+     * This listener gets called when the media scanner starts up or finishes,\r
+     * and when the sd card is unmounted.\r
+     */\r
+    private final BroadcastReceiver mScanListener = new BroadcastReceiver() {\r
+        @Override\r
+        public void onReceive(Context context, Intent intent) {\r
+            String action = intent.getAction();\r
+            if (Intent.ACTION_MEDIA_SCANNER_STARTED.equals(action)\r
+                    || Intent.ACTION_MEDIA_SCANNER_FINISHED.equals(action)) {\r
+                MusicUtils.setSpinnerState(MusicLibrary.this);\r
+            }\r
+        }\r
+    };\r
+}\r
diff --git a/src/com/andrew/apollo/activities/QueryBrowserActivity.java b/src/com/andrew/apollo/activities/QueryBrowserActivity.java
new file mode 100644 (file)
index 0000000..f1d9f25
--- /dev/null
@@ -0,0 +1,487 @@
+/*
+ * 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.andrew.apollo.activities;
+
+import android.app.ListActivity;
+import android.app.SearchManager;
+import android.content.AsyncQueryHandler;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.graphics.Color;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.provider.BaseColumns;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Audio;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+import android.widget.TextView;
+
+import com.andrew.apollo.Constants;
+import com.andrew.apollo.R;
+import com.andrew.apollo.service.ServiceToken;
+import com.andrew.apollo.utils.ApolloUtils;
+import com.andrew.apollo.utils.MusicUtils;
+
+public class QueryBrowserActivity extends ListActivity implements Constants, ServiceConnection {
+    private QueryListAdapter mAdapter;
+
+    private boolean mAdapterSent;
+
+    private String mFilterString = "";
+
+    private ServiceToken mToken;
+
+    public QueryBrowserActivity() {
+    }
+
+    /** Called when the activity is first created. */
+    @SuppressWarnings("deprecation")
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setVolumeControlStream(AudioManager.STREAM_MUSIC);
+        mAdapter = (QueryListAdapter)getLastNonConfigurationInstance();
+        mToken = MusicUtils.bindToService(this, this);
+        // defer the real work until we're bound to the service
+    }
+
+    @Override
+    public void onServiceConnected(ComponentName name, IBinder service) {
+        IntentFilter f = new IntentFilter();
+        f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
+        f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
+        f.addDataScheme(DATA_SCHEME);
+        registerReceiver(mScanListener, f);
+
+        Intent intent = getIntent();
+        String action = intent != null ? intent.getAction() : null;
+
+        if (Intent.ACTION_VIEW.equals(action)) {
+            // this is something we got from the search bar
+            Uri uri = intent.getData();
+            String path = uri.toString();
+            if (path.startsWith("content://media/external/audio/media/")) {
+                // This is a specific file
+                String id = uri.getLastPathSegment();
+                long[] list = new long[] {
+                    Long.valueOf(id)
+                };
+                MusicUtils.playAll(this, list, 0);
+                finish();
+                return;
+            } else if (path.startsWith("content://media/external/audio/albums/")) {
+                // This is an album, show the songs on it
+                Intent i = new Intent(Intent.ACTION_VIEW);
+                i.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
+                i.putExtra("album", uri.getLastPathSegment());
+                startActivity(i);
+                finish();
+                return;
+            } else if (path.startsWith("content://media/external/audio/artists/")) {
+                intent = new Intent(Intent.ACTION_VIEW);
+
+                Bundle bundle = new Bundle();
+                bundle.putString(MIME_TYPE, Audio.Artists.CONTENT_TYPE);
+                bundle.putString(ARTIST_KEY, uri.getLastPathSegment());
+                bundle.putLong(BaseColumns._ID,
+                        ApolloUtils.getArtistId(uri.getLastPathSegment(), ARTIST_ID, this));
+
+                intent.setClass(this, TracksBrowser.class);
+                intent.putExtras(bundle);
+                startActivity(intent);
+                return;
+            }
+        }
+
+        mFilterString = intent.getStringExtra(SearchManager.QUERY);
+        if (MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)) {
+            String focus = intent.getStringExtra(MediaStore.EXTRA_MEDIA_FOCUS);
+            String artist = intent.getStringExtra(MediaStore.EXTRA_MEDIA_ARTIST);
+            String album = intent.getStringExtra(MediaStore.EXTRA_MEDIA_ALBUM);
+            String title = intent.getStringExtra(MediaStore.EXTRA_MEDIA_TITLE);
+            if (focus != null) {
+                if (focus.startsWith("audio/") && title != null) {
+                    mFilterString = title;
+                } else if (focus.equals(Audio.Albums.ENTRY_CONTENT_TYPE)) {
+                    if (album != null) {
+                        mFilterString = album;
+                        if (artist != null) {
+                            mFilterString = mFilterString + " " + artist;
+                        }
+                    }
+                } else if (focus.equals(Audio.Artists.ENTRY_CONTENT_TYPE)) {
+                    if (artist != null) {
+                        mFilterString = artist;
+                    }
+                }
+            }
+        }
+
+        setContentView(R.layout.listview);
+        mTrackList = getListView();
+        mTrackList.setTextFilterEnabled(true);
+        if (mAdapter == null) {
+            mAdapter = new QueryListAdapter(getApplication(), this, R.layout.listview_items, null, // cursor
+                    new String[] {}, new int[] {}, 0);
+            setListAdapter(mAdapter);
+            if (TextUtils.isEmpty(mFilterString)) {
+                getQueryCursor(mAdapter.getQueryHandler(), null);
+            } else {
+                mTrackList.setFilterText(mFilterString);
+                mFilterString = null;
+            }
+        } else {
+            mAdapter.setActivity(this);
+            setListAdapter(mAdapter);
+            mQueryCursor = mAdapter.getCursor();
+            if (mQueryCursor != null) {
+                init(mQueryCursor);
+            } else {
+                getQueryCursor(mAdapter.getQueryHandler(), mFilterString);
+            }
+        }
+
+        LinearLayout emptyness = (LinearLayout)findViewById(R.id.empty_view);
+        emptyness.setVisibility(View.GONE);
+    }
+
+    @Override
+    public void onServiceDisconnected(ComponentName name) {
+
+    }
+
+    @Override
+    public Object onRetainNonConfigurationInstance() {
+        mAdapterSent = true;
+        return mAdapter;
+    }
+
+    @Override
+    public void onPause() {
+        mReScanHandler.removeCallbacksAndMessages(null);
+        super.onPause();
+    }
+
+    @Override
+    public void onDestroy() {
+        MusicUtils.unbindFromService(mToken);
+        unregisterReceiver(mScanListener);
+        // If we have an adapter and didn't send it off to another activity yet,
+        // we should
+        // close its cursor, which we do by assigning a null cursor to it. Doing
+        // this
+        // instead of closing the cursor directly keeps the framework from
+        // accessing
+        // the closed cursor later.
+        if (!mAdapterSent && mAdapter != null) {
+            mAdapter.changeCursor(null);
+        }
+        // Because we pass the adapter to the next activity, we need to make
+        // sure it doesn't keep a reference to this activity. We can do this
+        // by clearing its DatasetObservers, which setListAdapter(null) does.
+        try {
+            setListAdapter(null);
+        } catch (NullPointerException e) {
+
+        }
+        mAdapter = null;
+        super.onDestroy();
+    }
+
+    /*
+     * This listener gets called when the media scanner starts up, and when the
+     * sd card is unmounted.
+     */
+    private final BroadcastReceiver mScanListener = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            MusicUtils.setSpinnerState(QueryBrowserActivity.this);
+            mReScanHandler.sendEmptyMessage(0);
+        }
+    };
+
+    private final Handler mReScanHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            if (mAdapter != null) {
+                getQueryCursor(mAdapter.getQueryHandler(), null);
+            }
+            // if the query results in a null cursor, onQueryComplete() will
+            // call init(), which will post a delayed message to this handler
+            // in order to try again.
+        }
+    };
+
+    public void init(Cursor c) {
+
+        if (mAdapter == null) {
+            return;
+        }
+        mAdapter.changeCursor(c);
+
+        if (mQueryCursor == null) {
+            MusicUtils.displayDatabaseError(this);
+            setListAdapter(null);
+            mReScanHandler.sendEmptyMessageDelayed(0, 1000);
+            return;
+        }
+    }
+
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id) {
+        // Dialog doesn't allow us to wait for a result, so we need to store
+        // the info we need for when the dialog posts its result
+        mQueryCursor.moveToPosition(position);
+        if (mQueryCursor.isBeforeFirst() || mQueryCursor.isAfterLast()) {
+            return;
+        }
+        String selectedType = mQueryCursor.getString(mQueryCursor
+                .getColumnIndexOrThrow(Audio.Media.MIME_TYPE));
+
+        if ("artist".equals(selectedType)) {
+            Intent intent = new Intent(Intent.ACTION_VIEW);
+
+            TextView tv1 = (TextView)v.findViewById(R.id.listview_item_line_one);
+            String artistName = tv1.getText().toString();
+
+            Bundle bundle = new Bundle();
+            bundle.putString(MIME_TYPE, Audio.Artists.CONTENT_TYPE);
+            bundle.putString(ARTIST_KEY, artistName);
+            bundle.putLong(BaseColumns._ID, id);
+
+            intent.setClass(this, TracksBrowser.class);
+            intent.putExtras(bundle);
+            startActivity(intent);
+            finish();
+        } else if ("album".equals(selectedType)) {
+            TextView tv1 = (TextView)v.findViewById(R.id.listview_item_line_one);
+            TextView tv2 = (TextView)v.findViewById(R.id.listview_item_line_two);
+
+            String artistName = tv2.getText().toString();
+            String albumName = tv1.getText().toString();
+
+            Bundle bundle = new Bundle();
+            bundle.putString(MIME_TYPE, Audio.Albums.CONTENT_TYPE);
+            bundle.putString(ARTIST_KEY, artistName);
+            bundle.putString(ALBUM_KEY, albumName);
+            bundle.putLong(BaseColumns._ID, id);
+
+            Intent intent = new Intent(Intent.ACTION_VIEW);
+            intent.setClass(this, TracksBrowser.class);
+            intent.putExtras(bundle);
+            startActivity(intent);
+            finish();
+        } else if (position >= 0 && id >= 0) {
+            long[] list = new long[] {
+                id
+            };
+            MusicUtils.playAll(this, list, 0);
+        } else {
+            Log.e("QueryBrowser", "invalid position/id: " + position + "/" + id);
+        }
+    }
+
+    private Cursor getQueryCursor(AsyncQueryHandler async, String filter) {
+        if (filter == null) {
+            filter = "";
+        }
+        String[] ccols = new String[] {
+                BaseColumns._ID, Audio.Media.MIME_TYPE, Audio.Artists.ARTIST, Audio.Albums.ALBUM,
+                Audio.Media.TITLE, "data1", "data2"
+        };
+
+        Uri search = Uri.parse("content://media/external/audio/search/fancy/" + Uri.encode(filter));
+
+        Cursor ret = null;
+        if (async != null) {
+            async.startQuery(0, null, search, ccols, null, null, null);
+        } else {
+            ret = MusicUtils.query(this, search, ccols, null, null, null);
+        }
+        return ret;
+    }
+
+    static class QueryListAdapter extends SimpleCursorAdapter {
+        private QueryBrowserActivity mActivity = null;
+
+        private final AsyncQueryHandler mQueryHandler;
+
+        private String mConstraint = null;
+
+        private boolean mConstraintIsValid = false;
+
+        class QueryHandler extends AsyncQueryHandler {
+            QueryHandler(ContentResolver res) {
+                super(res);
+            }
+
+            @Override
+            protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+                mActivity.init(cursor);
+            }
+        }
+
+        QueryListAdapter(Context context, QueryBrowserActivity currentactivity, int layout,
+                Cursor cursor, String[] from, int[] to, int flags) {
+            super(context, layout, cursor, from, to, flags);
+            mActivity = currentactivity;
+            mQueryHandler = new QueryHandler(context.getContentResolver());
+        }
+
+        public void setActivity(QueryBrowserActivity newactivity) {
+            mActivity = newactivity;
+        }
+
+        public AsyncQueryHandler getQueryHandler() {
+            return mQueryHandler;
+        }
+
+        @Override
+        public void bindView(View view, Context context, Cursor cursor) {
+
+            TextView tv1 = (TextView)view.findViewById(R.id.listview_item_line_one);
+            tv1.setTextColor(Color.BLACK);
+            TextView tv2 = (TextView)view.findViewById(R.id.listview_item_line_two);
+            tv2.setTextColor(Color.BLACK);
+            ImageView iv = (ImageView)view.findViewById(R.id.listview_item_image);
+            iv.setVisibility(View.GONE);
+            FrameLayout fl = (FrameLayout)view.findViewById(R.id.track_list_context_frame);
+            fl.setVisibility(View.GONE);
+            ViewGroup.LayoutParams p = iv.getLayoutParams();
+            if (p == null) {
+                // seen this happen, not sure why
+                DatabaseUtils.dumpCursor(cursor);
+                return;
+            }
+            p.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+            p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+
+            String mimetype = cursor.getString(cursor.getColumnIndexOrThrow(Audio.Media.MIME_TYPE));
+
+            if (mimetype == null) {
+                mimetype = "audio/";
+            }
+            if (mimetype.equals("artist")) {
+                String name = cursor.getString(cursor.getColumnIndexOrThrow(Audio.Artists.ARTIST));
+                String displayname = name;
+                boolean isunknown = false;
+                if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) {
+                    displayname = context.getString(R.string.unknown);
+                    isunknown = true;
+                }
+                tv1.setText(displayname);
+
+                int numalbums = cursor.getInt(cursor.getColumnIndexOrThrow("data1"));
+                int numsongs = cursor.getInt(cursor.getColumnIndexOrThrow("data2"));
+
+                String songs_albums = MusicUtils.makeAlbumsLabel(context, numalbums, numsongs,
+                        isunknown);
+
+                tv2.setText(songs_albums);
+
+            } else if (mimetype.equals("album")) {
+                String name = cursor.getString(cursor.getColumnIndexOrThrow(Audio.Albums.ALBUM));
+                String displayname = name;
+                if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) {
+                    displayname = context.getString(R.string.unknown);
+                }
+                tv1.setText(displayname);
+
+                name = cursor.getString(cursor.getColumnIndexOrThrow(Audio.Artists.ARTIST));
+                displayname = name;
+                if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) {
+                    displayname = context.getString(R.string.unknown);
+                }
+                tv2.setText(displayname);
+
+            } else if (mimetype.startsWith("audio/") || mimetype.equals("application/ogg")
+                    || mimetype.equals("application/x-ogg")) {
+                String name = cursor.getString(cursor.getColumnIndexOrThrow(Audio.Media.TITLE));
+                tv1.setText(name);
+
+                String displayname = cursor.getString(cursor
+                        .getColumnIndexOrThrow(Audio.Artists.ARTIST));
+                if (displayname == null || displayname.equals(MediaStore.UNKNOWN_STRING)) {
+                    displayname = context.getString(R.string.unknown);
+                }
+                name = cursor.getString(cursor.getColumnIndexOrThrow(Audio.Albums.ALBUM));
+                if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) {
+                    name = context.getString(R.string.unknown);
+                }
+                tv2.setText(displayname + " - " + name);
+            }
+        }
+
+        @Override
+        public void changeCursor(Cursor cursor) {
+            if (mActivity.isFinishing() && cursor != null) {
+                cursor.close();
+                cursor = null;
+            }
+            if (cursor != mActivity.mQueryCursor) {
+                mActivity.mQueryCursor = cursor;
+                super.changeCursor(cursor);
+            }
+        }
+
+        @Override
+        public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
+            String s = constraint.toString();
+            if (mConstraintIsValid
+                    && ((s == null && mConstraint == null) || (s != null && s.equals(mConstraint)))) {
+                return getCursor();
+            }
+            Cursor c = mActivity.getQueryCursor(null, s);
+            mConstraint = s;
+            mConstraintIsValid = true;
+            return c;
+        }
+    }
+
+    private ListView mTrackList;
+
+    private Cursor mQueryCursor;
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            finish();
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+}
diff --git a/src/com/andrew/apollo/activities/QuickQueue.java b/src/com/andrew/apollo/activities/QuickQueue.java
new file mode 100644 (file)
index 0000000..136123a
--- /dev/null
@@ -0,0 +1,35 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.activities;\r
+\r
+import android.media.AudioManager;\r
+import android.os.Bundle;\r
+import android.provider.BaseColumns;\r
+import android.provider.MediaStore.Audio;\r
+import android.support.v4.app.FragmentActivity;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.grid.fragments.QuickQueueFragment;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ */\r
+public class QuickQueue extends FragmentActivity implements Constants {\r
+\r
+    @Override\r
+    protected void onCreate(Bundle icicle) {\r
+        // This needs to be called first\r
+        super.onCreate(icicle);\r
+\r
+        // Control Media volume\r
+        setVolumeControlStream(AudioManager.STREAM_MUSIC);\r
+\r
+        Bundle bundle = new Bundle();\r
+        bundle.putString(MIME_TYPE, Audio.Playlists.CONTENT_TYPE);\r
+        bundle.putLong(BaseColumns._ID, PLAYLIST_QUEUE);\r
+        getSupportFragmentManager().beginTransaction()\r
+                .replace(android.R.id.content, new QuickQueueFragment(bundle)).commit();\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/activities/ScanningProgress.java b/src/com/andrew/apollo/activities/ScanningProgress.java
new file mode 100644 (file)
index 0000000..72131e8
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * 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.andrew.apollo.activities;
+
+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.support.v4.app.FragmentActivity;
+import android.view.ViewGroup.LayoutParams;
+import android.view.Window;
+
+import com.andrew.apollo.R;
+import com.andrew.apollo.utils.MusicUtils;
+
+public class ScanningProgress extends FragmentActivity {
+    private final static int CHECK = 0;
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == CHECK) {
+                String status = Environment.getExternalStorageState();
+                if (!status.equals(Environment.MEDIA_MOUNTED)) {
+                    // If the card suddenly got unmounted again, there's
+                    // really no need to keep waiting for the media scanner.
+                    finish();
+                    return;
+                }
+                Cursor c = MusicUtils.query(ScanningProgress.this,
+                        MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, null, null, null, null);
+                if (c != null) {
+                    // The external media database is now ready for querying
+                    // (though it may still be in the process of being filled).
+                    c.close();
+                    setResult(RESULT_OK);
+                    finish();
+                    return;
+                }
+                Message next = obtainMessage(CHECK);
+                sendMessageDelayed(next, 3000);
+            }
+        }
+    };
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+        if (android.os.Environment.isExternalStorageRemovable()) {
+            setContentView(R.layout.scanning);
+        } else {
+            setContentView(R.layout.scanning_nosdcard);
+        }
+        getWindow().setLayout(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+        setResult(RESULT_CANCELED);
+
+        Message msg = mHandler.obtainMessage(CHECK);
+        mHandler.sendMessageDelayed(msg, 1000);
+    }
+
+    @Override
+    public void onDestroy() {
+        mHandler.removeMessages(CHECK);
+        super.onDestroy();
+    }
+}
diff --git a/src/com/andrew/apollo/activities/TracksBrowser.java b/src/com/andrew/apollo/activities/TracksBrowser.java
new file mode 100644 (file)
index 0000000..513e5c3
--- /dev/null
@@ -0,0 +1,444 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.activities;\r
+\r
+import android.content.BroadcastReceiver;\r
+import android.content.ComponentName;\r
+import android.content.Context;\r
+import android.content.Intent;\r
+import android.content.IntentFilter;\r
+import android.content.ServiceConnection;\r
+import android.content.pm.ActivityInfo;\r
+import android.content.res.Resources;\r
+import android.graphics.Bitmap;\r
+import android.graphics.BitmapFactory;\r
+import android.graphics.drawable.AnimationDrawable;\r
+import android.media.AudioManager;\r
+import android.os.AsyncTask;\r
+import android.os.Bundle;\r
+import android.os.IBinder;\r
+import android.os.SystemClock;\r
+import android.provider.BaseColumns;\r
+import android.provider.MediaStore.Audio;\r
+import android.support.v4.app.FragmentActivity;\r
+import android.support.v4.view.ViewPager;\r
+import android.view.MenuItem;\r
+import android.view.View;\r
+import android.view.View.OnClickListener;\r
+import android.widget.FrameLayout;\r
+import android.widget.ImageView;\r
+import android.widget.RelativeLayout;\r
+import android.widget.TextView;\r
+\r
+import com.andrew.apollo.BottomActionBarControlsFragment;\r
+import com.andrew.apollo.BottomActionBarFragment;\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.IApolloService;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.adapters.PagerAdapter;\r
+import com.andrew.apollo.list.fragments.ArtistAlbumsFragment;\r
+import com.andrew.apollo.list.fragments.TracksFragment;\r
+import com.andrew.apollo.service.ApolloService;\r
+import com.andrew.apollo.service.ServiceToken;\r
+import com.andrew.apollo.tasks.GetCachedImages;\r
+import com.andrew.apollo.tasks.LastfmGetAlbumImages;\r
+import com.andrew.apollo.tasks.LastfmGetArtistImagesOriginal;\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+import com.andrew.apollo.utils.MusicUtils;\r
+import com.andrew.apollo.utils.ThemeUtils;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ * @Note This displays specific track or album listings\r
+ */\r
+public class TracksBrowser extends FragmentActivity implements Constants, ServiceConnection {\r
+\r
+    // Bundle\r
+    private Bundle bundle;\r
+\r
+    private Intent intent;\r
+\r
+    private String mimeType;\r
+\r
+    private ServiceToken mToken;\r
+\r
+    private final long[] mHits = new long[3];\r
+\r
+    @Override\r
+    protected void onCreate(Bundle icicle) {\r
+        // Landscape mode on phone isn't ready\r
+        if (!ApolloUtils.isTablet(this))\r
+            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);\r
+\r
+        // Control Media volume\r
+        setVolumeControlStream(AudioManager.STREAM_MUSIC);\r
+\r
+        // Layout\r
+        setContentView(R.layout.track_browser);\r
+\r
+        // Important!\r
+        whatBundle(icicle);\r
+\r
+        // Update the colorstrip color\r
+        initColorstrip();\r
+\r
+        // Update the ActionBar\r
+        initActionBar();\r
+\r
+        // Update the half_and_half layout\r
+        initUpperHalf();\r
+\r
+        // Important!\r
+        initPager();\r
+\r
+        // Update the BottomActionBar\r
+        initBottomActionBar();\r
+        super.onCreate(icicle);\r
+    }\r
+\r
+    @Override\r
+    public void onSaveInstanceState(Bundle outcicle) {\r
+        outcicle.putAll(bundle);\r
+        super.onSaveInstanceState(outcicle);\r
+    }\r
+\r
+    @Override\r
+    public void onServiceConnected(ComponentName name, IBinder obj) {\r
+        MusicUtils.mService = IApolloService.Stub.asInterface(obj);\r
+    }\r
+\r
+    @Override\r
+    public void onServiceDisconnected(ComponentName name) {\r
+        MusicUtils.mService = null;\r
+    }\r
+\r
+    /**\r
+     * Update next BottomActionBar as needed\r
+     */\r
+    private final BroadcastReceiver mMediaStatusReceiver = new BroadcastReceiver() {\r
+\r
+        @Override\r
+        public void onReceive(Context context, Intent intent) {\r
+            if (ApolloUtils.isArtist(mimeType) || ApolloUtils.isAlbum(mimeType))\r
+                setArtistImage();\r
+        }\r
+\r
+    };\r
+\r
+    @Override\r
+    protected void onStart() {\r
+        // Bind to Service\r
+        mToken = MusicUtils.bindToService(this, this);\r
+\r
+        IntentFilter filter = new IntentFilter();\r
+        filter.addAction(ApolloService.META_CHANGED);\r
+        registerReceiver(mMediaStatusReceiver, filter);\r
+\r
+        setTitle();\r
+        super.onStart();\r
+    }\r
+\r
+    @Override\r
+    protected void onStop() {\r
+        // Unbind\r
+        if (MusicUtils.mService != null)\r
+            MusicUtils.unbindFromService(mToken);\r
+\r
+        unregisterReceiver(mMediaStatusReceiver);\r
+        super.onStop();\r
+    }\r
+\r
+    @Override\r
+    public boolean onOptionsItemSelected(MenuItem item) {\r
+        switch (item.getItemId()) {\r
+            case android.R.id.home:\r
+                // Artist ID\r
+                long id = ApolloUtils.getArtistId(getArtist(), ARTIST_ID, this);\r
+                if (ApolloUtils.isAlbum(mimeType) && id != 0)\r
+                    tracksBrowser(id);\r
+                super.onBackPressed();\r
+                break;\r
+            default:\r
+                break;\r
+        }\r
+        return super.onOptionsItemSelected(item);\r
+    }\r
+\r
+    /**\r
+     * @param icicle\r
+     * @return what Bundle we're dealing with\r
+     */\r
+    public void whatBundle(Bundle icicle) {\r
+        intent = getIntent();\r
+        bundle = icicle != null ? icicle : intent.getExtras();\r
+        if (bundle == null) {\r
+            bundle = new Bundle();\r
+        }\r
+        if (bundle.getString(INTENT_ACTION) == null) {\r
+            bundle.putString(INTENT_ACTION, intent.getAction());\r
+        }\r
+        if (bundle.getString(MIME_TYPE) == null) {\r
+            bundle.putString(MIME_TYPE, intent.getType());\r
+        }\r
+        mimeType = bundle.getString(MIME_TYPE);\r
+    }\r
+\r
+    /**\r
+     * For the theme chooser\r
+     */\r
+    private void initColorstrip() {\r
+        FrameLayout mColorstrip = (FrameLayout)findViewById(R.id.colorstrip);\r
+        mColorstrip.setBackgroundColor(getResources().getColor(R.color.holo_blue_dark));\r
+        ThemeUtils.setBackgroundColor(this, mColorstrip, "colorstrip");\r
+    }\r
+\r
+    /**\r
+     * Set the ActionBar title\r
+     */\r
+    private void initActionBar() {\r
+        ApolloUtils.showUpTitleOnly(getActionBar());\r
+\r
+        // The ActionBar Title and UP ids are hidden.\r
+        int titleId = Resources.getSystem().getIdentifier("action_bar_title", "id", "android");\r
+        int upId = Resources.getSystem().getIdentifier("up", "id", "android");\r
+\r
+        TextView actionBarTitle = (TextView)findViewById(titleId);\r
+        ImageView actionBarUp = (ImageView)findViewById(upId);\r
+\r
+        // Theme chooser\r
+        ThemeUtils.setActionBarBackground(this, getActionBar(), "action_bar_background");\r
+        ThemeUtils.setTextColor(this, actionBarTitle, "action_bar_title_color");\r
+        ThemeUtils.initThemeChooser(this, actionBarUp, "action_bar_up", THEME_ITEM_BACKGROUND);\r
+\r
+    }\r
+\r
+    /**\r
+     * Sets up the @half_and_half.xml layout\r
+     */\r
+    private void initUpperHalf() {\r
+\r
+        if (ApolloUtils.isArtist(mimeType)) {\r
+            // Get next artist image\r
+        } else if (ApolloUtils.isAlbum(mimeType)) {\r
+            // Album image\r
+            setAlbumImage();\r
+\r
+            // Artist name\r
+            TextView mArtistName = (TextView)findViewById(R.id.half_artist_image_text);\r
+            mArtistName.setVisibility(View.VISIBLE);\r
+            mArtistName.setText(getArtist());\r
+            mArtistName.setBackgroundColor(getResources().getColor(R.color.transparent_black));\r
+\r
+            // Album name\r
+            TextView mAlbumName = (TextView)findViewById(R.id.half_album_image_text);\r
+            mAlbumName.setText(getAlbum());\r
+            mAlbumName.setBackgroundColor(getResources().getColor(R.color.transparent_black));\r
+\r
+            // Album half container\r
+            RelativeLayout mSecondHalfContainer = (RelativeLayout)findViewById(R.id.album_half_container);\r
+            // Show the second half while viewing an album\r
+            mSecondHalfContainer.setVisibility(View.VISIBLE);\r
+        } else {\r
+            // Set the logo\r
+            setPromoImage();\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Initiate ViewPager and PagerAdapter\r
+     */\r
+    private void initPager() {\r
+        // Initiate PagerAdapter\r
+        PagerAdapter mPagerAdapter = new PagerAdapter(getSupportFragmentManager());\r
+        if (ApolloUtils.isArtist(mimeType))\r
+            // Show all albums for an artist\r
+            mPagerAdapter.addFragment(new ArtistAlbumsFragment(bundle));\r
+        // Show the tracks for an artist or album\r
+        mPagerAdapter.addFragment(new TracksFragment(bundle));\r
+\r
+        // Set up ViewPager\r
+        ViewPager mViewPager = (ViewPager)findViewById(R.id.viewPager);\r
+        mViewPager.setPageMargin(getResources().getInteger(R.integer.viewpager_margin_width));\r
+        mViewPager.setPageMarginDrawable(R.drawable.viewpager_margin);\r
+        mViewPager.setOffscreenPageLimit(mPagerAdapter.getCount());\r
+        mViewPager.setAdapter(mPagerAdapter);\r
+\r
+        // Theme chooser\r
+        ThemeUtils.initThemeChooser(this, mViewPager, "viewpager", THEME_ITEM_BACKGROUND);\r
+        ThemeUtils.setMarginDrawable(this, mViewPager, "viewpager_margin");\r
+    }\r
+\r
+    /**\r
+     * Initiate the BottomActionBar\r
+     */\r
+    private void initBottomActionBar() {\r
+        PagerAdapter pagerAdatper = new PagerAdapter(getSupportFragmentManager());\r
+        pagerAdatper.addFragment(new BottomActionBarFragment());\r
+        pagerAdatper.addFragment(new BottomActionBarControlsFragment());\r
+        ViewPager viewPager = (ViewPager)findViewById(R.id.bottomActionBarPager);\r
+        viewPager.setAdapter(pagerAdatper);\r
+    }\r
+\r
+    /**\r
+     * @return artist name from Bundle\r
+     */\r
+    public String getArtist() {\r
+        if (bundle.getString(ARTIST_KEY) != null)\r
+            return bundle.getString(ARTIST_KEY);\r
+        return getResources().getString(R.string.app_name);\r
+    }\r
+\r
+    /**\r
+     * @return album name from Bundle\r
+     */\r
+    public String getAlbum() {\r
+        if (bundle.getString(ALBUM_KEY) != null)\r
+            return bundle.getString(ALBUM_KEY);\r
+        return getResources().getString(R.string.app_name);\r
+    }\r
+\r
+    /**\r
+     * @return genre name from Bundle\r
+     */\r
+    public String getGenre() {\r
+        if (bundle.getString(GENRE_KEY) != null)\r
+            return bundle.getString(GENRE_KEY);\r
+        return getResources().getString(R.string.app_name);\r
+    }\r
+\r
+    /**\r
+     * @return playlist name from Bundle\r
+     */\r
+    public String getPlaylist() {\r
+        if (bundle.getString(PLAYLIST_NAME) != null)\r
+            return bundle.getString(PLAYLIST_NAME);\r
+        return getResources().getString(R.string.app_name);\r
+    }\r
+\r
+    /**\r
+     * Set the header when viewing a genre\r
+     */\r
+    public void setPromoImage() {\r
+\r
+        // Artist image & Genre image\r
+        ImageView mFirstHalfImage = (ImageView)findViewById(R.id.half_artist_image);\r
+\r
+        Bitmap header = BitmapFactory.decodeResource(getResources(), R.drawable.promo);\r
+        ApolloUtils.runnableBackground(mFirstHalfImage, header);\r
+    }\r
+\r
+    /**\r
+     * Cache and set artist image\r
+     */\r
+    public void setArtistImage() {\r
+\r
+        // Artist image & Genre image\r
+        final ImageView mFirstHalfImage = (ImageView)findViewById(R.id.half_artist_image);\r
+\r
+        mFirstHalfImage.post(new Runnable() {\r
+            @Override\r
+            public void run() {\r
+                // Only download images we don't already have\r
+                if (ApolloUtils.getImageURL(getArtist(), ARTIST_IMAGE_ORIGINAL, TracksBrowser.this) == null)\r
+                    new LastfmGetArtistImagesOriginal(TracksBrowser.this, mFirstHalfImage)\r
+                            .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, getArtist());\r
+                // Get and set cached image\r
+                new GetCachedImages(TracksBrowser.this, 0, mFirstHalfImage).executeOnExecutor(\r
+                        AsyncTask.THREAD_POOL_EXECUTOR, getArtist());\r
+            }\r
+        });\r
+\r
+        mFirstHalfImage.setOnClickListener(new OnClickListener() {\r
+\r
+            @Override\r
+            public void onClick(View v) {\r
+                System.arraycopy(mHits, 1, mHits, 0, mHits.length - 1);\r
+                mHits[mHits.length - 1] = SystemClock.uptimeMillis();\r
+                if (mHits[0] >= (SystemClock.uptimeMillis() - 250)) {\r
+                    AnimationDrawable meow = ApolloUtils.getNyanCat(TracksBrowser.this);\r
+                    mFirstHalfImage.setImageDrawable(meow);\r
+                    meow.start();\r
+                }\r
+            }\r
+        });\r
+    }\r
+\r
+    /**\r
+     * Cache and set album image\r
+     */\r
+    public void setAlbumImage() {\r
+\r
+        // Album image\r
+        final ImageView mSecondHalfImage = (ImageView)findViewById(R.id.half_album_image);\r
+\r
+        mSecondHalfImage.post(new Runnable() {\r
+            @Override\r
+            public void run() {\r
+                // Only download images we don't already have\r
+                if (ApolloUtils.getImageURL(getAlbum(), ALBUM_IMAGE, TracksBrowser.this) == null)\r
+                    new LastfmGetAlbumImages(TracksBrowser.this, mSecondHalfImage, 1)\r
+                            .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, getArtist(),\r
+                                    getAlbum());\r
+                // Get and set cached image\r
+                new GetCachedImages(TracksBrowser.this, 1, mSecondHalfImage).executeOnExecutor(\r
+                        AsyncTask.THREAD_POOL_EXECUTOR, getAlbum());\r
+            }\r
+        });\r
+    }\r
+\r
+    /**\r
+     * Return here from viewing the tracks for an album and view all albums and\r
+     * tracks for the same artist\r
+     */\r
+    private void tracksBrowser(long id) {\r
+\r
+        bundle.putString(MIME_TYPE, Audio.Artists.CONTENT_TYPE);\r
+        bundle.putString(ARTIST_KEY, getArtist());\r
+        bundle.putLong(BaseColumns._ID, id);\r
+\r
+        Intent intent = new Intent(Intent.ACTION_VIEW);\r
+        intent.setClass(this, TracksBrowser.class);\r
+        intent.putExtras(bundle);\r
+        startActivity(intent);\r
+    }\r
+\r
+    /**\r
+     * Set the correct title\r
+     */\r
+    private void setTitle() {\r
+        String name;\r
+        long id;\r
+        if (Audio.Playlists.CONTENT_TYPE.equals(mimeType)) {\r
+            id = bundle.getLong(BaseColumns._ID);\r
+            switch ((int)id) {\r
+                case (int)PLAYLIST_QUEUE:\r
+                    setTitle(R.string.nowplaying);\r
+                    return;\r
+                case (int)PLAYLIST_FAVORITES:\r
+                    setTitle(R.string.favorite);\r
+                    return;\r
+                default:\r
+                    if (id < 0) {\r
+                        setTitle(R.string.app_name);\r
+                        return;\r
+                    }\r
+            }\r
+            name = MusicUtils.getPlaylistName(this, id);\r
+        } else if (Audio.Artists.CONTENT_TYPE.equals(mimeType)) {\r
+            id = bundle.getLong(BaseColumns._ID);\r
+            name = MusicUtils.getArtistName(this, id, true);\r
+        } else if (Audio.Albums.CONTENT_TYPE.equals(mimeType)) {\r
+            id = bundle.getLong(BaseColumns._ID);\r
+            name = MusicUtils.getAlbumName(this, id, true);\r
+        } else if (Audio.Genres.CONTENT_TYPE.equals(mimeType)) {\r
+            id = bundle.getLong(BaseColumns._ID);\r
+            name = MusicUtils.parseGenreName(this, MusicUtils.getGenreName(this, id, true));\r
+        } else {\r
+            setTitle(R.string.app_name);\r
+            return;\r
+        }\r
+        setTitle(name);\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/adapters/AlbumAdapter.java b/src/com/andrew/apollo/adapters/AlbumAdapter.java
new file mode 100644 (file)
index 0000000..69ee784
--- /dev/null
@@ -0,0 +1,105 @@
+\r
+package com.andrew.apollo.adapters;\r
+\r
+import java.lang.ref.WeakReference;\r
+\r
+import android.content.Context;\r
+import android.database.Cursor;\r
+import android.graphics.drawable.AnimationDrawable;\r
+import android.os.AsyncTask;\r
+import android.os.RemoteException;\r
+import android.support.v4.widget.SimpleCursorAdapter;\r
+import android.view.View;\r
+import android.view.ViewGroup;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.grid.fragments.AlbumsFragment;\r
+import com.andrew.apollo.tasks.LastfmGetAlbumImages;\r
+import com.andrew.apollo.tasks.ViewHolderTask;\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+import com.andrew.apollo.utils.MusicUtils;\r
+import com.andrew.apollo.views.ViewHolderGrid;\r
+import com.androidquery.AQuery;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ */\r
+public class AlbumAdapter extends SimpleCursorAdapter implements Constants {\r
+\r
+    private AnimationDrawable mPeakOneAnimation, mPeakTwoAnimation;\r
+\r
+    private WeakReference<ViewHolderGrid> holderReference;\r
+\r
+    public AlbumAdapter(Context context, int layout, Cursor c, String[] from, int[] to, int flags) {\r
+        super(context, layout, c, from, to, flags);\r
+    }\r
+\r
+    @Override\r
+    public View getView(final int position, View convertView, ViewGroup parent) {\r
+        final View view = super.getView(position, convertView, parent);\r
+        // ViewHolderGrid\r
+        final ViewHolderGrid viewholder;\r
+\r
+        if (view != null) {\r
+\r
+            viewholder = new ViewHolderGrid(view);\r
+            holderReference = new WeakReference<ViewHolderGrid>(viewholder);\r
+            view.setTag(holderReference.get());\r
+\r
+        } else {\r
+            viewholder = (ViewHolderGrid)convertView.getTag();\r
+        }\r
+\r
+        // AQuery\r
+        final AQuery aq = new AQuery(view);\r
+\r
+        // Album name\r
+        String albumName = mCursor.getString(AlbumsFragment.mAlbumNameIndex);\r
+        holderReference.get().mViewHolderLineOne.setText(albumName);\r
+\r
+        // Artist name\r
+        String artistName = mCursor.getString(AlbumsFragment.mArtistNameIndex);\r
+        holderReference.get().mViewHolderLineTwo.setText(artistName);\r
+\r
+        // Match positions\r
+        holderReference.get().position = position;\r
+        if (aq.shouldDelay(position, view, parent, "")) {\r
+            holderReference.get().mViewHolderImage.setImageDrawable(null);\r
+        } else {\r
+            // Check for missing album images and cache them\r
+            if (ApolloUtils.getImageURL(albumName, ALBUM_IMAGE, mContext) == null) {\r
+                new LastfmGetAlbumImages(mContext, null, 0).executeOnExecutor(\r
+                        AsyncTask.THREAD_POOL_EXECUTOR, artistName, albumName);\r
+            } else {\r
+                new ViewHolderTask(null, holderReference.get(), position, mContext, 1, 1,\r
+                        holderReference.get().mViewHolderImage).executeOnExecutor(\r
+                        AsyncTask.THREAD_POOL_EXECUTOR, albumName);\r
+            }\r
+        }\r
+        // Now playing indicator\r
+        long currentalbumid = MusicUtils.getCurrentAlbumId();\r
+        long albumid = mCursor.getLong(AlbumsFragment.mAlbumIdIndex);\r
+        if (currentalbumid == albumid) {\r
+            holderReference.get().mPeakOne.setImageResource(R.anim.peak_meter_1);\r
+            holderReference.get().mPeakTwo.setImageResource(R.anim.peak_meter_2);\r
+            mPeakOneAnimation = (AnimationDrawable)holderReference.get().mPeakOne.getDrawable();\r
+            mPeakTwoAnimation = (AnimationDrawable)holderReference.get().mPeakTwo.getDrawable();\r
+            try {\r
+                if (MusicUtils.mService.isPlaying()) {\r
+                    mPeakOneAnimation.start();\r
+                    mPeakTwoAnimation.start();\r
+                } else {\r
+                    mPeakOneAnimation.stop();\r
+                    mPeakTwoAnimation.stop();\r
+                }\r
+            } catch (RemoteException e) {\r
+                e.printStackTrace();\r
+            }\r
+        } else {\r
+            holderReference.get().mPeakOne.setImageResource(0);\r
+            holderReference.get().mPeakTwo.setImageResource(0);\r
+        }\r
+        return view;\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/adapters/ArtistAdapter.java b/src/com/andrew/apollo/adapters/ArtistAdapter.java
new file mode 100644 (file)
index 0000000..eac0ef6
--- /dev/null
@@ -0,0 +1,107 @@
+\r
+package com.andrew.apollo.adapters;\r
+\r
+import java.lang.ref.WeakReference;\r
+\r
+import android.content.Context;\r
+import android.database.Cursor;\r
+import android.graphics.drawable.AnimationDrawable;\r
+import android.os.AsyncTask;\r
+import android.os.RemoteException;\r
+import android.provider.MediaStore;\r
+import android.support.v4.widget.SimpleCursorAdapter;\r
+import android.view.View;\r
+import android.view.ViewGroup;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.grid.fragments.ArtistsFragment;\r
+import com.andrew.apollo.tasks.LastfmGetArtistImages;\r
+import com.andrew.apollo.tasks.ViewHolderTask;\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+import com.andrew.apollo.utils.MusicUtils;\r
+import com.andrew.apollo.views.ViewHolderGrid;\r
+import com.androidquery.AQuery;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ */\r
+public class ArtistAdapter extends SimpleCursorAdapter implements Constants {\r
+\r
+    private AnimationDrawable mPeakOneAnimation, mPeakTwoAnimation;\r
+\r
+    private WeakReference<ViewHolderGrid> holderReference;\r
+\r
+    public ArtistAdapter(Context context, int layout, Cursor c, String[] from, int[] to, int flags) {\r
+        super(context, layout, c, from, to, flags);\r
+    }\r
+\r
+    @Override\r
+    public View getView(final int position, View convertView, ViewGroup parent) {\r
+        final View view = super.getView(position, convertView, parent);\r
+        // ViewHolderGrid\r
+        final ViewHolderGrid viewholder;\r
+\r
+        if (view != null) {\r
+\r
+            viewholder = new ViewHolderGrid(view);\r
+            holderReference = new WeakReference<ViewHolderGrid>(viewholder);\r
+            view.setTag(holderReference.get());\r
+\r
+        } else {\r
+            viewholder = (ViewHolderGrid)convertView.getTag();\r
+        }\r
+\r
+        // AQuery\r
+        final AQuery aq = new AQuery(view);\r
+\r
+        // Artist Name\r
+        String artistName = mCursor.getString(ArtistsFragment.mArtistNameIndex);\r
+        holderReference.get().mViewHolderLineOne.setText(artistName);\r
+\r
+        // Number of albums\r
+        int albums_plural = mCursor.getInt(ArtistsFragment.mArtistNumAlbumsIndex);\r
+        boolean unknown = artistName == null || artistName.equals(MediaStore.UNKNOWN_STRING);\r
+        String numAlbums = MusicUtils.makeAlbumsLabel(mContext, albums_plural, 0, unknown);\r
+        holderReference.get().mViewHolderLineTwo.setText(numAlbums);\r
+\r
+        holderReference.get().position = position;\r
+        if (aq.shouldDelay(position, view, parent, "")) {\r
+            holderReference.get().mViewHolderImage.setImageDrawable(null);\r
+        } else {\r
+            // Check for missing artist images and cache them\r
+            if (ApolloUtils.getImageURL(artistName, ARTIST_IMAGE, mContext) == null) {\r
+                new LastfmGetArtistImages(mContext).executeOnExecutor(\r
+                        AsyncTask.THREAD_POOL_EXECUTOR, artistName);\r
+            } else {\r
+                new ViewHolderTask(null, holderReference.get(), position, mContext, 0, 1,\r
+                        holderReference.get().mViewHolderImage).executeOnExecutor(\r
+                        AsyncTask.THREAD_POOL_EXECUTOR, artistName);\r
+            }\r
+        }\r
+        // Now playing indicator\r
+        long currentartistid = MusicUtils.getCurrentArtistId();\r
+        long artistid = mCursor.getLong(ArtistsFragment.mArtistIdIndex);\r
+        if (currentartistid == artistid) {\r
+            holderReference.get().mPeakOne.setImageResource(R.anim.peak_meter_1);\r
+            holderReference.get().mPeakTwo.setImageResource(R.anim.peak_meter_2);\r
+            mPeakOneAnimation = (AnimationDrawable)holderReference.get().mPeakOne.getDrawable();\r
+            mPeakTwoAnimation = (AnimationDrawable)holderReference.get().mPeakTwo.getDrawable();\r
+            try {\r
+                if (MusicUtils.mService.isPlaying()) {\r
+                    mPeakOneAnimation.start();\r
+                    mPeakTwoAnimation.start();\r
+                } else {\r
+                    mPeakOneAnimation.stop();\r
+                    mPeakTwoAnimation.stop();\r
+                }\r
+            } catch (RemoteException e) {\r
+                e.printStackTrace();\r
+            }\r
+        } else {\r
+            holderReference.get().mPeakOne.setImageResource(0);\r
+            holderReference.get().mPeakTwo.setImageResource(0);\r
+        }\r
+        return view;\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/adapters/ArtistAlbumAdapter.java b/src/com/andrew/apollo/adapters/ArtistAlbumAdapter.java
new file mode 100644 (file)
index 0000000..b75e517
--- /dev/null
@@ -0,0 +1,123 @@
+\r
+package com.andrew.apollo.adapters;\r
+\r
+import java.lang.ref.WeakReference;\r
+\r
+import android.content.Context;\r
+import android.database.Cursor;\r
+import android.graphics.drawable.AnimationDrawable;\r
+import android.os.AsyncTask;\r
+import android.os.RemoteException;\r
+import android.support.v4.widget.SimpleCursorAdapter;\r
+import android.view.View;\r
+import android.view.ViewGroup;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.list.fragments.ArtistAlbumsFragment;\r
+import com.andrew.apollo.tasks.LastfmGetAlbumImages;\r
+import com.andrew.apollo.tasks.ViewHolderTask;\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+import com.andrew.apollo.utils.MusicUtils;\r
+import com.andrew.apollo.views.ViewHolderList;\r
+import com.androidquery.AQuery;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ */\r
+public class ArtistAlbumAdapter extends SimpleCursorAdapter implements Constants {\r
+\r
+    private AnimationDrawable mPeakOneAnimation, mPeakTwoAnimation;\r
+\r
+    private WeakReference<ViewHolderList> holderReference;\r
+\r
+    public ArtistAlbumAdapter(Context context, int layout, Cursor c, String[] from, int[] to,\r
+            int flags) {\r
+        super(context, layout, c, from, to, flags);\r
+    }\r
+\r
+    /**\r
+     * Used to quickly our the ContextMenu\r
+     */\r
+    private final View.OnClickListener showContextMenu = new View.OnClickListener() {\r
+        @Override\r
+        public void onClick(View v) {\r
+            v.showContextMenu();\r
+        }\r
+    };\r
+\r
+    @Override\r
+    public View getView(int position, View convertView, ViewGroup parent) {\r
+        final View view = super.getView(position, convertView, parent);\r
+        // ViewHolderList\r
+        ViewHolderList viewholder;\r
+\r
+        if (view != null) {\r
+\r
+            viewholder = new ViewHolderList(view);\r
+            holderReference = new WeakReference<ViewHolderList>(viewholder);\r
+            view.setTag(holderReference.get());\r
+\r
+        } else {\r
+            viewholder = (ViewHolderList)convertView.getTag();\r
+        }\r
+\r
+        // AQuery\r
+        AQuery aq = new AQuery(view);\r
+\r
+        // Album name\r
+        String albumName = mCursor.getString(ArtistAlbumsFragment.mAlbumNameIndex);\r
+        holderReference.get().mViewHolderLineOne.setText(albumName);\r
+\r
+        // Artist name\r
+        String artistName = mCursor.getString(ArtistAlbumsFragment.mArtistNameIndex);\r
+\r
+        // Number of songs\r
+        int songs_plural = mCursor.getInt(ArtistAlbumsFragment.mSongCountIndex);\r
+        String numSongs = MusicUtils.makeAlbumsLabel(mContext, 0, songs_plural, true);\r
+        holderReference.get().mViewHolderLineTwo.setText(numSongs);\r
+\r
+        // Match positions\r
+        holderReference.get().position = position;\r
+        if (aq.shouldDelay(position, view, parent, "")) {\r
+            holderReference.get().mViewHolderImage.setImageDrawable(null);\r
+        } else {\r
+            // Check for missing album images and cache them\r
+            if (ApolloUtils.getImageURL(albumName, ALBUM_IMAGE, mContext) == null) {\r
+                new LastfmGetAlbumImages(mContext, null, 0).executeOnExecutor(\r
+                        AsyncTask.THREAD_POOL_EXECUTOR, artistName, albumName);\r
+            } else {\r
+                new ViewHolderTask(holderReference.get(), null, position, mContext, 1, 0,\r
+                        holderReference.get().mViewHolderImage).executeOnExecutor(\r
+                        AsyncTask.THREAD_POOL_EXECUTOR, albumName);\r
+            }\r
+        }\r
+\r
+        holderReference.get().mQuickContext.setOnClickListener(showContextMenu);\r
+\r
+        // Now playing indicator\r
+        long currentalbumid = MusicUtils.getCurrentAlbumId();\r
+        long albumid = mCursor.getLong(ArtistAlbumsFragment.mAlbumIdIndex);\r
+        if (currentalbumid == albumid) {\r
+            holderReference.get().mPeakOne.setImageResource(R.anim.peak_meter_1);\r
+            holderReference.get().mPeakTwo.setImageResource(R.anim.peak_meter_2);\r
+            mPeakOneAnimation = (AnimationDrawable)holderReference.get().mPeakOne.getDrawable();\r
+            mPeakTwoAnimation = (AnimationDrawable)holderReference.get().mPeakTwo.getDrawable();\r
+            try {\r
+                if (MusicUtils.mService.isPlaying()) {\r
+                    mPeakOneAnimation.start();\r
+                    mPeakTwoAnimation.start();\r
+                } else {\r
+                    mPeakOneAnimation.stop();\r
+                    mPeakTwoAnimation.stop();\r
+                }\r
+            } catch (RemoteException e) {\r
+                e.printStackTrace();\r
+            }\r
+        } else {\r
+            holderReference.get().mPeakOne.setImageResource(0);\r
+            holderReference.get().mPeakTwo.setImageResource(0);\r
+        }\r
+        return view;\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/adapters/GenreAdapter.java b/src/com/andrew/apollo/adapters/GenreAdapter.java
new file mode 100644 (file)
index 0000000..ce64a28
--- /dev/null
@@ -0,0 +1,73 @@
+\r
+package com.andrew.apollo.adapters;\r
+\r
+import java.lang.ref.WeakReference;\r
+\r
+import android.content.Context;\r
+import android.database.Cursor;\r
+import android.support.v4.widget.SimpleCursorAdapter;\r
+import android.view.View;\r
+import android.view.ViewGroup;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.list.fragments.GenresFragment;\r
+import com.andrew.apollo.utils.MusicUtils;\r
+import com.andrew.apollo.views.ViewHolderList;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ */\r
+public class GenreAdapter extends SimpleCursorAdapter implements Constants {\r
+\r
+    private WeakReference<ViewHolderList> holderReference;\r
+\r
+    private final int left;\r
+\r
+    public GenreAdapter(Context context, int layout, Cursor c, String[] from, int[] to, int flags) {\r
+        super(context, layout, c, from, to, flags);\r
+        // Helps center the text in the Genres tab\r
+        left = mContext.getResources().getDimensionPixelSize(\r
+                R.dimen.listview_items_padding_left_top);\r
+    }\r
+\r
+    /**\r
+     * Used to quickly our the ContextMenu\r
+     */\r
+    private final View.OnClickListener showContextMenu = new View.OnClickListener() {\r
+        @Override\r
+        public void onClick(View v) {\r
+            v.showContextMenu();\r
+        }\r
+    };\r
+\r
+    @Override\r
+    public View getView(final int position, View convertView, ViewGroup parent) {\r
+        final View view = super.getView(position, convertView, parent);\r
+        // ViewHolderList\r
+        final ViewHolderList viewholder;\r
+\r
+        if (view != null) {\r
+\r
+            viewholder = new ViewHolderList(view);\r
+            holderReference = new WeakReference<ViewHolderList>(viewholder);\r
+            view.setTag(holderReference.get());\r
+\r
+        } else {\r
+            viewholder = (ViewHolderList)convertView.getTag();\r
+        }\r
+\r
+        // Genre name\r
+        String genreName = mCursor.getString(GenresFragment.mGenreNameIndex);\r
+        holderReference.get().mViewHolderLineOne.setText(MusicUtils.parseGenreName(mContext,\r
+                genreName));\r
+\r
+        holderReference.get().mViewHolderLineOne.setPadding(left, 40, 0, 0);\r
+\r
+        holderReference.get().mViewHolderImage.setVisibility(View.GONE);\r
+        holderReference.get().mViewHolderLineTwo.setVisibility(View.GONE);\r
+\r
+        holderReference.get().mQuickContext.setOnClickListener(showContextMenu);\r
+        return view;\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/adapters/PagerAdapter.java b/src/com/andrew/apollo/adapters/PagerAdapter.java
new file mode 100644 (file)
index 0000000..263a875
--- /dev/null
@@ -0,0 +1,39 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.adapters;\r
+\r
+import java.util.ArrayList;\r
+\r
+import android.support.v4.app.Fragment;\r
+import android.support.v4.app.FragmentManager;\r
+import android.support.v4.app.FragmentPagerAdapter;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ */\r
+public class PagerAdapter extends FragmentPagerAdapter {\r
+\r
+    private final ArrayList<Fragment> mFragments = new ArrayList<Fragment>();\r
+\r
+    public PagerAdapter(FragmentManager manager) {\r
+        super(manager);\r
+    }\r
+\r
+    public void addFragment(Fragment fragment) {\r
+        mFragments.add(fragment);\r
+        notifyDataSetChanged();\r
+    }\r
+\r
+    @Override\r
+    public int getCount() {\r
+        return mFragments.size();\r
+    }\r
+\r
+    @Override\r
+    public Fragment getItem(int position) {\r
+        return mFragments.get(position);\r
+    }\r
+\r
+}\r
diff --git a/src/com/andrew/apollo/adapters/PlaylistAdapter.java b/src/com/andrew/apollo/adapters/PlaylistAdapter.java
new file mode 100644 (file)
index 0000000..bddddda
--- /dev/null
@@ -0,0 +1,70 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.adapters;\r
+\r
+import java.lang.ref.WeakReference;\r
+\r
+import android.content.Context;\r
+import android.database.Cursor;\r
+import android.support.v4.widget.SimpleCursorAdapter;\r
+import android.view.View;\r
+import android.view.ViewGroup;\r
+\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.list.fragments.PlaylistsFragment;\r
+import com.andrew.apollo.views.ViewHolderList;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ */\r
+public class PlaylistAdapter extends SimpleCursorAdapter {\r
+\r
+    private WeakReference<ViewHolderList> holderReference;\r
+\r
+    public PlaylistAdapter(Context context, int layout, Cursor c, String[] from, int[] to, int flags) {\r
+        super(context, layout, c, from, to, flags);\r
+    }\r
+\r
+    /**\r
+     * Used to quickly our the ContextMenu\r
+     */\r
+    private final View.OnClickListener showContextMenu = new View.OnClickListener() {\r
+        @Override\r
+        public void onClick(View v) {\r
+            v.showContextMenu();\r
+        }\r
+    };\r
+\r
+    @Override\r
+    public View getView(final int position, View convertView, ViewGroup parent) {\r
+        final View view = super.getView(position, convertView, parent);\r
+        // ViewHolderList\r
+        final ViewHolderList viewholder;\r
+\r
+        if (view != null) {\r
+\r
+            viewholder = new ViewHolderList(view);\r
+            holderReference = new WeakReference<ViewHolderList>(viewholder);\r
+            view.setTag(holderReference.get());\r
+\r
+        } else {\r
+            viewholder = (ViewHolderList)convertView.getTag();\r
+        }\r
+\r
+        String playlist_name = mCursor.getString(PlaylistsFragment.mPlaylistNameIndex);\r
+        holderReference.get().mViewHolderLineOne.setText(playlist_name);\r
+\r
+        // Helps center the text in the Playlist tab\r
+        int left = mContext.getResources().getDimensionPixelSize(\r
+                R.dimen.listview_items_padding_left_top);\r
+        holderReference.get().mViewHolderLineOne.setPadding(left, 40, 0, 0);\r
+\r
+        holderReference.get().mViewHolderImage.setVisibility(View.GONE);\r
+\r
+        holderReference.get().mQuickContext.setOnClickListener(showContextMenu);\r
+        return view;\r
+    }\r
+\r
+}\r
diff --git a/src/com/andrew/apollo/adapters/QuickQueueAdapter.java b/src/com/andrew/apollo/adapters/QuickQueueAdapter.java
new file mode 100644 (file)
index 0000000..416815e
--- /dev/null
@@ -0,0 +1,95 @@
+\r
+package com.andrew.apollo.adapters;\r
+\r
+import java.lang.ref.WeakReference;\r
+\r
+import android.content.Context;\r
+import android.database.Cursor;\r
+import android.os.AsyncTask;\r
+import android.support.v4.widget.SimpleCursorAdapter;\r
+import android.view.View;\r
+import android.view.ViewGroup;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.grid.fragments.QuickQueueFragment;\r
+import com.andrew.apollo.tasks.LastfmGetAlbumImages;\r
+import com.andrew.apollo.tasks.LastfmGetArtistImages;\r
+import com.andrew.apollo.tasks.ViewHolderQueueTask;\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+import com.andrew.apollo.views.ViewHolderQueue;\r
+import com.androidquery.AQuery;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ */\r
+public class QuickQueueAdapter extends SimpleCursorAdapter implements Constants {\r
+\r
+    private WeakReference<ViewHolderQueue> holderReference;\r
+\r
+    public QuickQueueAdapter(Context context, int layout, Cursor c, String[] from, int[] to,\r
+            int flags) {\r
+        super(context, layout, c, from, to, flags);\r
+    }\r
+\r
+    @Override\r
+    public View getView(final int position, View convertView, ViewGroup parent) {\r
+        final View view = super.getView(position, convertView, parent);\r
+        // ViewHolderQueue\r
+        final ViewHolderQueue viewholder;\r
+\r
+        if (view != null) {\r
+\r
+            viewholder = new ViewHolderQueue(view);\r
+            holderReference = new WeakReference<ViewHolderQueue>(viewholder);\r
+            view.setTag(holderReference.get());\r
+\r
+        } else {\r
+            viewholder = (ViewHolderQueue)convertView.getTag();\r
+        }\r
+\r
+        // AQuery\r
+        final AQuery aq = new AQuery(view);\r
+\r
+        // Artist Name\r
+        String artistName = mCursor.getString(QuickQueueFragment.mArtistIndex);\r
+\r
+        // Album name\r
+        String albumName = mCursor.getString(QuickQueueFragment.mAlbumIndex);\r
+\r
+        // Track name\r
+        String trackName = mCursor.getString(QuickQueueFragment.mTitleIndex);\r
+        holderReference.get().mTrackName.setText(trackName);\r
+\r
+        holderReference.get().position = position;\r
+        // Artist Image\r
+        if (aq.shouldDelay(position, view, parent, "")) {\r
+            holderReference.get().mArtistImage.setImageDrawable(null);\r
+        } else {\r
+            // Check for missing artist images and cache them\r
+            if (ApolloUtils.getImageURL(artistName, ARTIST_IMAGE, mContext) == null) {\r
+                new LastfmGetArtistImages(mContext).executeOnExecutor(\r
+                        AsyncTask.THREAD_POOL_EXECUTOR, artistName);\r
+            } else {\r
+                new ViewHolderQueueTask(holderReference.get(), position, mContext, 0, 0,\r
+                        holderReference.get().mArtistImage).executeOnExecutor(\r
+                        AsyncTask.THREAD_POOL_EXECUTOR, artistName);\r
+            }\r
+        }\r
+\r
+        // Album Image\r
+        if (aq.shouldDelay(position, view, parent, "")) {\r
+            holderReference.get().mAlbumArt.setImageDrawable(null);\r
+        } else {\r
+            // Check for missing album images and cache them\r
+            if (ApolloUtils.getImageURL(albumName, ALBUM_IMAGE, mContext) == null) {\r
+                new LastfmGetAlbumImages(mContext, null, 0).executeOnExecutor(\r
+                        AsyncTask.THREAD_POOL_EXECUTOR, artistName, albumName);\r
+            } else {\r
+                new ViewHolderQueueTask(holderReference.get(), position, mContext, 1, 1,\r
+                        holderReference.get().mAlbumArt).executeOnExecutor(\r
+                        AsyncTask.THREAD_POOL_EXECUTOR, albumName);\r
+            }\r
+        }\r
+        return view;\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/adapters/RecentlyAddedAdapter.java b/src/com/andrew/apollo/adapters/RecentlyAddedAdapter.java
new file mode 100644 (file)
index 0000000..665fa53
--- /dev/null
@@ -0,0 +1,111 @@
+\r
+package com.andrew.apollo.adapters;\r
+\r
+import java.lang.ref.WeakReference;\r
+\r
+import android.content.Context;\r
+import android.database.Cursor;\r
+import android.graphics.drawable.AnimationDrawable;\r
+import android.os.AsyncTask;\r
+import android.os.RemoteException;\r
+import android.support.v4.widget.SimpleCursorAdapter;\r
+import android.view.View;\r
+import android.view.ViewGroup;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.list.fragments.RecentlyAddedFragment;\r
+import com.andrew.apollo.tasks.LastfmGetAlbumImages;\r
+import com.andrew.apollo.tasks.ViewHolderTask;\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+import com.andrew.apollo.utils.MusicUtils;\r
+import com.andrew.apollo.views.ViewHolderList;\r
+import com.androidquery.AQuery;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ */\r
+public class RecentlyAddedAdapter extends SimpleCursorAdapter implements Constants {\r
+\r
+    private AnimationDrawable mPeakOneAnimation, mPeakTwoAnimation;\r
+\r
+    private WeakReference<ViewHolderList> holderReference;\r
+\r
+    public RecentlyAddedAdapter(Context context, int layout, Cursor c, String[] from, int[] to,\r
+            int flags) {\r
+        super(context, layout, c, from, to, flags);\r
+    }\r
+\r
+    @Override\r
+    public View getView(int position, View convertView, ViewGroup parent) {\r
+        final View view = super.getView(position, convertView, parent);\r
+        // ViewHolderList\r
+        ViewHolderList viewholder;\r
+\r
+        if (view != null) {\r
+\r
+            viewholder = new ViewHolderList(view);\r
+            holderReference = new WeakReference<ViewHolderList>(viewholder);\r
+            view.setTag(holderReference.get());\r
+\r
+        } else {\r
+            viewholder = (ViewHolderList)convertView.getTag();\r
+        }\r
+        // AQuery\r
+        final AQuery aq = new AQuery(convertView);\r
+\r
+        // Track name\r
+        String trackName = mCursor.getString(RecentlyAddedFragment.mTitleIndex);\r
+        holderReference.get().mViewHolderLineOne.setText(trackName);\r
+\r
+        // Artist name\r
+        String artistName = mCursor.getString(RecentlyAddedFragment.mArtistIndex);\r
+        holderReference.get().mViewHolderLineTwo.setText(artistName);\r
+\r
+        // Album name\r
+        String albumName = mCursor.getString(RecentlyAddedFragment.mAlbumIndex);\r
+\r
+        // Match positions\r
+        holderReference.get().position = position;\r
+        if (aq.shouldDelay(position, view, parent, "")) {\r
+            holderReference.get().mViewHolderImage.setImageDrawable(null);\r
+        } else {\r
+            // Check for missing artwork and cache then cache it\r
+            if (ApolloUtils.getImageURL(albumName, ALBUM_IMAGE, mContext) == null) {\r
+                new LastfmGetAlbumImages(mContext, null, 0).executeOnExecutor(\r
+                        AsyncTask.THREAD_POOL_EXECUTOR, artistName, albumName);\r
+            } else {\r
+                new ViewHolderTask(holderReference.get(), null, position, mContext, 1, 0,\r
+                        holderReference.get().mViewHolderImage).executeOnExecutor(\r
+                        AsyncTask.THREAD_POOL_EXECUTOR, albumName);\r
+            }\r
+        }\r
+\r
+        holderReference.get().mQuickContext.setVisibility(View.GONE);\r
+\r
+        // Now playing indicator\r
+        long currentaudioid = MusicUtils.getCurrentAudioId();\r
+        long audioid = mCursor.getLong(RecentlyAddedFragment.mMediaIdIndex);\r
+        if (currentaudioid == audioid) {\r
+            holderReference.get().mPeakOne.setImageResource(R.anim.peak_meter_1);\r
+            holderReference.get().mPeakTwo.setImageResource(R.anim.peak_meter_2);\r
+            mPeakOneAnimation = (AnimationDrawable)holderReference.get().mPeakOne.getDrawable();\r
+            mPeakTwoAnimation = (AnimationDrawable)holderReference.get().mPeakTwo.getDrawable();\r
+            try {\r
+                if (MusicUtils.mService.isPlaying()) {\r
+                    mPeakOneAnimation.start();\r
+                    mPeakTwoAnimation.start();\r
+                } else {\r
+                    mPeakOneAnimation.stop();\r
+                    mPeakTwoAnimation.stop();\r
+                }\r
+            } catch (RemoteException e) {\r
+                e.printStackTrace();\r
+            }\r
+        } else {\r
+            holderReference.get().mPeakOne.setImageResource(0);\r
+            holderReference.get().mPeakTwo.setImageResource(0);\r
+        }\r
+        return view;\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/adapters/ScrollingTabsAdapter.java b/src/com/andrew/apollo/adapters/ScrollingTabsAdapter.java
new file mode 100644 (file)
index 0000000..8ed4239
--- /dev/null
@@ -0,0 +1,34 @@
+\r
+package com.andrew.apollo.adapters;\r
+\r
+import android.support.v4.app.FragmentActivity;\r
+import android.view.LayoutInflater;\r
+import android.view.View;\r
+import android.widget.Button;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.utils.ThemeUtils;\r
+\r
+public class ScrollingTabsAdapter implements TabAdapter, Constants {\r
+\r
+    private final FragmentActivity activity;\r
+\r
+    public ScrollingTabsAdapter(FragmentActivity act) {\r
+        activity = act;\r
+    }\r
+\r
+    @Override\r
+    public View getView(int position) {\r
+\r
+        LayoutInflater inflater = activity.getLayoutInflater();\r
+        final Button tab = (Button)inflater.inflate(R.layout.tabs, null);\r
+\r
+        if (position < mTitles.length)\r
+            tab.setText(mTitles[position]);\r
+\r
+        // Theme chooser\r
+        ThemeUtils.setTextColor(activity, tab, "tab_text_color");\r
+        return tab;\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/adapters/TabAdapter.java b/src/com/andrew/apollo/adapters/TabAdapter.java
new file mode 100644 (file)
index 0000000..e24e6f4
--- /dev/null
@@ -0,0 +1,8 @@
+\r
+package com.andrew.apollo.adapters;\r
+\r
+import android.view.View;\r
+\r
+public interface TabAdapter {\r
+    public View getView(int position);\r
+}\r
diff --git a/src/com/andrew/apollo/adapters/TrackAdapter.java b/src/com/andrew/apollo/adapters/TrackAdapter.java
new file mode 100644 (file)
index 0000000..f465c8e
--- /dev/null
@@ -0,0 +1,97 @@
+\r
+package com.andrew.apollo.adapters;\r
+\r
+import java.lang.ref.WeakReference;\r
+\r
+import android.content.Context;\r
+import android.database.Cursor;\r
+import android.graphics.drawable.AnimationDrawable;\r
+import android.os.RemoteException;\r
+import android.support.v4.widget.SimpleCursorAdapter;\r
+import android.view.View;\r
+import android.view.ViewGroup;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.list.fragments.TracksFragment;\r
+import com.andrew.apollo.utils.MusicUtils;\r
+import com.andrew.apollo.views.ViewHolderList;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ */\r
+public class TrackAdapter extends SimpleCursorAdapter implements Constants {\r
+\r
+    private AnimationDrawable mPeakOneAnimation, mPeakTwoAnimation;\r
+\r
+    private WeakReference<ViewHolderList> holderReference;\r
+\r
+    public TrackAdapter(Context context, int layout, Cursor c, String[] from, int[] to, int flags) {\r
+        super(context, layout, c, from, to, flags);\r
+    }\r
+\r
+    /**\r
+     * Used to quickly our the ContextMenu\r
+     */\r
+    private final View.OnClickListener showContextMenu = new View.OnClickListener() {\r
+        @Override\r
+        public void onClick(View v) {\r
+            v.showContextMenu();\r
+        }\r
+    };\r
+\r
+    @Override\r
+    public View getView(int position, View convertView, ViewGroup parent) {\r
+        final View view = super.getView(position, convertView, parent);\r
+        // ViewHolderList\r
+        ViewHolderList viewholder;\r
+\r
+        if (view != null) {\r
+\r
+            viewholder = new ViewHolderList(view);\r
+            holderReference = new WeakReference<ViewHolderList>(viewholder);\r
+            view.setTag(holderReference.get());\r
+\r
+        } else {\r
+            viewholder = (ViewHolderList)convertView.getTag();\r
+        }\r
+\r
+        // Track name\r
+        String trackName = mCursor.getString(TracksFragment.mTitleIndex);\r
+        viewholder.mViewHolderLineOne.setText(trackName);\r
+\r
+        // Artist name\r
+        String artistName = mCursor.getString(TracksFragment.mArtistIndex);\r
+        holderReference.get().mViewHolderLineTwo.setText(artistName);\r
+\r
+        // Hide the album art\r
+        holderReference.get().mViewHolderImage.setVisibility(View.GONE);\r
+\r
+        holderReference.get().mQuickContext.setOnClickListener(showContextMenu);\r
+\r
+        // Now playing indicator\r
+        long currentaudioid = MusicUtils.getCurrentAudioId();\r
+        long audioid = mCursor.getLong(TracksFragment.mMediaIdIndex);\r
+        if (currentaudioid == audioid) {\r
+            holderReference.get().mPeakOne.setImageResource(R.anim.peak_meter_1);\r
+            holderReference.get().mPeakTwo.setImageResource(R.anim.peak_meter_2);\r
+            mPeakOneAnimation = (AnimationDrawable)holderReference.get().mPeakOne.getDrawable();\r
+            mPeakTwoAnimation = (AnimationDrawable)holderReference.get().mPeakTwo.getDrawable();\r
+            try {\r
+                if (MusicUtils.mService.isPlaying()) {\r
+                    mPeakOneAnimation.start();\r
+                    mPeakTwoAnimation.start();\r
+                } else {\r
+                    mPeakOneAnimation.stop();\r
+                    mPeakTwoAnimation.stop();\r
+                }\r
+            } catch (RemoteException e) {\r
+                e.printStackTrace();\r
+            }\r
+        } else {\r
+            holderReference.get().mPeakOne.setImageResource(0);\r
+            holderReference.get().mPeakTwo.setImageResource(0);\r
+        }\r
+        return view;\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/app/widgets/AppWidget11.java b/src/com/andrew/apollo/app/widgets/AppWidget11.java
new file mode 100644 (file)
index 0000000..6fdb63c
--- /dev/null
@@ -0,0 +1,145 @@
+\r
+package com.andrew.apollo.app.widgets;\r
+\r
+import android.app.PendingIntent;\r
+import android.appwidget.AppWidgetManager;\r
+import android.appwidget.AppWidgetProvider;\r
+import android.content.ComponentName;\r
+import android.content.Context;\r
+import android.content.Intent;\r
+import android.graphics.Bitmap;\r
+import android.view.View;\r
+import android.widget.RemoteViews;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.service.ApolloService;\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+import com.androidquery.AQuery;\r
+\r
+/**\r
+ * Simple widget to show currently playing album art along with play/pause and\r
+ * next track buttons.\r
+ */\r
+public class AppWidget11 extends AppWidgetProvider implements Constants {\r
+    static final String TAG = "MusicAppWidgetProvider1x1";\r
+\r
+    public static final String CMDAPPWIDGETUPDATE = "appwidgetupdate1x1";\r
+\r
+    private static AppWidget11 sInstance;\r
+\r
+    public static synchronized AppWidget11 getInstance() {\r
+        if (sInstance == null) {\r
+            sInstance = new AppWidget11();\r
+        }\r
+        return sInstance;\r
+    }\r
+\r
+    @Override\r
+    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {\r
+        defaultAppWidget(context, appWidgetIds);\r
+\r
+        // Send broadcast intent to any running ApolloService so it can\r
+        // wrap around with an immediate update.\r
+        Intent updateIntent = new Intent(ApolloService.SERVICECMD);\r
+        updateIntent.putExtra(ApolloService.CMDNAME, AppWidget11.CMDAPPWIDGETUPDATE);\r
+        updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);\r
+        updateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);\r
+        context.sendBroadcast(updateIntent);\r
+    }\r
+\r
+    /**\r
+     * Initialize given widgets to default state, where we launch Music on\r
+     * default click and hide actions if service not running.\r
+     */\r
+    private void defaultAppWidget(Context context, int[] appWidgetIds) {\r
+        final RemoteViews views = new RemoteViews(context.getPackageName(),\r
+                R.layout.onebyone_app_widget);\r
+\r
+        views.setImageViewResource(R.id.one_by_one_albumart, View.GONE);\r
+\r
+        linkButtons(context, views, false /* not playing */);\r
+        pushUpdate(context, appWidgetIds, views);\r
+    }\r
+\r
+    private void pushUpdate(Context context, int[] appWidgetIds, RemoteViews views) {\r
+        // Update specific list of appWidgetIds if given, otherwise default to\r
+        // all\r
+        final AppWidgetManager gm = AppWidgetManager.getInstance(context);\r
+        if (appWidgetIds != null) {\r
+            gm.updateAppWidget(appWidgetIds, views);\r
+        } else {\r
+            gm.updateAppWidget(new ComponentName(context, this.getClass()), views);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Check against {@link AppWidgetManager} if there are any instances of this\r
+     * widget.\r
+     */\r
+    private boolean hasInstances(Context context) {\r
+        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);\r
+        int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, this\r
+                .getClass()));\r
+        return (appWidgetIds.length > 0);\r
+    }\r
+\r
+    /**\r
+     * Handle a change notification coming over from {@link ApolloService}\r
+     */\r
+    public void notifyChange(ApolloService service, String what) {\r
+        if (hasInstances(service)) {\r
+            if (ApolloService.META_CHANGED.equals(what)\r
+                    || ApolloService.PLAYSTATE_CHANGED.equals(what)) {\r
+                performUpdate(service, null);\r
+            }\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Update all active widget instances by pushing changes\r
+     */\r
+    public void performUpdate(ApolloService service, int[] appWidgetIds) {\r
+        final RemoteViews views = new RemoteViews(service.getPackageName(),\r
+                R.layout.onebyone_app_widget);\r
+\r
+        // Set album art\r
+        AQuery aq = new AQuery(service);\r
+        Bitmap bitmap = aq.getCachedImage(ApolloUtils.getImageURL(service.getAlbumName(),\r
+                ALBUM_IMAGE, service));\r
+        if (bitmap != null) {\r
+            views.setViewVisibility(R.id.one_by_one_albumart, View.VISIBLE);\r
+            views.setImageViewBitmap(R.id.one_by_one_albumart, bitmap);\r
+        } else {\r
+            views.setViewVisibility(R.id.one_by_one_albumart, View.INVISIBLE);\r
+        }\r
+        // Set correct drawable for pause state\r
+        final boolean playing = service.isPlaying();\r
+\r
+        // Link actions buttons to intents\r
+        linkButtons(service, views, playing);\r
+\r
+        pushUpdate(service, appWidgetIds, views);\r
+    }\r
+\r
+    /**\r
+     * Link up various button actions using {@link PendingIntents}.\r
+     * \r
+     * @param playerActive True if player is active in background, which means\r
+     *            widget click will launch {@link MediaPlaybackActivity},\r
+     *            otherwise we launch {@link MusicBrowserActivity}.\r
+     */\r
+    private void linkButtons(Context context, RemoteViews views, boolean playerActive) {\r
+        // Connect up various buttons and touch events\r
+        Intent intent;\r
+        PendingIntent pendingIntent;\r
+\r
+        final ComponentName serviceName = new ComponentName(context, ApolloService.class);\r
+\r
+        intent = new Intent(ApolloService.TOGGLEPAUSE_ACTION);\r
+        intent.setComponent(serviceName);\r
+        pendingIntent = PendingIntent.getService(context, 0, intent, 0);\r
+        views.setOnClickPendingIntent(R.id.one_by_one_albumart, pendingIntent);\r
+\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/app/widgets/AppWidget41.java b/src/com/andrew/apollo/app/widgets/AppWidget41.java
new file mode 100644 (file)
index 0000000..be6b3bb
--- /dev/null
@@ -0,0 +1,196 @@
+/*\r
+ * Copyright (C) 2009 The Android Open Source Project\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.andrew.apollo.app.widgets;\r
+\r
+import android.app.PendingIntent;\r
+import android.appwidget.AppWidgetManager;\r
+import android.appwidget.AppWidgetProvider;\r
+import android.content.ComponentName;\r
+import android.content.Context;\r
+import android.content.Intent;\r
+import android.graphics.Bitmap;\r
+import android.view.View;\r
+import android.widget.RemoteViews;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.activities.AudioPlayerHolder;\r
+import com.andrew.apollo.activities.MusicLibrary;\r
+import com.andrew.apollo.service.ApolloService;\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+import com.androidquery.AQuery;\r
+\r
+/**\r
+ * Simple widget to show currently playing album art along with play/pause and\r
+ * next track buttons.\r
+ */\r
+\r
+public class AppWidget41 extends AppWidgetProvider implements Constants {\r
+\r
+    public static final String CMDAPPWIDGETUPDATE = "appwidgetupdate4x1";\r
+\r
+    private static AppWidget41 sInstance;\r
+\r
+    public static synchronized AppWidget41 getInstance() {\r
+        if (sInstance == null) {\r
+            sInstance = new AppWidget41();\r
+        }\r
+        return sInstance;\r
+    }\r
+\r
+    @Override\r
+    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {\r
+        defaultAppWidget(context, appWidgetIds);\r
+\r
+        // Send broadcast intent to any running ApolloService so it can\r
+        // wrap around with an immediate update.\r
+        Intent updateIntent = new Intent(ApolloService.SERVICECMD);\r
+        updateIntent.putExtra(ApolloService.CMDNAME, AppWidget41.CMDAPPWIDGETUPDATE);\r
+        updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);\r
+        updateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);\r
+        context.sendBroadcast(updateIntent);\r
+    }\r
+\r
+    /**\r
+     * Initialize given widgets to default state, where we launch Music on\r
+     * default click and hide actions if service not running.\r
+     */\r
+    private void defaultAppWidget(Context context, int[] appWidgetIds) {\r
+        final RemoteViews views = new RemoteViews(context.getPackageName(),\r
+                R.layout.fourbyone_app_widget);\r
+\r
+        linkButtons(context, views, false /* not playing */);\r
+        pushUpdate(context, appWidgetIds, views);\r
+    }\r
+\r
+    private void pushUpdate(Context context, int[] appWidgetIds, RemoteViews views) {\r
+        // Update specific list of appWidgetIds if given, otherwise default to\r
+        // all\r
+        final AppWidgetManager gm = AppWidgetManager.getInstance(context);\r
+        if (appWidgetIds != null) {\r
+            gm.updateAppWidget(appWidgetIds, views);\r
+        } else {\r
+            gm.updateAppWidget(new ComponentName(context, this.getClass()), views);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Check against {@link AppWidgetManager} if there are any instances of this\r
+     * widget.\r
+     */\r
+    private boolean hasInstances(Context context) {\r
+        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);\r
+        int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, this\r
+                .getClass()));\r
+        return (appWidgetIds.length > 0);\r
+    }\r
+\r
+    /**\r
+     * Handle a change notification coming over from {@link ApolloService}\r
+     */\r
+    public void notifyChange(ApolloService service, String what) {\r
+        if (hasInstances(service)) {\r
+            if (ApolloService.META_CHANGED.equals(what)\r
+                    || ApolloService.PLAYSTATE_CHANGED.equals(what)) {\r
+                performUpdate(service, null);\r
+            }\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Update all active widget instances by pushing changes\r
+     */\r
+    public void performUpdate(ApolloService service, int[] appWidgetIds) {\r
+        final RemoteViews views = new RemoteViews(service.getPackageName(),\r
+                R.layout.fourbyone_app_widget);\r
+\r
+        CharSequence titleName = service.getTrackName();\r
+        CharSequence artistName = service.getArtistName();\r
+\r
+        views.setTextViewText(R.id.four_by_one_title, titleName);\r
+        views.setTextViewText(R.id.four_by_one_artist, artistName);\r
+        // Set album art\r
+        AQuery aq = new AQuery(service);\r
+        Bitmap bitmap = aq.getCachedImage(ApolloUtils.getImageURL(service.getAlbumName(),\r
+                ALBUM_IMAGE, service));\r
+        if (bitmap != null) {\r
+            views.setViewVisibility(R.id.four_by_one_albumart, View.VISIBLE);\r
+            views.setViewVisibility(R.id.four_by_one_control_prev, View.GONE);\r
+            views.setImageViewBitmap(R.id.four_by_one_albumart, bitmap);\r
+        } else {\r
+            views.setViewVisibility(R.id.four_by_one_control_prev, View.VISIBLE);\r
+            views.setViewVisibility(R.id.four_by_one_albumart, View.GONE);\r
+        }\r
+\r
+        // Set correct drawable for pause state\r
+        final boolean playing = service.isPlaying();\r
+        if (playing) {\r
+            views.setImageViewResource(R.id.four_by_one_control_play,\r
+                    R.drawable.apollo_holo_light_pause);\r
+        } else {\r
+            views.setImageViewResource(R.id.four_by_one_control_play,\r
+                    R.drawable.apollo_holo_light_play);\r
+        }\r
+\r
+        // Link actions buttons to intents\r
+        linkButtons(service, views, playing);\r
+\r
+        pushUpdate(service, appWidgetIds, views);\r
+    }\r
+\r
+    /**\r
+     * Link up various button actions using {@link PendingIntents}.\r
+     * \r
+     * @param playerActive True if player is active in background, which means\r
+     *            widget click will launch {@link MediaPlaybackActivity},\r
+     *            otherwise we launch {@link MusicBrowserActivity}.\r
+     */\r
+    private void linkButtons(Context context, RemoteViews views, boolean playerActive) {\r
+        // Connect up various buttons and touch events\r
+        Intent intent;\r
+        PendingIntent pendingIntent;\r
+\r
+        final ComponentName serviceName = new ComponentName(context, ApolloService.class);\r
+\r
+        if (playerActive) {\r
+            intent = new Intent(context, AudioPlayerHolder.class);\r
+            pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);\r
+            views.setOnClickPendingIntent(R.id.four_by_one_album_appwidget, pendingIntent);\r
+            views.setOnClickPendingIntent(R.id.four_by_one_albumart, pendingIntent);\r
+        } else {\r
+            intent = new Intent(context, MusicLibrary.class);\r
+            pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);\r
+            views.setOnClickPendingIntent(R.id.four_by_one_album_appwidget, pendingIntent);\r
+            views.setOnClickPendingIntent(R.id.four_by_one_albumart, pendingIntent);\r
+        }\r
+\r
+        intent = new Intent(ApolloService.TOGGLEPAUSE_ACTION);\r
+        intent.setComponent(serviceName);\r
+        pendingIntent = PendingIntent.getService(context, 0, intent, 0);\r
+        views.setOnClickPendingIntent(R.id.four_by_one_control_play, pendingIntent);\r
+\r
+        intent = new Intent(ApolloService.NEXT_ACTION);\r
+        intent.setComponent(serviceName);\r
+        pendingIntent = PendingIntent.getService(context, 0, intent, 0);\r
+        views.setOnClickPendingIntent(R.id.four_by_one_control_next, pendingIntent);\r
+\r
+        intent = new Intent(ApolloService.PREVIOUS_ACTION);\r
+        intent.setComponent(serviceName);\r
+        pendingIntent = PendingIntent.getService(context, 0, intent, 0);\r
+        views.setOnClickPendingIntent(R.id.four_by_one_control_prev, pendingIntent);\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/app/widgets/AppWidget42.java b/src/com/andrew/apollo/app/widgets/AppWidget42.java
new file mode 100644 (file)
index 0000000..cda0719
--- /dev/null
@@ -0,0 +1,240 @@
+/*\r
+ * Copyright (C) 2009 The Android Open Source Project\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.andrew.apollo.app.widgets;\r
+\r
+import android.app.PendingIntent;\r
+import android.appwidget.AppWidgetManager;\r
+import android.appwidget.AppWidgetProvider;\r
+import android.content.ComponentName;\r
+import android.content.Context;\r
+import android.content.Intent;\r
+import android.graphics.Bitmap;\r
+import android.view.View;\r
+import android.widget.RemoteViews;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.activities.AudioPlayerHolder;\r
+import com.andrew.apollo.activities.MusicLibrary;\r
+import com.andrew.apollo.service.ApolloService;\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+import com.androidquery.AQuery;\r
+\r
+/**\r
+ * Simple widget to show currently playing album art along with play/pause and\r
+ * next track buttons.\r
+ */\r
+public class AppWidget42 extends AppWidgetProvider implements Constants {\r
+\r
+    public static final String CMDAPPWIDGETUPDATE = "appwidgetupdate4x2";\r
+\r
+    private static AppWidget42 sInstance;\r
+\r
+    public static synchronized AppWidget42 getInstance() {\r
+        if (sInstance == null) {\r
+            sInstance = new AppWidget42();\r
+        }\r
+        return sInstance;\r
+    }\r
+\r
+    @Override\r
+    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {\r
+        defaultAppWidget(context, appWidgetIds);\r
+\r
+        // Send broadcast intent to any running ApolloService so it can\r
+        // wrap around with an immediate update.\r
+        Intent updateIntent = new Intent(ApolloService.SERVICECMD);\r
+        updateIntent.putExtra(ApolloService.CMDNAME, AppWidget42.CMDAPPWIDGETUPDATE);\r
+        updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);\r
+        updateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);\r
+        context.sendBroadcast(updateIntent);\r
+    }\r
+\r
+    /**\r
+     * Initialize given widgets to default state, where we launch Music on\r
+     * default click and hide actions if service not running.\r
+     */\r
+    private void defaultAppWidget(Context context, int[] appWidgetIds) {\r
+        final RemoteViews views = new RemoteViews(context.getPackageName(),\r
+                R.layout.fourbytwo_app_widget);\r
+\r
+        linkButtons(context, views, false /* not playing */);\r
+        pushUpdate(context, appWidgetIds, views);\r
+    }\r
+\r
+    private void pushUpdate(Context context, int[] appWidgetIds, RemoteViews views) {\r
+        // Update specific list of appWidgetIds if given, otherwise default to\r
+        // all\r
+        final AppWidgetManager gm = AppWidgetManager.getInstance(context);\r
+        if (appWidgetIds != null) {\r
+            gm.updateAppWidget(appWidgetIds, views);\r
+        } else {\r
+            gm.updateAppWidget(new ComponentName(context, this.getClass()), views);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Check against {@link AppWidgetManager} if there are any instances of this\r
+     * widget.\r
+     */\r
+    private boolean hasInstances(Context context) {\r
+        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);\r
+        int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, this\r
+                .getClass()));\r
+        return (appWidgetIds.length > 0);\r
+    }\r
+\r
+    /**\r
+     * Handle a change notification coming over from {@link ApolloService}\r
+     */\r
+    public void notifyChange(ApolloService service, String what) {\r
+        if (hasInstances(service)) {\r
+            if (ApolloService.META_CHANGED.equals(what)\r
+                    || ApolloService.PLAYSTATE_CHANGED.equals(what)\r
+                    || ApolloService.REPEATMODE_CHANGED.equals(what)\r
+                    || ApolloService.SHUFFLEMODE_CHANGED.equals(what)) {\r
+                performUpdate(service, null);\r
+            }\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Update all active widget instances by pushing changes\r
+     */\r
+    public void performUpdate(ApolloService service, int[] appWidgetIds) {\r
+        final RemoteViews views = new RemoteViews(service.getPackageName(),\r
+                R.layout.fourbytwo_app_widget);\r
+\r
+        CharSequence artistName = service.getArtistName();\r
+        CharSequence albumName = service.getAlbumName();\r
+        CharSequence trackName = service.getTrackName();\r
+        views.setTextViewText(R.id.four_by_two_artistname, artistName);\r
+        views.setTextViewText(R.id.four_by_two_albumname, albumName);\r
+        views.setTextViewText(R.id.four_by_two_trackname, trackName);\r
+\r
+        // Set album art\r
+        AQuery aq = new AQuery(service);\r
+        Bitmap bitmap = aq.getCachedImage(ApolloUtils.getImageURL(service.getAlbumName(),\r
+                ALBUM_IMAGE, service));\r
+        if (bitmap != null) {\r
+            views.setViewVisibility(R.id.four_by_two_albumart, View.VISIBLE);\r
+            views.setImageViewBitmap(R.id.four_by_two_albumart, bitmap);\r
+        } else {\r
+            views.setViewVisibility(R.id.four_by_two_albumart, View.GONE);\r
+        }\r
+\r
+        // Set correct drawable for pause state\r
+        final boolean playing = service.isPlaying();\r
+        if (playing) {\r
+            views.setImageViewResource(R.id.four_by_two_control_play,\r
+                    R.drawable.apollo_holo_light_pause);\r
+        } else {\r
+            views.setImageViewResource(R.id.four_by_two_control_play,\r
+                    R.drawable.apollo_holo_light_play);\r
+        }\r
+\r
+        // Set correct drawable for repeat state\r
+        switch (service.getRepeatMode()) {\r
+            case ApolloService.REPEAT_ALL:\r
+                views.setImageViewResource(R.id.four_by_two_control_repeat,\r
+                        R.drawable.apollo_holo_light_repeat_all);\r
+                break;\r
+            case ApolloService.REPEAT_CURRENT:\r
+                views.setImageViewResource(R.id.four_by_two_control_repeat,\r
+                        R.drawable.apollo_holo_light_repeat_one);\r
+                break;\r
+            default:\r
+                views.setImageViewResource(R.id.four_by_two_control_repeat,\r
+                        R.drawable.apollo_holo_light_repeat_normal);\r
+                break;\r
+        }\r
+\r
+        // Set correct drawable for shuffle state\r
+        switch (service.getShuffleMode()) {\r
+            case ApolloService.SHUFFLE_NONE:\r
+                views.setImageViewResource(R.id.four_by_two_control_shuffle,\r
+                        R.drawable.apollo_holo_light_shuffle_normal);\r
+                break;\r
+            case ApolloService.SHUFFLE_AUTO:\r
+                views.setImageViewResource(R.id.four_by_two_control_shuffle,\r
+                        R.drawable.apollo_holo_light_shuffle_on);\r
+                break;\r
+            default:\r
+                views.setImageViewResource(R.id.four_by_two_control_shuffle,\r
+                        R.drawable.apollo_holo_light_shuffle_on);\r
+                break;\r
+        }\r
+        // Link actions buttons to intents\r
+        linkButtons(service, views, playing);\r
+\r
+        pushUpdate(service, appWidgetIds, views);\r
+\r
+    }\r
+\r
+    /**\r
+     * Link up various button actions using {@link PendingIntents}.\r
+     * \r
+     * @param playerActive True if player is active in background, which means\r
+     *            widget click will launch {@link MediaPlaybackActivity},\r
+     *            otherwise we launch {@link MusicBrowserActivity}.\r
+     */\r
+    private void linkButtons(Context context, RemoteViews views, boolean playerActive) {\r
+\r
+        // Connect up various buttons and touch events\r
+        Intent intent;\r
+        PendingIntent pendingIntent;\r
+\r
+        final ComponentName serviceName = new ComponentName(context, ApolloService.class);\r
+\r
+        if (playerActive) {\r
+            intent = new Intent(context, AudioPlayerHolder.class);\r
+            pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);\r
+            views.setOnClickPendingIntent(R.id.four_by_two_albumart, pendingIntent);\r
+            views.setOnClickPendingIntent(R.id.four_by_two_info, pendingIntent);\r
+        } else {\r
+            intent = new Intent(context, MusicLibrary.class);\r
+            pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);\r
+            views.setOnClickPendingIntent(R.id.four_by_two_albumart, pendingIntent);\r
+            views.setOnClickPendingIntent(R.id.four_by_two_info, pendingIntent);\r
+        }\r
+\r
+        intent = new Intent(ApolloService.TOGGLEPAUSE_ACTION);\r
+        intent.setComponent(serviceName);\r
+        pendingIntent = PendingIntent.getService(context, 0, intent, 0);\r
+        views.setOnClickPendingIntent(R.id.four_by_two_control_play, pendingIntent);\r
+\r
+        intent = new Intent(ApolloService.NEXT_ACTION);\r
+        intent.setComponent(serviceName);\r
+        pendingIntent = PendingIntent.getService(context, 0, intent, 0);\r
+        views.setOnClickPendingIntent(R.id.four_by_two_control_next, pendingIntent);\r
+\r
+        intent = new Intent(ApolloService.PREVIOUS_ACTION);\r
+        intent.setComponent(serviceName);\r
+        pendingIntent = PendingIntent.getService(context, 0, intent, 0);\r
+        views.setOnClickPendingIntent(R.id.four_by_two_control_prev, pendingIntent);\r
+\r
+        intent = new Intent(ApolloService.CYCLEREPEAT_ACTION);\r
+        intent.setComponent(serviceName);\r
+        pendingIntent = PendingIntent.getService(context, 0, intent, 0);\r
+        views.setOnClickPendingIntent(R.id.four_by_two_control_repeat, pendingIntent);\r
+\r
+        intent = new Intent(ApolloService.TOGGLESHUFFLE_ACTION);\r
+        intent.setComponent(serviceName);\r
+        pendingIntent = PendingIntent.getService(context, 0, intent, 0);\r
+        views.setOnClickPendingIntent(R.id.four_by_two_control_shuffle, pendingIntent);\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/grid/fragments/AlbumsFragment.java b/src/com/andrew/apollo/grid/fragments/AlbumsFragment.java
new file mode 100644 (file)
index 0000000..ee5552d
--- /dev/null
@@ -0,0 +1,263 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.grid.fragments;\r
+\r
+import android.content.BroadcastReceiver;\r
+import android.content.Context;\r
+import android.content.Intent;\r
+import android.content.IntentFilter;\r
+import android.database.Cursor;\r
+import android.net.Uri;\r
+import android.os.AsyncTask;\r
+import android.os.Bundle;\r
+import android.provider.BaseColumns;\r
+import android.provider.MediaStore.Audio;\r
+import android.provider.MediaStore.Audio.AlbumColumns;\r
+import android.support.v4.app.Fragment;\r
+import android.support.v4.app.LoaderManager.LoaderCallbacks;\r
+import android.support.v4.content.CursorLoader;\r
+import android.support.v4.content.Loader;\r
+import android.view.ContextMenu;\r
+import android.view.ContextMenu.ContextMenuInfo;\r
+import android.view.LayoutInflater;\r
+import android.view.MenuItem;\r
+import android.view.View;\r
+import android.view.ViewGroup;\r
+import android.widget.AdapterView;\r
+import android.widget.AdapterView.OnItemClickListener;\r
+import android.widget.GridView;\r
+import android.widget.ImageView;\r
+import android.widget.TextView;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.activities.TracksBrowser;\r
+import com.andrew.apollo.adapters.AlbumAdapter;\r
+import com.andrew.apollo.service.ApolloService;\r
+import com.andrew.apollo.tasks.GetCachedImages;\r
+import com.andrew.apollo.tasks.LastfmGetAlbumImages;\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+import com.andrew.apollo.utils.MusicUtils;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ */\r
+public class AlbumsFragment extends Fragment implements LoaderCallbacks<Cursor>, Constants,\r
+        OnItemClickListener {\r
+\r
+    // Adapter\r
+    private AlbumAdapter mAlbumAdapter;\r
+\r
+    // GridView\r
+    private GridView mGridView;\r
+\r
+    // Cursor\r
+    private Cursor mCursor;\r
+\r
+    // Options\r
+    private final int PLAY_SELECTION = 3;\r
+\r
+    private final int ADD_TO_PLAYLIST = 4;\r
+\r
+    private final int SEARCH = 5;\r
+\r
+    // Album ID\r
+    private String mCurrentAlbumId;\r
+\r
+    // Audio columns\r
+    public static int mAlbumIdIndex, mAlbumNameIndex, mArtistNameIndex;\r
+\r
+    // Bundle\r
+    public AlbumsFragment() {\r
+    }\r
+\r
+    public AlbumsFragment(Bundle args) {\r
+        setArguments(args);\r
+    }\r
+\r
+    @Override\r
+    public void onActivityCreated(Bundle savedInstanceState) {\r
+        super.onActivityCreated(savedInstanceState);\r
+        // AlbumAdapter\r
+        mAlbumAdapter = new AlbumAdapter(getActivity(), R.layout.gridview_items, null,\r
+                new String[] {}, new int[] {}, 0);\r
+        mGridView.setOnCreateContextMenuListener(this);\r
+        mGridView.setOnItemClickListener(this);\r
+        mGridView.setTextFilterEnabled(true);\r
+        mGridView.setAdapter(mAlbumAdapter);\r
+\r
+        // Important!\r
+        getLoaderManager().initLoader(0, null, this);\r
+    }\r
+\r
+    @Override\r
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\r
+        View root = inflater.inflate(R.layout.gridview, container, false);\r
+        mGridView = (GridView)root.findViewById(R.id.gridview);\r
+        return root;\r
+    }\r
+\r
+    @Override\r
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {\r
+        String[] projection = {\r
+                BaseColumns._ID, AlbumColumns.ALBUM, AlbumColumns.ARTIST, AlbumColumns.ALBUM_ART\r
+        };\r
+        Uri uri = Audio.Albums.EXTERNAL_CONTENT_URI;\r
+        String sortOrder = Audio.Albums.DEFAULT_SORT_ORDER;\r
+        return new CursorLoader(getActivity(), uri, projection, null, null, sortOrder);\r
+    }\r
+\r
+    @Override\r
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {\r
+        // Check for database errors\r
+        if (data == null) {\r
+            return;\r
+        }\r
+\r
+        mAlbumIdIndex = data.getColumnIndexOrThrow(BaseColumns._ID);\r
+        mAlbumNameIndex = data.getColumnIndexOrThrow(AlbumColumns.ALBUM);\r
+        mArtistNameIndex = data.getColumnIndexOrThrow(AlbumColumns.ARTIST);\r
+        mAlbumAdapter.changeCursor(data);\r
+        mCursor = data;\r
+    }\r
+\r
+    @Override\r
+    public void onLoaderReset(Loader<Cursor> loader) {\r
+        if (mAlbumAdapter != null)\r
+            mAlbumAdapter.changeCursor(null);\r
+    }\r
+\r
+    @Override\r
+    public void onSaveInstanceState(Bundle outState) {\r
+        outState.putAll(getArguments() != null ? getArguments() : new Bundle());\r
+        super.onSaveInstanceState(outState);\r
+    }\r
+\r
+    @Override\r
+    public void onItemClick(AdapterView<?> parent, View v, int position, long id) {\r
+        tracksBrowser(id);\r
+    }\r
+\r
+    /**\r
+     * Update the list as needed\r
+     */\r
+    private final BroadcastReceiver mMediaStatusReceiver = new BroadcastReceiver() {\r
+\r
+        @Override\r
+        public void onReceive(Context context, Intent intent) {\r
+            if (mGridView != null) {\r
+                mGridView.invalidateViews();\r
+            }\r
+        }\r
+\r
+    };\r
+\r
+    @Override\r
+    public void onStart() {\r
+        super.onStart();\r
+        IntentFilter filter = new IntentFilter();\r
+        filter.addAction(ApolloService.META_CHANGED);\r
+        filter.addAction(ApolloService.PLAYSTATE_CHANGED);\r
+        getActivity().registerReceiver(mMediaStatusReceiver, filter);\r
+    }\r
+\r
+    @Override\r
+    public void onStop() {\r
+        getActivity().unregisterReceiver(mMediaStatusReceiver);\r
+        super.onStop();\r
+    }\r
+\r
+    /**\r
+     * @param index\r
+     * @param id\r
+     */\r
+    private void tracksBrowser(long id) {\r
+\r
+        String artistName = mCursor.getString(mArtistNameIndex);\r
+        String albumName = mCursor.getString(mAlbumNameIndex);\r
+\r
+        Bundle bundle = new Bundle();\r
+        bundle.putString(MIME_TYPE, Audio.Albums.CONTENT_TYPE);\r
+        bundle.putString(ARTIST_KEY, artistName);\r
+        bundle.putString(ALBUM_KEY, albumName);\r
+        bundle.putLong(BaseColumns._ID, id);\r
+\r
+        Intent intent = new Intent(Intent.ACTION_VIEW);\r
+        intent.setClass(getActivity(), TracksBrowser.class);\r
+        intent.putExtras(bundle);\r
+        getActivity().startActivity(intent);\r
+    }\r
+\r
+    @Override\r
+    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {\r
+        menu.add(0, PLAY_SELECTION, 0, getResources().getString(R.string.play_all));\r
+        menu.add(0, ADD_TO_PLAYLIST, 0, getResources().getString(R.string.add_to_playlist));\r
+        menu.add(0, SEARCH, 0, getResources().getString(R.string.search));\r
+\r
+        mCurrentAlbumId = mCursor.getString(mCursor.getColumnIndexOrThrow(BaseColumns._ID));\r
+\r
+        menu.setHeaderView(setHeaderLayout());\r
+        super.onCreateContextMenu(menu, v, menuInfo);\r
+    }\r
+\r
+    @Override\r
+    public boolean onContextItemSelected(MenuItem item) {\r
+        switch (item.getItemId()) {\r
+            case PLAY_SELECTION: {\r
+                long[] list = MusicUtils.getSongListForAlbum(getActivity(),\r
+                        Long.parseLong(mCurrentAlbumId));\r
+                MusicUtils.playAll(getActivity(), list, 0);\r
+                break;\r
+            }\r
+            case ADD_TO_PLAYLIST: {\r
+                Intent intent = new Intent(INTENT_ADD_TO_PLAYLIST);\r
+                long[] list = MusicUtils.getSongListForAlbum(getActivity(),\r
+                        Long.parseLong(mCurrentAlbumId));\r
+                intent.putExtra(INTENT_PLAYLIST_LIST, list);\r
+                getActivity().startActivity(intent);\r
+                break;\r
+            }\r
+            case SEARCH: {\r
+                MusicUtils.doSearch(getActivity(), mCursor, mAlbumNameIndex);\r
+                break;\r
+            }\r
+            default:\r
+                break;\r
+        }\r
+        return super.onContextItemSelected(item);\r
+    }\r
+\r
+    /**\r
+     * @return A custom ContextMenu header\r
+     */\r
+    public View setHeaderLayout() {\r
+        // Get album name\r
+        String albumName = mCursor.getString(mAlbumNameIndex);\r
+        // Get artist name\r
+        String artistName = mCursor.getString(mArtistNameIndex);\r
+\r
+        // Inflate the header View\r
+        LayoutInflater inflater = getActivity().getLayoutInflater();\r
+        View header = inflater.inflate(R.layout.context_menu_header, null, false);\r
+\r
+        // Artist image\r
+        ImageView headerImage = (ImageView)header.findViewById(R.id.header_image);\r
+\r
+        // Only download images we don't already have\r
+        if (ApolloUtils.getImageURL(albumName, ALBUM_IMAGE, getActivity()) == null)\r
+            new LastfmGetAlbumImages(getActivity(), null, 0).executeOnExecutor(\r
+                    AsyncTask.THREAD_POOL_EXECUTOR, artistName, albumName);\r
+\r
+        // Get and set cached image\r
+        new GetCachedImages(getActivity(), 1, headerImage).executeOnExecutor(\r
+                AsyncTask.THREAD_POOL_EXECUTOR, albumName);\r
+\r
+        // Set artist name\r
+        TextView headerText = (TextView)header.findViewById(R.id.header_text);\r
+        headerText.setText(albumName);\r
+        headerText.setBackgroundColor(getResources().getColor(R.color.transparent_black));\r
+        return header;\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/grid/fragments/ArtistsFragment.java b/src/com/andrew/apollo/grid/fragments/ArtistsFragment.java
new file mode 100644 (file)
index 0000000..06e3289
--- /dev/null
@@ -0,0 +1,272 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.grid.fragments;\r
+\r
+import android.content.BroadcastReceiver;\r
+import android.content.Context;\r
+import android.content.Intent;\r
+import android.content.IntentFilter;\r
+import android.database.Cursor;\r
+import android.net.Uri;\r
+import android.os.AsyncTask;\r
+import android.os.Bundle;\r
+import android.provider.BaseColumns;\r
+import android.provider.MediaStore.Audio;\r
+import android.provider.MediaStore.Audio.ArtistColumns;\r
+import android.support.v4.app.Fragment;\r
+import android.support.v4.app.LoaderManager.LoaderCallbacks;\r
+import android.support.v4.content.CursorLoader;\r
+import android.support.v4.content.Loader;\r
+import android.view.ContextMenu;\r
+import android.view.ContextMenu.ContextMenuInfo;\r
+import android.view.LayoutInflater;\r
+import android.view.MenuItem;\r
+import android.view.View;\r
+import android.view.ViewGroup;\r
+import android.widget.AdapterView;\r
+import android.widget.AdapterView.OnItemClickListener;\r
+import android.widget.GridView;\r
+import android.widget.ImageView;\r
+import android.widget.TextView;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.activities.TracksBrowser;\r
+import com.andrew.apollo.adapters.ArtistAdapter;\r
+import com.andrew.apollo.service.ApolloService;\r
+import com.andrew.apollo.tasks.GetCachedImages;\r
+import com.andrew.apollo.tasks.LastfmGetArtistImagesOriginal;\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+import com.andrew.apollo.utils.MusicUtils;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ * @Note This is the first tab\r
+ */\r
+public class ArtistsFragment extends Fragment implements LoaderCallbacks<Cursor>,\r
+        OnItemClickListener, Constants {\r
+\r
+    // Adapter\r
+    private ArtistAdapter mArtistAdapter;\r
+\r
+    // GridView\r
+    private GridView mGridView;\r
+\r
+    // Cursor\r
+    private Cursor mCursor;\r
+\r
+    // Options\r
+    private final int PLAY_SELECTION = 0;\r
+\r
+    private final int ADD_TO_PLAYLIST = 1;\r
+\r
+    private final int SEARCH = 2;\r
+\r
+    // Artist ID\r
+    private String mCurrentArtistId;\r
+\r
+    // Album ID\r
+    private String mCurrentAlbumId;\r
+\r
+    // Audio columns\r
+    public static int mArtistIdIndex, mArtistNameIndex, mArtistNumAlbumsIndex;\r
+\r
+    public ArtistsFragment() {\r
+    }\r
+\r
+    public ArtistsFragment(Bundle bundle) {\r
+        setArguments(bundle);\r
+    }\r
+\r
+    @Override\r
+    public void onActivityCreated(Bundle savedInstanceState) {\r
+        super.onActivityCreated(savedInstanceState);\r
+        // ArtistAdapter\r
+        mArtistAdapter = new ArtistAdapter(getActivity(), R.layout.gridview_items, null,\r
+                new String[] {}, new int[] {}, 0);\r
+        mGridView.setOnCreateContextMenuListener(this);\r
+        mGridView.setOnItemClickListener(this);\r
+        mGridView.setAdapter(mArtistAdapter);\r
+        mGridView.setTextFilterEnabled(true);\r
+\r
+        // Important!\r
+        getLoaderManager().initLoader(0, null, this);\r
+    }\r
+\r
+    @Override\r
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\r
+        View root = inflater.inflate(R.layout.gridview, container, false);\r
+        mGridView = ((GridView)root.findViewById(R.id.gridview));\r
+        return root;\r
+    }\r
+\r
+    @Override\r
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {\r
+        String[] projection = {\r
+                BaseColumns._ID, ArtistColumns.ARTIST, ArtistColumns.NUMBER_OF_ALBUMS\r
+        };\r
+        Uri uri = Audio.Artists.EXTERNAL_CONTENT_URI;\r
+        String sortOrder = Audio.Artists.DEFAULT_SORT_ORDER;\r
+        return new CursorLoader(getActivity(), uri, projection, null, null, sortOrder);\r
+    }\r
+\r
+    @Override\r
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {\r
+        // Check for database errors\r
+        if (data == null) {\r
+            return;\r
+        }\r
+\r
+        mArtistIdIndex = data.getColumnIndexOrThrow(BaseColumns._ID);\r
+        mArtistNameIndex = data.getColumnIndexOrThrow(ArtistColumns.ARTIST);\r
+        mArtistNumAlbumsIndex = data.getColumnIndexOrThrow(ArtistColumns.NUMBER_OF_ALBUMS);\r
+        mArtistAdapter.changeCursor(data);\r
+        mCursor = data;\r
+    }\r
+\r
+    @Override\r
+    public void onLoaderReset(Loader<Cursor> loader) {\r
+        if (mArtistAdapter != null)\r
+            mArtistAdapter.changeCursor(null);\r
+    }\r
+\r
+    @Override\r
+    public void onSaveInstanceState(Bundle outState) {\r
+        outState.putAll(getArguments() != null ? getArguments() : new Bundle());\r
+        super.onSaveInstanceState(outState);\r
+    }\r
+\r
+    @Override\r
+    public void onItemClick(AdapterView<?> parent, View v, int position, long id) {\r
+        tracksBrowser(id);\r
+    }\r
+\r
+    /**\r
+     * @param id\r
+     */\r
+    private void tracksBrowser(long id) {\r
+\r
+        String artistName = mCursor.getString(mArtistNameIndex);\r
+\r
+        Bundle bundle = new Bundle();\r
+        bundle.putString(MIME_TYPE, Audio.Artists.CONTENT_TYPE);\r
+        bundle.putString(ARTIST_KEY, artistName);\r
+        bundle.putLong(BaseColumns._ID, id);\r
+\r
+        ApolloUtils.setArtistId(artistName, id, ARTIST_ID, getActivity());\r
+\r
+        Intent intent = new Intent(Intent.ACTION_VIEW);\r
+        intent.setClass(getActivity(), TracksBrowser.class);\r
+        intent.putExtras(bundle);\r
+        getActivity().startActivity(intent);\r
+    }\r
+\r
+    @Override\r
+    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {\r
+        menu.add(0, PLAY_SELECTION, 0, getResources().getString(R.string.play_all));\r
+        menu.add(0, ADD_TO_PLAYLIST, 0, getResources().getString(R.string.add_to_playlist));\r
+        menu.add(0, SEARCH, 0, getResources().getString(R.string.search));\r
+\r
+        mCurrentArtistId = mCursor.getString(mArtistIdIndex);\r
+        mCurrentAlbumId = mCursor.getString(mCursor.getColumnIndexOrThrow(BaseColumns._ID));\r
+\r
+        menu.setHeaderView(setHeaderLayout());\r
+        super.onCreateContextMenu(menu, v, menuInfo);\r
+    }\r
+\r
+    @Override\r
+    public boolean onContextItemSelected(MenuItem item) {\r
+        switch (item.getItemId()) {\r
+            case PLAY_SELECTION: {\r
+                long[] list = mCurrentArtistId != null ? MusicUtils.getSongListForArtist(\r
+                        getActivity(), Long.parseLong(mCurrentArtistId)) : MusicUtils\r
+                        .getSongListForAlbum(getActivity(), Long.parseLong(mCurrentAlbumId));\r
+                MusicUtils.playAll(getActivity(), list, 0);\r
+                break;\r
+            }\r
+            case ADD_TO_PLAYLIST: {\r
+                Intent intent = new Intent(INTENT_ADD_TO_PLAYLIST);\r
+                long[] list = mCurrentArtistId != null ? MusicUtils.getSongListForArtist(\r
+                        getActivity(), Long.parseLong(mCurrentArtistId)) : MusicUtils\r
+                        .getSongListForAlbum(getActivity(), Long.parseLong(mCurrentAlbumId));\r
+                intent.putExtra(INTENT_PLAYLIST_LIST, list);\r
+                getActivity().startActivity(intent);\r
+                break;\r
+            }\r
+            case SEARCH: {\r
+                MusicUtils.doSearch(getActivity(), mCursor, mArtistNameIndex);\r
+                break;\r
+            }\r
+            default:\r
+                break;\r
+        }\r
+        return super.onContextItemSelected(item);\r
+    }\r
+\r
+    /**\r
+     * Update the list as needed\r
+     */\r
+    private final BroadcastReceiver mMediaStatusReceiver = new BroadcastReceiver() {\r
+\r
+        @Override\r
+        public void onReceive(Context context, Intent intent) {\r
+            if (mGridView != null) {\r
+                mGridView.invalidateViews();\r
+            }\r
+        }\r
+\r
+    };\r
+\r
+    @Override\r
+    public void onStart() {\r
+        super.onStart();\r
+        IntentFilter filter = new IntentFilter();\r
+        filter.addAction(ApolloService.META_CHANGED);\r
+        filter.addAction(ApolloService.PLAYSTATE_CHANGED);\r
+        getActivity().registerReceiver(mMediaStatusReceiver, filter);\r
+    }\r
+\r
+    @Override\r
+    public void onStop() {\r
+        getActivity().unregisterReceiver(mMediaStatusReceiver);\r
+        super.onStop();\r
+    }\r
+\r
+    /**\r
+     * @return A custom ContextMenu header\r
+     */\r
+    public View setHeaderLayout() {\r
+        // Get artist name\r
+        final String artistName = mCursor.getString(mArtistNameIndex);\r
+\r
+        // Inflate the header View\r
+        LayoutInflater inflater = getActivity().getLayoutInflater();\r
+        View header = inflater.inflate(R.layout.context_menu_header, null, false);\r
+\r
+        // Artist image\r
+        final ImageView mHanderImage = (ImageView)header.findViewById(R.id.header_image);\r
+\r
+        mHanderImage.post(new Runnable() {\r
+\r
+            @Override\r
+            public void run() {\r
+                // Only download images we don't already have\r
+                if (ApolloUtils.getImageURL(artistName, ARTIST_IMAGE_ORIGINAL, getActivity()) == null)\r
+                    new LastfmGetArtistImagesOriginal(getActivity(), mHanderImage)\r
+                            .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, artistName);\r
+\r
+                // Get and set cached image\r
+                new GetCachedImages(getActivity(), 0, mHanderImage).executeOnExecutor(\r
+                        AsyncTask.THREAD_POOL_EXECUTOR, artistName);\r
+            }\r
+        });\r
+\r
+        // Set artist name\r
+        TextView headerText = (TextView)header.findViewById(R.id.header_text);\r
+        headerText.setText(artistName);\r
+        headerText.setBackgroundColor(getResources().getColor(R.color.transparent_black));\r
+        return header;\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/grid/fragments/QuickQueueFragment.java b/src/com/andrew/apollo/grid/fragments/QuickQueueFragment.java
new file mode 100644 (file)
index 0000000..157ee09
--- /dev/null
@@ -0,0 +1,270 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.grid.fragments;\r
+\r
+import android.content.BroadcastReceiver;\r
+import android.content.Context;\r
+import android.content.Intent;\r
+import android.content.IntentFilter;\r
+import android.database.Cursor;\r
+import android.net.Uri;\r
+import android.os.Bundle;\r
+import android.provider.BaseColumns;\r
+import android.provider.MediaStore.Audio;\r
+import android.provider.MediaStore.Audio.AudioColumns;\r
+import android.provider.MediaStore.MediaColumns;\r
+import android.support.v4.app.Fragment;\r
+import android.support.v4.app.LoaderManager.LoaderCallbacks;\r
+import android.support.v4.content.CursorLoader;\r
+import android.support.v4.content.Loader;\r
+import android.view.ContextMenu;\r
+import android.view.ContextMenu.ContextMenuInfo;\r
+import android.view.LayoutInflater;\r
+import android.view.MenuItem;\r
+import android.view.View;\r
+import android.view.ViewGroup;\r
+import android.widget.AdapterView;\r
+import android.widget.AdapterView.AdapterContextMenuInfo;\r
+import android.widget.AdapterView.OnItemClickListener;\r
+import android.widget.GridView;\r
+import android.widget.LinearLayout;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.NowPlayingCursor;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.adapters.QuickQueueAdapter;\r
+import com.andrew.apollo.service.ApolloService;\r
+import com.andrew.apollo.utils.MusicUtils;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ */\r
+public class QuickQueueFragment extends Fragment implements LoaderCallbacks<Cursor>,\r
+        OnItemClickListener, Constants {\r
+\r
+    // Adapter\r
+    private QuickQueueAdapter mQuickQueueAdapter;\r
+\r
+    // GridView\r
+    private GridView mGridView;\r
+\r
+    // Cursor\r
+    private Cursor mCursor;\r
+\r
+    // Selected position\r
+    private int mSelectedPosition;\r
+\r
+    // Options\r
+    private final int PLAY_SELECTION = 0;\r
+\r
+    private final int REMOVE = 1;\r
+\r
+    // Audio columns\r
+    public static int mTitleIndex, mAlbumIndex, mArtistIndex, mMediaIdIndex;\r
+\r
+    // Bundle\r
+    public QuickQueueFragment() {\r
+    }\r
+\r
+    public QuickQueueFragment(Bundle args) {\r
+        setArguments(args);\r
+    }\r
+\r
+    @Override\r
+    public void onActivityCreated(Bundle savedInstanceState) {\r
+        // Adapter\r
+        mQuickQueueAdapter = new QuickQueueAdapter(getActivity(), R.layout.quick_queue_items, null,\r
+                new String[] {}, new int[] {}, 0);\r
+        mGridView.setOnCreateContextMenuListener(this);\r
+        mGridView.setOnItemClickListener(this);\r
+        mGridView.setAdapter(mQuickQueueAdapter);\r
+\r
+        // Important!\r
+        getLoaderManager().initLoader(0, null, this);\r
+        super.onActivityCreated(savedInstanceState);\r
+    }\r
+\r
+    @Override\r
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\r
+        View root = inflater.inflate(R.layout.quick_queue, container, false);\r
+        mGridView = (GridView)root.findViewById(R.id.gridview);\r
+        mGridView.setNumColumns(1);\r
+\r
+        LinearLayout mQueueHolder = (LinearLayout)root.findViewById(R.id.quick_queue_holder);\r
+        mQueueHolder.setBackgroundColor(getResources().getColor(R.color.transparent_black));\r
+        return root;\r
+    }\r
+\r
+    @Override\r
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {\r
+        String[] projection = new String[] {\r
+                BaseColumns._ID, MediaColumns.TITLE, AudioColumns.ALBUM, AudioColumns.ARTIST,\r
+        };\r
+        StringBuilder selection = new StringBuilder();\r
+        Uri uri = Audio.Media.EXTERNAL_CONTENT_URI;\r
+        String sortOrder = Audio.Media.DEFAULT_SORT_ORDER;\r
+        uri = Audio.Media.EXTERNAL_CONTENT_URI;\r
+        long[] mNowPlaying = MusicUtils.getQueue();\r
+        if (mNowPlaying.length == 0)\r
+            return null;\r
+        selection = new StringBuilder();\r
+        selection.append(BaseColumns._ID + " IN (");\r
+        if (mNowPlaying == null || mNowPlaying.length <= 0)\r
+            return null;\r
+        for (long queue_id : mNowPlaying) {\r
+            selection.append(queue_id + ",");\r
+        }\r
+        selection.deleteCharAt(selection.length() - 1);\r
+        selection.append(")");\r
+\r
+        return new CursorLoader(getActivity(), uri, projection, selection.toString(), null,\r
+                sortOrder);\r
+    }\r
+\r
+    @Override\r
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {\r
+        // Check for database errors\r
+        if (data == null) {\r
+            return;\r
+        }\r
+\r
+        mMediaIdIndex = data.getColumnIndexOrThrow(BaseColumns._ID);\r
+        mTitleIndex = data.getColumnIndexOrThrow(MediaColumns.TITLE);\r
+        mArtistIndex = data.getColumnIndexOrThrow(AudioColumns.ARTIST);\r
+        mAlbumIndex = data.getColumnIndexOrThrow(AudioColumns.ALBUM);\r
+        mQuickQueueAdapter.changeCursor(data);\r
+        mCursor = data;\r
+    }\r
+\r
+    @Override\r
+    public void onLoaderReset(Loader<Cursor> loader) {\r
+        if (mQuickQueueAdapter != null)\r
+            mQuickQueueAdapter.changeCursor(null);\r
+    }\r
+\r
+    @Override\r
+    public void onSaveInstanceState(Bundle outState) {\r
+        outState.putAll(getArguments() != null ? getArguments() : new Bundle());\r
+        super.onSaveInstanceState(outState);\r
+    }\r
+\r
+    @Override\r
+    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {\r
+        menu.add(0, PLAY_SELECTION, 0, getResources().getString(R.string.play_all));\r
+        menu.add(0, REMOVE, 0, getResources().getString(R.string.remove));\r
+\r
+        AdapterContextMenuInfo mi = (AdapterContextMenuInfo)menuInfo;\r
+        mSelectedPosition = mi.position;\r
+        mCursor.moveToPosition(mSelectedPosition);\r
+\r
+        String title = mCursor.getString(mTitleIndex);\r
+        menu.setHeaderTitle(title);\r
+        super.onCreateContextMenu(menu, v, menuInfo);\r
+    }\r
+\r
+    @Override\r
+    public boolean onContextItemSelected(MenuItem item) {\r
+        switch (item.getItemId()) {\r
+            case PLAY_SELECTION:\r
+                int position = mSelectedPosition;\r
+                MusicUtils.playAll(getActivity(), mCursor, position);\r
+                getActivity().finish();\r
+                break;\r
+            case REMOVE:\r
+                removePlaylistItem(mSelectedPosition);\r
+                break;\r
+            default:\r
+                break;\r
+        }\r
+        return super.onContextItemSelected(item);\r
+    }\r
+\r
+    @Override\r
+    public void onItemClick(AdapterView<?> parent, View v, int position, long id) {\r
+        if (mCursor instanceof NowPlayingCursor) {\r
+            if (MusicUtils.mService != null) {\r
+                MusicUtils.setQueuePosition(position);\r
+            }\r
+        }\r
+        MusicUtils.playAll(getActivity(), mCursor, position);\r
+        getActivity().finish();\r
+    }\r
+\r
+    /**\r
+     * @param which\r
+     */\r
+    private void removePlaylistItem(int which) {\r
+        mCursor.moveToPosition(which);\r
+        long id = mCursor.getLong(mMediaIdIndex);\r
+        MusicUtils.removeTrack(id);\r
+        reloadQueueCursor();\r
+        mGridView.invalidateViews();\r
+    }\r
+\r
+    /**\r
+     * Reload the queue after we remove a track\r
+     */\r
+    private void reloadQueueCursor() {\r
+        String[] projection = new String[] {\r
+                BaseColumns._ID, MediaColumns.TITLE, AudioColumns.ALBUM, AudioColumns.ARTIST,\r
+        };\r
+        StringBuilder selection = new StringBuilder();\r
+        Uri uri = Audio.Media.EXTERNAL_CONTENT_URI;\r
+        String sortOrder = Audio.Media.DEFAULT_SORT_ORDER;\r
+        uri = Audio.Media.EXTERNAL_CONTENT_URI;\r
+        long[] mNowPlaying = MusicUtils.getQueue();\r
+        if (mNowPlaying.length == 0)\r
+            return;\r
+        selection = new StringBuilder();\r
+        selection.append(BaseColumns._ID + " IN (");\r
+        if (mNowPlaying == null || mNowPlaying.length <= 0)\r
+            return;\r
+        for (long queue_id : mNowPlaying) {\r
+            selection.append(queue_id + ",");\r
+        }\r
+        selection.deleteCharAt(selection.length() - 1);\r
+        selection.append(")");\r
+\r
+        mCursor = MusicUtils.query(getActivity(), uri, projection, selection.toString(), null,\r
+                sortOrder);\r
+        mQuickQueueAdapter.changeCursor(mCursor);\r
+    }\r
+\r
+    /**\r
+     * Update the list as needed\r
+     */\r
+    private final BroadcastReceiver mMediaStatusReceiver = new BroadcastReceiver() {\r
+\r
+        @Override\r
+        public void onReceive(Context context, Intent intent) {\r
+            if (mGridView != null) {\r
+                mGridView.invalidateViews();\r
+                // Scroll to the currently playing track in the queue\r
+                mGridView.postDelayed(new Runnable() {\r
+                    @Override\r
+                    public void run() {\r
+                        mGridView.setSelection(MusicUtils.getQueuePosition());\r
+                    }\r
+                }, 100);\r
+            }\r
+        }\r
+\r
+    };\r
+\r
+    @Override\r
+    public void onStart() {\r
+        super.onStart();\r
+        IntentFilter filter = new IntentFilter();\r
+        filter.addAction(ApolloService.META_CHANGED);\r
+        filter.addAction(ApolloService.QUEUE_CHANGED);\r
+        getActivity().registerReceiver(mMediaStatusReceiver, filter);\r
+    }\r
+\r
+    @Override\r
+    public void onStop() {\r
+        getActivity().unregisterReceiver(mMediaStatusReceiver);\r
+        super.onStop();\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/lastfm/api/Album.java b/src/com/andrew/apollo/lastfm/api/Album.java
new file mode 100644 (file)
index 0000000..e5dab7a
--- /dev/null
@@ -0,0 +1,114 @@
+/*\r
+ * Copyright (c) 2012, the Last.fm Java Project and Committers\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use of this software in source and binary forms, with or without modification, are\r
+ * permitted provided that the following conditions are met:\r
+ *\r
+ * - Redistributions of source code must retain the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer.\r
+ *\r
+ * - Redistributions in binary form must reproduce the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer in the documentation and/or other\r
+ *   materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED\r
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\r
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\r
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR\r
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+package com.andrew.apollo.lastfm.api;\r
+\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+\r
+import com.andrew.apollo.utils.DomElement;\r
+import com.andrew.apollo.utils.MapUtilities;\r
+import com.andrew.apollo.utils.StringUtilities;\r
+\r
+\r
+/**\r
+ * Wrapper class for Album related API calls and Album Bean.\r
+ * \r
+ * @author Janni Kovacs\r
+ */\r
+public class Album extends MusicEntry {\r
+\r
+    static final ItemFactory<Album> FACTORY = new AlbumFactory();\r
+\r
+    private String artist;\r
+\r
+    private Album(String name, String url, String artist) {\r
+        super(name, url);\r
+        this.artist = artist;\r
+    }\r
+\r
+    private Album(String name, String url, String mbid, int playcount, int listeners,\r
+            boolean streamable, String artist) {\r
+        super(name, url, mbid, playcount, listeners, streamable);\r
+        this.artist = artist;\r
+    }\r
+\r
+    public String getArtist() {\r
+        return artist;\r
+    }\r
+\r
+    /**\r
+     * Get the metadata for an album on Last.fm using the album name or a\r
+     * musicbrainz id. See playlist.fetch on how to get the album playlist.\r
+     * \r
+     * @param artist Artist's name\r
+     * @param albumOrMbid Album name or MBID\r
+     * @param apiKey The API key\r
+     * @return Album metadata\r
+     */\r
+    public static Album getInfo(String artist, String albumOrMbid, String apiKey) {\r
+        return getInfo(artist, albumOrMbid, null, apiKey);\r
+    }\r
+\r
+    /**\r
+     * Get the metadata for an album on Last.fm using the album name or a\r
+     * musicbrainz id. See playlist.fetch on how to get the album playlist.\r
+     * \r
+     * @param artist Artist's name\r
+     * @param albumOrMbid Album name or MBID\r
+     * @param username The username for the context of the request. If supplied,\r
+     *            the user's playcount for this album is included in the\r
+     *            response.\r
+     * @param apiKey The API key\r
+     * @return Album metadata\r
+     */\r
+    public static Album getInfo(String artist, String albumOrMbid, String username, String apiKey) {\r
+        Map<String, String> params = new HashMap<String, String>();\r
+        if (StringUtilities.isMbid(albumOrMbid)) {\r
+            params.put("mbid", albumOrMbid);\r
+        } else {\r
+            params.put("artist", artist);\r
+            params.put("album", albumOrMbid);\r
+        }\r
+        MapUtilities.nullSafePut(params, "username", username);\r
+        Result result = Caller.getInstance().call("album.getInfo", apiKey, params);\r
+        return ResponseBuilder.buildItem(result, Album.class);\r
+    }\r
+\r
+    private static class AlbumFactory implements ItemFactory<Album> {\r
+        @Override\r
+        public Album createItemFromElement(DomElement element) {\r
+            Album album = new Album(null, null, null);\r
+            MusicEntry.loadStandardInfo(album, element);\r
+            if (element.hasChild("artist")) {\r
+                album.artist = element.getChild("artist").getChildText("name");\r
+                if (album.artist == null)\r
+                    album.artist = element.getChildText("artist");\r
+            }\r
+            return album;\r
+        }\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/lastfm/api/Artist.java b/src/com/andrew/apollo/lastfm/api/Artist.java
new file mode 100644 (file)
index 0000000..7738cf1
--- /dev/null
@@ -0,0 +1,99 @@
+/*\r
+ * Copyright (c) 2012, the Last.fm Java Project and Committers\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use of this software in source and binary forms, with or without modification, are\r
+ * permitted provided that the following conditions are met:\r
+ *\r
+ * - Redistributions of source code must retain the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer.\r
+ *\r
+ * - Redistributions in binary form must reproduce the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer in the documentation and/or other\r
+ *   materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED\r
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\r
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\r
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR\r
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+package com.andrew.apollo.lastfm.api;\r
+\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+\r
+import com.andrew.apollo.utils.DomElement;\r
+import com.andrew.apollo.utils.MapUtilities;\r
+import com.andrew.apollo.utils.StringUtilities;\r
+\r
+/**\r
+ * Bean that contains artist information.<br/>\r
+ * This class contains static methods that executes API methods relating to\r
+ * artists.<br/>\r
+ * Method names are equivalent to the last.fm API method names.\r
+ * \r
+ * @author Janni Kovacs\r
+ */\r
+public class Artist extends MusicEntry {\r
+\r
+    static final ItemFactory<Artist> FACTORY = new ArtistFactory();\r
+\r
+    protected Artist(String name, String url) {\r
+        super(name, url);\r
+    }\r
+\r
+    protected Artist(String name, String url, String mbid, int playcount, int listeners,\r
+            boolean streamable) {\r
+        super(name, url, mbid, playcount, listeners, streamable);\r
+    }\r
+\r
+    /**\r
+     * Get {@link Image}s for this artist in a variety of sizes.\r
+     * \r
+     * @param artistOrMbid The artist name in question\r
+     * @param apiKey A Last.fm API key\r
+     * @return a list of {@link Image}s\r
+     */\r
+    public static PaginatedResult<Image> getImages(String artistOrMbid, String apiKey) {\r
+        return getImages(artistOrMbid, -1, -1, apiKey);\r
+    }\r
+\r
+    /**\r
+     * Get {@link Image}s for this artist in a variety of sizes.\r
+     * \r
+     * @param artistOrMbid The artist name in question\r
+     * @param page Which page of limit amount to display\r
+     * @param limit How many to return. Defaults and maxes out at 50\r
+     * @param apiKey A Last.fm API key\r
+     * @return a list of {@link Image}s\r
+     */\r
+    public static PaginatedResult<Image> getImages(String artistOrMbid, int page, int limit,\r
+            String apiKey) {\r
+        Map<String, String> params = new HashMap<String, String>();\r
+        if (StringUtilities.isMbid(artistOrMbid)) {\r
+            params.put("mbid", artistOrMbid);\r
+        } else {\r
+            params.put("artist", artistOrMbid);\r
+        }\r
+        MapUtilities.nullSafePut(params, "page", page);\r
+        MapUtilities.nullSafePut(params, "limit", limit);\r
+        Result result = Caller.getInstance().call("artist.getImages", apiKey, params);\r
+        return ResponseBuilder.buildPaginatedResult(result, Image.class);\r
+    }\r
+\r
+    private static class ArtistFactory implements ItemFactory<Artist> {\r
+        @Override\r
+        public Artist createItemFromElement(DomElement element) {\r
+            Artist artist = new Artist(null, null);\r
+            MusicEntry.loadStandardInfo(artist, element);\r
+            return artist;\r
+        }\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/lastfm/api/CallException.java b/src/com/andrew/apollo/lastfm/api/CallException.java
new file mode 100644 (file)
index 0000000..eada28a
--- /dev/null
@@ -0,0 +1,53 @@
+/*\r
+ * Copyright (c) 2012, the Last.fm Java Project and Committers\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use of this software in source and binary forms, with or without modification, are\r
+ * permitted provided that the following conditions are met:\r
+ *\r
+ * - Redistributions of source code must retain the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer.\r
+ *\r
+ * - Redistributions in binary form must reproduce the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer in the documentation and/or other\r
+ *   materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED\r
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\r
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\r
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR\r
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+package com.andrew.apollo.lastfm.api;\r
+\r
+/**\r
+ * @author Janni Kovacs\r
+ */\r
+public class CallException extends RuntimeException {\r
+\r
+    /**\r
+     * \r
+     */\r
+    private static final long serialVersionUID = 1L;\r
+\r
+    public CallException() {\r
+    }\r
+\r
+    public CallException(Throwable cause) {\r
+        super(cause);\r
+    }\r
+\r
+    public CallException(String message) {\r
+        super(message);\r
+    }\r
+\r
+    public CallException(String message, Throwable cause) {\r
+        super(message, cause);\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/lastfm/api/Caller.java b/src/com/andrew/apollo/lastfm/api/Caller.java
new file mode 100644 (file)
index 0000000..50796f4
--- /dev/null
@@ -0,0 +1,262 @@
+/*\r
+ * Copyright (c) 2012, the Last.fm Java Project and Committers\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use of this software in source and binary forms, with or without modification, are\r
+ * permitted provided that the following conditions are met:\r
+ *\r
+ * - Redistributions of source code must retain the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer.\r
+ *\r
+ * - Redistributions in binary form must reproduce the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer in the documentation and/or other\r
+ *   materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED\r
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\r
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\r
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR\r
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+package com.andrew.apollo.lastfm.api;\r
+\r
+import static com.andrew.apollo.utils.StringUtilities.encode;\r
+import static com.andrew.apollo.utils.StringUtilities.map;\r
+\r
+import java.io.BufferedWriter;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.InputStreamReader;\r
+import java.io.OutputStream;\r
+import java.io.OutputStreamWriter;\r
+import java.net.HttpURLConnection;\r
+import java.net.Proxy;\r
+import java.net.URL;\r
+import java.util.HashMap;\r
+import java.util.Iterator;\r
+import java.util.Map;\r
+import java.util.Map.Entry;\r
+\r
+import javax.xml.parsers.DocumentBuilder;\r
+import javax.xml.parsers.DocumentBuilderFactory;\r
+import javax.xml.parsers.ParserConfigurationException;\r
+\r
+import org.w3c.dom.Document;\r
+import org.w3c.dom.Element;\r
+import org.xml.sax.InputSource;\r
+import org.xml.sax.SAXException;\r
+\r
+import com.andrew.apollo.lastfm.api.Result.Status;\r
+\r
+/**\r
+ * The <code>Caller</code> class handles the low-level communication between the\r
+ * client and last.fm.<br/>\r
+ * Direct usage of this class should be unnecessary since all method calls are\r
+ * available via the methods in the <code>Artist</code>, <code>Album</code>,\r
+ * <code>User</code>, etc. classes. If specialized calls which are not covered\r
+ * by the Java API are necessary this class may be used directly.<br/>\r
+ * Supports the setting of a custom {@link Proxy} and a custom\r
+ * <code>User-Agent</code> HTTP header.\r
+ * \r
+ * @author Janni Kovacs\r
+ */\r
+public class Caller {\r
+\r
+    private static final String PARAM_API_KEY = "api_key";\r
+\r
+    private static final String DEFAULT_API_ROOT = "http://ws.audioscrobbler.com/2.0/";\r
+\r
+    private static final Caller instance = new Caller();\r
+\r
+    private final String apiRootUrl = DEFAULT_API_ROOT;\r
+\r
+    private final String userAgent = "Apollo";\r
+\r
+    private Result lastResult;\r
+\r
+    private Caller() {\r
+    }\r
+\r
+    /**\r
+     * Returns the single instance of the <code>Caller</code> class.\r
+     * \r
+     * @return a <code>Caller</code>\r
+     */\r
+    public static Caller getInstance() {\r
+        return instance;\r
+    }\r
+\r
+    public Result call(String method, String apiKey, String... params) throws CallException {\r
+        return call(method, apiKey, map(params));\r
+    }\r
+\r
+    public Result call(String method, String apiKey, Map<String, String> params)\r
+            throws CallException {\r
+        return call(method, apiKey, params, null);\r
+    }\r
+\r
+    public Result call(String method, Session session, String... params) {\r
+        return call(method, session.getApiKey(), map(params), session);\r
+    }\r
+\r
+    public Result call(String method, Session session, Map<String, String> params) {\r
+        return call(method, session.getApiKey(), params, session);\r
+    }\r
+\r
+    /**\r
+     * Performs the web-service call. If the <code>session</code> parameter is\r
+     * <code>non-null</code> then an authenticated call is made. If it's\r
+     * <code>null</code> then an unauthenticated call is made.<br/>\r
+     * The <code>apiKey</code> parameter is always required, even when a valid\r
+     * session is passed to this method.\r
+     * \r
+     * @param method The method to call\r
+     * @param apiKey A Last.fm API key\r
+     * @param params Parameters\r
+     * @param session A Session instance or <code>null</code>\r
+     * @return the result of the operation\r
+     */\r
+    private Result call(String method, String apiKey, Map<String, String> params, Session session) {\r
+        params = new HashMap<String, String>(params); // create new Map in case\r
+                                                      // params is an immutable\r
+                                                      // Map\r
+        InputStream inputStream = null;\r
+\r
+        // no entry in cache, load from web\r
+        if (inputStream == null) {\r
+            // fill parameter map with apiKey and session info\r
+            params.put(PARAM_API_KEY, apiKey);\r
+            if (session != null) {\r
+                params.put("sk", session.getKey());\r
+            }\r
+            try {\r
+                HttpURLConnection urlConnection = openPostConnection(method, params);\r
+                inputStream = getInputStreamFromConnection(urlConnection);\r
+\r
+                if (inputStream == null) {\r
+                    this.lastResult = Result.createHttpErrorResult(urlConnection.getResponseCode(),\r
+                            urlConnection.getResponseMessage());\r
+                    return lastResult;\r
+                }\r
+            } catch (IOException e) {\r
+                throw new CallException(e);\r
+            }\r
+        }\r
+\r
+        try {\r
+            Result result = createResultFromInputStream(inputStream);\r
+            this.lastResult = result;\r
+            return result;\r
+        } catch (IOException e) {\r
+            throw new CallException(e);\r
+        } catch (SAXException e) {\r
+            throw new CallException(e);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Creates a new {@link HttpURLConnection}, sets the proxy, if available,\r
+     * and sets the User-Agent property.\r
+     * \r
+     * @param url URL to connect to\r
+     * @return a new connection.\r
+     * @throws IOException if an I/O exception occurs.\r
+     */\r
+    public HttpURLConnection openConnection(String url) throws IOException {\r
+        URL u = new URL(url);\r
+        HttpURLConnection urlConnection;\r
+        urlConnection = (HttpURLConnection)u.openConnection();\r
+        urlConnection.setRequestProperty("User-Agent", userAgent);\r
+        urlConnection.setUseCaches(true);\r
+        return urlConnection;\r
+    }\r
+\r
+    private HttpURLConnection openPostConnection(String method, Map<String, String> params)\r
+            throws IOException {\r
+        HttpURLConnection urlConnection = openConnection(apiRootUrl);\r
+        urlConnection.setRequestMethod("POST");\r
+        urlConnection.setDoOutput(true);\r
+        urlConnection.setUseCaches(true);\r
+        OutputStream outputStream = urlConnection.getOutputStream();\r
+        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream));\r
+        String post = buildPostBody(method, params);\r
+        writer.write(post);\r
+        writer.close();\r
+        return urlConnection;\r
+    }\r
+\r
+    private InputStream getInputStreamFromConnection(HttpURLConnection connection)\r
+            throws IOException {\r
+        int responseCode = connection.getResponseCode();\r
+\r
+        if (responseCode == HttpURLConnection.HTTP_FORBIDDEN\r
+                || responseCode == HttpURLConnection.HTTP_BAD_REQUEST) {\r
+            return connection.getErrorStream();\r
+        } else if (responseCode == HttpURLConnection.HTTP_OK) {\r
+            return connection.getInputStream();\r
+        }\r
+\r
+        return null;\r
+    }\r
+\r
+    private Result createResultFromInputStream(InputStream inputStream) throws SAXException,\r
+            IOException {\r
+        Document document = newDocumentBuilder().parse(\r
+                new InputSource(new InputStreamReader(inputStream, "UTF-8")));\r
+        Element root = document.getDocumentElement(); // lfm element\r
+        String statusString = root.getAttribute("status");\r
+        Status status = "ok".equals(statusString) ? Status.OK : Status.FAILED;\r
+        if (status == Status.FAILED) {\r
+            Element errorElement = (Element)root.getElementsByTagName("error").item(0);\r
+            int errorCode = Integer.parseInt(errorElement.getAttribute("code"));\r
+            String message = errorElement.getTextContent();\r
+            return Result.createRestErrorResult(errorCode, message);\r
+        } else {\r
+            return Result.createOkResult(document);\r
+        }\r
+    }\r
+\r
+    private DocumentBuilder newDocumentBuilder() {\r
+        try {\r
+            DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();\r
+            return builderFactory.newDocumentBuilder();\r
+        } catch (ParserConfigurationException e) {\r
+            // better never happens\r
+            throw new RuntimeException(e);\r
+        }\r
+    }\r
+\r
+    private String buildPostBody(String method, Map<String, String> params, String... strings) {\r
+        StringBuilder builder = new StringBuilder(100);\r
+        builder.append("method=");\r
+        builder.append(method);\r
+        builder.append('&');\r
+        for (Iterator<Entry<String, String>> it = params.entrySet().iterator(); it.hasNext();) {\r
+            Entry<String, String> entry = it.next();\r
+            builder.append(entry.getKey());\r
+            builder.append('=');\r
+            builder.append(encode(entry.getValue()));\r
+            if (it.hasNext() || strings.length > 0)\r
+                builder.append('&');\r
+        }\r
+        int count = 0;\r
+        for (String string : strings) {\r
+            builder.append(count % 2 == 0 ? string : encode(string));\r
+            count++;\r
+            if (count != strings.length) {\r
+                if (count % 2 == 0) {\r
+                    builder.append('&');\r
+                } else {\r
+                    builder.append('=');\r
+                }\r
+            }\r
+        }\r
+        return builder.toString();\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/lastfm/api/Image.java b/src/com/andrew/apollo/lastfm/api/Image.java
new file mode 100644 (file)
index 0000000..12b2dca
--- /dev/null
@@ -0,0 +1,80 @@
+/*\r
+ * Copyright (c) 2012, the Last.fm Java Project and Committers\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use of this software in source and binary forms, with or without modification, are\r
+ * permitted provided that the following conditions are met:\r
+ *\r
+ * - Redistributions of source code must retain the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer.\r
+ *\r
+ * - Redistributions in binary form must reproduce the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer in the documentation and/or other\r
+ *   materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED\r
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\r
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\r
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR\r
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+package com.andrew.apollo.lastfm.api;\r
+\r
+import java.util.Locale;\r
+\r
+import com.andrew.apollo.utils.DomElement;\r
+\r
+/**\r
+ * An <code>Image</code> contains metadata and URLs for an artist's image.\r
+ * Metadata contains title, votes, format and other. Images are available in\r
+ * various sizes, see {@link ImageSize} for all sizes.\r
+ * \r
+ * @author Janni Kovacs\r
+ * @see ImageSize\r
+ * @see Artist#getImages(String, String)\r
+ */\r
+public class Image extends ImageHolder {\r
+\r
+    static final ItemFactory<Image> FACTORY = new ImageFactory();\r
+\r
+    private String url;\r
+\r
+    private Image() {\r
+    }\r
+\r
+    public String getUrl() {\r
+        return url;\r
+    }\r
+\r
+    private static class ImageFactory implements ItemFactory<Image> {\r
+        @Override\r
+        public Image createItemFromElement(DomElement element) {\r
+            Image i = new Image();\r
+            i.url = element.getChildText("url");\r
+            DomElement sizes = element.getChild("sizes");\r
+            for (DomElement image : sizes.getChildren("size")) {\r
+                // code copied from ImageHolder.loadImages\r
+                String attribute = image.getAttribute("name");\r
+                ImageSize size = null;\r
+                if (attribute == null) {\r
+                    size = ImageSize.LARGESQUARE;\r
+                } else {\r
+                    try {\r
+                        size = ImageSize.valueOf(attribute.toUpperCase(Locale.ENGLISH));\r
+                    } catch (IllegalArgumentException e) {\r
+                        // if they suddenly again introduce a new image size\r
+                    }\r
+                }\r
+                if (size != null)\r
+                    i.imageUrls.put(size, image.getText());\r
+            }\r
+            return i;\r
+        }\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/lastfm/api/ImageHolder.java b/src/com/andrew/apollo/lastfm/api/ImageHolder.java
new file mode 100644 (file)
index 0000000..8bb73ff
--- /dev/null
@@ -0,0 +1,85 @@
+/*\r
+ * Copyright (c) 2012, the Last.fm Java Project and Committers\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use of this software in source and binary forms, with or without modification, are\r
+ * permitted provided that the following conditions are met:\r
+ *\r
+ * - Redistributions of source code must retain the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer.\r
+ *\r
+ * - Redistributions in binary form must reproduce the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer in the documentation and/or other\r
+ *   materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED\r
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\r
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\r
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR\r
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+package com.andrew.apollo.lastfm.api;\r
+\r
+import java.util.Collection;\r
+import java.util.HashMap;\r
+import java.util.Locale;\r
+import java.util.Map;\r
+import java.util.Set;\r
+\r
+import com.andrew.apollo.utils.DomElement;\r
+\r
+/**\r
+ * Abstract superclass for all items that may contain images (such as\r
+ * {@link Artist}s, {@link Album}s or {@link Track}s).\r
+ * \r
+ * @author Janni Kovacs\r
+ */\r
+public abstract class ImageHolder {\r
+\r
+    protected Map<ImageSize, String> imageUrls = new HashMap<ImageSize, String>();\r
+\r
+    /**\r
+     * Returns a Set of all {@link ImageSize}s available.\r
+     * \r
+     * @return all sizes\r
+     */\r
+    public Set<ImageSize> availableSizes() {\r
+        return imageUrls.keySet();\r
+    }\r
+\r
+    /**\r
+     * Returns the URL of the image in the specified size, or <code>null</code>\r
+     * if not available.\r
+     * \r
+     * @param size The preferred size\r
+     * @return an image URL\r
+     */\r
+    public String getImageURL(ImageSize size) {\r
+        return imageUrls.get(size);\r
+    }\r
+\r
+    protected static void loadImages(ImageHolder holder, DomElement element) {\r
+        Collection<DomElement> images = element.getChildren("image");\r
+        for (DomElement image : images) {\r
+            String attribute = image.getAttribute("size");\r
+            ImageSize size = null;\r
+            if (attribute == null) {\r
+                size = ImageSize.LARGESQUARE; \r
+            } else {\r
+                try {\r
+                    size = ImageSize.valueOf(attribute.toUpperCase(Locale.ENGLISH));\r
+                } catch (IllegalArgumentException e) {\r
+                    // if they suddenly again introduce a new image size\r
+                }\r
+            }\r
+            if (size != null)\r
+                holder.imageUrls.put(size, image.getText());\r
+        }\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/lastfm/api/ImageSize.java b/src/com/andrew/apollo/lastfm/api/ImageSize.java
new file mode 100644 (file)
index 0000000..77361c7
--- /dev/null
@@ -0,0 +1,36 @@
+/*\r
+ * Copyright (c) 2012, the Last.fm Java Project and Committers\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use of this software in source and binary forms, with or without modification, are\r
+ * permitted provided that the following conditions are met:\r
+ *\r
+ * - Redistributions of source code must retain the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer.\r
+ *\r
+ * - Redistributions in binary form must reproduce the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer in the documentation and/or other\r
+ *   materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED\r
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\r
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\r
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR\r
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+package com.andrew.apollo.lastfm.api;\r
+\r
+/**\r
+ * @author Janni Kovacs\r
+ */\r
+public enum ImageSize {\r
+\r
+    LARGE, LARGESQUARE, ORIGINAL\r
+\r
+}\r
diff --git a/src/com/andrew/apollo/lastfm/api/ItemFactory.java b/src/com/andrew/apollo/lastfm/api/ItemFactory.java
new file mode 100644 (file)
index 0000000..331b0c7
--- /dev/null
@@ -0,0 +1,49 @@
+/*\r
+ * Copyright (c) 2012, the Last.fm Java Project and Committers\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use of this software in source and binary forms, with or without modification, are\r
+ * permitted provided that the following conditions are met:\r
+ *\r
+ * - Redistributions of source code must retain the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer.\r
+ *\r
+ * - Redistributions in binary form must reproduce the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer in the documentation and/or other\r
+ *   materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED\r
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\r
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\r
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR\r
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+package com.andrew.apollo.lastfm.api;\r
+\r
+import com.andrew.apollo.utils.DomElement;\r
+\r
+/**\r
+ * An <code>ItemFactory</code> can be used to instantiate a value object - such as Artist, Album, Track, Tag - from an XML element. Use the\r
+ * {@link ItemFactoryBuilder} to obtain item factories for a specific type.\r
+ *\r
+ * @author Janni Kovacs\r
+ * @see com.andrew.apollo.lastfm.api.ItemFactoryBuilder\r
+ * @see ResponseBuilder\r
+ */\r
+interface ItemFactory<T> {\r
+\r
+       /**\r
+        * Create a new instance of the type <code>T</code>, based on the passed {@link DomElement}.\r
+        *\r
+        * @param element the XML element\r
+        * @return a new object\r
+        */\r
+       public T createItemFromElement(DomElement element);\r
+\r
+}\r
diff --git a/src/com/andrew/apollo/lastfm/api/ItemFactoryBuilder.java b/src/com/andrew/apollo/lastfm/api/ItemFactoryBuilder.java
new file mode 100644 (file)
index 0000000..de48796
--- /dev/null
@@ -0,0 +1,77 @@
+/*\r
+ * Copyright (c) 2012, the Last.fm Java Project and Committers\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use of this software in source and binary forms, with or without modification, are\r
+ * permitted provided that the following conditions are met:\r
+ *\r
+ * - Redistributions of source code must retain the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer.\r
+ *\r
+ * - Redistributions in binary form must reproduce the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer in the documentation and/or other\r
+ *   materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED\r
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\r
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\r
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR\r
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+package com.andrew.apollo.lastfm.api;\r
+\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+\r
+/**\r
+ * The <code>ItemFactoryBuilder</code> can be used to obtain {@link ItemFactory\r
+ * ItemFactories} for a specific type.\r
+ * \r
+ * @author Janni Kovacs\r
+ * @see ItemFactory\r
+ */\r
+final class ItemFactoryBuilder {\r
+\r
+    private static final ItemFactoryBuilder INSTANCE = new ItemFactoryBuilder();\r
+\r
+    @SuppressWarnings("rawtypes")\r
+    private final Map<Class, ItemFactory> factories = new HashMap<Class, ItemFactory>();\r
+\r
+    private ItemFactoryBuilder() {\r
+        // register default factories\r
+        addItemFactory(Album.class, Album.FACTORY);\r
+        addItemFactory(Artist.class, Artist.FACTORY);\r
+        addItemFactory(Image.class, Image.FACTORY);\r
+    }\r
+\r
+    /**\r
+     * Retrieve the instance of the <code>ItemFactoryBuilder</code>.\r
+     * \r
+     * @return the instance\r
+     */\r
+    public static ItemFactoryBuilder getFactoryBuilder() {\r
+        return INSTANCE;\r
+    }\r
+\r
+    public <T> void addItemFactory(Class<T> itemClass, ItemFactory<T> factory) {\r
+        factories.put(itemClass, factory);\r
+    }\r
+\r
+    /**\r
+     * Retrieves an {@link ItemFactory} for the given type, or <code>null</code>\r
+     * if no such factory was registered.\r
+     * \r
+     * @param itemClass the type's Class object\r
+     * @return the <code>ItemFactory</code> or <code>null</code>\r
+     */\r
+    @SuppressWarnings("unchecked")\r
+    public <T> ItemFactory<T> getItemFactory(Class<T> itemClass) {\r
+        return factories.get(itemClass);\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/lastfm/api/MusicEntry.java b/src/com/andrew/apollo/lastfm/api/MusicEntry.java
new file mode 100644 (file)
index 0000000..f0359d3
--- /dev/null
@@ -0,0 +1,123 @@
+/*\r
+ * Copyright (c) 2012, the Last.fm Java Project and Committers\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use of this software in source and binary forms, with or without modification, are\r
+ * permitted provided that the following conditions are met:\r
+ *\r
+ * - Redistributions of source code must retain the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer.\r
+ *\r
+ * - Redistributions in binary form must reproduce the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer in the documentation and/or other\r
+ *   materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED\r
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\r
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\r
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR\r
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+package com.andrew.apollo.lastfm.api;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+\r
+import com.andrew.apollo.utils.DomElement;\r
+\r
+\r
+/**\r
+ * <code>MusicEntry</code> is the abstract superclass for {@link Track},\r
+ * {@link Artist} and {@link Album}. It encapsulates data and provides methods\r
+ * used in all subclasses, for example: name, playcount, images and more.\r
+ * \r
+ * @author Janni Kovacs\r
+ */\r
+public abstract class MusicEntry extends ImageHolder {\r
+\r
+    protected String name;\r
+\r
+    protected String url;\r
+\r
+    protected String mbid;\r
+\r
+    protected String id;\r
+\r
+    /**\r
+     * This property is only available on hype charts, like\r
+     * {@link Chart#getHypedArtists(String)} or\r
+     * {@link de.umass.lastfm.Group#getHype(String, String)}\r
+     */\r
+    protected int percentageChange;\r
+\r
+    protected Collection<String> tags = new ArrayList<String>();\r
+\r
+    protected MusicEntry(String name, String url) {\r
+        this(name, url, null, -1, -1, false);\r
+    }\r
+\r
+    protected MusicEntry(String name, String url, String mbid, int playcount, int listeners,\r
+            boolean streamable) {\r
+        this.name = name;\r
+        this.url = url;\r
+        this.mbid = mbid;\r
+    }\r
+\r
+    public String getMbid() {\r
+        return mbid;\r
+    }\r
+\r
+    public String getName() {\r
+        return name;\r
+    }\r
+\r
+    public String getId() {\r
+        return id;\r
+    }\r
+\r
+    public String getUrl() {\r
+        return url;\r
+    }\r
+\r
+    public Collection<String> getTags() {\r
+        return tags;\r
+    }\r
+\r
+    @Override\r
+    public String toString() {\r
+        return this.getClass().getSimpleName() + "[" + "name='" + name + '\'' + ", id='" + id\r
+                + '\'' + ", url='" + url + '\'' + ", mbid='" + mbid + '\'' + ']';\r
+    }\r
+\r
+    /**\r
+     * Loads all generic information from an XML <code>DomElement</code> into\r
+     * the given <code>MusicEntry</code> instance, i.e. the following tags:<br/>\r
+     * <ul>\r
+     * <li>playcount/plays</li>\r
+     * <li>listeners</li>\r
+     * <li>streamable</li>\r
+     * <li>name</li>\r
+     * <li>url</li>\r
+     * <li>mbid</li>\r
+     * <li>image</li>\r
+     * <li>tags</li>\r
+     * </ul>\r
+     * \r
+     * @param entry An entry\r
+     * @param element XML source element\r
+     */\r
+    protected static void loadStandardInfo(MusicEntry entry, DomElement element) {\r
+        // copy\r
+        entry.name = element.getChildText("name");\r
+        entry.url = element.getChildText("url");\r
+        entry.mbid = element.getChildText("mbid");\r
+        // images\r
+        ImageHolder.loadImages(entry, element);\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/lastfm/api/PaginatedResult.java b/src/com/andrew/apollo/lastfm/api/PaginatedResult.java
new file mode 100644 (file)
index 0000000..ebc1672
--- /dev/null
@@ -0,0 +1,91 @@
+/*\r
+ * Copyright (c) 2012, the Last.fm Java Project and Committers\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use of this software in source and binary forms, with or without modification, are\r
+ * permitted provided that the following conditions are met:\r
+ *\r
+ * - Redistributions of source code must retain the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer.\r
+ *\r
+ * - Redistributions in binary form must reproduce the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer in the documentation and/or other\r
+ *   materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED\r
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\r
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\r
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR\r
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+package com.andrew.apollo.lastfm.api;\r
+\r
+import java.util.Collection;\r
+import java.util.Iterator;\r
+\r
+/**\r
+ * A <code>PaginatedResult</code> is returned by methods which result set might be so large that it needs\r
+ * to be paginated. Each <code>PaginatedResult</code> contains the total number of result pages, the current\r
+ * page and a <code>Collection</code> of entries for the current page.\r
+ *\r
+ * @author Janni Kovacs\r
+ */\r
+public class PaginatedResult<T> implements Iterable<T> {\r
+\r
+       private int page;\r
+       private int totalPages;\r
+       private Collection<T> pageResults;\r
+\r
+       PaginatedResult(int page, int totalPages, Collection<T> pageResults) {\r
+               this.page = page;\r
+               this.totalPages = totalPages;\r
+               this.pageResults = pageResults;\r
+       }\r
+\r
+       /**\r
+        * Returns the page number of this result.\r
+        *\r
+        * @return page number\r
+        */\r
+       public int getPage() {\r
+               return page;\r
+       }\r
+\r
+       /**\r
+        * Returns a list of entries of the type <code>T</code> for this page.\r
+        *\r
+        * @return page results\r
+        */\r
+       public Collection<T> getPageResults() {\r
+               return pageResults;\r
+       }\r
+\r
+       /**\r
+        * Returns the total number of pages available.\r
+        *\r
+        * @return total pages\r
+        */\r
+       public int getTotalPages() {\r
+               return totalPages;\r
+       }\r
+\r
+       /**\r
+        * Returns <code>true</code> if this Result contains no elements, which is the case for service calls that would have returned a\r
+        * <code>PaginatedResult</code> but fail.\r
+        *\r
+        * @return <code>true</code> if this result contains no elements\r
+        */\r
+       public boolean isEmpty() {\r
+               return pageResults == null || pageResults.isEmpty();\r
+       }\r
+\r
+       public Iterator<T> iterator() {\r
+               return getPageResults().iterator();\r
+       }\r
+}\r
diff --git a/src/com/andrew/apollo/lastfm/api/ResponseBuilder.java b/src/com/andrew/apollo/lastfm/api/ResponseBuilder.java
new file mode 100644 (file)
index 0000000..0f60778
--- /dev/null
@@ -0,0 +1,123 @@
+/*\r
+ * Copyright (c) 2012, the Last.fm Java Project and Committers\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use of this software in source and binary forms, with or without modification, are\r
+ * permitted provided that the following conditions are met:\r
+ *\r
+ * - Redistributions of source code must retain the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer.\r
+ *\r
+ * - Redistributions in binary form must reproduce the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer in the documentation and/or other\r
+ *   materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED\r
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\r
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\r
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR\r
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+package com.andrew.apollo.lastfm.api;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+\r
+import com.andrew.apollo.utils.DomElement;\r
+\r
+\r
+/**\r
+ * This utility class can be used to generically generate Result objects (usually Lists or {@link PaginatedResult}s) from an XML response\r
+ * using {@link ItemFactory ItemFactories}.\r
+ *\r
+ * @author Janni Kovacs\r
+ */\r
+public final class ResponseBuilder {\r
+\r
+       private ResponseBuilder() {\r
+       }\r
+\r
+       private static <T> ItemFactory<T> getItemFactory(Class<T> itemClass) {\r
+               return ItemFactoryBuilder.getFactoryBuilder().getItemFactory(itemClass);\r
+       }\r
+\r
+       public static <T> Collection<T> buildCollection(Result result, Class<T> itemClass) {\r
+               return buildCollection(result, getItemFactory(itemClass));\r
+       }\r
+\r
+       public static <T> Collection<T> buildCollection(Result result, ItemFactory<T> factory) {\r
+               if (!result.isSuccessful())\r
+                       return Collections.emptyList();\r
+               return buildCollection(result.getContentElement(), factory);\r
+       }\r
+\r
+       public static <T> Collection<T> buildCollection(DomElement element, Class<T> itemClass) {\r
+               return buildCollection(element, getItemFactory(itemClass));\r
+       }\r
+\r
+       public static <T> Collection<T> buildCollection(DomElement element, ItemFactory<T> factory) {\r
+               if (element == null)\r
+                       return Collections.emptyList();\r
+               Collection<DomElement> children = element.getChildren();\r
+               Collection<T> items = new ArrayList<T>(children.size());\r
+               for (DomElement child : children) {\r
+                       items.add(factory.createItemFromElement(child));\r
+               }\r
+               return items;\r
+       }\r
+\r
+       public static <T> PaginatedResult<T> buildPaginatedResult(Result result, Class<T> itemClass) {\r
+               return buildPaginatedResult(result, getItemFactory(itemClass));\r
+       }\r
+\r
+       public static <T> PaginatedResult<T> buildPaginatedResult(Result result, ItemFactory<T> factory) {\r
+               if (!result.isSuccessful()) {\r
+                       return new PaginatedResult<T>(0, 0, Collections.<T>emptyList());\r
+               }\r
+\r
+               DomElement contentElement = result.getContentElement();\r
+               return buildPaginatedResult(contentElement, contentElement, factory);\r
+       }\r
+\r
+       public static <T> PaginatedResult<T> buildPaginatedResult(DomElement contentElement, DomElement childElement, Class<T> itemClass) {\r
+               return buildPaginatedResult(contentElement, childElement, getItemFactory(itemClass));\r
+       }\r
+\r
+       public static <T> PaginatedResult<T> buildPaginatedResult(DomElement contentElement, DomElement childElement, ItemFactory<T> factory) {\r
+               Collection<T> items = buildCollection(childElement, factory);\r
+\r
+               String totalPagesAttribute = contentElement.getAttribute("totalPages");\r
+               if (totalPagesAttribute == null)\r
+                       totalPagesAttribute = contentElement.getAttribute("totalpages");\r
+\r
+               int page = Integer.parseInt(contentElement.getAttribute("page"));\r
+               int totalPages = Integer.parseInt(totalPagesAttribute);\r
+\r
+               return new PaginatedResult<T>(page, totalPages, items);\r
+       }\r
+\r
+       public static <T> T buildItem(Result result, Class<T> itemClass) {\r
+               return buildItem(result, getItemFactory(itemClass));\r
+       }\r
+\r
+       public static <T> T buildItem(Result result, ItemFactory<T> factory) {\r
+               if (!result.isSuccessful())\r
+                       return null;\r
+               return buildItem(result.getContentElement(), factory);\r
+       }\r
+\r
+       public static <T> T buildItem(DomElement element, Class<T> itemClass) {\r
+               return buildItem(element, getItemFactory(itemClass));\r
+       }\r
+\r
+       private static <T> T buildItem(DomElement element, ItemFactory<T> factory) {\r
+               return factory.createItemFromElement(element);\r
+       }\r
+}\r
diff --git a/src/com/andrew/apollo/lastfm/api/Result.java b/src/com/andrew/apollo/lastfm/api/Result.java
new file mode 100644 (file)
index 0000000..b6541eb
--- /dev/null
@@ -0,0 +1,119 @@
+/*\r
+ * Copyright (c) 2012, the Last.fm Java Project and Committers\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use of this software in source and binary forms, with or without modification, are\r
+ * permitted provided that the following conditions are met:\r
+ *\r
+ * - Redistributions of source code must retain the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer.\r
+ *\r
+ * - Redistributions in binary form must reproduce the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer in the documentation and/or other\r
+ *   materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED\r
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\r
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\r
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR\r
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+package com.andrew.apollo.lastfm.api;\r
+\r
+import org.w3c.dom.Document;\r
+\r
+import com.andrew.apollo.utils.DomElement;\r
+\r
+\r
+/**\r
+ * The <code>Result</code> class contains the response sent by the server, i.e. the status (either ok or failed),\r
+ * an error code and message if failed and the xml response sent by the server.\r
+ *\r
+ * @author Janni Kovacs\r
+ */\r
+public class Result {\r
+\r
+       public enum Status {\r
+               OK,\r
+               FAILED\r
+       }\r
+\r
+       protected Status status;\r
+       protected String errorMessage = null;\r
+       protected int errorCode = -1;\r
+       protected int httpErrorCode = -1;\r
+\r
+       protected Document resultDocument;\r
+\r
+       protected Result(Document resultDocument) {\r
+               this.status = Status.OK;\r
+               this.resultDocument = resultDocument;\r
+       }\r
+\r
+       protected Result(String errorMessage) {\r
+               this.status = Status.FAILED;\r
+               this.errorMessage = errorMessage;\r
+       }\r
+\r
+       static Result createOkResult(Document resultDocument) {\r
+               return new Result(resultDocument);\r
+       }\r
+\r
+       static Result createHttpErrorResult(int httpErrorCode, String errorMessage) {\r
+               Result r = new Result(errorMessage);\r
+               r.httpErrorCode = httpErrorCode;\r
+               return r;\r
+       }\r
+\r
+       static Result createRestErrorResult(int errorCode, String errorMessage) {\r
+               Result r = new Result(errorMessage);\r
+               r.errorCode = errorCode;\r
+               return r;\r
+       }\r
+\r
+       /**\r
+        * Returns if the operation was successful. Same as <code>getStatus() == Status.OK</code>.\r
+        *\r
+        * @return <code>true</code> if the operation was successful\r
+        */\r
+       public boolean isSuccessful() {\r
+               return status == Status.OK;\r
+       }\r
+\r
+       public int getErrorCode() {\r
+               return errorCode;\r
+       }\r
+\r
+       public int getHttpErrorCode() {\r
+               return httpErrorCode;\r
+       }\r
+\r
+       public Status getStatus() {\r
+               return status;\r
+       }\r
+\r
+       public Document getResultDocument() {\r
+               return resultDocument;\r
+       }\r
+\r
+       public String getErrorMessage() {\r
+               return errorMessage;\r
+       }\r
+\r
+       public DomElement getContentElement() {\r
+               if (!isSuccessful())\r
+                       return null;\r
+               return new DomElement(resultDocument.getDocumentElement()).getChild("*");\r
+       }\r
+\r
+       @Override\r
+       public String toString() {\r
+               return "Result[isSuccessful=" + isSuccessful() + ", errorCode=" + errorCode + ", httpErrorCode=" + httpErrorCode + ", errorMessage="\r
+                               + errorMessage + ", status=" + status+"]";\r
+       }\r
+}\r
diff --git a/src/com/andrew/apollo/lastfm/api/Session.java b/src/com/andrew/apollo/lastfm/api/Session.java
new file mode 100644 (file)
index 0000000..5056e20
--- /dev/null
@@ -0,0 +1,121 @@
+/*\r
+ * Copyright (c) 2012, the Last.fm Java Project and Committers\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use of this software in source and binary forms, with or without modification, are\r
+ * permitted provided that the following conditions are met:\r
+ *\r
+ * - Redistributions of source code must retain the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer.\r
+ *\r
+ * - Redistributions in binary form must reproduce the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer in the documentation and/or other\r
+ *   materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED\r
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\r
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\r
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR\r
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+package com.andrew.apollo.lastfm.api;\r
+\r
+import com.andrew.apollo.utils.DomElement;\r
+\r
+/**\r
+ * Contains Session data relevant for making API calls which require\r
+ * authentication. A <code>Session</code> instance is passed to all methods\r
+ * requiring previous authentication.\r
+ * \r
+ * @author Janni Kovacs\r
+ * @see de.umass.lastfm.Authenticator\r
+ */\r
+public class Session {\r
+\r
+    private String apiKey;\r
+\r
+    private String secret;\r
+\r
+    private String username;\r
+\r
+    private String key;\r
+\r
+    private boolean subscriber;\r
+\r
+    private Session() {\r
+    }\r
+\r
+    /**\r
+     * Restores a Session instance with the given session key.\r
+     * \r
+     * @param apiKey An api key\r
+     * @param secret A secret\r
+     * @param sessionKey The previously obtained session key\r
+     * @return a Session instance\r
+     */\r
+    public static Session createSession(String apiKey, String secret, String sessionKey) {\r
+        return createSession(apiKey, secret, sessionKey, null, false);\r
+    }\r
+\r
+    /**\r
+     * Restores a Session instance with the given session key.\r
+     * \r
+     * @param apiKey An api key\r
+     * @param secret A secret\r
+     * @param sessionKey The previously obtained session key\r
+     * @param username A Last.fm username\r
+     * @param subscriber Subscriber status\r
+     * @return a Session instance\r
+     */\r
+    public static Session createSession(String apiKey, String secret, String sessionKey,\r
+            String username, boolean subscriber) {\r
+        Session s = new Session();\r
+        s.apiKey = apiKey;\r
+        s.secret = secret;\r
+        s.key = sessionKey;\r
+        s.username = username;\r
+        s.subscriber = subscriber;\r
+        return s;\r
+    }\r
+\r
+    public String getSecret() {\r
+        return secret;\r
+    }\r
+\r
+    public String getApiKey() {\r
+        return apiKey;\r
+    }\r
+\r
+    public String getKey() {\r
+        return key;\r
+    }\r
+\r
+    public boolean isSubscriber() {\r
+        return subscriber;\r
+    }\r
+\r
+    public String getUsername() {\r
+        return username;\r
+    }\r
+\r
+    @Override\r
+    public String toString() {\r
+        return "Session[" + "apiKey=" + apiKey + ", secret=" + secret + ", username=" + username\r
+                + ", key=" + key + ", subscriber=" + subscriber + ']';\r
+    }\r
+\r
+    static Session sessionFromElement(DomElement element, String apiKey, String secret) {\r
+        if (element == null)\r
+            return null;\r
+        String user = element.getChildText("name");\r
+        String key = element.getChildText("key");\r
+        boolean subsc = element.getChildText("subscriber").equals("1");\r
+        return createSession(apiKey, secret, key, user, subsc);\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/list/fragments/ArtistAlbumsFragment.java b/src/com/andrew/apollo/list/fragments/ArtistAlbumsFragment.java
new file mode 100644 (file)
index 0000000..e28d640
--- /dev/null
@@ -0,0 +1,276 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.list.fragments;\r
+\r
+import android.content.BroadcastReceiver;\r
+import android.content.Context;\r
+import android.content.Intent;\r
+import android.content.IntentFilter;\r
+import android.database.Cursor;\r
+import android.net.Uri;\r
+import android.os.AsyncTask;\r
+import android.os.Bundle;\r
+import android.provider.BaseColumns;\r
+import android.provider.MediaStore.Audio;\r
+import android.provider.MediaStore.Audio.AlbumColumns;\r
+import android.support.v4.app.Fragment;\r
+import android.support.v4.app.LoaderManager.LoaderCallbacks;\r
+import android.support.v4.content.CursorLoader;\r
+import android.support.v4.content.Loader;\r
+import android.view.ContextMenu;\r
+import android.view.ContextMenu.ContextMenuInfo;\r
+import android.view.LayoutInflater;\r
+import android.view.MenuItem;\r
+import android.view.View;\r
+import android.view.ViewGroup;\r
+import android.widget.AdapterView;\r
+import android.widget.AdapterView.OnItemClickListener;\r
+import android.widget.ImageView;\r
+import android.widget.ListView;\r
+import android.widget.TextView;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.activities.TracksBrowser;\r
+import com.andrew.apollo.adapters.ArtistAlbumAdapter;\r
+import com.andrew.apollo.service.ApolloService;\r
+import com.andrew.apollo.tasks.GetCachedImages;\r
+import com.andrew.apollo.tasks.LastfmGetAlbumImages;\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+import com.andrew.apollo.utils.MusicUtils;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ * @Note This is used in the @TracksBrowser after touching an artist from @ArtistsFragment\r
+ */\r
+public class ArtistAlbumsFragment extends Fragment implements LoaderCallbacks<Cursor>,\r
+        OnItemClickListener, Constants {\r
+\r
+    // Adapter\r
+    private ArtistAlbumAdapter mArtistAlbumAdapter;\r
+\r
+    // Audio columns\r
+    public static int mAlbumIdIndex, mAlbumNameIndex, mSongCountIndex, mArtistNameIndex;\r
+\r
+    // ListView\r
+    private ListView mListView;\r
+\r
+    // Options\r
+    private final int PLAY_SELECTION = 15;\r
+\r
+    private final int ADD_TO_PLAYLIST = 16;\r
+\r
+    private final int SEARCH = 17;\r
+\r
+    // Album ID\r
+    private String mCurrentAlbumId;\r
+\r
+    // Cursor\r
+    private Cursor mCursor;\r
+\r
+    public ArtistAlbumsFragment() {\r
+    }\r
+\r
+    public ArtistAlbumsFragment(Bundle args) {\r
+        setArguments(args);\r
+    }\r
+\r
+    @Override\r
+    public void onActivityCreated(Bundle savedInstanceState) {\r
+        super.onActivityCreated(savedInstanceState);\r
+        // AlbumAdapter\r
+        mArtistAlbumAdapter = new ArtistAlbumAdapter(getActivity(), R.layout.listview_items, null,\r
+                new String[] {}, new int[] {}, 0);\r
+        mListView.setOnCreateContextMenuListener(this);\r
+        mListView.setAdapter(mArtistAlbumAdapter);\r
+        mListView.setOnItemClickListener(this);\r
+\r
+        // Important!\r
+        getLoaderManager().initLoader(0, null, this);\r
+    }\r
+\r
+    @Override\r
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\r
+        View root = inflater.inflate(R.layout.listview, container, false);\r
+        mListView = (ListView)root.findViewById(android.R.id.list);\r
+\r
+        // Set the header for @TrackBrowser\r
+        String header = getActivity().getResources().getString(R.string.album_header);\r
+        int left = getActivity().getResources().getInteger(R.integer.listview_padding_left);\r
+        int right = getActivity().getResources().getInteger(R.integer.listview_padding_right);\r
+        ApolloUtils.listHeader(this, root, header);\r
+        ApolloUtils.setListPadding(this, mListView, left, 0, right, 0);\r
+        return root;\r
+    }\r
+\r
+    @Override\r
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {\r
+        String[] projection = {\r
+                BaseColumns._ID, AlbumColumns.ALBUM, AlbumColumns.NUMBER_OF_SONGS,\r
+                AlbumColumns.ARTIST\r
+        };\r
+        if (getArguments() != null) {\r
+            long artistId = getArguments().getLong((BaseColumns._ID));\r
+            Uri uri = Audio.Artists.Albums.getContentUri(EXTERNAL, artistId);\r
+            String sortOrder = Audio.Albums.DEFAULT_SORT_ORDER;\r
+            return new CursorLoader(getActivity(), uri, projection, null, null, sortOrder);\r
+        }\r
+        return null;\r
+    }\r
+\r
+    @Override\r
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {\r
+        // Check for database errors\r
+        if (data == null) {\r
+            return;\r
+        }\r
+\r
+        mAlbumIdIndex = data.getColumnIndexOrThrow(BaseColumns._ID);\r
+        mAlbumNameIndex = data.getColumnIndexOrThrow(AlbumColumns.ALBUM);\r
+        mSongCountIndex = data.getColumnIndexOrThrow(AlbumColumns.NUMBER_OF_SONGS);\r
+        mArtistNameIndex = data.getColumnIndexOrThrow(AlbumColumns.ARTIST);\r
+        mArtistAlbumAdapter.changeCursor(data);\r
+        mCursor = data;\r
+    }\r
+\r
+    @Override\r
+    public void onLoaderReset(Loader<Cursor> loader) {\r
+        if (mArtistAlbumAdapter != null)\r
+            mArtistAlbumAdapter.changeCursor(null);\r
+    }\r
+\r
+    @Override\r
+    public void onSaveInstanceState(Bundle outState) {\r
+        outState.putAll(getArguments() != null ? getArguments() : new Bundle());\r
+        super.onSaveInstanceState(outState);\r
+    }\r
+\r
+    @Override\r
+    public void onItemClick(AdapterView<?> parent, View v, int position, long id) {\r
+        tracksBrowser(id);\r
+    }\r
+\r
+    /**\r
+     * Update the list as needed\r
+     */\r
+    private final BroadcastReceiver mMediaStatusReceiver = new BroadcastReceiver() {\r
+\r
+        @Override\r
+        public void onReceive(Context context, Intent intent) {\r
+            if (mListView != null) {\r
+                mListView.invalidateViews();\r
+            }\r
+        }\r
+\r
+    };\r
+\r
+    @Override\r
+    public void onStart() {\r
+        super.onStart();\r
+        IntentFilter filter = new IntentFilter();\r
+        filter.addAction(ApolloService.META_CHANGED);\r
+        filter.addAction(ApolloService.PLAYSTATE_CHANGED);\r
+        getActivity().registerReceiver(mMediaStatusReceiver, filter);\r
+    }\r
+\r
+    @Override\r
+    public void onStop() {\r
+        getActivity().unregisterReceiver(mMediaStatusReceiver);\r
+        super.onStop();\r
+    }\r
+\r
+    /**\r
+     * @param index\r
+     * @param id\r
+     */\r
+    private void tracksBrowser(long id) {\r
+\r
+        String artistName = mCursor.getString(mArtistNameIndex);\r
+        String albumName = mCursor.getString(mAlbumNameIndex);\r
+\r
+        Bundle bundle = new Bundle();\r
+        bundle.putString(MIME_TYPE, Audio.Albums.CONTENT_TYPE);\r
+        bundle.putString(ALBUM_KEY, albumName);\r
+        bundle.putString(ARTIST_KEY, artistName);\r
+        bundle.putLong(BaseColumns._ID, id);\r
+\r
+        Intent intent = new Intent(Intent.ACTION_VIEW);\r
+        intent.setClass(getActivity(), TracksBrowser.class);\r
+        intent.putExtras(bundle);\r
+        getActivity().startActivity(intent);\r
+        getActivity().finish();\r
+    }\r
+\r
+    @Override\r
+    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {\r
+        menu.add(0, PLAY_SELECTION, 0, getResources().getString(R.string.play_all));\r
+        menu.add(0, ADD_TO_PLAYLIST, 0, getResources().getString(R.string.add_to_playlist));\r
+        menu.add(0, SEARCH, 0, getResources().getString(R.string.search));\r
+\r
+        mCurrentAlbumId = mCursor.getString(mCursor.getColumnIndexOrThrow(BaseColumns._ID));\r
+\r
+        menu.setHeaderView(setHeaderLayout());\r
+        super.onCreateContextMenu(menu, v, menuInfo);\r
+    }\r
+\r
+    @Override\r
+    public boolean onContextItemSelected(MenuItem item) {\r
+        switch (item.getItemId()) {\r
+            case PLAY_SELECTION: {\r
+                long[] list = MusicUtils.getSongListForAlbum(getActivity(),\r
+                        Long.parseLong(mCurrentAlbumId));\r
+                MusicUtils.playAll(getActivity(), list, 0);\r
+                break;\r
+            }\r
+            case ADD_TO_PLAYLIST: {\r
+                Intent intent = new Intent(INTENT_ADD_TO_PLAYLIST);\r
+                long[] list = MusicUtils.getSongListForAlbum(getActivity(),\r
+                        Long.parseLong(mCurrentAlbumId));\r
+                intent.putExtra(INTENT_PLAYLIST_LIST, list);\r
+                getActivity().startActivity(intent);\r
+                break;\r
+            }\r
+            case SEARCH: {\r
+                MusicUtils.doSearch(getActivity(), mCursor, mAlbumNameIndex);\r
+                break;\r
+            }\r
+            default:\r
+                break;\r
+        }\r
+        return super.onContextItemSelected(item);\r
+    }\r
+\r
+    /**\r
+     * @return A custom ContextMenu header\r
+     */\r
+    public View setHeaderLayout() {\r
+        // Get album name\r
+        String albumName = mCursor.getString(mAlbumNameIndex);\r
+        // Get artist name\r
+        String artistName = mCursor.getString(mArtistNameIndex);\r
+\r
+        // Inflate the header View\r
+        LayoutInflater inflater = getActivity().getLayoutInflater();\r
+        View header = inflater.inflate(R.layout.context_menu_header, null, false);\r
+\r
+        // Artist image\r
+        ImageView headerImage = (ImageView)header.findViewById(R.id.header_image);\r
+\r
+        // Only download images we don't already have\r
+        if (ApolloUtils.getImageURL(albumName, ALBUM_IMAGE, getActivity()) == null)\r
+            new LastfmGetAlbumImages(getActivity(), null, 0).executeOnExecutor(\r
+                    AsyncTask.THREAD_POOL_EXECUTOR, artistName, albumName);\r
+\r
+        // Get and set cached image\r
+        new GetCachedImages(getActivity(), 1, headerImage).executeOnExecutor(\r
+                AsyncTask.THREAD_POOL_EXECUTOR, albumName);\r
+\r
+        // Set artist name\r
+        TextView headerText = (TextView)header.findViewById(R.id.header_text);\r
+        headerText.setText(albumName);\r
+        headerText.setBackgroundColor(getResources().getColor(R.color.transparent_black));\r
+        return header;\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/list/fragments/GenresFragment.java b/src/com/andrew/apollo/list/fragments/GenresFragment.java
new file mode 100644 (file)
index 0000000..3aa322c
--- /dev/null
@@ -0,0 +1,218 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.list.fragments;\r
+\r
+import android.content.ContentResolver;\r
+import android.content.Context;\r
+import android.content.Intent;\r
+import android.database.Cursor;\r
+import android.net.Uri;\r
+import android.os.Bundle;\r
+import android.provider.BaseColumns;\r
+import android.provider.MediaStore.Audio;\r
+import android.provider.MediaStore.Audio.AudioColumns;\r
+import android.provider.MediaStore.Audio.Genres;\r
+import android.provider.MediaStore.Audio.GenresColumns;\r
+import android.provider.MediaStore.MediaColumns;\r
+import android.support.v4.app.Fragment;\r
+import android.support.v4.app.LoaderManager.LoaderCallbacks;\r
+import android.support.v4.content.CursorLoader;\r
+import android.support.v4.content.Loader;\r
+import android.view.ContextMenu;\r
+import android.view.ContextMenu.ContextMenuInfo;\r
+import android.view.LayoutInflater;\r
+import android.view.MenuItem;\r
+import android.view.View;\r
+import android.view.ViewGroup;\r
+import android.widget.AdapterView;\r
+import android.widget.AdapterView.OnItemClickListener;\r
+import android.widget.ListView;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.activities.TracksBrowser;\r
+import com.andrew.apollo.adapters.GenreAdapter;\r
+import com.andrew.apollo.utils.MusicUtils;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ * @Note This is the fifth and final tab\r
+ */\r
+public class GenresFragment extends Fragment implements LoaderCallbacks<Cursor>, Constants,\r
+        OnItemClickListener {\r
+\r
+    // Adapter\r
+    private GenreAdapter mGenreAdapter;\r
+\r
+    // ListView\r
+    private ListView mListView;\r
+\r
+    // Cursor\r
+    private Cursor mCursor;\r
+\r
+    // Current genre Id\r
+    private String mCurrentGenreId;\r
+\r
+    // Options\r
+    private final int PLAY_SELECTION = 14;\r
+\r
+    // Audio columns\r
+    public static int mGenreIdIndex, mGenreNameIndex;\r
+\r
+    // Bundle\r
+    public GenresFragment() {\r
+    }\r
+\r
+    public GenresFragment(Bundle args) {\r
+        setArguments(args);\r
+    }\r
+\r
+    @Override\r
+    public void onActivityCreated(Bundle savedInstanceState) {\r
+        super.onActivityCreated(savedInstanceState);\r
+        // GenreAdapter\r
+        mGenreAdapter = new GenreAdapter(getActivity(), R.layout.listview_items, null,\r
+                new String[] {}, new int[] {}, 0);\r
+        mListView.setOnCreateContextMenuListener(this);\r
+        mListView.setOnItemClickListener(this);\r
+        mListView.setAdapter(mGenreAdapter);\r
+\r
+        // Important!\r
+        getLoaderManager().initLoader(0, null, this);\r
+    }\r
+\r
+    @Override\r
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\r
+        View root = inflater.inflate(R.layout.listview, container, false);\r
+        mListView = (ListView)root.findViewById(android.R.id.list);\r
+        return root;\r
+    }\r
+\r
+    @Override\r
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {\r
+        String[] projection = new String[] {\r
+                BaseColumns._ID, GenresColumns.NAME\r
+        };\r
+        Uri uri = Audio.Genres.EXTERNAL_CONTENT_URI;\r
+        String selection = getBetterGenresWhereClause(getActivity());\r
+        String sortOrder = Audio.Genres.DEFAULT_SORT_ORDER;\r
+        return new CursorLoader(getActivity(), uri, projection, selection, null, sortOrder);\r
+    }\r
+\r
+    @Override\r
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {\r
+        // Check for database errors\r
+        if (data == null) {\r
+            return;\r
+        }\r
+\r
+        mGenreIdIndex = data.getColumnIndexOrThrow(BaseColumns._ID);\r
+        mGenreNameIndex = data.getColumnIndexOrThrow(GenresColumns.NAME);\r
+        mGenreAdapter.changeCursor(data);\r
+        mCursor = data;\r
+    }\r
+\r
+    @Override\r
+    public void onLoaderReset(Loader<Cursor> loader) {\r
+        if (mGenreAdapter != null)\r
+            mGenreAdapter.changeCursor(null);\r
+    }\r
+\r
+    @Override\r
+    public void onSaveInstanceState(Bundle outState) {\r
+        outState.putAll(getArguments() != null ? getArguments() : new Bundle());\r
+        super.onSaveInstanceState(outState);\r
+    }\r
+\r
+    @Override\r
+    public void onItemClick(AdapterView<?> parent, View v, int position, long id) {\r
+        tracksBrowser(position, id);\r
+    }\r
+\r
+    private void tracksBrowser(int index, long id) {\r
+\r
+        String genreKey = mCursor.getString(mGenreNameIndex);\r
+\r
+        Bundle bundle = new Bundle();\r
+        bundle.putString(MIME_TYPE, Audio.Genres.CONTENT_TYPE);\r
+        bundle.putString(GENRE_KEY, genreKey);\r
+        bundle.putLong(BaseColumns._ID, id);\r
+\r
+        Intent intent = new Intent(getActivity(), TracksBrowser.class);\r
+        intent.putExtras(bundle);\r
+        getActivity().startActivity(intent);\r
+    }\r
+\r
+    @Override\r
+    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {\r
+        menu.add(0, PLAY_SELECTION, 0, getResources().getString(R.string.play_all));\r
+\r
+        mCurrentGenreId = mCursor.getString(mCursor.getColumnIndexOrThrow(BaseColumns._ID));\r
+\r
+        String title = mCursor.getString(mGenreNameIndex);\r
+        menu.setHeaderTitle(title);\r
+        super.onCreateContextMenu(menu, v, menuInfo);\r
+    }\r
+\r
+    @Override\r
+    public boolean onContextItemSelected(MenuItem item) {\r
+        switch (item.getItemId()) {\r
+            case PLAY_SELECTION:\r
+                long[] list = MusicUtils.getSongListForGenre(getActivity(),\r
+                        Long.parseLong(mCurrentGenreId));\r
+                MusicUtils.playAll(getActivity(), list, 0);\r
+                break;\r
+            default:\r
+                break;\r
+        }\r
+        return super.onContextItemSelected(item);\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @return correct genre name\r
+     */\r
+    public String getBetterGenresWhereClause(Context context) {\r
+        StringBuilder builder = new StringBuilder();\r
+        ContentResolver resolver = context.getContentResolver();\r
+        String[] genres_cols = new String[] {\r
+            BaseColumns._ID\r
+        };\r
+        Uri genres_uri = Audio.Genres.EXTERNAL_CONTENT_URI;\r
+        Cursor genres_cursor = resolver.query(genres_uri, genres_cols, null, null, null);\r
+        if (genres_cursor != null) {\r
+            if (genres_cursor.getCount() <= 0) {\r
+                genres_cursor.close();\r
+                return null;\r
+            }\r
+        } else\r
+            return null;\r
+        builder.append(BaseColumns._ID + " IN (");\r
+        genres_cursor.moveToFirst();\r
+        while (!genres_cursor.isAfterLast()) {\r
+            long genre_id = genres_cursor.getLong(0);\r
+            StringBuilder where = new StringBuilder();\r
+            where.append(AudioColumns.IS_MUSIC + "=1");\r
+            where.append(" AND " + MediaColumns.TITLE + "!=''");\r
+            String[] cols = new String[] {\r
+                BaseColumns._ID\r
+            };\r
+            Uri uri = Genres.Members.getContentUri(EXTERNAL, genre_id);\r
+            Cursor member_cursor = context.getContentResolver().query(uri, cols, where.toString(),\r
+                    null, null);\r
+            if (member_cursor != null) {\r
+                if (member_cursor.getCount() > 0) {\r
+                    builder.append(genre_id + ",");\r
+                }\r
+                member_cursor.close();\r
+            }\r
+            genres_cursor.moveToNext();\r
+        }\r
+        genres_cursor.close();\r
+        builder.deleteCharAt(builder.length() - 1);\r
+        builder.append(")");\r
+        return builder.toString();\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/list/fragments/PlaylistsFragment.java b/src/com/andrew/apollo/list/fragments/PlaylistsFragment.java
new file mode 100644 (file)
index 0000000..c5e829c
--- /dev/null
@@ -0,0 +1,197 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.list.fragments;\r
+\r
+import android.content.ContentUris;\r
+import android.content.Intent;\r
+import android.database.Cursor;\r
+import android.net.Uri;\r
+import android.os.Bundle;\r
+import android.provider.BaseColumns;\r
+import android.provider.MediaStore;\r
+import android.provider.MediaStore.Audio;\r
+import android.provider.MediaStore.Audio.PlaylistsColumns;\r
+import android.support.v4.app.Fragment;\r
+import android.support.v4.app.LoaderManager.LoaderCallbacks;\r
+import android.support.v4.content.CursorLoader;\r
+import android.support.v4.content.Loader;\r
+import android.view.ContextMenu;\r
+import android.view.ContextMenu.ContextMenuInfo;\r
+import android.view.LayoutInflater;\r
+import android.view.MenuItem;\r
+import android.view.View;\r
+import android.view.ViewGroup;\r
+import android.widget.AdapterView;\r
+import android.widget.AdapterView.AdapterContextMenuInfo;\r
+import android.widget.AdapterView.OnItemClickListener;\r
+import android.widget.ListView;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.activities.TracksBrowser;\r
+import com.andrew.apollo.adapters.PlaylistAdapter;\r
+import com.andrew.apollo.utils.MusicUtils;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ */\r
+public class PlaylistsFragment extends Fragment implements LoaderCallbacks<Cursor>, Constants,\r
+        OnItemClickListener {\r
+\r
+    // Adapter\r
+    private PlaylistAdapter mPlaylistAdapter;\r
+\r
+    // ListView\r
+    private ListView mListView;\r
+\r
+    // Cursor\r
+    private Cursor mCursor;\r
+\r
+    // Current playlist Id\r
+    private String mCurrentPlaylistId;\r
+\r
+    // Options\r
+    private static final int PLAY_SELECTION = 11;\r
+\r
+    private static final int DELETE_PLAYLIST = 12;\r
+\r
+    private static final int RENAME_PLAYLIST = 13;\r
+\r
+    // Aduio columns\r
+    public static int mPlaylistNameIndex, mPlaylistIdIndex;\r
+\r
+    // Bundle\r
+    public PlaylistsFragment() {\r
+    }\r
+\r
+    public PlaylistsFragment(Bundle args) {\r
+        setArguments(args);\r
+    }\r
+\r
+    @Override\r
+    public void onActivityCreated(Bundle savedInstanceState) {\r
+        super.onActivityCreated(savedInstanceState);\r
+        // Adapter\r
+        mPlaylistAdapter = new PlaylistAdapter(getActivity(), R.layout.listview_items, null,\r
+                new String[] {}, new int[] {}, 0);\r
+        mListView.setOnCreateContextMenuListener(this);\r
+        mListView.setAdapter(mPlaylistAdapter);\r
+        mListView.setOnItemClickListener(this);\r
+\r
+        getLoaderManager().initLoader(0, null, this);\r
+    }\r
+\r
+    @Override\r
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\r
+        View root = inflater.inflate(R.layout.listview, container, false);\r
+        mListView = (ListView)root.findViewById(android.R.id.list);\r
+        return root;\r
+    }\r
+\r
+    @Override\r
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {\r
+        String[] projection = new String[] {\r
+                BaseColumns._ID, PlaylistsColumns.NAME\r
+        };\r
+        Uri uri = Audio.Playlists.EXTERNAL_CONTENT_URI;\r
+        String sortOrder = Audio.Playlists.DEFAULT_SORT_ORDER;\r
+        return new CursorLoader(getActivity(), uri, projection, null, null, sortOrder);\r
+    }\r
+\r
+    @Override\r
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {\r
+        // Check for database errors\r
+        if (data == null) {\r
+            return;\r
+        }\r
+\r
+        mPlaylistIdIndex = data.getColumnIndexOrThrow(BaseColumns._ID);\r
+        mPlaylistNameIndex = data.getColumnIndexOrThrow(PlaylistsColumns.NAME);\r
+        mPlaylistAdapter.changeCursor(data);\r
+        mCursor = data;\r
+    }\r
+\r
+    @Override\r
+    public void onLoaderReset(Loader<Cursor> loader) {\r
+        if (mPlaylistAdapter != null)\r
+            mPlaylistAdapter.changeCursor(null);\r
+    }\r
+\r
+    @Override\r
+    public void onSaveInstanceState(Bundle outState) {\r
+        outState.putAll(getArguments() != null ? getArguments() : new Bundle());\r
+        super.onSaveInstanceState(outState);\r
+    }\r
+\r
+    @Override\r
+    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {\r
+\r
+        AdapterContextMenuInfo mi = (AdapterContextMenuInfo)menuInfo;\r
+\r
+        menu.add(0, PLAY_SELECTION, 0, getResources().getString(R.string.play_all));\r
+\r
+        if (mi.id >= 0) {\r
+            menu.add(0, RENAME_PLAYLIST, 0, getResources().getString(R.string.rename_playlist));\r
+            menu.add(0, DELETE_PLAYLIST, 0, getResources().getString(R.string.delete_playlist));\r
+        }\r
+\r
+        mCurrentPlaylistId = mCursor.getString(mCursor.getColumnIndexOrThrow(BaseColumns._ID));\r
+\r
+        String title = mCursor.getString(mPlaylistNameIndex);\r
+        menu.setHeaderTitle(title);\r
+        super.onCreateContextMenu(menu, v, menuInfo);\r
+    }\r
+\r
+    @Override\r
+    public boolean onContextItemSelected(MenuItem item) {\r
+        AdapterContextMenuInfo mi = (AdapterContextMenuInfo)item.getMenuInfo();\r
+        switch (item.getItemId()) {\r
+            case PLAY_SELECTION: {\r
+                long[] list = MusicUtils.getSongListForPlaylist(getActivity(),\r
+                        Long.parseLong(mCurrentPlaylistId));\r
+                MusicUtils.playAll(getActivity(), list, 0);\r
+                break;\r
+            }\r
+            case RENAME_PLAYLIST: {\r
+                Intent intent = new Intent(INTENT_RENAME_PLAYLIST);\r
+                intent.putExtra(INTENT_KEY_RENAME, mi.id);\r
+                getActivity().startActivity(intent);\r
+                break;\r
+            }\r
+            case DELETE_PLAYLIST: {\r
+                Uri uri = ContentUris.withAppendedId(\r
+                        MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, mi.id);\r
+                getActivity().getContentResolver().delete(uri, null, null);\r
+                break;\r
+            }\r
+            default:\r
+                break;\r
+        }\r
+        return super.onContextItemSelected(item);\r
+    }\r
+\r
+    @Override\r
+    public void onItemClick(AdapterView<?> parent, View v, int position, long id) {\r
+        tracksBrowser(id);\r
+    }\r
+\r
+    /**\r
+     * @param id\r
+     */\r
+    private void tracksBrowser(long id) {\r
+\r
+        String playlistName = mCursor.getString(mPlaylistNameIndex);\r
+\r
+        Bundle bundle = new Bundle();\r
+        bundle.putString(MIME_TYPE, Audio.Playlists.CONTENT_TYPE);\r
+        bundle.putString(PLAYLIST_NAME, playlistName);\r
+        bundle.putLong(BaseColumns._ID, id);\r
+\r
+        Intent intent = new Intent(Intent.ACTION_VIEW);\r
+        intent.setClass(getActivity(), TracksBrowser.class);\r
+        intent.putExtras(bundle);\r
+        getActivity().startActivity(intent);\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/list/fragments/RecentlyAddedFragment.java b/src/com/andrew/apollo/list/fragments/RecentlyAddedFragment.java
new file mode 100644 (file)
index 0000000..2eadfc3
--- /dev/null
@@ -0,0 +1,166 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.list.fragments;\r
+\r
+import android.content.BroadcastReceiver;\r
+import android.content.Context;\r
+import android.content.Intent;\r
+import android.content.IntentFilter;\r
+import android.database.Cursor;\r
+import android.net.Uri;\r
+import android.os.Bundle;\r
+import android.provider.BaseColumns;\r
+import android.provider.MediaStore.Audio;\r
+import android.provider.MediaStore.Audio.AudioColumns;\r
+import android.provider.MediaStore.MediaColumns;\r
+import android.support.v4.app.Fragment;\r
+import android.support.v4.app.LoaderManager.LoaderCallbacks;\r
+import android.support.v4.content.CursorLoader;\r
+import android.support.v4.content.Loader;\r
+import android.view.LayoutInflater;\r
+import android.view.View;\r
+import android.view.ViewGroup;\r
+import android.widget.AdapterView;\r
+import android.widget.AdapterView.OnItemClickListener;\r
+import android.widget.ListView;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.NowPlayingCursor;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.adapters.RecentlyAddedAdapter;\r
+import com.andrew.apollo.service.ApolloService;\r
+import com.andrew.apollo.utils.MusicUtils;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ */\r
+public class RecentlyAddedFragment extends Fragment implements LoaderCallbacks<Cursor>,\r
+        OnItemClickListener, Constants {\r
+\r
+    // Adapter\r
+    private RecentlyAddedAdapter mRecentlyAddedAdapter;\r
+\r
+    // ListView\r
+    private ListView mListView;\r
+\r
+    // Cursor\r
+    private Cursor mCursor;\r
+\r
+    // Audio columns\r
+    public static int mTitleIndex, mAlbumIndex, mArtistIndex, mMediaIdIndex;\r
+\r
+    // Bundle\r
+    public RecentlyAddedFragment() {\r
+    }\r
+\r
+    public RecentlyAddedFragment(Bundle args) {\r
+        setArguments(args);\r
+    }\r
+\r
+    @Override\r
+    public void onActivityCreated(Bundle savedInstanceState) {\r
+        super.onActivityCreated(savedInstanceState);\r
+        // Adapter\r
+        mRecentlyAddedAdapter = new RecentlyAddedAdapter(getActivity(), R.layout.listview_items,\r
+                null, new String[] {}, new int[] {}, 0);\r
+        mListView.setOnCreateContextMenuListener(this);\r
+        mListView.setAdapter(mRecentlyAddedAdapter);\r
+        mListView.setOnItemClickListener(this);\r
+\r
+        // Important!\r
+        getLoaderManager().initLoader(0, null, this);\r
+    }\r
+\r
+    @Override\r
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\r
+        View root = inflater.inflate(R.layout.listview, container, false);\r
+        mListView = (ListView)root.findViewById(android.R.id.list);\r
+        return root;\r
+    }\r
+\r
+    @Override\r
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {\r
+        String[] projection = new String[] {\r
+                BaseColumns._ID, MediaColumns.TITLE, AudioColumns.ALBUM, AudioColumns.ARTIST\r
+        };\r
+        StringBuilder where = new StringBuilder();\r
+        String sortOrder = MediaColumns.DATE_ADDED + " DESC";\r
+        Uri uri = Audio.Media.EXTERNAL_CONTENT_URI;\r
+        int X = MusicUtils.getIntPref(getActivity(), NUMWEEKS, 5) * 3600 * 24 * 7;\r
+        where = new StringBuilder();\r
+        where.append(MediaColumns.TITLE + " != ''");\r
+        where.append(" AND " + AudioColumns.IS_MUSIC + "=1");\r
+        where.append(" AND " + MediaColumns.DATE_ADDED + ">"\r
+                + (System.currentTimeMillis() / 1000 - X));\r
+        return new CursorLoader(getActivity(), uri, projection, where.toString(), null, sortOrder);\r
+    }\r
+\r
+    @Override\r
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {\r
+        // Check for database errors\r
+        if (data == null) {\r
+            return;\r
+        }\r
+\r
+        mMediaIdIndex = data.getColumnIndexOrThrow(BaseColumns._ID);\r
+        mTitleIndex = data.getColumnIndexOrThrow(MediaColumns.TITLE);\r
+        mArtistIndex = data.getColumnIndexOrThrow(AudioColumns.ARTIST);\r
+        mAlbumIndex = data.getColumnIndexOrThrow(AudioColumns.ALBUM);\r
+        mRecentlyAddedAdapter.changeCursor(data);\r
+        mCursor = data;\r
+    }\r
+\r
+    @Override\r
+    public void onLoaderReset(Loader<Cursor> loader) {\r
+        if (mRecentlyAddedAdapter != null)\r
+            mRecentlyAddedAdapter.changeCursor(null);\r
+    }\r
+\r
+    @Override\r
+    public void onSaveInstanceState(Bundle outState) {\r
+        outState.putAll(getArguments() != null ? getArguments() : new Bundle());\r
+        super.onSaveInstanceState(outState);\r
+    }\r
+\r
+    @Override\r
+    public void onItemClick(AdapterView<?> parent, View v, int position, long id) {\r
+        if (mCursor instanceof NowPlayingCursor) {\r
+            if (MusicUtils.mService != null) {\r
+                MusicUtils.setQueuePosition(position);\r
+                return;\r
+            }\r
+        }\r
+        MusicUtils.playAll(getActivity(), mCursor, position);\r
+    }\r
+\r
+    /**\r
+     * Update the list as needed\r
+     */\r
+    private final BroadcastReceiver mMediaStatusReceiver = new BroadcastReceiver() {\r
+\r
+        @Override\r
+        public void onReceive(Context context, Intent intent) {\r
+            if (mListView != null) {\r
+                mListView.invalidateViews();\r
+            }\r
+        }\r
+\r
+    };\r
+\r
+    @Override\r
+    public void onStart() {\r
+        super.onStart();\r
+        IntentFilter filter = new IntentFilter();\r
+        filter.addAction(ApolloService.META_CHANGED);\r
+        filter.addAction(ApolloService.PLAYSTATE_CHANGED);\r
+        getActivity().registerReceiver(mMediaStatusReceiver, filter);\r
+    }\r
+\r
+    @Override\r
+    public void onStop() {\r
+        getActivity().unregisterReceiver(mMediaStatusReceiver);\r
+        super.onStop();\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/list/fragments/TracksFragment.java b/src/com/andrew/apollo/list/fragments/TracksFragment.java
new file mode 100644 (file)
index 0000000..5fbfa11
--- /dev/null
@@ -0,0 +1,463 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.list.fragments;\r
+\r
+import android.content.BroadcastReceiver;\r
+import android.content.Context;\r
+import android.content.Intent;\r
+import android.content.IntentFilter;\r
+import android.database.Cursor;\r
+import android.net.Uri;\r
+import android.os.Bundle;\r
+import android.provider.BaseColumns;\r
+import android.provider.MediaStore.Audio;\r
+import android.provider.MediaStore.Audio.Albums;\r
+import android.provider.MediaStore.Audio.Artists;\r
+import android.provider.MediaStore.Audio.AudioColumns;\r
+import android.provider.MediaStore.Audio.Genres;\r
+import android.provider.MediaStore.Audio.Playlists;\r
+import android.provider.MediaStore.MediaColumns;\r
+import android.support.v4.app.Fragment;\r
+import android.support.v4.app.LoaderManager.LoaderCallbacks;\r
+import android.support.v4.content.CursorLoader;\r
+import android.support.v4.content.Loader;\r
+import android.view.ContextMenu;\r
+import android.view.ContextMenu.ContextMenuInfo;\r
+import android.view.LayoutInflater;\r
+import android.view.MenuItem;\r
+import android.view.View;\r
+import android.view.ViewGroup;\r
+import android.widget.AdapterView;\r
+import android.widget.AdapterView.AdapterContextMenuInfo;\r
+import android.widget.AdapterView.OnItemClickListener;\r
+import android.widget.LinearLayout;\r
+import android.widget.ListView;\r
+import android.widget.TextView;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.NowPlayingCursor;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.adapters.TrackAdapter;\r
+import com.andrew.apollo.service.ApolloService;\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+import com.andrew.apollo.utils.MusicUtils;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ */\r
+public class TracksFragment extends Fragment implements LoaderCallbacks<Cursor>,\r
+        OnItemClickListener, Constants {\r
+\r
+    // Adapter\r
+    private TrackAdapter mTrackAdapter;\r
+\r
+    // ListView\r
+    private ListView mListView;\r
+\r
+    // Cursor\r
+    private Cursor mCursor;\r
+\r
+    // Playlist ID\r
+    private long mPlaylistId = -1;\r
+\r
+    // Selected position\r
+    private int mSelectedPosition;\r
+\r
+    // Used to set ringtone\r
+    private long mSelectedId;\r
+\r
+    // Options\r
+    private final int PLAY_SELECTION = 6;\r
+\r
+    private final int USE_AS_RINGTONE = 7;\r
+\r
+    private final int ADD_TO_PLAYLIST = 8;\r
+\r
+    private final int SEARCH = 9;\r
+\r
+    private final int REMOVE = 10;\r
+\r
+    private boolean mEditMode = false;\r
+\r
+    // Audio columns\r
+    public static int mTitleIndex, mAlbumIndex, mArtistIndex, mMediaIdIndex;\r
+\r
+    // Bundle\r
+    public TracksFragment() {\r
+    }\r
+\r
+    public TracksFragment(Bundle args) {\r
+        setArguments(args);\r
+    }\r
+\r
+    @Override\r
+    public void onActivityCreated(Bundle savedInstanceState) {\r
+        super.onActivityCreated(savedInstanceState);\r
+\r
+        isEditMode();\r
+\r
+        // Adapter\r
+        mTrackAdapter = new TrackAdapter(getActivity(), R.layout.listview_items, null,\r
+                new String[] {}, new int[] {}, 0);\r
+        mListView.setOnCreateContextMenuListener(this);\r
+        mListView.setOnItemClickListener(this);\r
+        mListView.setAdapter(mTrackAdapter);\r
+\r
+        // Important!\r
+        getLoaderManager().initLoader(0, null, this);\r
+    }\r
+\r
+    @Override\r
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\r
+        View root = inflater.inflate(R.layout.listview, container, false);\r
+        mListView = (ListView)root.findViewById(android.R.id.list);\r
+\r
+        // Align the track list with the header, in other words,OCD.\r
+        TextView mHeader = (TextView)root.findViewById(R.id.title);\r
+        int eight = (int)getActivity().getResources().getDimension(\r
+                R.dimen.list_separator_padding_left_right);\r
+        mHeader.setPadding(eight, 0, 0, 0);\r
+\r
+        // Set the header while in @TracksBrowser\r
+        String header = getActivity().getResources().getString(R.string.track_header);\r
+        int left = getActivity().getResources().getInteger(R.integer.listview_padding_left);\r
+        int right = getActivity().getResources().getInteger(R.integer.listview_padding_right);\r
+        ApolloUtils.listHeader(this, root, header);\r
+        ApolloUtils.setListPadding(this, mListView, left, 0, right, 0);\r
+\r
+        // Hide the extra spacing from the Bottom ActionBar in the queue\r
+        // Fragment in @AudioPlayerHolder\r
+        if (getArguments() != null) {\r
+            mPlaylistId = getArguments().getLong(BaseColumns._ID);\r
+            String mimeType = getArguments().getString(MIME_TYPE);\r
+            if (Audio.Playlists.CONTENT_TYPE.equals(mimeType)) {\r
+                switch ((int)mPlaylistId) {\r
+                    case (int)PLAYLIST_QUEUE:\r
+                        LinearLayout emptyness = (LinearLayout)root.findViewById(R.id.empty_view);\r
+                        emptyness.setVisibility(View.GONE);\r
+                }\r
+            }\r
+        }\r
+        return root;\r
+    }\r
+\r
+    @Override\r
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {\r
+        String[] projection = new String[] {\r
+                BaseColumns._ID, MediaColumns.TITLE, AudioColumns.ALBUM, AudioColumns.ARTIST\r
+        };\r
+        StringBuilder where = new StringBuilder();\r
+        String sortOrder = Audio.Media.DEFAULT_SORT_ORDER;\r
+        where.append(AudioColumns.IS_MUSIC + "=1").append(" AND " + MediaColumns.TITLE + " != ''");\r
+        Uri uri = Audio.Media.EXTERNAL_CONTENT_URI;\r
+        if (getArguments() != null) {\r
+            mPlaylistId = getArguments().getLong(BaseColumns._ID);\r
+            String mimeType = getArguments().getString(MIME_TYPE);\r
+            if (Audio.Playlists.CONTENT_TYPE.equals(mimeType)) {\r
+                where = new StringBuilder();\r
+                where.append(AudioColumns.IS_MUSIC + "=1");\r
+                where.append(" AND " + MediaColumns.TITLE + " != ''");\r
+                switch ((int)mPlaylistId) {\r
+                    case (int)PLAYLIST_QUEUE:\r
+                        uri = Audio.Media.EXTERNAL_CONTENT_URI;\r
+                        long[] mNowPlaying = MusicUtils.getQueue();\r
+                        if (mNowPlaying.length == 0)\r
+                            return null;\r
+                        where = new StringBuilder();\r
+                        where.append(BaseColumns._ID + " IN (");\r
+                        if (mNowPlaying == null || mNowPlaying.length <= 0)\r
+                            return null;\r
+                        for (long queue_id : mNowPlaying) {\r
+                            where.append(queue_id + ",");\r
+                        }\r
+                        where.deleteCharAt(where.length() - 1);\r
+                        where.append(")");\r
+                        break;\r
+                    case (int)PLAYLIST_FAVORITES:\r
+                        long favorites_id = MusicUtils.getFavoritesId(getActivity());\r
+                        projection = new String[] {\r
+                                Playlists.Members._ID, Playlists.Members.AUDIO_ID,\r
+                                MediaColumns.TITLE, AudioColumns.ALBUM, AudioColumns.ARTIST\r
+                        };\r
+                        uri = Playlists.Members.getContentUri(EXTERNAL, favorites_id);\r
+                        sortOrder = Playlists.Members.DEFAULT_SORT_ORDER;\r
+                        break;\r
+                    default:\r
+                        if (id < 0)\r
+                            return null;\r
+                        projection = new String[] {\r
+                                Playlists.Members._ID, Playlists.Members.AUDIO_ID,\r
+                                MediaColumns.TITLE, AudioColumns.ALBUM, AudioColumns.ARTIST,\r
+                                AudioColumns.DURATION\r
+                        };\r
+\r
+                        uri = Playlists.Members.getContentUri(EXTERNAL, mPlaylistId);\r
+                        sortOrder = Playlists.Members.DEFAULT_SORT_ORDER;\r
+                        break;\r
+                }\r
+            } else if (Audio.Genres.CONTENT_TYPE.equals(mimeType)) {\r
+                long genreId = getArguments().getLong(BaseColumns._ID);\r
+                uri = Genres.Members.getContentUri(EXTERNAL, genreId);\r
+                projection = new String[] {\r
+                        BaseColumns._ID, MediaColumns.TITLE, AudioColumns.ALBUM,\r
+                        AudioColumns.ARTIST\r
+                };\r
+                where = new StringBuilder();\r
+                where.append(AudioColumns.IS_MUSIC + "=1").append(\r
+                        " AND " + MediaColumns.TITLE + " != ''");\r
+                sortOrder = Genres.Members.DEFAULT_SORT_ORDER;\r
+            } else {\r
+                if (Audio.Albums.CONTENT_TYPE.equals(mimeType)) {\r
+                    long albumId = getArguments().getLong(BaseColumns._ID);\r
+                    where.append(" AND " + AudioColumns.ALBUM_ID + "=" + albumId);\r
+                    sortOrder = Audio.Albums.DEFAULT_SORT_ORDER;\r
+                } else if (Audio.Artists.CONTENT_TYPE.equals(mimeType)) {\r
+                    sortOrder = MediaColumns.TITLE;\r
+                    long artist_id = getArguments().getLong(BaseColumns._ID);\r
+                    where.append(" AND " + AudioColumns.ARTIST_ID + "=" + artist_id);\r
+                }\r
+            }\r
+        }\r
+        return new CursorLoader(getActivity(), uri, projection, where.toString(), null, sortOrder);\r
+    }\r
+\r
+    @Override\r
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {\r
+        // Check for database errors\r
+        if (data == null) {\r
+            return;\r
+        }\r
+\r
+        if (getArguments() != null\r
+                && Playlists.CONTENT_TYPE.equals(getArguments().getString(MIME_TYPE))\r
+                && (getArguments().getLong(BaseColumns._ID) >= 0 || getArguments().getLong(\r
+                        BaseColumns._ID) == PLAYLIST_FAVORITES)) {\r
+            mMediaIdIndex = data.getColumnIndexOrThrow(Playlists.Members.AUDIO_ID);\r
+            mTitleIndex = data.getColumnIndexOrThrow(MediaColumns.TITLE);\r
+            mAlbumIndex = data.getColumnIndexOrThrow(AudioColumns.ALBUM);\r
+            // FIXME\r
+            // mArtistIndex =\r
+            // data.getColumnIndexOrThrow(Playlists.Members.ARTIST);\r
+        } else if (getArguments() != null\r
+                && Genres.CONTENT_TYPE.equals(getArguments().getString(MIME_TYPE))) {\r
+            mMediaIdIndex = data.getColumnIndexOrThrow(BaseColumns._ID);\r
+            mTitleIndex = data.getColumnIndexOrThrow(MediaColumns.TITLE);\r
+            mArtistIndex = data.getColumnIndexOrThrow(AudioColumns.ARTIST);\r
+            mAlbumIndex = data.getColumnIndexOrThrow(AudioColumns.ALBUM);\r
+        } else if (getArguments() != null\r
+                && Artists.CONTENT_TYPE.equals(getArguments().getString(MIME_TYPE))) {\r
+            mTitleIndex = data.getColumnIndexOrThrow(MediaColumns.TITLE);\r
+            // mArtistIndex is "line2" of the ListView\r
+            mArtistIndex = data.getColumnIndexOrThrow(AudioColumns.ALBUM);\r
+        } else if (getArguments() != null\r
+                && Albums.CONTENT_TYPE.equals(getArguments().getString(MIME_TYPE))) {\r
+            mMediaIdIndex = data.getColumnIndexOrThrow(BaseColumns._ID);\r
+            mTitleIndex = data.getColumnIndexOrThrow(MediaColumns.TITLE);\r
+            mArtistIndex = data.getColumnIndexOrThrow(AudioColumns.ARTIST);\r
+        } else {\r
+            mMediaIdIndex = data.getColumnIndexOrThrow(BaseColumns._ID);\r
+            mTitleIndex = data.getColumnIndexOrThrow(MediaColumns.TITLE);\r
+            mArtistIndex = data.getColumnIndexOrThrow(AudioColumns.ARTIST);\r
+            mAlbumIndex = data.getColumnIndexOrThrow(AudioColumns.ALBUM);\r
+        }\r
+        mTrackAdapter.changeCursor(data);\r
+        mCursor = data;\r
+    }\r
+\r
+    @Override\r
+    public void onLoaderReset(Loader<Cursor> loader) {\r
+        if (mTrackAdapter != null)\r
+            mTrackAdapter.changeCursor(null);\r
+    }\r
+\r
+    @Override\r
+    public void onSaveInstanceState(Bundle outState) {\r
+        outState.putAll(getArguments() != null ? getArguments() : new Bundle());\r
+        super.onSaveInstanceState(outState);\r
+    }\r
+\r
+    @Override\r
+    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {\r
+        menu.add(0, PLAY_SELECTION, 0, getResources().getString(R.string.play_all));\r
+        menu.add(0, ADD_TO_PLAYLIST, 0, getResources().getString(R.string.add_to_playlist));\r
+        menu.add(0, USE_AS_RINGTONE, 0, getResources().getString(R.string.use_as_ringtone));\r
+        if (mEditMode) {\r
+            menu.add(0, REMOVE, 0, R.string.remove);\r
+        }\r
+        menu.add(0, SEARCH, 0, getResources().getString(R.string.search));\r
+\r
+        AdapterContextMenuInfo mi = (AdapterContextMenuInfo)menuInfo;\r
+        mSelectedPosition = mi.position;\r
+        mCursor.moveToPosition(mSelectedPosition);\r
+\r
+        try {\r
+            mSelectedId = mCursor.getLong(mMediaIdIndex);\r
+        } catch (IllegalArgumentException ex) {\r
+            mSelectedId = mi.id;\r
+        }\r
+\r
+        String title = mCursor.getString(mTitleIndex);\r
+        menu.setHeaderTitle(title);\r
+        super.onCreateContextMenu(menu, v, menuInfo);\r
+    }\r
+\r
+    @Override\r
+    public boolean onContextItemSelected(MenuItem item) {\r
+        switch (item.getItemId()) {\r
+            case PLAY_SELECTION:\r
+                int position = mSelectedPosition;\r
+                MusicUtils.playAll(getActivity(), mCursor, position);\r
+                break;\r
+            case ADD_TO_PLAYLIST: {\r
+                Intent intent = new Intent(INTENT_ADD_TO_PLAYLIST);\r
+                long[] list = new long[] {\r
+                    mSelectedId\r
+                };\r
+                intent.putExtra(INTENT_PLAYLIST_LIST, list);\r
+                getActivity().startActivity(intent);\r
+                break;\r
+            }\r
+            case USE_AS_RINGTONE:\r
+                MusicUtils.setRingtone(getActivity(), mSelectedId);\r
+                break;\r
+            case REMOVE: {\r
+                removePlaylistItem(mSelectedPosition);\r
+                break;\r
+            }\r
+            case SEARCH: {\r
+                MusicUtils.doSearch(getActivity(), mCursor, mTitleIndex);\r
+                break;\r
+            }\r
+            default:\r
+                break;\r
+        }\r
+        return super.onContextItemSelected(item);\r
+    }\r
+\r
+    @Override\r
+    public void onItemClick(AdapterView<?> parent, View v, int position, long id) {\r
+        if (mCursor instanceof NowPlayingCursor) {\r
+            if (MusicUtils.mService != null) {\r
+                MusicUtils.setQueuePosition(position);\r
+                return;\r
+            }\r
+        }\r
+        MusicUtils.playAll(getActivity(), mCursor, position);\r
+    }\r
+\r
+    /**\r
+     * Update the list as needed\r
+     */\r
+    private final BroadcastReceiver mMediaStatusReceiver = new BroadcastReceiver() {\r
+\r
+        @Override\r
+        public void onReceive(Context context, Intent intent) {\r
+            if (mListView != null) {\r
+                mListView.invalidateViews();\r
+                // Scroll to the currently playing track in the queue\r
+                if (mPlaylistId == PLAYLIST_QUEUE)\r
+                    mListView.postDelayed(new Runnable() {\r
+                        @Override\r
+                        public void run() {\r
+                            mListView.setSelection(MusicUtils.getQueuePosition());\r
+                        }\r
+                    }, 100);\r
+            }\r
+        }\r
+\r
+    };\r
+\r
+    @Override\r
+    public void onStart() {\r
+        super.onStart();\r
+\r
+        IntentFilter filter = new IntentFilter();\r
+        filter.addAction(ApolloService.META_CHANGED);\r
+        filter.addAction(ApolloService.QUEUE_CHANGED);\r
+        filter.addAction(ApolloService.PLAYSTATE_CHANGED);\r
+        getActivity().registerReceiver(mMediaStatusReceiver, filter);\r
+    }\r
+\r
+    @Override\r
+    public void onStop() {\r
+        getActivity().unregisterReceiver(mMediaStatusReceiver);\r
+        super.onStop();\r
+    }\r
+\r
+    /**\r
+     * @param which\r
+     */\r
+    private void removePlaylistItem(int which) {\r
+\r
+        mCursor.moveToPosition(which);\r
+        long id = mCursor.getLong(mMediaIdIndex);\r
+        if (mPlaylistId >= 0) {\r
+            Uri uri = Playlists.Members.getContentUri(EXTERNAL, mPlaylistId);\r
+            getActivity().getContentResolver().delete(uri, Playlists.Members.AUDIO_ID + "=" + id,\r
+                    null);\r
+        } else if (mPlaylistId == PLAYLIST_QUEUE) {\r
+            MusicUtils.removeTrack(id);\r
+            reloadQueueCursor();\r
+        } else if (mPlaylistId == PLAYLIST_FAVORITES) {\r
+            MusicUtils.removeFromFavorites(getActivity(), id);\r
+        }\r
+        mListView.invalidateViews();\r
+    }\r
+\r
+    /**\r
+     * Reload the queue after we remove a track\r
+     */\r
+    private void reloadQueueCursor() {\r
+        if (mPlaylistId == PLAYLIST_QUEUE) {\r
+            String[] cols = new String[] {\r
+                    BaseColumns._ID, MediaColumns.TITLE, MediaColumns.DATA, AudioColumns.ALBUM,\r
+                    AudioColumns.ARTIST, AudioColumns.ARTIST_ID\r
+            };\r
+            StringBuilder selection = new StringBuilder();\r
+            selection.append(AudioColumns.IS_MUSIC + "=1");\r
+            selection.append(" AND " + MediaColumns.TITLE + " != ''");\r
+            Uri uri = Audio.Media.EXTERNAL_CONTENT_URI;\r
+            long[] mNowPlaying = MusicUtils.getQueue();\r
+            if (mNowPlaying.length == 0) {\r
+            }\r
+            selection = new StringBuilder();\r
+            selection.append(BaseColumns._ID + " IN (");\r
+            for (int i = 0; i < mNowPlaying.length; i++) {\r
+                selection.append(mNowPlaying[i]);\r
+                if (i < mNowPlaying.length - 1) {\r
+                    selection.append(",");\r
+                }\r
+            }\r
+            selection.append(")");\r
+            mCursor = MusicUtils.query(getActivity(), uri, cols, selection.toString(), null, null);\r
+            mTrackAdapter.changeCursor(mCursor);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Check if we're viewing the contents of a playlist\r
+     */\r
+    public void isEditMode() {\r
+        if (getArguments() != null) {\r
+            String mimetype = getArguments().getString(MIME_TYPE);\r
+            if (Audio.Playlists.CONTENT_TYPE.equals(mimetype)) {\r
+                mPlaylistId = getArguments().getLong(BaseColumns._ID);\r
+                switch ((int)mPlaylistId) {\r
+                    case (int)PLAYLIST_QUEUE:\r
+                        mEditMode = true;\r
+                        break;\r
+                    case (int)PLAYLIST_FAVORITES:\r
+                        mEditMode = true;\r
+                        break;\r
+                    default:\r
+                        if (mPlaylistId > 0) {\r
+                            mEditMode = true;\r
+                        }\r
+                        break;\r
+                }\r
+            }\r
+        }\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/menu/PlaylistDialog.java b/src/com/andrew/apollo/menu/PlaylistDialog.java
new file mode 100644 (file)
index 0000000..05159b8
--- /dev/null
@@ -0,0 +1,328 @@
+/*
+ *              Copyright (C) 2011 The MusicMod 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.andrew.apollo.menu;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.ContentResolver;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.DialogInterface.OnClickListener;
+import android.content.DialogInterface.OnShowListener;
+import android.database.Cursor;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Audio;
+import android.support.v4.app.FragmentActivity;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.DisplayMetrics;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.Toast;
+
+import com.andrew.apollo.Constants;
+import com.andrew.apollo.R;
+import com.andrew.apollo.utils.MusicUtils;
+
+public class PlaylistDialog extends FragmentActivity implements Constants, TextWatcher,
+        OnCancelListener, OnShowListener {
+
+    private AlertDialog mPlaylistDialog;
+
+    private String action;
+
+    private EditText mPlaylist;
+
+    private String mDefaultName, mOriginalName;
+
+    private long mRenameId;
+
+    private long[] mList = new long[] {};
+
+    private final OnClickListener mRenamePlaylistListener = new OnClickListener() {
+
+        @Override
+        public void onClick(DialogInterface dialog, int which) {
+
+            String name = mPlaylist.getText().toString();
+            MusicUtils.renamePlaylist(PlaylistDialog.this, mRenameId, name);
+            finish();
+        }
+    };
+
+    private final OnClickListener mCreatePlaylistListener = new OnClickListener() {
+
+        @Override
+        public void onClick(DialogInterface dialog, int which) {
+
+            String name = mPlaylist.getText().toString();
+            if (name != null && name.length() > 0) {
+                int id = idForplaylist(name);
+                if (id >= 0) {
+                    MusicUtils.clearPlaylist(PlaylistDialog.this, id);
+                    MusicUtils.addToPlaylist(PlaylistDialog.this, mList, id);
+                } else {
+                    long new_id = MusicUtils.createPlaylist(PlaylistDialog.this, name);
+                    if (new_id >= 0) {
+                        MusicUtils.addToPlaylist(PlaylistDialog.this, mList, new_id);
+                    }
+                }
+                finish();
+            }
+        }
+    };
+
+    @Override
+    public void afterTextChanged(Editable s) {
+
+        // don't care about this one
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+        // don't care about this one
+    }
+
+    @Override
+    public void onCancel(DialogInterface dialog) {
+
+        if (dialog == mPlaylistDialog) {
+            finish();
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+
+        super.onCreate(icicle);
+        setContentView(new LinearLayout(this));
+
+        action = getIntent().getAction();
+
+        mRenameId = icicle != null ? icicle.getLong(INTENT_KEY_RENAME) : getIntent().getLongExtra(
+                INTENT_KEY_RENAME, -1);
+        mList = icicle != null ? icicle.getLongArray(INTENT_PLAYLIST_LIST) : getIntent()
+                .getLongArrayExtra(INTENT_PLAYLIST_LIST);
+        if (INTENT_RENAME_PLAYLIST.equals(action)) {
+            mOriginalName = nameForId(mRenameId);
+            mDefaultName = icicle != null ? icicle.getString(INTENT_KEY_DEFAULT_NAME)
+                    : mOriginalName;
+        } else if (INTENT_CREATE_PLAYLIST.equals(action)) {
+            mDefaultName = icicle != null ? icicle.getString(INTENT_KEY_DEFAULT_NAME)
+                    : makePlaylistName();
+            mOriginalName = mDefaultName;
+        }
+
+        DisplayMetrics dm = new DisplayMetrics();
+        dm = getResources().getDisplayMetrics();
+
+        mPlaylistDialog = new AlertDialog.Builder(this).create();
+        mPlaylistDialog.setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+        if (action != null && mRenameId >= 0 && mOriginalName != null || mDefaultName != null) {
+
+            mPlaylist = new EditText(this);
+            mPlaylist.setSingleLine(true);
+            mPlaylist.setText(mDefaultName);
+            mPlaylist.setSelection(mDefaultName.length());
+            mPlaylist.addTextChangedListener(this);
+
+            mPlaylistDialog.setIcon(android.R.drawable.ic_dialog_info);
+            String promptformat;
+            String prompt = "";
+            if (INTENT_RENAME_PLAYLIST.equals(action)) {
+                promptformat = getString(R.string.rename_playlist);
+                prompt = String.format(promptformat, mOriginalName, mDefaultName);
+            } else if (INTENT_CREATE_PLAYLIST.equals(action)) {
+                promptformat = getString(R.string.new_playlist);
+                prompt = String.format(promptformat, mDefaultName);
+            }
+
+            mPlaylistDialog.setTitle(prompt);
+            mPlaylistDialog.setView(mPlaylist, (int)(8 * dm.density), (int)(8 * dm.density),
+                    (int)(8 * dm.density), (int)(4 * dm.density));
+            if (INTENT_RENAME_PLAYLIST.equals(action)) {
+                mPlaylistDialog.setButton(Dialog.BUTTON_POSITIVE, getString(R.string.save),
+                        mRenamePlaylistListener);
+                mPlaylistDialog.setOnShowListener(this);
+            } else if (INTENT_CREATE_PLAYLIST.equals(action)) {
+                mPlaylistDialog.setButton(Dialog.BUTTON_POSITIVE, getString(R.string.save),
+                        mCreatePlaylistListener);
+            }
+            mPlaylistDialog.setButton(Dialog.BUTTON_NEGATIVE, getString(android.R.string.cancel),
+                    new OnClickListener() {
+
+                        @Override
+                        public void onClick(DialogInterface dialog, int which) {
+
+                            finish();
+                        }
+                    });
+            mPlaylistDialog.setOnCancelListener(this);
+            mPlaylistDialog.show();
+        } else {
+            Toast.makeText(this, R.string.error, Toast.LENGTH_SHORT).show();
+            finish();
+        }
+
+    }
+
+    @Override
+    public void onPause() {
+
+        if (mPlaylistDialog != null && mPlaylistDialog.isShowing()) {
+            mPlaylistDialog.dismiss();
+        }
+        super.onPause();
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outcicle) {
+
+        if (INTENT_RENAME_PLAYLIST.equals(action)) {
+            outcicle.putString(INTENT_KEY_DEFAULT_NAME, mPlaylist.getText().toString());
+            outcicle.putLong(INTENT_KEY_RENAME, mRenameId);
+        } else if (INTENT_CREATE_PLAYLIST.equals(action)) {
+            outcicle.putString(INTENT_KEY_DEFAULT_NAME, mPlaylist.getText().toString());
+        }
+    }
+
+    @Override
+    public void onShow(DialogInterface dialog) {
+
+        if (dialog == mPlaylistDialog) {
+            setSaveButton();
+        }
+    }
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+
+        setSaveButton();
+    }
+
+    private int idForplaylist(String name) {
+
+        Cursor cursor = MusicUtils.query(this, Audio.Playlists.EXTERNAL_CONTENT_URI, new String[] {
+            Audio.Playlists._ID
+        }, Audio.Playlists.NAME + "=?", new String[] {
+            name
+        }, Audio.Playlists.NAME, 0);
+        int id = -1;
+        if (cursor != null) {
+            cursor.moveToFirst();
+            if (!cursor.isAfterLast()) {
+                id = cursor.getInt(0);
+            }
+            cursor.close();
+        }
+
+        return id;
+    }
+
+    private String makePlaylistName() {
+
+        String template = getString(R.string.new_playlist_name_template);
+        int num = 1;
+
+        String[] cols = new String[] {
+            Audio.Playlists.NAME
+        };
+        ContentResolver resolver = getContentResolver();
+        String whereclause = Audio.Playlists.NAME + " != ''";
+        Cursor cursor = resolver.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, cols,
+                whereclause, null, Audio.Playlists.NAME);
+
+        if (cursor == null)
+            return null;
+
+        String suggestedname;
+        suggestedname = String.format(template, num++);
+
+        // Need to loop until we've made 1 full pass through without finding a
+        // match. Looping more than once shouldn't happen very often, but will
+        // happen if you have playlists named
+        // "New Playlist 1"/10/2/3/4/5/6/7/8/9, where making only one pass would
+        // result in "New Playlist 10" being erroneously picked for the new
+        // name.
+        boolean done = false;
+        while (!done) {
+            done = true;
+            cursor.moveToFirst();
+            while (!cursor.isAfterLast()) {
+                String playlistname = cursor.getString(0);
+                if (playlistname.compareToIgnoreCase(suggestedname) == 0) {
+                    suggestedname = String.format(template, num++);
+                    done = false;
+                }
+                cursor.moveToNext();
+            }
+        }
+        cursor.close();
+        return suggestedname;
+    };
+
+    private String nameForId(long id) {
+
+        Cursor cursor = MusicUtils.query(this, Audio.Playlists.EXTERNAL_CONTENT_URI, new String[] {
+            Audio.Playlists.NAME
+        }, Audio.Playlists._ID + "=?", new String[] {
+            Long.valueOf(id).toString()
+        }, Audio.Playlists.NAME);
+        String name = null;
+        if (cursor != null) {
+            cursor.moveToFirst();
+            if (!cursor.isAfterLast()) {
+                name = cursor.getString(0);
+            }
+            cursor.close();
+        }
+        return name;
+    }
+
+    private void setSaveButton() {
+
+        String typedname = mPlaylist.getText().toString();
+        Button button = mPlaylistDialog.getButton(Dialog.BUTTON_POSITIVE);
+        if (button == null)
+            return;
+        if (typedname.trim().length() == 0 || PLAYLIST_NAME_FAVORITES.equals(typedname)) {
+            button.setEnabled(false);
+        } else {
+            button.setEnabled(true);
+            if (idForplaylist(typedname) >= 0 && !mOriginalName.equals(typedname)) {
+                button.setText(R.string.overwrite);
+            } else {
+                button.setText(R.string.save);
+            }
+        }
+        button.invalidate();
+    }
+
+    @Override
+    protected void onResume() {
+
+        super.onResume();
+        if (mPlaylistDialog != null) {
+            mPlaylistDialog.show();
+        }
+    }
+}
diff --git a/src/com/andrew/apollo/menu/PlaylistPicker.java b/src/com/andrew/apollo/menu/PlaylistPicker.java
new file mode 100644 (file)
index 0000000..8eb7afc
--- /dev/null
@@ -0,0 +1,121 @@
+
+package com.andrew.apollo.menu;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.widget.LinearLayout;
+import android.widget.Toast;
+
+import com.andrew.apollo.R;
+import com.andrew.apollo.utils.MusicUtils;
+
+public class PlaylistPicker extends FragmentActivity implements DialogInterface.OnClickListener,
+        DialogInterface.OnCancelListener, com.andrew.apollo.Constants {
+
+    private AlertDialog mPlayListPickerDialog;
+
+    List<Map<String, String>> mAllPlayLists = new ArrayList<Map<String, String>>();
+
+    List<String> mPlayListNames = new ArrayList<String>();
+
+    long[] mList = new long[] {};
+
+    @Override
+    public void onCancel(DialogInterface dialog) {
+        finish();
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+
+        long listId = Long.valueOf(mAllPlayLists.get(which).get("id"));
+        String listName = mAllPlayLists.get(which).get("name");
+        Intent intent;
+        boolean mCreateShortcut = getIntent().getAction().equals(Intent.ACTION_CREATE_SHORTCUT);
+
+        if (mCreateShortcut) {
+            final Intent shortcut = new Intent();
+            // shortcut.setAction(INTENT_PLAY_SHORTCUT);
+            shortcut.putExtra("id", listId);
+
+            intent = new Intent();
+            intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcut);
+            intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, listName);
+            intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
+                    Intent.ShortcutIconResource.fromContext(this, R.drawable.ic_launcher));
+            setResult(RESULT_OK, intent);
+        } else {
+            if (listId >= 0) {
+                MusicUtils.addToPlaylist(this, mList, listId);
+            } else if (listId == PLAYLIST_QUEUE) {
+                MusicUtils.addToCurrentPlaylist(this, mList);
+            } else if (listId == PLAYLIST_NEW) {
+                intent = new Intent(INTENT_CREATE_PLAYLIST);
+                intent.putExtra(INTENT_PLAYLIST_LIST, mList);
+                startActivity(intent);
+            }
+        }
+        finish();
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+
+        super.onCreate(icicle);
+        setContentView(new LinearLayout(this));
+
+        if (getIntent().getAction() != null) {
+
+            if (INTENT_ADD_TO_PLAYLIST.equals(getIntent().getAction())
+                    && getIntent().getLongArrayExtra(INTENT_PLAYLIST_LIST) != null) {
+
+                MusicUtils.makePlaylistList(this, false, mAllPlayLists);
+                mList = getIntent().getLongArrayExtra(INTENT_PLAYLIST_LIST);
+                for (int i = 0; i < mAllPlayLists.size(); i++) {
+                    mPlayListNames.add(mAllPlayLists.get(i).get("name"));
+                }
+                mPlayListPickerDialog = new AlertDialog.Builder(this)
+                        .setTitle(R.string.add_to_playlist)
+                        .setItems(mPlayListNames.toArray(new CharSequence[mPlayListNames.size()]),
+                                this).setOnCancelListener(this).show();
+            } else if (getIntent().getAction().equals(Intent.ACTION_CREATE_SHORTCUT)) {
+                MusicUtils.makePlaylistList(this, true, mAllPlayLists);
+                for (int i = 0; i < mAllPlayLists.size(); i++) {
+                    mPlayListNames.add(mAllPlayLists.get(i).get("name"));
+                }
+                mPlayListPickerDialog = new AlertDialog.Builder(this)
+                        .setItems(mPlayListNames.toArray(new CharSequence[mPlayListNames.size()]),
+                                this).setOnCancelListener(this).show();
+            }
+        } else {
+            Toast.makeText(this, R.string.error, Toast.LENGTH_SHORT).show();
+            finish();
+        }
+    }
+
+    @Override
+    public void onPause() {
+
+        if (mPlayListPickerDialog != null && mPlayListPickerDialog.isShowing()) {
+            mPlayListPickerDialog.dismiss();
+        }
+        super.onPause();
+    }
+
+    @Override
+    protected void onResume() {
+
+        super.onResume();
+        if (mPlayListPickerDialog != null && !mPlayListPickerDialog.isShowing()) {
+            mPlayListPickerDialog.show();
+        }
+    }
+
+}
diff --git a/src/com/andrew/apollo/menu/PlaylistPickerDialog.java b/src/com/andrew/apollo/menu/PlaylistPickerDialog.java
new file mode 100644 (file)
index 0000000..7920fc7
--- /dev/null
@@ -0,0 +1,28 @@
+
+package com.andrew.apollo.menu;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.DialogInterface.OnClickListener;
+
+public class PlaylistPickerDialog extends AlertDialog implements OnCancelListener, OnClickListener {
+
+    public PlaylistPickerDialog(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void onCancel(DialogInterface dialog) {
+        // TODO Auto-generated method stub
+
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        // TODO Auto-generated method stub
+
+    }
+
+}
diff --git a/src/com/andrew/apollo/preferences/SettingsFragment.java b/src/com/andrew/apollo/preferences/SettingsFragment.java
new file mode 100644 (file)
index 0000000..3f38b0a
--- /dev/null
@@ -0,0 +1,22 @@
+\r
+package com.andrew.apollo.preferences;\r
+\r
+import android.os.Bundle;\r
+import android.preference.PreferenceFragment;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.R;\r
+\r
+public class SettingsFragment extends PreferenceFragment implements Constants {\r
+\r
+    public SettingsFragment() {\r
+    }\r
+\r
+    @Override\r
+    public void onActivityCreated(Bundle savedInstanceState) {\r
+        // Load settings XML\r
+        int preferencesResId = R.xml.settings;\r
+        addPreferencesFromResource(preferencesResId);\r
+        super.onActivityCreated(savedInstanceState);\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/preferences/SettingsHolder.java b/src/com/andrew/apollo/preferences/SettingsHolder.java
new file mode 100644 (file)
index 0000000..f9896ce
--- /dev/null
@@ -0,0 +1,220 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.preferences;\r
+\r
+import java.util.List;\r
+\r
+import android.content.BroadcastReceiver;\r
+import android.content.ComponentName;\r
+import android.content.Context;\r
+import android.content.Intent;\r
+import android.content.IntentFilter;\r
+import android.content.ServiceConnection;\r
+import android.content.SharedPreferences;\r
+import android.content.pm.PackageManager;\r
+import android.content.pm.ResolveInfo;\r
+import android.net.Uri;\r
+import android.os.Bundle;\r
+import android.os.IBinder;\r
+import android.preference.ListPreference;\r
+import android.preference.Preference;\r
+import android.preference.Preference.OnPreferenceChangeListener;\r
+import android.preference.PreferenceActivity;\r
+import android.view.MenuItem;\r
+import android.view.View;\r
+import android.view.View.OnClickListener;\r
+import android.widget.ImageView;\r
+import android.widget.TextView;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.IApolloService;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.activities.AudioPlayerHolder;\r
+import com.andrew.apollo.activities.MusicLibrary;\r
+import com.andrew.apollo.service.ApolloService;\r
+import com.andrew.apollo.service.ServiceToken;\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+import com.andrew.apollo.utils.MusicUtils;\r
+import com.andrew.apollo.utils.ThemeUtils;\r
+import com.androidquery.AQuery;\r
+\r
+/**\r
+ * @author Andrew Neal FIXME - Work on the IllegalStateException thrown when\r
+ *         using PreferenceFragment and theme chooser\r
+ */\r
+@SuppressWarnings("deprecation")\r
+public class SettingsHolder extends PreferenceActivity implements ServiceConnection, Constants {\r
+\r
+    // Service\r
+    private ServiceToken mToken;\r
+\r
+    @Override\r
+    protected void onCreate(Bundle savedInstanceState) {\r
+        // This should be called first thing\r
+        super.onCreate(savedInstanceState);\r
+\r
+        // Load settings XML\r
+        int preferencesResId = R.xml.settings;\r
+        addPreferencesFromResource(preferencesResId);\r
+\r
+        // Load the theme chooser\r
+        initThemeChooser();\r
+\r
+        // ActionBar\r
+        initActionBar();\r
+    }\r
+\r
+    @Override\r
+    public boolean onOptionsItemSelected(MenuItem item) {\r
+        switch (item.getItemId()) {\r
+            case android.R.id.home:\r
+                finish();\r
+                return true;\r
+        }\r
+        return super.onOptionsItemSelected(item);\r
+    }\r
+\r
+    @Override\r
+    public void onServiceConnected(ComponentName name, IBinder obj) {\r
+        MusicUtils.mService = IApolloService.Stub.asInterface(obj);\r
+    }\r
+\r
+    @Override\r
+    public void onServiceDisconnected(ComponentName name) {\r
+        MusicUtils.mService = null;\r
+    }\r
+\r
+    /**\r
+     * Update the ActionBar as needed\r
+     */\r
+    private final BroadcastReceiver mMediaStatusReceiver = new BroadcastReceiver() {\r
+\r
+        @Override\r
+        public void onReceive(Context context, Intent intent) {\r
+            // Update the ActionBar\r
+            initActionBar();\r
+        }\r
+\r
+    };\r
+\r
+    @Override\r
+    protected void onStart() {\r
+        // Bind to Service\r
+        mToken = MusicUtils.bindToService(this, this);\r
+\r
+        IntentFilter filter = new IntentFilter();\r
+        filter.addAction(ApolloService.META_CHANGED);\r
+        filter.addAction(ApolloService.QUEUE_CHANGED);\r
+        filter.addAction(ApolloService.PLAYSTATE_CHANGED);\r
+\r
+        registerReceiver(mMediaStatusReceiver, filter);\r
+        super.onStart();\r
+    }\r
+\r
+    @Override\r
+    protected void onStop() {\r
+        // Unbind\r
+        if (MusicUtils.mService != null)\r
+            MusicUtils.unbindFromService(mToken);\r
+\r
+        unregisterReceiver(mMediaStatusReceiver);\r
+        super.onStop();\r
+    }\r
+\r
+    /**\r
+     * Update the ActionBar\r
+     */\r
+    public void initActionBar() {\r
+        // Custom ActionBar layout\r
+        View view = getLayoutInflater().inflate(R.layout.custom_action_bar, null);\r
+        // Show the ActionBar\r
+        getActionBar().setCustomView(view);\r
+        getActionBar().setTitle(R.string.settings);\r
+        getActionBar().setDisplayHomeAsUpEnabled(true);\r
+        getActionBar().setDisplayShowHomeEnabled(true);\r
+        getActionBar().setDisplayShowCustomEnabled(true);\r
+\r
+        ImageView mAlbumArt = (ImageView)view.findViewById(R.id.action_bar_album_art);\r
+        TextView mTrackName = (TextView)view.findViewById(R.id.action_bar_track_name);\r
+        TextView mAlbumName = (TextView)view.findViewById(R.id.action_bar_album_name);\r
+\r
+        String url = ApolloUtils.getImageURL(MusicUtils.getAlbumName(), ALBUM_IMAGE, this);\r
+        AQuery aq = new AQuery(this);\r
+        mAlbumArt.setImageBitmap(aq.getCachedImage(url));\r
+\r
+        mTrackName.setText(MusicUtils.getTrackName());\r
+        mAlbumName.setText(MusicUtils.getAlbumName());\r
+\r
+        view.setOnClickListener(new OnClickListener() {\r
+            @Override\r
+            public void onClick(View v) {\r
+                Context context = v.getContext();\r
+                context.startActivity(new Intent(context, AudioPlayerHolder.class));\r
+                finish();\r
+            }\r
+        });\r
+    }\r
+\r
+    /**\r
+     * @param v\r
+     */\r
+    public void applyTheme(View v) {\r
+        ThemePreview themePreview = (ThemePreview)findPreference(THEME_PREVIEW);\r
+        String packageName = themePreview.getValue().toString();\r
+        ThemeUtils.setThemePackageName(this, packageName);\r
+        Intent intent = new Intent();\r
+        intent.setClass(this, MusicLibrary.class);\r
+        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);\r
+        startActivity(intent);\r
+        finish();\r
+    }\r
+\r
+    /**\r
+     * @param v\r
+     */\r
+    public void getThemes(View v) {\r
+        Uri marketUri = Uri\r
+                .parse("https://market.android.com/search?q=ApolloThemes&c=apps&featured=APP_STORE_SEARCH");\r
+        Intent marketIntent = new Intent(Intent.ACTION_VIEW).setData(marketUri);\r
+        startActivity(marketIntent);\r
+        finish();\r
+    }\r
+\r
+    /**\r
+     * Set up the theme chooser\r
+     */\r
+    public void initThemeChooser() {\r
+        SharedPreferences sp = getPreferenceManager().getSharedPreferences();\r
+        String themePackage = sp.getString(THEME_PACKAGE_NAME, APOLLO);\r
+        ListPreference themeLp = (ListPreference)findPreference(THEME_PACKAGE_NAME);\r
+        themeLp.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {\r
+            @Override\r
+            public boolean onPreferenceChange(Preference preference, Object newValue) {\r
+                ThemePreview themePreview = (ThemePreview)findPreference(THEME_PREVIEW);\r
+                themePreview.setTheme(newValue.toString());\r
+                return false;\r
+            }\r
+        });\r
+\r
+        Intent intent = new Intent("com.andrew.apollo.THEMES");\r
+        intent.addCategory("android.intent.category.DEFAULT");\r
+        PackageManager pm = getPackageManager();\r
+        List<ResolveInfo> themes = pm.queryIntentActivities(intent, 0);\r
+        String[] entries = new String[themes.size() + 1];\r
+        String[] values = new String[themes.size() + 1];\r
+        entries[0] = APOLLO;\r
+        values[0] = APOLLO;\r
+        for (int i = 0; i < themes.size(); i++) {\r
+            String appPackageName = (themes.get(i)).activityInfo.packageName.toString();\r
+            String themeName = (themes.get(i)).loadLabel(pm).toString();\r
+            entries[i + 1] = themeName;\r
+            values[i + 1] = appPackageName;\r
+        }\r
+        themeLp.setEntries(entries);\r
+        themeLp.setEntryValues(values);\r
+        ThemePreview themePreview = (ThemePreview)findPreference(THEME_PREVIEW);\r
+        themePreview.setTheme(themePackage);\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/preferences/ThemePreview.java b/src/com/andrew/apollo/preferences/ThemePreview.java
new file mode 100644 (file)
index 0000000..7a8ddf8
--- /dev/null
@@ -0,0 +1,111 @@
+
+package com.andrew.apollo.preferences;
+
+import android.content.Context;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.preference.Preference;
+import android.text.Html;
+import android.text.method.LinkMovementMethod;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.andrew.apollo.Constants;
+import com.andrew.apollo.R;
+
+public class ThemePreview extends Preference implements Constants {
+    private CharSequence themeName;
+
+    private CharSequence themePackageName;
+
+    private CharSequence themeDescription;
+
+    private Drawable themePreview;
+
+    public ThemePreview(Context context) {
+        super(context);
+    }
+
+    public ThemePreview(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public ThemePreview(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected void onBindView(View view) {
+        super.onBindView(view);
+        if (themePackageName != null && themePackageName.toString().length() > 0) {
+            TextView vThemeTitle = (TextView)view.findViewById(R.id.themeTitle);
+            vThemeTitle.setText(themeName);
+            TextView vThemeDescription = (TextView)view.findViewById(R.id.themeDescription);
+            vThemeDescription.setMovementMethod(LinkMovementMethod.getInstance());
+            vThemeDescription.setText(Html.fromHtml(themeDescription.toString()));
+            ImageView vThemePreview = (ImageView)view.findViewById(R.id.themeIcon);
+            if (themePreview != null)
+                vThemePreview.setImageDrawable(themePreview);
+            else
+                vThemePreview.setImageResource(R.drawable.ic_launcher);
+            vThemeTitle.setText(themeName);
+
+            Button applyButton = (Button)view.findViewById(R.id.themeApply);
+            applyButton.setEnabled(true);
+        } else {
+            Button applyButton = (Button)view.findViewById(R.id.themeApply);
+            applyButton.setEnabled(false);
+        }
+    }
+
+    /**
+     * @param packageName
+     */
+    public void setTheme(CharSequence packageName) {
+        themePackageName = packageName;
+        themeName = null;
+        themeDescription = null;
+        if (themePreview != null)
+            themePreview.setCallback(null);
+        themePreview = null;
+        if (!packageName.equals(APOLLO)) {
+            Resources themeResources = null;
+            try {
+                themeResources = getContext().getPackageManager().getResourcesForApplication(
+                        packageName.toString());
+            } catch (NameNotFoundException e) {
+                e.printStackTrace();
+            }
+            if (themeResources != null) {
+                int themeNameId = themeResources.getIdentifier(THEME_TITLE, "string",
+                        packageName.toString());
+                if (themeNameId != 0) {
+                    themeName = themeResources.getString(themeNameId);
+                }
+                int themeDescriptionId = themeResources.getIdentifier(THEME_DESCRIPTION, "string",
+                        packageName.toString());
+                if (themeDescriptionId != 0) {
+                    themeDescription = themeResources.getString(themeDescriptionId);
+                }
+                int themePreviewId = themeResources.getIdentifier(THEME_PREVIEW, "drawable",
+                        packageName.toString());
+                if (themePreviewId != 0) {
+                    themePreview = themeResources.getDrawable(themePreviewId);
+                }
+            }
+        }
+        if (themeName == null)
+            themeName = getContext().getResources().getString(R.string.apollo_themes);
+        if (themeDescription == null)
+            themeDescription = getContext().getResources().getString(R.string.themes);
+        notifyChanged();
+    }
+
+    public CharSequence getValue() {
+        return themePackageName;
+    }
+}
diff --git a/src/com/andrew/apollo/service/ApolloService.java b/src/com/andrew/apollo/service/ApolloService.java
new file mode 100644 (file)
index 0000000..bc2c142
--- /dev/null
@@ -0,0 +1,2280 @@
+/* Copyright (C) 2007 The Android Open Source Project\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.andrew.apollo.service;\r
+\r
+import java.io.IOException;\r
+import java.lang.ref.WeakReference;\r
+import java.util.Random;\r
+import java.util.Vector;\r
+\r
+import android.app.Notification;\r
+import android.app.NotificationManager;\r
+import android.app.PendingIntent;\r
+import android.app.Service;\r
+import android.appwidget.AppWidgetManager;\r
+import android.content.BroadcastReceiver;\r
+import android.content.ComponentName;\r
+import android.content.ContentResolver;\r
+import android.content.ContentUris;\r
+import android.content.ContentValues;\r
+import android.content.Context;\r
+import android.content.Intent;\r
+import android.content.IntentFilter;\r
+import android.content.SharedPreferences;\r
+import android.content.SharedPreferences.Editor;\r
+import android.database.Cursor;\r
+import android.database.sqlite.SQLiteException;\r
+import android.graphics.Bitmap;\r
+import android.media.AudioManager;\r
+import android.media.AudioManager.OnAudioFocusChangeListener;\r
+import android.media.MediaMetadataRetriever;\r
+import android.media.MediaPlayer;\r
+import android.media.RemoteControlClient;\r
+import android.media.RemoteControlClient.MetadataEditor;\r
+import android.media.audiofx.AudioEffect;\r
+import android.net.Uri;\r
+import android.os.Handler;\r
+import android.os.IBinder;\r
+import android.os.Message;\r
+import android.os.PowerManager;\r
+import android.os.PowerManager.WakeLock;\r
+import android.os.RemoteException;\r
+import android.os.SystemClock;\r
+import android.provider.BaseColumns;\r
+import android.provider.MediaStore;\r
+import android.provider.MediaStore.Audio;\r
+import android.provider.MediaStore.Audio.AudioColumns;\r
+import android.provider.MediaStore.MediaColumns;\r
+import android.util.Log;\r
+import android.view.KeyEvent;\r
+import android.view.View;\r
+import android.widget.RemoteViews;\r
+import android.widget.Toast;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.IApolloService;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.app.widgets.AppWidget11;\r
+import com.andrew.apollo.app.widgets.AppWidget41;\r
+import com.andrew.apollo.app.widgets.AppWidget42;\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+import com.andrew.apollo.utils.MusicUtils;\r
+import com.andrew.apollo.utils.SharedPreferencesCompat;\r
+import com.androidquery.AQuery;\r
+\r
+public class ApolloService extends Service implements Constants {\r
+    /**\r
+     * used to specify whether enqueue() should start playing the new list of\r
+     * files right away, next or once all the currently queued files have been\r
+     * played\r
+     */\r
+    public static final int NOW = 1;\r
+\r
+    public static final int NEXT = 2;\r
+\r
+    public static final int LAST = 3;\r
+\r
+    public static final int PLAYBACKSERVICE_STATUS = 1;\r
+\r
+    public static final int SHUFFLE_NONE = 0;\r
+\r
+    public static final int SHUFFLE_NORMAL = 1;\r
+\r
+    public static final int SHUFFLE_AUTO = 2;\r
+\r
+    public static final int REPEAT_NONE = 0;\r
+\r
+    public static final int REPEAT_CURRENT = 1;\r
+\r
+    public static final int REPEAT_ALL = 2;\r
+\r
+    public static final String PLAYSTATE_CHANGED = "com.andrew.apollo.playstatechanged";\r
+\r
+    public static final String META_CHANGED = "com.andrew.apollo.metachanged";\r
+\r
+    public static final String FAVORITE_CHANGED = "com.andrew.apollo.favoritechanged";\r
+\r
+    public static final String QUEUE_CHANGED = "com.andrew.apollo.queuechanged";\r
+\r
+    public static final String REPEATMODE_CHANGED = "com.andrew.apollo.repeatmodechanged";\r
+\r
+    public static final String SHUFFLEMODE_CHANGED = "com.andrew.apollo.shufflemodechanged";\r
+\r
+    public static final String PROGRESSBAR_CHANGED = "com.andrew.apollo.progressbarchnaged";\r
+\r
+    public static final String REFRESH_PROGRESSBAR = "com.andrew.apollo.refreshprogessbar";\r
+\r
+    public static final String CYCLEREPEAT_ACTION = "com.andrew.apollo.musicservicecommand.cyclerepeat";\r
+\r
+    public static final String TOGGLESHUFFLE_ACTION = "com.andrew.apollo.musicservicecommand.toggleshuffle";\r
+\r
+    public static final String SERVICECMD = "com.andrew.apollo.musicservicecommand";\r
+\r
+    public static final String CMDNAME = "command";\r
+\r
+    public static final String CMDTOGGLEPAUSE = "togglepause";\r
+\r
+    public static final String CMDSTOP = "stop";\r
+\r
+    public static final String CMDPAUSE = "pause";\r
+\r
+    public static final String CMDPLAY = "play";\r
+\r
+    public static final String CMDPREVIOUS = "previous";\r
+\r
+    public static final String CMDNEXT = "next";\r
+\r
+    public static final String CMDNOTIF = "buttonId";\r
+\r
+    public static final String CMDTOGGLEFAVORITE = "togglefavorite";\r
+\r
+    public static final String CMDCYCLEREPEAT = "cyclerepeat";\r
+\r
+    public static final String CMDTOGGLESHUFFLE = "toggleshuffle";\r
+\r
+    public static final String TOGGLEPAUSE_ACTION = "com.andrew.apollo.musicservicecommand.togglepause";\r
+\r
+    public static final String PAUSE_ACTION = "com.andrew.apollo.musicservicecommand.pause";\r
+\r
+    public static final String PREVIOUS_ACTION = "com.andrew.apollo.musicservicecommand.previous";\r
+\r
+    public static final String NEXT_ACTION = "com.andrew.apollo.musicservicecommand.next";\r
+\r
+    private static final int TRACK_ENDED = 1;\r
+\r
+    private static final int RELEASE_WAKELOCK = 2;\r
+\r
+    private static final int SERVER_DIED = 3;\r
+\r
+    private static final int FOCUSCHANGE = 4;\r
+\r
+    private static final int FADEDOWN = 5;\r
+\r
+    private static final int FADEUP = 6;\r
+\r
+    private static final int MAX_HISTORY_SIZE = 100;\r
+\r
+    private Notification status;\r
+\r
+    private MultiPlayer mPlayer;\r
+\r
+    private String mFileToPlay;\r
+\r
+    private int mShuffleMode = SHUFFLE_NONE;\r
+\r
+    private int mRepeatMode = REPEAT_NONE;\r
+\r
+    private int mMediaMountedCount = 0;\r
+\r
+    private long[] mAutoShuffleList = null;\r
+\r
+    private long[] mPlayList = null;\r
+\r
+    private int mPlayListLen = 0;\r
+\r
+    private final Vector<Integer> mHistory = new Vector<Integer>(MAX_HISTORY_SIZE);\r
+\r
+    private Cursor mCursor;\r
+\r
+    private int mPlayPos = -1;\r
+\r
+    private static final String LOGTAG = "MediaPlaybackService";\r
+\r
+    private final Shuffler mRand = new Shuffler();\r
+\r
+    private int mOpenFailedCounter = 0;\r
+\r
+    String[] mCursorCols = new String[] {\r
+            "audio._id AS _id", MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM,\r
+            MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DATA,\r
+            MediaStore.Audio.Media.MIME_TYPE, MediaStore.Audio.Media.ALBUM_ID,\r
+            MediaStore.Audio.Media.ARTIST_ID, MediaStore.Audio.Media.IS_PODCAST,\r
+            MediaStore.Audio.Media.BOOKMARK\r
+    };\r
+\r
+    private final static int IDCOLIDX = 0;\r
+\r
+    private final static int PODCASTCOLIDX = 8;\r
+\r
+    private final static int BOOKMARKCOLIDX = 9;\r
+\r
+    private BroadcastReceiver mUnmountReceiver = null;\r
+\r
+    private WakeLock mWakeLock;\r
+\r
+    private int mServiceStartId = -1;\r
+\r
+    private boolean mServiceInUse = false;\r
+\r
+    private boolean mIsSupposedToBePlaying = false;\r
+\r
+    private boolean mQuietMode = false;\r
+\r
+    private AudioManager mAudioManager;\r
+\r
+    private boolean mQueueIsSaveable = true;\r
+\r
+    // used to track what type of audio focus loss caused the playback to pause\r
+    private boolean mPausedByTransientLossOfFocus = false;\r
+\r
+    private SharedPreferences mPreferences;\r
+\r
+    // We use this to distinguish between different cards when saving/restoring\r
+    // playlists.\r
+    // This will have to change if we want to support multiple simultaneous\r
+    // cards.\r
+    private int mCardId;\r
+\r
+    private final AppWidget11 mAppWidgetProvider1x1 = AppWidget11.getInstance();\r
+\r
+    private final AppWidget42 mAppWidgetProvider4x2 = AppWidget42.getInstance();\r
+\r
+    private final AppWidget41 mAppWidgetProvider4x1 = AppWidget41.getInstance();\r
+\r
+    // interval after which we stop the service when idle\r
+    private static final int IDLE_DELAY = 60000;\r
+\r
+    private RemoteControlClient mRemoteControlClient;\r
+\r
+    private final Handler mMediaplayerHandler = new Handler() {\r
+        float mCurrentVolume = 1.0f;\r
+\r
+        @Override\r
+        public void handleMessage(Message msg) {\r
+            switch (msg.what) {\r
+                case FADEDOWN:\r
+                    mCurrentVolume -= .05f;\r
+                    if (mCurrentVolume > .2f) {\r
+                        mMediaplayerHandler.sendEmptyMessageDelayed(FADEDOWN, 10);\r
+                    } else {\r
+                        mCurrentVolume = .2f;\r
+                    }\r
+                    mPlayer.setVolume(mCurrentVolume);\r
+                    break;\r
+                case FADEUP:\r
+                    mCurrentVolume += .01f;\r
+                    if (mCurrentVolume < 1.0f) {\r
+                        mMediaplayerHandler.sendEmptyMessageDelayed(FADEUP, 10);\r
+                    } else {\r
+                        mCurrentVolume = 1.0f;\r
+                    }\r
+                    mPlayer.setVolume(mCurrentVolume);\r
+                    break;\r
+                case SERVER_DIED:\r
+                    if (mIsSupposedToBePlaying) {\r
+                        next(true);\r
+                    } else {\r
+                        // the server died when we were idle, so just\r
+                        // reopen the same song (it will start again\r
+                        // from the beginning though when the user\r
+                        // restarts)\r
+                        openCurrent();\r
+                    }\r
+                    break;\r
+                case TRACK_ENDED:\r
+                    if (mRepeatMode == REPEAT_CURRENT) {\r
+                        seek(0);\r
+                        play();\r
+                    } else {\r
+                        next(false);\r
+                    }\r
+                    break;\r
+                case RELEASE_WAKELOCK:\r
+                    mWakeLock.release();\r
+                    break;\r
+\r
+                case FOCUSCHANGE:\r
+                    // This code is here so we can better synchronize it with\r
+                    // the code that\r
+                    // handles fade-in\r
+                    switch (msg.arg1) {\r
+                        case AudioManager.AUDIOFOCUS_LOSS:\r
+                            Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS");\r
+                            if (isPlaying()) {\r
+                                mPausedByTransientLossOfFocus = false;\r
+                            }\r
+                            pause();\r
+                            break;\r
+                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:\r
+                            mMediaplayerHandler.removeMessages(FADEUP);\r
+                            mMediaplayerHandler.sendEmptyMessage(FADEDOWN);\r
+                            break;\r
+                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:\r
+                            Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS_TRANSIENT");\r
+                            if (isPlaying()) {\r
+                                mPausedByTransientLossOfFocus = true;\r
+                            }\r
+                            pause();\r
+                            break;\r
+                        case AudioManager.AUDIOFOCUS_GAIN:\r
+                            Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_GAIN");\r
+                            if (!isPlaying() && mPausedByTransientLossOfFocus) {\r
+                                mPausedByTransientLossOfFocus = false;\r
+                                mCurrentVolume = 0f;\r
+                                mPlayer.setVolume(mCurrentVolume);\r
+                                play(); // also queues a fade-in\r
+                            } else {\r
+                                mMediaplayerHandler.removeMessages(FADEDOWN);\r
+                                mMediaplayerHandler.sendEmptyMessage(FADEUP);\r
+                            }\r
+                            break;\r
+                        default:\r
+                            Log.e(LOGTAG, "Unknown audio focus change code");\r
+                    }\r
+                    break;\r
+\r
+                default:\r
+                    break;\r
+            }\r
+        }\r
+    };\r
+\r
+    private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {\r
+        @Override\r
+        public void onReceive(Context context, Intent intent) {\r
+            String action = intent.getAction();\r
+            String cmd = intent.getStringExtra("command");\r
+            if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {\r
+                next(true);\r
+            } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {\r
+                prev();\r
+            } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {\r
+                if (isPlaying()) {\r
+                    pause();\r
+                    mPausedByTransientLossOfFocus = false;\r
+                } else {\r
+                    play();\r
+                }\r
+            } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {\r
+                pause();\r
+                mPausedByTransientLossOfFocus = false;\r
+            } else if (CMDPLAY.equals(cmd)) {\r
+                play();\r
+            } else if (CMDSTOP.equals(cmd)) {\r
+                pause();\r
+                mPausedByTransientLossOfFocus = false;\r
+                seek(0);\r
+            } else if (CMDTOGGLEFAVORITE.equals(cmd)) {\r
+                if (!isFavorite()) {\r
+                    addToFavorites();\r
+                } else {\r
+                    removeFromFavorites();\r
+                }\r
+            } else if (CMDCYCLEREPEAT.equals(cmd) || CYCLEREPEAT_ACTION.equals(action)) {\r
+                cycleRepeat();\r
+            } else if (CMDTOGGLESHUFFLE.equals(cmd) || TOGGLESHUFFLE_ACTION.equals(action)) {\r
+                toggleShuffle();\r
+            } else if (AppWidget42.CMDAPPWIDGETUPDATE.equals(cmd)) {\r
+                int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);\r
+                mAppWidgetProvider4x2.performUpdate(ApolloService.this, appWidgetIds);\r
+            } else if (AppWidget41.CMDAPPWIDGETUPDATE.equals(cmd)) {\r
+                int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);\r
+                mAppWidgetProvider4x1.performUpdate(ApolloService.this, appWidgetIds);\r
+            } else if (AppWidget11.CMDAPPWIDGETUPDATE.equals(cmd)) {\r
+                int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);\r
+                mAppWidgetProvider1x1.performUpdate(ApolloService.this, appWidgetIds);\r
+            }\r
+        }\r
+    };\r
+\r
+    private final OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {\r
+        @Override\r
+        public void onAudioFocusChange(int focusChange) {\r
+            mMediaplayerHandler.obtainMessage(FOCUSCHANGE, focusChange, 0).sendToTarget();\r
+        }\r
+    };\r
+\r
+    public ApolloService() {\r
+    }\r
+\r
+    @Override\r
+    public void onCreate() {\r
+        super.onCreate();\r
+\r
+        mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);\r
+        ComponentName rec = new ComponentName(getPackageName(),\r
+                MediaButtonIntentReceiver.class.getName());\r
+        mAudioManager.registerMediaButtonEventReceiver(rec);\r
+        Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);\r
+        mediaButtonIntent.setComponent(rec);\r
+        PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0,\r
+                mediaButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT);\r
+        mRemoteControlClient = new RemoteControlClient(mediaPendingIntent);\r
+        mAudioManager.registerRemoteControlClient(mRemoteControlClient);\r
+\r
+        int flags = RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS\r
+                | RemoteControlClient.FLAG_KEY_MEDIA_NEXT | RemoteControlClient.FLAG_KEY_MEDIA_PLAY\r
+                | RemoteControlClient.FLAG_KEY_MEDIA_PAUSE\r
+                | RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE\r
+                | RemoteControlClient.FLAG_KEY_MEDIA_STOP;\r
+        mRemoteControlClient.setTransportControlFlags(flags);\r
+\r
+        mPreferences = getSharedPreferences(APOLLO_PREFERENCES, MODE_WORLD_READABLE\r
+                | MODE_WORLD_WRITEABLE);\r
+        mCardId = MusicUtils.getCardId(this);\r
+\r
+        registerExternalStorageListener();\r
+\r
+        // Needs to be done in this thread, since otherwise\r
+        // ApplicationContext.getPowerManager() crashes.\r
+        mPlayer = new MultiPlayer();\r
+        mPlayer.setHandler(mMediaplayerHandler);\r
+\r
+        reloadQueue();\r
+        notifyChange(QUEUE_CHANGED);\r
+        notifyChange(META_CHANGED);\r
+\r
+        IntentFilter commandFilter = new IntentFilter();\r
+        commandFilter.addAction(SERVICECMD);\r
+        commandFilter.addAction(TOGGLEPAUSE_ACTION);\r
+        commandFilter.addAction(PAUSE_ACTION);\r
+        commandFilter.addAction(NEXT_ACTION);\r
+        commandFilter.addAction(PREVIOUS_ACTION);\r
+        commandFilter.addAction(CYCLEREPEAT_ACTION);\r
+        commandFilter.addAction(TOGGLESHUFFLE_ACTION);\r
+        registerReceiver(mIntentReceiver, commandFilter);\r
+\r
+        PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);\r
+        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());\r
+        mWakeLock.setReferenceCounted(false);\r
+\r
+        // If the service was idle, but got killed before it stopped itself, the\r
+        // system will relaunch it. Make sure it gets stopped again in that\r
+        // case.\r
+        Message msg = mDelayedStopHandler.obtainMessage();\r
+        mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);\r
+    }\r
+\r
+    @Override\r
+    public void onDestroy() {\r
+        // Check that we're not being destroyed while something is still\r
+        // playing.\r
+        if (mIsSupposedToBePlaying) {\r
+            Log.e(LOGTAG, "Service being destroyed while still playing.");\r
+        }\r
+        // release all MediaPlayer resources, including the native player and\r
+        // wakelocks\r
+        Intent i = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);\r
+        i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());\r
+        i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());\r
+        sendBroadcast(i);\r
+        mPlayer.release();\r
+        mPlayer = null;\r
+\r
+        mAudioManager.abandonAudioFocus(mAudioFocusListener);\r
+        mAudioManager.unregisterRemoteControlClient(mRemoteControlClient);\r
+\r
+        // make sure there aren't any other messages coming\r
+        mDelayedStopHandler.removeCallbacksAndMessages(null);\r
+        mMediaplayerHandler.removeCallbacksAndMessages(null);\r
+\r
+        if (mCursor != null) {\r
+            mCursor.close();\r
+            mCursor = null;\r
+        }\r
+\r
+        unregisterReceiver(mIntentReceiver);\r
+        if (mUnmountReceiver != null) {\r
+            unregisterReceiver(mUnmountReceiver);\r
+            mUnmountReceiver = null;\r
+        }\r
+        mWakeLock.release();\r
+        super.onDestroy();\r
+    }\r
+\r
+    private final char hexdigits[] = new char[] {\r
+            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'\r
+    };\r
+\r
+    private void saveQueue(boolean full) {\r
+        if (!mQueueIsSaveable) {\r
+            return;\r
+        }\r
+\r
+        Editor ed = mPreferences.edit();\r
+        // long start = System.currentTimeMillis();\r
+        if (full) {\r
+            StringBuilder q = new StringBuilder();\r
+\r
+            // The current playlist is saved as a list of "reverse hexadecimal"\r
+            // numbers, which we can generate faster than normal decimal or\r
+            // hexadecimal numbers, which in turn allows us to save the playlist\r
+            // more often without worrying too much about performance.\r
+            // (saving the full state takes about 40 ms under no-load conditions\r
+            // on the phone)\r
+            int len = mPlayListLen;\r
+            for (int i = 0; i < len; i++) {\r
+                long n = mPlayList[i];\r
+                if (n < 0) {\r
+                    continue;\r
+                } else if (n == 0) {\r
+                    q.append("0;");\r
+                } else {\r
+                    while (n != 0) {\r
+                        int digit = (int)(n & 0xf);\r
+                        n >>>= 4;\r
+                        q.append(hexdigits[digit]);\r
+                    }\r
+                    q.append(";");\r
+                }\r
+            }\r
+            // Log.i("@@@@ service", "created queue string in " +\r
+            // (System.currentTimeMillis() - start) + " ms");\r
+            ed.putString("queue", q.toString());\r
+            ed.putInt("cardid", mCardId);\r
+            if (mShuffleMode != SHUFFLE_NONE) {\r
+                // In shuffle mode we need to save the history too\r
+                len = mHistory.size();\r
+                q.setLength(0);\r
+                for (int i = 0; i < len; i++) {\r
+                    int n = mHistory.get(i);\r
+                    if (n == 0) {\r
+                        q.append("0;");\r
+                    } else {\r
+                        while (n != 0) {\r
+                            int digit = (n & 0xf);\r
+                            n >>>= 4;\r
+                            q.append(hexdigits[digit]);\r
+                        }\r
+                        q.append(";");\r
+                    }\r
+                }\r
+                ed.putString("history", q.toString());\r
+            }\r
+        }\r
+        ed.putInt("curpos", mPlayPos);\r
+        if (mPlayer.isInitialized()) {\r
+            ed.putLong("seekpos", mPlayer.position());\r
+        }\r
+        ed.putInt("repeatmode", mRepeatMode);\r
+        ed.putInt("shufflemode", mShuffleMode);\r
+        SharedPreferencesCompat.apply(ed);\r
+\r
+        // Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis()\r
+        // - start) + " ms");\r
+    }\r
+\r
+    private void reloadQueue() {\r
+        String q = null;\r
+\r
+        int id = mCardId;\r
+        if (mPreferences.contains("cardid")) {\r
+            id = mPreferences.getInt("cardid", ~mCardId);\r
+        }\r
+        if (id == mCardId) {\r
+            // Only restore the saved playlist if the card is still\r
+            // the same one as when the playlist was saved\r
+            q = mPreferences.getString("queue", "");\r
+        }\r
+        int qlen = q != null ? q.length() : 0;\r
+        if (qlen > 1) {\r
+            // Log.i("@@@@ service", "loaded queue: " + q);\r
+            int plen = 0;\r
+            int n = 0;\r
+            int shift = 0;\r
+            for (int i = 0; i < qlen; i++) {\r
+                char c = q.charAt(i);\r
+                if (c == ';') {\r
+                    ensurePlayListCapacity(plen + 1);\r
+                    mPlayList[plen] = n;\r
+                    plen++;\r
+                    n = 0;\r
+                    shift = 0;\r
+                } else {\r
+                    if (c >= '0' && c <= '9') {\r
+                        n += ((c - '0') << shift);\r
+                    } else if (c >= 'a' && c <= 'f') {\r
+                        n += ((10 + c - 'a') << shift);\r
+                    } else {\r
+                        // bogus playlist data\r
+                        plen = 0;\r
+                        break;\r
+                    }\r
+                    shift += 4;\r
+                }\r
+            }\r
+            mPlayListLen = plen;\r
+\r
+            int pos = mPreferences.getInt("curpos", 0);\r
+            if (pos < 0 || pos >= mPlayListLen) {\r
+                // The saved playlist is bogus, discard it\r
+                mPlayListLen = 0;\r
+                return;\r
+            }\r
+            mPlayPos = pos;\r
+\r
+            // When reloadQueue is called in response to a card-insertion,\r
+            // we might not be able to query the media provider right away.\r
+            // To deal with this, try querying for the current file, and if\r
+            // that fails, wait a while and try again. If that too fails,\r
+            // assume there is a problem and don't restore the state.\r
+            Cursor crsr = MusicUtils.query(this, Audio.Media.EXTERNAL_CONTENT_URI, new String[] {\r
+                "_id"\r
+            }, "_id=" + mPlayList[mPlayPos], null, null);\r
+            if (crsr == null || crsr.getCount() == 0) {\r
+                // wait a bit and try again\r
+                SystemClock.sleep(3000);\r
+                crsr = getContentResolver().query(Audio.Media.EXTERNAL_CONTENT_URI, mCursorCols,\r
+                        "_id=" + mPlayList[mPlayPos], null, null);\r
+            }\r
+            if (crsr != null) {\r
+                crsr.close();\r
+            }\r
+\r
+            // Make sure we don't auto-skip to the next song, since that\r
+            // also starts playback. What could happen in that case is:\r
+            // - music is paused\r
+            // - go to UMS and delete some files, including the currently\r
+            // playing one\r
+            // - come back from UMS\r
+            // (time passes)\r
+            // - music app is killed for some reason (out of memory)\r
+            // - music service is restarted, service restores state, doesn't\r
+            // find\r
+            // the "current" file, goes to the next and: playback starts on its\r
+            // own, potentially at some random inconvenient time.\r
+            mOpenFailedCounter = 20;\r
+            mQuietMode = true;\r
+            openCurrent();\r
+            mQuietMode = false;\r
+            if (!mPlayer.isInitialized()) {\r
+                // couldn't restore the saved state\r
+                mPlayListLen = 0;\r
+                return;\r
+            }\r
+\r
+            long seekpos = mPreferences.getLong("seekpos", 0);\r
+            seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);\r
+            Log.d(LOGTAG, "restored queue, currently at position " + position() + "/" + duration()\r
+                    + " (requested " + seekpos + ")");\r
+\r
+            int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);\r
+            if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {\r
+                repmode = REPEAT_NONE;\r
+            }\r
+            mRepeatMode = repmode;\r
+\r
+            int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);\r
+            if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {\r
+                shufmode = SHUFFLE_NONE;\r
+            }\r
+            if (shufmode != SHUFFLE_NONE) {\r
+                // in shuffle mode we need to restore the history too\r
+                q = mPreferences.getString("history", "");\r
+                qlen = q != null ? q.length() : 0;\r
+                if (qlen > 1) {\r
+                    plen = 0;\r
+                    n = 0;\r
+                    shift = 0;\r
+                    mHistory.clear();\r
+                    for (int i = 0; i < qlen; i++) {\r
+                        char c = q.charAt(i);\r
+                        if (c == ';') {\r
+                            if (n >= mPlayListLen) {\r
+                                // bogus history data\r
+                                mHistory.clear();\r
+                                break;\r
+                            }\r
+                            mHistory.add(n);\r
+                            n = 0;\r
+                            shift = 0;\r
+                        } else {\r
+                            if (c >= '0' && c <= '9') {\r
+                                n += ((c - '0') << shift);\r
+                            } else if (c >= 'a' && c <= 'f') {\r
+                                n += ((10 + c - 'a') << shift);\r
+                            } else {\r
+                                // bogus history data\r
+                                mHistory.clear();\r
+                                break;\r
+                            }\r
+                            shift += 4;\r
+                        }\r
+                    }\r
+                }\r
+            }\r
+            if (shufmode == SHUFFLE_AUTO) {\r
+                if (!makeAutoShuffleList()) {\r
+                    shufmode = SHUFFLE_NONE;\r
+                }\r
+            }\r
+            mShuffleMode = shufmode;\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public IBinder onBind(Intent intent) {\r
+        mDelayedStopHandler.removeCallbacksAndMessages(null);\r
+        mServiceInUse = true;\r
+        return mBinder;\r
+    }\r
+\r
+    @Override\r
+    public void onRebind(Intent intent) {\r
+        mDelayedStopHandler.removeCallbacksAndMessages(null);\r
+        mServiceInUse = true;\r
+    }\r
+\r
+    @Override\r
+    public int onStartCommand(Intent intent, int flags, int startId) {\r
+        mServiceStartId = startId;\r
+        mDelayedStopHandler.removeCallbacksAndMessages(null);\r
+\r
+        if (intent != null) {\r
+            String action = intent.getAction();\r
+            String cmd = intent.getStringExtra("command");\r
+\r
+            if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {\r
+                next(true);\r
+            } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {\r
+                if (position() < 2000) {\r
+                    prev();\r
+                } else {\r
+                    seek(0);\r
+                    play();\r
+                }\r
+            } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {\r
+                if (mIsSupposedToBePlaying) {\r
+                    pause();\r
+                    mPausedByTransientLossOfFocus = false;\r
+                } else {\r
+                    play();\r
+                }\r
+            } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {\r
+                pause();\r
+                mPausedByTransientLossOfFocus = false;\r
+            } else if (CMDPLAY.equals(cmd)) {\r
+                play();\r
+            } else if (CMDSTOP.equals(cmd)) {\r
+                pause();\r
+                if (intent.getIntExtra(CMDNOTIF, 0) == 3) {\r
+                    stopForeground(true);\r
+                }\r
+                mPausedByTransientLossOfFocus = false;\r
+                seek(0);\r
+            } else if (CMDTOGGLEFAVORITE.equals(cmd)) {\r
+                if (!isFavorite()) {\r
+                    addToFavorites();\r
+                } else {\r
+                    removeFromFavorites();\r
+                }\r
+            } else if (CMDCYCLEREPEAT.equals(cmd) || CYCLEREPEAT_ACTION.equals(action)) {\r
+                cycleRepeat();\r
+            } else if (CMDTOGGLESHUFFLE.equals(cmd) || TOGGLESHUFFLE_ACTION.equals(action)) {\r
+                toggleShuffle();\r
+            }\r
+        }\r
+\r
+        // make sure the service will shut down on its own if it was\r
+        // just started but not bound to and nothing is playing\r
+        mDelayedStopHandler.removeCallbacksAndMessages(null);\r
+        Message msg = mDelayedStopHandler.obtainMessage();\r
+        mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);\r
+        return START_STICKY;\r
+    }\r
+\r
+    @Override\r
+    public boolean onUnbind(Intent intent) {\r
+        mServiceInUse = false;\r
+\r
+        // Take a snapshot of the current playlist\r
+        saveQueue(true);\r
+\r
+        if (mIsSupposedToBePlaying || mPausedByTransientLossOfFocus) {\r
+            // something is currently playing, or will be playing once\r
+            // an in-progress action requesting audio focus ends, so don't stop\r
+            // the service now.\r
+            return true;\r
+        }\r
+\r
+        // If there is a playlist but playback is paused, then wait a while\r
+        // before stopping the service, so that pause/resume isn't slow.\r
+        // Also delay stopping the service if we're transitioning between\r
+        // tracks.\r
+        if (mPlayListLen > 0 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {\r
+            Message msg = mDelayedStopHandler.obtainMessage();\r
+            mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);\r
+            return true;\r
+        }\r
+\r
+        // No active playlist, OK to stop the service right now\r
+        stopSelf(mServiceStartId);\r
+        return true;\r
+    }\r
+\r
+    private final Handler mDelayedStopHandler = new Handler() {\r
+        @Override\r
+        public void handleMessage(Message msg) {\r
+            // Check again to make sure nothing is playing right now\r
+            if (isPlaying() || mPausedByTransientLossOfFocus || mServiceInUse\r
+                    || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {\r
+                return;\r
+            }\r
+            // save the queue again, because it might have changed\r
+            // since the user exited the music app (because of\r
+            // party-shuffle or because the play-position changed)\r
+            saveQueue(true);\r
+            stopSelf(mServiceStartId);\r
+        }\r
+    };\r
+\r
+    /**\r
+     * Called when we receive a ACTION_MEDIA_EJECT notification.\r
+     * \r
+     * @param storagePath path to mount point for the removed media\r
+     */\r
+    public void closeExternalStorageFiles(String storagePath) {\r
+        // stop playback and clean up if the SD card is going to be unmounted.\r
+        stop(true);\r
+        notifyChange(QUEUE_CHANGED);\r
+        notifyChange(META_CHANGED);\r
+    }\r
+\r
+    /**\r
+     * Registers an intent to listen for ACTION_MEDIA_EJECT notifications. The\r
+     * intent will call closeExternalStorageFiles() if the external media is\r
+     * going to be ejected, so applications can clean up any files they have\r
+     * open.\r
+     */\r
+    public void registerExternalStorageListener() {\r
+        if (mUnmountReceiver == null) {\r
+            mUnmountReceiver = new BroadcastReceiver() {\r
+                @Override\r
+                public void onReceive(Context context, Intent intent) {\r
+                    String action = intent.getAction();\r
+                    if (action.equals(Intent.ACTION_MEDIA_EJECT)) {\r
+                        saveQueue(true);\r
+                        mQueueIsSaveable = false;\r
+                        closeExternalStorageFiles(intent.getData().getPath());\r
+                    } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {\r
+                        mMediaMountedCount++;\r
+                        mCardId = MusicUtils.getCardId(ApolloService.this);\r
+                        reloadQueue();\r
+                        mQueueIsSaveable = true;\r
+                        notifyChange(QUEUE_CHANGED);\r
+                        notifyChange(META_CHANGED);\r
+                    }\r
+                }\r
+            };\r
+            IntentFilter iFilter = new IntentFilter();\r
+            iFilter.addAction(Intent.ACTION_MEDIA_EJECT);\r
+            iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);\r
+            iFilter.addDataScheme(DATA_SCHEME);\r
+            registerReceiver(mUnmountReceiver, iFilter);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Notify the change-receivers that something has changed. The intent that\r
+     * is sent contains the following data for the currently playing track: "id"\r
+     * - Integer: the database row ID "artist" - String: the name of the artist\r
+     * "album" - String: the name of the album "track" - String: the name of the\r
+     * track The intent has an action that is one of\r
+     * "com.andrew.apollo.metachanged" "com.andrew.apollo.queuechanged",\r
+     * "com.andrew.apollo.playbackcomplete" "com.andrew.apollo.playstatechanged"\r
+     * respectively indicating that a new track has started playing, that the\r
+     * playback queue has changed, that playback has stopped because the last\r
+     * file in the list has been played, or that the play-state changed\r
+     * (paused/resumed).\r
+     */\r
+    private void notifyChange(String what) {\r
+\r
+        Intent i = new Intent(what);\r
+        i.putExtra("id", Long.valueOf(getAudioId()));\r
+        i.putExtra("artist", getArtistName());\r
+        i.putExtra("album", getAlbumName());\r
+        i.putExtra("track", getTrackName());\r
+        i.putExtra("playing", mIsSupposedToBePlaying);\r
+        i.putExtra("isfavorite", isFavorite());\r
+        sendStickyBroadcast(i);\r
+\r
+        if (what.equals(PLAYSTATE_CHANGED)) {\r
+            mRemoteControlClient\r
+                    .setPlaybackState(mIsSupposedToBePlaying ? RemoteControlClient.PLAYSTATE_PLAYING\r
+                            : RemoteControlClient.PLAYSTATE_PAUSED);\r
+        } else if (what.equals(META_CHANGED)) {\r
+            RemoteControlClient.MetadataEditor ed = mRemoteControlClient.editMetadata(true);\r
+            ed.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, getTrackName());\r
+            ed.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, getAlbumName());\r
+            ed.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, getArtistName());\r
+            ed.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, duration());\r
+            AQuery aq = new AQuery(this);\r
+            Bitmap b = aq\r
+                    .getCachedImage(ApolloUtils.getImageURL(getAlbumName(), ALBUM_IMAGE, this));\r
+            if (b != null) {\r
+                ed.putBitmap(MetadataEditor.BITMAP_KEY_ARTWORK, b);\r
+            }\r
+            ed.apply();\r
+        }\r
+\r
+        if (what.equals(QUEUE_CHANGED)) {\r
+            saveQueue(true);\r
+        } else {\r
+            saveQueue(false);\r
+        }\r
+\r
+        mAppWidgetProvider1x1.notifyChange(this, what);\r
+        mAppWidgetProvider4x1.notifyChange(this, what);\r
+        mAppWidgetProvider4x2.notifyChange(this, what);\r
+\r
+    }\r
+\r
+    private void ensurePlayListCapacity(int size) {\r
+        if (mPlayList == null || size > mPlayList.length) {\r
+            // reallocate at 2x requested size so we don't\r
+            // need to grow and copy the array for every\r
+            // insert\r
+            long[] newlist = new long[size * 2];\r
+            int len = mPlayList != null ? mPlayList.length : mPlayListLen;\r
+            for (int i = 0; i < len; i++) {\r
+                newlist[i] = mPlayList[i];\r
+            }\r
+            mPlayList = newlist;\r
+        }\r
+        // FIXME: shrink the array when the needed size is much smaller\r
+        // than the allocated size\r
+    }\r
+\r
+    // insert the list of songs at the specified position in the playlist\r
+    private void addToPlayList(long[] list, int position) {\r
+        int addlen = list.length;\r
+        if (position < 0) { // overwrite\r
+            mPlayListLen = 0;\r
+            position = 0;\r
+        }\r
+        ensurePlayListCapacity(mPlayListLen + addlen);\r
+        if (position > mPlayListLen) {\r
+            position = mPlayListLen;\r
+        }\r
+\r
+        // move part of list after insertion point\r
+        int tailsize = mPlayListLen - position;\r
+        for (int i = tailsize; i > 0; i--) {\r
+            mPlayList[position + i] = mPlayList[position + i - addlen];\r
+        }\r
+\r
+        // copy list into playlist\r
+        for (int i = 0; i < addlen; i++) {\r
+            mPlayList[position + i] = list[i];\r
+        }\r
+        mPlayListLen += addlen;\r
+        if (mPlayListLen == 0) {\r
+            mCursor.close();\r
+            mCursor = null;\r
+            notifyChange(META_CHANGED);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Appends a list of tracks to the current playlist. If nothing is playing\r
+     * currently, playback will be started at the first track. If the action is\r
+     * NOW, playback will switch to the first of the new tracks immediately.\r
+     * \r
+     * @param list The list of tracks to append.\r
+     * @param action NOW, NEXT or LAST\r
+     */\r
+    public void enqueue(long[] list, int action) {\r
+        synchronized (this) {\r
+            if (action == NEXT && mPlayPos + 1 < mPlayListLen) {\r
+                addToPlayList(list, mPlayPos + 1);\r
+                notifyChange(QUEUE_CHANGED);\r
+            } else {\r
+                // action == LAST || action == NOW || mPlayPos + 1 ==\r
+                // mPlayListLen\r
+                addToPlayList(list, Integer.MAX_VALUE);\r
+                notifyChange(QUEUE_CHANGED);\r
+                if (action == NOW) {\r
+                    mPlayPos = mPlayListLen - list.length;\r
+                    openCurrent();\r
+                    play();\r
+                    notifyChange(META_CHANGED);\r
+                    return;\r
+                }\r
+            }\r
+            if (mPlayPos < 0) {\r
+                mPlayPos = 0;\r
+                openCurrent();\r
+                play();\r
+                notifyChange(META_CHANGED);\r
+            }\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Replaces the current playlist with a new list, and prepares for starting\r
+     * playback at the specified position in the list, or a random position if\r
+     * the specified position is 0.\r
+     * \r
+     * @param list The new list of tracks.\r
+     */\r
+    public void open(long[] list, int position) {\r
+        synchronized (this) {\r
+            if (mShuffleMode == SHUFFLE_AUTO) {\r
+                mShuffleMode = SHUFFLE_NORMAL;\r
+            }\r
+            long oldId = getAudioId();\r
+            int listlength = list.length;\r
+            boolean newlist = true;\r
+            if (mPlayListLen == listlength) {\r
+                // possible fast path: list might be the same\r
+                newlist = false;\r
+                for (int i = 0; i < listlength; i++) {\r
+                    if (list[i] != mPlayList[i]) {\r
+                        newlist = true;\r
+                        break;\r
+                    }\r
+                }\r
+            }\r
+            if (newlist) {\r
+                addToPlayList(list, -1);\r
+                notifyChange(QUEUE_CHANGED);\r
+            }\r
+            if (position >= 0) {\r
+                mPlayPos = position;\r
+            } else {\r
+                mPlayPos = mRand.nextInt(mPlayListLen);\r
+            }\r
+            mHistory.clear();\r
+\r
+            saveBookmarkIfNeeded();\r
+            openCurrent();\r
+            if (oldId != getAudioId()) {\r
+                notifyChange(META_CHANGED);\r
+            }\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Returns the current play list\r
+     * \r
+     * @return An array of integers containing the IDs of the tracks in the play\r
+     *         list\r
+     */\r
+    public long[] getQueue() {\r
+        synchronized (this) {\r
+            int len = mPlayListLen;\r
+            long[] list = new long[len];\r
+            for (int i = 0; i < len; i++) {\r
+                list[i] = mPlayList[i];\r
+            }\r
+            return list;\r
+        }\r
+    }\r
+\r
+    private void openCurrent() {\r
+        synchronized (this) {\r
+            if (mCursor != null) {\r
+                mCursor.close();\r
+                mCursor = null;\r
+            }\r
+\r
+            if (mPlayListLen == 0) {\r
+                return;\r
+            }\r
+            stop(false);\r
+\r
+            String id = String.valueOf(mPlayList[mPlayPos]);\r
+\r
+            mCursor = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,\r
+                    mCursorCols, "_id=" + id, null, null);\r
+            if (mCursor != null) {\r
+                mCursor.moveToFirst();\r
+                open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id);\r
+                // go to bookmark if needed\r
+                if (isPodcast()) {\r
+                    long bookmark = getBookmark();\r
+                    // Start playing a little bit before the bookmark,\r
+                    // so it's easier to get back in to the narrative.\r
+                    seek(bookmark - 5000);\r
+                }\r
+            }\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Opens the specified file and readies it for playback.\r
+     * \r
+     * @param path The full path of the file to be opened.\r
+     */\r
+    public void open(String path) {\r
+        synchronized (this) {\r
+            if (path == null) {\r
+                return;\r
+            }\r
+\r
+            // if mCursor is null, try to associate path with a database cursor\r
+            if (mCursor == null) {\r
+\r
+                ContentResolver resolver = getContentResolver();\r
+                Uri uri;\r
+                String where;\r
+                String selectionArgs[];\r
+                if (path.startsWith("content://media/")) {\r
+                    uri = Uri.parse(path);\r
+                    where = null;\r
+                    selectionArgs = null;\r
+                } else {\r
+                    uri = MediaStore.Audio.Media.getContentUriForPath(path);\r
+                    where = MediaColumns.DATA + "=?";\r
+                    selectionArgs = new String[] {\r
+                        path\r
+                    };\r
+                }\r
+\r
+                try {\r
+                    mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);\r
+                    if (mCursor != null) {\r
+                        if (mCursor.getCount() == 0) {\r
+                            mCursor.close();\r
+                            mCursor = null;\r
+                        } else {\r
+                            mCursor.moveToNext();\r
+                            ensurePlayListCapacity(1);\r
+                            mPlayListLen = 1;\r
+                            mPlayList[0] = mCursor.getLong(IDCOLIDX);\r
+                            mPlayPos = 0;\r
+                        }\r
+                    }\r
+                } catch (UnsupportedOperationException ex) {\r
+                }\r
+            }\r
+            mFileToPlay = path;\r
+            mPlayer.setDataSource(mFileToPlay);\r
+            if (!mPlayer.isInitialized()) {\r
+                stop(true);\r
+                if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) {\r
+                    // beware: this ends up being recursive because next() calls\r
+                    // open() again.\r
+                    next(false);\r
+                }\r
+                if (!mPlayer.isInitialized() && mOpenFailedCounter != 0) {\r
+                    // need to make sure we only shows this once\r
+                    mOpenFailedCounter = 0;\r
+                    if (!mQuietMode) {\r
+                        Toast.makeText(this, "Error", Toast.LENGTH_SHORT).show();\r
+                    }\r
+                }\r
+\r
+            } else {\r
+                mOpenFailedCounter = 0;\r
+            }\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Starts playback of a previously opened file.\r
+     */\r
+    public void play() {\r
+        mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,\r
+                AudioManager.AUDIOFOCUS_GAIN);\r
+        mAudioManager.registerMediaButtonEventReceiver(new ComponentName(getPackageName(),\r
+                MediaButtonIntentReceiver.class.getName()));\r
+\r
+        AQuery aq = new AQuery(this);\r
+        Bitmap b = aq.getCachedImage(ApolloUtils.getImageURL(getAlbumName(), ALBUM_IMAGE, this));\r
+\r
+        if (mPlayer.isInitialized()) {\r
+            // if we are at the end of the song, go to the next song first\r
+\r
+            mPlayer.start();\r
+            // make sure we fade in, in case a previous fadein was stopped\r
+            // because\r
+            // of another focus loss\r
+            mMediaplayerHandler.removeMessages(FADEDOWN);\r
+            mMediaplayerHandler.sendEmptyMessage(FADEUP);\r
+\r
+            RemoteViews views = new RemoteViews(getPackageName(), R.layout.status_bar);\r
+            if (b != null) {\r
+                views.setViewVisibility(R.id.status_bar_icon, View.GONE);\r
+                views.setViewVisibility(R.id.status_bar_album_art, View.VISIBLE);\r
+                views.setImageViewBitmap(R.id.status_bar_album_art, b);\r
+            } else {\r
+                views.setViewVisibility(R.id.status_bar_icon, View.VISIBLE);\r
+                views.setViewVisibility(R.id.status_bar_album_art, View.GONE);\r
+            }\r
+            ComponentName rec = new ComponentName(getPackageName(),\r
+                    MediaButtonIntentReceiver.class.getName());\r
+            Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);\r
+            mediaButtonIntent.putExtra(CMDNOTIF, 1);\r
+            mediaButtonIntent.setComponent(rec);\r
+            KeyEvent mediaKey = new KeyEvent(KeyEvent.ACTION_DOWN,\r
+                    KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);\r
+            mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, mediaKey);\r
+            PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(),\r
+                    1, mediaButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT);\r
+            views.setOnClickPendingIntent(R.id.status_bar_play, mediaPendingIntent);\r
+            mediaButtonIntent.putExtra(CMDNOTIF, 2);\r
+            mediaKey = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT);\r
+            mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, mediaKey);\r
+            mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 2,\r
+                    mediaButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT);\r
+            views.setOnClickPendingIntent(R.id.status_bar_next, mediaPendingIntent);\r
+            mediaButtonIntent.putExtra(CMDNOTIF, 3);\r
+            mediaKey = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_STOP);\r
+            mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, mediaKey);\r
+            mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 3,\r
+                    mediaButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT);\r
+            views.setOnClickPendingIntent(R.id.status_bar_collapse, mediaPendingIntent);\r
+            views.setImageViewResource(R.id.status_bar_play, R.drawable.apollo_holo_dark_pause);\r
+\r
+            views.setTextViewText(R.id.status_bar_track_name, getTrackName());\r
+            views.setTextViewText(R.id.status_bar_artist_name, getArtistName());\r
+\r
+            status = new Notification();\r
+            status.contentView = views;\r
+            status.flags = Notification.FLAG_ONGOING_EVENT;\r
+            status.icon = R.drawable.stat_notify_music;\r
+            status.contentIntent = PendingIntent\r
+                    .getActivity(this, 0, new Intent("com.andrew.apollo.PLAYBACK_VIEWER")\r
+                            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0);\r
+            startForeground(PLAYBACKSERVICE_STATUS, status);\r
+            if (!mIsSupposedToBePlaying) {\r
+                mIsSupposedToBePlaying = true;\r
+                notifyChange(PLAYSTATE_CHANGED);\r
+            }\r
+        } else if (mPlayListLen <= 0) {\r
+            // This is mostly so that if you press 'play' on a bluetooth headset\r
+            // without every having played anything before, it will still play\r
+            // something.\r
+            setShuffleMode(SHUFFLE_AUTO);\r
+        }\r
+    }\r
+\r
+    private void stop(boolean remove_status_icon) {\r
+        if (mPlayer.isInitialized()) {\r
+            mPlayer.stop();\r
+        }\r
+        mFileToPlay = null;\r
+        if (mCursor != null) {\r
+            mCursor.close();\r
+            mCursor = null;\r
+        }\r
+        if (remove_status_icon) {\r
+            gotoIdleState();\r
+        } else {\r
+            stopForeground(false);\r
+        }\r
+        if (remove_status_icon) {\r
+            mIsSupposedToBePlaying = false;\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Stops playback.\r
+     */\r
+    public void stop() {\r
+        stop(true);\r
+    }\r
+\r
+    /**\r
+     * Pauses playback (call play() to resume)\r
+     */\r
+    public void pause() {\r
+        synchronized (this) {\r
+            mMediaplayerHandler.removeMessages(FADEUP);\r
+            if (mIsSupposedToBePlaying) {\r
+                mPlayer.pause();\r
+                gotoIdleState();\r
+                mIsSupposedToBePlaying = false;\r
+                notifyChange(PLAYSTATE_CHANGED);\r
+                saveBookmarkIfNeeded();\r
+            }\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Returns whether something is currently playing\r
+     * \r
+     * @return true if something is playing (or will be playing shortly, in case\r
+     *         we're currently transitioning between tracks), false if not.\r
+     */\r
+    public boolean isPlaying() {\r
+        return mIsSupposedToBePlaying;\r
+    }\r
+\r
+    /*\r
+     * Desired behavior for prev/next/shuffle: - NEXT will move to the next\r
+     * track in the list when not shuffling, and to a track randomly picked from\r
+     * the not-yet-played tracks when shuffling. If all tracks have already been\r
+     * played, pick from the full set, but avoid picking the previously played\r
+     * track if possible. - when shuffling, PREV will go to the previously\r
+     * played track. Hitting PREV again will go to the track played before that,\r
+     * etc. When the start of the history has been reached, PREV is a no-op.\r
+     * When not shuffling, PREV will go to the sequentially previous track (the\r
+     * difference with the shuffle-case is mainly that when not shuffling, the\r
+     * user can back up to tracks that are not in the history). Example: When\r
+     * playing an album with 10 tracks from the start, and enabling shuffle\r
+     * while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.\r
+     * the final play order might be 1-2-3-4-5-8-10-6-9-7. When hitting 'prev' 8\r
+     * times while playing track 7 in this example, the user will go to tracks\r
+     * 9-6-10-8-5-4-3-2. If the user then hits 'next', a random track will be\r
+     * picked again. If at any time user disables shuffling the next/previous\r
+     * track will be picked in sequential order again.\r
+     */\r
+\r
+    public void prev() {\r
+        synchronized (this) {\r
+            if (mShuffleMode == SHUFFLE_NORMAL) {\r
+                // go to previously-played track and remove it from the history\r
+                int histsize = mHistory.size();\r
+                if (histsize == 0) {\r
+                    // prev is a no-op\r
+                    return;\r
+                }\r
+                Integer pos = mHistory.remove(histsize - 1);\r
+                mPlayPos = pos.intValue();\r
+            } else {\r
+                if (mPlayPos > 0) {\r
+                    mPlayPos--;\r
+                } else {\r
+                    mPlayPos = mPlayListLen - 1;\r
+                }\r
+            }\r
+            saveBookmarkIfNeeded();\r
+            stop(false);\r
+            openCurrent();\r
+            play();\r
+\r
+            notifyChange(META_CHANGED);\r
+        }\r
+    }\r
+\r
+    public void next(boolean force) {\r
+        synchronized (this) {\r
+            if (mPlayListLen <= 0) {\r
+                Log.d(LOGTAG, "No play queue");\r
+                return;\r
+            }\r
+\r
+            if (mShuffleMode == SHUFFLE_NORMAL) {\r
+\r
+                if (mPlayPos >= 0) {\r
+                    mHistory.add(mPlayPos);\r
+                }\r
+                if (mHistory.size() > MAX_HISTORY_SIZE) {\r
+                    mHistory.removeElementAt(0);\r
+                }\r
+\r
+                int numTracks = mPlayListLen;\r
+                int[] tracks = new int[numTracks];\r
+                for (int i = 0; i < numTracks; i++) {\r
+                    tracks[i] = i;\r
+                }\r
+\r
+                int numHistory = mHistory.size();\r
+                int numUnplayed = numTracks;\r
+                for (int i = 0; i < numHistory; i++) {\r
+                    int idx = mHistory.get(i).intValue();\r
+                    if (idx < numTracks && tracks[idx] >= 0) {\r
+                        numUnplayed--;\r
+                        tracks[idx] = -1;\r
+                    }\r
+                }\r
+\r
+                // 'numUnplayed' now indicates how many tracks have not yet\r
+                // been played, and 'tracks' contains the indices of those\r
+                // tracks.\r
+                if (numUnplayed <= 0) {\r
+                    // everything's already been played\r
+                    if (mRepeatMode == REPEAT_ALL || force) {\r
+                        // pick from full set\r
+                        numUnplayed = numTracks;\r
+                        for (int i = 0; i < numTracks; i++) {\r
+                            tracks[i] = i;\r
+                        }\r
+                    } else {\r
+                        // all done\r
+                        gotoIdleState();\r
+                        if (mIsSupposedToBePlaying) {\r
+                            mIsSupposedToBePlaying = false;\r
+                            notifyChange(PLAYSTATE_CHANGED);\r
+                        }\r
+                        return;\r
+                    }\r
+                }\r
+                int skip = mRand.nextInt(numUnplayed);\r
+                int cnt = -1;\r
+                while (true) {\r
+                    while (tracks[++cnt] < 0)\r
+                        ;\r
+                    skip--;\r
+                    if (skip < 0) {\r
+                        break;\r
+                    }\r
+                }\r
+                mPlayPos = cnt;\r
+            } else if (mShuffleMode == SHUFFLE_AUTO) {\r
+                doAutoShuffleUpdate();\r
+                mPlayPos++;\r
+            } else {\r
+                if (mPlayPos >= mPlayListLen - 1) {\r
+                    // we're at the end of the list\r
+                    if (mRepeatMode == REPEAT_NONE && !force) {\r
+                        // all done\r
+                        gotoIdleState();\r
+                        mIsSupposedToBePlaying = false;\r
+                        notifyChange(PLAYSTATE_CHANGED);\r
+                        return;\r
+                    } else if (mRepeatMode == REPEAT_ALL || force) {\r
+                        mPlayPos = 0;\r
+                    }\r
+                } else {\r
+                    mPlayPos++;\r
+                }\r
+            }\r
+            saveBookmarkIfNeeded();\r
+            stop(false);\r
+            openCurrent();\r
+            play();\r
+            notifyChange(META_CHANGED);\r
+        }\r
+    }\r
+\r
+    public void cycleRepeat() {\r
+        if (mRepeatMode == REPEAT_NONE) {\r
+            setRepeatMode(REPEAT_ALL);\r
+        } else if (mRepeatMode == REPEAT_ALL) {\r
+            setRepeatMode(REPEAT_CURRENT);\r
+            if (mShuffleMode != SHUFFLE_NONE) {\r
+                setShuffleMode(SHUFFLE_NONE);\r
+            }\r
+        } else {\r
+            setRepeatMode(REPEAT_NONE);\r
+        }\r
+    }\r
+\r
+    public void toggleShuffle() {\r
+        if (mShuffleMode == SHUFFLE_NONE) {\r
+            setShuffleMode(SHUFFLE_NORMAL);\r
+            if (mRepeatMode == REPEAT_CURRENT) {\r
+                setRepeatMode(REPEAT_ALL);\r
+            }\r
+        } else if (mShuffleMode == SHUFFLE_NORMAL || mShuffleMode == SHUFFLE_AUTO) {\r
+            setShuffleMode(SHUFFLE_NONE);\r
+        }\r
+    }\r
+\r
+    private void gotoIdleState() {\r
+        mDelayedStopHandler.removeCallbacksAndMessages(null);\r
+        Message msg = mDelayedStopHandler.obtainMessage();\r
+        mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);\r
+        stopForeground(false);\r
+        if (status != null) {\r
+            status.contentView.setImageViewResource(R.id.status_bar_play,\r
+                    mIsSupposedToBePlaying ? R.drawable.apollo_holo_dark_play\r
+                            : R.drawable.apollo_holo_dark_pause);\r
+            NotificationManager mManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);\r
+            mManager.notify(PLAYBACKSERVICE_STATUS, status);\r
+        }\r
+    }\r
+\r
+    private void saveBookmarkIfNeeded() {\r
+        try {\r
+            if (isPodcast()) {\r
+                long pos = position();\r
+                long bookmark = getBookmark();\r
+                long duration = duration();\r
+                if ((pos < bookmark && (pos + 10000) > bookmark)\r
+                        || (pos > bookmark && (pos - 10000) < bookmark)) {\r
+                    // The existing bookmark is close to the current\r
+                    // position, so don't update it.\r
+                    return;\r
+                }\r
+                if (pos < 15000 || (pos + 10000) > duration) {\r
+                    // if we're near the start or end, clear the bookmark\r
+                    pos = 0;\r
+                }\r
+\r
+                // write 'pos' to the bookmark field\r
+                ContentValues values = new ContentValues();\r
+                values.put(AudioColumns.BOOKMARK, pos);\r
+                Uri uri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,\r
+                        mCursor.getLong(IDCOLIDX));\r
+                getContentResolver().update(uri, values, null, null);\r
+            }\r
+        } catch (SQLiteException ex) {\r
+        }\r
+    }\r
+\r
+    // Make sure there are at least 5 items after the currently playing item\r
+    // and no more than 10 items before.\r
+    private void doAutoShuffleUpdate() {\r
+        boolean notify = false;\r
+\r
+        // remove old entries\r
+        if (mPlayPos > 10) {\r
+            removeTracks(0, mPlayPos - 9);\r
+            notify = true;\r
+        }\r
+        // add new entries if needed\r
+        int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));\r
+        for (int i = 0; i < to_add; i++) {\r
+            // pick something at random from the list\r
+\r
+            int lookback = mHistory.size();\r
+            int idx = -1;\r
+            while (true) {\r
+                idx = mRand.nextInt(mAutoShuffleList.length);\r
+                if (!wasRecentlyUsed(idx, lookback)) {\r
+                    break;\r
+                }\r
+                lookback /= 2;\r
+            }\r
+            mHistory.add(idx);\r
+            if (mHistory.size() > MAX_HISTORY_SIZE) {\r
+                mHistory.remove(0);\r
+            }\r
+            ensurePlayListCapacity(mPlayListLen + 1);\r
+            mPlayList[mPlayListLen++] = mAutoShuffleList[idx];\r
+            notify = true;\r
+        }\r
+        if (notify) {\r
+            notifyChange(QUEUE_CHANGED);\r
+        }\r
+    }\r
+\r
+    // check that the specified idx is not in the history (but only look at at\r
+    // most lookbacksize entries in the history)\r
+    private boolean wasRecentlyUsed(int idx, int lookbacksize) {\r
+\r
+        // early exit to prevent infinite loops in case idx == mPlayPos\r
+        if (lookbacksize == 0) {\r
+            return false;\r
+        }\r
+\r
+        int histsize = mHistory.size();\r
+        if (histsize < lookbacksize) {\r
+            lookbacksize = histsize;\r
+        }\r
+        int maxidx = histsize - 1;\r
+        for (int i = 0; i < lookbacksize; i++) {\r
+            long entry = mHistory.get(maxidx - i);\r
+            if (entry == idx) {\r
+                return true;\r
+            }\r
+        }\r
+        return false;\r
+    }\r
+\r
+    // A simple variation of Random that makes sure that the\r
+    // value it returns is not equal to the value it returned\r
+    // previously, unless the interval is 1.\r
+    private static class Shuffler {\r
+        private int mPrevious;\r
+\r
+        private final Random mRandom = new Random();\r
+\r
+        public int nextInt(int interval) {\r
+            int ret;\r
+            do {\r
+                ret = mRandom.nextInt(interval);\r
+            } while (ret == mPrevious && interval > 1);\r
+            mPrevious = ret;\r
+            return ret;\r
+        }\r
+    };\r
+\r
+    private boolean makeAutoShuffleList() {\r
+        ContentResolver res = getContentResolver();\r
+        Cursor c = null;\r
+        try {\r
+            c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, new String[] {\r
+                BaseColumns._ID\r
+            }, AudioColumns.IS_MUSIC + "=1", null, null);\r
+            if (c == null || c.getCount() == 0) {\r
+                return false;\r
+            }\r
+            int len = c.getCount();\r
+            long[] list = new long[len];\r
+            for (int i = 0; i < len; i++) {\r
+                c.moveToNext();\r
+                list[i] = c.getLong(0);\r
+            }\r
+            mAutoShuffleList = list;\r
+            return true;\r
+        } catch (RuntimeException ex) {\r
+        } finally {\r
+            if (c != null) {\r
+                c.close();\r
+            }\r
+        }\r
+        return false;\r
+    }\r
+\r
+    /**\r
+     * Removes the range of tracks specified from the play list. If a file\r
+     * within the range is the file currently being played, playback will move\r
+     * to the next file after the range.\r
+     * \r
+     * @param first The first file to be removed\r
+     * @param last The last file to be removed\r
+     * @return the number of tracks deleted\r
+     */\r
+    public int removeTracks(int first, int last) {\r
+        int numremoved = removeTracksInternal(first, last);\r
+        if (numremoved > 0) {\r
+            notifyChange(QUEUE_CHANGED);\r
+        }\r
+        return numremoved;\r
+    }\r
+\r
+    private int removeTracksInternal(int first, int last) {\r
+        synchronized (this) {\r
+            if (last < first)\r
+                return 0;\r
+            if (first < 0)\r
+                first = 0;\r
+            if (last >= mPlayListLen)\r
+                last = mPlayListLen - 1;\r
+\r
+            boolean gotonext = false;\r
+            if (first <= mPlayPos && mPlayPos <= last) {\r
+                mPlayPos = first;\r
+                gotonext = true;\r
+            } else if (mPlayPos > last) {\r
+                mPlayPos -= (last - first + 1);\r
+            }\r
+            int num = mPlayListLen - last - 1;\r
+            for (int i = 0; i < num; i++) {\r
+                mPlayList[first + i] = mPlayList[last + 1 + i];\r
+            }\r
+            mPlayListLen -= last - first + 1;\r
+\r
+            if (gotonext) {\r
+                if (mPlayListLen == 0) {\r
+                    stop(true);\r
+                    mPlayPos = -1;\r
+                    if (mCursor != null) {\r
+                        mCursor.close();\r
+                        mCursor = null;\r
+                    }\r
+                } else {\r
+                    if (mPlayPos >= mPlayListLen) {\r
+                        mPlayPos = 0;\r
+                    }\r
+                    boolean wasPlaying = mIsSupposedToBePlaying;\r
+                    stop(false);\r
+                    openCurrent();\r
+                    if (wasPlaying) {\r
+                        play();\r
+                    }\r
+                }\r
+                notifyChange(META_CHANGED);\r
+            }\r
+            return last - first + 1;\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Removes all instances of the track with the given id from the playlist.\r
+     * \r
+     * @param id The id to be removed\r
+     * @return how many instances of the track were removed\r
+     */\r
+    public int removeTrack(long id) {\r
+        int numremoved = 0;\r
+        synchronized (this) {\r
+            for (int i = 0; i < mPlayListLen; i++) {\r
+                if (mPlayList[i] == id) {\r
+                    numremoved += removeTracksInternal(i, i);\r
+                    i--;\r
+                }\r
+            }\r
+        }\r
+        if (numremoved > 0) {\r
+            notifyChange(QUEUE_CHANGED);\r
+        }\r
+        return numremoved;\r
+    }\r
+\r
+    public void setShuffleMode(int shufflemode) {\r
+        synchronized (this) {\r
+            if (mShuffleMode == shufflemode && mPlayListLen > 0) {\r
+                return;\r
+            }\r
+            mShuffleMode = shufflemode;\r
+            notifyChange(SHUFFLEMODE_CHANGED);\r
+            if (mShuffleMode == SHUFFLE_AUTO) {\r
+                if (makeAutoShuffleList()) {\r
+                    mPlayListLen = 0;\r
+                    doAutoShuffleUpdate();\r
+                    mPlayPos = 0;\r
+                    openCurrent();\r
+                    play();\r
+                    notifyChange(META_CHANGED);\r
+                    return;\r
+                } else {\r
+                    // failed to build a list of files to shuffle\r
+                    mShuffleMode = SHUFFLE_NONE;\r
+                }\r
+            }\r
+            saveQueue(false);\r
+        }\r
+    }\r
+\r
+    public int getShuffleMode() {\r
+        return mShuffleMode;\r
+    }\r
+\r
+    public void setRepeatMode(int repeatmode) {\r
+        synchronized (this) {\r
+            mRepeatMode = repeatmode;\r
+            notifyChange(REPEATMODE_CHANGED);\r
+            saveQueue(false);\r
+        }\r
+    }\r
+\r
+    public int getRepeatMode() {\r
+        return mRepeatMode;\r
+    }\r
+\r
+    public int getMediaMountedCount() {\r
+        return mMediaMountedCount;\r
+    }\r
+\r
+    /**\r
+     * Returns the path of the currently playing file, or null if no file is\r
+     * currently playing.\r
+     */\r
+    public String getPath() {\r
+        return mFileToPlay;\r
+    }\r
+\r
+    /**\r
+     * Returns the rowid of the currently playing file, or -1 if no file is\r
+     * currently playing.\r
+     */\r
+    public long getAudioId() {\r
+        synchronized (this) {\r
+            if (mPlayPos >= 0 && mPlayer.isInitialized()) {\r
+                return mPlayList[mPlayPos];\r
+            }\r
+        }\r
+        return -1;\r
+    }\r
+\r
+    /**\r
+     * Returns the position in the queue\r
+     * \r
+     * @return the position in the queue\r
+     */\r
+    public int getQueuePosition() {\r
+        synchronized (this) {\r
+            return mPlayPos;\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Starts playing the track at the given position in the queue.\r
+     * \r
+     * @param pos The position in the queue of the track that will be played.\r
+     */\r
+    public void setQueuePosition(int pos) {\r
+        synchronized (this) {\r
+            stop(false);\r
+            mPlayPos = pos;\r
+            openCurrent();\r
+            play();\r
+            notifyChange(META_CHANGED);\r
+            if (mShuffleMode == SHUFFLE_AUTO) {\r
+                doAutoShuffleUpdate();\r
+            }\r
+        }\r
+    }\r
+\r
+    public String getArtistName() {\r
+        synchronized (this) {\r
+            if (mCursor == null) {\r
+                return null;\r
+            }\r
+            return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.ARTIST));\r
+        }\r
+    }\r
+\r
+    public long getArtistId() {\r
+        synchronized (this) {\r
+            if (mCursor == null) {\r
+                return -1;\r
+            }\r
+            return mCursor.getLong(mCursor.getColumnIndexOrThrow(AudioColumns.ARTIST_ID));\r
+        }\r
+    }\r
+\r
+    public String getAlbumName() {\r
+        synchronized (this) {\r
+            if (mCursor == null) {\r
+                return null;\r
+            }\r
+            return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.ALBUM));\r
+        }\r
+    }\r
+\r
+    public long getAlbumId() {\r
+        synchronized (this) {\r
+            if (mCursor == null) {\r
+                return -1;\r
+            }\r
+            return mCursor.getLong(mCursor.getColumnIndexOrThrow(AudioColumns.ALBUM_ID));\r
+        }\r
+    }\r
+\r
+    public String getTrackName() {\r
+        synchronized (this) {\r
+            if (mCursor == null) {\r
+                return null;\r
+            }\r
+            return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaColumns.TITLE));\r
+        }\r
+    }\r
+\r
+    private boolean isPodcast() {\r
+        synchronized (this) {\r
+            if (mCursor == null) {\r
+                return false;\r
+            }\r
+            return (mCursor.getInt(PODCASTCOLIDX) > 0);\r
+        }\r
+    }\r
+\r
+    private long getBookmark() {\r
+        synchronized (this) {\r
+            if (mCursor == null) {\r
+                return 0;\r
+            }\r
+            return mCursor.getLong(BOOKMARKCOLIDX);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Returns the duration of the file in milliseconds. Currently this method\r
+     * returns -1 for the duration of MIDI files.\r
+     */\r
+    public long duration() {\r
+        if (mPlayer.isInitialized()) {\r
+            return mPlayer.duration();\r
+        }\r
+        return -1;\r
+    }\r
+\r
+    /**\r
+     * Returns the current playback position in milliseconds\r
+     */\r
+    public long position() {\r
+        if (mPlayer.isInitialized()) {\r
+            return mPlayer.position();\r
+        }\r
+        return -1;\r
+    }\r
+\r
+    /**\r
+     * Seeks to the position specified.\r
+     * \r
+     * @param pos The position to seek to, in milliseconds\r
+     */\r
+    public long seek(long pos) {\r
+        if (mPlayer.isInitialized()) {\r
+            if (pos < 0)\r
+                pos = 0;\r
+            if (pos > mPlayer.duration())\r
+                pos = mPlayer.duration();\r
+            return mPlayer.seek(pos);\r
+        }\r
+        return -1;\r
+    }\r
+\r
+    /**\r
+     * Sets the audio session ID.\r
+     * \r
+     * @param sessionId: the audio session ID.\r
+     */\r
+    public void setAudioSessionId(int sessionId) {\r
+        synchronized (this) {\r
+            mPlayer.setAudioSessionId(sessionId);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Returns the audio session ID.\r
+     */\r
+    public int getAudioSessionId() {\r
+        synchronized (this) {\r
+            return mPlayer.getAudioSessionId();\r
+        }\r
+    }\r
+\r
+    public void toggleFavorite() {\r
+        if (!isFavorite()) {\r
+            addToFavorites();\r
+        } else {\r
+            removeFromFavorites();\r
+        }\r
+    }\r
+\r
+    public boolean isFavorite() {\r
+        if (getAudioId() >= 0)\r
+            return isFavorite(getAudioId());\r
+        return false;\r
+    }\r
+\r
+    public boolean isFavorite(long id) {\r
+        return MusicUtils.isFavorite(this, id);\r
+    }\r
+\r
+    public void removeFromFavorites() {\r
+        if (getAudioId() >= 0) {\r
+            removeFromFavorites(getAudioId());\r
+        }\r
+    }\r
+\r
+    public void removeFromFavorites(long id) {\r
+        MusicUtils.removeFromFavorites(this, id);\r
+        notifyChange(FAVORITE_CHANGED);\r
+    }\r
+\r
+    public void addToFavorites() {\r
+        if (getAudioId() >= 0) {\r
+            addToFavorites(getAudioId());\r
+        }\r
+    }\r
+\r
+    public void addToFavorites(long id) {\r
+        MusicUtils.addToFavorites(this, id);\r
+        notifyChange(FAVORITE_CHANGED);\r
+    }\r
+\r
+    /**\r
+     * Provides a unified interface for dealing with midi files and other media\r
+     * files.\r
+     */\r
+    private class MultiPlayer {\r
+        private MediaPlayer mMediaPlayer = new MediaPlayer();\r
+\r
+        private Handler mHandler;\r
+\r
+        private boolean mIsInitialized = false;\r
+\r
+        public MultiPlayer() {\r
+            mMediaPlayer.setWakeMode(ApolloService.this, PowerManager.PARTIAL_WAKE_LOCK);\r
+        }\r
+\r
+        public void setDataSource(String path) {\r
+            try {\r
+                mMediaPlayer.reset();\r
+                mMediaPlayer.setOnPreparedListener(null);\r
+                if (path.startsWith("content://")) {\r
+                    mMediaPlayer.setDataSource(ApolloService.this, Uri.parse(path));\r
+                } else {\r
+                    mMediaPlayer.setDataSource(path);\r
+                }\r
+                mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);\r
+                mMediaPlayer.prepare();\r
+            } catch (IOException ex) {\r
+                mIsInitialized = false;\r
+                return;\r
+            } catch (IllegalArgumentException ex) {\r
+                mIsInitialized = false;\r
+                return;\r
+            }\r
+            mMediaPlayer.setOnCompletionListener(listener);\r
+            mMediaPlayer.setOnErrorListener(errorListener);\r
+            Intent i = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);\r
+            i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());\r
+            i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());\r
+            sendBroadcast(i);\r
+            mIsInitialized = true;\r
+        }\r
+\r
+        public boolean isInitialized() {\r
+            return mIsInitialized;\r
+        }\r
+\r
+        public void start() {\r
+            mMediaPlayer.start();\r
+        }\r
+\r
+        public void stop() {\r
+            mMediaPlayer.reset();\r
+            mIsInitialized = false;\r
+        }\r
+\r
+        /**\r
+         * You CANNOT use this player anymore after calling release()\r
+         */\r
+        public void release() {\r
+            stop();\r
+            mMediaPlayer.release();\r
+        }\r
+\r
+        public void pause() {\r
+            mMediaPlayer.pause();\r
+        }\r
+\r
+        public void setHandler(Handler handler) {\r
+            mHandler = handler;\r
+        }\r
+\r
+        MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {\r
+            @Override\r
+            public void onCompletion(MediaPlayer mp) {\r
+                // Acquire a temporary wakelock, since when we return from\r
+                // this callback the MediaPlayer will release its wakelock\r
+                // and allow the device to go to sleep.\r
+                // This temporary wakelock is released when the RELEASE_WAKELOCK\r
+                // message is processed, but just in case, put a timeout on it.\r
+                mWakeLock.acquire(30000);\r
+                mHandler.sendEmptyMessage(TRACK_ENDED);\r
+                mHandler.sendEmptyMessage(RELEASE_WAKELOCK);\r
+            }\r
+        };\r
+\r
+        MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {\r
+            @Override\r
+            public boolean onError(MediaPlayer mp, int what, int extra) {\r
+                switch (what) {\r
+                    case MediaPlayer.MEDIA_ERROR_SERVER_DIED:\r
+                        mIsInitialized = false;\r
+                        mMediaPlayer.release();\r
+                        // Creating a new MediaPlayer and settings its wakemode\r
+                        // does not\r
+                        // require the media service, so it's OK to do this now,\r
+                        // while the\r
+                        // service is still being restarted\r
+                        mMediaPlayer = new MediaPlayer();\r
+                        mMediaPlayer\r
+                                .setWakeMode(ApolloService.this, PowerManager.PARTIAL_WAKE_LOCK);\r
+                        mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);\r
+                        return true;\r
+                    default:\r
+                        Log.d("MultiPlayer", "Error: " + what + "," + extra);\r
+                        break;\r
+                }\r
+                return false;\r
+            }\r
+        };\r
+\r
+        public long duration() {\r
+            return mMediaPlayer.getDuration();\r
+        }\r
+\r
+        public long position() {\r
+            return mMediaPlayer.getCurrentPosition();\r
+        }\r
+\r
+        public long seek(long whereto) {\r
+            mMediaPlayer.seekTo((int)whereto);\r
+            return whereto;\r
+        }\r
+\r
+        public void setVolume(float vol) {\r
+            mMediaPlayer.setVolume(vol, vol);\r
+        }\r
+\r
+        public void setAudioSessionId(int sessionId) {\r
+            mMediaPlayer.setAudioSessionId(sessionId);\r
+        }\r
+\r
+        public int getAudioSessionId() {\r
+            return mMediaPlayer.getAudioSessionId();\r
+        }\r
+    }\r
+\r
+    /*\r
+     * By making this a static class with a WeakReference to the Service, we\r
+     * ensure that the Service can be GCd even when the system process still has\r
+     * a remote reference to the stub.\r
+     */\r
+    static class ServiceStub extends IApolloService.Stub {\r
+        WeakReference<ApolloService> mService;\r
+\r
+        ServiceStub(ApolloService service) {\r
+            mService = new WeakReference<ApolloService>(service);\r
+        }\r
+\r
+        @Override\r
+        public void openFile(String path) {\r
+            mService.get().open(path);\r
+        }\r
+\r
+        @Override\r
+        public void open(long[] list, int position) {\r
+            mService.get().open(list, position);\r
+        }\r
+\r
+        @Override\r
+        public int getQueuePosition() {\r
+            return mService.get().getQueuePosition();\r
+        }\r
+\r
+        @Override\r
+        public void setQueuePosition(int index) {\r
+            mService.get().setQueuePosition(index);\r
+        }\r
+\r
+        @Override\r
+        public boolean isPlaying() {\r
+            return mService.get().isPlaying();\r
+        }\r
+\r
+        @Override\r
+        public void stop() {\r
+            mService.get().stop();\r
+        }\r
+\r
+        @Override\r
+        public void pause() {\r
+            mService.get().pause();\r
+        }\r
+\r
+        @Override\r
+        public void play() {\r
+            mService.get().play();\r
+        }\r
+\r
+        @Override\r
+        public void prev() {\r
+            mService.get().prev();\r
+        }\r
+\r
+        @Override\r
+        public void next() {\r
+            mService.get().next(true);\r
+        }\r
+\r
+        @Override\r
+        public String getTrackName() {\r
+            return mService.get().getTrackName();\r
+        }\r
+\r
+        @Override\r
+        public String getAlbumName() {\r
+            return mService.get().getAlbumName();\r
+        }\r
+\r
+        @Override\r
+        public long getAlbumId() {\r
+            return mService.get().getAlbumId();\r
+        }\r
+\r
+        @Override\r
+        public String getArtistName() {\r
+            return mService.get().getArtistName();\r
+        }\r
+\r
+        @Override\r
+        public long getArtistId() {\r
+            return mService.get().getArtistId();\r
+        }\r
+\r
+        @Override\r
+        public void enqueue(long[] list, int action) {\r
+            mService.get().enqueue(list, action);\r
+        }\r
+\r
+        @Override\r
+        public long[] getQueue() {\r
+            return mService.get().getQueue();\r
+        }\r
+\r
+        @Override\r
+        public String getPath() {\r
+            return mService.get().getPath();\r
+        }\r
+\r
+        @Override\r
+        public long getAudioId() {\r
+            return mService.get().getAudioId();\r
+        }\r
+\r
+        @Override\r
+        public long position() {\r
+            return mService.get().position();\r
+        }\r
+\r
+        @Override\r
+        public long duration() {\r
+            return mService.get().duration();\r
+        }\r
+\r
+        @Override\r
+        public long seek(long pos) {\r
+            return mService.get().seek(pos);\r
+        }\r
+\r
+        @Override\r
+        public void setShuffleMode(int shufflemode) {\r
+            mService.get().setShuffleMode(shufflemode);\r
+        }\r
+\r
+        @Override\r
+        public int getShuffleMode() {\r
+            return mService.get().getShuffleMode();\r
+        }\r
+\r
+        @Override\r
+        public int removeTracks(int first, int last) {\r
+            return mService.get().removeTracks(first, last);\r
+        }\r
+\r
+        @Override\r
+        public int removeTrack(long id) {\r
+            return mService.get().removeTrack(id);\r
+        }\r
+\r
+        @Override\r
+        public void setRepeatMode(int repeatmode) {\r
+            mService.get().setRepeatMode(repeatmode);\r
+        }\r
+\r
+        @Override\r
+        public int getRepeatMode() {\r
+            return mService.get().getRepeatMode();\r
+        }\r
+\r
+        @Override\r
+        public int getMediaMountedCount() {\r
+            return mService.get().getMediaMountedCount();\r
+        }\r
+\r
+        @Override\r
+        public int getAudioSessionId() {\r
+            return mService.get().getAudioSessionId();\r
+        }\r
+\r
+        @Override\r
+        public void addToFavorites(long id) throws RemoteException {\r
+            mService.get().addToFavorites(id);\r
+        }\r
+\r
+        @Override\r
+        public void removeFromFavorites(long id) throws RemoteException {\r
+            mService.get().removeFromFavorites(id);\r
+        }\r
+\r
+        @Override\r
+        public boolean isFavorite(long id) throws RemoteException {\r
+            return mService.get().isFavorite(id);\r
+        }\r
+\r
+        @Override\r
+        public void toggleFavorite() throws RemoteException {\r
+            mService.get().toggleFavorite();\r
+        }\r
+\r
+    }\r
+\r
+    private final IBinder mBinder = new ServiceStub(this);\r
+\r
+}\r
diff --git a/src/com/andrew/apollo/service/MediaButtonIntentReceiver.java b/src/com/andrew/apollo/service/MediaButtonIntentReceiver.java
new file mode 100644 (file)
index 0000000..a95214c
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * 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.andrew.apollo.service;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.os.Handler;
+import android.os.Message;
+import android.view.KeyEvent;
+
+import com.andrew.apollo.activities.AudioPlayerHolder;
+
+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, AudioPlayerHolder.class);
+                        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                        context.startActivity(i);
+                        mLaunched = true;
+                    }
+                    break;
+            }
+        }
+    };
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String intentAction = intent.getAction();
+        if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intentAction)) {
+            Intent i = new Intent(context, ApolloService.class);
+            i.setAction(ApolloService.SERVICECMD);
+            i.putExtra(ApolloService.CMDNAME, ApolloService.CMDPAUSE);
+            context.startService(i);
+        } else if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) {
+            KeyEvent event = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+
+            if (event == null) {
+                return;
+            }
+
+            int keycode = event.getKeyCode();
+            int action = event.getAction();
+            long eventtime = event.getEventTime();
+            int buttonId = intent.getIntExtra(ApolloService.CMDNOTIF, 0);
+
+            // single quick press: pause/resume.
+            // double press: next track
+            // long press: start auto-shuffle mode.
+
+            String command = null;
+            switch (keycode) {
+                case KeyEvent.KEYCODE_MEDIA_STOP:
+                    command = ApolloService.CMDSTOP;
+                    break;
+                case KeyEvent.KEYCODE_HEADSETHOOK:
+                case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+                    command = ApolloService.CMDTOGGLEPAUSE;
+                    break;
+                case KeyEvent.KEYCODE_MEDIA_NEXT:
+                    command = ApolloService.CMDNEXT;
+                    break;
+                case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+                    command = ApolloService.CMDPREVIOUS;
+                    break;
+                case KeyEvent.KEYCODE_MEDIA_PAUSE:
+                    command = ApolloService.CMDPAUSE;
+                    break;
+                case KeyEvent.KEYCODE_MEDIA_PLAY:
+                    command = ApolloService.CMDPLAY;
+                    break;
+            }
+
+            if (command != null) {
+                if (action == KeyEvent.ACTION_DOWN) {
+                    if (mDown && (buttonId == 0)) {
+                        if ((ApolloService.CMDTOGGLEPAUSE.equals(command) || ApolloService.CMDPLAY
+                                .equals(command))
+                                && mLastClickTime != 0
+                                && eventtime - mLastClickTime > LONG_PRESS_DELAY) {
+                            mHandler.sendMessage(mHandler.obtainMessage(MSG_LONGPRESS_TIMEOUT,
+                                    context));
+                        }
+                    } else if (event.getRepeatCount() == 0) {
+                        // only consider the first event in a sequence, not the
+                        // repeat events,
+                        // so that we don't trigger in cases where the first
+                        // event went to
+                        // a different app (e.g. when the user ends a phone call
+                        // by
+                        // long pressing the headset button)
+
+                        // The service may or may not be running, but we need to
+                        // send it
+                        // a command.
+                        Intent i = new Intent(context, ApolloService.class);
+                        i.setAction(ApolloService.SERVICECMD);
+                        i.putExtra(ApolloService.CMDNOTIF, buttonId);
+                        if (keycode == KeyEvent.KEYCODE_HEADSETHOOK
+                                && eventtime - mLastClickTime < 300) {
+                            i.putExtra(ApolloService.CMDNAME, ApolloService.CMDNEXT);
+                            context.startService(i);
+                            mLastClickTime = 0;
+                        } else {
+                            i.putExtra(ApolloService.CMDNAME, command);
+                            context.startService(i);
+                            mLastClickTime = eventtime;
+                        }
+
+                        mLaunched = false;
+                        if (buttonId == 0) {
+                            mDown = true;
+                        }
+                    }
+                } else {
+                    mHandler.removeMessages(MSG_LONGPRESS_TIMEOUT);
+                    mDown = false;
+                }
+                if (isOrderedBroadcast()) {
+                    abortBroadcast();
+                }
+            }
+        }
+    }
+}
diff --git a/src/com/andrew/apollo/service/ServiceBinder.java b/src/com/andrew/apollo/service/ServiceBinder.java
new file mode 100644 (file)
index 0000000..2dea385
--- /dev/null
@@ -0,0 +1,31 @@
+\r
+package com.andrew.apollo.service;\r
+\r
+import android.content.ComponentName;\r
+import android.content.ServiceConnection;\r
+import android.os.IBinder;\r
+\r
+import com.andrew.apollo.IApolloService;\r
+import com.andrew.apollo.utils.MusicUtils;\r
+\r
+public class ServiceBinder implements ServiceConnection {\r
+    private final ServiceConnection mCallback;\r
+\r
+    public ServiceBinder(ServiceConnection callback) {\r
+        mCallback = callback;\r
+    }\r
+\r
+    @Override\r
+    public void onServiceConnected(ComponentName className, IBinder service) {\r
+        MusicUtils.mService = IApolloService.Stub.asInterface(service);\r
+        if (mCallback != null)\r
+            mCallback.onServiceConnected(className, service);\r
+    }\r
+\r
+    @Override\r
+    public void onServiceDisconnected(ComponentName className) {\r
+        if (mCallback != null)\r
+            mCallback.onServiceDisconnected(className);\r
+        MusicUtils.mService = null;\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/service/ServiceToken.java b/src/com/andrew/apollo/service/ServiceToken.java
new file mode 100644 (file)
index 0000000..22be005
--- /dev/null
@@ -0,0 +1,12 @@
+\r
+package com.andrew.apollo.service;\r
+\r
+import android.content.ContextWrapper;\r
+\r
+public class ServiceToken {\r
+    public ContextWrapper mWrappedContext;\r
+\r
+    public ServiceToken(ContextWrapper context) {\r
+        mWrappedContext = context;\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/tasks/BitmapFromURL.java b/src/com/andrew/apollo/tasks/BitmapFromURL.java
new file mode 100644 (file)
index 0000000..a147f09
--- /dev/null
@@ -0,0 +1,43 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.tasks;\r
+\r
+import java.lang.ref.WeakReference;\r
+\r
+import android.graphics.Bitmap;\r
+import android.os.AsyncTask;\r
+import android.widget.ImageView;\r
+\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ */\r
+public class BitmapFromURL extends AsyncTask<String, Integer, Bitmap> {\r
+\r
+    private final WeakReference<ImageView> imageViewReference;\r
+\r
+    private final ImageView mImageView;\r
+\r
+    private WeakReference<Bitmap> bitmapReference;\r
+\r
+    public BitmapFromURL(ImageView iv) {\r
+        imageViewReference = new WeakReference<ImageView>(iv);\r
+        mImageView = imageViewReference.get();\r
+    }\r
+\r
+    @Override\r
+    protected Bitmap doInBackground(String... params) {\r
+        bitmapReference = new WeakReference<Bitmap>(ApolloUtils.getBitmapFromURL(params[0]));\r
+        return bitmapReference.get();\r
+    }\r
+\r
+    @Override\r
+    protected void onPostExecute(Bitmap result) {\r
+        if (result != null && mImageView != null)\r
+            ApolloUtils.runnableBackground(mImageView, result);\r
+        super.onPostExecute(result);\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/tasks/FetchAlbumImages.java b/src/com/andrew/apollo/tasks/FetchAlbumImages.java
new file mode 100644 (file)
index 0000000..64c6d96
--- /dev/null
@@ -0,0 +1,122 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.tasks;\r
+\r
+import java.lang.ref.WeakReference;\r
+import java.util.ArrayList;\r
+\r
+import android.content.Context;\r
+import android.database.Cursor;\r
+import android.net.Uri;\r
+import android.os.AsyncTask;\r
+import android.provider.BaseColumns;\r
+import android.provider.MediaStore.Audio;\r
+import android.provider.MediaStore.Audio.AlbumColumns;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ * @returns A String[] of all the artists and albums on a device in default\r
+ *          album order that are then fed into the Last.fm API\r
+ */\r
+public class FetchAlbumImages {\r
+\r
+    private final Context mContext;\r
+\r
+    private final WeakReference<Context> contextReference;\r
+\r
+    private final int choice;\r
+\r
+    public FetchAlbumImages(Context context, int opt) {\r
+        contextReference = new WeakReference<Context>(context);\r
+        mContext = contextReference.get();\r
+        choice = opt;\r
+    }\r
+\r
+    /**\r
+     * @return album names in default album sort order\r
+     */\r
+    public String[] getAlbumArtists() {\r
+        String[] projection = new String[] {\r
+                BaseColumns._ID, AlbumColumns.ARTIST\r
+        };\r
+        String sortOrder = Audio.Albums.DEFAULT_SORT_ORDER;\r
+        Uri uri = Audio.Albums.EXTERNAL_CONTENT_URI;\r
+        Cursor c = mContext.getContentResolver().query(uri, projection, null, null, sortOrder);\r
+        ArrayList<String> artistIds = new ArrayList<String>();\r
+        if (c != null) {\r
+            int count = c.getCount();\r
+            if (count > 0) {\r
+                final int ARTIST_IDX = c.getColumnIndex(AlbumColumns.ARTIST);\r
+                for (int i = 0; i < count; i++) {\r
+                    c.moveToPosition(i);\r
+                    artistIds.add(c.getString(ARTIST_IDX));\r
+                }\r
+            }\r
+            c.close();\r
+            c = null;\r
+        }\r
+        return artistIds.toArray(new String[artistIds.size()]);\r
+    }\r
+\r
+    /**\r
+     * @author Andrew Neal\r
+     * @returns artist names in default album sort order that are then fed into\r
+     *          the Last.fm API along with @getAlbumArtists()\r
+     */\r
+    public class getAlbums extends AsyncTask<Void, Integer, String[]> implements Constants {\r
+\r
+        @Override\r
+        protected String[] doInBackground(Void... params) {\r
+            String[] projection = new String[] {\r
+                    BaseColumns._ID, AlbumColumns.ALBUM\r
+            };\r
+            String sortOrder = Audio.Albums.DEFAULT_SORT_ORDER;\r
+            Uri uri = Audio.Albums.EXTERNAL_CONTENT_URI;\r
+            Cursor c = mContext.getContentResolver().query(uri, projection, null, null, sortOrder);\r
+            ArrayList<String> artistIds = new ArrayList<String>();\r
+            if (c != null) {\r
+                int count = c.getCount();\r
+                if (count > 0) {\r
+                    final int ARTIST_IDX = c.getColumnIndex(AlbumColumns.ALBUM);\r
+                    for (int i = 0; i < count; i++) {\r
+                        c.moveToPosition(i);\r
+                        artistIds.add(c.getString(ARTIST_IDX));\r
+                    }\r
+                }\r
+                c.close();\r
+                c = null;\r
+            }\r
+            return artistIds.toArray(new String[artistIds.size()]);\r
+        }\r
+\r
+        @Override\r
+        protected void onPostExecute(String[] result) {\r
+            for (int i = 0; i < result.length; i++) {\r
+                // Only download images we don't already have\r
+                if (choice == 0 && result != null) {\r
+                    if (ApolloUtils.getImageURL(result[i], ALBUM_IMAGE, mContext) == null) {\r
+                        new LastfmGetAlbumImages(mContext, null, 0).executeOnExecutor(\r
+                                AsyncTask.THREAD_POOL_EXECUTOR, getAlbumArtists()[i], result[i]);\r
+                    }\r
+                } else if (choice == 1 && result != null) {\r
+                    // Unless the user wants to grab new images\r
+                    new LastfmGetAlbumImages(mContext, null, 0).executeOnExecutor(\r
+                            AsyncTask.THREAD_POOL_EXECUTOR, getAlbumArtists()[i], result[i]);\r
+                }\r
+            }\r
+            super.onPostExecute(result);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Fetch album art\r
+     */\r
+    public void runTask() {\r
+        new getAlbums().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/tasks/FetchArtistImages.java b/src/com/andrew/apollo/tasks/FetchArtistImages.java
new file mode 100644 (file)
index 0000000..4800369
--- /dev/null
@@ -0,0 +1,86 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.tasks;\r
+\r
+import java.lang.ref.WeakReference;\r
+import java.util.ArrayList;\r
+\r
+import android.content.Context;\r
+import android.database.Cursor;\r
+import android.net.Uri;\r
+import android.os.AsyncTask;\r
+import android.provider.BaseColumns;\r
+import android.provider.MediaStore.Audio;\r
+import android.provider.MediaStore.Audio.ArtistColumns;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ * @returns A String[] of all the artists on a device in default artist order\r
+ *          that are then fed into the Last.fm API\r
+ */\r
+public class FetchArtistImages extends AsyncTask<Void, Integer, String[]> implements Constants {\r
+\r
+    private final WeakReference<Context> contextReference;\r
+\r
+    private final int choice;\r
+\r
+    public FetchArtistImages(Context context, int opt) {\r
+        contextReference = new WeakReference<Context>(context);\r
+        choice = opt;\r
+    }\r
+\r
+    @Override\r
+    protected String[] doInBackground(Void... params) {\r
+        String[] projection = new String[] {\r
+                BaseColumns._ID, ArtistColumns.ARTIST\r
+        };\r
+        String sortOrder = Audio.Artists.DEFAULT_SORT_ORDER;\r
+        Uri uri = Audio.Artists.EXTERNAL_CONTENT_URI;\r
+        Cursor c = contextReference.get().getContentResolver()\r
+                .query(uri, projection, null, null, sortOrder);\r
+        ArrayList<String> artistIds = new ArrayList<String>();\r
+        if (c != null) {\r
+            int count = c.getCount();\r
+            if (count > 0) {\r
+                final int ARTIST_IDX = c.getColumnIndex(ArtistColumns.ARTIST);\r
+                for (int i = 0; i < count; i++) {\r
+                    c.moveToPosition(i);\r
+                    artistIds.add(c.getString(ARTIST_IDX));\r
+                }\r
+            }\r
+            c.close();\r
+            c = null;\r
+        }\r
+        return artistIds.toArray(new String[artistIds.size()]);\r
+    }\r
+\r
+    @Override\r
+    protected void onPostExecute(String[] result) {\r
+        for (int i = 0; i < result.length; i++) {\r
+            // Only download images we don't already have\r
+            if (choice == 0 && result != null) {\r
+                if (ApolloUtils.getImageURL(result[i], ARTIST_IMAGE, contextReference.get()) == null) {\r
+                    new LastfmGetArtistImages(contextReference.get()).executeOnExecutor(\r
+                            AsyncTask.THREAD_POOL_EXECUTOR, result[i]);\r
+                }\r
+            } else if (choice == 1 && result != null) {\r
+                // Unless the user wants to grab new images\r
+                new LastfmGetArtistImages(contextReference.get()).executeOnExecutor(\r
+                        AsyncTask.THREAD_POOL_EXECUTOR, result[i]);\r
+            }\r
+        }\r
+        super.onPostExecute(result);\r
+    }\r
+\r
+    /**\r
+     * Fetch artist images\r
+     */\r
+    public void runTask() {\r
+        executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/tasks/GetCachedImages.java b/src/com/andrew/apollo/tasks/GetCachedImages.java
new file mode 100644 (file)
index 0000000..041c9dd
--- /dev/null
@@ -0,0 +1,71 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.tasks;\r
+\r
+import java.lang.ref.WeakReference;\r
+\r
+import android.content.Context;\r
+import android.graphics.Bitmap;\r
+import android.os.AsyncTask;\r
+import android.widget.ImageView;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+import com.androidquery.AQuery;\r
+\r
+/**\r
+ * @author Andrew Neal Returns a cached image for @TracksBrowser\r
+ */\r
+public class GetCachedImages extends AsyncTask<String, Integer, Bitmap> implements Constants {\r
+\r
+    private final Context mContext;\r
+\r
+    private final int choice;\r
+\r
+    private final WeakReference<ImageView> imageViewReference;\r
+\r
+    private final AQuery aquery;\r
+\r
+    private final ImageView mImageView;\r
+\r
+    private String url;\r
+\r
+    private WeakReference<Bitmap> bitmapReference;\r
+\r
+    private final WeakReference<Context> contextReference;\r
+\r
+    public GetCachedImages(Context c, int opt, ImageView iv) {\r
+        contextReference = new WeakReference<Context>(c);\r
+        mContext = contextReference.get();\r
+        choice = opt;\r
+        imageViewReference = new WeakReference<ImageView>(iv);\r
+        mImageView = imageViewReference.get();\r
+\r
+        // AQuery\r
+        aquery = new AQuery(mContext);\r
+    }\r
+\r
+    @Override\r
+    protected Bitmap doInBackground(String... args) {\r
+        if (choice == 0)\r
+            url = ApolloUtils.getImageURL(args[0], ARTIST_IMAGE_ORIGINAL, mContext);\r
+        if (choice == 1)\r
+            url = ApolloUtils.getImageURL(args[0], ALBUM_IMAGE, mContext);\r
+        bitmapReference = new WeakReference<Bitmap>(aquery.getCachedImage(url, 300));\r
+        return bitmapReference.get();\r
+    }\r
+\r
+    @Override\r
+    protected void onPostExecute(Bitmap result) {\r
+        if (imageViewReference != null && result != null) {\r
+            ApolloUtils.runnableBackground(mImageView, result);\r
+        } else {\r
+            result = aquery.getCachedImage(R.drawable.promo);\r
+            ApolloUtils.runnableBackground(mImageView, result);\r
+        }\r
+        super.onPostExecute(result);\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/tasks/LastfmGetAlbumImages.java b/src/com/andrew/apollo/tasks/LastfmGetAlbumImages.java
new file mode 100644 (file)
index 0000000..20ca3bf
--- /dev/null
@@ -0,0 +1,76 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.tasks;\r
+\r
+import java.lang.ref.WeakReference;\r
+\r
+import android.app.Activity;\r
+import android.content.Context;\r
+import android.os.AsyncTask;\r
+import android.widget.ImageView;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.lastfm.api.Album;\r
+import com.andrew.apollo.lastfm.api.ImageSize;\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+import com.androidquery.AQuery;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ * @returns A convenient image size that's perfect for a GridView.\r
+ */\r
+public class LastfmGetAlbumImages extends AsyncTask<String, Integer, String> implements Constants {\r
+\r
+    // URL to cache\r
+    private String url = null;\r
+\r
+    // AQuery\r
+    private final AQuery aq;\r
+\r
+    private final WeakReference<Context> contextReference;\r
+\r
+    private final WeakReference<ImageView> imageviewReference;\r
+\r
+    private final ImageView mImageView;\r
+\r
+    private final int choice;\r
+\r
+    private Album album;\r
+\r
+    public LastfmGetAlbumImages(Context context, ImageView iv, int opt) {\r
+        contextReference = new WeakReference<Context>(context);\r
+        imageviewReference = new WeakReference<ImageView>(iv);\r
+        mImageView = imageviewReference.get();\r
+        choice = opt;\r
+\r
+        // Initiate AQuery\r
+        aq = new AQuery((Activity)contextReference.get(), iv);\r
+    }\r
+\r
+    @Override\r
+    protected String doInBackground(String... name) {\r
+        if (ApolloUtils.isOnline(contextReference.get()) && name[0] != null && name[1] != null) {\r
+            try {\r
+                album = Album.getInfo(name[0], name[1], LASTFM_API_KEY);\r
+                url = album.getImageURL(ImageSize.LARGE);\r
+                aq.cache(url, 0);\r
+                ApolloUtils.setImageURL(name[1], url, ALBUM_IMAGE, contextReference.get());\r
+                return url;\r
+            } catch (Exception e) {\r
+                return null;\r
+            }\r
+        } else {\r
+            url = ApolloUtils.getImageURL(name[1], ALBUM_IMAGE, contextReference.get());\r
+        }\r
+        return url;\r
+    }\r
+\r
+    @Override\r
+    protected void onPostExecute(String result) {\r
+        if (result != null && mImageView != null && choice == 1)\r
+            new BitmapFromURL(mImageView).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, result);\r
+        super.onPostExecute(result);\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/tasks/LastfmGetArtistImages.java b/src/com/andrew/apollo/tasks/LastfmGetArtistImages.java
new file mode 100644 (file)
index 0000000..1cf3bf0
--- /dev/null
@@ -0,0 +1,65 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.tasks;\r
+\r
+import java.lang.ref.WeakReference;\r
+import java.util.Iterator;\r
+\r
+import android.content.Context;\r
+import android.os.AsyncTask;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.lastfm.api.Artist;\r
+import com.andrew.apollo.lastfm.api.Image;\r
+import com.andrew.apollo.lastfm.api.ImageSize;\r
+import com.andrew.apollo.lastfm.api.PaginatedResult;\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+import com.androidquery.AQuery;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ * @returns A convenient image size that's perfect for a GridView.\r
+ */\r
+public class LastfmGetArtistImages extends AsyncTask<String, Integer, String> implements Constants {\r
+\r
+    // URL to cache\r
+    private String url = null;\r
+\r
+    private PaginatedResult<Image> artist;\r
+\r
+    // AQuery\r
+    private final AQuery aq;\r
+\r
+    private final WeakReference<Context> contextReference;\r
+\r
+    public LastfmGetArtistImages(Context context) {\r
+        contextReference = new WeakReference<Context>(context);\r
+\r
+        // Initiate AQuery\r
+        aq = new AQuery(contextReference.get());\r
+    }\r
+\r
+    @Override\r
+    protected String doInBackground(String... artistname) {\r
+        if (ApolloUtils.isOnline(contextReference.get()) && artistname[0] != null) {\r
+            try {\r
+                artist = Artist.getImages(artistname[0], 1, 1, LASTFM_API_KEY);\r
+                Iterator<Image> iterator = artist.getPageResults().iterator();\r
+                while (iterator.hasNext()) {\r
+                    Image mTemp = iterator.next();\r
+                    url = mTemp.getImageURL(ImageSize.LARGESQUARE);\r
+                }\r
+                aq.cache(url, 0);\r
+                ApolloUtils.setImageURL(artistname[0], url, ARTIST_IMAGE, contextReference.get());\r
+                return url;\r
+            } catch (Exception e) {\r
+                return null;\r
+            }\r
+        } else {\r
+            url = ApolloUtils.getImageURL(artistname[0], ARTIST_IMAGE, contextReference.get());\r
+        }\r
+        return url;\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/tasks/LastfmGetArtistImagesOriginal.java b/src/com/andrew/apollo/tasks/LastfmGetArtistImagesOriginal.java
new file mode 100644 (file)
index 0000000..46f5eab
--- /dev/null
@@ -0,0 +1,80 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.tasks;\r
+\r
+import java.lang.ref.WeakReference;\r
+import java.util.Iterator;\r
+\r
+import android.content.Context;\r
+import android.os.AsyncTask;\r
+import android.widget.ImageView;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.lastfm.api.Artist;\r
+import com.andrew.apollo.lastfm.api.Image;\r
+import com.andrew.apollo.lastfm.api.ImageSize;\r
+import com.andrew.apollo.lastfm.api.PaginatedResult;\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+import com.androidquery.AQuery;\r
+\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ * @Note This is used to display artist images in @TracksBrowser\r
+ */\r
+public class LastfmGetArtistImagesOriginal extends AsyncTask<String, Integer, String> implements\r
+        Constants {\r
+\r
+    // URL to cache\r
+    private String url = null;\r
+\r
+    private final ImageView mImageView;\r
+\r
+    private final WeakReference<ImageView> imageviewReference;\r
+\r
+    // AQuery\r
+    private final AQuery aq;\r
+\r
+    // Context\r
+    private final Context mContext;\r
+\r
+    private final WeakReference<Context> contextReference;\r
+\r
+    public LastfmGetArtistImagesOriginal(Context context, ImageView iv) {\r
+        contextReference = new WeakReference<Context>(context);\r
+        mContext = contextReference.get();\r
+        imageviewReference = new WeakReference<ImageView>(iv);\r
+        mImageView = imageviewReference.get();\r
+\r
+        // Initiate AQuery\r
+        aq = new AQuery(mContext);\r
+    }\r
+\r
+    @Override\r
+    protected String doInBackground(String... artistname) {\r
+        if (ApolloUtils.isOnline(mContext)) {\r
+            PaginatedResult<Image> artist = Artist.getImages(artistname[0], 1, 1, LASTFM_API_KEY);\r
+            Iterator<Image> iterator = artist.getPageResults().iterator();\r
+            while (iterator.hasNext()) {\r
+                Image mTemp = iterator.next();\r
+                url = mTemp.getImageURL(ImageSize.ORIGINAL);\r
+            }\r
+            aq.cache(url, 0);\r
+            ApolloUtils.setImageURL(artistname[0], url, ARTIST_IMAGE_ORIGINAL, mContext);\r
+            return url;\r
+        } else {\r
+            url = ApolloUtils.getImageURL(artistname[0], ARTIST_IMAGE_ORIGINAL, mContext);\r
+        }\r
+        return url;\r
+    }\r
+\r
+    @Override\r
+    protected void onPostExecute(String result) {\r
+        if (result != null && mImageView != null) {\r
+            new BitmapFromURL(mImageView).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, result);\r
+        }\r
+        super.onPostExecute(result);\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/tasks/ViewHolderQueueTask.java b/src/com/andrew/apollo/tasks/ViewHolderQueueTask.java
new file mode 100644 (file)
index 0000000..5515409
--- /dev/null
@@ -0,0 +1,81 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.tasks;\r
+\r
+import java.lang.ref.WeakReference;\r
+\r
+import android.content.Context;\r
+import android.graphics.Bitmap;\r
+import android.os.AsyncTask;\r
+import android.widget.ImageView;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+import com.andrew.apollo.views.ViewHolderQueue;\r
+import com.androidquery.AQuery;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ */\r
+public class ViewHolderQueueTask extends AsyncTask<String, Integer, Bitmap> implements Constants {\r
+\r
+    private final ViewHolderQueue mViewHolderQueue;\r
+\r
+    private final WeakReference<ImageView> imageViewReference;\r
+\r
+    private final Context mContext;\r
+\r
+    private final int mPosition;\r
+\r
+    private final int choice;\r
+\r
+    private final int holderChoice;\r
+\r
+    private final AQuery aquery;\r
+\r
+    private final ImageView mImageView;\r
+\r
+    private String url;\r
+\r
+    private WeakReference<Bitmap> bitmapReference;\r
+\r
+    private final WeakReference<Context> contextReference;\r
+\r
+    public ViewHolderQueueTask(ViewHolderQueue vh, int position, Context c, int opt, int holderOpt,\r
+            ImageView iv) {\r
+        mViewHolderQueue = vh;\r
+        mPosition = position;\r
+        contextReference = new WeakReference<Context>(c);\r
+        mContext = contextReference.get();\r
+        choice = opt;\r
+        holderChoice = holderOpt;\r
+        imageViewReference = new WeakReference<ImageView>(iv);\r
+        mImageView = imageViewReference.get();\r
+\r
+        // AQuery\r
+        aquery = new AQuery(mContext);\r
+    }\r
+\r
+    @Override\r
+    protected Bitmap doInBackground(String... args) {\r
+        if (choice == 0)\r
+            url = ApolloUtils.getImageURL(args[0], ARTIST_IMAGE, mContext);\r
+        if (choice == 1)\r
+            url = ApolloUtils.getImageURL(args[0], ALBUM_IMAGE, mContext);\r
+        bitmapReference = new WeakReference<Bitmap>(aquery.getCachedImage(url));\r
+        return bitmapReference.get();\r
+    }\r
+\r
+    @Override\r
+    protected void onPostExecute(Bitmap result) {\r
+        if (imageViewReference != null && holderChoice == 0\r
+                && mViewHolderQueue.position == mPosition && mViewHolderQueue != null)\r
+            aquery.id(mImageView).image(result);\r
+        if (imageViewReference != null && holderChoice == 1\r
+                && mViewHolderQueue.position == mPosition && mViewHolderQueue != null)\r
+            aquery.id(mImageView).image(result);\r
+        super.onPostExecute(result);\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/tasks/ViewHolderTask.java b/src/com/andrew/apollo/tasks/ViewHolderTask.java
new file mode 100644 (file)
index 0000000..9c3eac6
--- /dev/null
@@ -0,0 +1,93 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.tasks;\r
+\r
+import java.lang.ref.WeakReference;\r
+\r
+import android.content.Context;\r
+import android.graphics.Bitmap;\r
+import android.os.AsyncTask;\r
+import android.widget.ImageView;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.utils.ApolloUtils;\r
+import com.andrew.apollo.views.ViewHolderGrid;\r
+import com.andrew.apollo.views.ViewHolderList;\r
+import com.androidquery.AQuery;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ */\r
+public class ViewHolderTask extends AsyncTask<String, Integer, Bitmap> implements Constants {\r
+\r
+    private final ViewHolderList mViewHolderList;\r
+\r
+    private final ViewHolderGrid mViewHolderGrid;\r
+\r
+    private final WeakReference<ImageView> imageViewReference;\r
+\r
+    private final Context mContext;\r
+\r
+    private final int mPosition;\r
+\r
+    private final int choice;\r
+\r
+    private final int holderChoice;\r
+\r
+    private final AQuery aquery;\r
+\r
+    private final ImageView mImageView;\r
+\r
+    private final int albumart;\r
+\r
+    private final WeakReference<Context> contextReference;\r
+\r
+    private String url;\r
+\r
+    private WeakReference<Bitmap> bitmapReference;\r
+\r
+    public ViewHolderTask(ViewHolderList vh, ViewHolderGrid vhg, int position, Context c, int opt,\r
+            int holderOpt, ImageView iv) {\r
+        mViewHolderList = vh;\r
+        mViewHolderGrid = vhg;\r
+        mPosition = position;\r
+        contextReference = new WeakReference<Context>(c);\r
+        mContext = contextReference.get();\r
+        choice = opt;\r
+        holderChoice = holderOpt;\r
+        imageViewReference = new WeakReference<ImageView>(iv);\r
+        mImageView = imageViewReference.get();\r
+        aquery = new AQuery(mContext);\r
+\r
+        albumart = mContext.getResources().getInteger(R.integer.listview_album_art);\r
+    }\r
+\r
+    @Override\r
+    protected Bitmap doInBackground(String... args) {\r
+        if (choice == 0)\r
+            url = ApolloUtils.getImageURL(args[0], ARTIST_IMAGE, mContext);\r
+        if (choice == 1)\r
+            url = ApolloUtils.getImageURL(args[0], ALBUM_IMAGE, mContext);\r
+        bitmapReference = new WeakReference<Bitmap>(aquery.getCachedImage(url));\r
+        if (holderChoice == 0) {\r
+            return ApolloUtils.getResizedBitmap(bitmapReference.get(), albumart, albumart);\r
+        } else if (holderChoice == 1) {\r
+            return bitmapReference.get();\r
+        }\r
+        return null;\r
+    }\r
+\r
+    @Override\r
+    protected void onPostExecute(Bitmap result) {\r
+        if (result != null && imageViewReference != null && holderChoice == 0\r
+                && mViewHolderList.position == mPosition && mViewHolderList != null)\r
+            mImageView.setImageBitmap(result);\r
+        if (result != null && imageViewReference != null && holderChoice == 1\r
+                && mViewHolderGrid.position == mPosition && mViewHolderGrid != null)\r
+            mImageView.setImageBitmap(result);\r
+        super.onPostExecute(result);\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/ui/widgets/BottomActionBar.java b/src/com/andrew/apollo/ui/widgets/BottomActionBar.java
new file mode 100644 (file)
index 0000000..7141456
--- /dev/null
@@ -0,0 +1,126 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.ui.widgets;\r
+\r
+import android.app.Activity;\r
+import android.content.Context;\r
+import android.content.Intent;\r
+import android.os.AsyncTask;\r
+import android.os.RemoteException;\r
+import android.util.AttributeSet;\r
+import android.view.View;\r
+import android.view.View.OnClickListener;\r
+import android.view.View.OnLongClickListener;\r
+import android.widget.ImageButton;\r
+import android.widget.ImageView;\r
+import android.widget.LinearLayout;\r
+import android.widget.TextView;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.activities.AudioPlayerHolder;\r
+import com.andrew.apollo.activities.QuickQueue;\r
+import com.andrew.apollo.tasks.GetCachedImages;\r
+import com.andrew.apollo.utils.MusicUtils;\r
+import com.andrew.apollo.utils.ThemeUtils;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ */\r
+public class BottomActionBar extends LinearLayout implements OnClickListener, OnLongClickListener,\r
+        Constants {\r
+\r
+    public BottomActionBar(Context context) {\r
+        super(context);\r
+    }\r
+\r
+    public BottomActionBar(Context context, AttributeSet attrs) {\r
+        super(context, attrs);\r
+        setOnClickListener(this);\r
+        setOnLongClickListener(this);\r
+    }\r
+\r
+    public BottomActionBar(Context context, AttributeSet attrs, int defStyle) {\r
+        super(context, attrs, defStyle);\r
+    }\r
+\r
+    /**\r
+     * Updates the bottom ActionBar's info\r
+     * \r
+     * @param activity\r
+     * @throws RemoteException\r
+     */\r
+    public void updateBottomActionBar(Activity activity) {\r
+        View bottomActionBar = activity.findViewById(R.id.bottom_action_bar);\r
+        if (bottomActionBar == null) {\r
+            return;\r
+        }\r
+\r
+        if (MusicUtils.mService != null && MusicUtils.getCurrentAudioId() != -1) {\r
+\r
+            // Track name\r
+            TextView mTrackName = (TextView)bottomActionBar\r
+                    .findViewById(R.id.bottom_action_bar_track_name);\r
+            mTrackName.setText(MusicUtils.getTrackName());\r
+\r
+            // Artist name\r
+            TextView mArtistName = (TextView)bottomActionBar\r
+                    .findViewById(R.id.bottom_action_bar_artist_name);\r
+            mArtistName.setText(MusicUtils.getArtistName());\r
+\r
+            // Album art\r
+            ImageView mAlbumArt = (ImageView)bottomActionBar\r
+                    .findViewById(R.id.bottom_action_bar_album_art);\r
+\r
+            new GetCachedImages(activity, 1, mAlbumArt).executeOnExecutor(\r
+                    AsyncTask.THREAD_POOL_EXECUTOR, MusicUtils.getAlbumName());\r
+\r
+            // Favorite image\r
+            ImageButton mFavorite = (ImageButton)bottomActionBar\r
+                    .findViewById(R.id.bottom_action_bar_item_one);\r
+\r
+            MusicUtils.setFavoriteImage(mFavorite);\r
+\r
+            // Divider\r
+            ImageView mDivider = (ImageView)activity\r
+                    .findViewById(R.id.bottom_action_bar_info_divider);\r
+\r
+            ImageButton mSearch = (ImageButton)bottomActionBar\r
+                    .findViewById(R.id.bottom_action_bar_item_two);\r
+\r
+            ImageButton mOverflow = (ImageButton)bottomActionBar\r
+                    .findViewById(R.id.bottom_action_bar_item_three);\r
+\r
+            // Theme chooser\r
+            ThemeUtils.setTextColor(activity, mTrackName, "bottom_action_bar_text_color");\r
+            ThemeUtils.setTextColor(activity, mArtistName, "bottom_action_bar_text_color");\r
+            ThemeUtils.setBackgroundColor(activity, mDivider, "bottom_action_bar_info_divider");\r
+            ThemeUtils.setImageButton(activity, mSearch, "apollo_search");\r
+            ThemeUtils.setImageButton(activity, mOverflow, "apollo_overflow");\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public void onClick(View v) {\r
+        switch (v.getId()) {\r
+            case R.id.bottom_action_bar:\r
+                Intent intent = new Intent();\r
+                intent.setClass(v.getContext(), AudioPlayerHolder.class);\r
+                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);\r
+                v.getContext().startActivity(intent);\r
+                break;\r
+            default:\r
+                break;\r
+        }\r
+\r
+    }\r
+\r
+    @Override\r
+    public boolean onLongClick(View v) {\r
+        Context context = v.getContext();\r
+        context.startActivity(new Intent(context, QuickQueue.class));\r
+        return true;\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/ui/widgets/BottomActionBarItem.java b/src/com/andrew/apollo/ui/widgets/BottomActionBarItem.java
new file mode 100644 (file)
index 0000000..dd02758
--- /dev/null
@@ -0,0 +1,154 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.ui.widgets;\r
+\r
+import android.app.Activity;\r
+import android.content.Context;\r
+import android.content.Intent;\r
+import android.database.Cursor;\r
+import android.media.audiofx.AudioEffect;\r
+import android.net.Uri;\r
+import android.provider.BaseColumns;\r
+import android.provider.MediaStore.Audio;\r
+import android.provider.MediaStore.Audio.AudioColumns;\r
+import android.util.AttributeSet;\r
+import android.view.MenuItem;\r
+import android.view.View;\r
+import android.view.View.OnClickListener;\r
+import android.view.View.OnLongClickListener;\r
+import android.widget.ImageButton;\r
+import android.widget.PopupMenu;\r
+import android.widget.PopupMenu.OnMenuItemClickListener;\r
+import android.widget.Toast;\r
+\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.preferences.SettingsHolder;\r
+import com.andrew.apollo.tasks.FetchAlbumImages;\r
+import com.andrew.apollo.tasks.FetchArtistImages;\r
+import com.andrew.apollo.utils.MusicUtils;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ */\r
+public class BottomActionBarItem extends ImageButton implements OnLongClickListener,\r
+        OnClickListener, OnMenuItemClickListener {\r
+\r
+    private final Context mContext;\r
+\r
+    private static final int EFFECTS_PANEL = 0;\r
+\r
+    public BottomActionBarItem(Context context) {\r
+        super(context);\r
+        mContext = context;\r
+    }\r
+\r
+    public BottomActionBarItem(Context context, AttributeSet attrs) {\r
+        super(context, attrs);\r
+        setOnLongClickListener(this);\r
+        setOnClickListener(this);\r
+        mContext = context;\r
+    }\r
+\r
+    public BottomActionBarItem(Context context, AttributeSet attrs, int defStyle) {\r
+        super(context, attrs, defStyle);\r
+        mContext = context;\r
+    }\r
+\r
+    @Override\r
+    public boolean onLongClick(View v) {\r
+        Toast.makeText(getContext(), v.getContentDescription(), Toast.LENGTH_SHORT).show();\r
+        return true;\r
+    }\r
+\r
+    @Override\r
+    public void onClick(View v) {\r
+        switch (v.getId()) {\r
+            case R.id.bottom_action_bar_item_one:\r
+                MusicUtils.toggleFavorite();\r
+                MusicUtils.setFavoriteImage(this);\r
+                break;\r
+            case R.id.bottom_action_bar_item_two:\r
+                ((Activity)mContext).onSearchRequested();\r
+                break;\r
+            case R.id.bottom_action_bar_item_three:\r
+                showPopup(v);\r
+                break;\r
+            default:\r
+                break;\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @param v\r
+     */\r
+    private void showPopup(View v) {\r
+        PopupMenu popup = new PopupMenu(getContext(), v);\r
+        popup.setOnMenuItemClickListener(this);\r
+        popup.inflate(R.menu.overflow_library);\r
+        popup.show();\r
+    }\r
+\r
+    @Override\r
+    public boolean onMenuItemClick(MenuItem item) {\r
+        switch (item.getItemId()) {\r
+            case R.id.settings:\r
+                mContext.startActivity(new Intent(mContext, SettingsHolder.class));\r
+                break;\r
+            case R.id.equalizer:\r
+                Intent i = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);\r
+                i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, MusicUtils.getCurrentAudioId());\r
+                ((Activity)mContext).startActivityForResult(i, EFFECTS_PANEL);\r
+                break;\r
+            case R.id.shuffle_all:\r
+                // TODO Only shuffle the tracks that are shown\r
+                shuffleAll();\r
+                break;\r
+            // case R.id.fetch_artwork:\r
+            // initAlbumImages();\r
+            // break;\r
+            // case R.id.fetch_artist_images:\r
+            // initArtistImages();\r
+            // break;\r
+            default:\r
+                break;\r
+        }\r
+        return false;\r
+    }\r
+\r
+    /**\r
+     * Manually re-fetch artist imgages. Maybe the user wants to update them or\r
+     * something went wrong the first time around.\r
+     */\r
+    public void initArtistImages() {\r
+        FetchArtistImages getArtistImages = new FetchArtistImages(mContext, 1);\r
+        getArtistImages.runTask();\r
+    }\r
+\r
+    /**\r
+     * Manually fetch all of the album art.\r
+     */\r
+    public void initAlbumImages() {\r
+        FetchAlbumImages getAlbumImages = new FetchAlbumImages(mContext, 1);\r
+        getAlbumImages.runTask();\r
+    }\r
+\r
+    /**\r
+     * Shuffle all the tracks\r
+     */\r
+    public void shuffleAll() {\r
+        Uri uri = Audio.Media.EXTERNAL_CONTENT_URI;\r
+        String[] projection = new String[] {\r
+            BaseColumns._ID\r
+        };\r
+        String selection = AudioColumns.IS_MUSIC + "=1";\r
+        String sortOrder = Audio.Media.DEFAULT_SORT_ORDER;\r
+        Cursor cursor = MusicUtils.query(mContext, uri, projection, selection, null, sortOrder);\r
+        if (cursor != null) {\r
+            MusicUtils.shuffleAll(mContext, cursor);\r
+            cursor.close();\r
+            cursor = null;\r
+        }\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/ui/widgets/RepeatingImageButton.java b/src/com/andrew/apollo/ui/widgets/RepeatingImageButton.java
new file mode 100644 (file)
index 0000000..1c73614
--- /dev/null
@@ -0,0 +1,137 @@
+/*\r
+ * Copyright (C) 2008 The Android Open Source Project\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.andrew.apollo.ui.widgets;\r
+\r
+import android.content.Context;\r
+import android.os.SystemClock;\r
+import android.util.AttributeSet;\r
+import android.view.KeyEvent;\r
+import android.view.MotionEvent;\r
+import android.view.View;\r
+import android.widget.ImageButton;\r
+\r
+/**\r
+ * A button that will repeatedly call a 'listener' method as long as the button\r
+ * is pressed.\r
+ */\r
+public class RepeatingImageButton extends ImageButton {\r
+\r
+    private long mStartTime;\r
+\r
+    private int mRepeatCount;\r
+\r
+    private RepeatListener mListener;\r
+\r
+    private long mInterval = 500;\r
+\r
+    public RepeatingImageButton(Context context) {\r
+        this(context, null);\r
+    }\r
+\r
+    public RepeatingImageButton(Context context, AttributeSet attrs) {\r
+        this(context, attrs, android.R.attr.imageButtonStyle);\r
+    }\r
+\r
+    public RepeatingImageButton(Context context, AttributeSet attrs, int defStyle) {\r
+        super(context, attrs, defStyle);\r
+        setFocusable(true);\r
+        setLongClickable(true);\r
+    }\r
+\r
+    /**\r
+     * Sets the listener to be called while the button is pressed and the\r
+     * interval in milliseconds with which it will be called.\r
+     * \r
+     * @param l The listener that will be called\r
+     * @param interval The interval in milliseconds for calls\r
+     */\r
+    public void setRepeatListener(RepeatListener l, long interval) {\r
+        mListener = l;\r
+        mInterval = interval;\r
+    }\r
+\r
+    @Override\r
+    public boolean performLongClick() {\r
+        mStartTime = SystemClock.elapsedRealtime();\r
+        mRepeatCount = 0;\r
+        post(mRepeater);\r
+        return true;\r
+    }\r
+\r
+    @Override\r
+    public boolean onTouchEvent(MotionEvent event) {\r
+        if (event.getAction() == MotionEvent.ACTION_UP) {\r
+            // remove the repeater, but call the hook one more time\r
+            removeCallbacks(mRepeater);\r
+            if (mStartTime != 0) {\r
+                doRepeat(true);\r
+                mStartTime = 0;\r
+            }\r
+        }\r
+        return super.onTouchEvent(event);\r
+    }\r
+\r
+    @Override\r
+    public boolean onKeyDown(int keyCode, KeyEvent event) {\r
+        switch (keyCode) {\r
+            case KeyEvent.KEYCODE_DPAD_CENTER:\r
+            case KeyEvent.KEYCODE_ENTER:\r
+                // need to call super to make long press work, but return\r
+                // true so that the application doesn't get the down event.\r
+                super.onKeyDown(keyCode, event);\r
+                return true;\r
+        }\r
+        return super.onKeyDown(keyCode, event);\r
+    }\r
+\r
+    @Override\r
+    public boolean onKeyUp(int keyCode, KeyEvent event) {\r
+        switch (keyCode) {\r
+            case KeyEvent.KEYCODE_DPAD_CENTER:\r
+            case KeyEvent.KEYCODE_ENTER:\r
+                // remove the repeater, but call the hook one more time\r
+                removeCallbacks(mRepeater);\r
+                if (mStartTime != 0) {\r
+                    doRepeat(true);\r
+                    mStartTime = 0;\r
+                }\r
+        }\r
+        return super.onKeyUp(keyCode, event);\r
+    }\r
+\r
+    private final Runnable mRepeater = new Runnable() {\r
+        @Override\r
+        public void run() {\r
+            doRepeat(false);\r
+            if (isPressed()) {\r
+                postDelayed(this, mInterval);\r
+            }\r
+        }\r
+    };\r
+\r
+    private void doRepeat(boolean last) {\r
+        long now = SystemClock.elapsedRealtime();\r
+        if (mListener != null) {\r
+            mListener.onRepeat(this, now - mStartTime, last ? -1 : mRepeatCount++);\r
+        }\r
+    }\r
+\r
+    public interface RepeatListener {\r
+\r
+        void onRepeat(View v, long duration, int repeatcount);\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/ui/widgets/ScrollableTabView.java b/src/com/andrew/apollo/ui/widgets/ScrollableTabView.java
new file mode 100644 (file)
index 0000000..3b9142c
--- /dev/null
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2011 Andreas Stuetz <andreas.stuetz@gmail.com>
+ *
+ * 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.andrew.apollo.ui.widgets;
+
+import java.util.ArrayList;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.v4.view.ViewPager;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.HorizontalScrollView;
+import android.widget.LinearLayout;
+
+import com.andrew.apollo.adapters.TabAdapter;
+
+/**
+ * I'm using a custom tab view in place of ActionBarTabs entirely for the theme
+ * engine.
+ */
+public class ScrollableTabView extends HorizontalScrollView implements
+        ViewPager.OnPageChangeListener {
+
+    private final Context mContext;
+
+    private ViewPager mPager;
+
+    private TabAdapter mAdapter;
+
+    private final LinearLayout mContainer;
+
+    private final ArrayList<View> mTabs = new ArrayList<View>();
+
+    private Drawable mDividerDrawable;
+
+    private final int mDividerColor = 0xFF636363;
+
+    private int mDividerMarginTop = 12;
+
+    private int mDividerMarginBottom = 12;
+
+    private int mDividerWidth = 1;
+
+    public ScrollableTabView(Context context) {
+        this(context, null);
+    }
+
+    public ScrollableTabView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ScrollableTabView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs);
+
+        this.mContext = context;
+
+        mDividerMarginTop = (int)(getResources().getDisplayMetrics().density * mDividerMarginTop);
+        mDividerMarginBottom = (int)(getResources().getDisplayMetrics().density * mDividerMarginBottom);
+        mDividerWidth = (int)(getResources().getDisplayMetrics().density * mDividerWidth);
+
+        this.setHorizontalScrollBarEnabled(false);
+        this.setHorizontalFadingEdgeEnabled(false);
+
+        mContainer = new LinearLayout(context);
+        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
+                android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+                android.view.ViewGroup.LayoutParams.MATCH_PARENT);
+        mContainer.setLayoutParams(params);
+        mContainer.setOrientation(LinearLayout.HORIZONTAL);
+
+        this.addView(mContainer);
+
+    }
+
+    public void setAdapter(TabAdapter adapter) {
+        this.mAdapter = adapter;
+
+        if (mPager != null && mAdapter != null)
+            initTabs();
+    }
+
+    public void setViewPager(ViewPager pager) {
+        this.mPager = pager;
+        mPager.setOnPageChangeListener(this);
+
+        if (mPager != null && mAdapter != null)
+            initTabs();
+    }
+
+    private void initTabs() {
+
+        mContainer.removeAllViews();
+        mTabs.clear();
+
+        if (mAdapter == null)
+            return;
+
+        for (int i = 0; i < mPager.getAdapter().getCount(); i++) {
+
+            final int index = i;
+
+            View tab = mAdapter.getView(i);
+            mContainer.addView(tab);
+
+            tab.setFocusable(true);
+
+            mTabs.add(tab);
+
+            if (i != mPager.getAdapter().getCount() - 1) {
+                mContainer.addView(getSeparator());
+            }
+
+            tab.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    if (mPager.getCurrentItem() == index) {
+                        selectTab(index);
+                    } else {
+                        mPager.setCurrentItem(index, true);
+                    }
+                }
+            });
+
+        }
+
+        selectTab(mPager.getCurrentItem());
+    }
+
+    @Override
+    public void onPageScrollStateChanged(int state) {
+    }
+
+    @Override
+    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+    }
+
+    @Override
+    public void onPageSelected(int position) {
+        selectTab(position);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+
+        if (changed)
+            selectTab(mPager.getCurrentItem());
+    }
+
+    private View getSeparator() {
+        View v = new View(mContext);
+
+        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mDividerWidth,
+                android.view.ViewGroup.LayoutParams.MATCH_PARENT);
+        params.setMargins(0, mDividerMarginTop, 0, mDividerMarginBottom);
+        v.setLayoutParams(params);
+
+        if (mDividerDrawable != null)
+            v.setBackgroundDrawable(mDividerDrawable);
+        else
+            v.setBackgroundColor(mDividerColor);
+
+        return v;
+    }
+
+    private void selectTab(int position) {
+
+        for (int i = 0, pos = 0; i < mContainer.getChildCount(); i += 2, pos++) {
+            View tab = mContainer.getChildAt(i);
+            tab.setSelected(pos == position);
+        }
+
+        View selectedTab = mContainer.getChildAt(position * 2);
+
+        final int w = selectedTab.getMeasuredWidth();
+        final int l = selectedTab.getLeft();
+
+        final int x = l - this.getWidth() / 2 + w / 2;
+
+        smoothScrollTo(x, this.getScrollY());
+
+    }
+
+}
diff --git a/src/com/andrew/apollo/utils/ApolloUtils.java b/src/com/andrew/apollo/utils/ApolloUtils.java
new file mode 100644 (file)
index 0000000..9704343
--- /dev/null
@@ -0,0 +1,327 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.utils;\r
+\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.net.HttpURLConnection;\r
+import java.net.URL;\r
+\r
+import android.app.ActionBar;\r
+import android.content.Context;\r
+import android.content.Intent;\r
+import android.content.SharedPreferences;\r
+import android.content.res.Configuration;\r
+import android.graphics.Bitmap;\r
+import android.graphics.BitmapFactory;\r
+import android.graphics.Canvas;\r
+import android.graphics.Matrix;\r
+import android.graphics.drawable.AnimationDrawable;\r
+import android.graphics.drawable.BitmapDrawable;\r
+import android.graphics.drawable.Drawable;\r
+import android.net.ConnectivityManager;\r
+import android.net.NetworkInfo;\r
+import android.net.Uri;\r
+import android.provider.MediaStore.Audio;\r
+import android.support.v4.app.Fragment;\r
+import android.view.View;\r
+import android.widget.ImageView;\r
+import android.widget.ListView;\r
+import android.widget.TextView;\r
+import android.widget.Toast;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.R;\r
+import com.androidquery.util.AQUtility;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ * @Note Various methods used to help with specific Apollo statements\r
+ */\r
+public class ApolloUtils implements Constants {\r
+\r
+    /**\r
+     * Used to fit a Bitmap nicely inside a View\r
+     * \r
+     * @param view\r
+     * @param bitmap\r
+     */\r
+    public static void setBackground(View view, Bitmap bitmap) {\r
+\r
+        if (bitmap == null) {\r
+            view.setBackgroundResource(0);\r
+            return;\r
+        }\r
+\r
+        int vwidth = view.getWidth();\r
+        int vheight = view.getHeight();\r
+        int bwidth = bitmap.getWidth();\r
+        int bheight = bitmap.getHeight();\r
+\r
+        float scalex = (float)vwidth / bwidth;\r
+        float scaley = (float)vheight / bheight;\r
+        float scale = Math.max(scalex, scaley) * 1.0f;\r
+\r
+        Bitmap.Config config = Bitmap.Config.ARGB_8888;\r
+        Bitmap background = Bitmap.createBitmap(vwidth, vheight, config);\r
+\r
+        Canvas canvas = new Canvas(background);\r
+\r
+        Matrix matrix = new Matrix();\r
+        matrix.setTranslate(-bwidth / 2, -bheight / 2);\r
+        matrix.postScale(scale, scale);\r
+        matrix.postTranslate(vwidth / 2, vheight / 2);\r
+\r
+        canvas.drawBitmap(bitmap, matrix, null);\r
+\r
+        view.setBackgroundDrawable(new BitmapDrawable(view.getResources(), background));\r
+    }\r
+\r
+    /**\r
+     * @param view\r
+     * @param bitmap This is to avoid Bitmap's IllegalArgumentException\r
+     */\r
+    public static void runnableBackground(final ImageView view, final Bitmap bitmap) {\r
+        view.post(new Runnable() {\r
+\r
+            @Override\r
+            public void run() {\r
+                ApolloUtils.setBackground(view, bitmap);\r
+            }\r
+        });\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @return whether there is an active data connection\r
+     */\r
+    public static boolean isOnline(Context context) {\r
+        boolean state = false;\r
+        ConnectivityManager cm = (ConnectivityManager)context\r
+                .getSystemService(Context.CONNECTIVITY_SERVICE);\r
+\r
+        NetworkInfo wifiNetwork = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI);\r
+        if (wifiNetwork != null) {\r
+            state = wifiNetwork.isConnectedOrConnecting();\r
+        }\r
+\r
+        NetworkInfo mobileNetwork = cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);\r
+        if (mobileNetwork != null) {\r
+            state = mobileNetwork.isConnectedOrConnecting();\r
+        }\r
+\r
+        NetworkInfo activeNetwork = cm.getActiveNetworkInfo();\r
+        if (activeNetwork != null) {\r
+            state = activeNetwork.isConnectedOrConnecting();\r
+        }\r
+        return state;\r
+    }\r
+\r
+    /**\r
+     * Sets cached image URLs\r
+     * \r
+     * @param artistName\r
+     * @param url\r
+     * @param key\r
+     * @param context\r
+     */\r
+    public static void setImageURL(String name, String url, String key, Context context) {\r
+        SharedPreferences settings = context.getSharedPreferences(key, 0);\r
+        SharedPreferences.Editor editor = settings.edit();\r
+        editor.putString(name, url);\r
+        editor.commit();\r
+    }\r
+\r
+    /**\r
+     * @param name\r
+     * @param key\r
+     * @param context\r
+     * @return cached image URLs\r
+     */\r
+    public static String getImageURL(String name, String key, Context context) {\r
+        SharedPreferences settings = context.getSharedPreferences(key, 0);\r
+        return settings.getString(name, null);\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @return if a Tablet is the device being used\r
+     */\r
+    public static boolean isTablet(Context context) {\r
+        return (context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE;\r
+    }\r
+\r
+    /**\r
+     * UP accordance without the icon\r
+     * \r
+     * @param actionBar\r
+     */\r
+    public static void showUpTitleOnly(ActionBar actionBar) {\r
+        actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_TITLE,\r
+                ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_TITLE\r
+                        | ActionBar.DISPLAY_SHOW_HOME);\r
+    }\r
+\r
+    /**\r
+     * @param bitmap\r
+     * @param newHeight\r
+     * @param newWidth\r
+     * @return a scaled Bitmap\r
+     */\r
+    public static Bitmap getResizedBitmap(Bitmap bitmap, int newHeight, int newWidth) {\r
+\r
+        if (bitmap == null) {\r
+            return null;\r
+        }\r
+\r
+        int width = bitmap.getWidth();\r
+        int height = bitmap.getHeight();\r
+        float scaleWidth = ((float)newWidth) / width;\r
+        float scaleHeight = ((float)newHeight) / height;\r
+        Matrix matrix = new Matrix();\r
+        matrix.postScale(scaleWidth, scaleHeight);\r
+        Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false);\r
+        return resizedBitmap;\r
+    }\r
+\r
+    /**\r
+     * Header used in the track browser\r
+     * \r
+     * @param fragment\r
+     * @param view\r
+     * @param string\r
+     */\r
+    public static void listHeader(Fragment fragment, View view, String string) {\r
+        if (fragment.getArguments() != null) {\r
+            TextView mHeader = (TextView)view.findViewById(R.id.title);\r
+            String mimetype = fragment.getArguments().getString(MIME_TYPE);\r
+            if (Audio.Artists.CONTENT_TYPE.equals(mimetype)) {\r
+                mHeader.setVisibility(View.VISIBLE);\r
+                mHeader.setText(string);\r
+            } else if (Audio.Albums.CONTENT_TYPE.equals(mimetype)) {\r
+                mHeader.setVisibility(View.VISIBLE);\r
+                mHeader.setText(string);\r
+            }\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Sets the ListView paddingLeft for the header\r
+     * \r
+     * @param fragment\r
+     * @param mListView\r
+     */\r
+    public static void setListPadding(Fragment fragment, ListView mListView, int left, int top,\r
+            int right, int bottom) {\r
+        if (fragment.getArguments() != null) {\r
+            String mimetype = fragment.getArguments().getString(MIME_TYPE);\r
+            if (Audio.Albums.CONTENT_TYPE.equals(mimetype)) {\r
+                mListView.setPadding(AQUtility.dip2pixel(fragment.getActivity(), left), top,\r
+                        AQUtility.dip2pixel(fragment.getActivity(), right), bottom);\r
+            } else if (Audio.Artists.CONTENT_TYPE.equals(mimetype)) {\r
+                mListView.setPadding(AQUtility.dip2pixel(fragment.getActivity(), left), top,\r
+                        AQUtility.dip2pixel(fragment.getActivity(), right), bottom);\r
+            }\r
+        }\r
+    }\r
+\r
+    // Returns if we're viewing an album\r
+    public static boolean isAlbum(String mimeType) {\r
+        return Audio.Albums.CONTENT_TYPE.equals(mimeType);\r
+    }\r
+\r
+    // Returns if we're viewing an artists albums\r
+    public static boolean isArtist(String mimeType) {\r
+        return Audio.Artists.CONTENT_TYPE.equals(mimeType);\r
+    }\r
+\r
+    // Returns if we're viewing a genre\r
+    public static boolean isGenre(String mimeType) {\r
+        return Audio.Genres.CONTENT_TYPE.equals(mimeType);\r
+    }\r
+\r
+    /**\r
+     * @param artistName\r
+     * @param id\r
+     * @param key\r
+     * @param context\r
+     */\r
+    public static void setArtistId(String artistName, long id, String key, Context context) {\r
+        SharedPreferences settings = context.getSharedPreferences(key, 0);\r
+        SharedPreferences.Editor editor = settings.edit();\r
+        editor.putLong(artistName, id);\r
+        editor.commit();\r
+    }\r
+\r
+    /**\r
+     * @param artistName\r
+     * @param key\r
+     * @param context\r
+     * @return artist ID\r
+     */\r
+    public static Long getArtistId(String artistName, String key, Context context) {\r
+        SharedPreferences settings = context.getSharedPreferences(key, 0);\r
+        return settings.getLong(artistName, 0);\r
+    }\r
+\r
+    /**\r
+     * @param artistName\r
+     */\r
+    public static void shopFor(Context mContext, String artistName) {\r
+        String str = "https://market.android.com/search?q=%s&c=music&featured=MUSIC_STORE_SEARCH";\r
+        Intent shopIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(String.format(str,\r
+                Uri.encode(artistName))));\r
+        shopIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\r
+        shopIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);\r
+        mContext.startActivity(shopIntent);\r
+    }\r
+\r
+    /**\r
+     * @param src\r
+     * @return Bitmap fro URL\r
+     */\r
+    public static Bitmap getBitmapFromURL(String src) {\r
+        try {\r
+            URL url = new URL(src);\r
+            HttpURLConnection connection = (HttpURLConnection)url.openConnection();\r
+            connection.setDoInput(true);\r
+            connection.connect();\r
+            InputStream input = connection.getInputStream();\r
+            Bitmap myBitmap = BitmapFactory.decodeStream(input);\r
+            return myBitmap;\r
+        } catch (IOException e) {\r
+            e.printStackTrace();\r
+            return null;\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @param message\r
+     */\r
+    public static void showToast(int message, Toast mToast, Context context) {\r
+        if (mToast == null) {\r
+            mToast = Toast.makeText(context, "", Toast.LENGTH_SHORT);\r
+        }\r
+        mToast.setText(context.getString(message));\r
+        mToast.show();\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @return meow\r
+     */\r
+    public static AnimationDrawable getNyanCat(Context context) {\r
+        final AnimationDrawable animation = new AnimationDrawable();\r
+        for (int i = 0; i < 12; i++) {\r
+            try {\r
+                animation.addFrame(Drawable.createFromStream(\r
+                        context.getAssets().open("Frame" + i + ".png"), null), 75);\r
+            } catch (IOException e) {\r
+            }\r
+        }\r
+        animation.setOneShot(false);\r
+        return animation;\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/utils/DomElement.java b/src/com/andrew/apollo/utils/DomElement.java
new file mode 100644 (file)
index 0000000..611faf0
--- /dev/null
@@ -0,0 +1,167 @@
+/*\r
+ * Copyright (c) 2012, the Last.fm Java Project and Committers\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use of this software in source and binary forms, with or without modification, are\r
+ * permitted provided that the following conditions are met:\r
+ *\r
+ * - Redistributions of source code must retain the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer.\r
+ *\r
+ * - Redistributions in binary form must reproduce the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer in the documentation and/or other\r
+ *   materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED\r
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\r
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\r
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR\r
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+package com.andrew.apollo.utils;\r
+\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+import org.w3c.dom.Element;\r
+import org.w3c.dom.Node;\r
+import org.w3c.dom.NodeList;\r
+\r
+/**\r
+ * <code>DomElement</code> wraps around an {@link Element} and provides convenience methods.\r
+ *\r
+ * @author Janni Kovacs\r
+ */\r
+public class DomElement {\r
+       private Element e;\r
+\r
+       /**\r
+        * Creates a new wrapper around the given {@link Element}.\r
+        *\r
+        * @param elem An w3c Element\r
+        */\r
+       public DomElement(Element elem) {\r
+               this.e = elem;\r
+       }\r
+\r
+       /**\r
+        * @return the original Element\r
+        */\r
+       public Element getElement() {\r
+               return e;\r
+       }\r
+\r
+       /**\r
+        * Tests if this element has an attribute with the specified name.\r
+        *\r
+        * @param name Name of the attribute.\r
+        * @return <code>true</code> if this element has an attribute with the specified name.\r
+        */\r
+       public boolean hasAttribute(String name) {\r
+               return e.hasAttribute(name);\r
+       }\r
+       \r
+       /**\r
+        * Returns the attribute value to a given attribute name or <code>null</code> if the attribute doesn't exist.\r
+        *\r
+        * @param name The attribute's name\r
+        * @return Attribute value or <code>null</code>\r
+        */\r
+       public String getAttribute(String name) {\r
+               return e.hasAttribute(name) ? e.getAttribute(name) : null;\r
+       }\r
+\r
+       /**\r
+        * @return the text content of the element\r
+        */\r
+       public String getText() {\r
+               // XXX e.getTextContent() doesn't exsist under Android (Lukasz Wisniewski)\r
+               /// getTextContent() is now available in at least Android 2.2 if not earlier, so we'll keep using that\r
+               // return e.hasChildNodes() ? e.getFirstChild().getNodeValue() : null;\r
+               return e.getTextContent();\r
+       }\r
+\r
+       /**\r
+        * Checks if this element has a child element with the given name.\r
+        *\r
+        * @param name The child's name\r
+        * @return <code>true</code> if this element has a child element with the given name\r
+        */\r
+       public boolean hasChild(String name) {\r
+               NodeList list = e.getElementsByTagName(name);\r
+               for (int i = 0, j = list.getLength(); i < j; i++) {\r
+                       Node item = list.item(i);\r
+                       if (item.getParentNode() == e)\r
+                               return true;\r
+               }\r
+               return false;\r
+       }\r
+\r
+       /**\r
+        * Returns the child element with the given name or <code>null</code> if it doesn't exist.\r
+        *\r
+        * @param name The child's name\r
+        * @return the child element or <code>null</code>\r
+        */\r
+       public DomElement getChild(String name) {\r
+               NodeList list = e.getElementsByTagName(name);\r
+               if (list.getLength() == 0)\r
+                       return null;\r
+               for (int i = 0, j = list.getLength(); i < j; i++) {\r
+                       Node item = list.item(i);\r
+                       if (item.getParentNode() == e)\r
+                               return new DomElement((Element) item);\r
+               }\r
+               return null;\r
+       }\r
+\r
+       /**\r
+        * Returns the text content of a child node with the given name. If no such child exists or the child\r
+        * does not have text content, <code>null</code> is returned.\r
+        *\r
+        * @param name The child's name\r
+        * @return the child's text content or <code>null</code>\r
+        */\r
+       public String getChildText(String name) {\r
+               DomElement child = getChild(name);\r
+               return child != null ? child.getText() : null;\r
+       }\r
+\r
+       /**\r
+        * @return all children of this element\r
+        */\r
+       public List<DomElement> getChildren() {\r
+               return getChildren("*");\r
+       }\r
+\r
+       /**\r
+        * Returns all children of this element with the given tag name.\r
+        *\r
+        * @param name The children's tag name\r
+        * @return all matching children\r
+        */\r
+       public List<DomElement> getChildren(String name) {\r
+               List<DomElement> l = new ArrayList<DomElement>();\r
+               NodeList list = e.getElementsByTagName(name);\r
+               for (int i = 0; i < list.getLength(); i++) {\r
+                       Node node = list.item(i);\r
+                       if (node.getParentNode() == e)\r
+                               l.add(new DomElement((Element) node));\r
+               }\r
+               return l;\r
+       }\r
+\r
+       /**\r
+        * Returns this element's tag name.\r
+        *\r
+        * @return the tag name\r
+        */\r
+       public String getTagName() {\r
+               return e.getTagName();\r
+       }\r
+}\r
diff --git a/src/com/andrew/apollo/utils/MapUtilities.java b/src/com/andrew/apollo/utils/MapUtilities.java
new file mode 100644 (file)
index 0000000..1a25077
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2012, the Last.fm Java Project and Committers
+ * All rights reserved.
+ *
+ * Redistribution and use of this software in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above
+ *   copyright notice, this list of conditions and the
+ *   following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the
+ *   following disclaimer in the documentation and/or other
+ *   materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.andrew.apollo.utils;
+
+import java.util.Map;
+
+/**
+ * Utility class to perform various operations on Maps.
+ *
+ * @author Adrian Woodhead
+ */
+public final class MapUtilities {
+
+       private MapUtilities() {
+       }
+
+       /**
+        * Puts the passed key and value into the map only if the value is not null.
+        *
+        * @param map Map to add key and value to.
+        * @param key Map key.
+        * @param value Map value, if null will not be added to map.
+        */
+       public static void nullSafePut(Map<String, String> map, String key, String value) {
+               if (value != null) {
+                       map.put(key, value);
+               }
+       }
+
+       /**
+        * Puts the passed key and value into the map only if the value is not null.
+        *
+        * @param map Map to add key and value to.
+        * @param key Map key.
+        * @param value Map value, if null will not be added to map.
+        */
+       public static void nullSafePut(Map<String, String> map, String key, Integer value) {
+               if (value != null) {
+                       map.put(key, value.toString());
+               }
+       }
+
+       /**
+        * Puts the passed key and value into the map only if the value is not -1.
+        *
+        * @param map Map to add key and value to.
+        * @param key Map key.
+        * @param value Map value, if -1 will not be added to map.
+        */
+       public static void nullSafePut(Map<String, String> map, String key, int value) {
+               if (value != -1) {
+                       map.put(key, Integer.toString(value));
+               }
+       }
+
+       /**
+        * Puts the passed key and value into the map only if the value is not -1.
+        *
+        * @param map Map to add key and value to.
+        * @param key Map key.
+        * @param value Map value, if -1 will not be added to map.
+        */
+       public static void nullSafePut(Map<String, String> map, String key, double value) {
+               if (value != -1) {
+                       map.put(key, Double.toString(value));
+               }
+       }
+}
diff --git a/src/com/andrew/apollo/utils/MusicUtils.java b/src/com/andrew/apollo/utils/MusicUtils.java
new file mode 100644 (file)
index 0000000..c7df4d8
--- /dev/null
@@ -0,0 +1,1222 @@
+\r
+package com.andrew.apollo.utils;\r
+\r
+import java.util.Arrays;\r
+import java.util.Formatter;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Locale;\r
+import java.util.Map;\r
+\r
+import android.app.Activity;\r
+import android.app.SearchManager;\r
+import android.content.ContentResolver;\r
+import android.content.ContentUris;\r
+import android.content.ContentValues;\r
+import android.content.Context;\r
+import android.content.ContextWrapper;\r
+import android.content.Intent;\r
+import android.content.ServiceConnection;\r
+import android.content.SharedPreferences;\r
+import android.content.res.Resources;\r
+import android.database.Cursor;\r
+import android.net.Uri;\r
+import android.os.Environment;\r
+import android.os.RemoteException;\r
+import android.provider.BaseColumns;\r
+import android.provider.MediaStore;\r
+import android.provider.MediaStore.Audio;\r
+import android.provider.MediaStore.Audio.AlbumColumns;\r
+import android.provider.MediaStore.Audio.ArtistColumns;\r
+import android.provider.MediaStore.Audio.AudioColumns;\r
+import android.provider.MediaStore.Audio.Genres;\r
+import android.provider.MediaStore.Audio.GenresColumns;\r
+import android.provider.MediaStore.Audio.Playlists;\r
+import android.provider.MediaStore.Audio.PlaylistsColumns;\r
+import android.provider.MediaStore.MediaColumns;\r
+import android.provider.Settings;\r
+import android.support.v4.app.FragmentActivity;\r
+import android.text.TextUtils;\r
+import android.view.Window;\r
+import android.widget.ImageButton;\r
+import android.widget.Toast;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.IApolloService;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.activities.ScanningProgress;\r
+import com.andrew.apollo.service.ApolloService;\r
+import com.andrew.apollo.service.ServiceBinder;\r
+import com.andrew.apollo.service.ServiceToken;\r
+\r
+/**\r
+ * Various methods used to help with specific music statements\r
+ */\r
+public class MusicUtils implements Constants {\r
+\r
+    // Used to make number of albums/songs/time strings\r
+    private final static StringBuilder sFormatBuilder = new StringBuilder();\r
+\r
+    private final static Formatter sFormatter = new Formatter(sFormatBuilder, Locale.getDefault());\r
+\r
+    public static IApolloService mService = null;\r
+\r
+    private static HashMap<Context, ServiceBinder> sConnectionMap = new HashMap<Context, ServiceBinder>();\r
+\r
+    private final static long[] sEmptyList = new long[0];\r
+\r
+    private static final Object[] sTimeArgs = new Object[5];\r
+\r
+    private static ContentValues[] sContentValuesCache = null;\r
+\r
+    private static String mLastSdStatus;\r
+\r
+    private final static int SCAN_DONE = 0;\r
+\r
+    /**\r
+     * @param context\r
+     * @return\r
+     */\r
+    public static ServiceToken bindToService(FragmentActivity context) {\r
+        return bindToService(context, null);\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @param callback\r
+     * @return\r
+     */\r
+    public static ServiceToken bindToService(Context context, ServiceConnection callback) {\r
+        Activity realActivity = ((Activity)context).getParent();\r
+        if (realActivity == null) {\r
+            realActivity = (Activity)context;\r
+        }\r
+        ContextWrapper cw = new ContextWrapper(realActivity);\r
+        cw.startService(new Intent(cw, ApolloService.class));\r
+        ServiceBinder sb = new ServiceBinder(callback);\r
+        if (cw.bindService((new Intent()).setClass(cw, ApolloService.class), sb, 0)) {\r
+            sConnectionMap.put(cw, sb);\r
+            return new ServiceToken(cw);\r
+        }\r
+        return null;\r
+    }\r
+\r
+    /**\r
+     * @param token\r
+     */\r
+    public static void unbindFromService(ServiceToken token) {\r
+        if (token == null) {\r
+            return;\r
+        }\r
+        ContextWrapper cw = token.mWrappedContext;\r
+        ServiceBinder sb = sConnectionMap.remove(cw);\r
+        if (sb == null) {\r
+            return;\r
+        }\r
+        cw.unbindService(sb);\r
+        if (sConnectionMap.isEmpty()) {\r
+            mService = null;\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @param numalbums\r
+     * @param numsongs\r
+     * @param isUnknown\r
+     * @return a string based on the number of albums for an artist or songs for\r
+     *         an album\r
+     */\r
+    public static String makeAlbumsLabel(Context mContext, int numalbums, int numsongs,\r
+            boolean isUnknown) {\r
+\r
+        StringBuilder songs_albums = new StringBuilder();\r
+\r
+        Resources r = mContext.getResources();\r
+        if (isUnknown) {\r
+            String f = r.getQuantityText(R.plurals.Nsongs, numsongs).toString();\r
+            sFormatBuilder.setLength(0);\r
+            sFormatter.format(f, Integer.valueOf(numsongs));\r
+            songs_albums.append(sFormatBuilder);\r
+        } else {\r
+            String f = r.getQuantityText(R.plurals.Nalbums, numalbums).toString();\r
+            sFormatBuilder.setLength(0);\r
+            sFormatter.format(f, Integer.valueOf(numalbums));\r
+            songs_albums.append(sFormatBuilder);\r
+            songs_albums.append("\n");\r
+        }\r
+        return songs_albums.toString();\r
+    }\r
+\r
+    /**\r
+     * @param mContext\r
+     * @return\r
+     */\r
+    public static int getCardId(Context mContext) {\r
+\r
+        ContentResolver res = mContext.getContentResolver();\r
+        Cursor c = res.query(Uri.parse("content://media/external/fs_id"), null, null, null, null);\r
+        int id = -1;\r
+        if (c != null) {\r
+            c.moveToFirst();\r
+            id = c.getInt(0);\r
+            c.close();\r
+        }\r
+        return id;\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @param uri\r
+     * @param projection\r
+     * @param selection\r
+     * @param selectionArgs\r
+     * @param sortOrder\r
+     * @param limit\r
+     * @return\r
+     */\r
+    public static Cursor query(Context context, Uri uri, String[] projection, String selection,\r
+            String[] selectionArgs, String sortOrder, int limit) {\r
+        try {\r
+            ContentResolver resolver = context.getContentResolver();\r
+            if (resolver == null) {\r
+                return null;\r
+            }\r
+            if (limit > 0) {\r
+                uri = uri.buildUpon().appendQueryParameter("limit", "" + limit).build();\r
+            }\r
+            return resolver.query(uri, projection, selection, selectionArgs, sortOrder);\r
+        } catch (UnsupportedOperationException ex) {\r
+            return null;\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @param uri\r
+     * @param projection\r
+     * @param selection\r
+     * @param selectionArgs\r
+     * @param sortOrder\r
+     * @return\r
+     */\r
+    public static Cursor query(Context context, Uri uri, String[] projection, String selection,\r
+            String[] selectionArgs, String sortOrder) {\r
+        return query(context, uri, projection, selection, selectionArgs, sortOrder, 0);\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @param cursor\r
+     */\r
+    public static void shuffleAll(Context context, Cursor cursor) {\r
+        playAll(context, cursor, 0, true);\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @param cursor\r
+     */\r
+    public static void playAll(Context context, Cursor cursor) {\r
+        playAll(context, cursor, 0, false);\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @param cursor\r
+     * @param position\r
+     */\r
+    public static void playAll(Context context, Cursor cursor, int position) {\r
+        playAll(context, cursor, position, false);\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @param list\r
+     * @param position\r
+     */\r
+    public static void playAll(Context context, long[] list, int position) {\r
+        playAll(context, list, position, false);\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @param cursor\r
+     * @param position\r
+     * @param force_shuffle\r
+     */\r
+    private static void playAll(Context context, Cursor cursor, int position, boolean force_shuffle) {\r
+\r
+        long[] list = getSongListForCursor(cursor);\r
+        playAll(context, list, position, force_shuffle);\r
+    }\r
+\r
+    /**\r
+     * @param cursor\r
+     * @return\r
+     */\r
+    public static long[] getSongListForCursor(Cursor cursor) {\r
+        if (cursor == null) {\r
+            return sEmptyList;\r
+        }\r
+        int len = cursor.getCount();\r
+        long[] list = new long[len];\r
+        cursor.moveToFirst();\r
+        int colidx = -1;\r
+        try {\r
+            colidx = cursor.getColumnIndexOrThrow(Audio.Playlists.Members.AUDIO_ID);\r
+        } catch (IllegalArgumentException ex) {\r
+            colidx = cursor.getColumnIndexOrThrow(BaseColumns._ID);\r
+        }\r
+        for (int i = 0; i < len; i++) {\r
+            list[i] = cursor.getLong(colidx);\r
+            cursor.moveToNext();\r
+        }\r
+        return list;\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @param list\r
+     * @param position\r
+     * @param force_shuffle\r
+     */\r
+    private static void playAll(Context context, long[] list, int position, boolean force_shuffle) {\r
+        if (list.length == 0 || mService == null) {\r
+            return;\r
+        }\r
+        try {\r
+            if (force_shuffle) {\r
+                mService.setShuffleMode(ApolloService.SHUFFLE_NORMAL);\r
+            }\r
+            long curid = mService.getAudioId();\r
+            int curpos = mService.getQueuePosition();\r
+            if (position != -1 && curpos == position && curid == list[position]) {\r
+                // The selected file is the file that's currently playing;\r
+                // figure out if we need to restart with a new playlist,\r
+                // or just launch the playback activity.\r
+                long[] playlist = mService.getQueue();\r
+                if (Arrays.equals(list, playlist)) {\r
+                    // we don't need to set a new list, but we should resume\r
+                    // playback if needed\r
+                    mService.play();\r
+                    return;\r
+                }\r
+            }\r
+            if (position < 0) {\r
+                position = 0;\r
+            }\r
+            mService.open(list, force_shuffle ? -1 : position);\r
+            mService.play();\r
+        } catch (RemoteException ex) {\r
+            ex.printStackTrace();\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @return\r
+     */\r
+    public static long[] getQueue() {\r
+\r
+        if (mService == null)\r
+            return sEmptyList;\r
+\r
+        try {\r
+            return mService.getQueue();\r
+        } catch (RemoteException e) {\r
+            e.printStackTrace();\r
+        }\r
+        return sEmptyList;\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @param name\r
+     * @param def\r
+     * @return number of weeks used to create the Recent tab\r
+     */\r
+    public static int getIntPref(Context context, String name, int def) {\r
+        SharedPreferences prefs = context.getSharedPreferences(context.getPackageName(),\r
+                Context.MODE_PRIVATE);\r
+        return prefs.getInt(name, def);\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @param id\r
+     * @return\r
+     */\r
+    public static long[] getSongListForArtist(Context context, long id) {\r
+        final String[] projection = new String[] {\r
+            BaseColumns._ID\r
+        };\r
+        String selection = AudioColumns.ARTIST_ID + "=" + id + " AND " + AudioColumns.IS_MUSIC\r
+                + "=1";\r
+        String sortOrder = AudioColumns.ALBUM_KEY + "," + AudioColumns.TRACK;\r
+        Uri uri = Audio.Media.EXTERNAL_CONTENT_URI;\r
+        Cursor cursor = query(context, uri, projection, selection, null, sortOrder);\r
+        if (cursor != null) {\r
+            long[] list = getSongListForCursor(cursor);\r
+            cursor.close();\r
+            return list;\r
+        }\r
+        return sEmptyList;\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @param id\r
+     * @return\r
+     */\r
+    public static long[] getSongListForAlbum(Context context, long id) {\r
+        final String[] projection = new String[] {\r
+            BaseColumns._ID\r
+        };\r
+        String selection = AudioColumns.ALBUM_ID + "=" + id + " AND " + AudioColumns.IS_MUSIC\r
+                + "=1";\r
+        String sortOrder = AudioColumns.TRACK;\r
+        Uri uri = Audio.Media.EXTERNAL_CONTENT_URI;\r
+        Cursor cursor = query(context, uri, projection, selection, null, sortOrder);\r
+        if (cursor != null) {\r
+            long[] list = getSongListForCursor(cursor);\r
+            cursor.close();\r
+            return list;\r
+        }\r
+        return sEmptyList;\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @param id\r
+     * @return\r
+     */\r
+    public static long[] getSongListForGenre(Context context, long id) {\r
+        String[] projection = new String[] {\r
+            BaseColumns._ID\r
+        };\r
+        StringBuilder selection = new StringBuilder();\r
+        selection.append(AudioColumns.IS_MUSIC + "=1");\r
+        selection.append(" AND " + MediaColumns.TITLE + "!=''");\r
+        Uri uri = Genres.Members.getContentUri(EXTERNAL, id);\r
+        Cursor cursor = context.getContentResolver().query(uri, projection, selection.toString(),\r
+                null, null);\r
+        if (cursor != null) {\r
+            long[] list = getSongListForCursor(cursor);\r
+            cursor.close();\r
+            return list;\r
+        }\r
+        return sEmptyList;\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @param id\r
+     * @return\r
+     */\r
+    public static long[] getSongListForPlaylist(Context context, long id) {\r
+        final String[] projection = new String[] {\r
+            Audio.Playlists.Members.AUDIO_ID\r
+        };\r
+        String sortOrder = Playlists.Members.DEFAULT_SORT_ORDER;\r
+        Uri uri = Playlists.Members.getContentUri(EXTERNAL, id);\r
+        Cursor cursor = query(context, uri, projection, null, null, sortOrder);\r
+        if (cursor != null) {\r
+            long[] list = getSongListForCursor(cursor);\r
+            cursor.close();\r
+            return list;\r
+        }\r
+        return sEmptyList;\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @param name\r
+     * @return\r
+     */\r
+    public static long createPlaylist(Context context, String name) {\r
+\r
+        if (name != null && name.length() > 0) {\r
+            ContentResolver resolver = context.getContentResolver();\r
+            String[] cols = new String[] {\r
+                PlaylistsColumns.NAME\r
+            };\r
+            String whereclause = PlaylistsColumns.NAME + " = '" + name + "'";\r
+            Cursor cur = resolver.query(Audio.Playlists.EXTERNAL_CONTENT_URI, cols, whereclause,\r
+                    null, null);\r
+            if (cur.getCount() <= 0) {\r
+                ContentValues values = new ContentValues(1);\r
+                values.put(PlaylistsColumns.NAME, name);\r
+                Uri uri = resolver.insert(Audio.Playlists.EXTERNAL_CONTENT_URI, values);\r
+                return Long.parseLong(uri.getLastPathSegment());\r
+            }\r
+            return -1;\r
+        }\r
+        return -1;\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @return\r
+     */\r
+    public static long getFavoritesId(Context context) {\r
+        long favorites_id = -1;\r
+        String favorites_where = PlaylistsColumns.NAME + "='" + "Favorites" + "'";\r
+        String[] favorites_cols = new String[] {\r
+            BaseColumns._ID\r
+        };\r
+        Uri favorites_uri = Audio.Playlists.EXTERNAL_CONTENT_URI;\r
+        Cursor cursor = query(context, favorites_uri, favorites_cols, favorites_where, null, null);\r
+        if (cursor.getCount() <= 0) {\r
+            favorites_id = createPlaylist(context, "Favorites");\r
+        } else {\r
+            cursor.moveToFirst();\r
+            favorites_id = cursor.getLong(0);\r
+            cursor.close();\r
+        }\r
+        return favorites_id;\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @param id\r
+     */\r
+    public static void setRingtone(Context context, long id) {\r
+        ContentResolver resolver = context.getContentResolver();\r
+        // Set the flag in the database to mark this as a ringtone\r
+        Uri ringUri = ContentUris.withAppendedId(Audio.Media.EXTERNAL_CONTENT_URI, id);\r
+        try {\r
+            ContentValues values = new ContentValues(2);\r
+            values.put(AudioColumns.IS_RINGTONE, "1");\r
+            values.put(AudioColumns.IS_ALARM, "1");\r
+            resolver.update(ringUri, values, null, null);\r
+        } catch (UnsupportedOperationException ex) {\r
+            // most likely the card just got unmounted\r
+            return;\r
+        }\r
+\r
+        String[] cols = new String[] {\r
+                BaseColumns._ID, MediaColumns.DATA, MediaColumns.TITLE\r
+        };\r
+\r
+        String where = BaseColumns._ID + "=" + id;\r
+        Cursor cursor = query(context, Audio.Media.EXTERNAL_CONTENT_URI, cols, where, null, null);\r
+        try {\r
+            if (cursor != null && cursor.getCount() == 1) {\r
+                // Set the system setting to make this the current ringtone\r
+                cursor.moveToFirst();\r
+                Settings.System.putString(resolver, Settings.System.RINGTONE, ringUri.toString());\r
+                String message = context.getString(R.string.set_as_ringtone, cursor.getString(2));\r
+                Toast.makeText(context, message, Toast.LENGTH_SHORT).show();\r
+            }\r
+        } finally {\r
+            if (cursor != null) {\r
+                cursor.close();\r
+            }\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @param plid\r
+     */\r
+    public static void clearPlaylist(Context context, int plid) {\r
+        Uri uri = Audio.Playlists.Members.getContentUri(EXTERNAL, plid);\r
+        context.getContentResolver().delete(uri, null, null);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @param ids\r
+     * @param playlistid\r
+     */\r
+    public static void addToPlaylist(Context context, long[] ids, long playlistid) {\r
+\r
+        if (ids == null) {\r
+        } else {\r
+            int size = ids.length;\r
+            ContentResolver resolver = context.getContentResolver();\r
+            // need to determine the number of items currently in the playlist,\r
+            // so the play_order field can be maintained.\r
+            String[] cols = new String[] {\r
+                "count(*)"\r
+            };\r
+            Uri uri = Audio.Playlists.Members.getContentUri(EXTERNAL, playlistid);\r
+            Cursor cur = resolver.query(uri, cols, null, null, null);\r
+            cur.moveToFirst();\r
+            int base = cur.getInt(0);\r
+            cur.close();\r
+            int numinserted = 0;\r
+            for (int i = 0; i < size; i += 1000) {\r
+                makeInsertItems(ids, i, 1000, base);\r
+                numinserted += resolver.bulkInsert(uri, sContentValuesCache);\r
+            }\r
+            String message = context.getResources().getQuantityString(\r
+                    R.plurals.NNNtrackstoplaylist, numinserted, numinserted);\r
+            Toast.makeText(context, message, Toast.LENGTH_SHORT).show();\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @param ids\r
+     * @param offset\r
+     * @param len\r
+     * @param base\r
+     */\r
+    private static void makeInsertItems(long[] ids, int offset, int len, int base) {\r
+\r
+        // adjust 'len' if would extend beyond the end of the source array\r
+        if (offset + len > ids.length) {\r
+            len = ids.length - offset;\r
+        }\r
+        // allocate the ContentValues array, or reallocate if it is the wrong\r
+        // size\r
+        if (sContentValuesCache == null || sContentValuesCache.length != len) {\r
+            sContentValuesCache = new ContentValues[len];\r
+        }\r
+        // fill in the ContentValues array with the right values for this pass\r
+        for (int i = 0; i < len; i++) {\r
+            if (sContentValuesCache[i] == null) {\r
+                sContentValuesCache[i] = new ContentValues();\r
+            }\r
+\r
+            sContentValuesCache[i].put(Playlists.Members.PLAY_ORDER, base + offset + i);\r
+            sContentValuesCache[i].put(Playlists.Members.AUDIO_ID, ids[offset + i]);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Toggle favorites\r
+     */\r
+    public static void toggleFavorite() {\r
+\r
+        if (mService == null)\r
+            return;\r
+        try {\r
+            mService.toggleFavorite();\r
+        } catch (RemoteException e) {\r
+            e.printStackTrace();\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @param id\r
+     */\r
+    public static void addToFavorites(Context context, long id) {\r
+\r
+        long favorites_id;\r
+\r
+        if (id < 0) {\r
+\r
+        } else {\r
+            ContentResolver resolver = context.getContentResolver();\r
+\r
+            String favorites_where = PlaylistsColumns.NAME + "='" + PLAYLIST_NAME_FAVORITES + "'";\r
+            String[] favorites_cols = new String[] {\r
+                BaseColumns._ID\r
+            };\r
+            Uri favorites_uri = Audio.Playlists.EXTERNAL_CONTENT_URI;\r
+            Cursor cursor = resolver.query(favorites_uri, favorites_cols, favorites_where, null,\r
+                    null);\r
+            if (cursor.getCount() <= 0) {\r
+                favorites_id = createPlaylist(context, PLAYLIST_NAME_FAVORITES);\r
+            } else {\r
+                cursor.moveToFirst();\r
+                favorites_id = cursor.getLong(0);\r
+                cursor.close();\r
+            }\r
+\r
+            String[] cols = new String[] {\r
+                Playlists.Members.AUDIO_ID\r
+            };\r
+            Uri uri = Playlists.Members.getContentUri(EXTERNAL, favorites_id);\r
+            Cursor cur = resolver.query(uri, cols, null, null, null);\r
+\r
+            int base = cur.getCount();\r
+            cur.moveToFirst();\r
+            while (!cur.isAfterLast()) {\r
+                if (cur.getLong(0) == id)\r
+                    return;\r
+                cur.moveToNext();\r
+            }\r
+            cur.close();\r
+\r
+            ContentValues values = new ContentValues();\r
+            values.put(Playlists.Members.AUDIO_ID, id);\r
+            values.put(Playlists.Members.PLAY_ORDER, base + 1);\r
+            resolver.insert(uri, values);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @param id\r
+     * @return\r
+     */\r
+    public static boolean isFavorite(Context context, long id) {\r
+\r
+        long favorites_id;\r
+\r
+        if (id < 0) {\r
+\r
+        } else {\r
+            ContentResolver resolver = context.getContentResolver();\r
+\r
+            String favorites_where = PlaylistsColumns.NAME + "='" + PLAYLIST_NAME_FAVORITES + "'";\r
+            String[] favorites_cols = new String[] {\r
+                BaseColumns._ID\r
+            };\r
+            Uri favorites_uri = Audio.Playlists.EXTERNAL_CONTENT_URI;\r
+            Cursor cursor = resolver.query(favorites_uri, favorites_cols, favorites_where, null,\r
+                    null);\r
+            if (cursor.getCount() <= 0) {\r
+                favorites_id = createPlaylist(context, PLAYLIST_NAME_FAVORITES);\r
+            } else {\r
+                cursor.moveToFirst();\r
+                favorites_id = cursor.getLong(0);\r
+                cursor.close();\r
+            }\r
+\r
+            String[] cols = new String[] {\r
+                Playlists.Members.AUDIO_ID\r
+            };\r
+            Uri uri = Playlists.Members.getContentUri(EXTERNAL, favorites_id);\r
+            Cursor cur = resolver.query(uri, cols, null, null, null);\r
+\r
+            cur.moveToFirst();\r
+            while (!cur.isAfterLast()) {\r
+                if (cur.getLong(0) == id) {\r
+                    cur.close();\r
+                    return true;\r
+                }\r
+                cur.moveToNext();\r
+            }\r
+            cur.close();\r
+            return false;\r
+        }\r
+        return false;\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @param id\r
+     */\r
+    public static void removeFromFavorites(Context context, long id) {\r
+        long favorites_id;\r
+        if (id < 0) {\r
+        } else {\r
+            ContentResolver resolver = context.getContentResolver();\r
+            String favorites_where = PlaylistsColumns.NAME + "='" + PLAYLIST_NAME_FAVORITES + "'";\r
+            String[] favorites_cols = new String[] {\r
+                BaseColumns._ID\r
+            };\r
+            Uri favorites_uri = Audio.Playlists.EXTERNAL_CONTENT_URI;\r
+            Cursor cursor = resolver.query(favorites_uri, favorites_cols, favorites_where, null,\r
+                    null);\r
+            if (cursor.getCount() <= 0) {\r
+                favorites_id = createPlaylist(context, PLAYLIST_NAME_FAVORITES);\r
+            } else {\r
+                cursor.moveToFirst();\r
+                favorites_id = cursor.getLong(0);\r
+                cursor.close();\r
+            }\r
+            Uri uri = Playlists.Members.getContentUri(EXTERNAL, favorites_id);\r
+            resolver.delete(uri, Playlists.Members.AUDIO_ID + "=" + id, null);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @param mService\r
+     * @param mImageButton\r
+     * @param id\r
+     */\r
+    public static void setFavoriteImage(ImageButton mImageButton) {\r
+        try {\r
+            if (MusicUtils.mService.isFavorite(MusicUtils.mService.getAudioId())) {\r
+                mImageButton.setImageResource(R.drawable.apollo_holo_light_favorite_selected);\r
+            } else {\r
+                mImageButton.setImageResource(R.drawable.apollo_holo_light_favorite_normal);\r
+                // Theme chooser\r
+                ThemeUtils.setImageButton(mImageButton.getContext(), mImageButton,\r
+                        "apollo_favorite_normal");\r
+            }\r
+        } catch (RemoteException e) {\r
+            e.printStackTrace();\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @param mContext\r
+     * @param id\r
+     * @param name\r
+     */\r
+    public static void renamePlaylist(Context mContext, long id, String name) {\r
+\r
+        if (name != null && name.length() > 0) {\r
+            ContentResolver resolver = mContext.getContentResolver();\r
+            ContentValues values = new ContentValues(1);\r
+            values.put(PlaylistsColumns.NAME, name);\r
+            resolver.update(Audio.Playlists.EXTERNAL_CONTENT_URI, values, BaseColumns._ID + "=?",\r
+                    new String[] {\r
+                        String.valueOf(id)\r
+                    });\r
+            Toast.makeText(mContext, "Playlist renamed", Toast.LENGTH_SHORT).show();\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @param mContext\r
+     * @param list\r
+     */\r
+    public static void addToCurrentPlaylist(Context mContext, long[] list) {\r
+\r
+        if (mService == null)\r
+            return;\r
+        try {\r
+            mService.enqueue(list, ApolloService.LAST);\r
+            String message = mContext.getResources().getQuantityString(\r
+                    R.plurals.NNNtrackstoplaylist, list.length, Integer.valueOf(list.length));\r
+            Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();\r
+        } catch (RemoteException ex) {\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @param secs\r
+     * @return time String\r
+     */\r
+    public static String makeTimeString(Context context, long secs) {\r
+\r
+        String durationformat = context.getString(secs < 3600 ? R.string.durationformatshort\r
+                : R.string.durationformatlong);\r
+\r
+        /*\r
+         * Provide multiple arguments so the format can be changed easily by\r
+         * modifying the xml.\r
+         */\r
+        sFormatBuilder.setLength(0);\r
+\r
+        final Object[] timeArgs = sTimeArgs;\r
+        timeArgs[0] = secs / 3600;\r
+        timeArgs[1] = secs / 60;\r
+        timeArgs[2] = secs / 60 % 60;\r
+        timeArgs[3] = secs;\r
+        timeArgs[4] = secs % 60;\r
+\r
+        return sFormatter.format(durationformat, timeArgs).toString();\r
+    }\r
+\r
+    /**\r
+     * @return current album ID\r
+     */\r
+    public static long getCurrentAlbumId() {\r
+\r
+        if (mService != null) {\r
+            try {\r
+                return mService.getAlbumId();\r
+            } catch (RemoteException ex) {\r
+            }\r
+        }\r
+        return -1;\r
+    }\r
+\r
+    /**\r
+     * @return current artist ID\r
+     */\r
+    public static long getCurrentArtistId() {\r
+\r
+        if (MusicUtils.mService != null) {\r
+            try {\r
+                return mService.getArtistId();\r
+            } catch (RemoteException ex) {\r
+            }\r
+        }\r
+        return -1;\r
+    }\r
+\r
+    /**\r
+     * @return current track ID\r
+     */\r
+    public static long getCurrentAudioId() {\r
+\r
+        if (MusicUtils.mService != null) {\r
+            try {\r
+                return mService.getAudioId();\r
+            } catch (RemoteException ex) {\r
+            }\r
+        }\r
+        return -1;\r
+    }\r
+\r
+    /**\r
+     * @return current artist name\r
+     */\r
+    public static String getArtistName() {\r
+\r
+        if (mService != null) {\r
+            try {\r
+                return mService.getArtistName();\r
+            } catch (RemoteException ex) {\r
+            }\r
+        }\r
+        return null;\r
+    }\r
+\r
+    /**\r
+     * @return current album name\r
+     */\r
+    public static String getAlbumName() {\r
+\r
+        if (mService != null) {\r
+            try {\r
+                return mService.getAlbumName();\r
+            } catch (RemoteException ex) {\r
+            }\r
+        }\r
+        return null;\r
+    }\r
+\r
+    /**\r
+     * @return current track name\r
+     */\r
+    public static String getTrackName() {\r
+\r
+        if (mService != null) {\r
+            try {\r
+                return mService.getTrackName();\r
+            } catch (RemoteException ex) {\r
+            }\r
+        }\r
+        return null;\r
+    }\r
+\r
+    /**\r
+     * @return duration of a track\r
+     */\r
+    public static long getDuration() {\r
+        if (mService != null) {\r
+            try {\r
+                return mService.duration();\r
+            } catch (RemoteException e) {\r
+            }\r
+        }\r
+        return 0;\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @return whether the mediascanner is running\r
+     */\r
+    public static boolean isMediaScannerScanning(Context context) {\r
+        boolean result = false;\r
+        Cursor cursor = query(context, MediaStore.getMediaScannerUri(), new String[] {\r
+            MediaStore.MEDIA_SCANNER_VOLUME\r
+        }, null, null, null);\r
+        if (cursor != null) {\r
+            if (cursor.getCount() == 1) {\r
+                cursor.moveToFirst();\r
+                result = EXTERNAL.equals(cursor.getString(0));\r
+            }\r
+            cursor.close();\r
+        }\r
+        return result;\r
+    }\r
+\r
+    /**\r
+     * @param a\r
+     */\r
+    public static void setSpinnerState(Activity a) {\r
+        if (isMediaScannerScanning(a)) {\r
+            // Start the progress spinner\r
+            a.getWindow().setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS,\r
+                    Window.PROGRESS_INDETERMINATE_ON);\r
+\r
+            a.getWindow().setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS,\r
+                    Window.PROGRESS_VISIBILITY_ON);\r
+        } else {\r
+            // Stop the progress spinner\r
+            a.getWindow().setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS,\r
+                    Window.PROGRESS_VISIBILITY_OFF);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @param a\r
+     * @param resID\r
+     */\r
+    public static void displayDatabaseError(Activity a) {\r
+        if (a.isFinishing()) {\r
+            return;\r
+        }\r
+\r
+        String status = Environment.getExternalStorageState();\r
+        if (status.equals(Environment.MEDIA_MOUNTED)) {\r
+            Intent intent = new Intent();\r
+            intent.setClass(a, ScanningProgress.class);\r
+            a.startActivityForResult(intent, SCAN_DONE);\r
+        } else if (!TextUtils.equals(mLastSdStatus, status)) {\r
+            mLastSdStatus = status;\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Create a Search Chooser\r
+     */\r
+    public static void doSearch(Context mContext, Cursor mCursor, int index) {\r
+        CharSequence title = null;\r
+        Intent i = new Intent();\r
+        i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH);\r
+        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\r
+        String query = mCursor.getString(index);\r
+        title = "";\r
+        i.putExtra("", query);\r
+        title = title + " " + query;\r
+        title = "Search " + title;\r
+        i.putExtra(SearchManager.QUERY, query);\r
+        mContext.startActivity(Intent.createChooser(i, title));\r
+    }\r
+\r
+    /**\r
+     * @param id\r
+     * @return removes track from a playlist\r
+     */\r
+    public static int removeTrack(long id) {\r
+        if (mService == null)\r
+            return 0;\r
+\r
+        try {\r
+            return mService.removeTrack(id);\r
+        } catch (RemoteException e) {\r
+            e.printStackTrace();\r
+        }\r
+        return 0;\r
+    }\r
+\r
+    /**\r
+     * @param index\r
+     */\r
+    public static void setQueuePosition(int index) {\r
+        if (mService == null)\r
+            return;\r
+        try {\r
+            mService.setQueuePosition(index);\r
+        } catch (RemoteException e) {\r
+        }\r
+    }\r
+\r
+    public static String getArtistName(Context mContext, long artist_id, boolean default_name) {\r
+        String where = BaseColumns._ID + "=" + artist_id;\r
+        String[] cols = new String[] {\r
+            ArtistColumns.ARTIST\r
+        };\r
+        Uri uri = Audio.Artists.EXTERNAL_CONTENT_URI;\r
+        Cursor cursor = mContext.getContentResolver().query(uri, cols, where, null, null);\r
+        if (cursor.getCount() <= 0) {\r
+            if (default_name)\r
+                return mContext.getString(R.string.unknown);\r
+            else\r
+                return MediaStore.UNKNOWN_STRING;\r
+        } else {\r
+            cursor.moveToFirst();\r
+            String name = cursor.getString(0);\r
+            cursor.close();\r
+            if (name == null || MediaStore.UNKNOWN_STRING.equals(name)) {\r
+                if (default_name)\r
+                    return mContext.getString(R.string.unknown);\r
+                else\r
+                    return MediaStore.UNKNOWN_STRING;\r
+            }\r
+            return name;\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @param mContext\r
+     * @param album_id\r
+     * @param default_name\r
+     * @return album name\r
+     */\r
+    public static String getAlbumName(Context mContext, long album_id, boolean default_name) {\r
+        String where = BaseColumns._ID + "=" + album_id;\r
+        String[] cols = new String[] {\r
+            AlbumColumns.ALBUM\r
+        };\r
+        Uri uri = Audio.Albums.EXTERNAL_CONTENT_URI;\r
+        Cursor cursor = mContext.getContentResolver().query(uri, cols, where, null, null);\r
+        if (cursor.getCount() <= 0) {\r
+            if (default_name)\r
+                return mContext.getString(R.string.unknown);\r
+            else\r
+                return MediaStore.UNKNOWN_STRING;\r
+        } else {\r
+            cursor.moveToFirst();\r
+            String name = cursor.getString(0);\r
+            cursor.close();\r
+            if (name == null || MediaStore.UNKNOWN_STRING.equals(name)) {\r
+                if (default_name)\r
+                    return mContext.getString(R.string.unknown);\r
+                else\r
+                    return MediaStore.UNKNOWN_STRING;\r
+            }\r
+            return name;\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @param playlist_id\r
+     * @return playlist name\r
+     */\r
+    public static String getPlaylistName(Context mContext, long playlist_id) {\r
+        String where = BaseColumns._ID + "=" + playlist_id;\r
+        String[] cols = new String[] {\r
+            PlaylistsColumns.NAME\r
+        };\r
+        Uri uri = Audio.Playlists.EXTERNAL_CONTENT_URI;\r
+        Cursor cursor = mContext.getContentResolver().query(uri, cols, where, null, null);\r
+        if (cursor.getCount() <= 0)\r
+            return "";\r
+        cursor.moveToFirst();\r
+        String name = cursor.getString(0);\r
+        cursor.close();\r
+        return name;\r
+    }\r
+\r
+    /**\r
+     * @param mContext\r
+     * @param genre_id\r
+     * @param default_name\r
+     * @return genre name\r
+     */\r
+    public static String getGenreName(Context mContext, long genre_id, boolean default_name) {\r
+        String where = BaseColumns._ID + "=" + genre_id;\r
+        String[] cols = new String[] {\r
+            GenresColumns.NAME\r
+        };\r
+        Uri uri = Audio.Genres.EXTERNAL_CONTENT_URI;\r
+        Cursor cursor = mContext.getContentResolver().query(uri, cols, where, null, null);\r
+        if (cursor.getCount() <= 0) {\r
+            if (default_name)\r
+                return mContext.getString(R.string.unknown);\r
+            else\r
+                return MediaStore.UNKNOWN_STRING;\r
+        } else {\r
+            cursor.moveToFirst();\r
+            String name = cursor.getString(0);\r
+            cursor.close();\r
+            if (name == null || MediaStore.UNKNOWN_STRING.equals(name)) {\r
+                if (default_name)\r
+                    return mContext.getString(R.string.unknown);\r
+                else\r
+                    return MediaStore.UNKNOWN_STRING;\r
+            }\r
+            return name;\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @param genre\r
+     * @return parsed genre name\r
+     */\r
+    public static String parseGenreName(Context mContext, String genre) {\r
+        int genre_id = -1;\r
+\r
+        if (genre == null || genre.trim().length() <= 0)\r
+            return mContext.getResources().getString(R.string.unknown);\r
+\r
+        try {\r
+            genre_id = Integer.parseInt(genre);\r
+        } catch (NumberFormatException e) {\r
+            return genre;\r
+        }\r
+        if (genre_id >= 0 && genre_id < GENRES_DB.length)\r
+            return GENRES_DB[genre_id];\r
+        else\r
+            return mContext.getResources().getString(R.string.unknown);\r
+    }\r
+\r
+    /**\r
+     * @return if music is playing\r
+     */\r
+    public static boolean isPlaying() {\r
+        if (mService == null)\r
+            return false;\r
+\r
+        try {\r
+            mService.isPlaying();\r
+        } catch (RemoteException e) {\r
+        }\r
+        return false;\r
+    }\r
+\r
+    /**\r
+     * @return current track's queue position\r
+     */\r
+    public static int getQueuePosition() {\r
+        if (mService == null)\r
+            return 0;\r
+        try {\r
+            return mService.getQueuePosition();\r
+        } catch (RemoteException e) {\r
+        }\r
+        return 0;\r
+    }\r
+\r
+    /**\r
+     * @param mContext\r
+     * @param create_shortcut\r
+     * @param list\r
+     */\r
+    public static void makePlaylistList(Context mContext, boolean create_shortcut,\r
+            List<Map<String, String>> list) {\r
+\r
+        Map<String, String> map;\r
+\r
+        String[] cols = new String[] {\r
+                Audio.Playlists._ID, Audio.Playlists.NAME\r
+        };\r
+        StringBuilder where = new StringBuilder();\r
+\r
+        ContentResolver resolver = mContext.getContentResolver();\r
+        if (resolver == null) {\r
+            System.out.println("resolver = null");\r
+        } else {\r
+            where.append(Audio.Playlists.NAME + " != ''");\r
+            where.append(" AND " + Audio.Playlists.NAME + " != '" + PLAYLIST_NAME_FAVORITES + "'");\r
+            Cursor cur = resolver.query(Audio.Playlists.EXTERNAL_CONTENT_URI, cols,\r
+                    where.toString(), null, Audio.Playlists.NAME);\r
+            list.clear();\r
+\r
+            // map = new HashMap<String, String>();\r
+            // map.put("id", String.valueOf(PLAYLIST_FAVORITES));\r
+            // map.put("name", mContext.getString(R.string.favorite));\r
+            // list.add(map);\r
+\r
+            map = new HashMap<String, String>();\r
+            map.put("id", String.valueOf(PLAYLIST_QUEUE));\r
+            map.put("name", mContext.getString(R.string.queue));\r
+            list.add(map);\r
+\r
+            map = new HashMap<String, String>();\r
+            map.put("id", String.valueOf(PLAYLIST_NEW));\r
+            map.put("name", mContext.getString(R.string.new_playlist));\r
+            list.add(map);\r
+\r
+            if (cur != null && cur.getCount() > 0) {\r
+                cur.moveToFirst();\r
+                while (!cur.isAfterLast()) {\r
+                    map = new HashMap<String, String>();\r
+                    map.put("id", String.valueOf(cur.getLong(0)));\r
+                    map.put("name", cur.getString(1));\r
+                    list.add(map);\r
+                    cur.moveToNext();\r
+                }\r
+            }\r
+            if (cur != null) {\r
+                cur.close();\r
+            }\r
+        }\r
+    }\r
+\r
+}\r
diff --git a/src/com/andrew/apollo/utils/SharedPreferencesCompat.java b/src/com/andrew/apollo/utils/SharedPreferencesCompat.java
new file mode 100644 (file)
index 0000000..7cb0bc5
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.andrew.apollo.utils;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+
+/**
+ * Reflection utils to call SharedPreferences$Editor.apply when possible,
+ * falling back to commit when apply isn't available.
+ */
+public class SharedPreferencesCompat {
+    private static final Method sApplyMethod = findApplyMethod();
+
+    private static Method findApplyMethod() {
+        try {
+            Class<Editor> cls = SharedPreferences.Editor.class;
+            return cls.getMethod("apply");
+        } catch (NoSuchMethodException unused) {
+            //$FALL-THROUGH$
+        }
+        return null;
+    }
+
+    public static void apply(SharedPreferences.Editor editor) {
+        if (sApplyMethod != null) {
+            try {
+                sApplyMethod.invoke(editor);
+                return;
+            } catch (InvocationTargetException unused) {
+                //$FALL-THROUGH$
+            } catch (IllegalAccessException unused) {
+                //$FALL-THROUGH$
+            }
+        }
+        editor.commit();
+    }
+}
diff --git a/src/com/andrew/apollo/utils/StringUtilities.java b/src/com/andrew/apollo/utils/StringUtilities.java
new file mode 100644 (file)
index 0000000..2047f27
--- /dev/null
@@ -0,0 +1,189 @@
+/*\r
+ * Copyright (c) 2012, the Last.fm Java Project and Committers\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use of this software in source and binary forms, with or without modification, are\r
+ * permitted provided that the following conditions are met:\r
+ *\r
+ * - Redistributions of source code must retain the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer.\r
+ *\r
+ * - Redistributions in binary form must reproduce the above\r
+ *   copyright notice, this list of conditions and the\r
+ *   following disclaimer in the documentation and/or other\r
+ *   materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED\r
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\r
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\r
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR\r
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+package com.andrew.apollo.utils;\r
+\r
+import java.io.UnsupportedEncodingException;\r
+import java.net.URLDecoder;\r
+import java.net.URLEncoder;\r
+import java.security.MessageDigest;\r
+import java.security.NoSuchAlgorithmException;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+import java.util.regex.Pattern;\r
+\r
+/**\r
+ * Utilitiy class with methods to calculate an md5 hash and to encode URLs.\r
+ *\r
+ * @author Janni Kovacs\r
+ */\r
+public final class StringUtilities {\r
+\r
+       private static MessageDigest digest;\r
+       private static Pattern MBID_PATTERN = Pattern\r
+                       .compile("^[0-9a-f]{8}\\-[0-9a-f]{4}\\-[0-9a-f]{4}\\-[0-9a-f]{4}\\-[0-9a-f]{12}$",\r
+                                       Pattern.CASE_INSENSITIVE);\r
+       private static final Pattern MD5_PATTERN = Pattern.compile("[a-fA-F0-9]{32}");\r
+\r
+       static {\r
+               try {\r
+                       digest = MessageDigest.getInstance("MD5");\r
+               } catch (NoSuchAlgorithmException e) {\r
+                       // better never happens\r
+               }\r
+       }\r
+\r
+       /**\r
+        * Returns a 32 chararacter hexadecimal representation of an MD5 hash of the given String.\r
+        * \r
+        * @param s the String to hash\r
+        * @return the md5 hash\r
+        */\r
+       public static String md5(String s) {\r
+               try {\r
+                       byte[] bytes = digest.digest(s.getBytes("UTF-8"));\r
+                       StringBuilder b = new StringBuilder(32);\r
+                       for (byte aByte : bytes) {\r
+                               String hex = Integer.toHexString((int) aByte & 0xFF);\r
+                               if (hex.length() == 1)\r
+                                       b.append('0');\r
+                               b.append(hex);\r
+                       }\r
+                       return b.toString();\r
+               } catch (UnsupportedEncodingException e) {\r
+                       // utf-8 always available\r
+               }\r
+               return null;\r
+       }\r
+\r
+       /**\r
+        * URL Encodes the given String <code>s</code> using the UTF-8 character encoding.\r
+        *\r
+        * @param s a String\r
+        * @return url encoded string\r
+        */\r
+       public static String encode(String s) {\r
+               if(s == null)\r
+                       return null;\r
+               try {\r
+                       return URLEncoder.encode(s, "UTF-8");\r
+               } catch (UnsupportedEncodingException e) {\r
+                       // utf-8 always available\r
+               }\r
+               return null;\r
+       }\r
+\r
+       /**\r
+        * Decodes an URL encoded String <code>s</code> using the UTF-8 character encoding.\r
+        *\r
+        * @param s an encoded String\r
+        * @return the decoded String\r
+        */\r
+       public static String decode(String s) {\r
+               if(s == null)\r
+                       return null;\r
+               try {\r
+                       return URLDecoder.decode(s, "UTF-8");\r
+               } catch (UnsupportedEncodingException e) {\r
+                       // utf-8 always available\r
+               }\r
+               return null;\r
+       }\r
+\r
+       /**\r
+        * Checks if the supplied String <i>may</i> be a Musicbrainz ID. This method returns <code>true</code> for Strings that are\r
+        * exactly 36 characters long and match the MBID pattern <code>[0-9a-f]{8}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{12}</code>.\r
+        *\r
+        * @param nameOrMbid a possible MBID\r
+        * @return <code>true</code> if this String <i>may</i> be a MBID\r
+        */\r
+       public static boolean isMbid(String nameOrMbid) {\r
+               // example: bfcc6d75-a6a5-4bc6-8282-47aec8531818\r
+               return nameOrMbid != null && nameOrMbid.length() == 36 && MBID_PATTERN.matcher(nameOrMbid).matches();\r
+       }\r
+\r
+       /**\r
+        * Creates a Map out of an array with Strings.\r
+        *\r
+        * @param strings input strings, key-value alternating\r
+        * @return a parameter map\r
+        */\r
+       public static Map<String, String> map(String... strings) {\r
+               if (strings.length % 2 != 0)\r
+                       throw new IllegalArgumentException("strings.length % 2 != 0");\r
+               Map<String, String> mp = new HashMap<String, String>();\r
+               for (int i = 0; i < strings.length; i += 2) {\r
+                       mp.put(strings[i], strings[i + 1]);\r
+               }\r
+               return mp;\r
+       }\r
+\r
+       /**\r
+        * Strips all characters from a String, that might be invalid to be used in file names.\r
+        * By default <tt>: / \ < > | ? " *</tt> are all replaced by <tt>-</tt>.\r
+        * Note that this is no guarantee that the returned name will be definately valid.\r
+        *\r
+        * @param s the String to clean up\r
+        * @return the cleaned up String\r
+        */\r
+       public static String cleanUp(String s) {\r
+               return s.replaceAll("[*:/\\\\?|<>\"]", "-");\r
+       }\r
+\r
+       /**\r
+        * Tests if the given string <i>might</i> already be a 32-char md5 string.\r
+        *\r
+        * @param s String to test\r
+        * @return <code>true</code> if the given String might be a md5 string\r
+        */\r
+       public static boolean isMD5(String s) {\r
+               return s.length() == 32 && MD5_PATTERN.matcher(s).matches();\r
+       }\r
+\r
+       /**\r
+        * Converts a Last.fm boolean result string to a boolean.\r
+        *\r
+        * @param resultString A Last.fm boolean result string.\r
+        * @return <code>true</code> if the given String represents a true, <code>false</code> otherwise.\r
+        */\r
+       public static boolean convertToBoolean(String resultString) {\r
+               return "1".equals(resultString);\r
+       }\r
+\r
+       /**\r
+        * Converts from a boolean to a Last.fm boolean result string.\r
+        * \r
+        * @param value A boolean value.\r
+        * @return A string representing a Last.fm boolean.\r
+        */\r
+       public static String convertFromBoolean(boolean value) {\r
+               if (value) {\r
+                       return "1";\r
+               } else {\r
+                       return "0";\r
+               }\r
+       }\r
+\r
+}\r
diff --git a/src/com/andrew/apollo/utils/ThemeUtils.java b/src/com/andrew/apollo/utils/ThemeUtils.java
new file mode 100644 (file)
index 0000000..c1daa44
--- /dev/null
@@ -0,0 +1,303 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.utils;\r
+\r
+import android.app.ActionBar;\r
+import android.content.Context;\r
+import android.content.SharedPreferences;\r
+import android.content.pm.PackageManager;\r
+import android.content.pm.PackageManager.NameNotFoundException;\r
+import android.content.res.Resources;\r
+import android.graphics.drawable.Drawable;\r
+import android.support.v4.view.ViewPager;\r
+import android.view.MenuItem;\r
+import android.view.View;\r
+import android.widget.ImageButton;\r
+import android.widget.ImageView;\r
+import android.widget.SeekBar;\r
+import android.widget.TextView;\r
+\r
+import com.andrew.apollo.Constants;\r
+\r
+/**\r
+ * @author Andrew Neal TODO - clean this up\r
+ */\r
+public class ThemeUtils implements Constants {\r
+\r
+    /**\r
+     * @param context\r
+     * @param default_theme\r
+     * @return theme package name\r
+     */\r
+    public static String getThemePackageName(Context context, String default_theme) {\r
+        SharedPreferences sp = context.getSharedPreferences(APOLLO_PREFERENCES, 0);\r
+        return sp.getString(THEME_PACKAGE_NAME, default_theme);\r
+    }\r
+\r
+    /**\r
+     * @param context\r
+     * @param packageName\r
+     */\r
+    public static void setThemePackageName(Context context, String packageName) {\r
+        SharedPreferences sp = context.getSharedPreferences(APOLLO_PREFERENCES, 0);\r
+        SharedPreferences.Editor editor = sp.edit();\r
+        editor.putString(THEME_PACKAGE_NAME, packageName);\r
+        editor.commit();\r
+    }\r
+\r
+    /**\r
+     * @param themeResources\r
+     * @param themePackage\r
+     * @param item_name\r
+     * @param item\r
+     * @param themeType\r
+     */\r
+    public static void loadThemeResource(Resources themeResources, String themePackage,\r
+            String item_name, View item, int themeType) {\r
+        Drawable d = null;\r
+        if (themeResources != null) {\r
+            int resource_id = themeResources.getIdentifier(item_name, "drawable", themePackage);\r
+            if (resource_id != 0) {\r
+                try {\r
+                    d = themeResources.getDrawable(resource_id);\r
+                } catch (Resources.NotFoundException e) {\r
+                    return;\r
+                }\r
+                if (themeType == THEME_ITEM_FOREGROUND && item instanceof ImageView) {\r
+                    Drawable tmp = ((ImageView)item).getDrawable();\r
+                    if (tmp != null) {\r
+                        tmp.setCallback(null);\r
+                        tmp = null;\r
+                    }\r
+                    ((ImageView)item).setImageDrawable(d);\r
+                } else {\r
+                    Drawable tmp = item.getBackground();\r
+                    if (tmp != null) {\r
+                        tmp.setCallback(null);\r
+                        tmp = null;\r
+                    }\r
+                    item.setBackgroundDrawable(d);\r
+                }\r
+            }\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @param mContext\r
+     * @param view\r
+     * @param resourceName\r
+     * @param themeType\r
+     */\r
+    public static void initThemeChooser(Context mContext, View view, String resourceName,\r
+            int themeType) {\r
+        String themePackage = getThemePackageName(mContext, APOLLO);\r
+        PackageManager pm = mContext.getPackageManager();\r
+        Resources themeResources = null;\r
+        if (!themePackage.equals(APOLLO)) {\r
+            try {\r
+                themeResources = pm.getResourcesForApplication(themePackage);\r
+            } catch (NameNotFoundException e) {\r
+                setThemePackageName(mContext, APOLLO);\r
+            }\r
+        }\r
+\r
+        if (themeResources != null)\r
+            loadThemeResource(themeResources, themePackage, resourceName, view, themeType);\r
+    }\r
+\r
+    /**\r
+     * @param mContext\r
+     * @param view\r
+     * @param resourceName\r
+     */\r
+    public static void setTextColor(Context mContext, TextView view, String resourceName) {\r
+        String themePackage = getThemePackageName(mContext, APOLLO);\r
+        PackageManager pm = mContext.getPackageManager();\r
+        Resources themeResources = null;\r
+        if (!themePackage.equals(APOLLO)) {\r
+            try {\r
+                themeResources = pm.getResourcesForApplication(themePackage);\r
+            } catch (NameNotFoundException e) {\r
+                setThemePackageName(mContext, APOLLO);\r
+            }\r
+        }\r
+        if (themeResources != null) {\r
+            int resourceID = themeResources.getIdentifier(resourceName, "color", themePackage);\r
+            if (resourceID != 0) {\r
+                view.setTextColor(themeResources.getColor(resourceID));\r
+            }\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @param mContext\r
+     * @param view\r
+     * @param resourceName\r
+     */\r
+    public static void setBackgroundColor(Context mContext, View view, String resourceName) {\r
+        String themePackage = getThemePackageName(mContext, APOLLO);\r
+        PackageManager pm = mContext.getPackageManager();\r
+        Resources themeResources = null;\r
+        if (!themePackage.equals(APOLLO)) {\r
+            try {\r
+                themeResources = pm.getResourcesForApplication(themePackage);\r
+            } catch (NameNotFoundException e) {\r
+                setThemePackageName(mContext, APOLLO);\r
+            }\r
+        }\r
+        if (themeResources != null) {\r
+            int resourceID = themeResources.getIdentifier(resourceName, "color", themePackage);\r
+            if (resourceID != 0) {\r
+                view.setBackgroundColor(themeResources.getColor(resourceID));\r
+            }\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @param mContext\r
+     * @param view\r
+     * @param resourceName\r
+     */\r
+    public static void setImageButton(Context mContext, ImageButton view, String resourceName) {\r
+        String themePackage = getThemePackageName(mContext, APOLLO);\r
+        PackageManager pm = mContext.getPackageManager();\r
+        Resources themeResources = null;\r
+        if (!themePackage.equals(APOLLO)) {\r
+            try {\r
+                themeResources = pm.getResourcesForApplication(themePackage);\r
+            } catch (NameNotFoundException e) {\r
+                setThemePackageName(mContext, APOLLO);\r
+            }\r
+        }\r
+        if (themeResources != null) {\r
+            int resourceID = themeResources.getIdentifier(resourceName, "drawable", themePackage);\r
+            if (resourceID != 0) {\r
+                view.setImageDrawable(themeResources.getDrawable(resourceID));\r
+            }\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @param mContext\r
+     * @param view\r
+     * @param resourceName\r
+     */\r
+    public static void setMarginDrawable(Context mContext, ViewPager view, String resourceName) {\r
+        String themePackage = getThemePackageName(mContext, APOLLO);\r
+        PackageManager pm = mContext.getPackageManager();\r
+        Resources themeResources = null;\r
+        if (!themePackage.equals(APOLLO)) {\r
+            try {\r
+                themeResources = pm.getResourcesForApplication(themePackage);\r
+            } catch (NameNotFoundException e) {\r
+                setThemePackageName(mContext, APOLLO);\r
+            }\r
+        }\r
+        if (themeResources != null) {\r
+            int resourceID = themeResources.getIdentifier(resourceName, "drawable", themePackage);\r
+            if (resourceID != 0) {\r
+                view.setPageMarginDrawable(themeResources.getDrawable(resourceID));\r
+            }\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @param mContext\r
+     * @param view\r
+     * @param resourceName\r
+     */\r
+    public static void setActionBarBackground(Context mContext, ActionBar view, String resourceName) {\r
+        String themePackage = getThemePackageName(mContext, APOLLO);\r
+        PackageManager pm = mContext.getPackageManager();\r
+        Resources themeResources = null;\r
+        if (!themePackage.equals(APOLLO)) {\r
+            try {\r
+                themeResources = pm.getResourcesForApplication(themePackage);\r
+            } catch (NameNotFoundException e) {\r
+                setThemePackageName(mContext, APOLLO);\r
+            }\r
+        }\r
+        if (themeResources != null) {\r
+            int resourceID = themeResources.getIdentifier(resourceName, "drawable", themePackage);\r
+            if (resourceID != 0) {\r
+                view.setBackgroundDrawable(themeResources.getDrawable(resourceID));\r
+            }\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @param mContext\r
+     * @param view\r
+     * @param resourceName\r
+     */\r
+    public static void setActionBarItem(Context mContext, MenuItem view, String resourceName) {\r
+        String themePackage = getThemePackageName(mContext, APOLLO);\r
+        PackageManager pm = mContext.getPackageManager();\r
+        Resources themeResources = null;\r
+        if (!themePackage.equals(APOLLO)) {\r
+            try {\r
+                themeResources = pm.getResourcesForApplication(themePackage);\r
+            } catch (NameNotFoundException e) {\r
+                setThemePackageName(mContext, APOLLO);\r
+            }\r
+        }\r
+        if (themeResources != null) {\r
+            int resourceID = themeResources.getIdentifier(resourceName, "drawable", themePackage);\r
+            if (resourceID != 0) {\r
+                view.setIcon(themeResources.getDrawable(resourceID));\r
+            }\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @param mContext\r
+     * @param view\r
+     * @param resourceName\r
+     */\r
+    public static void setProgessDrawable(Context mContext, SeekBar view, String resourceName) {\r
+        String themePackage = getThemePackageName(mContext, APOLLO);\r
+        PackageManager pm = mContext.getPackageManager();\r
+        Resources themeResources = null;\r
+        if (!themePackage.equals(APOLLO)) {\r
+            try {\r
+                themeResources = pm.getResourcesForApplication(themePackage);\r
+            } catch (NameNotFoundException e) {\r
+                setThemePackageName(mContext, APOLLO);\r
+            }\r
+        }\r
+        if (themeResources != null) {\r
+            int resourceID = themeResources.getIdentifier(resourceName, "drawable", themePackage);\r
+            if (resourceID != 0) {\r
+                view.setProgressDrawable(themeResources.getDrawable(resourceID));\r
+            }\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @param mContext\r
+     * @return which overflow icon to use\r
+     */\r
+    public static boolean overflowLight(Context mContext) {\r
+        String themePackage = getThemePackageName(mContext, APOLLO);\r
+        PackageManager pm = mContext.getPackageManager();\r
+        Resources themeResources = null;\r
+        if (!themePackage.equals(APOLLO)) {\r
+            try {\r
+                themeResources = pm.getResourcesForApplication(themePackage);\r
+            } catch (NameNotFoundException e) {\r
+                setThemePackageName(mContext, APOLLO);\r
+            }\r
+        }\r
+        if (themeResources != null) {\r
+            int resourceID = themeResources.getIdentifier("overflow.light", "bool", themePackage);\r
+            if (resourceID != 0) {\r
+                Boolean overflow = themeResources.getBoolean(resourceID);\r
+                if (overflow)\r
+                    return true;\r
+            }\r
+        }\r
+        return false;\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/views/ViewHolderGrid.java b/src/com/andrew/apollo/views/ViewHolderGrid.java
new file mode 100644 (file)
index 0000000..cce2fc6
--- /dev/null
@@ -0,0 +1,39 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.views;\r
+\r
+import android.view.View;\r
+import android.widget.ImageView;\r
+import android.widget.LinearLayout;\r
+import android.widget.TextView;\r
+\r
+import com.andrew.apollo.R;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ */\r
+public class ViewHolderGrid {\r
+\r
+    public final ImageView mViewHolderImage, mPeakOne, mPeakTwo;\r
+\r
+    public final TextView mViewHolderLineOne;\r
+\r
+    public final TextView mViewHolderLineTwo;\r
+\r
+    public int position;\r
+\r
+    public final LinearLayout mInfoHolder;\r
+\r
+    public ViewHolderGrid(View view) {\r
+        mViewHolderImage = (ImageView)view.findViewById(R.id.gridview_image);\r
+        mViewHolderLineOne = (TextView)view.findViewById(R.id.gridview_line_one);\r
+        mViewHolderLineTwo = (TextView)view.findViewById(R.id.gridview_line_two);\r
+        mPeakOne = (ImageView)view.findViewById(R.id.peak_one);\r
+        mPeakTwo = (ImageView)view.findViewById(R.id.peak_two);\r
+        mInfoHolder = (LinearLayout)view.findViewById(R.id.gridview_info_holder);\r
+        mInfoHolder.setBackgroundColor(view.getResources().getColor(R.color.transparent_black));\r
+    }\r
+\r
+}\r
diff --git a/src/com/andrew/apollo/views/ViewHolderList.java b/src/com/andrew/apollo/views/ViewHolderList.java
new file mode 100644 (file)
index 0000000..d264467
--- /dev/null
@@ -0,0 +1,48 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.views;\r
+\r
+import android.view.View;\r
+import android.widget.FrameLayout;\r
+import android.widget.ImageView;\r
+import android.widget.TextView;\r
+\r
+import com.andrew.apollo.Constants;\r
+import com.andrew.apollo.R;\r
+import com.andrew.apollo.utils.ThemeUtils;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ */\r
+public class ViewHolderList implements Constants {\r
+\r
+    public final ImageView mViewHolderImage, mPeakOne, mPeakTwo, mQuickContextDivider,\r
+            mQuickContextTip;\r
+\r
+    public final TextView mViewHolderLineOne;\r
+\r
+    public final TextView mViewHolderLineTwo;\r
+\r
+    public int position;\r
+\r
+    public final FrameLayout mQuickContext;\r
+\r
+    public ViewHolderList(View view) {\r
+        mViewHolderImage = (ImageView)view.findViewById(R.id.listview_item_image);\r
+        mViewHolderLineOne = (TextView)view.findViewById(R.id.listview_item_line_one);\r
+        mViewHolderLineTwo = (TextView)view.findViewById(R.id.listview_item_line_two);\r
+        mQuickContext = (FrameLayout)view.findViewById(R.id.track_list_context_frame);\r
+        mPeakOne = (ImageView)view.findViewById(R.id.peak_one);\r
+        mPeakTwo = (ImageView)view.findViewById(R.id.peak_two);\r
+        mQuickContextDivider = (ImageView)view.findViewById(R.id.quick_context_line);\r
+        mQuickContextTip = (ImageView)view.findViewById(R.id.quick_context_tip);\r
+\r
+        // Theme chooser\r
+        ThemeUtils.setTextColor(view.getContext(), mViewHolderLineOne, "list_view_text_color");\r
+        ThemeUtils.setTextColor(view.getContext(), mViewHolderLineTwo, "list_view_text_color");\r
+        ThemeUtils.setBackgroundColor(view.getContext(), mQuickContextDivider,\r
+                "list_view_quick_context_menu_button_divider");\r
+    }\r
+}\r
diff --git a/src/com/andrew/apollo/views/ViewHolderQueue.java b/src/com/andrew/apollo/views/ViewHolderQueue.java
new file mode 100644 (file)
index 0000000..f998553
--- /dev/null
@@ -0,0 +1,34 @@
+/**\r
+ * \r
+ */\r
+\r
+package com.andrew.apollo.views;\r
+\r
+import android.view.View;\r
+import android.widget.ImageView;\r
+import android.widget.TextView;\r
+\r
+import com.andrew.apollo.R;\r
+\r
+/**\r
+ * @author Andrew Neal\r
+ */\r
+public class ViewHolderQueue {\r
+\r
+    public final ImageView mArtistImage, mPeakOne, mPeakTwo;\r
+\r
+    public final ImageView mAlbumArt;\r
+\r
+    public final TextView mTrackName;\r
+\r
+    public int position;\r
+\r
+    public ViewHolderQueue(View view) {\r
+        mArtistImage = (ImageView)view.findViewById(R.id.queue_artist_image);\r
+        mAlbumArt = (ImageView)view.findViewById(R.id.queue_album_art);\r
+        mTrackName = (TextView)view.findViewById(R.id.queue_track_name);\r
+        mPeakOne = (ImageView)view.findViewById(R.id.peak_one);\r
+        mPeakTwo = (ImageView)view.findViewById(R.id.peak_two);\r
+    }\r
+\r
+}\r