--- /dev/null
+git rm -r --cached bin\r
+git rm -r --cached gen
\ No newline at end of file
<?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
-<?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
--- /dev/null
+-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
#
# 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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
-<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
-<?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
--- /dev/null
+<?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
-<?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
-<?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 & Flip Actions</string>
- <string name="shaker_summary">Select your "shake" and "flip" actions</string>
- <string name="sensitivity">Sensitivity</string>
- <string name="sensitivity_summary">Shake & 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
--- /dev/null
+<?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
<?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
<?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" />
<?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
-->
<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" />
<?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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+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
--- /dev/null
+\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.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);
+ }
+}
--- /dev/null
+/**\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
--- /dev/null
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.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();
+ }
+}
--- /dev/null
+/**\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
--- /dev/null
+\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
--- /dev/null
+\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
--- /dev/null
+\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
--- /dev/null
+\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+\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
--- /dev/null
+\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
--- /dev/null
+\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
--- /dev/null
+\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
--- /dev/null
+\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
--- /dev/null
+\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/*
+ * 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();
+ }
+ }
+}
--- /dev/null
+
+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();
+ }
+ }
+
+}
--- /dev/null
+
+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
+
+ }
+
+}
--- /dev/null
+\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
--- /dev/null
+/**\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
--- /dev/null
+
+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;
+ }
+}
--- /dev/null
+/* 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
--- /dev/null
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.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();
+ }
+ }
+ }
+ }
+}
--- /dev/null
+\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
--- /dev/null
+\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/*\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
--- /dev/null
+/*
+ * 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());
+
+ }
+
+}
--- /dev/null
+/**\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
--- /dev/null
+/*\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
--- /dev/null
+/*
+ * 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));
+ }
+ }
+}
--- /dev/null
+\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
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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