Repository: naman14/Timber
Branch: master
Commit: b53a7d54682e
Files: 320
Total size: 1.3 MB
Directory structure:
gitextract_5h1zqhrq/
├── .gitignore
├── .travis.yml
├── Changelog.md
├── README.md
├── app/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ ├── androidTest/
│ │ └── java/
│ │ └── com/
│ │ └── naman14/
│ │ └── timber/
│ │ └── ApplicationTest.java
│ └── main/
│ ├── AndroidManifest.xml
│ ├── aidl/
│ │ └── com/
│ │ └── naman14/
│ │ └── timber/
│ │ ├── ITimberService.aidl
│ │ └── helpers/
│ │ └── MusicPlaybackTrack.aidl
│ ├── java/
│ │ └── com/
│ │ └── naman14/
│ │ └── timber/
│ │ ├── MusicPlayer.java
│ │ ├── MusicService.java
│ │ ├── TimberApp.java
│ │ ├── WearBrowserService.java
│ │ ├── activities/
│ │ │ ├── BaseActivity.java
│ │ │ ├── BaseThemedActivity.java
│ │ │ ├── DonateActivity.java
│ │ │ ├── MainActivity.java
│ │ │ ├── NowPlayingActivity.java
│ │ │ ├── PlaylistDetailActivity.java
│ │ │ ├── SearchActivity.java
│ │ │ └── SettingsActivity.java
│ │ ├── adapters/
│ │ │ ├── AlbumAdapter.java
│ │ │ ├── AlbumSongsAdapter.java
│ │ │ ├── ArtistAdapter.java
│ │ │ ├── ArtistAlbumAdapter.java
│ │ │ ├── ArtistSongAdapter.java
│ │ │ ├── BaseQueueAdapter.java
│ │ │ ├── BaseSongAdapter.java
│ │ │ ├── FolderAdapter.java
│ │ │ ├── PlayingQueueAdapter.java
│ │ │ ├── PlaylistAdapter.java
│ │ │ ├── SearchAdapter.java
│ │ │ ├── SlidingQueueAdapter.java
│ │ │ └── SongsListAdapter.java
│ │ ├── cast/
│ │ │ ├── CastOptionsProvider.java
│ │ │ ├── ExpandedControlsActivity.java
│ │ │ ├── SimpleSessionManagerListener.java
│ │ │ ├── TimberCastHelper.java
│ │ │ └── WebServer.java
│ │ ├── dataloaders/
│ │ │ ├── AlbumLoader.java
│ │ │ ├── AlbumSongLoader.java
│ │ │ ├── ArtistAlbumLoader.java
│ │ │ ├── ArtistLoader.java
│ │ │ ├── ArtistSongLoader.java
│ │ │ ├── FolderLoader.java
│ │ │ ├── LastAddedLoader.java
│ │ │ ├── NowPlayingCursor.java
│ │ │ ├── PlaylistLoader.java
│ │ │ ├── PlaylistSongLoader.java
│ │ │ ├── QueueLoader.java
│ │ │ ├── SongLoader.java
│ │ │ ├── SortedCursor.java
│ │ │ └── TopTracksLoader.java
│ │ ├── dialogs/
│ │ │ ├── AddPlaylistDialog.java
│ │ │ ├── CreatePlaylistDialog.java
│ │ │ ├── LastFmLoginDialog.java
│ │ │ └── StorageSelectDialog.java
│ │ ├── fragments/
│ │ │ ├── AlbumDetailFragment.java
│ │ │ ├── AlbumFragment.java
│ │ │ ├── ArtistBioFragment.java
│ │ │ ├── ArtistDetailFragment.java
│ │ │ ├── ArtistFragment.java
│ │ │ ├── ArtistMusicFragment.java
│ │ │ ├── FoldersFragment.java
│ │ │ ├── MainFragment.java
│ │ │ ├── PlaylistFragment.java
│ │ │ ├── QueueFragment.java
│ │ │ ├── SettingsFragment.java
│ │ │ ├── SimilarArtistFragment.java
│ │ │ └── SongsFragment.java
│ │ ├── helpers/
│ │ │ ├── MediaButtonIntentReceiver.java
│ │ │ └── MusicPlaybackTrack.java
│ │ ├── lastfmapi/
│ │ │ ├── LastFmClient.java
│ │ │ ├── LastFmRestService.java
│ │ │ ├── LastFmUserRestService.java
│ │ │ ├── RestServiceFactory.java
│ │ │ ├── callbacks/
│ │ │ │ ├── AlbumInfoListener.java
│ │ │ │ ├── ArtistInfoListener.java
│ │ │ │ └── UserListener.java
│ │ │ └── models/
│ │ │ ├── AlbumBio.java
│ │ │ ├── AlbumInfo.java
│ │ │ ├── AlbumQuery.java
│ │ │ ├── AlbumTracks.java
│ │ │ ├── ArtistBio.java
│ │ │ ├── ArtistInfo.java
│ │ │ ├── ArtistQuery.java
│ │ │ ├── ArtistTag.java
│ │ │ ├── Artwork.java
│ │ │ ├── LastfmAlbum.java
│ │ │ ├── LastfmArtist.java
│ │ │ ├── LastfmUserSession.java
│ │ │ ├── ScrobbleInfo.java
│ │ │ ├── ScrobbleQuery.java
│ │ │ ├── UserLoginInfo.java
│ │ │ └── UserLoginQuery.java
│ │ ├── listeners/
│ │ │ ├── MusicStateListener.java
│ │ │ └── SimplelTransitionListener.java
│ │ ├── models/
│ │ │ ├── Album.java
│ │ │ ├── Artist.java
│ │ │ ├── Playlist.java
│ │ │ └── Song.java
│ │ ├── nowplaying/
│ │ │ ├── BaseNowplayingFragment.java
│ │ │ ├── Timber1.java
│ │ │ ├── Timber2.java
│ │ │ ├── Timber3.java
│ │ │ ├── Timber4.java
│ │ │ ├── Timber5.java
│ │ │ └── Timber6.java
│ │ ├── permissions/
│ │ │ ├── Nammu.java
│ │ │ ├── PermissionCallback.java
│ │ │ ├── PermissionListener.java
│ │ │ └── PermissionRequest.java
│ │ ├── provider/
│ │ │ ├── MusicDB.java
│ │ │ ├── MusicPlaybackState.java
│ │ │ ├── RecentStore.java
│ │ │ ├── SearchHistory.java
│ │ │ └── SongPlayCount.java
│ │ ├── slidinguppanel/
│ │ │ ├── SlidingUpPanelLayout.java
│ │ │ └── ViewDragHelper.java
│ │ ├── subfragments/
│ │ │ ├── ArtistTagFragment.java
│ │ │ ├── LyricsFragment.java
│ │ │ ├── PlaylistPagerFragment.java
│ │ │ ├── QuickControlsFragment.java
│ │ │ ├── StyleSelectorFragment.java
│ │ │ └── SubStyleSelectorFragment.java
│ │ ├── timely/
│ │ │ ├── TimelyView.java
│ │ │ ├── animation/
│ │ │ │ └── TimelyEvaluator.java
│ │ │ └── model/
│ │ │ ├── NumberUtils.java
│ │ │ ├── core/
│ │ │ │ └── Figure.java
│ │ │ └── number/
│ │ │ ├── Eight.java
│ │ │ ├── Five.java
│ │ │ ├── Four.java
│ │ │ ├── Nine.java
│ │ │ ├── Null.java
│ │ │ ├── One.java
│ │ │ ├── Seven.java
│ │ │ ├── Six.java
│ │ │ ├── Three.java
│ │ │ ├── Two.java
│ │ │ └── Zero.java
│ │ ├── transition/
│ │ │ └── PlayTransition.java
│ │ ├── utils/
│ │ │ ├── ATEUtils.java
│ │ │ ├── Constants.java
│ │ │ ├── FabAnimationUtils.java
│ │ │ ├── Helpers.java
│ │ │ ├── ImageUtils.java
│ │ │ ├── LyricsExtractor.java
│ │ │ ├── LyricsLoader.java
│ │ │ ├── NavigationUtils.java
│ │ │ ├── PreferencesUtility.java
│ │ │ ├── SlideTrackSwitcher.java
│ │ │ ├── SortOrder.java
│ │ │ └── TimberUtils.java
│ │ └── widgets/
│ │ ├── BaseRecyclerView.java
│ │ ├── BubbleTextGetter.java
│ │ ├── CircleImageView.java
│ │ ├── CircularSeekBar.java
│ │ ├── DividerItemDecoration.java
│ │ ├── DragSortRecycler.java
│ │ ├── FastScroller.java
│ │ ├── MultiViewPager.java
│ │ ├── MusicVisualizer.java
│ │ ├── PlayPauseButton.java
│ │ ├── PlayPauseDrawable.java
│ │ ├── PopupImageView.java
│ │ ├── SquareImageView.java
│ │ ├── TextDrawable.java
│ │ ├── ThemedPreferenceCategory.java
│ │ └── desktop/
│ │ ├── BaseWidget.java
│ │ ├── SmallWidget.java
│ │ ├── StandardWidget.java
│ │ └── WhiteWidget.java
│ └── res/
│ ├── anim/
│ │ ├── activity_fade_in.xml
│ │ ├── activity_fade_out.xml
│ │ ├── design_fab_out.xml
│ │ ├── scale.xml
│ │ ├── slide_in_from_bottom.xml
│ │ ├── slide_in_from_left.xml
│ │ ├── slide_in_from_right.xml
│ │ ├── slide_out_to_bottom.xml
│ │ ├── slide_out_to_left.xml
│ │ └── slide_out_to_right.xml
│ ├── color/
│ │ ├── state_selector_black.xml
│ │ ├── state_selector_dark.xml
│ │ └── state_selector_light.xml
│ ├── drawable/
│ │ ├── bg_gradient.xml
│ │ ├── ic_file_music_dark.xml
│ │ ├── ic_folder_open_black_24dp.xml
│ │ ├── ic_folder_open_white_24dp.xml
│ │ ├── ic_folder_parent_dark.xml
│ │ ├── ic_menu.xml
│ │ ├── ic_timer_wait.xml
│ │ ├── item_divider_black.xml
│ │ ├── item_divider_white.xml
│ │ ├── progress_drawable.xml
│ │ ├── progress_drawable_black.xml
│ │ ├── progress_drawable_dark.xml
│ │ ├── progress_drawable_withbackground.xml
│ │ ├── progress_drawable_withbackground_black.xml
│ │ ├── progress_drawable_withbackground_dark.xml
│ │ ├── recyclerview_fastscroller_bubble.xml
│ │ ├── recyclerview_fastscroller_handle.xml
│ │ └── selectable_item_background.xml
│ ├── drawable-xhdpi/
│ │ ├── above_shadow.xml
│ │ └── below_shadow.xml
│ ├── layout/
│ │ ├── activity_donate.xml
│ │ ├── activity_main.xml
│ │ ├── activity_nowplaying.xml
│ │ ├── activity_playlist_detail.xml
│ │ ├── activity_search.xml
│ │ ├── activity_settings.xml
│ │ ├── artist_detail_albums_header.xml
│ │ ├── bottom_nowplaying_card.xml
│ │ ├── dialog_lastfm_login.xml
│ │ ├── empty_view.xml
│ │ ├── fragment_album_detail.xml
│ │ ├── fragment_artist_bio.xml
│ │ ├── fragment_artist_detail.xml
│ │ ├── fragment_artist_music.xml
│ │ ├── fragment_cast_mini_controller.xml
│ │ ├── fragment_folders.xml
│ │ ├── fragment_lyrics.xml
│ │ ├── fragment_main.xml
│ │ ├── fragment_playback_controls.xml
│ │ ├── fragment_playlist.xml
│ │ ├── fragment_playlist_pager.xml
│ │ ├── fragment_queue.xml
│ │ ├── fragment_recyclerview.xml
│ │ ├── fragment_similar_artists.xml
│ │ ├── fragment_style_selector.xml
│ │ ├── fragment_style_selector_pager.xml
│ │ ├── fragment_timber1.xml
│ │ ├── fragment_timber2.xml
│ │ ├── fragment_timber3.xml
│ │ ├── fragment_timber4.xml
│ │ ├── fragment_timber5.xml
│ │ ├── fragment_timber6.xml
│ │ ├── include_list_viewpager.xml
│ │ ├── item_album_grid.xml
│ │ ├── item_album_list.xml
│ │ ├── item_album_search.xml
│ │ ├── item_album_song.xml
│ │ ├── item_artist.xml
│ │ ├── item_artist_album.xml
│ │ ├── item_artist_grid.xml
│ │ ├── item_artist_song.xml
│ │ ├── item_donate_product.xml
│ │ ├── item_folder_list.xml
│ │ ├── item_playing_queue.xml
│ │ ├── item_song.xml
│ │ ├── item_song_playlist.xml
│ │ ├── item_song_sliding_queue.xml
│ │ ├── item_song_timber1.xml
│ │ ├── layout_artist_tag.xml
│ │ ├── layout_dummy_header_artist.xml
│ │ ├── nav_header.xml
│ │ ├── recyclerview_fastscroller.xml
│ │ ├── search_section_header.xml
│ │ ├── song_elapsedtime.xml
│ │ ├── widget_small.xml
│ │ ├── widget_standard.xml
│ │ └── widget_white.xml
│ ├── menu/
│ │ ├── album_detail.xml
│ │ ├── album_sort_by.xml
│ │ ├── artist_detail.xml
│ │ ├── artist_sort_by.xml
│ │ ├── drawer_view.xml
│ │ ├── menu_cast.xml
│ │ ├── menu_expanded_controller.xml
│ │ ├── menu_folders.xml
│ │ ├── menu_main.xml
│ │ ├── menu_playlist.xml
│ │ ├── menu_playlist_detail.xml
│ │ ├── menu_search.xml
│ │ ├── menu_show_as.xml
│ │ ├── now_playing.xml
│ │ ├── popup_playing_queue.xml
│ │ ├── popup_song.xml
│ │ └── song_sort_by.xml
│ ├── transition/
│ │ ├── image_transform.xml
│ │ └── play_transition.xml
│ ├── values/
│ │ ├── arrays.xml
│ │ ├── attrs.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── plurals.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ ├── values-da/
│ │ └── strings.xml
│ ├── values-de/
│ │ └── strings.xml
│ ├── values-es/
│ │ └── strings.xml
│ ├── values-fr/
│ │ └── strings.xml
│ ├── values-id/
│ │ └── strings.xml
│ ├── values-it/
│ │ └── strings.xml
│ ├── values-ko/
│ │ └── strings.xml
│ ├── values-nb-rNO/
│ │ └── strings.xml
│ ├── values-pt/
│ │ └── strings.xml
│ ├── values-ru/
│ │ └── strings.xml
│ ├── values-tr/
│ │ └── strings.xml
│ ├── values-v19/
│ │ └── styles.xml
│ ├── values-v21/
│ │ ├── dimens.xml
│ │ └── styles.xml
│ ├── values-w820dp/
│ │ └── dimens.xml
│ ├── values-zh/
│ │ └── strings.xml
│ └── xml/
│ ├── preferences.xml
│ ├── widget_small.xml
│ ├── widget_standard.xml
│ └── widget_white.xml
├── build.gradle
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── mock/
│ ├── mock-google-services.json
│ └── mock-secret.xml
├── mock.gradle
└── settings.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.gradle
/local.properties
.idea
.DS_Store
/build
/captures
/*.iml
/app/app-release.apk
app/app.iml
app/manifest-merger-release-report.txt
secret.xml
google-services.json
================================================
FILE: .travis.yml
================================================
language: android
jdk: oraclejdk8
branches:
only:
- master
script: "./gradlew clean assembleDebug"
android:
components:
- platform-tools
- tools
- build-tools-28.0.3
- android-28
- extra-android-m2repository
================================================
FILE: Changelog.md
================================================
#Changelog
v0.21b-
* Add Homescreen widget
* View playlists in list, grid and default
* Delete playlists, clear auto playlists and hide auto playlists
* Remove songs from playlists, reorder playlists
* Delete songs from device
v0.14b-
* Added Theme engine
* Performance improvements
* Many bugs fixed
v0.13b-
* Create playlists and add songs to playlists
* Go to artist, album, add to queue, play next in song popup menu
* Pause music playback when headphones removed
* Fixed equalizer not working
* Fixed track numbers being wrong
* Bluetooth controls
* Show song duration in Timber3 now playing style
* Added Chinese,French,German and Italian translation
* Bug fixes and improvements
v0.122b-
* Fix crash in search activity
* Fix one time crash in marshmallow after allowing permission
* Added albums view options(list,grid)
v0.12b-
* Fix crash in artist detail and search
* Add intent for equalizer
* Ability to sort songs, albums and artists according to year,duration,track,number of songs, a-z, z-a etc
v0.11b-
* Fix crash on asynctask onpostexecute
* Fix crash on onTouchEvent in NavigationView
* Updated more material launcher and notification icon
* Reduced Apk size, enabled Proguard
* Added option to choose default start page
* Added option to remember the last page opened
* Fixed blurred albumart not changing if albumart not available
v0.1b
* Initial Release
================================================
FILE: README.md
================================================
# Timber
[](https://travis-ci.org/naman14/Timber)
Material Design Music Player
This project is no longer in active development. Please refer to [TimberX](https://github.com/naman14/TimberX) instead
## Screenshots
## Features
- Material design
- Browse Songs, Albums, Artists
- Create and edit playlists
- 6 different now playing styles
- Homescreen widgets
- Browse device folders
- Dark theme and UI customisability
- Gestures for track switching
- LastFM scrobble
- Android Wear and Android Auto support
- Playing queue in notification (Xposed)
- Lyrics support
- Chromecast support
## Changelog
Changelog is available [here](https://github.com/naman14/Timber/blob/master/Changelog.md)
## Credits
* CyanogenMod's [Eleven Music Player](https://github.com/CyanogenMod/android_packages_apps_Eleven)
* [TimelyTextView](https://github.com/adnan-SM/TimelyTextView)
* [MultiViewPager](https://github.com/Pixplicity/MultiViewPager)
* [PlayPauseButton](https://github.com/recruit-lifestyle/PlayPauseButton)
* [CircularSeekBar](https://github.com/devadvance/circularseekbar)
* [Nammu](https://github.com/tajchert/Nammu)
# Donate
Paypal donation email-
namandwivedi14@gmail.com
## License
>(c) 2015 Naman Dwivedi
>This is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
>This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
>You should have received a copy of the GNU General Public License along with this app. If not, see .
================================================
FILE: app/.gitignore
================================================
/build
================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'
android {
compileSdkVersion rootProject.compileSdkVersion
defaultConfig {
applicationId "naman14.timber"
minSdkVersion rootProject.minSdkVersion
targetSdkVersion rootProject.targetSdkVersion
versionCode 22
versionName "1.8"
//renderscript support mode is not supported for 21+ with gradle version 2.0
renderscriptTargetApi 16
renderscriptSupportModeEnabled false
multiDexEnabled true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
versionNameSuffix "-debug"
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
lintOptions {
disable 'MissingTranslation'
disable 'ExtraTranslation'
}
}
repositories {
jcenter()
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "androidx.appcompat:appcompat:1.0.2"
implementation "com.google.android.material:material:1.0.0"
implementation "androidx.cardview:cardview:1.0.0"
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation "androidx.palette:palette:1.0.0"
implementation "androidx.percentlayout:percentlayout:1.0.0"
implementation 'androidx.multidex:multidex:2.0.1'
implementation "androidx.mediarouter:mediarouter:1.1.0"
implementation 'com.google.android.gms:play-services-cast-framework:16.1.2'
implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.4'
implementation 'net.steamcrafted:materialiconlib:1.1.4'
implementation 'com.squareup.retrofit:retrofit:1.9.0'
implementation 'com.squareup.okhttp:okhttp-urlconnection:2.3.0'
implementation 'com.squareup.okhttp:okhttp:2.3.0'
implementation 'com.google.code.gson:gson:2.3'
implementation 'de.Maxr1998:track-selector-lib:1.2'
implementation 'com.afollestad.material-dialogs:core:0.9.0.2'
implementation 'com.afollestad.material-dialogs:commons:0.9.0.2'
implementation('com.github.naman14:app-theme-engine:0.5.2@aar') {
transitive = true
}
implementation 'com.google.firebase:firebase-crashlytics:17.2.2'
implementation 'com.anjlab.android.iab.v3:library:1.0.+'
implementation 'org.nanohttpd:nanohttpd:2.3.1'
}
apply from: '../mock.gradle'
================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/naman/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# retrofit
-keep class com.squareup.okhttp.** { *; }
-keep class retrofit.** { *; }
-keep interface com.squareup.okhttp.** { *; }
-keep class com.naman14.timber.lastfmapi.models.** { *; }
-keep class android.support.v4.app.** { *; }
-keep interface android.support.v4.app.** { *; }
-keep class android.support.v7.app.** { *; }
-keep interface android.support.v7.app.** { *; }
-dontwarn com.squareup.okhttp.**
-dontwarn okio.**
-dontwarn retrofit.**
-dontwarn rx.**
-keepclasseswithmembers class * {
@retrofit.http.* ;
}
-keepattributes Exceptions
-keepattributes Signature
-keepattributes *Annotation*
#renderscript
-keep class android.support.v8.renderscript.** { *; }
#searchview
-keep class android.support.v7.widget.SearchView { *; }
================================================
FILE: app/src/androidTest/java/com/naman14/timber/ApplicationTest.java
================================================
package com.naman14.timber;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* Testing Fundamentals
*/
public class ApplicationTest extends ApplicationTestCase {
public ApplicationTest() {
super(Application.class);
}
}
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
================================================
FILE: app/src/main/aidl/com/naman14/timber/ITimberService.aidl
================================================
package com.naman14.timber;
import com.naman14.timber.helpers.MusicPlaybackTrack;
interface ITimberService
{
void openFile(String path);
void open(in long [] list, int position, long sourceId, int sourceType);
void stop();
void pause();
void play();
void prev(boolean forcePrevious);
void next();
void enqueue(in long [] list, int action, long sourceId, int sourceType);
void setQueuePosition(int index);
void setShuffleMode(int shufflemode);
void setRepeatMode(int repeatmode);
void moveQueueItem(int from, int to);
void refresh();
void playlistChanged();
boolean isPlaying();
long [] getQueue();
long getQueueItemAtPosition(int position);
int getQueueSize();
int getQueuePosition();
int getQueueHistoryPosition(int position);
int getQueueHistorySize();
int[] getQueueHistoryList();
long duration();
long position();
long seek(long pos);
void seekRelative(long deltaInMs);
long getAudioId();
MusicPlaybackTrack getCurrentTrack();
MusicPlaybackTrack getTrack(int index);
long getNextAudioId();
long getPreviousAudioId();
long getArtistId();
long getAlbumId();
String getArtistName();
String getTrackName();
String getAlbumName();
String getPath();
int getShuffleMode();
int removeTracks(int first, int last);
int removeTrack(long id);
boolean removeTrackAtPosition(long id, int position);
int getRepeatMode();
int getMediaMountedCount();
int getAudioSessionId();
}
================================================
FILE: app/src/main/aidl/com/naman14/timber/helpers/MusicPlaybackTrack.aidl
================================================
package com.naman14.timber.helpers;
parcelable MusicPlaybackTrack;
================================================
FILE: app/src/main/java/com/naman14/timber/MusicPlayer.java
================================================
/*
* Copyright (C) 2012 Andrew Neal
* Copyright (C) 2014 The CyanogenMod Project
* Copyright (C) 2015 Naman Dwivedi
*
* 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.naman14.timber;
import android.app.Activity;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.ServiceConnection;
import android.database.Cursor;
import android.net.Uri;
import android.os.IBinder;
import android.os.RemoteException;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import android.widget.Toast;
import com.naman14.timber.dataloaders.SongLoader;
import com.naman14.timber.helpers.MusicPlaybackTrack;
import com.naman14.timber.utils.TimberUtils.IdType;
import java.util.Arrays;
import java.util.WeakHashMap;
public class MusicPlayer {
private static final WeakHashMap mConnectionMap;
private static final long[] sEmptyList;
public static ITimberService mService = null;
private static ContentValues[] mContentValuesCache = null;
static {
mConnectionMap = new WeakHashMap();
sEmptyList = new long[0];
}
public static final ServiceToken bindToService(final Context context,
final ServiceConnection callback) {
Activity realActivity = ((Activity) context).getParent();
if (realActivity == null) {
realActivity = (Activity) context;
}
final ContextWrapper contextWrapper = new ContextWrapper(realActivity);
contextWrapper.startService(new Intent(contextWrapper, MusicService.class));
final ServiceBinder binder = new ServiceBinder(callback,
contextWrapper.getApplicationContext());
if (contextWrapper.bindService(
new Intent().setClass(contextWrapper, MusicService.class), binder, 0)) {
mConnectionMap.put(contextWrapper, binder);
return new ServiceToken(contextWrapper);
}
return null;
}
public static void unbindFromService(final ServiceToken token) {
if (token == null) {
return;
}
final ContextWrapper mContextWrapper = token.mWrappedContext;
final ServiceBinder mBinder = mConnectionMap.remove(mContextWrapper);
if (mBinder == null) {
return;
}
mContextWrapper.unbindService(mBinder);
if (mConnectionMap.isEmpty()) {
mService = null;
}
}
public static final boolean isPlaybackServiceConnected() {
return mService != null;
}
public static void next() {
try {
if (mService != null) {
mService.next();
}
} catch (final RemoteException ignored) {
}
}
public static void initPlaybackServiceWithSettings(final Context context) {
}
public static void asyncNext(final Context context) {
final Intent previous = new Intent(context, MusicService.class);
previous.setAction(MusicService.NEXT_ACTION);
context.startService(previous);
}
public static void previous(final Context context, final boolean force) {
final Intent previous = new Intent(context, MusicService.class);
if (force) {
previous.setAction(MusicService.PREVIOUS_FORCE_ACTION);
} else {
previous.setAction(MusicService.PREVIOUS_ACTION);
}
context.startService(previous);
}
public static void playOrPause() {
try {
if (mService != null) {
if (mService.isPlaying()) {
mService.pause();
} else {
mService.play();
}
}
} catch (final Exception ignored) {
}
}
public static void cycleRepeat() {
try {
if (mService != null) {
switch (mService.getRepeatMode()) {
case MusicService.REPEAT_NONE:
mService.setRepeatMode(MusicService.REPEAT_ALL);
break;
case MusicService.REPEAT_ALL:
mService.setRepeatMode(MusicService.REPEAT_CURRENT);
if (mService.getShuffleMode() != MusicService.SHUFFLE_NONE) {
mService.setShuffleMode(MusicService.SHUFFLE_NONE);
}
break;
default:
mService.setRepeatMode(MusicService.REPEAT_NONE);
break;
}
}
} catch (final RemoteException ignored) {
}
}
public static void cycleShuffle() {
try {
if (mService != null) {
switch (mService.getShuffleMode()) {
case MusicService.SHUFFLE_NONE:
mService.setShuffleMode(MusicService.SHUFFLE_NORMAL);
if (mService.getRepeatMode() == MusicService.REPEAT_CURRENT) {
mService.setRepeatMode(MusicService.REPEAT_ALL);
}
break;
case MusicService.SHUFFLE_NORMAL:
mService.setShuffleMode(MusicService.SHUFFLE_NONE);
break;
case MusicService.SHUFFLE_AUTO:
mService.setShuffleMode(MusicService.SHUFFLE_NONE);
break;
default:
break;
}
}
} catch (final RemoteException ignored) {
}
}
public static final boolean isPlaying() {
if (mService != null) {
try {
return mService.isPlaying();
} catch (final RemoteException ignored) {
}
}
return false;
}
public static final int getShuffleMode() {
if (mService != null) {
try {
return mService.getShuffleMode();
} catch (final RemoteException ignored) {
}
}
return 0;
}
public static void setShuffleMode(int mode) {
try {
if (mService != null) {
mService.setShuffleMode(mode);
}
} catch (RemoteException ignored) {
}
}
public static final int getRepeatMode() {
if (mService != null) {
try {
return mService.getRepeatMode();
} catch (final RemoteException ignored) {
}
}
return 0;
}
public static final String getTrackName() {
if (mService != null) {
try {
return mService.getTrackName();
} catch (final RemoteException ignored) {
}
}
return null;
}
public static final String getArtistName() {
if (mService != null) {
try {
return mService.getArtistName();
} catch (final RemoteException ignored) {
}
}
return null;
}
public static final String getAlbumName() {
if (mService != null) {
try {
return mService.getAlbumName();
} catch (final RemoteException ignored) {
}
}
return null;
}
public static final long getCurrentAlbumId() {
if (mService != null) {
try {
return mService.getAlbumId();
} catch (final RemoteException ignored) {
}
}
return -1;
}
public static final long getCurrentAudioId() {
if (mService != null) {
try {
return mService.getAudioId();
} catch (final RemoteException ignored) {
}
}
return -1;
}
public static final MusicPlaybackTrack getCurrentTrack() {
if (mService != null) {
try {
return mService.getCurrentTrack();
} catch (final RemoteException ignored) {
}
}
return null;
}
public static final MusicPlaybackTrack getTrack(int index) {
if (mService != null) {
try {
return mService.getTrack(index);
} catch (final RemoteException ignored) {
}
}
return null;
}
public static final long getNextAudioId() {
if (mService != null) {
try {
return mService.getNextAudioId();
} catch (final RemoteException ignored) {
}
}
return -1;
}
public static final long getPreviousAudioId() {
if (mService != null) {
try {
return mService.getPreviousAudioId();
} catch (final RemoteException ignored) {
}
}
return -1;
}
public static final long getCurrentArtistId() {
if (mService != null) {
try {
return mService.getArtistId();
} catch (final RemoteException ignored) {
}
}
return -1;
}
public static final int getAudioSessionId() {
if (mService != null) {
try {
return mService.getAudioSessionId();
} catch (final RemoteException ignored) {
}
}
return -1;
}
public static final long[] getQueue() {
try {
if (mService != null) {
return mService.getQueue();
} else {
}
} catch (final RemoteException ignored) {
}
return sEmptyList;
}
public static final long getQueueItemAtPosition(int position) {
try {
if (mService != null) {
return mService.getQueueItemAtPosition(position);
} else {
}
} catch (final RemoteException ignored) {
}
return -1;
}
public static final int getQueueSize() {
try {
if (mService != null) {
return mService.getQueueSize();
} else {
}
} catch (final RemoteException ignored) {
}
return 0;
}
public static final int getQueuePosition() {
try {
if (mService != null) {
return mService.getQueuePosition();
}
} catch (final RemoteException ignored) {
}
return 0;
}
public static void setQueuePosition(final int position) {
if (mService != null) {
try {
mService.setQueuePosition(position);
} catch (final RemoteException ignored) {
}
}
}
public static void refresh() {
try {
if (mService != null) {
mService.refresh();
}
} catch (final RemoteException ignored) {
}
}
public static final int getQueueHistorySize() {
if (mService != null) {
try {
return mService.getQueueHistorySize();
} catch (final RemoteException ignored) {
}
}
return 0;
}
public static final int getQueueHistoryPosition(int position) {
if (mService != null) {
try {
return mService.getQueueHistoryPosition(position);
} catch (final RemoteException ignored) {
}
}
return -1;
}
public static final int[] getQueueHistoryList() {
if (mService != null) {
try {
return mService.getQueueHistoryList();
} catch (final RemoteException ignored) {
}
}
return null;
}
public static final int removeTrack(final long id) {
try {
if (mService != null) {
return mService.removeTrack(id);
}
} catch (final RemoteException ingored) {
}
return 0;
}
public static final boolean removeTrackAtPosition(final long id, final int position) {
try {
if (mService != null) {
return mService.removeTrackAtPosition(id, position);
}
} catch (final RemoteException ingored) {
}
return false;
}
public static void moveQueueItem(final int from, final int to) {
try {
if (mService != null) {
mService.moveQueueItem(from, to);
} else {
}
} catch (final RemoteException ignored) {
}
}
public static void playArtist(final Context context, final long artistId, int position, boolean shuffle) {
final long[] artistList = getSongListForArtist(context, artistId);
if (artistList != null) {
playAll(context, artistList, position, artistId, IdType.Artist, shuffle);
}
}
public static void playAlbum(final Context context, final long albumId, int position, boolean shuffle) {
final long[] albumList = getSongListForAlbum(context, albumId);
if (albumList != null) {
playAll(context, albumList, position, albumId, IdType.Album, shuffle);
}
}
public static void playAll(final Context context, final long[] list, int position,
final long sourceId, final IdType sourceType,
final boolean forceShuffle) {
if (list == null || list.length == 0 || mService == null) {
return;
}
try {
if (forceShuffle) {
mService.setShuffleMode(MusicService.SHUFFLE_NORMAL);
}
final long currentId = mService.getAudioId();
final int currentQueuePosition = getQueuePosition();
if (position != -1 && currentQueuePosition == position && currentId == list[position]) {
final long[] playlist = getQueue();
if (Arrays.equals(list, playlist)) {
mService.play();
return;
}
}
if (position < 0) {
position = 0;
}
mService.open(list, forceShuffle ? -1 : position, sourceId, sourceType.mId);
mService.play();
} catch (final RemoteException ignored) {
} catch (IllegalStateException e) {
e.printStackTrace();
}
}
public static void playNext(Context context, final long[] list, final long sourceId, final IdType sourceType) {
if (mService == null) {
return;
}
try {
mService.enqueue(list, MusicService.NEXT, sourceId, sourceType.mId);
final String message = makeLabel(context, R.plurals.NNNtrackstoqueue, list.length);
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
} catch (final RemoteException ignored) {
}
}
public static void shuffleAll(final Context context) {
Cursor cursor = SongLoader.makeSongCursor(context, null, null);
final long[] trackList = SongLoader.getSongListForCursor(cursor);
if (trackList.length == 0 || mService == null) {
return;
}
try {
mService.setShuffleMode(MusicService.SHUFFLE_NORMAL);
if (getQueuePosition() == 0 && mService.getAudioId() == trackList[0] && Arrays.equals(trackList, getQueue())) {
mService.play();
return;
}
mService.open(trackList, -1, -1, IdType.NA.mId);
mService.play();
cursor.close();
} catch (final RemoteException ignored) {
}
}
public static final long[] getSongListForArtist(final Context context, final long id) {
final String[] projection = new String[]{
BaseColumns._ID
};
final String selection = MediaStore.Audio.AudioColumns.ARTIST_ID + "=" + id + " AND "
+ MediaStore.Audio.AudioColumns.IS_MUSIC + "=1";
Cursor cursor = context.getContentResolver().query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection, null,
MediaStore.Audio.AudioColumns.ALBUM_KEY + "," + MediaStore.Audio.AudioColumns.TRACK);
if (cursor != null) {
final long[] mList = SongLoader.getSongListForCursor(cursor);
cursor.close();
cursor = null;
return mList;
}
return sEmptyList;
}
public static final long[] getSongListForAlbum(final Context context, final long id) {
final String[] projection = new String[]{
BaseColumns._ID
};
final String selection = MediaStore.Audio.AudioColumns.ALBUM_ID + "=" + id + " AND " + MediaStore.Audio.AudioColumns.IS_MUSIC
+ "=1";
Cursor cursor = context.getContentResolver().query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection, null,
MediaStore.Audio.AudioColumns.TRACK + ", " + MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
if (cursor != null) {
final long[] mList = SongLoader.getSongListForCursor(cursor);
cursor.close();
cursor = null;
return mList;
}
return sEmptyList;
}
public static final int getSongCountForAlbumInt(final Context context, final long id) {
int songCount = 0;
if (id == -1) {
return songCount;
}
Uri uri = ContentUris.withAppendedId(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, id);
Cursor cursor = context.getContentResolver().query(uri,
new String[]{MediaStore.Audio.AlbumColumns.NUMBER_OF_SONGS}, null, null, null);
if (cursor != null) {
cursor.moveToFirst();
if (!cursor.isAfterLast()) {
if (!cursor.isNull(0)) {
songCount = cursor.getInt(0);
}
}
cursor.close();
cursor = null;
}
return songCount;
}
public static final String getReleaseDateForAlbum(final Context context, final long id) {
if (id == -1) {
return null;
}
Uri uri = ContentUris.withAppendedId(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, id);
Cursor cursor = context.getContentResolver().query(uri, new String[]{
MediaStore.Audio.AlbumColumns.FIRST_YEAR
}, null, null, null);
String releaseDate = null;
if (cursor != null) {
cursor.moveToFirst();
if (!cursor.isAfterLast()) {
releaseDate = cursor.getString(0);
}
cursor.close();
cursor = null;
}
return releaseDate;
}
public static void seek(final long position) {
if (mService != null) {
try {
mService.seek(position);
} catch (final RemoteException ignored) {
} catch (IllegalStateException ignored) {
}
}
}
public static void seekRelative(final long deltaInMs) {
if (mService != null) {
try {
mService.seekRelative(deltaInMs);
} catch (final RemoteException ignored) {
} catch (final IllegalStateException ignored) {
}
}
}
public static final long position() {
if (mService != null) {
try {
return mService.position();
} catch (final RemoteException ignored) {
} catch (final IllegalStateException ex) {
}
}
return 0;
}
public static final long duration() {
if (mService != null) {
try {
return mService.duration();
} catch (final RemoteException ignored) {
} catch (final IllegalStateException ignored) {
}
}
return 0;
}
public static void clearQueue() {
if (mService!=null) {
try {
mService.removeTracks(0, Integer.MAX_VALUE);
} catch (final RemoteException ignored) {
}
}
}
public static void addToQueue(final Context context, final long[] list, long sourceId,
IdType sourceType) {
if (mService == null) {
return;
}
try {
mService.enqueue(list, MusicService.LAST, sourceId, sourceType.mId);
final String message = makeLabel(context, R.plurals.NNNtrackstoqueue, list.length);
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
} catch (final RemoteException ignored) {
}
}
public static final String makeLabel(final Context context, final int pluralInt,
final int number) {
return context.getResources().getQuantityString(pluralInt, number, number);
}
public static void addToPlaylist(final Context context, final long[] ids, final long playlistid) {
final int size = ids.length;
final ContentResolver resolver = context.getContentResolver();
final String[] projection = new String[]{
"max(" + "play_order" + ")",
};
final Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistid);
Cursor cursor = null;
int base = 0;
try {
cursor = resolver.query(uri, projection, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
base = cursor.getInt(0) + 1;
}
} finally {
if (cursor != null) {
cursor.close();
cursor = null;
}
}
int numinserted = 0;
for (int offSet = 0; offSet < size; offSet += 1000) {
makeInsertItems(ids, offSet, 1000, base);
numinserted += resolver.bulkInsert(uri, mContentValuesCache);
}
final String message = context.getResources().getQuantityString(
R.plurals.NNNtrackstoplaylist, numinserted, numinserted);
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
}
public static void makeInsertItems(final long[] ids, final int offset, int len, final int base) {
if (offset + len > ids.length) {
len = ids.length - offset;
}
if (mContentValuesCache == null || mContentValuesCache.length != len) {
mContentValuesCache = new ContentValues[len];
}
for (int i = 0; i < len; i++) {
if (mContentValuesCache[i] == null) {
mContentValuesCache[i] = new ContentValues();
}
mContentValuesCache[i].put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, base + offset + i);
mContentValuesCache[i].put(MediaStore.Audio.Playlists.Members.AUDIO_ID, ids[offset + i]);
}
}
public static final long createPlaylist(final Context context, final String name) {
if (name != null && name.length() > 0) {
final ContentResolver resolver = context.getContentResolver();
final String[] projection = new String[]{
MediaStore.Audio.PlaylistsColumns.NAME
};
final String selection = MediaStore.Audio.PlaylistsColumns.NAME + " = '" + name + "'";
Cursor cursor = resolver.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
projection, selection, null, null);
if (cursor.getCount() <= 0) {
final ContentValues values = new ContentValues(1);
values.put(MediaStore.Audio.PlaylistsColumns.NAME, name);
final Uri uri = resolver.insert(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
values);
return Long.parseLong(uri.getLastPathSegment());
}
if (cursor != null) {
cursor.close();
cursor = null;
}
return -1;
}
return -1;
}
public static final void openFile(final String path) {
if (mService != null) {
try {
mService.openFile(path);
} catch (final RemoteException ignored) {
}
}
}
public static final class ServiceBinder implements ServiceConnection {
private final ServiceConnection mCallback;
private final Context mContext;
public ServiceBinder(final ServiceConnection callback, final Context context) {
mCallback = callback;
mContext = context;
}
@Override
public void onServiceConnected(final ComponentName className, final IBinder service) {
mService = ITimberService.Stub.asInterface(service);
if (mCallback != null) {
mCallback.onServiceConnected(className, service);
}
initPlaybackServiceWithSettings(mContext);
}
@Override
public void onServiceDisconnected(final ComponentName className) {
if (mCallback != null) {
mCallback.onServiceDisconnected(className);
}
mService = null;
}
}
public static final class ServiceToken {
public ContextWrapper mWrappedContext;
public ServiceToken(final ContextWrapper context) {
mWrappedContext = context;
}
}
}
================================================
FILE: app/src/main/java/com/naman14/timber/MusicService.java
================================================
/*
* Copyright (C) 2012 Andrew Neal
* Copyright (C) 2014 The CyanogenMod Project
* Copyright (C) 2015 Naman Dwivedi
*
* 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.naman14.timber;
import android.Manifest;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
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.SharedPreferences;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.media.AudioManager;
import android.media.AudioManager.OnAudioFocusChangeListener;
import android.media.MediaMetadataEditor;
import android.media.MediaMetadataRetriever;
import android.media.MediaPlayer;
import android.media.RemoteControlClient;
import android.media.audiofx.AudioEffect;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.RemoteException;
import android.os.SystemClock;
import android.provider.MediaStore;
import android.provider.MediaStore.Audio.AlbumColumns;
import android.provider.MediaStore.Audio.AudioColumns;
import androidx.core.app.NotificationManagerCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import androidx.media.app.NotificationCompat;
import androidx.palette.graphics.Palette;
import android.text.TextUtils;
import android.util.Log;
import com.naman14.timber.helpers.MediaButtonIntentReceiver;
import com.naman14.timber.helpers.MusicPlaybackTrack;
import com.naman14.timber.lastfmapi.LastFmClient;
import com.naman14.timber.lastfmapi.models.LastfmUserSession;
import com.naman14.timber.lastfmapi.models.ScrobbleQuery;
import com.naman14.timber.permissions.Nammu;
import com.naman14.timber.provider.MusicPlaybackState;
import com.naman14.timber.provider.RecentStore;
import com.naman14.timber.provider.SongPlayCount;
import com.naman14.timber.utils.NavigationUtils;
import com.naman14.timber.utils.PreferencesUtility;
import com.naman14.timber.utils.TimberUtils;
import com.naman14.timber.utils.TimberUtils.IdType;
import com.nostra13.universalimageloader.core.ImageLoader;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Random;
import java.util.TreeSet;
import de.Maxr1998.trackselectorlib.ModNotInstalledException;
import de.Maxr1998.trackselectorlib.NotificationHelper;
import de.Maxr1998.trackselectorlib.TrackItem;
@SuppressLint("NewApi")
public class MusicService extends Service {
public static final String PLAYSTATE_CHANGED = "com.naman14.timber.playstatechanged";
public static final String POSITION_CHANGED = "com.naman14.timber.positionchanged";
public static final String META_CHANGED = "com.naman14.timber.metachanged";
public static final String QUEUE_CHANGED = "com.naman14.timber.queuechanged";
public static final String PLAYLIST_CHANGED = "com.naman14.timber.playlistchanged";
public static final String REPEATMODE_CHANGED = "com.naman14.timber.repeatmodechanged";
public static final String SHUFFLEMODE_CHANGED = "com.naman14.timber.shufflemodechanged";
public static final String TRACK_ERROR = "com.naman14.timber.trackerror";
public static final String TIMBER_PACKAGE_NAME = "com.naman14.timber";
public static final String MUSIC_PACKAGE_NAME = "com.android.music";
public static final String SERVICECMD = "com.naman14.timber.musicservicecommand";
public static final String TOGGLEPAUSE_ACTION = "com.naman14.timber.togglepause";
public static final String PAUSE_ACTION = "com.naman14.timber.pause";
public static final String STOP_ACTION = "com.naman14.timber.stop";
public static final String PREVIOUS_ACTION = "com.naman14.timber.previous";
public static final String PREVIOUS_FORCE_ACTION = "com.naman14.timber.previous.force";
public static final String NEXT_ACTION = "fcom.naman14.timber.next";
public static final String REPEAT_ACTION = "com.naman14.timber.repeat";
public static final String SHUFFLE_ACTION = "com.naman14.timber.shuffle";
public static final String FROM_MEDIA_BUTTON = "frommediabutton";
public static final String REFRESH = "com.naman14.timber.refresh";
public static final String UPDATE_LOCKSCREEN = "com.naman14.timber.updatelockscreen";
public static final String CMDNAME = "command";
public static final String CMDTOGGLEPAUSE = "togglepause";
public static final String CMDSTOP = "stop";
public static final String CMDPAUSE = "pause";
public static final String CMDPLAY = "play";
public static final String CMDPREVIOUS = "previous";
public static final String CMDNEXT = "next";
public static final String CMDNOTIF = "buttonId";
public static final String UPDATE_PREFERENCES = "updatepreferences";
public static final String CHANNEL_ID = "timber_channel_01";
public static final int NEXT = 2;
public static final int LAST = 3;
public static final int SHUFFLE_NONE = 0;
public static final int SHUFFLE_NORMAL = 1;
public static final int SHUFFLE_AUTO = 2;
public static final int REPEAT_NONE = 0;
public static final int REPEAT_CURRENT = 1;
public static final int REPEAT_ALL = 2;
public static final int MAX_HISTORY_SIZE = 1000;
private static final String TAG = "MusicPlaybackService";
private static final boolean D = false;
private static final String SHUTDOWN = "com.naman14.timber.shutdown";
private static final int IDCOLIDX = 0;
private static final int TRACK_ENDED = 1;
private static final int TRACK_WENT_TO_NEXT = 2;
private static final int RELEASE_WAKELOCK = 3;
private static final int SERVER_DIED = 4;
private static final int FOCUSCHANGE = 5;
private static final int FADEDOWN = 6;
private static final int FADEUP = 7;
private static final int IDLE_DELAY = 5 * 60 * 1000;
private static final long REWIND_INSTEAD_PREVIOUS_THRESHOLD = 3000;
private static final String[] PROJECTION = new String[]{
"audio._id AS _id", MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM,
MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DATA,
MediaStore.Audio.Media.MIME_TYPE, MediaStore.Audio.Media.ALBUM_ID,
MediaStore.Audio.Media.ARTIST_ID
};
private static final String[] ALBUM_PROJECTION = new String[]{
MediaStore.Audio.Albums.ALBUM, MediaStore.Audio.Albums.ARTIST,
MediaStore.Audio.Albums.LAST_YEAR
};
private static final String[] NOTIFICATION_PROJECTION = new String[]{
"audio._id AS _id", AudioColumns.ALBUM_ID, AudioColumns.TITLE,
AudioColumns.ARTIST, AudioColumns.DURATION
};
private static final Shuffler mShuffler = new Shuffler();
private static final int NOTIFY_MODE_NONE = 0;
private static final int NOTIFY_MODE_FOREGROUND = 1;
private static final int NOTIFY_MODE_BACKGROUND = 2;
private static final String[] PROJECTION_MATRIX = new String[]{
"_id", MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM,
MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DATA,
MediaStore.Audio.Media.MIME_TYPE, MediaStore.Audio.Media.ALBUM_ID,
MediaStore.Audio.Media.ARTIST_ID
};
private static LinkedList mHistory = new LinkedList<>();
private final IBinder mBinder = new ServiceStub(this);
private MultiPlayer mPlayer;
private String mFileToPlay;
private WakeLock mWakeLock;
private AlarmManager mAlarmManager;
private PendingIntent mShutdownIntent;
private boolean mShutdownScheduled;
private NotificationManagerCompat mNotificationManager;
private Cursor mCursor;
private Cursor mAlbumCursor;
private AudioManager mAudioManager;
private SharedPreferences mPreferences;
private boolean mServiceInUse = false;
private boolean mIsSupposedToBePlaying = false;
private long mLastPlayedTime;
private int mNotifyMode = NOTIFY_MODE_NONE;
private long mNotificationPostTime = 0;
private boolean mQueueIsSaveable = true;
private boolean mPausedByTransientLossOfFocus = false;
private MediaSessionCompat mSession;
@SuppressWarnings("deprecation")
private RemoteControlClient mRemoteControlClient;
private ComponentName mMediaButtonReceiverComponent;
private int mCardId;
private int mPlayPos = -1;
private int mNextPlayPos = -1;
private int mOpenFailedCounter = 0;
private int mMediaMountedCount = 0;
private int mShuffleMode = SHUFFLE_NONE;
private int mRepeatMode = REPEAT_NONE;
private int mServiceStartId = -1;
private ArrayList mPlaylist = new ArrayList(100);
private long[] mAutoShuffleList = null;
private MusicPlayerHandler mPlayerHandler;
private final OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(final int focusChange) {
mPlayerHandler.obtainMessage(FOCUSCHANGE, focusChange, 0).sendToTarget();
}
};
private HandlerThread mHandlerThread;
private BroadcastReceiver mUnmountReceiver = null;
private MusicPlaybackState mPlaybackStateStore;
private boolean mShowAlbumArtOnLockscreen;
private boolean mActivateXTrackSelector;
private SongPlayCount mSongPlayCount;
private RecentStore mRecentStore;
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
final String command = intent.getStringExtra(CMDNAME);
handleCommandIntent(intent);
}
};
private ContentObserver mMediaStoreObserver;
@Override
public IBinder onBind(final Intent intent) {
if (D) Log.d(TAG, "Service bound, intent = " + intent);
cancelShutdown();
mServiceInUse = true;
return mBinder;
}
@Override
public boolean onUnbind(final Intent intent) {
if (D) Log.d(TAG, "Service unbound");
mServiceInUse = false;
saveQueue(true);
if (mIsSupposedToBePlaying || mPausedByTransientLossOfFocus) {
return true;
} else if (mPlaylist.size() > 0 || mPlayerHandler.hasMessages(TRACK_ENDED)) {
scheduleDelayedShutdown();
return true;
}
stopSelf(mServiceStartId);
return true;
}
@Override
public void onRebind(final Intent intent) {
cancelShutdown();
mServiceInUse = true;
}
@Override
public void onCreate() {
if (D) Log.d(TAG, "Creating service");
super.onCreate();
mNotificationManager = NotificationManagerCompat.from(this);
createNotificationChannel();
// gets a pointer to the playback state store
mPlaybackStateStore = MusicPlaybackState.getInstance(this);
mSongPlayCount = SongPlayCount.getInstance(this);
mRecentStore = RecentStore.getInstance(this);
mHandlerThread = new HandlerThread("MusicPlayerHandler",
android.os.Process.THREAD_PRIORITY_BACKGROUND);
mHandlerThread.start();
mPlayerHandler = new MusicPlayerHandler(this, mHandlerThread.getLooper());
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
mMediaButtonReceiverComponent = new ComponentName(getPackageName(),
MediaButtonIntentReceiver.class.getName());
mAudioManager.registerMediaButtonEventReceiver(mMediaButtonReceiverComponent);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
setUpMediaSession();
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
setUpRemoteControlClient();
mPreferences = getSharedPreferences("Service", 0);
mCardId = getCardId();
registerExternalStorageListener();
mPlayer = new MultiPlayer(this);
mPlayer.setHandler(mPlayerHandler);
// Initialize the intent filter and each action
final IntentFilter filter = new IntentFilter();
filter.addAction(SERVICECMD);
filter.addAction(TOGGLEPAUSE_ACTION);
filter.addAction(PAUSE_ACTION);
filter.addAction(STOP_ACTION);
filter.addAction(NEXT_ACTION);
filter.addAction(PREVIOUS_ACTION);
filter.addAction(PREVIOUS_FORCE_ACTION);
filter.addAction(REPEAT_ACTION);
filter.addAction(SHUFFLE_ACTION);
filter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
filter.addAction(Intent.ACTION_SCREEN_ON);
// Attach the broadcast listener
registerReceiver(mIntentReceiver, filter);
mMediaStoreObserver = new MediaStoreObserver(mPlayerHandler);
getContentResolver().registerContentObserver(
MediaStore.Audio.Media.INTERNAL_CONTENT_URI, true, mMediaStoreObserver);
getContentResolver().registerContentObserver(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, true, mMediaStoreObserver);
// Initialize the wake lock
final PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName());
mWakeLock.setReferenceCounted(false);
final Intent shutdownIntent = new Intent(this, MusicService.class);
shutdownIntent.setAction(SHUTDOWN);
mAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
mShutdownIntent = PendingIntent.getService(this, 0, shutdownIntent, 0);
scheduleDelayedShutdown();
reloadQueueAfterPermissionCheck();
notifyChange(QUEUE_CHANGED);
notifyChange(META_CHANGED);
//Try to push LastFMCache
if (LastfmUserSession.getSession(this) != null) {
LastFmClient.getInstance(this).Scrobble(null);
}
PreferencesUtility pref = PreferencesUtility.getInstance(this);
mShowAlbumArtOnLockscreen = pref.getSetAlbumartLockscreen();
mActivateXTrackSelector = pref.getXPosedTrackselectorEnabled();
}
@SuppressWarnings("deprecation")
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
private void setUpRemoteControlClient() {
//Legacy for ICS
if (mRemoteControlClient == null) {
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
mediaButtonIntent.setComponent(mMediaButtonReceiverComponent);
PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, 0);
// create and register the remote control client
mRemoteControlClient = new RemoteControlClient(mediaPendingIntent);
mAudioManager.registerRemoteControlClient(mRemoteControlClient);
}
mRemoteControlClient.setTransportControlFlags(
RemoteControlClient.FLAG_KEY_MEDIA_PLAY |
RemoteControlClient.FLAG_KEY_MEDIA_PAUSE |
RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS |
RemoteControlClient.FLAG_KEY_MEDIA_NEXT |
RemoteControlClient.FLAG_KEY_MEDIA_STOP);
}
private void setUpMediaSession() {
mSession = new MediaSessionCompat(this, "Timber");
mSession.setCallback(new MediaSessionCompat.Callback() {
@Override
public void onPause() {
pause();
mPausedByTransientLossOfFocus = false;
}
@Override
public void onPlay() {
play();
}
@Override
public void onSeekTo(long pos) {
seek(pos);
}
@Override
public void onSkipToNext() {
gotoNext(true);
}
@Override
public void onSkipToPrevious() {
prev(false);
}
@Override
public void onStop() {
pause();
mPausedByTransientLossOfFocus = false;
seek(0);
releaseServiceUiAndStop();
}
});
mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
| MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS);
}
@Override
public void onDestroy() {
if (D) Log.d(TAG, "Destroying service");
super.onDestroy();
//Try to push LastFMCache
if (LastfmUserSession.getSession(this).isLogedin()) {
LastFmClient.getInstance(this).Scrobble(null);
}
// Remove any sound effects
final Intent audioEffectsIntent = new Intent(
AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
audioEffectsIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
audioEffectsIntent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
sendBroadcast(audioEffectsIntent);
mAlarmManager.cancel(mShutdownIntent);
mPlayerHandler.removeCallbacksAndMessages(null);
if (TimberUtils.isJellyBeanMR2())
mHandlerThread.quitSafely();
else mHandlerThread.quit();
mPlayer.release();
mPlayer = null;
mAudioManager.abandonAudioFocus(mAudioFocusListener);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
mSession.release();
getContentResolver().unregisterContentObserver(mMediaStoreObserver);
closeCursor();
unregisterReceiver(mIntentReceiver);
if (mUnmountReceiver != null) {
unregisterReceiver(mUnmountReceiver);
mUnmountReceiver = null;
}
mWakeLock.release();
}
@Override
public int onStartCommand(final Intent intent, final int flags, final int startId) {
if (D) Log.d(TAG, "Got new intent " + intent + ", startId = " + startId);
mServiceStartId = startId;
if (intent != null) {
final String action = intent.getAction();
if (SHUTDOWN.equals(action)) {
mShutdownScheduled = false;
releaseServiceUiAndStop();
return START_NOT_STICKY;
}
handleCommandIntent(intent);
}
scheduleDelayedShutdown();
if (intent != null && intent.getBooleanExtra(FROM_MEDIA_BUTTON, false)) {
MediaButtonIntentReceiver.completeWakefulIntent(intent);
}
return START_NOT_STICKY; //no sense to use START_STICKY with using startForeground
}
void scrobble() {
if (LastfmUserSession.getSession(this).isLogedin()) {
Log.d("Scrobble", "to LastFM");
String trackname = getTrackName();
if (trackname != null)
LastFmClient.getInstance(this).Scrobble(new ScrobbleQuery(getArtistName(), trackname, System.currentTimeMillis() / 1000));
}
}
private void releaseServiceUiAndStop() {
if (isPlaying()
|| mPausedByTransientLossOfFocus
|| mPlayerHandler.hasMessages(TRACK_ENDED)) {
return;
}
if (D) Log.d(TAG, "Nothing is playing anymore, releasing notification");
cancelNotification();
mAudioManager.abandonAudioFocus(mAudioFocusListener);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
mSession.setActive(false);
if (!mServiceInUse) {
saveQueue(true);
stopSelf(mServiceStartId);
}
}
private void handleCommandIntent(Intent intent) {
final String action = intent.getAction();
final String command = SERVICECMD.equals(action) ? intent.getStringExtra(CMDNAME) : null;
if (D) Log.d(TAG, "handleCommandIntent: action = " + action + ", command = " + command);
if (NotificationHelper.checkIntent(intent)) {
goToPosition(mPlayPos + NotificationHelper.getPosition(intent));
return;
}
if (CMDNEXT.equals(command) || NEXT_ACTION.equals(action)) {
gotoNext(true);
} else if (CMDPREVIOUS.equals(command) || PREVIOUS_ACTION.equals(action)
|| PREVIOUS_FORCE_ACTION.equals(action)) {
prev(PREVIOUS_FORCE_ACTION.equals(action));
} else if (CMDTOGGLEPAUSE.equals(command) || TOGGLEPAUSE_ACTION.equals(action)) {
if (isPlaying()) {
pause();
mPausedByTransientLossOfFocus = false;
} else {
play();
}
} else if (CMDPAUSE.equals(command) || PAUSE_ACTION.equals(action)) {
pause();
mPausedByTransientLossOfFocus = false;
} else if (CMDPLAY.equals(command)) {
play();
} else if (CMDSTOP.equals(command) || STOP_ACTION.equals(action)) {
pause();
mPausedByTransientLossOfFocus = false;
seek(0);
releaseServiceUiAndStop();
} else if (REPEAT_ACTION.equals(action)) {
cycleRepeat();
} else if (SHUFFLE_ACTION.equals(action)) {
cycleShuffle();
} else if (UPDATE_PREFERENCES.equals(action)) {
onPreferencesUpdate(intent.getExtras());
}
else if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(action)) {
if (PreferencesUtility.getInstance(getApplicationContext()).pauseEnabledOnDetach()) {
pause();
}
}
}
private void onPreferencesUpdate(Bundle extras) {
mShowAlbumArtOnLockscreen = extras.getBoolean("lockscreen", mShowAlbumArtOnLockscreen);
mActivateXTrackSelector = extras.getBoolean("xtrack",mActivateXTrackSelector);
LastfmUserSession session = LastfmUserSession.getSession(this);
session.mToken = extras.getString("lf_token", session.mToken);
session.mUsername = extras.getString("lf_user", session.mUsername);
if ("logout".equals(session.mToken)) {
session.mToken = null;
session.mUsername = null;
}
notifyChange(META_CHANGED);
}
private void updateNotification() {
final int newNotifyMode;
if (isPlaying()) {
newNotifyMode = NOTIFY_MODE_FOREGROUND;
} else if (recentlyPlayed()) {
newNotifyMode = NOTIFY_MODE_BACKGROUND;
} else {
newNotifyMode = NOTIFY_MODE_NONE;
}
int notificationId = hashCode();
if (mNotifyMode != newNotifyMode) {
if (mNotifyMode == NOTIFY_MODE_FOREGROUND) {
if (TimberUtils.isLollipop())
stopForeground(newNotifyMode == NOTIFY_MODE_NONE);
else
stopForeground(newNotifyMode == NOTIFY_MODE_NONE || newNotifyMode == NOTIFY_MODE_BACKGROUND);
} else if (newNotifyMode == NOTIFY_MODE_NONE) {
mNotificationManager.cancel(notificationId);
mNotificationPostTime = 0;
}
}
if (newNotifyMode == NOTIFY_MODE_FOREGROUND) {
startForeground(notificationId, buildNotification());
} else if (newNotifyMode == NOTIFY_MODE_BACKGROUND) {
mNotificationManager.notify(notificationId, buildNotification());
}
mNotifyMode = newNotifyMode;
}
private void cancelNotification() {
stopForeground(true);
mNotificationManager.cancel(hashCode());
mNotificationPostTime = 0;
mNotifyMode = NOTIFY_MODE_NONE;
}
private int getCardId() {
if (TimberUtils.isMarshmallow()) {
if (Nammu.checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE)) {
return getmCardId();
} else return 0;
} else {
return getmCardId();
}
}
private int getmCardId() {
final ContentResolver resolver = getContentResolver();
Cursor cursor = resolver.query(Uri.parse("content://media/external/fs_id"), null, null,
null, null);
int mCardId = -1;
if (cursor != null && cursor.moveToFirst()) {
mCardId = cursor.getInt(0);
cursor.close();
cursor = null;
}
return mCardId;
}
public void closeExternalStorageFiles(final String storagePath) {
stop(true);
notifyChange(QUEUE_CHANGED);
notifyChange(META_CHANGED);
}
public void registerExternalStorageListener() {
if (mUnmountReceiver == null) {
mUnmountReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
final String action = intent.getAction();
if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
saveQueue(true);
mQueueIsSaveable = false;
closeExternalStorageFiles(intent.getData().getPath());
} else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
mMediaMountedCount++;
mCardId = getCardId();
reloadQueueAfterPermissionCheck();
mQueueIsSaveable = true;
notifyChange(QUEUE_CHANGED);
notifyChange(META_CHANGED);
}
}
};
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_MEDIA_EJECT);
filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
filter.addDataScheme("file");
registerReceiver(mUnmountReceiver, filter);
}
}
private void scheduleDelayedShutdown() {
if (D) Log.v(TAG, "Scheduling shutdown in " + IDLE_DELAY + " ms");
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + IDLE_DELAY, mShutdownIntent);
mShutdownScheduled = true;
}
private void cancelShutdown() {
if (D) Log.d(TAG, "Cancelling delayed shutdown, scheduled = " + mShutdownScheduled);
if (mShutdownScheduled) {
mAlarmManager.cancel(mShutdownIntent);
mShutdownScheduled = false;
}
}
private void stop(final boolean goToIdle) {
if (D) Log.d(TAG, "Stopping playback, goToIdle = " + goToIdle);
long duration = this.duration();
long position = this.position();
if (duration > 30000 && (position >= duration / 2) || position > 240000) {
scrobble();
}
if (mPlayer.isInitialized()) {
mPlayer.stop();
}
mFileToPlay = null;
closeCursor();
if (goToIdle) {
setIsSupposedToBePlaying(false, false);
} else {
if (TimberUtils.isLollipop())
stopForeground(false);
else stopForeground(true);
}
}
private int removeTracksInternal(int first, int last) {
synchronized (this) {
if (last < first) {
return 0;
} else if (first < 0) {
first = 0;
} else if (last >= mPlaylist.size()) {
last = mPlaylist.size() - 1;
}
boolean gotonext = false;
if (first <= mPlayPos && mPlayPos <= last) {
mPlayPos = first;
gotonext = true;
} else if (mPlayPos > last) {
mPlayPos -= last - first + 1;
}
final int numToRemove = last - first + 1;
if (first == 0 && last == mPlaylist.size() - 1) {
mPlayPos = -1;
mNextPlayPos = -1;
mPlaylist.clear();
mHistory.clear();
} else {
for (int i = 0; i < numToRemove; i++) {
mPlaylist.remove(first);
}
ListIterator positionIterator = mHistory.listIterator();
while (positionIterator.hasNext()) {
int pos = positionIterator.next();
if (pos >= first && pos <= last) {
positionIterator.remove();
} else if (pos > last) {
positionIterator.set(pos - numToRemove);
}
}
}
if (gotonext) {
if (mPlaylist.size() == 0) {
stop(true);
mPlayPos = -1;
closeCursor();
} else {
if (mShuffleMode != SHUFFLE_NONE) {
mPlayPos = getNextPosition(true);
} else if (mPlayPos >= mPlaylist.size()) {
mPlayPos = 0;
}
final boolean wasPlaying = isPlaying();
stop(false);
openCurrentAndNext();
if (wasPlaying) {
play();
}
}
notifyChange(META_CHANGED);
}
return last - first + 1;
}
}
private void addToPlayList(final long[] list, int position, long sourceId, TimberUtils.IdType sourceType) {
final int addlen = list.length;
if (position < 0) {
mPlaylist.clear();
position = 0;
}
mPlaylist.ensureCapacity(mPlaylist.size() + addlen);
if (position > mPlaylist.size()) {
position = mPlaylist.size();
}
final ArrayList arrayList = new ArrayList(addlen);
for (int i = 0; i < list.length; i++) {
arrayList.add(new MusicPlaybackTrack(list[i], sourceId, sourceType, i));
}
mPlaylist.addAll(position, arrayList);
if (mPlaylist.size() == 0) {
closeCursor();
notifyChange(META_CHANGED);
}
}
private void updateCursor(final long trackId) {
updateCursor("_id=" + trackId, null);
}
private void updateCursor(final String selection, final String[] selectionArgs) {
synchronized (this) {
closeCursor();
mCursor = openCursorAndGoToFirst(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
PROJECTION, selection, selectionArgs);
}
updateAlbumCursor();
}
private void updateCursor(final Uri uri) {
synchronized (this) {
closeCursor();
mCursor = openCursorAndGoToFirst(uri, PROJECTION, null, null);
}
updateAlbumCursor();
}
private void updateAlbumCursor() {
long albumId = getAlbumId();
if (albumId >= 0) {
mAlbumCursor = openCursorAndGoToFirst(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
ALBUM_PROJECTION, "_id=" + albumId, null);
} else {
mAlbumCursor = null;
}
}
private Cursor openCursorAndGoToFirst(Uri uri, String[] projection,
String selection, String[] selectionArgs) {
Cursor c = getContentResolver().query(uri, projection,
selection, selectionArgs, null);
if (c == null) {
return null;
}
if (!c.moveToFirst()) {
c.close();
return null;
}
return c;
}
private synchronized void closeCursor() {
if (mCursor != null) {
mCursor.close();
mCursor = null;
}
if (mAlbumCursor != null) {
mAlbumCursor.close();
mAlbumCursor = null;
}
}
private void openCurrentAndNext() {
openCurrentAndMaybeNext(true);
}
private void openCurrentAndMaybeNext(final boolean openNext) {
synchronized (this) {
closeCursor();
if (mPlaylist.size() == 0) {
return;
}
stop(false);
boolean shutdown = false;
updateCursor(mPlaylist.get(mPlayPos).mId);
while (true) {
if (mCursor != null
&& openFile(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/"
+ mCursor.getLong(IDCOLIDX))) {
break;
}
closeCursor();
if (mOpenFailedCounter++ < 10 && mPlaylist.size() > 1) {
final int pos = getNextPosition(false);
if (pos < 0) {
shutdown = true;
break;
}
mPlayPos = pos;
stop(false);
mPlayPos = pos;
updateCursor(mPlaylist.get(mPlayPos).mId);
} else {
mOpenFailedCounter = 0;
Log.w(TAG, "Failed to open file for playback");
shutdown = true;
break;
}
}
if (shutdown) {
scheduleDelayedShutdown();
if (mIsSupposedToBePlaying) {
mIsSupposedToBePlaying = false;
notifyChange(PLAYSTATE_CHANGED);
}
} else if (openNext) {
setNextTrack();
}
}
}
private void sendErrorMessage(final String trackName) {
final Intent i = new Intent(TRACK_ERROR);
i.putExtra(TrackErrorExtra.TRACK_NAME, trackName);
sendBroadcast(i);
}
private int getNextPosition(final boolean force) {
if (mPlaylist == null || mPlaylist.isEmpty()) {
return -1;
}
if (!force && mRepeatMode == REPEAT_CURRENT) {
if (mPlayPos < 0) {
return 0;
}
return mPlayPos;
} else if (mShuffleMode == SHUFFLE_NORMAL) {
final int numTracks = mPlaylist.size();
final int[] trackNumPlays = new int[numTracks];
for (int i = 0; i < numTracks; i++) {
trackNumPlays[i] = 0;
}
final int numHistory = mHistory.size();
for (int i = 0; i < numHistory; i++) {
final int idx = mHistory.get(i).intValue();
if (idx >= 0 && idx < numTracks) {
trackNumPlays[idx]++;
}
}
if (mPlayPos >= 0 && mPlayPos < numTracks) {
trackNumPlays[mPlayPos]++;
}
int minNumPlays = Integer.MAX_VALUE;
int numTracksWithMinNumPlays = 0;
for (int i = 0; i < trackNumPlays.length; i++) {
if (trackNumPlays[i] < minNumPlays) {
minNumPlays = trackNumPlays[i];
numTracksWithMinNumPlays = 1;
} else if (trackNumPlays[i] == minNumPlays) {
numTracksWithMinNumPlays++;
}
}
if (minNumPlays > 0 && numTracksWithMinNumPlays == numTracks
&& mRepeatMode != REPEAT_ALL && !force) {
return -1;
}
int skip = mShuffler.nextInt(numTracksWithMinNumPlays);
for (int i = 0; i < trackNumPlays.length; i++) {
if (trackNumPlays[i] == minNumPlays) {
if (skip == 0) {
return i;
} else {
skip--;
}
}
}
if (D)
Log.e(TAG, "Getting the next position resulted did not get a result when it should have");
return -1;
} else if (mShuffleMode == SHUFFLE_AUTO) {
doAutoShuffleUpdate();
return mPlayPos + 1;
} else {
if (mPlayPos >= mPlaylist.size() - 1) {
if (mRepeatMode == REPEAT_NONE && !force) {
return -1;
} else if (mRepeatMode == REPEAT_ALL || force) {
return 0;
}
return -1;
} else {
return mPlayPos + 1;
}
}
}
private void setNextTrack() {
setNextTrack(getNextPosition(false));
}
private void setNextTrack(int position) {
mNextPlayPos = position;
if (D) Log.d(TAG, "setNextTrack: next play position = " + mNextPlayPos);
if (mNextPlayPos >= 0 && mPlaylist != null && mNextPlayPos < mPlaylist.size()) {
final long id = mPlaylist.get(mNextPlayPos).mId;
mPlayer.setNextDataSource(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id);
} else {
mPlayer.setNextDataSource(null);
}
}
private boolean makeAutoShuffleList() {
Cursor cursor = null;
try {
cursor = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
new String[]{
MediaStore.Audio.Media._ID
}, MediaStore.Audio.Media.IS_MUSIC + "=1", null, null);
if (cursor == null || cursor.getCount() == 0) {
return false;
}
final int len = cursor.getCount();
final long[] list = new long[len];
for (int i = 0; i < len; i++) {
cursor.moveToNext();
list[i] = cursor.getLong(0);
}
mAutoShuffleList = list;
return true;
} catch (final RuntimeException e) {
} finally {
if (cursor != null) {
cursor.close();
cursor = null;
}
}
return false;
}
private void doAutoShuffleUpdate() {
boolean notify = false;
if (mPlayPos > 10) {
removeTracks(0, mPlayPos - 9);
notify = true;
}
final int toAdd = 7 - (mPlaylist.size() - (mPlayPos < 0 ? -1 : mPlayPos));
for (int i = 0; i < toAdd; i++) {
int lookback = mHistory.size();
int idx = -1;
while (true) {
idx = mShuffler.nextInt(mAutoShuffleList.length);
if (!wasRecentlyUsed(idx, lookback)) {
break;
}
lookback /= 2;
}
mHistory.add(idx);
if (mHistory.size() > MAX_HISTORY_SIZE) {
mHistory.remove(0);
}
mPlaylist.add(new MusicPlaybackTrack(mAutoShuffleList[idx], -1, TimberUtils.IdType.NA, -1));
notify = true;
}
if (notify) {
notifyChange(QUEUE_CHANGED);
}
}
private boolean wasRecentlyUsed(final int idx, int lookbacksize) {
if (lookbacksize == 0) {
return false;
}
final int histsize = mHistory.size();
if (histsize < lookbacksize) {
lookbacksize = histsize;
}
final int maxidx = histsize - 1;
for (int i = 0; i < lookbacksize; i++) {
final long entry = mHistory.get(maxidx - i);
if (entry == idx) {
return true;
}
}
return false;
}
private void notifyChange(final String what) {
if (D) Log.d(TAG, "notifyChange: what = " + what);
// Update the lockscreen controls
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
updateMediaSession(what);
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
updateRemoteControlClient(what);
if (what.equals(POSITION_CHANGED)) {
return;
}
final Intent intent = new Intent(what);
intent.putExtra("id", getAudioId());
intent.putExtra("artist", getArtistName());
intent.putExtra("album", getAlbumName());
intent.putExtra("albumid", getAlbumId());
intent.putExtra("track", getTrackName());
intent.putExtra("playing", isPlaying());
sendStickyBroadcast(intent);
final Intent musicIntent = new Intent(intent);
musicIntent.setAction(what.replace(TIMBER_PACKAGE_NAME, MUSIC_PACKAGE_NAME));
sendStickyBroadcast(musicIntent);
if (what.equals(META_CHANGED)) {
mRecentStore.addSongId(getAudioId());
mSongPlayCount.bumpSongCount(getAudioId());
} else if (what.equals(QUEUE_CHANGED)) {
saveQueue(true);
if (isPlaying()) {
if (mNextPlayPos >= 0 && mNextPlayPos < mPlaylist.size()
&& getShuffleMode() != SHUFFLE_NONE) {
setNextTrack(mNextPlayPos);
} else {
setNextTrack();
}
}
} else {
saveQueue(false);
}
if (what.equals(PLAYSTATE_CHANGED)) {
updateNotification();
}
}
@SuppressWarnings("deprecation")
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
private void updateRemoteControlClient(final String what) {
//Legacy for ICS
if (mRemoteControlClient != null) {
int playState = mIsSupposedToBePlaying
? RemoteControlClient.PLAYSTATE_PLAYING
: RemoteControlClient.PLAYSTATE_PAUSED;
if (what.equals(META_CHANGED) || what.equals(QUEUE_CHANGED)) {
Bitmap albumArt = null;
if (mShowAlbumArtOnLockscreen) {
albumArt = ImageLoader.getInstance().loadImageSync(TimberUtils.getAlbumArtUri(getAlbumId()).toString());
if (albumArt != null) {
Bitmap.Config config = albumArt.getConfig();
if (config == null) {
config = Bitmap.Config.ARGB_8888;
}
albumArt = albumArt.copy(config, false);
}
}
RemoteControlClient.MetadataEditor editor = mRemoteControlClient.editMetadata(true);
editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, getAlbumName());
editor.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, getArtistName());
editor.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, getTrackName());
editor.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, duration());
editor.putBitmap(MediaMetadataEditor.BITMAP_KEY_ARTWORK, albumArt);
editor.apply();
}
mRemoteControlClient.setPlaybackState(playState);
}
}
private void updateMediaSession(final String what) {
int playState = mIsSupposedToBePlaying
? PlaybackStateCompat.STATE_PLAYING
: PlaybackStateCompat.STATE_PAUSED;
if (what.equals(PLAYSTATE_CHANGED) || what.equals(POSITION_CHANGED)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mSession.setPlaybackState(new PlaybackStateCompat.Builder()
.setState(playState, position(), 1.0f)
.setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_PLAY_PAUSE |
PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)
.build());
}
} else if (what.equals(META_CHANGED) || what.equals(QUEUE_CHANGED)) {
Bitmap albumArt = null;
if (mShowAlbumArtOnLockscreen) {
albumArt = ImageLoader.getInstance().loadImageSync(TimberUtils.getAlbumArtUri(getAlbumId()).toString());
if (albumArt != null) {
Bitmap.Config config = albumArt.getConfig();
if (config == null) {
config = Bitmap.Config.ARGB_8888;
}
albumArt = albumArt.copy(config, false);
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mSession.setMetadata(new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, getArtistName())
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, getAlbumArtistName())
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, getAlbumName())
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, getTrackName())
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration())
.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, getQueuePosition() + 1)
.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, getQueue().length)
.putString(MediaMetadataCompat.METADATA_KEY_GENRE, getGenreName())
.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArt)
.build());
mSession.setPlaybackState(new PlaybackStateCompat.Builder()
.setState(playState, position(), 1.0f)
.setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_PLAY_PAUSE |
PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)
.build());
}
}
}
private void createNotificationChannel() {
if (TimberUtils.isOreo()) {
CharSequence name = "Timber";
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID, name, importance);
manager.createNotificationChannel(mChannel);
}
}
private Notification buildNotification() {
final String albumName = getAlbumName();
final String artistName = getArtistName();
final boolean isPlaying = isPlaying();
String text = TextUtils.isEmpty(albumName)
? artistName : artistName + " - " + albumName;
int playButtonResId = isPlaying
? R.drawable.ic_pause_white_36dp : R.drawable.ic_play_white_36dp;
Intent nowPlayingIntent = NavigationUtils.getNowPlayingIntent(this);
PendingIntent clickIntent = PendingIntent.getActivity(this, 0, nowPlayingIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Bitmap artwork;
artwork = ImageLoader.getInstance().loadImageSync(TimberUtils.getAlbumArtUri(getAlbumId()).toString());
if (artwork == null) {
artwork = ImageLoader.getInstance().loadImageSync("drawable://" + R.drawable.ic_empty_music2);
}
if (mNotificationPostTime == 0) {
mNotificationPostTime = System.currentTimeMillis();
}
androidx.core.app.NotificationCompat.Builder builder = new androidx.core.app.NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setLargeIcon(artwork)
.setContentIntent(clickIntent)
.setContentTitle(getTrackName())
.setContentText(text)
.setWhen(mNotificationPostTime)
.addAction(R.drawable.ic_skip_previous_white_36dp,
"",
retrievePlaybackAction(PREVIOUS_ACTION))
.addAction(playButtonResId, "",
retrievePlaybackAction(TOGGLEPAUSE_ACTION))
.addAction(R.drawable.ic_skip_next_white_36dp,
"",
retrievePlaybackAction(NEXT_ACTION));
if (TimberUtils.isJellyBeanMR1()) {
builder.setShowWhen(false);
}
if (TimberUtils.isLollipop()) {
builder.setVisibility(Notification.VISIBILITY_PUBLIC);
NotificationCompat.MediaStyle style = new NotificationCompat.MediaStyle()
.setMediaSession(mSession.getSessionToken())
.setShowActionsInCompactView(0, 1, 2, 3);
builder.setStyle(style);
}
if (artwork != null && TimberUtils.isLollipop()) {
builder.setColor(Palette.from(artwork).generate().getVibrantColor(Color.parseColor("#403f4d")));
}
if (TimberUtils.isOreo()) {
builder.setColorized(true);
}
Notification n = builder.build();
if (mActivateXTrackSelector) {
addXTrackSelector(n);
}
return n;
}
private void addXTrackSelector(Notification n) {
if (NotificationHelper.isSupported(n)) {
StringBuilder selection = new StringBuilder();
StringBuilder order = new StringBuilder().append("CASE _id \n");
for (int i = 0; i < mPlaylist.size(); i++) {
selection.append("_id=").append(mPlaylist.get(i).mId).append(" OR ");
order.append("WHEN ").append(mPlaylist.get(i).mId).append(" THEN ").append(i).append("\n");
}
order.append("END");
Cursor c = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, NOTIFICATION_PROJECTION, selection.substring(0, selection.length() - 3), null, order.toString());
if (c != null && c.getCount() != 0) {
c.moveToFirst();
ArrayList list = new ArrayList<>();
do {
TrackItem t = new TrackItem()
.setArt(TimberUtils.getAlbumArtUri(c.getLong(c.getColumnIndexOrThrow(AudioColumns.ALBUM_ID))))
.setTitle(c.getString(c.getColumnIndexOrThrow(AudioColumns.TITLE)))
.setArtist(c.getString(c.getColumnIndexOrThrow(AudioColumns.ARTIST)))
.setDuration(TimberUtils.makeShortTimeString(this, c.getInt(c.getColumnIndexOrThrow(AudioColumns.DURATION)) / 1000));
list.add(t.get());
} while (c.moveToNext());
try {
NotificationHelper.insertToNotification(n, list, this, getQueuePosition());
} catch (ModNotInstalledException e) {
e.printStackTrace();
}
c.close();
}
}
}
private final PendingIntent retrievePlaybackAction(final String action) {
final ComponentName serviceName = new ComponentName(this, MusicService.class);
Intent intent = new Intent(action);
intent.setComponent(serviceName);
return PendingIntent.getService(this, 0, intent, 0);
}
private void saveQueue(final boolean full) {
if (!mQueueIsSaveable) {
return;
}
final SharedPreferences.Editor editor = mPreferences.edit();
if (full) {
mPlaybackStateStore.saveState(mPlaylist,
mShuffleMode != SHUFFLE_NONE ? mHistory : null);
editor.putInt("cardid", mCardId);
}
editor.putInt("curpos", mPlayPos);
if (mPlayer.isInitialized()) {
editor.putLong("seekpos", mPlayer.position());
}
editor.putInt("repeatmode", mRepeatMode);
editor.putInt("shufflemode", mShuffleMode);
editor.apply();
}
private void reloadQueueAfterPermissionCheck() {
if (TimberUtils.isMarshmallow()) {
if (Nammu.checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE)) {
reloadQueue();
}
} else {
reloadQueue();
}
}
private void reloadQueue() {
int id = mCardId;
if (mPreferences.contains("cardid")) {
id = mPreferences.getInt("cardid", ~mCardId);
}
if (id == mCardId) {
mPlaylist = mPlaybackStateStore.getQueue();
}
if (mPlaylist.size() > 0) {
final int pos = mPreferences.getInt("curpos", 0);
if (pos < 0 || pos >= mPlaylist.size()) {
mPlaylist.clear();
return;
}
mPlayPos = pos;
updateCursor(mPlaylist.get(mPlayPos).mId);
if (mCursor == null) {
SystemClock.sleep(3000);
updateCursor(mPlaylist.get(mPlayPos).mId);
}
synchronized (this) {
closeCursor();
mOpenFailedCounter = 20;
openCurrentAndNext();
}
if (!mPlayer.isInitialized()) {
mPlaylist.clear();
return;
}
final long seekpos = mPreferences.getLong("seekpos", 0);
seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
if (D) {
Log.d(TAG, "restored queue, currently at position "
+ position() + "/" + duration()
+ " (requested " + seekpos + ")");
}
int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
repmode = REPEAT_NONE;
}
mRepeatMode = repmode;
int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
shufmode = SHUFFLE_NONE;
}
if (shufmode != SHUFFLE_NONE) {
mHistory = mPlaybackStateStore.getHistory(mPlaylist.size());
}
if (shufmode == SHUFFLE_AUTO) {
if (!makeAutoShuffleList()) {
shufmode = SHUFFLE_NONE;
}
}
mShuffleMode = shufmode;
}
}
public boolean openFile(final String path) {
if (D) Log.d(TAG, "openFile: path = " + path);
synchronized (this) {
if (path == null) {
return false;
}
if (mCursor == null) {
Uri uri = Uri.parse(path);
boolean shouldAddToPlaylist = true;
long id = -1;
try {
id = Long.valueOf(uri.getLastPathSegment());
} catch (NumberFormatException ex) {
// Ignore
}
if (id != -1 && path.startsWith(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString())) {
updateCursor(uri);
} else if (id != -1 && path.startsWith(
MediaStore.Files.getContentUri("external").toString())) {
updateCursor(id);
} else if (path.startsWith("content://downloads/")) {
String mpUri = getValueForDownloadedFile(this, uri, "mediaprovider_uri");
if (D) Log.i(TAG, "Downloaded file's MP uri : " + mpUri);
if (!TextUtils.isEmpty(mpUri)) {
if (openFile(mpUri)) {
notifyChange(META_CHANGED);
return true;
} else {
return false;
}
} else {
updateCursorForDownloadedFile(this, uri);
shouldAddToPlaylist = false;
}
} else {
String where = MediaStore.Audio.Media.DATA + "=?";
String[] selectionArgs = new String[]{path};
updateCursor(where, selectionArgs);
}
try {
if (mCursor != null && shouldAddToPlaylist) {
mPlaylist.clear();
mPlaylist.add(new MusicPlaybackTrack(
mCursor.getLong(IDCOLIDX), -1, TimberUtils.IdType.NA, -1));
notifyChange(QUEUE_CHANGED);
mPlayPos = 0;
mHistory.clear();
}
} catch (final UnsupportedOperationException ex) {
// Ignore
}
}
mFileToPlay = path;
mPlayer.setDataSource(mFileToPlay);
if (mPlayer.isInitialized()) {
mOpenFailedCounter = 0;
return true;
}
String trackName = getTrackName();
if (TextUtils.isEmpty(trackName)) {
trackName = path;
}
sendErrorMessage(trackName);
stop(true);
return false;
}
}
private void updateCursorForDownloadedFile(Context context, Uri uri) {
synchronized (this) {
closeCursor();
MatrixCursor cursor = new MatrixCursor(PROJECTION_MATRIX);
String title = getValueForDownloadedFile(this, uri, "title");
cursor.addRow(new Object[]{
null,
null,
null,
title,
null,
null,
null,
null
});
mCursor = cursor;
mCursor.moveToFirst();
}
}
private String getValueForDownloadedFile(Context context, Uri uri, String column) {
Cursor cursor = null;
final String[] projection = {
column
};
try {
cursor = context.getContentResolver().query(uri, projection, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
return cursor.getString(0);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return null;
}
public int getAudioSessionId() {
synchronized (this) {
return mPlayer.getAudioSessionId();
}
}
public int getMediaMountedCount() {
return mMediaMountedCount;
}
public int getShuffleMode() {
return mShuffleMode;
}
public void setShuffleMode(final int shufflemode) {
synchronized (this) {
if (mShuffleMode == shufflemode && mPlaylist.size() > 0) {
return;
}
mShuffleMode = shufflemode;
if (mShuffleMode == SHUFFLE_AUTO) {
if (makeAutoShuffleList()) {
mPlaylist.clear();
doAutoShuffleUpdate();
mPlayPos = 0;
openCurrentAndNext();
play();
notifyChange(META_CHANGED);
return;
} else {
mShuffleMode = SHUFFLE_NONE;
}
} else {
setNextTrack();
}
saveQueue(false);
notifyChange(SHUFFLEMODE_CHANGED);
}
}
public int getRepeatMode() {
return mRepeatMode;
}
public void setRepeatMode(final int repeatmode) {
synchronized (this) {
mRepeatMode = repeatmode;
setNextTrack();
saveQueue(false);
notifyChange(REPEATMODE_CHANGED);
}
}
public int removeTrack(final long id) {
int numremoved = 0;
synchronized (this) {
for (int i = 0; i < mPlaylist.size(); i++) {
if (mPlaylist.get(i).mId == id) {
numremoved += removeTracksInternal(i, i);
i--;
}
}
}
if (numremoved > 0) {
notifyChange(QUEUE_CHANGED);
}
return numremoved;
}
public boolean removeTrackAtPosition(final long id, final int position) {
synchronized (this) {
if (position >= 0 &&
position < mPlaylist.size() &&
mPlaylist.get(position).mId == id) {
return removeTracks(position, position) > 0;
}
}
return false;
}
public int removeTracks(final int first, final int last) {
final int numremoved = removeTracksInternal(first, last);
if (numremoved > 0) {
notifyChange(QUEUE_CHANGED);
}
return numremoved;
}
public int getQueuePosition() {
synchronized (this) {
return mPlayPos;
}
}
public void setQueuePosition(final int index) {
synchronized (this) {
stop(false);
mPlayPos = index;
openCurrentAndNext();
play();
notifyChange(META_CHANGED);
if (mShuffleMode == SHUFFLE_AUTO) {
doAutoShuffleUpdate();
}
}
}
public int getQueueHistorySize() {
synchronized (this) {
return mHistory.size();
}
}
public int getQueueHistoryPosition(int position) {
synchronized (this) {
if (position >= 0 && position < mHistory.size()) {
return mHistory.get(position);
}
}
return -1;
}
public int[] getQueueHistoryList() {
synchronized (this) {
int[] history = new int[mHistory.size()];
for (int i = 0; i < mHistory.size(); i++) {
history[i] = mHistory.get(i);
}
return history;
}
}
public String getPath() {
synchronized (this) {
if (mCursor == null) {
return null;
}
return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.DATA));
}
}
public String getAlbumName() {
synchronized (this) {
if (mCursor == null) {
return null;
}
return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.ALBUM));
}
}
public String getTrackName() {
synchronized (this) {
if (mCursor == null) {
return null;
}
return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.TITLE));
}
}
public String getGenreName() {
synchronized (this) {
if (mCursor == null || mPlayPos < 0 || mPlayPos >= mPlaylist.size()) {
return null;
}
String[] genreProjection = {MediaStore.Audio.Genres.NAME};
Uri genreUri = MediaStore.Audio.Genres.getContentUriForAudioId("external",
(int) mPlaylist.get(mPlayPos).mId);
Cursor genreCursor = getContentResolver().query(genreUri, genreProjection,
null, null, null);
if (genreCursor != null) {
try {
if (genreCursor.moveToFirst()) {
return genreCursor.getString(
genreCursor.getColumnIndexOrThrow(MediaStore.Audio.Genres.NAME));
}
} finally {
genreCursor.close();
}
}
return null;
}
}
public String getArtistName() {
synchronized (this) {
if (mCursor == null) {
return null;
}
return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.ARTIST));
}
}
public String getAlbumArtistName() {
synchronized (this) {
if (mAlbumCursor == null) {
return null;
}
return mAlbumCursor.getString(mAlbumCursor.getColumnIndexOrThrow(AlbumColumns.ARTIST));
}
}
public long getAlbumId() {
synchronized (this) {
if (mCursor == null) {
return -1;
}
return mCursor.getLong(mCursor.getColumnIndexOrThrow(AudioColumns.ALBUM_ID));
}
}
public long getArtistId() {
synchronized (this) {
if (mCursor == null) {
return -1;
}
return mCursor.getLong(mCursor.getColumnIndexOrThrow(AudioColumns.ARTIST_ID));
}
}
public long getAudioId() {
MusicPlaybackTrack track = getCurrentTrack();
if (track != null) {
return track.mId;
}
return -1;
}
public MusicPlaybackTrack getCurrentTrack() {
return getTrack(mPlayPos);
}
public synchronized MusicPlaybackTrack getTrack(int index) {
if (index >= 0 && index < mPlaylist.size() && mPlayer.isInitialized()) {
return mPlaylist.get(index);
}
return null;
}
public long getNextAudioId() {
synchronized (this) {
if (mNextPlayPos >= 0 && mNextPlayPos < mPlaylist.size() && mPlayer.isInitialized()) {
return mPlaylist.get(mNextPlayPos).mId;
}
}
return -1;
}
public long getPreviousAudioId() {
synchronized (this) {
if (mPlayer.isInitialized()) {
int pos = getPreviousPlayPosition(false);
if (pos >= 0 && pos < mPlaylist.size()) {
return mPlaylist.get(pos).mId;
}
}
}
return -1;
}
public long seek(long position) {
if (mPlayer.isInitialized()) {
if (position < 0) {
position = 0;
} else if (position > mPlayer.duration()) {
position = mPlayer.duration();
}
long result = mPlayer.seek(position);
notifyChange(POSITION_CHANGED);
return result;
}
return -1;
}
public void seekRelative(long deltaInMs) {
synchronized (this) {
if (mPlayer.isInitialized()) {
final long newPos = position() + deltaInMs;
final long duration = duration();
if (newPos < 0) {
prev(true);
// seek to the new duration + the leftover position
seek(duration() + newPos);
} else if (newPos >= duration) {
gotoNext(true);
// seek to the leftover duration
seek(newPos - duration);
} else {
seek(newPos);
}
}
}
}
public long position() {
if (mPlayer.isInitialized()) {
return mPlayer.position();
}
return -1;
}
public long duration() {
if (mPlayer.isInitialized()) {
return mPlayer.duration();
}
return -1;
}
public long[] getQueue() {
synchronized (this) {
final int len = mPlaylist.size();
final long[] list = new long[len];
for (int i = 0; i < len; i++) {
list[i] = mPlaylist.get(i).mId;
}
return list;
}
}
public long getQueueItemAtPosition(int position) {
synchronized (this) {
if (position >= 0 && position < mPlaylist.size()) {
return mPlaylist.get(position).mId;
}
}
return -1;
}
public int getQueueSize() {
synchronized (this) {
return mPlaylist.size();
}
}
public boolean isPlaying() {
return mIsSupposedToBePlaying;
}
private void setIsSupposedToBePlaying(boolean value, boolean notify) {
if (mIsSupposedToBePlaying != value) {
mIsSupposedToBePlaying = value;
if (!mIsSupposedToBePlaying) {
scheduleDelayedShutdown();
mLastPlayedTime = System.currentTimeMillis();
}
if (notify) {
notifyChange(PLAYSTATE_CHANGED);
}
}
}
private boolean recentlyPlayed() {
return isPlaying() || System.currentTimeMillis() - mLastPlayedTime < IDLE_DELAY;
}
public void open(final long[] list, final int position, long sourceId, TimberUtils.IdType sourceType) {
synchronized (this) {
if (mShuffleMode == SHUFFLE_AUTO) {
mShuffleMode = SHUFFLE_NORMAL;
}
final long oldId = getAudioId();
final int listlength = list.length;
boolean newlist = true;
if (mPlaylist.size() == listlength) {
newlist = false;
for (int i = 0; i < listlength; i++) {
if (list[i] != mPlaylist.get(i).mId) {
newlist = true;
break;
}
}
}
if (newlist) {
addToPlayList(list, -1, sourceId, sourceType);
notifyChange(QUEUE_CHANGED);
}
if (position >= 0) {
mPlayPos = position;
} else {
mPlayPos = mShuffler.nextInt(mPlaylist.size());
}
mHistory.clear();
openCurrentAndNext();
if (oldId != getAudioId()) {
notifyChange(META_CHANGED);
}
}
}
public void stop() {
stop(true);
}
public void play() {
play(true);
}
public void play(boolean createNewNextTrack) {
int status = mAudioManager.requestAudioFocus(mAudioFocusListener,
AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
if (D) Log.d(TAG, "Starting playback: audio focus request status = " + status);
if (status != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
return;
}
final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
sendBroadcast(intent);
mAudioManager.registerMediaButtonEventReceiver(new ComponentName(getPackageName(),
MediaButtonIntentReceiver.class.getName()));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
mSession.setActive(true);
if (createNewNextTrack) {
setNextTrack();
} else {
setNextTrack(mNextPlayPos);
}
if (mPlayer.isInitialized()) {
final long duration = mPlayer.duration();
if (mRepeatMode != REPEAT_CURRENT && duration > 2000
&& mPlayer.position() >= duration - 2000) {
gotoNext(true);
}
mPlayer.start();
mPlayerHandler.removeMessages(FADEDOWN);
mPlayerHandler.sendEmptyMessage(FADEUP);
setIsSupposedToBePlaying(true, true);
cancelShutdown();
updateNotification();
notifyChange(META_CHANGED);
} else if (mPlaylist.size() <= 0) {
setShuffleMode(SHUFFLE_AUTO);
}
}
public void pause() {
if (D) Log.d(TAG, "Pausing playback");
synchronized (this) {
mPlayerHandler.removeMessages(FADEUP);
if (mIsSupposedToBePlaying) {
final Intent intent = new Intent(
AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
sendBroadcast(intent);
mPlayer.pause();
notifyChange(META_CHANGED);
setIsSupposedToBePlaying(false, true);
}
}
}
public void gotoNext(final boolean force) {
if (D) Log.d(TAG, "Going to next track");
synchronized (this) {
if (mPlaylist.size() <= 0) {
if (D) Log.d(TAG, "No play queue");
scheduleDelayedShutdown();
return;
}
int pos = mNextPlayPos;
if (pos < 0) {
pos = getNextPosition(force);
}
if (pos < 0) {
setIsSupposedToBePlaying(false, true);
return;
}
stop(false);
setAndRecordPlayPos(pos);
openCurrentAndNext();
play();
notifyChange(META_CHANGED);
}
}
public void goToPosition(int pos) {
synchronized (this) {
if (mPlaylist.size() <= 0) {
if (D) Log.d(TAG, "No play queue");
scheduleDelayedShutdown();
return;
}
if (pos < 0) {
return;
}
if (pos == mPlayPos) {
if (!isPlaying()) {
play();
}
return;
}
stop(false);
setAndRecordPlayPos(pos);
openCurrentAndNext();
play();
notifyChange(META_CHANGED);
}
}
public void setAndRecordPlayPos(int nextPos) {
synchronized (this) {
if (mShuffleMode != SHUFFLE_NONE) {
mHistory.add(mPlayPos);
if (mHistory.size() > MAX_HISTORY_SIZE) {
mHistory.remove(0);
}
}
mPlayPos = nextPos;
}
}
public void prev(boolean forcePrevious) {
synchronized (this) {
boolean goPrevious = getRepeatMode() != REPEAT_CURRENT &&
(position() < REWIND_INSTEAD_PREVIOUS_THRESHOLD || forcePrevious);
if (goPrevious) {
if (D) Log.d(TAG, "Going to previous track");
int pos = getPreviousPlayPosition(true);
if (pos < 0) {
return;
}
mNextPlayPos = mPlayPos;
mPlayPos = pos;
stop(false);
openCurrent();
play(false);
notifyChange(META_CHANGED);
} else {
if (D) Log.d(TAG, "Going to beginning of track");
seek(0);
play(false);
}
}
}
public int getPreviousPlayPosition(boolean removeFromHistory) {
synchronized (this) {
if (mShuffleMode == SHUFFLE_NORMAL) {
final int histsize = mHistory.size();
if (histsize == 0) {
return -1;
}
final Integer pos = mHistory.get(histsize - 1);
if (removeFromHistory) {
mHistory.remove(histsize - 1);
}
return pos.intValue();
} else {
if (mPlayPos > 0) {
return mPlayPos - 1;
} else {
return mPlaylist.size() - 1;
}
}
}
}
private void openCurrent() {
openCurrentAndMaybeNext(false);
}
public void moveQueueItem(int index1, int index2) {
synchronized (this) {
if (index1 >= mPlaylist.size()) {
index1 = mPlaylist.size() - 1;
}
if (index2 >= mPlaylist.size()) {
index2 = mPlaylist.size() - 1;
}
if (index1 == index2) {
return;
}
final MusicPlaybackTrack track = mPlaylist.remove(index1);
if (index1 < index2) {
mPlaylist.add(index2, track);
if (mPlayPos == index1) {
mPlayPos = index2;
} else if (mPlayPos >= index1 && mPlayPos <= index2) {
mPlayPos--;
}
} else if (index2 < index1) {
mPlaylist.add(index2, track);
if (mPlayPos == index1) {
mPlayPos = index2;
} else if (mPlayPos >= index2 && mPlayPos <= index1) {
mPlayPos++;
}
}
notifyChange(QUEUE_CHANGED);
}
}
public void enqueue(final long[] list, final int action, long sourceId, IdType sourceType) {
synchronized (this) {
if (action == NEXT && mPlayPos + 1 < mPlaylist.size()) {
addToPlayList(list, mPlayPos + 1, sourceId, sourceType);
mNextPlayPos = mPlayPos + 1;
notifyChange(QUEUE_CHANGED);
} else {
addToPlayList(list, Integer.MAX_VALUE, sourceId, sourceType);
notifyChange(QUEUE_CHANGED);
}
if (mPlayPos < 0) {
mPlayPos = 0;
openCurrentAndNext();
play();
notifyChange(META_CHANGED);
}
}
}
private void cycleRepeat() {
if (mRepeatMode == REPEAT_NONE) {
setRepeatMode(REPEAT_CURRENT);
if (mShuffleMode != SHUFFLE_NONE) {
setShuffleMode(SHUFFLE_NONE);
}
} else {
setRepeatMode(REPEAT_NONE);
}
}
private void cycleShuffle() {
if (mShuffleMode == SHUFFLE_NONE) {
setShuffleMode(SHUFFLE_NORMAL);
// if (mRepeatMode == REPEAT_CURRENT) {
// setRepeatMode(REPEAT_ALL);
// }
} else if (mShuffleMode == SHUFFLE_NORMAL || mShuffleMode == SHUFFLE_AUTO) {
setShuffleMode(SHUFFLE_NONE);
}
}
public void refresh() {
notifyChange(REFRESH);
}
public void playlistChanged() {
notifyChange(PLAYLIST_CHANGED);
}
public interface TrackErrorExtra {
String TRACK_NAME = "trackname";
}
private static final class MusicPlayerHandler extends Handler {
private final WeakReference mService;
private float mCurrentVolume = 1.0f;
public MusicPlayerHandler(final MusicService service, final Looper looper) {
super(looper);
mService = new WeakReference(service);
}
@Override
public void handleMessage(final Message msg) {
final MusicService service = mService.get();
if (service == null) {
return;
}
synchronized (service) {
switch (msg.what) {
case FADEDOWN:
mCurrentVolume -= .05f;
if (mCurrentVolume > .2f) {
sendEmptyMessageDelayed(FADEDOWN, 10);
} else {
mCurrentVolume = .2f;
}
service.mPlayer.setVolume(mCurrentVolume);
break;
case FADEUP:
mCurrentVolume += .01f;
if (mCurrentVolume < 1.0f) {
sendEmptyMessageDelayed(FADEUP, 10);
} else {
mCurrentVolume = 1.0f;
}
service.mPlayer.setVolume(mCurrentVolume);
break;
case SERVER_DIED:
if (service.isPlaying()) {
final TrackErrorInfo info = (TrackErrorInfo) msg.obj;
service.sendErrorMessage(info.mTrackName);
service.removeTrack(info.mId);
} else {
service.openCurrentAndNext();
}
break;
case TRACK_WENT_TO_NEXT:
mService.get().scrobble();
service.setAndRecordPlayPos(service.mNextPlayPos);
service.setNextTrack();
if (service.mCursor != null) {
service.mCursor.close();
service.mCursor = null;
}
service.updateCursor(service.mPlaylist.get(service.mPlayPos).mId);
service.notifyChange(META_CHANGED);
service.updateNotification();
break;
case TRACK_ENDED:
if (service.mRepeatMode == REPEAT_CURRENT) {
service.seek(0);
service.play();
} else {
service.gotoNext(false);
}
break;
case RELEASE_WAKELOCK:
service.mWakeLock.release();
break;
case FOCUSCHANGE:
if (D) Log.d(TAG, "Received audio focus change event " + msg.arg1);
switch (msg.arg1) {
case AudioManager.AUDIOFOCUS_LOSS:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
if (service.isPlaying()) {
service.mPausedByTransientLossOfFocus =
msg.arg1 == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
}
service.pause();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
removeMessages(FADEUP);
sendEmptyMessage(FADEDOWN);
break;
case AudioManager.AUDIOFOCUS_GAIN:
if (!service.isPlaying()
&& service.mPausedByTransientLossOfFocus) {
service.mPausedByTransientLossOfFocus = false;
mCurrentVolume = 0f;
service.mPlayer.setVolume(mCurrentVolume);
service.play();
} else {
removeMessages(FADEDOWN);
sendEmptyMessage(FADEUP);
}
break;
default:
}
break;
default:
break;
}
}
}
}
private static final class Shuffler {
private final LinkedList mHistoryOfNumbers = new LinkedList();
private final TreeSet mPreviousNumbers = new TreeSet();
private final Random mRandom = new Random();
private int mPrevious;
public Shuffler() {
super();
}
public int nextInt(final int interval) {
int next;
do {
next = mRandom.nextInt(interval);
} while (next == mPrevious && interval > 1
&& !mPreviousNumbers.contains(Integer.valueOf(next)));
mPrevious = next;
mHistoryOfNumbers.add(mPrevious);
mPreviousNumbers.add(mPrevious);
cleanUpHistory();
return next;
}
private void cleanUpHistory() {
if (!mHistoryOfNumbers.isEmpty() && mHistoryOfNumbers.size() >= MAX_HISTORY_SIZE) {
for (int i = 0; i < Math.max(1, MAX_HISTORY_SIZE / 2); i++) {
mPreviousNumbers.remove(mHistoryOfNumbers.removeFirst());
}
}
}
}
private static final class TrackErrorInfo {
public long mId;
public String mTrackName;
public TrackErrorInfo(long id, String trackName) {
mId = id;
mTrackName = trackName;
}
}
private static final class MultiPlayer implements MediaPlayer.OnErrorListener,
MediaPlayer.OnCompletionListener {
private final WeakReference mService;
private MediaPlayer mCurrentMediaPlayer = new MediaPlayer();
private MediaPlayer mNextMediaPlayer;
private Handler mHandler;
private boolean mIsInitialized = false;
private String mNextMediaPath;
public MultiPlayer(final MusicService service) {
mService = new WeakReference(service);
mCurrentMediaPlayer.setWakeMode(mService.get(), PowerManager.PARTIAL_WAKE_LOCK);
}
public void setDataSource(final String path) {
try {
mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path);
if (mIsInitialized) {
setNextDataSource(null);
}
} catch (IllegalStateException e) {
e.printStackTrace();
}
}
private boolean setDataSourceImpl(final MediaPlayer player, final String path) {
try {
player.reset();
player.setOnPreparedListener(null);
if (path.startsWith("content://")) {
player.setDataSource(mService.get(), Uri.parse(path));
} else {
player.setDataSource(path);
}
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
player.prepare();
} catch (final IOException todo) {
return false;
} catch (final IllegalArgumentException todo) {
return false;
}
player.setOnCompletionListener(this);
player.setOnErrorListener(this);
return true;
}
public void setNextDataSource(final String path) {
mNextMediaPath = null;
try {
mCurrentMediaPlayer.setNextMediaPlayer(null);
} catch (IllegalArgumentException e) {
Log.i(TAG, "Next media player is current one, continuing");
} catch (IllegalStateException e) {
Log.e(TAG, "Media player not initialized!");
return;
}
if (mNextMediaPlayer != null) {
mNextMediaPlayer.release();
mNextMediaPlayer = null;
}
if (path == null) {
return;
}
mNextMediaPlayer = new MediaPlayer();
mNextMediaPlayer.setWakeMode(mService.get(), PowerManager.PARTIAL_WAKE_LOCK);
mNextMediaPlayer.setAudioSessionId(getAudioSessionId());
try {
if (setDataSourceImpl(mNextMediaPlayer, path)) {
mNextMediaPath = path;
mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer);
} else {
if (mNextMediaPlayer != null) {
mNextMediaPlayer.release();
mNextMediaPlayer = null;
}
}
} catch (IllegalStateException e) {
e.printStackTrace();
}
}
public void setHandler(final Handler handler) {
mHandler = handler;
}
public boolean isInitialized() {
return mIsInitialized;
}
public void start() {
mCurrentMediaPlayer.start();
}
public void stop() {
mCurrentMediaPlayer.reset();
mIsInitialized = false;
}
public void release() {
mCurrentMediaPlayer.release();
}
public void pause() {
mCurrentMediaPlayer.pause();
}
public long duration() {
return mCurrentMediaPlayer.getDuration();
}
public long position() {
return mCurrentMediaPlayer.getCurrentPosition();
}
public long seek(final long whereto) {
mCurrentMediaPlayer.seekTo((int) whereto);
return whereto;
}
public void setVolume(final float vol) {
try {
mCurrentMediaPlayer.setVolume(vol, vol);
} catch (IllegalStateException e) {
e.printStackTrace();
}
}
public int getAudioSessionId() {
return mCurrentMediaPlayer.getAudioSessionId();
}
public void setAudioSessionId(final int sessionId) {
mCurrentMediaPlayer.setAudioSessionId(sessionId);
}
@Override
public boolean onError(final MediaPlayer mp, final int what, final int extra) {
Log.w(TAG, "Music Server Error what: " + what + " extra: " + extra);
switch (what) {
case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
final MusicService service = mService.get();
final TrackErrorInfo errorInfo = new TrackErrorInfo(service.getAudioId(),
service.getTrackName());
mIsInitialized = false;
mCurrentMediaPlayer.release();
mCurrentMediaPlayer = new MediaPlayer();
mCurrentMediaPlayer.setWakeMode(service, PowerManager.PARTIAL_WAKE_LOCK);
Message msg = mHandler.obtainMessage(SERVER_DIED, errorInfo);
mHandler.sendMessageDelayed(msg, 2000);
return true;
default:
break;
}
return false;
}
@Override
public void onCompletion(final MediaPlayer mp) {
if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) {
mCurrentMediaPlayer.release();
mCurrentMediaPlayer = mNextMediaPlayer;
mNextMediaPath = null;
mNextMediaPlayer = null;
mHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT);
} else {
mService.get().mWakeLock.acquire(30000);
mHandler.sendEmptyMessage(TRACK_ENDED);
mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
}
}
}
private static final class ServiceStub extends ITimberService.Stub {
private final WeakReference mService;
private ServiceStub(final MusicService service) {
mService = new WeakReference(service);
}
@Override
public void openFile(final String path) throws RemoteException {
mService.get().openFile(path);
}
@Override
public void open(final long[] list, final int position, long sourceId, int sourceType)
throws RemoteException {
mService.get().open(list, position, sourceId, IdType.getTypeById(sourceType));
}
@Override
public void stop() throws RemoteException {
mService.get().stop();
}
@Override
public void pause() throws RemoteException {
mService.get().pause();
}
@Override
public void play() throws RemoteException {
mService.get().play();
}
@Override
public void prev(boolean forcePrevious) throws RemoteException {
mService.get().prev(forcePrevious);
}
@Override
public void next() throws RemoteException {
mService.get().gotoNext(true);
}
@Override
public void enqueue(final long[] list, final int action, long sourceId, int sourceType)
throws RemoteException {
mService.get().enqueue(list, action, sourceId, IdType.getTypeById(sourceType));
}
@Override
public void moveQueueItem(final int from, final int to) throws RemoteException {
mService.get().moveQueueItem(from, to);
}
@Override
public void refresh() throws RemoteException {
mService.get().refresh();
}
@Override
public void playlistChanged() throws RemoteException {
mService.get().playlistChanged();
}
@Override
public boolean isPlaying() throws RemoteException {
return mService.get().isPlaying();
}
@Override
public long[] getQueue() throws RemoteException {
return mService.get().getQueue();
}
@Override
public long getQueueItemAtPosition(int position) throws RemoteException {
return mService.get().getQueueItemAtPosition(position);
}
@Override
public int getQueueSize() throws RemoteException {
return mService.get().getQueueSize();
}
@Override
public int getQueueHistoryPosition(int position) throws RemoteException {
return mService.get().getQueueHistoryPosition(position);
}
@Override
public int getQueueHistorySize() throws RemoteException {
return mService.get().getQueueHistorySize();
}
@Override
public int[] getQueueHistoryList() throws RemoteException {
return mService.get().getQueueHistoryList();
}
@Override
public long duration() throws RemoteException {
return mService.get().duration();
}
@Override
public long position() throws RemoteException {
return mService.get().position();
}
@Override
public long seek(final long position) throws RemoteException {
return mService.get().seek(position);
}
@Override
public void seekRelative(final long deltaInMs) throws RemoteException {
mService.get().seekRelative(deltaInMs);
}
@Override
public long getAudioId() throws RemoteException {
return mService.get().getAudioId();
}
@Override
public MusicPlaybackTrack getCurrentTrack() throws RemoteException {
return mService.get().getCurrentTrack();
}
@Override
public MusicPlaybackTrack getTrack(int index) throws RemoteException {
return mService.get().getTrack(index);
}
@Override
public long getNextAudioId() throws RemoteException {
return mService.get().getNextAudioId();
}
@Override
public long getPreviousAudioId() throws RemoteException {
return mService.get().getPreviousAudioId();
}
@Override
public long getArtistId() throws RemoteException {
return mService.get().getArtistId();
}
@Override
public long getAlbumId() throws RemoteException {
return mService.get().getAlbumId();
}
@Override
public String getArtistName() throws RemoteException {
return mService.get().getArtistName();
}
@Override
public String getTrackName() throws RemoteException {
return mService.get().getTrackName();
}
@Override
public String getAlbumName() throws RemoteException {
return mService.get().getAlbumName();
}
@Override
public String getPath() throws RemoteException {
return mService.get().getPath();
}
@Override
public int getQueuePosition() throws RemoteException {
return mService.get().getQueuePosition();
}
@Override
public void setQueuePosition(final int index) throws RemoteException {
mService.get().setQueuePosition(index);
}
@Override
public int getShuffleMode() throws RemoteException {
return mService.get().getShuffleMode();
}
@Override
public void setShuffleMode(final int shufflemode) throws RemoteException {
mService.get().setShuffleMode(shufflemode);
}
@Override
public int getRepeatMode() throws RemoteException {
return mService.get().getRepeatMode();
}
@Override
public void setRepeatMode(final int repeatmode) throws RemoteException {
mService.get().setRepeatMode(repeatmode);
}
@Override
public int removeTracks(final int first, final int last) throws RemoteException {
return mService.get().removeTracks(first, last);
}
@Override
public int removeTrack(final long id) throws RemoteException {
return mService.get().removeTrack(id);
}
@Override
public boolean removeTrackAtPosition(final long id, final int position)
throws RemoteException {
return mService.get().removeTrackAtPosition(id, position);
}
@Override
public int getMediaMountedCount() throws RemoteException {
return mService.get().getMediaMountedCount();
}
@Override
public int getAudioSessionId() throws RemoteException {
return mService.get().getAudioSessionId();
}
}
private class MediaStoreObserver extends ContentObserver implements Runnable {
private static final long REFRESH_DELAY = 500;
private Handler mHandler;
public MediaStoreObserver(Handler handler) {
super(handler);
mHandler = handler;
}
@Override
public void onChange(boolean selfChange) {
mHandler.removeCallbacks(this);
mHandler.postDelayed(this, REFRESH_DELAY);
}
@Override
public void run() {
Log.e("ELEVEN", "calling refresh!");
refresh();
}
}
}
================================================
FILE: app/src/main/java/com/naman14/timber/TimberApp.java
================================================
/*
* Copyright (C) 2015 Naman Dwivedi
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package com.naman14.timber;
import androidx.multidex.MultiDexApplication;
import com.afollestad.appthemeengine.ATE;
import com.naman14.timber.permissions.Nammu;
import com.naman14.timber.utils.PreferencesUtility;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
import com.nostra13.universalimageloader.utils.L;
import java.io.IOException;
import java.io.InputStream;
public class TimberApp extends MultiDexApplication {
private static TimberApp mInstance;
public static synchronized TimberApp getInstance() {
return mInstance;
}
@Override
public void onCreate() {
super.onCreate();
mInstance = this;
ImageLoaderConfiguration localImageLoaderConfiguration = new ImageLoaderConfiguration.Builder(this).imageDownloader(new BaseImageDownloader(this) {
PreferencesUtility prefs = PreferencesUtility.getInstance(TimberApp.this);
@Override
protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
if (prefs.loadArtistAndAlbumImages())
return super.getStreamFromNetwork(imageUri, extra);
throw new IOException();
}
}).build();
ImageLoader.getInstance().init(localImageLoaderConfiguration);
L.writeLogs(false);
L.disableLogging();
L.writeDebugLogs(false);
Nammu.init(this);
if (!ATE.config(this, "light_theme").isConfigured()) {
ATE.config(this, "light_theme")
.activityTheme(R.style.AppThemeLight)
.primaryColorRes(R.color.colorPrimaryLightDefault)
.accentColorRes(R.color.colorAccentLightDefault)
.coloredNavigationBar(false)
.usingMaterialDialogs(true)
.commit();
}
if (!ATE.config(this, "dark_theme").isConfigured()) {
ATE.config(this, "dark_theme")
.activityTheme(R.style.AppThemeDark)
.primaryColorRes(R.color.colorPrimaryDarkDefault)
.accentColorRes(R.color.colorAccentDarkDefault)
.coloredNavigationBar(false)
.usingMaterialDialogs(true)
.commit();
}
if (!ATE.config(this, "light_theme_notoolbar").isConfigured()) {
ATE.config(this, "light_theme_notoolbar")
.activityTheme(R.style.AppThemeLight)
.coloredActionBar(false)
.primaryColorRes(R.color.colorPrimaryLightDefault)
.accentColorRes(R.color.colorAccentLightDefault)
.coloredNavigationBar(false)
.usingMaterialDialogs(true)
.commit();
}
if (!ATE.config(this, "dark_theme_notoolbar").isConfigured()) {
ATE.config(this, "dark_theme_notoolbar")
.activityTheme(R.style.AppThemeDark)
.coloredActionBar(false)
.primaryColorRes(R.color.colorPrimaryDarkDefault)
.accentColorRes(R.color.colorAccentDarkDefault)
.coloredNavigationBar(true)
.usingMaterialDialogs(true)
.commit();
}
}
}
================================================
FILE: app/src/main/java/com/naman14/timber/WearBrowserService.java
================================================
/*
* Copyright (C) 2015 Naman Dwivedi
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package com.naman14.timber;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.media.MediaDescription;
import android.media.browse.MediaBrowser;
import android.media.session.MediaSession;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.service.media.MediaBrowserService;
import androidx.annotation.Nullable;
import com.naman14.timber.dataloaders.AlbumLoader;
import com.naman14.timber.dataloaders.AlbumSongLoader;
import com.naman14.timber.dataloaders.ArtistAlbumLoader;
import com.naman14.timber.dataloaders.ArtistLoader;
import com.naman14.timber.dataloaders.ArtistSongLoader;
import com.naman14.timber.dataloaders.PlaylistLoader;
import com.naman14.timber.dataloaders.PlaylistSongLoader;
import com.naman14.timber.dataloaders.SongLoader;
import com.naman14.timber.models.Album;
import com.naman14.timber.models.Artist;
import com.naman14.timber.models.Playlist;
import com.naman14.timber.models.Song;
import com.naman14.timber.utils.TimberUtils;
import java.util.ArrayList;
import java.util.List;
@TargetApi(21)
public class WearBrowserService extends MediaBrowserService {
public static final String MEDIA_ID_ROOT = "__ROOT__";
public static final int TYPE_ARTIST = 0;
public static final int TYPE_ALBUM = 1;
public static final int TYPE_SONG = 2;
public static final int TYPE_PLAYLIST = 3;
public static final int TYPE_ARTIST_SONG_ALBUMS = 4;
public static final int TYPE_ALBUM_SONGS = 5;
public static final int TYPE_ARTIST_ALL_SONGS = 6;
public static final int TYPE_PLAYLIST_ALL_SONGS = 7;
MediaSession mSession;
public static WearBrowserService sInstance;
private Context mContext;
private boolean mServiceStarted;
public static WearBrowserService getInstance() {
return sInstance;
}
@Override
public void onCreate() {
super.onCreate();
sInstance = this;
mContext = this;
mSession = new MediaSession(this, "WearBrowserService");
setSessionToken(mSession.getSessionToken());
mSession.setCallback(new MediaSessionCallback());
mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
}
@Override
public int onStartCommand(Intent startIntent, int flags, int startId) {
return START_STICKY;
}
@Override
public void onDestroy() {
mServiceStarted = false;
mSession.release();
}
@Override
public void onLoadChildren(String parentId, Result> result) {
result.detach();
loadChildren(parentId, result);
}
@Nullable
@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
return new BrowserRoot(MEDIA_ID_ROOT, null);
}
private final class MediaSessionCallback extends MediaSession.Callback {
@Override
public void onPlay() {
setSessionActive();
}
@Override
public void onSeekTo(long position) {
}
@Override
public void onPlayFromMediaId(final String mediaId, Bundle extras) {
long songId = Long.parseLong(mediaId);
setSessionActive();
MusicPlayer.playAll(mContext, new long[]{songId}, 0, -1, TimberUtils.IdType.NA, false);
}
@Override
public void onPause() {
}
@Override
public void onStop() {
setSessionInactive();
}
@Override
public void onSkipToNext() {
}
@Override
public void onSkipToPrevious() {
}
@Override
public void onFastForward() {
}
@Override
public void onRewind() {
}
@Override
public void onCustomAction(String action, Bundle extras) {
}
}
private void setSessionActive() {
if (!mServiceStarted) {
startService(new Intent(getApplicationContext(), WearBrowserService.class));
mServiceStarted = true;
}
if (!mSession.isActive()) {
mSession.setActive(true);
}
}
private void setSessionInactive() {
if (mServiceStarted) {
stopSelf();
mServiceStarted = false;
}
if (mSession.isActive()) {
mSession.setActive(false);
}
}
private void addMediaRoots(List mMediaRoot) {
mMediaRoot.add(new MediaBrowser.MediaItem(
new MediaDescription.Builder()
.setMediaId(Integer.toString(TYPE_ARTIST))
.setTitle(getString(R.string.artists))
.setIconUri(Uri.parse("android.resource://" +
"naman14.timber/drawable/ic_empty_music2"))
.setSubtitle(getString(R.string.artists))
.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE
));
mMediaRoot.add(new MediaBrowser.MediaItem(
new MediaDescription.Builder()
.setMediaId(Integer.toString(TYPE_ALBUM))
.setTitle(getString(R.string.albums))
.setIconUri(Uri.parse("android.resource://" +
"naman14.timber/drawable/ic_empty_music2"))
.setSubtitle(getString(R.string.albums))
.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE
));
mMediaRoot.add(new MediaBrowser.MediaItem(
new MediaDescription.Builder()
.setMediaId(Integer.toString(TYPE_SONG))
.setTitle(getString(R.string.songs))
.setIconUri(Uri.parse("android.resource://" +
"naman14.timber/drawable/ic_empty_music2"))
.setSubtitle(getString(R.string.songs))
.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE
));
mMediaRoot.add(new MediaBrowser.MediaItem(
new MediaDescription.Builder()
.setMediaId(Integer.toString(TYPE_PLAYLIST))
.setTitle(getString(R.string.playlists))
.setIconUri(Uri.parse("android.resource://" +
"naman14.timber/drawable/ic_empty_music2"))
.setSubtitle(getString(R.string.playlists))
.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE
));
}
private void loadChildren(final String parentId, final Result> result) {
final List mediaItems = new ArrayList<>();
new AsyncTask() {
@Override
protected Void doInBackground(final Void... unused) {
if (parentId.equals(MEDIA_ID_ROOT)) {
addMediaRoots(mediaItems);
} else {
switch (Integer.parseInt(Character.toString(parentId.charAt(0)))) {
case TYPE_ARTIST:
List artistList = ArtistLoader.getAllArtists(mContext);
for (Artist artist : artistList) {
String albumNmber = TimberUtils.makeLabel(mContext, R.plurals.Nalbums, artist.albumCount);
String songCount = TimberUtils.makeLabel(mContext, R.plurals.Nsongs, artist.songCount);
fillMediaItems(mediaItems, Integer.toString(TYPE_ARTIST_SONG_ALBUMS) + Long.toString(artist.id), artist.name, Uri.parse("android.resource://" +
"naman14.timber/drawable/ic_empty_music2"), TimberUtils.makeCombinedString(mContext, albumNmber, songCount), MediaBrowser.MediaItem.FLAG_BROWSABLE);
}
break;
case TYPE_ALBUM:
List albumList = AlbumLoader.getAllAlbums(mContext);
for (Album album : albumList) {
fillMediaItems(mediaItems, Integer.toString(TYPE_ALBUM_SONGS) + Long.toString(album.id), album.title, TimberUtils.getAlbumArtUri(album.id), album.artistName, MediaBrowser.MediaItem.FLAG_BROWSABLE);
}
break;
case TYPE_SONG:
List songList = SongLoader.getAllSongs(mContext);
for (Song song : songList) {
fillMediaItems(mediaItems, String.valueOf(song.id), song.title, TimberUtils.getAlbumArtUri(song.albumId), song.artistName, MediaBrowser.MediaItem.FLAG_PLAYABLE);
}
break;
case TYPE_ALBUM_SONGS:
List albumSongList = AlbumSongLoader.getSongsForAlbum(mContext, Long.parseLong(parentId.substring(1)));
for (Song song : albumSongList) {
fillMediaItems(mediaItems, String.valueOf(song.id), song.title, TimberUtils.getAlbumArtUri(song.albumId), song.artistName, MediaBrowser.MediaItem.FLAG_PLAYABLE);
}
break;
case TYPE_ARTIST_SONG_ALBUMS:
fillMediaItems(mediaItems, Integer.toString(TYPE_ARTIST_ALL_SONGS) + Long.parseLong(parentId.substring(1)), "All songs", Uri.parse("android.resource://" +
"naman14.timber/drawable/ic_empty_music2"), "All songs by artist", MediaBrowser.MediaItem.FLAG_BROWSABLE);
List artistAlbums = ArtistAlbumLoader.getAlbumsForArtist(mContext, Long.parseLong(parentId.substring(1)));
for (Album album : artistAlbums) {
String songCount = TimberUtils.makeLabel(mContext, R.plurals.Nsongs, album.songCount);
fillMediaItems(mediaItems, Integer.toString(TYPE_ALBUM_SONGS) + Long.toString(album.id), album.title, TimberUtils.getAlbumArtUri(album.id), songCount, MediaBrowser.MediaItem.FLAG_BROWSABLE);
}
break;
case TYPE_ARTIST_ALL_SONGS:
List artistSongs = ArtistSongLoader.getSongsForArtist(mContext, Long.parseLong(parentId.substring(1)));
for (Song song : artistSongs) {
fillMediaItems(mediaItems, String.valueOf(song.id), song.title, TimberUtils.getAlbumArtUri(song.albumId), song.albumName, MediaBrowser.MediaItem.FLAG_PLAYABLE);
}
break;
case TYPE_PLAYLIST:
List playlistList = PlaylistLoader.getPlaylists(mContext, false);
for (Playlist playlist : playlistList) {
String songCount = TimberUtils.makeLabel(mContext, R.plurals.Nsongs, playlist.songCount);
fillMediaItems(mediaItems, Integer.toString(TYPE_PLAYLIST_ALL_SONGS) + Long.toString(playlist.id), playlist.name,
Uri.parse("android.resource://" +
"naman14.timber/drawable/ic_empty_music2"), songCount, MediaBrowser.MediaItem.FLAG_BROWSABLE);
}
break;
case TYPE_PLAYLIST_ALL_SONGS:
List playlistSongs = PlaylistSongLoader.getSongsInPlaylist(mContext, Long.parseLong(parentId.substring(1)));
for (Song song : playlistSongs) {
fillMediaItems(mediaItems, String.valueOf(song.id), song.title, TimberUtils.getAlbumArtUri(song.albumId), song.albumName, MediaBrowser.MediaItem.FLAG_PLAYABLE);
}
break;
}
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
result.sendResult(mediaItems);
}
}.execute();
}
private void fillMediaItems(List mediaItems, String mediaId, String title, Uri icon, String subTitle, int playableOrBrowsable) {
mediaItems.add(new MediaBrowser.MediaItem(
new MediaDescription.Builder()
.setMediaId(mediaId)
.setTitle(title)
.setIconUri(icon)
.setSubtitle(subTitle)
.build(), playableOrBrowsable
));
}
}
================================================
FILE: app/src/main/java/com/naman14/timber/activities/BaseActivity.java
================================================
/*
* Copyright (C) 2012 Andrew Neal
* Copyright (C) 2014 The CyanogenMod Project
* Copyright (C) 2015 Naman Dwivedi
*
* 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.naman14.timber.activities;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.media.AudioManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentManager;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import com.afollestad.appthemeengine.ATE;
import com.afollestad.appthemeengine.ATEActivity;
import com.google.android.gms.cast.framework.CastButtonFactory;
import com.google.android.gms.cast.framework.CastContext;
import com.google.android.gms.cast.framework.CastSession;
import com.google.android.gms.cast.framework.Session;
import com.google.android.gms.cast.framework.SessionManager;
import com.google.android.gms.cast.framework.SessionManagerListener;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.naman14.timber.ITimberService;
import com.naman14.timber.MusicPlayer;
import com.naman14.timber.MusicService;
import com.naman14.timber.R;
import com.naman14.timber.cast.SimpleSessionManagerListener;
import com.naman14.timber.cast.WebServer;
import com.naman14.timber.listeners.MusicStateListener;
import com.naman14.timber.slidinguppanel.SlidingUpPanelLayout;
import com.naman14.timber.subfragments.QuickControlsFragment;
import com.naman14.timber.utils.Helpers;
import com.naman14.timber.utils.NavigationUtils;
import com.naman14.timber.utils.TimberUtils;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import static com.naman14.timber.MusicPlayer.mService;
public class BaseActivity extends ATEActivity implements ServiceConnection, MusicStateListener {
private final ArrayList mMusicStateListener = new ArrayList<>();
private MusicPlayer.ServiceToken mToken;
private PlaybackStatus mPlaybackStatus;
private CastSession mCastSession;
private SessionManager mSessionManager;
private final SessionManagerListener mSessionManagerListener =
new SessionManagerListenerImpl();
private WebServer castServer;
public boolean playServicesAvailable = false;
private class SessionManagerListenerImpl extends SimpleSessionManagerListener {
@Override
public void onSessionStarting(Session session) {
super.onSessionStarting(session);
startCastServer();
}
@Override
public void onSessionStarted(Session session, String sessionId) {
invalidateOptionsMenu();
mCastSession = mSessionManager.getCurrentCastSession();
showCastMiniController();
}
@Override
public void onSessionResumed(Session session, boolean wasSuspended) {
invalidateOptionsMenu();
mCastSession = mSessionManager.getCurrentCastSession();
}
@Override
public void onSessionEnded(Session session, int error) {
mCastSession = null;
hideCastMiniController();
stopCastServer();
}
@Override
public void onSessionResuming(Session session, String s) {
super.onSessionResuming(session, s);
startCastServer();
}
@Override
public void onSessionSuspended(Session session, int i) {
super.onSessionSuspended(session, i);
stopCastServer();
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mToken = MusicPlayer.bindToService(this, this);
mPlaybackStatus = new PlaybackStatus(this);
//make volume keys change multimedia volume even if music is not playing now
setVolumeControlStream(AudioManager.STREAM_MUSIC);
try {
playServicesAvailable = GoogleApiAvailability
.getInstance().isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS;
} catch (Exception ignored) {
}
if (playServicesAvailable)
initCast();
}
@Override
protected void onStart() {
super.onStart();
final IntentFilter filter = new IntentFilter();
// Play and pause changes
filter.addAction(MusicService.PLAYSTATE_CHANGED);
// Track changes
filter.addAction(MusicService.META_CHANGED);
// Update a list, probably the playlist fragment's
filter.addAction(MusicService.REFRESH);
// If a playlist has changed, notify us
filter.addAction(MusicService.PLAYLIST_CHANGED);
// If there is an error playing a track
filter.addAction(MusicService.TRACK_ERROR);
registerReceiver(mPlaybackStatus, filter);
}
@Override
protected void onStop() {
super.onStop();
}
@Override
public void onResume() {
if (playServicesAvailable) {
mCastSession = mSessionManager.getCurrentCastSession();
mSessionManager.addSessionManagerListener(mSessionManagerListener);
}
//For Android 8.0+: service may get destroyed if in background too long
if(mService == null){
mToken = MusicPlayer.bindToService(this, this);
}
onMetaChanged();
super.onResume();
}
@Override
protected void onPause() {
super.onPause();
if (playServicesAvailable) {
mSessionManager.removeSessionManagerListener(mSessionManagerListener);
mCastSession = null;
}
}
@Override
public void onServiceConnected(final ComponentName name, final IBinder service) {
mService = ITimberService.Stub.asInterface(service);
onMetaChanged();
}
private void initCast() {
CastContext castContext = CastContext.getSharedInstance(this);
mSessionManager = castContext.getSessionManager();
}
@Override
public void onServiceDisconnected(final ComponentName name) {
mService = null;
}
@Override
protected void onDestroy() {
super.onDestroy();
// Unbind from the service
if (mToken != null) {
MusicPlayer.unbindFromService(mToken);
mToken = null;
}
try {
unregisterReceiver(mPlaybackStatus);
} catch (final Throwable e) {
}
mMusicStateListener.clear();
}
@Override
public void onMetaChanged() {
// Let the listener know to the meta chnaged
for (final MusicStateListener listener : mMusicStateListener) {
if (listener != null) {
listener.onMetaChanged();
}
}
}
@Override
public void restartLoader() {
// Let the listener know to update a list
for (final MusicStateListener listener : mMusicStateListener) {
if (listener != null) {
listener.restartLoader();
}
}
}
@Override
public void onPlaylistChanged() {
// Let the listener know to update a list
for (final MusicStateListener listener : mMusicStateListener) {
if (listener != null) {
listener.onPlaylistChanged();
}
}
}
public void setMusicStateListenerListener(final MusicStateListener status) {
if (status == this) {
throw new UnsupportedOperationException("Override the method, don't add a listener");
}
if (status != null) {
mMusicStateListener.add(status);
}
}
public void removeMusicStateListenerListener(final MusicStateListener status) {
if (status != null) {
mMusicStateListener.remove(status);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
getMenuInflater().inflate(R.menu.menu_cast, menu);
if (playServicesAvailable) {
CastButtonFactory.setUpMediaRouteButton(getApplicationContext(),
menu,
R.id.media_route_menu_item);
}
if (!TimberUtils.hasEffectsPanel(BaseActivity.this)) {
menu.removeItem(R.id.action_equalizer);
}
ATE.applyMenu(this, getATEKey(), menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
super.onBackPressed();
return true;
case R.id.action_settings:
NavigationUtils.navigateToSettings(this);
return true;
case R.id.action_shuffle:
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
MusicPlayer.shuffleAll(BaseActivity.this);
}
}, 80);
return true;
case R.id.action_search:
NavigationUtils.navigateToSearch(this);
return true;
case R.id.action_equalizer:
NavigationUtils.navigateToEqualizer(this);
return true;
}
return super.onOptionsItemSelected(item);
}
@Nullable
@Override
public String getATEKey() {
return Helpers.getATEKey(this);
}
public void setPanelSlideListeners(SlidingUpPanelLayout panelLayout) {
panelLayout.setPanelSlideListener(new SlidingUpPanelLayout.PanelSlideListener() {
@Override
public void onPanelSlide(View panel, float slideOffset) {
View nowPlayingCard = QuickControlsFragment.topContainer;
if (nowPlayingCard != null)
nowPlayingCard.setAlpha(1 - slideOffset);
}
@Override
public void onPanelCollapsed(View panel) {
View nowPlayingCard = QuickControlsFragment.topContainer;
if (nowPlayingCard != null)
nowPlayingCard.setAlpha(1);
}
@Override
public void onPanelExpanded(View panel) {
View nowPlayingCard = QuickControlsFragment.topContainer;
if (nowPlayingCard != null)
nowPlayingCard.setAlpha(0);
}
@Override
public void onPanelAnchored(View panel) {
}
@Override
public void onPanelHidden(View panel) {
}
});
}
private final static class PlaybackStatus extends BroadcastReceiver {
private final WeakReference mReference;
public PlaybackStatus(final BaseActivity activity) {
mReference = new WeakReference(activity);
}
@Override
public void onReceive(final Context context, final Intent intent) {
final String action = intent.getAction();
BaseActivity baseActivity = mReference.get();
if (baseActivity != null) {
if (action.equals(MusicService.META_CHANGED)) {
baseActivity.onMetaChanged();
} else if (action.equals(MusicService.PLAYSTATE_CHANGED)) {
// baseActivity.mPlayPauseProgressButton.getPlayPauseButton().updateState();
} else if (action.equals(MusicService.REFRESH)) {
baseActivity.restartLoader();
} else if (action.equals(MusicService.PLAYLIST_CHANGED)) {
baseActivity.onPlaylistChanged();
} else if (action.equals(MusicService.TRACK_ERROR)) {
final String errorMsg = context.getString(R.string.error_playing_track,
intent.getStringExtra(MusicService.TrackErrorExtra.TRACK_NAME));
Toast.makeText(baseActivity, errorMsg, Toast.LENGTH_SHORT).show();
}
}
}
}
public class initQuickControls extends AsyncTask {
@Override
protected String doInBackground(String... params) {
QuickControlsFragment fragment1 = new QuickControlsFragment();
FragmentManager fragmentManager1 = getSupportFragmentManager();
fragmentManager1.beginTransaction()
.replace(R.id.quickcontrols_container, fragment1).commitAllowingStateLoss();
return "Executed";
}
@Override
protected void onPostExecute(String result) {
}
@Override
protected void onPreExecute() {
}
}
public void showCastMiniController() {
//implement by overriding in activities
}
public void hideCastMiniController() {
//implement by overriding in activities
}
public CastSession getCastSession() {
return mCastSession;
}
private void startCastServer() {
castServer = new WebServer(this);
try {
castServer.start();
} catch (IOException e) {
e.printStackTrace();
}
}
private void stopCastServer() {
if (castServer != null) {
castServer.stop();
}
}
}
================================================
FILE: app/src/main/java/com/naman14/timber/activities/BaseThemedActivity.java
================================================
package com.naman14.timber.activities;
import android.media.AudioManager;
import android.os.Bundle;
import androidx.annotation.Nullable;
import com.afollestad.appthemeengine.ATEActivity;
import com.naman14.timber.utils.Helpers;
/**
* Created by naman on 31/12/15.
*/
public class BaseThemedActivity extends ATEActivity {
@Nullable
@Override
public String getATEKey() {
return Helpers.getATEKey(this);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//make volume keys change multimedia volume even if music is not playing now
setVolumeControlStream(AudioManager.STREAM_MUSIC);
}
}
================================================
FILE: app/src/main/java/com/naman14/timber/activities/DonateActivity.java
================================================
package com.naman14.timber.activities;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import androidx.appcompat.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.anjlab.android.iab.v3.BillingProcessor;
import com.anjlab.android.iab.v3.SkuDetails;
import com.anjlab.android.iab.v3.TransactionDetails;
import com.naman14.timber.R;
import com.naman14.timber.utils.PreferencesUtility;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* Created by naman on 29/10/16.
*/
public class DonateActivity extends BaseThemedActivity implements BillingProcessor.IBillingHandler {
private static final String DONATION_1 = "naman14.timber.donate_1";
private static final String DONATION_2 = "naman14.timber.donate_2";
private static final String DONATION_3 = "naman14.timber.donate_3";
private static final String DONATION_5 = "naman14.timber.donate_5";
private static final String DONATION_10 = "naman14.timber.donate_10";
private static final String DONATION_20 = "naman14.timber.donate_20";
private boolean readyToPurchase = false;
BillingProcessor bp;
private LinearLayout productListView;
private ProgressBar progressBar;
private TextView status;
private String action = "support";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_donate);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle("Support development");
action = getIntent().getAction();
productListView = (LinearLayout) findViewById(R.id.product_list);
progressBar = (ProgressBar) findViewById(R.id.progressBar);
status = (TextView) findViewById(R.id.donation_status);
if (action != null && action.equals("restore")) {
status.setText("Restoring purchases..");
}
bp = new BillingProcessor(this, getString(R.string.play_billing_license_key), this);
}
@Override
public void onBillingInitialized() {
readyToPurchase = true;
checkStatus();
if (!(action != null && action.equals("restore")))
getProducts();
}
@Override
public void onProductPurchased(String productId, TransactionDetails details) {
checkStatus();
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(DonateActivity.this, "Thanks for your support!", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onBillingError(int errorCode, Throwable error) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(DonateActivity.this, "Unable to process purchase", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onPurchaseHistoryRestored() {
}
@Override
public void onDestroy() {
if (bp != null)
bp.release();
super.onDestroy();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (!bp.handleActivityResult(requestCode, resultCode, data))
super.onActivityResult(requestCode, resultCode, data);
}
private void checkStatus() {
new AsyncTask() {
@Override
protected Boolean doInBackground(Void... voids) {
List owned = bp.listOwnedProducts();
return owned != null && owned.size() != 0;
}
@Override
protected void onPostExecute(Boolean b) {
super.onPostExecute(b);
if (b) {
PreferencesUtility.getInstance(DonateActivity.this).setFullUnlocked(true);
status.setText("Thanks for your support!");
if (action!=null && action.equals("restore")) {
status.setText("Your purchases has been restored. Thanks for your support");
progressBar.setVisibility(View.GONE);
}
if (getSupportActionBar() != null)
getSupportActionBar().setTitle("Support development");
} else {
if (action!=null && action.equals("restore")) {
status.setText("No previous purchase found");
getProducts();
}
}
}
}.execute();
}
private void getProducts() {
new AsyncTask>() {
@Override
protected List doInBackground(Void... voids) {
ArrayList products = new ArrayList<>();
products.add(DONATION_1);
products.add(DONATION_2);
products.add(DONATION_3);
products.add(DONATION_5);
products.add(DONATION_10);
products.add(DONATION_20);
return bp.getPurchaseListingDetails(products);
}
@Override
protected void onPostExecute(List productList) {
super.onPostExecute(productList);
if (productList == null)
return;
Collections.sort(productList, new Comparator() {
@Override
public int compare(SkuDetails skuDetails, SkuDetails t1) {
if (skuDetails.priceValue >= t1.priceValue)
return 1;
else if (skuDetails.priceValue <= t1.priceValue)
return -1;
else return 0;
}
});
for (int i = 0; i < productList.size(); i++) {
final SkuDetails product = productList.get(i);
View rootView = LayoutInflater.from(DonateActivity.this).inflate(R.layout.item_donate_product, productListView, false);
TextView detail = (TextView) rootView.findViewById(R.id.product_detail);
detail.setText(product.priceText);
rootView.findViewById(R.id.btn_donate).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (readyToPurchase)
bp.purchase(DonateActivity.this, product.productId);
else
Toast.makeText(DonateActivity.this, "Unable to initiate purchase", Toast.LENGTH_SHORT).show();
}
});
productListView.addView(rootView);
}
progressBar.setVisibility(View.GONE);
}
}.execute();
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
super.onBackPressed();
return true;
default:
break;
}
return super.onOptionsItemSelected(item);
}
}
================================================
FILE: app/src/main/java/com/naman14/timber/activities/MainActivity.java
================================================
/*
* Copyright (C) 2015 Naman Dwivedi
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package com.naman14.timber.activities;
import android.Manifest;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import com.google.android.material.navigation.NavigationView;
import com.google.android.material.snackbar.Snackbar;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import com.afollestad.appthemeengine.customizers.ATEActivityThemeCustomizer;
import com.anjlab.android.iab.v3.BillingProcessor;
import com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity;
import com.naman14.timber.MusicPlayer;
import com.naman14.timber.R;
import com.naman14.timber.cast.ExpandedControlsActivity;
import com.naman14.timber.fragments.AlbumDetailFragment;
import com.naman14.timber.fragments.ArtistDetailFragment;
import com.naman14.timber.fragments.FoldersFragment;
import com.naman14.timber.fragments.MainFragment;
import com.naman14.timber.fragments.PlaylistFragment;
import com.naman14.timber.fragments.QueueFragment;
import com.naman14.timber.permissions.Nammu;
import com.naman14.timber.permissions.PermissionCallback;
import com.naman14.timber.slidinguppanel.SlidingUpPanelLayout;
import com.naman14.timber.subfragments.LyricsFragment;
import com.naman14.timber.utils.Constants;
import com.naman14.timber.utils.Helpers;
import com.naman14.timber.utils.NavigationUtils;
import com.naman14.timber.utils.TimberUtils;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import java.util.HashMap;
import java.util.Map;
public class MainActivity extends BaseActivity implements ATEActivityThemeCustomizer {
private SlidingUpPanelLayout panelLayout;
private NavigationView navigationView;
private TextView songtitle, songartist;
private ImageView albumart;
private String action;
private Map navigationMap = new HashMap();
private Handler navDrawerRunnable = new Handler();
private Runnable runnable;
private DrawerLayout mDrawerLayout;
private boolean isDarkTheme;
private Runnable navigateLibrary = new Runnable() {
public void run() {
navigationView.getMenu().findItem(R.id.nav_library).setChecked(true);
Fragment fragment = new MainFragment();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, fragment).commitAllowingStateLoss();
}
};
private Runnable navigatePlaylist = new Runnable() {
public void run() {
navigationView.getMenu().findItem(R.id.nav_playlists).setChecked(true);
Fragment fragment = new PlaylistFragment();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.hide(getSupportFragmentManager().findFragmentById(R.id.fragment_container));
transaction.replace(R.id.fragment_container, fragment).commit();
}
};
private Runnable navigateFolder = new Runnable() {
public void run() {
navigationView.getMenu().findItem(R.id.nav_folders).setChecked(true);
Fragment fragment = new FoldersFragment();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.hide(getSupportFragmentManager().findFragmentById(R.id.fragment_container));
transaction.replace(R.id.fragment_container, fragment).commit();
}
};
private Runnable navigateQueue = new Runnable() {
public void run() {
navigationView.getMenu().findItem(R.id.nav_queue).setChecked(true);
Fragment fragment = new QueueFragment();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.hide(getSupportFragmentManager().findFragmentById(R.id.fragment_container));
transaction.replace(R.id.fragment_container, fragment).commit();
}
};
private Runnable navigateAlbum = new Runnable() {
public void run() {
long albumID = getIntent().getExtras().getLong(Constants.ALBUM_ID);
Fragment fragment = AlbumDetailFragment.newInstance(albumID, false, null);
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.fragment_container, fragment).commit();
}
};
private Runnable navigateArtist = new Runnable() {
public void run() {
long artistID = getIntent().getExtras().getLong(Constants.ARTIST_ID);
Fragment fragment = ArtistDetailFragment.newInstance(artistID, false, null);
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.fragment_container, fragment).commit();
}
};
private Runnable navigateLyrics = new Runnable() {
public void run() {
Fragment fragment = new LyricsFragment();
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.fragment_container, fragment).commit();
}
};
private Runnable navigateNowplaying = new Runnable() {
public void run() {
navigateLibrary.run();
startActivity(new Intent(MainActivity.this, NowPlayingActivity.class));
}
};
private final PermissionCallback permissionReadstorageCallback = new PermissionCallback() {
@Override
public void permissionGranted() {
loadEverything();
}
@Override
public void permissionRefused() {
finish();
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
action = getIntent().getAction();
isDarkTheme = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("dark_theme", false);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
navigationMap.put(Constants.NAVIGATE_LIBRARY, navigateLibrary);
navigationMap.put(Constants.NAVIGATE_PLAYLIST, navigatePlaylist);
navigationMap.put(Constants.NAVIGATE_QUEUE, navigateQueue);
navigationMap.put(Constants.NAVIGATE_NOWPLAYING, navigateNowplaying);
navigationMap.put(Constants.NAVIGATE_ALBUM, navigateAlbum);
navigationMap.put(Constants.NAVIGATE_ARTIST, navigateArtist);
navigationMap.put(Constants.NAVIGATE_LYRICS, navigateLyrics);
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
panelLayout = (SlidingUpPanelLayout) findViewById(R.id.sliding_layout);
navigationView = (NavigationView) findViewById(R.id.nav_view);
View header = navigationView.inflateHeaderView(R.layout.nav_header);
albumart = (ImageView) header.findViewById(R.id.album_art);
songtitle = (TextView) header.findViewById(R.id.song_title);
songartist = (TextView) header.findViewById(R.id.song_artist);
setPanelSlideListeners(panelLayout);
navDrawerRunnable.postDelayed(new Runnable() {
@Override
public void run() {
setupDrawerContent(navigationView);
setupNavigationIcons(navigationView);
}
}, 700);
if (TimberUtils.isMarshmallow()) {
checkPermissionAndThenLoad();
//checkWritePermissions();
} else {
loadEverything();
}
addBackstackListener();
if(Intent.ACTION_VIEW.equals(action)) {
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
MusicPlayer.clearQueue();
MusicPlayer.openFile(getIntent().getData().getPath());
MusicPlayer.playOrPause();
navigateNowplaying.run();
}
}, 350);
}
if (!panelLayout.isPanelHidden() && MusicPlayer.getTrackName() == null ) {
panelLayout.hidePanel();
}
if (playServicesAvailable) {
final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.BOTTOM;
FrameLayout contentRoot = findViewById(R.id.content_root);
contentRoot.addView(LayoutInflater.from(this)
.inflate(R.layout.fragment_cast_mini_controller, null), params);
findViewById(R.id.castMiniController).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startActivity(new Intent(MainActivity.this, ExpandedControllerActivity.class));
}
});
}
}
private void loadEverything() {
Runnable navigation = navigationMap.get(action);
if (navigation != null) {
navigation.run();
} else {
navigateLibrary.run();
}
new initQuickControls().execute("");
}
private void checkPermissionAndThenLoad() {
//check for permission
if (Nammu.checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE) && Nammu.checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
loadEverything();
} else {
if (Nammu.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)) {
Snackbar.make(panelLayout, "Timber will need to read external storage to display songs on your device.",
Snackbar.LENGTH_INDEFINITE)
.setAction("OK", new View.OnClickListener() {
@Override
public void onClick(View view) {
Nammu.askForPermission(MainActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, permissionReadstorageCallback);
}
}).show();
} else {
Nammu.askForPermission(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, permissionReadstorageCallback);
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home: {
if (isNavigatingMain()) {
mDrawerLayout.openDrawer(GravityCompat.START);
} else super.onBackPressed();
return true;
}
}
return super.onOptionsItemSelected(item);
}
@Override
public void onBackPressed() {
if (panelLayout.isPanelExpanded()) {
panelLayout.collapsePanel();
} else if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
mDrawerLayout.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}
private void setupDrawerContent(NavigationView navigationView) {
navigationView.setNavigationItemSelectedListener(
new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(final MenuItem menuItem) {
updatePosition(menuItem);
return true;
}
});
}
private void setupNavigationIcons(NavigationView navigationView) {
//material-icon-lib currently doesn't work with navigationview of design support library 22.2.0+
//set icons manually for now
//https://github.com/code-mc/material-icon-lib/issues/15
if (!isDarkTheme) {
navigationView.getMenu().findItem(R.id.nav_library).setIcon(R.drawable.library_music);
navigationView.getMenu().findItem(R.id.nav_playlists).setIcon(R.drawable.playlist_play);
navigationView.getMenu().findItem(R.id.nav_queue).setIcon(R.drawable.music_note);
navigationView.getMenu().findItem(R.id.nav_folders).setIcon(R.drawable.ic_folder_open_black_24dp);
navigationView.getMenu().findItem(R.id.nav_nowplaying).setIcon(R.drawable.bookmark_music);
navigationView.getMenu().findItem(R.id.nav_settings).setIcon(R.drawable.settings);
navigationView.getMenu().findItem(R.id.nav_about).setIcon(R.drawable.information);
navigationView.getMenu().findItem(R.id.nav_donate).setIcon(R.drawable.payment_black);
} else {
navigationView.getMenu().findItem(R.id.nav_library).setIcon(R.drawable.library_music_white);
navigationView.getMenu().findItem(R.id.nav_playlists).setIcon(R.drawable.playlist_play_white);
navigationView.getMenu().findItem(R.id.nav_queue).setIcon(R.drawable.music_note_white);
navigationView.getMenu().findItem(R.id.nav_folders).setIcon(R.drawable.ic_folder_open_white_24dp);
navigationView.getMenu().findItem(R.id.nav_nowplaying).setIcon(R.drawable.bookmark_music_white);
navigationView.getMenu().findItem(R.id.nav_settings).setIcon(R.drawable.settings_white);
navigationView.getMenu().findItem(R.id.nav_about).setIcon(R.drawable.information_white);
navigationView.getMenu().findItem(R.id.nav_donate).setIcon(R.drawable.payment_white);
}
try {
if (!BillingProcessor.isIabServiceAvailable(this)) {
navigationView.getMenu().removeItem(R.id.nav_donate);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void updatePosition(final MenuItem menuItem) {
runnable = null;
switch (menuItem.getItemId()) {
case R.id.nav_library:
runnable = navigateLibrary;
break;
case R.id.nav_playlists:
runnable = navigatePlaylist;
break;
case R.id.nav_folders:
runnable = navigateFolder;
break;
case R.id.nav_nowplaying:
if (getCastSession() != null) {
startActivity(new Intent(MainActivity.this, ExpandedControlsActivity.class));
} else {
NavigationUtils.navigateToNowplaying(MainActivity.this, false);
}
break;
case R.id.nav_queue:
runnable = navigateQueue;
break;
case R.id.nav_settings:
NavigationUtils.navigateToSettings(MainActivity.this);
break;
case R.id.nav_about:
mDrawerLayout.closeDrawers();
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
Helpers.showAbout(MainActivity.this);
}
}, 350);
break;
case R.id.nav_donate:
startActivity(new Intent(MainActivity.this, DonateActivity.class));
break;
}
if (runnable != null) {
menuItem.setChecked(true);
mDrawerLayout.closeDrawers();
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
runnable.run();
}
}, 350);
}
}
public void setDetailsToHeader() {
String name = MusicPlayer.getTrackName();
String artist = MusicPlayer.getArtistName();
if (name != null && artist != null) {
songtitle.setText(name);
songartist.setText(artist);
}
ImageLoader.getInstance().displayImage(TimberUtils.getAlbumArtUri(MusicPlayer.getCurrentAlbumId()).toString(), albumart,
new DisplayImageOptions.Builder().cacheInMemory(true)
.showImageOnFail(R.drawable.ic_empty_music2)
.resetViewBeforeLoading(true)
.build());
}
@Override
public void onMetaChanged() {
super.onMetaChanged();
setDetailsToHeader();
if (panelLayout.isPanelHidden() && MusicPlayer.getTrackName() != null) {
panelLayout.showPanel();
}
}
@Override
public void onRequestPermissionsResult(
int requestCode, String[] permissions, int[] grantResults) {
Nammu.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
private boolean isNavigatingMain() {
Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
return (currentFragment instanceof MainFragment || currentFragment instanceof QueueFragment
|| currentFragment instanceof PlaylistFragment || currentFragment instanceof FoldersFragment);
}
private void addBackstackListener() {
getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
@Override
public void onBackStackChanged() {
getSupportFragmentManager().findFragmentById(R.id.fragment_container).onResume();
}
});
}
@Override
public int getActivityTheme() {
return isDarkTheme ? R.style.AppThemeNormalDark : R.style.AppThemeNormalLight;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
getSupportFragmentManager().findFragmentById(R.id.fragment_container).onActivityResult(requestCode, resultCode, data);
}
@Override
public void showCastMiniController() {
findViewById(R.id.castMiniController).setVisibility(View.VISIBLE);
findViewById(R.id.quickcontrols_container).setVisibility(View.GONE);
panelLayout.hidePanel();
}
@Override
public void hideCastMiniController() {
findViewById(R.id.castMiniController).setVisibility(View.GONE);
findViewById(R.id.quickcontrols_container).setVisibility(View.VISIBLE);
panelLayout.showPanel();
}
}
================================================
FILE: app/src/main/java/com/naman14/timber/activities/NowPlayingActivity.java
================================================
package com.naman14.timber.activities;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.os.Bundle;
import android.preference.PreferenceManager;
import androidx.annotation.StyleRes;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import com.afollestad.appthemeengine.Config;
import com.afollestad.appthemeengine.customizers.ATEActivityThemeCustomizer;
import com.afollestad.appthemeengine.customizers.ATEStatusBarCustomizer;
import com.afollestad.appthemeengine.customizers.ATEToolbarCustomizer;
import com.naman14.timber.R;
import com.naman14.timber.utils.Constants;
import com.naman14.timber.utils.NavigationUtils;
import com.naman14.timber.utils.PreferencesUtility;
/**
* Created by naman on 01/01/16.
*/
public class NowPlayingActivity extends BaseActivity implements ATEActivityThemeCustomizer, ATEToolbarCustomizer, ATEStatusBarCustomizer {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_nowplaying);
SharedPreferences prefs = getSharedPreferences(Constants.FRAGMENT_ID, Context.MODE_PRIVATE);
String fragmentID = prefs.getString(Constants.NOWPLAYING_FRAGMENT_ID, Constants.TIMBER3);
Fragment fragment = NavigationUtils.getFragmentForNowplayingID(fragmentID);
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.container, fragment).commit();
}
@StyleRes
@Override
public int getActivityTheme() {
return PreferenceManager.getDefaultSharedPreferences(this).getBoolean("dark_theme", false) ? R.style.AppTheme_FullScreen_Dark : R.style.AppTheme_FullScreen_Light;
}
@Override
public int getLightToolbarMode() {
return Config.LIGHT_TOOLBAR_AUTO;
}
@Override
public int getLightStatusBarMode() {
return Config.LIGHT_STATUS_BAR_OFF;
}
@Override
public int getToolbarColor() {
return Color.TRANSPARENT;
}
@Override
public int getStatusBarColor() {
return Color.TRANSPARENT;
}
@Override
public void onResume() {
super.onResume();
if (PreferencesUtility.getInstance(this).didNowplayingThemeChanged()) {
PreferencesUtility.getInstance(this).setNowPlayingThemeChanged(false);
recreate();
}
}
}
================================================
FILE: app/src/main/java/com/naman14/timber/activities/PlaylistDetailActivity.java
================================================
/*
* Copyright (C) 2015 Naman Dwivedi
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package com.naman14.timber.activities;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.provider.MediaStore;
import androidx.annotation.NonNull;
import androidx.annotation.StyleRes;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.appcompat.widget.Toolbar;
import android.transition.Transition;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.afollestad.appthemeengine.Config;
import com.afollestad.appthemeengine.customizers.ATEActivityThemeCustomizer;
import com.afollestad.appthemeengine.customizers.ATEToolbarCustomizer;
import com.afollestad.materialdialogs.DialogAction;
import com.afollestad.materialdialogs.MaterialDialog;
import com.naman14.timber.R;
import com.naman14.timber.adapters.SongsListAdapter;
import com.naman14.timber.dataloaders.LastAddedLoader;
import com.naman14.timber.dataloaders.PlaylistLoader;
import com.naman14.timber.dataloaders.PlaylistSongLoader;
import com.naman14.timber.dataloaders.SongLoader;
import com.naman14.timber.dataloaders.TopTracksLoader;
import com.naman14.timber.listeners.SimplelTransitionListener;
import com.naman14.timber.models.Song;
import com.naman14.timber.utils.Constants;
import com.naman14.timber.utils.TimberUtils;
import com.naman14.timber.widgets.DividerItemDecoration;
import com.naman14.timber.widgets.DragSortRecycler;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import java.util.HashMap;
import java.util.List;
public class PlaylistDetailActivity extends BaseActivity implements ATEActivityThemeCustomizer, ATEToolbarCustomizer {
private String action;
private long playlistID;
private HashMap playlistsMap = new HashMap<>();
private AppCompatActivity mContext = PlaylistDetailActivity.this;
private SongsListAdapter mAdapter;
private RecyclerView recyclerView;
private ImageView blurFrame;
private TextView playlistname;
private View foreground;
private boolean animate;
private Runnable playlistLastAdded = new Runnable() {
public void run() {
new loadLastAdded().execute("");
}
};
private Runnable playlistRecents = new Runnable() {
@Override
public void run() {
new loadRecentlyPlayed().execute("");
}
};
private Runnable playlistToptracks = new Runnable() {
@Override
public void run() {
new loadTopTracks().execute("");
}
};
private Runnable playlistUsercreated = new Runnable() {
@Override
public void run() {
new loadUserCreatedPlaylist().execute("");
}
};
@TargetApi(21)
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_playlist_detail);
action = getIntent().getAction();
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle("");
playlistsMap.put(Constants.NAVIGATE_PLAYLIST_LASTADDED, playlistLastAdded);
playlistsMap.put(Constants.NAVIGATE_PLAYLIST_RECENT, playlistRecents);
playlistsMap.put(Constants.NAVIGATE_PLAYLIST_TOPTRACKS, playlistToptracks);
playlistsMap.put(Constants.NAVIGATE_PLAYLIST_USERCREATED, playlistUsercreated);
recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
blurFrame = (ImageView) findViewById(R.id.blurFrame);
playlistname = (TextView) findViewById(R.id.name);
foreground = findViewById(R.id.foreground);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
setAlbumart();
animate = getIntent().getBooleanExtra(Constants.ACTIVITY_TRANSITION, false);
if (animate && TimberUtils.isLollipop()) {
if(savedInstanceState != null && savedInstanceState.containsKey("ROTATION_RECREATION")){
setUpSongs();
}
else{
getWindow().getEnterTransition().addListener(new EnterTransitionListener());
}
} else {
setUpSongs();
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("ROTATION_RECREATION", "Rotacion");
}
private void setAlbumart() {
playlistname.setText(getIntent().getExtras().getString(Constants.PLAYLIST_NAME));
foreground.setBackgroundColor(getIntent().getExtras().getInt(Constants.PLAYLIST_FOREGROUND_COLOR));
loadBitmap(TimberUtils.getAlbumArtUri(getIntent().getExtras().getLong(Constants.ALBUM_ID)).toString());
}
private void setUpSongs() {
Runnable navigation = playlistsMap.get(action);
if (navigation != null) {
navigation.run();
DragSortRecycler dragSortRecycler = new DragSortRecycler();
dragSortRecycler.setViewHandleId(R.id.reorder);
dragSortRecycler.setOnItemMovedListener(new DragSortRecycler.OnItemMovedListener() {
@Override
public void onItemMoved(int from, int to) {
Log.d("playlist", "onItemMoved " + from + " to " + to);
Song song = mAdapter.getSongAt(from);
mAdapter.removeSongAt(from);
mAdapter.addSongTo(to, song);
mAdapter.notifyDataSetChanged();
MediaStore.Audio.Playlists.Members.moveItem(getContentResolver(),
playlistID, from, to);
}
});
recyclerView.addItemDecoration(dragSortRecycler);
recyclerView.addOnItemTouchListener(dragSortRecycler);
recyclerView.addOnScrollListener(dragSortRecycler.getScrollListener());
} else {
Log.d("PlaylistDetail", "mo action specified");
}
}
private void loadBitmap(String uri) {
ImageLoader.getInstance().displayImage(uri, blurFrame,
new DisplayImageOptions.Builder().cacheInMemory(true)
.showImageOnFail(R.drawable.ic_empty_music2)
.resetViewBeforeLoading(true)
.build());
}
private void setRecyclerViewAapter() {
recyclerView.setAdapter(mAdapter);
if (animate && TimberUtils.isLollipop()) {
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
recyclerView.addItemDecoration(new DividerItemDecoration(mContext, DividerItemDecoration.VERTICAL_LIST, R.drawable.item_divider_white));
}
}, 250);
} else
recyclerView.addItemDecoration(new DividerItemDecoration(mContext, DividerItemDecoration.VERTICAL_LIST, R.drawable.item_divider_white));
}
@StyleRes
@Override
public int getActivityTheme() {
return PreferenceManager.getDefaultSharedPreferences(this).getBoolean("dark_theme", false) ? R.style.AppTheme_FullScreen_Dark : R.style.AppTheme_FullScreen_Light;
}
private class loadLastAdded extends AsyncTask {
@Override
protected String doInBackground(String... params) {
List lastadded = LastAddedLoader.getLastAddedSongs(mContext);
mAdapter = new SongsListAdapter(mContext, lastadded, true, animate);
mAdapter.setPlaylistId(playlistID);
return "Executed";
}
@Override
protected void onPostExecute(String result) {
setRecyclerViewAapter();
}
@Override
protected void onPreExecute() {
}
}
private class loadRecentlyPlayed extends AsyncTask {
@Override
protected String doInBackground(String... params) {
TopTracksLoader loader = new TopTracksLoader(mContext, TopTracksLoader.QueryType.RecentSongs);
List recentsongs = SongLoader.getSongsForCursor(TopTracksLoader.getCursor());
mAdapter = new SongsListAdapter(mContext, recentsongs, true, animate);
mAdapter.setPlaylistId(playlistID);
return "Executed";
}
@Override
protected void onPostExecute(String result) {
setRecyclerViewAapter();
}
@Override
protected void onPreExecute() {
}
}
private class loadTopTracks extends AsyncTask {
@Override
protected String doInBackground(String... params) {
TopTracksLoader loader = new TopTracksLoader(mContext, TopTracksLoader.QueryType.TopTracks);
List toptracks = SongLoader.getSongsForCursor(TopTracksLoader.getCursor());
mAdapter = new SongsListAdapter(mContext, toptracks, true, animate);
mAdapter.setPlaylistId(playlistID);
return "Executed";
}
@Override
protected void onPostExecute(String result) {
setRecyclerViewAapter();
}
@Override
protected void onPreExecute() {
}
}
private class loadUserCreatedPlaylist extends AsyncTask {
@Override
protected String doInBackground(String... params) {
playlistID = getIntent().getExtras().getLong(Constants.PLAYLIST_ID);
List playlistsongs = PlaylistSongLoader.getSongsInPlaylist(mContext, playlistID);
mAdapter = new SongsListAdapter(mContext, playlistsongs, true, animate);
mAdapter.setPlaylistId(playlistID);
return "Executed";
}
@Override
protected void onPostExecute(String result) {
setRecyclerViewAapter();
}
@Override
protected void onPreExecute() {
}
}
private class EnterTransitionListener extends SimplelTransitionListener {
@TargetApi(21)
public void onTransitionEnd(Transition paramTransition) {
setUpSongs();
}
public void onTransitionStart(Transition paramTransition) {
}
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.menu_playlist_detail, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (action.equals(Constants.NAVIGATE_PLAYLIST_USERCREATED)) {
menu.findItem(R.id.action_delete_playlist).setVisible(true);
menu.findItem(R.id.action_clear_auto_playlist).setVisible(false);
} else {
menu.findItem(R.id.action_delete_playlist).setVisible(false);
menu.findItem(R.id.action_clear_auto_playlist).setTitle("Clear " + playlistname.getText().toString());
}
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
super.onBackPressed();
return true;
case R.id.action_delete_playlist:
showDeletePlaylistDialog();
break;
case R.id.action_clear_auto_playlist:
clearAutoPlaylists();
break;
default:
break;
}
return super.onOptionsItemSelected(item);
}
private void showDeletePlaylistDialog() {
new MaterialDialog.Builder(this)
.title("Delete playlist?")
.content("Are you sure you want to delete playlist " + playlistname.getText().toString() + " ?")
.positiveText("Delete")
.negativeText("Cancel")
.onPositive(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
PlaylistLoader.deletePlaylists(PlaylistDetailActivity.this, playlistID);
Intent returnIntent = new Intent();
setResult(Activity.RESULT_OK, returnIntent);
finish();
}
})
.onNegative(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
dialog.dismiss();
}
})
.show();
}
private void clearAutoPlaylists() {
switch (action) {
case Constants.NAVIGATE_PLAYLIST_LASTADDED:
TimberUtils.clearLastAdded(this);
break;
case Constants.NAVIGATE_PLAYLIST_RECENT:
TimberUtils.clearRecent(this);
break;
case Constants.NAVIGATE_PLAYLIST_TOPTRACKS:
TimberUtils.clearTopTracks(this);
break;
}
Intent returnIntent = new Intent();
setResult(Activity.RESULT_OK, returnIntent);
finish();
}
@Override
public void onMetaChanged() {
super.onMetaChanged();
if (mAdapter != null)
mAdapter.notifyDataSetChanged();
}
@Override
public int getToolbarColor() {
return Color.TRANSPARENT;
}
@Override
public int getLightToolbarMode() {
return Config.LIGHT_TOOLBAR_AUTO;
}
}
================================================
FILE: app/src/main/java/com/naman14/timber/activities/SearchActivity.java
================================================
/*
* Copyright (C) 2015 Naman Dwivedi
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package com.naman14.timber.activities;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.core.view.MenuItemCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import com.naman14.timber.R;
import com.naman14.timber.adapters.SearchAdapter;
import com.naman14.timber.dataloaders.AlbumLoader;
import com.naman14.timber.dataloaders.ArtistLoader;
import com.naman14.timber.dataloaders.SongLoader;
import com.naman14.timber.models.Album;
import com.naman14.timber.models.Artist;
import com.naman14.timber.models.Song;
import com.naman14.timber.provider.SearchHistory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class SearchActivity extends BaseActivity implements SearchView.OnQueryTextListener, View.OnTouchListener {
private final Executor mSearchExecutor = Executors.newSingleThreadExecutor();
@Nullable
private AsyncTask mSearchTask = null;
private SearchView mSearchView;
private InputMethodManager mImm;
private String queryString;
private SearchAdapter adapter;
private RecyclerView recyclerView;
private List