aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--app/src/main/AndroidManifest.xml16
-rw-r--r--app/src/main/java/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java8
-rw-r--r--app/src/main/java/github/daneren2005/dsub/domain/SearchResult.java10
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/NowPlayingFragment.java191
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java13
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/SubsonicFragment.java226
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/AutoMediaBrowserService.java231
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/DownloadService.java114
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/BackgroundTask.java3
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/Constants.java1
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/ImageLoader.java27
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/SilentServiceTask.java41
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/UpdateHelper.java263
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientBase.java66
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientHelper.java32
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java29
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientLP.java495
-rw-r--r--app/src/main/res/drawable-hdpi/action_toggle_list_dark.pngbin601 -> 1031 bytes
-rw-r--r--app/src/main/res/drawable-hdpi/action_toggle_list_light.pngbin616 -> 1165 bytes
-rw-r--r--app/src/main/res/drawable-hdpi/ic_action_rating_bad.pngbin0 -> 817 bytes
-rw-r--r--app/src/main/res/drawable-hdpi/ic_action_rating_bad_selected.pngbin559 -> 584 bytes
-rw-r--r--app/src/main/res/drawable-hdpi/ic_action_rating_good.pngbin0 -> 835 bytes
-rw-r--r--app/src/main/res/drawable-hdpi/ic_action_rating_good_selected.pngbin550 -> 577 bytes
-rw-r--r--app/src/main/res/drawable-hdpi/ic_toggle_star.pngbin904 -> 969 bytes
-rw-r--r--app/src/main/res/drawable-hdpi/ic_toggle_star_outline.pngbin0 -> 1322 bytes
-rw-r--r--app/src/main/res/drawable-mdpi/action_toggle_list_dark.pngbin381 -> 669 bytes
-rw-r--r--app/src/main/res/drawable-mdpi/action_toggle_list_light.pngbin414 -> 745 bytes
-rw-r--r--app/src/main/res/drawable-mdpi/ic_action_rating_bad.pngbin0 -> 469 bytes
-rw-r--r--app/src/main/res/drawable-mdpi/ic_action_rating_bad_selected.pngbin345 -> 374 bytes
-rw-r--r--app/src/main/res/drawable-mdpi/ic_action_rating_good.pngbin0 -> 459 bytes
-rw-r--r--app/src/main/res/drawable-mdpi/ic_action_rating_good_selected.pngbin326 -> 357 bytes
-rw-r--r--app/src/main/res/drawable-mdpi/ic_toggle_star.pngbin612 -> 664 bytes
-rw-r--r--app/src/main/res/drawable-mdpi/ic_toggle_star_outline.pngbin0 -> 844 bytes
-rw-r--r--app/src/main/res/drawable-xhdpi/action_toggle_list_dark.pngbin931 -> 1448 bytes
-rw-r--r--app/src/main/res/drawable-xhdpi/action_toggle_list_light.pngbin958 -> 1556 bytes
-rw-r--r--app/src/main/res/drawable-xhdpi/ic_action_rating_bad.pngbin0 -> 898 bytes
-rw-r--r--app/src/main/res/drawable-xhdpi/ic_action_rating_bad_selected.pngbin655 -> 681 bytes
-rw-r--r--app/src/main/res/drawable-xhdpi/ic_action_rating_good.pngbin0 -> 900 bytes
-rw-r--r--app/src/main/res/drawable-xhdpi/ic_action_rating_good_selected.pngbin648 -> 665 bytes
-rw-r--r--app/src/main/res/drawable-xhdpi/ic_toggle_star.pngbin1260 -> 1367 bytes
-rw-r--r--app/src/main/res/drawable-xhdpi/ic_toggle_star_outline.pngbin0 -> 1864 bytes
-rw-r--r--app/src/main/res/drawable-xxhdpi/action_toggle_list_dark.pngbin1545 -> 2436 bytes
-rw-r--r--app/src/main/res/drawable-xxhdpi/action_toggle_list_light.pngbin1577 -> 2659 bytes
-rw-r--r--app/src/main/res/drawable-xxhdpi/ic_action_rating_bad.pngbin0 -> 1662 bytes
-rw-r--r--app/src/main/res/drawable-xxhdpi/ic_action_rating_bad_selected.pngbin1018 -> 1074 bytes
-rw-r--r--app/src/main/res/drawable-xxhdpi/ic_action_rating_good.pngbin0 -> 1681 bytes
-rw-r--r--app/src/main/res/drawable-xxhdpi/ic_action_rating_good_selected.pngbin1085 -> 1116 bytes
-rw-r--r--app/src/main/res/drawable-xxhdpi/ic_toggle_star.pngbin1930 -> 2060 bytes
-rw-r--r--app/src/main/res/drawable-xxhdpi/ic_toggle_star_outline.pngbin0 -> 2882 bytes
-rw-r--r--app/src/main/res/drawable-xxxhdpi/action_toggle_list_dark.pngbin2390 -> 3602 bytes
-rw-r--r--app/src/main/res/drawable-xxxhdpi/action_toggle_list_light.pngbin2397 -> 3848 bytes
-rw-r--r--app/src/main/res/drawable-xxxhdpi/ic_action_rating_bad.pngbin0 -> 1730 bytes
-rw-r--r--app/src/main/res/drawable-xxxhdpi/ic_action_rating_bad_selected.pngbin1558 -> 1603 bytes
-rw-r--r--app/src/main/res/drawable-xxxhdpi/ic_action_rating_good.pngbin0 -> 1731 bytes
-rw-r--r--app/src/main/res/drawable-xxxhdpi/ic_action_rating_good_selected.pngbin1695 -> 1650 bytes
-rw-r--r--app/src/main/res/drawable-xxxhdpi/ic_toggle_star.pngbin2798 -> 2986 bytes
-rw-r--r--app/src/main/res/drawable-xxxhdpi/ic_toggle_star_outline.pngbin0 -> 4166 bytes
-rw-r--r--app/src/main/res/values/strings.xml2
-rw-r--r--app/src/main/res/xml/auto_app_description.xml4
60 files changed, 1329 insertions, 445 deletions
diff --git a/README.md b/README.md
index 23324cd7..e7d83ca4 100644
--- a/README.md
+++ b/README.md
@@ -29,5 +29,5 @@ Media Icons are double standard size. On https://romannurik.github.io/AndroidAs
```
PARAM_RESOURCES.iconSize = {w: 64, h: 64}
PARAM_RESOURCES['targetRect-clipart'] = { x: 12, y: 12, w: 40, h: 40 }
-form.fields[0].params.maxFinalSize = {w: 512, h: 512}
+form.fields_[0].params_.maxFinalSize = {w: 512, h: 512}
```
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index eeb0cc75..edd354d6 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -90,6 +90,15 @@
<service android:name=".service.DownloadService"
android:label="DSub Playback Service"/>
+
+ <service android:name=".service.AutoMediaBrowserService"
+ android:exported="true">
+
+ <intent-filter>
+ <action android:name="android.media.browse.MediaBrowserService"/>
+ </intent-filter>
+ </service>
+
<service android:name="org.fourthline.cling.android.AndroidUpnpServiceImpl"/>
<service android:name="github.daneren2005.dsub.service.sync.AuthenticatorService">
<intent-filter>
@@ -241,6 +250,11 @@
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
- </application>
+
+ <meta-data android:name="com.google.android.gms.car.application"
+ android:resource="@xml/auto_app_description"/>
+ <meta-data android:name="com.google.android.gms.car.notification.SmallIcon"
+ android:resource="@drawable/stat_notify_playing" />
+ </application>
</manifest>
diff --git a/app/src/main/java/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java b/app/src/main/java/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java
index 0dbb643f..72b63e69 100644
--- a/app/src/main/java/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java
+++ b/app/src/main/java/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java
@@ -850,8 +850,9 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo
@Override
public void onSongsChanged(List<DownloadFile> songs, DownloadFile currentPlaying, int currentPlayingIndex) {
- if(this.currentPlaying != currentPlaying || currentPlaying == null) {
+ if(this.currentPlaying != currentPlaying || this.currentPlaying == null) {
onSongChanged(currentPlaying, currentPlayingIndex);
+ onMetadataUpdate(currentPlaying != null ? currentPlaying.getSong() : null, DownloadService.METADATA_UPDATED_ALL);
}
}
@@ -867,4 +868,9 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo
startButton.setImageResource(typedArray.getResourceId(0, 0));
typedArray.recycle();
}
+
+ @Override
+ public void onMetadataUpdate(MusicDirectory.Entry entry, int fieldChange) {
+
+ }
}
diff --git a/app/src/main/java/github/daneren2005/dsub/domain/SearchResult.java b/app/src/main/java/github/daneren2005/dsub/domain/SearchResult.java
index 3427f2ca..4d761b02 100644
--- a/app/src/main/java/github/daneren2005/dsub/domain/SearchResult.java
+++ b/app/src/main/java/github/daneren2005/dsub/domain/SearchResult.java
@@ -49,4 +49,14 @@ public class SearchResult implements Serializable {
public List<MusicDirectory.Entry> getSongs() {
return songs;
}
+
+ public boolean hasArtists() {
+ return !artists.isEmpty();
+ }
+ public boolean hasAlbums() {
+ return !albums.isEmpty();
+ }
+ public boolean hasSongs() {
+ return !songs.isEmpty();
+ }
} \ No newline at end of file
diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/NowPlayingFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/NowPlayingFragment.java
index 281491af..5a3b8cf5 100644
--- a/app/src/main/java/github/daneren2005/dsub/fragments/NowPlayingFragment.java
+++ b/app/src/main/java/github/daneren2005/dsub/fragments/NowPlayingFragment.java
@@ -59,6 +59,7 @@ import github.daneren2005.dsub.activity.SubsonicFragmentActivity;
import github.daneren2005.dsub.adapter.SectionAdapter;
import github.daneren2005.dsub.audiofx.EqualizerController;
import github.daneren2005.dsub.domain.Bookmark;
+import github.daneren2005.dsub.domain.MusicDirectory;
import github.daneren2005.dsub.domain.PlayerState;
import github.daneren2005.dsub.domain.RepeatMode;
import github.daneren2005.dsub.domain.ServerInfo;
@@ -192,24 +193,7 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
starButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- DownloadFile currentDownload = getDownloadService().getCurrentPlaying();
- if (currentDownload != null) {
- final Entry currentSong = currentDownload.getSong();
- toggleStarred(currentSong, new OnStarChange() {
- @Override
- void starChange(boolean starred) {
- if(currentSong.isStarred()) {
- starButton.setImageDrawable(DrawableTint.getTintedDrawable(context, R.drawable.ic_toggle_star));
- } else {
- if(context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
- starButton.setImageResource(DrawableTint.getDrawableRes(context, R.attr.star_outline));
- } else {
- starButton.setImageResource(R.drawable.ic_toggle_star_outline_dark);
- }
- }
- }
- });
- }
+ getDownloadService().toggleStarred();
}
});
} else {
@@ -356,37 +340,7 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
if(downloadService == null) {
return;
}
-
- DownloadFile downloadFile = downloadService.getCurrentPlaying();
- if(downloadFile == null) {
- return;
- }
- Entry entry = downloadFile.getSong();
-
- // If rating == 1, already set so unset
- if(entry.getRating() == 1) {
- setRating(entry, 0);
-
- if(context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
- rateBadButton.setImageResource(R.drawable.ic_action_rating_bad_dark);
- } else {
- rateBadButton.setImageResource(DrawableTint.getDrawableRes(context, R.attr.rating_bad));
- }
- } else {
- // Immediately skip to the next song
- downloadService.next(true);
-
- // Otherwise set rating to 1
- setRating(entry, 1);
- rateBadButton.setImageDrawable(DrawableTint.getTintedDrawable(context, R.drawable.ic_action_rating_bad_selected));
-
- // Make sure good rating is blank
- if (context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
- rateGoodButton.setImageResource(R.drawable.ic_action_rating_good_dark);
- } else {
- rateGoodButton.setImageResource(DrawableTint.getDrawableRes(context, R.attr.rating_good));
- }
- }
+ downloadService.toggleRating(1);
}
});
rateGoodButton.setOnClickListener(new View.OnClickListener() {
@@ -396,34 +350,7 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
if(downloadService == null) {
return;
}
-
- DownloadFile downloadFile = downloadService.getCurrentPlaying();
- if(downloadFile == null) {
- return;
- }
- Entry entry = downloadFile.getSong();
-
- // If rating == 5, already set so unset
- if(entry.getRating() == 5) {
- setRating(entry, 0);
-
- if (context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
- rateGoodButton.setImageResource(R.drawable.ic_action_rating_good_dark);
- } else {
- rateGoodButton.setImageResource(DrawableTint.getDrawableRes(context, R.attr.rating_good));
- }
- } else {
- // Otherwise set rating to maximum
- setRating(entry, 5);
- rateGoodButton.setImageDrawable(DrawableTint.getTintedDrawable(context, R.drawable.ic_action_rating_good_selected));
-
- // Make sure bad rating is blank
- if (context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
- rateBadButton.setImageResource(R.drawable.ic_action_rating_bad_dark);
- } else {
- rateBadButton.setImageResource(DrawableTint.getDrawableRes(context, R.attr.rating_bad));
- }
- }
+ downloadService.toggleRating(5);
}
});
@@ -497,7 +424,11 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
if(downloadService != null && downloadService.getSleepTimer()) {
int timeRemaining = downloadService.getSleepTimeRemaining();
timerMenu = menu.findItem(R.id.menu_toggle_timer);
- timerMenu.setTitle(context.getResources().getString(R.string.download_stop_time_remaining, Util.formatDuration(timeRemaining)));
+ if(timeRemaining > 1){
+ timerMenu.setTitle(context.getResources().getString(R.string.download_stop_time_remaining, Util.formatDuration(timeRemaining)));
+ } else {
+ timerMenu.setTitle(R.string.menu_set_timer);
+ }
}
if(downloadService != null && downloadService.getKeepScreenOn()) {
menu.findItem(R.id.menu_screen_on_off).setChecked(true);
@@ -690,10 +621,10 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
createNewPlaylist(entries, true);
return true;
case R.id.menu_star:
- toggleStarred(song.getSong());
+ UpdateHelper.toggleStarred(context, song.getSong());
return true;
case R.id.menu_rate:
- setRating(song.getSong());
+ UpdateHelper.setRating(context, song.getSong());
return true;
case R.id.menu_toggle_timer:
if(getDownloadService().getSleepTimer()) {
@@ -1089,7 +1020,7 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
MusicService musicService = MusicServiceFactory.getMusicService(context);
musicService.createBookmark(currentSong, position, comment, context, null);
- new EntryInstanceUpdater(currentSong) {
+ new UpdateHelper.EntryInstanceUpdater(currentSong) {
@Override
public void update(Entry found) {
found.setBookmark(new Bookmark(position));
@@ -1231,54 +1162,10 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
Entry song = currentPlaying.getSong();
songTitleTextView.setText(song.getTitle());
getImageLoader().loadImage(albumArtImageView, song, true, true);
- if(song.isStarred()) {
- starButton.setImageDrawable(DrawableTint.getTintedDrawable(context, R.drawable.ic_toggle_star));
- } else {
- if(context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
- starButton.setImageResource(DrawableTint.getDrawableRes(context, R.attr.star_outline));
- } else {
- starButton.setImageResource(R.drawable.ic_toggle_star_outline_dark);
- }
- }
setSubtitle(context.getResources().getString(R.string.download_playing_out_of, currentPlayingIndex + 1, currentPlayingSize));
-
- int badRating, goodRating, bookmark;
- if(song.getRating() == 1) {
- rateBadButton.setImageDrawable(DrawableTint.getTintedDrawable(context, R.drawable.ic_action_rating_bad_selected));
- } else {
- if(context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
- badRating = R.drawable.ic_action_rating_bad_dark;
- } else {
- badRating = DrawableTint.getDrawableRes(context, R.attr.rating_bad);
- }
- rateBadButton.setImageResource(badRating);
- }
-
- if(song.getRating() == 5) {
- rateGoodButton.setImageDrawable(DrawableTint.getTintedDrawable(context, R.drawable.ic_action_rating_good_selected));
- } else {
- if(context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
- goodRating = R.drawable.ic_action_rating_good_dark;
- } else {
- goodRating = DrawableTint.getDrawableRes(context, R.attr.rating_good);
- }
- rateGoodButton.setImageResource(goodRating);
- }
-
- if(song.getBookmark() != null) {
- bookmarkButton.setImageDrawable(DrawableTint.getTintedDrawable(context, R.drawable.ic_menu_bookmark_selected));
- } else {
- if(context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
- bookmark = R.drawable.ic_menu_bookmark_dark;
- } else {
- bookmark = DrawableTint.getDrawableRes(context, R.attr.bookmark);
- }
- bookmarkButton.setImageResource(bookmark);
- }
} else {
songTitleTextView.setText(null);
getImageLoader().loadImage(albumArtImageView, (Entry) null, true, false);
- starButton.setImageResource(R.drawable.ic_toggle_star_outline_dark);
setSubtitle(null);
}
}
@@ -1315,6 +1202,7 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
setSubtitle(context.getResources().getString(R.string.download_playing_out_of, currentPlayingIndex + 1, currentPlayingSize));
if(this.currentPlaying != currentPlaying) {
onSongChanged(currentPlaying, currentPlayingIndex);
+ onMetadataUpdate(currentPlaying != null ? currentPlaying.getSong() : null, DownloadService.METADATA_UPDATED_ALL);
}
}
@@ -1344,7 +1232,11 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
DownloadService downloadService = getDownloadService();
if(downloadService.getSleepTimer() && timerMenu != null) {
int timeRemaining = downloadService.getSleepTimeRemaining();
- timerMenu.setTitle(context.getResources().getString(R.string.download_stop_time_remaining, Util.formatDuration(timeRemaining)));
+ if(timeRemaining > 1){
+ timerMenu.setTitle(context.getResources().getString(R.string.download_stop_time_remaining, Util.formatDuration(timeRemaining)));
+ } else {
+ timerMenu.setTitle(R.string.menu_set_timer);
+ }
}
}
@@ -1397,6 +1289,53 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
}
}
+ @Override
+ public void onMetadataUpdate(Entry song, int fieldChange) {
+ if(song != null && song.isStarred()) {
+ starButton.setImageDrawable(DrawableTint.getTintedDrawable(context, R.drawable.ic_toggle_star));
+ } else {
+ if(context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ starButton.setImageResource(DrawableTint.getDrawableRes(context, R.attr.star_outline));
+ } else {
+ starButton.setImageResource(R.drawable.ic_toggle_star_outline_dark);
+ }
+ }
+
+ int badRating, goodRating, bookmark;
+ if(song != null && song.getRating() == 1) {
+ rateBadButton.setImageDrawable(DrawableTint.getTintedDrawable(context, R.drawable.ic_action_rating_bad_selected));
+ } else {
+ if(context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
+ badRating = R.drawable.ic_action_rating_bad_dark;
+ } else {
+ badRating = DrawableTint.getDrawableRes(context, R.attr.rating_bad);
+ }
+ rateBadButton.setImageResource(badRating);
+ }
+
+ if(song != null && song.getRating() == 5) {
+ rateGoodButton.setImageDrawable(DrawableTint.getTintedDrawable(context, R.drawable.ic_action_rating_good_selected));
+ } else {
+ if(context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
+ goodRating = R.drawable.ic_action_rating_good_dark;
+ } else {
+ goodRating = DrawableTint.getDrawableRes(context, R.attr.rating_good);
+ }
+ rateGoodButton.setImageResource(goodRating);
+ }
+
+ if(song != null && song.getBookmark() != null) {
+ bookmarkButton.setImageDrawable(DrawableTint.getTintedDrawable(context, R.drawable.ic_menu_bookmark_selected));
+ } else {
+ if(context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
+ bookmark = R.drawable.ic_menu_bookmark_dark;
+ } else {
+ bookmark = DrawableTint.getDrawableRes(context, R.attr.bookmark);
+ }
+ bookmarkButton.setImageResource(bookmark);
+ }
+ }
+
public void updateRepeatButton() {
DownloadService downloadService = getDownloadService();
switch (downloadService.getRepeatMode()) {
diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java
index a1129a45..c7d8e191 100644
--- a/app/src/main/java/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java
+++ b/app/src/main/java/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java
@@ -57,6 +57,7 @@ import github.daneren2005.dsub.util.LoadingTask;
import github.daneren2005.dsub.util.Pair;
import github.daneren2005.dsub.util.SilentBackgroundTask;
import github.daneren2005.dsub.util.TabBackgroundTask;
+import github.daneren2005.dsub.util.UpdateHelper;
import github.daneren2005.dsub.util.UserUtil;
import github.daneren2005.dsub.util.Util;
import github.daneren2005.dsub.view.FastScroller;
@@ -1037,7 +1038,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section
musicService.setStarred(entries, artists, albums, false, this, context);
for(Entry entry: unstar) {
- new EntryInstanceUpdater(entry) {
+ new UpdateHelper.EntryInstanceUpdater(entry) {
@Override
public void update(Entry found) {
found.setStarred(false);
@@ -1382,10 +1383,10 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section
starButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- toggleStarred(directory, new OnStarChange() {
+ UpdateHelper.toggleStarred(context, directory, new UpdateHelper.OnStarChange() {
@Override
- void starChange(boolean starred) {
- if(directory.isStarred()) {
+ public void starChange(boolean starred) {
+ if (directory.isStarred()) {
starButton.setImageResource(DrawableTint.getDrawableRes(context, R.attr.star_outline));
starButton.setImageDrawable(DrawableTint.getTintedDrawable(context, R.drawable.ic_toggle_star));
} else {
@@ -1406,9 +1407,9 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section
ratingBarWrapper.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- setRating(directory, new OnRatingChange() {
+ UpdateHelper.setRating(context, directory, new UpdateHelper.OnRatingChange() {
@Override
- void ratingChange(int rating) {
+ public void ratingChange(int rating) {
ratingBar.setRating(directory.getRating());
}
});
diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SubsonicFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SubsonicFragment.java
index 8672f534..7baf9009 100644
--- a/app/src/main/java/github/daneren2005/dsub/fragments/SubsonicFragment.java
+++ b/app/src/main/java/github/daneren2005/dsub/fragments/SubsonicFragment.java
@@ -77,6 +77,7 @@ import github.daneren2005.dsub.util.MenuUtil;
import github.daneren2005.dsub.util.ProgressListener;
import github.daneren2005.dsub.util.SilentBackgroundTask;
import github.daneren2005.dsub.util.LoadingTask;
+import github.daneren2005.dsub.util.UpdateHelper;
import github.daneren2005.dsub.util.UserUtil;
import github.daneren2005.dsub.util.Util;
import github.daneren2005.dsub.view.AlbumView;
@@ -327,7 +328,7 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
deleteRecursively(artist);
break;
case R.id.artist_menu_star:
- toggleStarred(artist);
+ UpdateHelper.toggleStarred(context, artist);
break;
case R.id.album_menu_play_now:
artistOverride = true;
@@ -354,7 +355,7 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
downloadRecursively(entry.getId(), true, true, false, false, true);
break;
case R.id.album_menu_star:
- toggleStarred(entry);
+ UpdateHelper.toggleStarred(context, entry);
break;
case R.id.album_menu_delete:
deleteRecursively(entry);
@@ -387,7 +388,7 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
addToPlaylist(songs);
break;
case R.id.song_menu_star:
- toggleStarred(entry);
+ UpdateHelper.toggleStarred(context, entry);
break;
case R.id.song_menu_play_external:
playExternalPlayer(entry);
@@ -411,7 +412,7 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
deleteBookmark(entry, null);
break;
case R.id.menu_rate:
- setRating(entry);
+ UpdateHelper.setRating(context, entry);
break;
default:
return false;
@@ -790,106 +791,6 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
dialog.show();
}
- public void toggleStarred(Entry entry) {
- toggleStarred(entry, null);
- }
-
- public void toggleStarred(final Entry entry, final OnStarChange onStarChange) {
- final boolean starred = !entry.isStarred();
- entry.setStarred(starred);
- if(onStarChange != null) {
- onStarChange.starChange(starred);
- }
-
- new SilentBackgroundTask<Void>(context) {
- @Override
- protected Void doInBackground() throws Throwable {
- MusicService musicService = MusicServiceFactory.getMusicService(context);
- if(entry.isDirectory() && Util.isTagBrowsing(context) && !Util.isOffline(context)) {
- if(entry.isAlbum()) {
- musicService.setStarred(null, null, Arrays.asList(entry), starred, null, context);
- } else {
- musicService.setStarred(null, Arrays.asList(entry), null, starred, null, context);
- }
- } else {
- musicService.setStarred(Arrays.asList(entry), null, null, starred, null, context);
- }
-
- new EntryInstanceUpdater(entry) {
- @Override
- public void update(Entry found) {
- found.setStarred(starred);
- }
- }.execute();
-
- return null;
- }
-
- @Override
- protected void done(Void result) {
- // UpdateView
- Util.toast(context, context.getResources().getString(starred ? R.string.starring_content_starred : R.string.starring_content_unstarred, entry.getTitle()));
- }
-
- @Override
- protected void error(Throwable error) {
- Log.w(TAG, "Failed to star", error);
- entry.setStarred(!starred);
- if(onStarChange != null) {
- onStarChange.starChange(!starred);
- }
-
- String msg;
- if (error instanceof OfflineException || error instanceof ServerTooOldException) {
- msg = getErrorMessage(error);
- } else {
- msg = context.getResources().getString(R.string.starring_content_error, entry.getTitle()) + " " + getErrorMessage(error);
- }
-
- Util.toast(context, msg, false);
- }
- }.execute();
- }
-
- public void toggleStarred(final Artist entry) {
- final boolean starred = !entry.isStarred();
- entry.setStarred(starred);
-
- new SilentBackgroundTask<Void>(context) {
- @Override
- protected Void doInBackground() throws Throwable {
- MusicService musicService = MusicServiceFactory.getMusicService(context);
- if(Util.isTagBrowsing(context) && !Util.isOffline(context)) {
- musicService.setStarred(null, Arrays.asList(new Entry(entry)), null, starred, null, context);
- } else {
- musicService.setStarred(Arrays.asList(new Entry(entry)), null, null, starred, null, context);
- }
- return null;
- }
-
- @Override
- protected void done(Void result) {
- // UpdateView
- Util.toast(context, context.getResources().getString(starred ? R.string.starring_content_starred : R.string.starring_content_unstarred, entry.getName()));
- }
-
- @Override
- protected void error(Throwable error) {
- Log.w(TAG, "Failed to star", error);
- entry.setStarred(!starred);
-
- String msg;
- if (error instanceof OfflineException || error instanceof ServerTooOldException) {
- msg = getErrorMessage(error);
- } else {
- msg = context.getResources().getString(R.string.starring_content_error, entry.getName()) + " " + getErrorMessage(error);
- }
-
- Util.toast(context, msg, false);
- }
- }.execute();
- }
-
protected void downloadRecursively(final String id, final boolean save, final boolean append, final boolean autoplay, final boolean shuffle, final boolean background) {
downloadRecursively(id, "", true, save, append, autoplay, shuffle, background);
}
@@ -1737,7 +1638,7 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
MusicService musicService = MusicServiceFactory.getMusicService(context);
musicService.deleteBookmark(entry, context, null);
- new EntryInstanceUpdater(entry) {
+ new UpdateHelper.EntryInstanceUpdater(entry) {
@Override
public void update(Entry found) {
found.setBookmark(null);
@@ -1773,80 +1674,6 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
});
}
- protected void setRating(Entry entry) {
- setRating(entry, null);
- }
- protected void setRating(final Entry entry, final OnRatingChange onRatingChange) {
- View layout = context.getLayoutInflater().inflate(R.layout.rating, null);
- final RatingBar ratingBar = (RatingBar) layout.findViewById(R.id.rating_bar);
- ratingBar.setRating((float) entry.getRating());
-
- AlertDialog.Builder builder = new AlertDialog.Builder(context);
- builder.setTitle(context.getResources().getString(R.string.rating_title, entry.getTitle()))
- .setView(layout)
- .setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- int rating = (int) ratingBar.getRating();
- setRating(entry, rating, onRatingChange);
- }
- })
- .setNegativeButton(R.string.common_cancel, null);
-
- AlertDialog dialog = builder.create();
- dialog.show();
- }
-
- protected void setRating(Entry entry, int rating) {
- setRating(entry, rating, null);
- }
- protected void setRating(final Entry entry, final int rating, final OnRatingChange onRatingChange) {
- final int oldRating = entry.getRating();
- entry.setRating(rating);
-
- if(onRatingChange != null) {
- onRatingChange.ratingChange(rating);
- }
-
- new SilentBackgroundTask<Void>(context) {
- @Override
- protected Void doInBackground() throws Throwable {
- MusicService musicService = MusicServiceFactory.getMusicService(context);
- musicService.setRating(entry, rating, context, null);
-
- new EntryInstanceUpdater(entry) {
- @Override
- public void update(Entry found) {
- found.setRating(rating);
- }
- }.execute();
- return null;
- }
-
- @Override
- protected void done(Void result) {
- Util.toast(context, context.getResources().getString(rating > 0 ? R.string.rating_set_rating : R.string.rating_remove_rating, entry.getTitle()));
- }
-
- @Override
- protected void error(Throwable error) {
- entry.setRating(oldRating);
- if(onRatingChange != null) {
- onRatingChange.ratingChange(oldRating);
- }
-
- String msg;
- if (error instanceof OfflineException || error instanceof ServerTooOldException) {
- msg = getErrorMessage(error);
- } else {
- msg = context.getResources().getString(rating > 0 ? R.string.rating_set_rating_failed : R.string.rating_remove_rating_failed, entry.getTitle()) + " " + getErrorMessage(error);
- }
-
- Util.toast(context, msg, false);
- }
- }.execute();
- }
-
public SectionAdapter getCurrentAdapter() { return null; }
public void stopActionMode() {
SectionAdapter adapter = getCurrentAdapter();
@@ -1965,47 +1792,6 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
}
}
- protected abstract class EntryInstanceUpdater {
- private Entry entry;
-
- public EntryInstanceUpdater(Entry entry) {
- this.entry = entry;
- }
-
- public abstract void update(Entry found);
-
- public void execute() {
- DownloadService downloadService = getDownloadService();
- if(downloadService != null && !entry.isDirectory()) {
- boolean serializeChanges = false;
- List<DownloadFile> downloadFiles = downloadService.getDownloads();
- for(DownloadFile file: downloadFiles) {
- Entry check = file.getSong();
- if(entry.getId().equals(check.getId())) {
- update(entry);
- serializeChanges = true;
- }
- }
-
- if(serializeChanges) {
- downloadService.serializeQueue();
- }
- }
-
- Entry find = UpdateView.findEntry(entry);
- if(find != null) {
- update(find);
- }
- }
- }
-
- public abstract class OnRatingChange {
- abstract void ratingChange(int rating);
- }
- public abstract class OnStarChange {
- abstract void starChange(boolean starred);
- }
-
public abstract class RecursiveLoader extends LoadingTask<Boolean> {
protected MusicService musicService;
protected static final int MAX_SONGS = 500;
diff --git a/app/src/main/java/github/daneren2005/dsub/service/AutoMediaBrowserService.java b/app/src/main/java/github/daneren2005/dsub/service/AutoMediaBrowserService.java
new file mode 100644
index 00000000..d579ef54
--- /dev/null
+++ b/app/src/main/java/github/daneren2005/dsub/service/AutoMediaBrowserService.java
@@ -0,0 +1,231 @@
+/*
+ This file is part of Subsonic.
+
+ Subsonic 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.
+
+ Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2015 (C) Scott Jackson
+*/
+package github.daneren2005.dsub.service;
+
+import android.annotation.TargetApi;
+import android.content.Intent;
+import android.media.MediaDescription;
+import android.media.browse.MediaBrowser;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.service.media.MediaBrowserService;
+import android.support.annotation.Nullable;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.domain.Playlist;
+import github.daneren2005.dsub.domain.ServerInfo;
+import github.daneren2005.dsub.util.Constants;
+import github.daneren2005.dsub.util.SilentBackgroundTask;
+import github.daneren2005.dsub.util.SilentServiceTask;
+import github.daneren2005.dsub.util.Util;
+import github.daneren2005.dsub.util.compat.RemoteControlClientLP;
+
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class AutoMediaBrowserService extends MediaBrowserService {
+ private static final String TAG = AutoMediaBrowserService.class.getSimpleName();
+ private static final String BROWSER_ROOT = "root";
+ private static final String BROWSER_ALBUM_LISTS = "albumLists";
+ private static final String BROWSER_LIBRARY = "library";
+ private static final String BROWSER_PLAYLISTS = "playlists";
+ private static final String PLAYLIST_PREFIX = "pl-";
+ private static final String ALBUM_TYPE_PREFIX = "ty-";
+
+ private DownloadService downloadService;
+ private Handler handler = new Handler();
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ getDownloadService();
+ }
+
+ @Nullable
+ @Override
+ public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
+ BrowserRoot root = new BrowserRoot(BROWSER_ROOT, null);
+ return root;
+ }
+
+ @Override
+ public void onLoadChildren(String parentId, Result<List<MediaBrowser.MediaItem>> result) {
+ if(BROWSER_ROOT.equals(parentId)) {
+ getRootFolders(result);
+ } else if(BROWSER_ALBUM_LISTS.equals(parentId)) {
+ getAlbumLists(result);
+ } else if(parentId.startsWith(ALBUM_TYPE_PREFIX)) {
+ int id = Integer.valueOf(parentId.substring(ALBUM_TYPE_PREFIX.length()));
+ getAlbumList(result, id);
+ } else if(BROWSER_LIBRARY.equals(parentId)) {
+ getLibrary(result);
+ } else if(BROWSER_PLAYLISTS.equals(parentId)) {
+ getPlaylists(result);
+ } else if(parentId.startsWith(PLAYLIST_PREFIX)) {
+ getPlayOptions(result, parentId.substring(PLAYLIST_PREFIX.length()), Constants.INTENT_EXTRA_NAME_PLAYLIST_ID);
+ } else {
+ // No idea what it is, send empty result
+ result.sendResult(new ArrayList<MediaBrowser.MediaItem>());
+ }
+ }
+
+ private void getRootFolders(Result<List<MediaBrowser.MediaItem>> result) {
+ List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>();
+
+ /*MediaDescription.Builder albumLists = new MediaDescription.Builder();
+ albumLists.setTitle(downloadService.getString(R.string.main_albums_title))
+ .setMediaId(BROWSER_ALBUM_LISTS);
+ mediaItems.add(new MediaBrowser.MediaItem(albumLists.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE));
+
+ MediaDescription.Builder library = new MediaDescription.Builder();
+ library.setTitle(downloadService.getString(R.string.button_bar_browse))
+ .setMediaId(BROWSER_LIBRARY);
+ mediaItems.add(new MediaBrowser.MediaItem(library.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE));*/
+
+ MediaDescription.Builder playlists = new MediaDescription.Builder();
+ playlists.setTitle(downloadService.getString(R.string.button_bar_playlists))
+ .setMediaId(BROWSER_PLAYLISTS);
+ mediaItems.add(new MediaBrowser.MediaItem(playlists.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE));
+
+ result.sendResult(mediaItems);
+ }
+
+ private void getAlbumLists(Result<List<MediaBrowser.MediaItem>> result) {
+ List<Integer> albums = new ArrayList<>();
+ albums.add(R.string.main_albums_newest);
+ albums.add(R.string.main_albums_random);
+ if(ServerInfo.checkServerVersion(downloadService, "1.8")) {
+ albums.add(R.string.main_albums_alphabetical);
+ }
+ if(!Util.isTagBrowsing(downloadService)) {
+ albums.add(R.string.main_albums_highest);
+ }
+ // albums.add(R.string.main_albums_starred);
+ // albums.add(R.string.main_albums_genres);
+ // albums.add(R.string.main_albums_year);
+ albums.add(R.string.main_albums_recent);
+ albums.add(R.string.main_albums_frequent);
+
+ List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>();
+
+ for(Integer id: albums) {
+ MediaDescription description = new MediaDescription.Builder()
+ .setTitle(downloadService.getResources().getString(id))
+ .setMediaId(ALBUM_TYPE_PREFIX + id)
+ .build();
+
+ mediaItems.add(new MediaBrowser.MediaItem(description, MediaBrowser.MediaItem.FLAG_BROWSABLE));
+ }
+
+ result.sendResult(mediaItems);
+ }
+ private void getAlbumList(Result<List<MediaBrowser.MediaItem>> result, int id) {
+
+ }
+
+ private void getLibrary(Result<List<MediaBrowser.MediaItem>> result) {
+
+ }
+
+ private void getPlaylists(final Result<List<MediaBrowser.MediaItem>> result) {
+ new SilentServiceTask<List<Playlist>>(downloadService) {
+ @Override
+ protected List<Playlist> doInBackground(MusicService musicService) throws Throwable {
+ return musicService.getPlaylists(false, downloadService, null);
+ }
+
+ @Override
+ protected void done(List<Playlist> playlists) {
+ List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>();
+
+ for(Playlist playlist: playlists) {
+ MediaDescription description = new MediaDescription.Builder()
+ .setTitle(playlist.getName())
+ .setMediaId(PLAYLIST_PREFIX + playlist.getId())
+ .build();
+
+ mediaItems.add(new MediaBrowser.MediaItem(description, MediaBrowser.MediaItem.FLAG_BROWSABLE));
+ }
+
+ result.sendResult(mediaItems);
+ }
+ }.execute();
+
+ result.detach();
+ }
+ private void getPlayOptions(Result<List<MediaBrowser.MediaItem>> result, String id, String idConstant) {
+ List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>();
+
+ Bundle playAllExtras = new Bundle();
+ playAllExtras.putString(idConstant, id);
+
+ MediaDescription.Builder playAll = new MediaDescription.Builder();
+ playAll.setTitle(downloadService.getString(R.string.menu_play))
+ .setMediaId("play-" + id)
+ .setExtras(playAllExtras);
+ mediaItems.add(new MediaBrowser.MediaItem(playAll.build(), MediaBrowser.MediaItem.FLAG_PLAYABLE));
+
+ Bundle shuffleExtras = new Bundle();
+ shuffleExtras.putString(idConstant, id);
+ shuffleExtras.putBoolean(Constants.INTENT_EXTRA_NAME_SHUFFLE, true);
+
+ MediaDescription.Builder shuffle = new MediaDescription.Builder();
+ shuffle.setTitle(downloadService.getString(R.string.menu_shuffle))
+ .setMediaId("shuffle-" + id)
+ .setExtras(shuffleExtras);
+ mediaItems.add(new MediaBrowser.MediaItem(shuffle.build(), MediaBrowser.MediaItem.FLAG_PLAYABLE));
+
+ /*Bundle playLastExtras = new Bundle();
+ playLastExtras.putString(idConstant, id);
+ playLastExtras.putBoolean(Constants.INTENT_EXTRA_PLAY_LAST, true);
+
+ MediaDescription.Builder playLast = new MediaDescription.Builder();
+ playLast.setTitle(downloadService.getString(R.string.menu_play_last))
+ .setMediaId("playLast-" + id)
+ .setExtras(playLastExtras);
+ mediaItems.add(new MediaBrowser.MediaItem(playLast.build(), MediaBrowser.MediaItem.FLAG_PLAYABLE));*/
+
+ result.sendResult(mediaItems);
+ }
+
+ public void getDownloadService() {
+ if(DownloadService.getInstance() == null) {
+ startService(new Intent(this, DownloadService.class));
+ }
+
+ waitForDownloadService();
+ }
+ public void waitForDownloadService() {
+ downloadService = DownloadService.getInstance();
+ if(downloadService == null) {
+ handler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ waitForDownloadService();
+ }
+ }, 100);
+ } else {
+ RemoteControlClientLP remoteControlClient = (RemoteControlClientLP) downloadService.getRemoteControlClient();
+ setSessionToken(remoteControlClient.getMediaSession().getSessionToken());
+ }
+ }
+}
diff --git a/app/src/main/java/github/daneren2005/dsub/service/DownloadService.java b/app/src/main/java/github/daneren2005/dsub/service/DownloadService.java
index 7af21831..d33fa527 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/DownloadService.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/DownloadService.java
@@ -48,8 +48,9 @@ import github.daneren2005.dsub.util.Constants;
import github.daneren2005.dsub.util.MediaRouteManager;
import github.daneren2005.dsub.util.ShufflePlayBuffer;
import github.daneren2005.dsub.util.SimpleServiceBinder;
+import github.daneren2005.dsub.util.UpdateHelper;
import github.daneren2005.dsub.util.Util;
-import github.daneren2005.dsub.util.compat.RemoteControlClientHelper;
+import github.daneren2005.dsub.util.compat.RemoteControlClientBase;
import github.daneren2005.dsub.util.tags.BastpUtil;
import github.daneren2005.dsub.view.UpdateView;
import github.daneren2005.serverproxy.BufferProxy;
@@ -102,11 +103,17 @@ public class DownloadService extends Service {
public static final int REWIND = 10000;
private static final double DELETE_CUTOFF = 0.84;
private static final int REQUIRED_ALBUM_MATCHES = 4;
+ private static final int REMOTE_PLAYLIST_TOTAL = 3;
private static final int SHUFFLE_MODE_NONE = 0;
private static final int SHUFFLE_MODE_ALL = 1;
private static final int SHUFFLE_MODE_ARTIST = 2;
- private RemoteControlClientHelper mRemoteControl;
+ public static final int METADATA_UPDATED_ALL = 0;
+ public static final int METADATA_UPDATED_STAR = 1;
+ public static final int METADATA_UPDATED_RATING = 2;
+ public static final int METADATA_UPDATED_BOOKMARK = 4;
+
+ private RemoteControlClientBase mRemoteControl;
private final IBinder binder = new SimpleServiceBinder<DownloadService>(this);
private Looper mediaPlayerLooper;
@@ -238,7 +245,7 @@ public class DownloadService extends Service {
if (mRemoteControl == null) {
// Use the remote control APIs (if available) to set the playback state
- mRemoteControl = RemoteControlClientHelper.createInstance();
+ mRemoteControl = RemoteControlClientBase.createInstance();
ComponentName mediaButtonReceiverComponent = new ComponentName(getPackageName(), MediaButtonIntentReceiver.class.getName());
mRemoteControl.register(this, mediaButtonReceiverComponent);
}
@@ -442,10 +449,21 @@ public class DownloadService extends Service {
lifecycleSupport.serializeDownloadQueue();
}
- private void updateRemotePlaylist() {
+ private synchronized void updateRemotePlaylist() {
+ List<DownloadFile> playlist = new ArrayList<>();
+ if(currentPlaying != null) {
+ int index = downloadList.indexOf(currentPlaying);
+ int size = size();
+ int end = index + REMOTE_PLAYLIST_TOTAL;
+ for(int i = index; i < size && i < end; i++) {
+ playlist.add(downloadList.get(i));
+ }
+ }
+
if (remoteState != LOCAL && remoteController != null) {
remoteController.updatePlaylist();
}
+ mRemoteControl.updatePlaylist(playlist);
}
public synchronized void restore(List<MusicDirectory.Entry> songs, List<MusicDirectory.Entry> toDelete, int currentPlayingIndex, int currentPlayingPosition) {
@@ -1020,6 +1038,7 @@ public class DownloadService extends Service {
proxy = null;
}
checkDownloads();
+ updateRemotePlaylist();
}
/** Plays or resumes the playback, depending on the current player state. */
@@ -1756,6 +1775,8 @@ public class DownloadService extends Service {
setPlayerState(PAUSED);
onSongProgress();
}
+
+ updateRemotePlaylist();
}
// Only call when starting, setPlayerState(PAUSED) already calls this
@@ -2232,6 +2253,10 @@ public class DownloadService extends Service {
}
clearCurrentBookmark(downloadFile.getSong(), true);
}
+
+ public RemoteControlClientBase getRemoteControlClient() {
+ return mRemoteControl;
+ }
private boolean isPastCutoff() {
return isPastCutoff(getPlayerPosition(), getPlayerDuration());
@@ -2461,6 +2486,55 @@ public class DownloadService extends Service {
}
}
+ public void toggleStarred() {
+ final DownloadFile currentPlaying = this.currentPlaying;
+ if(currentPlaying == null) {
+ return;
+ }
+
+ UpdateHelper.toggleStarred(this, currentPlaying.getSong(), new UpdateHelper.OnStarChange() {
+ @Override
+ public void starChange(boolean starred) {
+ if(currentPlaying == DownloadService.this.currentPlaying) {
+ onMetadataUpdate(METADATA_UPDATED_STAR);
+ }
+ }
+ });
+ }
+ public void toggleRating(int rating) {
+ if(currentPlaying == null) {
+ return;
+ }
+
+ MusicDirectory.Entry entry = currentPlaying.getSong();
+ if(entry.getRating() == rating) {
+ setRating(0);
+ } else {
+ setRating(rating);
+ }
+ }
+ public void setRating(int rating) {
+ final DownloadFile currentPlaying = this.currentPlaying;
+ if(currentPlaying == null) {
+ return;
+ }
+ MusicDirectory.Entry entry = currentPlaying.getSong();
+
+ // Immediately skip to the next song if down thumbed
+ if(rating == 1) {
+ next(true);
+ }
+
+ UpdateHelper.setRating(this, entry, rating, new UpdateHelper.OnRatingChange() {
+ @Override
+ public void ratingChange(int rating) {
+ if(currentPlaying == DownloadService.this.currentPlaying) {
+ onMetadataUpdate(METADATA_UPDATED_RATING);
+ }
+ }
+ });
+ }
+
public void addOnSongChangedListener(OnSongChangedListener listener) {
addOnSongChangedListener(listener, false);
}
@@ -2500,6 +2574,9 @@ public class DownloadService extends Service {
public void run() {
if(revision == atRevision) {
listener.onSongChanged(currentPlaying, currentPlayingIndex);
+
+ MusicDirectory.Entry entry = currentPlaying != null ? currentPlaying.getSong() : null;
+ listener.onMetadataUpdate(entry, METADATA_UPDATED_ALL);
}
}
});
@@ -2542,6 +2619,13 @@ public class DownloadService extends Service {
}
});
}
+
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ mRemoteControl.setPlaybackState(playerState.getRemoteControlClientPlayState());
+ }
+ });
}
private void onStateUpdate() {
final long atRevision = revision;
@@ -2556,6 +2640,27 @@ public class DownloadService extends Service {
});
}
}
+ private synchronized void onMetadataUpdate() {
+ onMetadataUpdate(METADATA_UPDATED_ALL);
+ }
+ private synchronized void onMetadataUpdate(final int updateType) {
+ for(final OnSongChangedListener listener: onSongChangedListeners) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ MusicDirectory.Entry entry = currentPlaying != null ? currentPlaying.getSong() : null;
+ listener.onMetadataUpdate(entry, updateType);
+ }
+ });
+ }
+
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ mRemoteControl.metadataChanged(currentPlaying.getSong());
+ }
+ });
+ }
private class BufferTask extends SilentBackgroundTask<Void> {
private final DownloadFile downloadFile;
@@ -2668,5 +2773,6 @@ public class DownloadService extends Service {
void onSongsChanged(List<DownloadFile> songs, DownloadFile currentPlaying, int currentPlayingIndex);
void onSongProgress(DownloadFile currentPlaying, int millisPlayed, Integer duration, boolean isSeekable);
void onStateUpdate(DownloadFile downloadFile, PlayerState playerState);
+ void onMetadataUpdate(MusicDirectory.Entry entry, int fieldChange);
}
}
diff --git a/app/src/main/java/github/daneren2005/dsub/util/BackgroundTask.java b/app/src/main/java/github/daneren2005/dsub/util/BackgroundTask.java
index 30081d7d..18f245d5 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/BackgroundTask.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/BackgroundTask.java
@@ -93,6 +93,9 @@ public abstract class BackgroundTask<T> implements ProgressListener {
return (context instanceof Activity) ? ((Activity) context) : null;
}
+ protected Context getContext() {
+ return context;
+ }
protected Handler getHandler() {
return handler;
}
diff --git a/app/src/main/java/github/daneren2005/dsub/util/Constants.java b/app/src/main/java/github/daneren2005/dsub/util/Constants.java
index 3ac5ee90..89e7de3b 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/Constants.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/Constants.java
@@ -66,6 +66,7 @@ public final class Constants {
public static final String INTENT_EXTRA_SEARCH_SONG = "searchSong";
public static final String INTENT_EXTRA_TOP_TRACKS = "topTracks";
public static final String INTENT_EXTRA_SHOW_ALL = "showAll";
+ public static final String INTENT_EXTRA_PLAY_LAST = "playLast";
// Preferences keys.
public static final String PREFERENCES_KEY_SERVER_KEY = "server";
diff --git a/app/src/main/java/github/daneren2005/dsub/util/ImageLoader.java b/app/src/main/java/github/daneren2005/dsub/util/ImageLoader.java
index 8b027d70..470d72fb 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/ImageLoader.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/ImageLoader.java
@@ -29,7 +29,6 @@ import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
-import android.media.RemoteControlClient;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
@@ -46,6 +45,7 @@ import github.daneren2005.dsub.domain.Playlist;
import github.daneren2005.dsub.domain.ServerInfo;
import github.daneren2005.dsub.service.MusicService;
import github.daneren2005.dsub.service.MusicServiceFactory;
+import github.daneren2005.dsub.util.compat.RemoteControlClientBase;
/**
* Asynchronous loading of images, with caching.
@@ -257,22 +257,22 @@ public class ImageLoader {
return task;
}
- public SilentBackgroundTask<Void> loadImage(Context context, RemoteControlClient remoteControl, MusicDirectory.Entry entry) {
+ public SilentBackgroundTask<Void> loadImage(Context context, RemoteControlClientBase remoteControl, MusicDirectory.Entry entry) {
Bitmap bitmap;
if (entry == null || entry.getCoverArt() == null) {
bitmap = getUnknownImage(entry, imageSizeLarge);
- setImage(remoteControl, Util.createDrawableFromBitmap(context, bitmap));
+ setImage(entry, remoteControl, Util.createDrawableFromBitmap(context, bitmap));
return null;
}
bitmap = cache.get(getKey(entry.getCoverArt(), imageSizeLarge));
if (bitmap != null && !bitmap.isRecycled()) {
Drawable drawable = Util.createDrawableFromBitmap(this.context, bitmap);
- setImage(remoteControl, drawable);
+ setImage(entry, remoteControl, drawable);
return null;
}
- setImage(remoteControl, Util.createDrawableFromBitmap(context, null));
+ setImage(entry, remoteControl, Util.createDrawableFromBitmap(context, null));
ImageTask task = new RemoteControlClientImageTask(context, entry, imageSizeLarge, imageSizeLarge, false, remoteControl);
task.execute();
return task;
@@ -367,28 +367,27 @@ public class ImageLoader {
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
- private void setImage(RemoteControlClient remoteControl, Drawable drawable) {
+ private void setImage(MusicDirectory.Entry entry, RemoteControlClientBase remoteControl, Drawable drawable) {
if(remoteControl != null && drawable != null) {
Bitmap origBitmap = ((BitmapDrawable)drawable).getBitmap();
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && origBitmap != null) {
origBitmap = origBitmap.copy(origBitmap.getConfig(), false);
}
if ( origBitmap != null && !origBitmap.isRecycled()) {
- remoteControl.editMetadata(false).putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, origBitmap).apply();
+ remoteControl.updateAlbumArt(entry, origBitmap);
} else {
if(origBitmap != null) {
Log.e(TAG, "Tried to load a recycled bitmap.");
}
- remoteControl.editMetadata(false)
- .putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, null)
- .apply();
+
+ remoteControl.updateAlbumArt(entry, null);
}
}
}
public abstract class ImageTask extends SilentBackgroundTask<Void> {
private final Context mContext;
- private final MusicDirectory.Entry mEntry;
+ protected final MusicDirectory.Entry mEntry;
private final int mSize;
private final int mSaveSize;
private final boolean mIsNowPlaying;
@@ -448,9 +447,9 @@ public class ImageLoader {
}
private class RemoteControlClientImageTask extends ImageTask {
- private RemoteControlClient mRemoteControl;
+ private RemoteControlClientBase mRemoteControl;
- public RemoteControlClientImageTask(Context context, MusicDirectory.Entry entry, int size, int saveSize, boolean isNowPlaying, RemoteControlClient remoteControl) {
+ public RemoteControlClientImageTask(Context context, MusicDirectory.Entry entry, int size, int saveSize, boolean isNowPlaying, RemoteControlClientBase remoteControl) {
super(context, entry, size, saveSize, isNowPlaying);
mRemoteControl = remoteControl;
@@ -458,7 +457,7 @@ public class ImageLoader {
@Override
protected void done(Void result) {
- setImage(mRemoteControl, mDrawable);
+ setImage(mEntry, mRemoteControl, mDrawable);
}
}
diff --git a/app/src/main/java/github/daneren2005/dsub/util/SilentServiceTask.java b/app/src/main/java/github/daneren2005/dsub/util/SilentServiceTask.java
new file mode 100644
index 00000000..9fdfe66f
--- /dev/null
+++ b/app/src/main/java/github/daneren2005/dsub/util/SilentServiceTask.java
@@ -0,0 +1,41 @@
+/*
+ This file is part of Subsonic.
+
+ Subsonic 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.
+
+ Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2015 (C) Scott Jackson
+*/
+
+package github.daneren2005.dsub.util;
+
+import android.content.Context;
+
+import github.daneren2005.dsub.service.MusicService;
+import github.daneren2005.dsub.service.MusicServiceFactory;
+
+public abstract class SilentServiceTask<T> extends SilentBackgroundTask<T> {
+ protected MusicService musicService;
+
+ public SilentServiceTask(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected T doInBackground() throws Throwable {
+ musicService = MusicServiceFactory.getMusicService(getContext());
+ return doInBackground(musicService);
+ }
+
+ protected abstract T doInBackground(MusicService musicService) throws Throwable;
+}
diff --git a/app/src/main/java/github/daneren2005/dsub/util/UpdateHelper.java b/app/src/main/java/github/daneren2005/dsub/util/UpdateHelper.java
new file mode 100644
index 00000000..c7e0a04b
--- /dev/null
+++ b/app/src/main/java/github/daneren2005/dsub/util/UpdateHelper.java
@@ -0,0 +1,263 @@
+/*
+ This file is part of Subsonic.
+
+ Subsonic 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.
+
+ Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2015 (C) Scott Jackson
+*/
+
+package github.daneren2005.dsub.util;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.support.v7.app.AlertDialog;
+import android.util.Log;
+import android.view.View;
+import android.widget.RatingBar;
+
+import java.util.Arrays;
+import java.util.List;
+
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.domain.Artist;
+import github.daneren2005.dsub.domain.MusicDirectory;
+import github.daneren2005.dsub.domain.MusicDirectory.Entry;
+import github.daneren2005.dsub.fragments.SubsonicFragment;
+import github.daneren2005.dsub.service.DownloadFile;
+import github.daneren2005.dsub.service.DownloadService;
+import github.daneren2005.dsub.service.MusicService;
+import github.daneren2005.dsub.service.MusicServiceFactory;
+import github.daneren2005.dsub.service.OfflineException;
+import github.daneren2005.dsub.service.ServerTooOldException;
+import github.daneren2005.dsub.view.UpdateView;
+
+public final class UpdateHelper {
+ private static final String TAG = UpdateHelper.class.getSimpleName();
+
+ public static void toggleStarred(Context context, Entry entry) {
+ toggleStarred(context, entry, null);
+ }
+
+ public static void toggleStarred(final Context context, final Entry entry, final OnStarChange onStarChange) {
+ final boolean starred = !entry.isStarred();
+ entry.setStarred(starred);
+ if(onStarChange != null) {
+ onStarChange.starChange(starred);
+ }
+
+ new SilentBackgroundTask<Void>(context) {
+ @Override
+ protected Void doInBackground() throws Throwable {
+ MusicService musicService = MusicServiceFactory.getMusicService(context);
+ if(entry.isDirectory() && Util.isTagBrowsing(context) && !Util.isOffline(context)) {
+ if(entry.isAlbum()) {
+ musicService.setStarred(null, null, Arrays.asList(entry), starred, null, context);
+ } else {
+ musicService.setStarred(null, Arrays.asList(entry), null, starred, null, context);
+ }
+ } else {
+ musicService.setStarred(Arrays.asList(entry), null, null, starred, null, context);
+ }
+
+ new EntryInstanceUpdater(entry) {
+ @Override
+ public void update(Entry found) {
+ found.setStarred(starred);
+ }
+ }.execute();
+
+ return null;
+ }
+
+ @Override
+ protected void done(Void result) {
+ // UpdateView
+ Util.toast(context, context.getResources().getString(starred ? R.string.starring_content_starred : R.string.starring_content_unstarred, entry.getTitle()));
+ }
+
+ @Override
+ protected void error(Throwable error) {
+ Log.w(TAG, "Failed to star", error);
+ entry.setStarred(!starred);
+ if(onStarChange != null) {
+ onStarChange.starChange(!starred);
+ }
+
+ String msg;
+ if (error instanceof OfflineException || error instanceof ServerTooOldException) {
+ msg = getErrorMessage(error);
+ } else {
+ msg = context.getResources().getString(R.string.starring_content_error, entry.getTitle()) + " " + getErrorMessage(error);
+ }
+
+ Util.toast(context, msg, false);
+ }
+ }.execute();
+ }
+
+ public static void toggleStarred(final Context context, final Artist entry) {
+ final boolean starred = !entry.isStarred();
+ entry.setStarred(starred);
+
+ new SilentBackgroundTask<Void>(context) {
+ @Override
+ protected Void doInBackground() throws Throwable {
+ MusicService musicService = MusicServiceFactory.getMusicService(context);
+ if(Util.isTagBrowsing(context) && !Util.isOffline(context)) {
+ musicService.setStarred(null, Arrays.asList(new Entry(entry)), null, starred, null, context);
+ } else {
+ musicService.setStarred(Arrays.asList(new Entry(entry)), null, null, starred, null, context);
+ }
+ return null;
+ }
+
+ @Override
+ protected void done(Void result) {
+ // UpdateView
+ Util.toast(context, context.getResources().getString(starred ? R.string.starring_content_starred : R.string.starring_content_unstarred, entry.getName()));
+ }
+
+ @Override
+ protected void error(Throwable error) {
+ Log.w(TAG, "Failed to star", error);
+ entry.setStarred(!starred);
+
+ String msg;
+ if (error instanceof OfflineException || error instanceof ServerTooOldException) {
+ msg = getErrorMessage(error);
+ } else {
+ msg = context.getResources().getString(R.string.starring_content_error, entry.getName()) + " " + getErrorMessage(error);
+ }
+
+ Util.toast(context, msg, false);
+ }
+ }.execute();
+ }
+
+ public static void setRating(Activity context, Entry entry) {
+ setRating(context, entry, null);
+ }
+ public static void setRating(final Activity context, final Entry entry, final OnRatingChange onRatingChange) {
+ View layout = context.getLayoutInflater().inflate(R.layout.rating, null);
+ final RatingBar ratingBar = (RatingBar) layout.findViewById(R.id.rating_bar);
+ ratingBar.setRating((float) entry.getRating());
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(context.getResources().getString(R.string.rating_title, entry.getTitle()))
+ .setView(layout)
+ .setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ int rating = (int) ratingBar.getRating();
+ setRating(context, entry, rating, onRatingChange);
+ }
+ })
+ .setNegativeButton(R.string.common_cancel, null);
+
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ }
+
+ public static void setRating(Context context, Entry entry, int rating) {
+ setRating(context, entry, rating, null);
+ }
+ public static void setRating(final Context context, final Entry entry, final int rating, final OnRatingChange onRatingChange) {
+ final int oldRating = entry.getRating();
+ entry.setRating(rating);
+
+ if(onRatingChange != null) {
+ onRatingChange.ratingChange(rating);
+ }
+
+ new SilentBackgroundTask<Void>(context) {
+ @Override
+ protected Void doInBackground() throws Throwable {
+ MusicService musicService = MusicServiceFactory.getMusicService(context);
+ musicService.setRating(entry, rating, context, null);
+
+ new EntryInstanceUpdater(entry) {
+ @Override
+ public void update(Entry found) {
+ found.setRating(rating);
+ }
+ }.execute();
+ return null;
+ }
+
+ @Override
+ protected void done(Void result) {
+ Util.toast(context, context.getResources().getString(rating > 0 ? R.string.rating_set_rating : R.string.rating_remove_rating, entry.getTitle()));
+ }
+
+ @Override
+ protected void error(Throwable error) {
+ entry.setRating(oldRating);
+ if(onRatingChange != null) {
+ onRatingChange.ratingChange(oldRating);
+ }
+
+ String msg;
+ if (error instanceof OfflineException || error instanceof ServerTooOldException) {
+ msg = getErrorMessage(error);
+ } else {
+ msg = context.getResources().getString(rating > 0 ? R.string.rating_set_rating_failed : R.string.rating_remove_rating_failed, entry.getTitle()) + " " + getErrorMessage(error);
+ }
+
+ Util.toast(context, msg, false);
+ }
+ }.execute();
+ }
+
+ public static abstract class EntryInstanceUpdater {
+ private Entry entry;
+
+ public EntryInstanceUpdater(Entry entry) {
+ this.entry = entry;
+ }
+
+ public abstract void update(Entry found);
+
+ public void execute() {
+ DownloadService downloadService = DownloadService.getInstance();
+ if(downloadService != null && !entry.isDirectory()) {
+ boolean serializeChanges = false;
+ List<DownloadFile> downloadFiles = downloadService.getDownloads();
+ for(DownloadFile file: downloadFiles) {
+ Entry check = file.getSong();
+ if(entry.getId().equals(check.getId())) {
+ update(entry);
+ serializeChanges = true;
+ }
+ }
+
+ if(serializeChanges) {
+ downloadService.serializeQueue();
+ }
+ }
+
+ Entry find = UpdateView.findEntry(entry);
+ if(find != null) {
+ update(find);
+ }
+ }
+ }
+
+ public static abstract class OnStarChange {
+ public abstract void starChange(boolean starred);
+ }
+ public static abstract class OnRatingChange {
+ public abstract void ratingChange(int rating);
+ }
+}
diff --git a/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientBase.java b/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientBase.java
index 320092e9..1f7035dc 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientBase.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientBase.java
@@ -1,43 +1,39 @@
package github.daneren2005.dsub.util.compat;
-import github.daneren2005.dsub.domain.MusicDirectory.Entry;
+import github.daneren2005.dsub.domain.MusicDirectory;
+import github.daneren2005.dsub.service.DownloadFile;
+
import android.content.ComponentName;
import android.content.Context;
+import android.graphics.Bitmap;
import android.support.v7.media.MediaRouter;
-import android.util.Log;
-
-public class RemoteControlClientBase extends RemoteControlClientHelper {
-
- private static final String TAG = RemoteControlClientBase.class.getSimpleName();
-
- @Override
- public void register(Context context, ComponentName mediaButtonReceiverComponent) {
-
- }
-
- @Override
- public void unregister(Context context) {
-
- }
-
- @Override
- public void setPlaybackState(int state) {
-
- }
-
- @Override
- public void updateMetadata(Context context, Entry currentSong) {
-
- }
-
- @Override
- public void registerRoute(MediaRouter router) {
-
+import android.os.Build;
+
+import java.util.List;
+
+public abstract class RemoteControlClientBase {
+
+ public static RemoteControlClientBase createInstance() {
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ return new RemoteControlClientLP();
+ } else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ return new RemoteControlClientJB();
+ } else {
+ return new RemoteControlClientICS();
+ }
}
-
- @Override
- public void unregisterRoute(MediaRouter router) {
-
+
+ protected RemoteControlClientBase() {
+ // Avoid instantiation
}
-
+
+ public abstract void register(final Context context, final ComponentName mediaButtonReceiverComponent);
+ public abstract void unregister(final Context context);
+ public abstract void setPlaybackState(int state);
+ public abstract void updateMetadata(Context context, MusicDirectory.Entry currentSong);
+ public abstract void metadataChanged(MusicDirectory.Entry currentSong);
+ public abstract void updateAlbumArt(MusicDirectory.Entry currentSong, Bitmap bitmap);
+ public abstract void registerRoute(MediaRouter router);
+ public abstract void unregisterRoute(MediaRouter router);
+ public abstract void updatePlaylist(List<DownloadFile> playlist);
}
diff --git a/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientHelper.java b/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientHelper.java
deleted file mode 100644
index 93075a28..00000000
--- a/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientHelper.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package github.daneren2005.dsub.util.compat;
-
-import github.daneren2005.dsub.domain.MusicDirectory;
-import android.content.ComponentName;
-import android.content.Context;
-import android.support.v7.media.MediaRouter;
-import android.os.Build;
-
-public abstract class RemoteControlClientHelper {
-
- public static RemoteControlClientHelper createInstance() {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
- return new RemoteControlClientBase();
- } else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
- return new RemoteControlClientJB();
- } else {
- return new RemoteControlClientICS();
- }
- }
-
- protected RemoteControlClientHelper() {
- // Avoid instantiation
- }
-
- public abstract void register(final Context context, final ComponentName mediaButtonReceiverComponent);
- public abstract void unregister(final Context context);
- public abstract void setPlaybackState(final int state);
- public abstract void updateMetadata(final Context context, final MusicDirectory.Entry currentSong);
- public abstract void registerRoute(MediaRouter router);
- public abstract void unregisterRoute(MediaRouter router);
-
-}
diff --git a/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java b/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java
index 46b6b47d..55002c4b 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java
@@ -1,6 +1,7 @@
package github.daneren2005.dsub.util.compat;
import github.daneren2005.dsub.domain.MusicDirectory;
+import github.daneren2005.dsub.service.DownloadFile;
import github.daneren2005.dsub.service.DownloadService;
import github.daneren2005.dsub.util.ImageLoader;
import android.annotation.TargetApi;
@@ -8,15 +9,18 @@ import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.graphics.Bitmap;
import android.media.AudioManager;
import android.media.MediaMetadataRetriever;
import android.media.RemoteControlClient;
import android.support.v7.media.MediaRouter;
+import java.util.List;
+
import github.daneren2005.dsub.activity.SubsonicActivity;
@TargetApi(14)
-public class RemoteControlClientICS extends RemoteControlClientHelper {
+public class RemoteControlClientICS extends RemoteControlClientBase {
private static String TAG = RemoteControlClientICS.class.getSimpleName();
protected RemoteControlClient mRemoteControl;
@@ -72,15 +76,25 @@ public class RemoteControlClientICS extends RemoteControlClientHelper {
updateMetadata(currentSong, editor);
editor.apply();
if (currentSong == null || imageLoader == null) {
- mRemoteControl.editMetadata(true)
- .putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, null)
- .apply();
+ updateAlbumArt(currentSong, null);
} else {
- imageLoader.loadImage(context, mRemoteControl, currentSong);
+ imageLoader.loadImage(context, this, currentSong);
}
}
@Override
+ public void metadataChanged(MusicDirectory.Entry currentSong) {
+
+ }
+
+ @Override
+ public void updateAlbumArt(MusicDirectory.Entry currentSong, Bitmap bitmap) {
+ mRemoteControl.editMetadata(true)
+ .putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, bitmap).
+ apply();
+ }
+
+ @Override
public void registerRoute(MediaRouter router) {
if(mRemoteControl == null) {
return;
@@ -98,6 +112,11 @@ public class RemoteControlClientICS extends RemoteControlClientHelper {
router.removeRemoteControlClient(mRemoteControl);
}
+ @Override
+ public void updatePlaylist(List<DownloadFile> playlist) {
+
+ }
+
protected void updateMetadata(final MusicDirectory.Entry currentSong, final RemoteControlClient.MetadataEditor editor) {
editor.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, (currentSong == null) ? null : currentSong.getArtist())
.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, (currentSong == null) ? null : currentSong.getAlbum())
diff --git a/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientLP.java b/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientLP.java
new file mode 100644
index 00000000..456446f3
--- /dev/null
+++ b/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientLP.java
@@ -0,0 +1,495 @@
+/*
+ This file is part of Subsonic.
+
+ Subsonic 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.
+
+ Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2015 (C) Scott Jackson
+*/
+package github.daneren2005.dsub.util.compat;
+
+import android.annotation.TargetApi;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.media.AudioAttributes;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.RemoteControlClient;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.support.v7.media.MediaRouter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.activity.SubsonicActivity;
+import github.daneren2005.dsub.activity.SubsonicFragmentActivity;
+import github.daneren2005.dsub.domain.MusicDirectory;
+import github.daneren2005.dsub.domain.Playlist;
+import github.daneren2005.dsub.domain.SearchCritera;
+import github.daneren2005.dsub.domain.SearchResult;
+import github.daneren2005.dsub.service.DownloadFile;
+import github.daneren2005.dsub.service.DownloadService;
+import github.daneren2005.dsub.service.MusicService;
+import github.daneren2005.dsub.util.Constants;
+import github.daneren2005.dsub.util.ImageLoader;
+import github.daneren2005.dsub.util.SilentServiceTask;
+import github.daneren2005.dsub.util.Util;
+
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class RemoteControlClientLP extends RemoteControlClientBase {
+ private static final String TAG = RemoteControlClientLP.class.getSimpleName();
+ private static final String CUSTOM_ACTION_THUMBS_UP = "github.daneren2005.dsub.THUMBS_UP";
+ private static final String CUSTOM_ACTION_THUMBS_DOWN = "github.daneren2005.dsub.THUMBS_DOWN";
+ private static final String CUSTOM_ACTION_STAR = "github.daneren2005.dsub.STAR";
+ // Copied from MediaControlConstants so I did not have to include the entire Wear SDK just for these constant
+ private static final String SHOW_ON_WEAR = "android.support.wearable.media.extra.CUSTOM_ACTION_SHOW_ON_WEAR";
+ private static final String WEAR_RESERVE_SKIP_TO_NEXT = "android.support.wearable.media.extra.RESERVE_SLOT_SKIP_TO_NEXT";
+ private static final String WEAR_RESERVE_SKIP_TO_PREVIOUS = "android.support.wearable.media.extra.RESERVE_SLOT_SKIP_TO_PREVIOUS";
+ private static final String WEAR_BACKGROUND_THEME = "android.support.wearable.media.extra.BACKGROUND_COLOR_FROM_THEME";
+ // These constants don't seem to exist anywhere in the SDK. Grabbed from Google's sample media player app
+ private static final String AUTO_RESERVE_SKIP_TO_NEXT = "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_NEXT";
+ private static final String AUTO_RESERVE_SKIP_TO_PREVIOUS = "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS";
+
+ protected MediaSession mediaSession;
+ protected DownloadService downloadService;
+ protected ImageLoader imageLoader;
+ protected List<DownloadFile> currentQueue;
+ protected int previousState;
+
+ @Override
+ public void register(Context context, ComponentName mediaButtonReceiverComponent) {
+ downloadService = (DownloadService) context;
+ mediaSession = new MediaSession(downloadService, "DSub MediaSession");
+
+ Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ mediaButtonIntent.setComponent(mediaButtonReceiverComponent);
+ PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(context.getApplicationContext(), 0, mediaButtonIntent, 0);
+ mediaSession.setMediaButtonReceiver(mediaPendingIntent);
+
+ Intent activityIntent = new Intent(context, SubsonicFragmentActivity.class);
+ activityIntent.putExtra(Constants.INTENT_EXTRA_NAME_DOWNLOAD, true);
+ activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ PendingIntent activityPendingIntent = PendingIntent.getActivity(context, 0, activityIntent, 0);
+ mediaSession.setSessionActivity(activityPendingIntent);
+
+ mediaSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS);
+ mediaSession.setCallback(new EventCallback());
+
+ AudioAttributes.Builder audioAttributesBuilder = new AudioAttributes.Builder();
+ audioAttributesBuilder.setUsage(AudioAttributes.USAGE_MEDIA)
+ .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC);
+ mediaSession.setPlaybackToLocal(audioAttributesBuilder.build());
+ mediaSession.setActive(true);
+
+ Bundle sessionExtras = new Bundle();
+ sessionExtras.putBoolean(WEAR_BACKGROUND_THEME, true);
+ sessionExtras.putBoolean(WEAR_RESERVE_SKIP_TO_PREVIOUS, true);
+ sessionExtras.putBoolean(WEAR_RESERVE_SKIP_TO_NEXT, true);
+ sessionExtras.putBoolean(AUTO_RESERVE_SKIP_TO_PREVIOUS, true);
+ sessionExtras.putBoolean(AUTO_RESERVE_SKIP_TO_NEXT, true);
+ mediaSession.setExtras(sessionExtras);
+
+ imageLoader = SubsonicActivity.getStaticImageLoader(context);
+ }
+
+ @Override
+ public void unregister(Context context) {
+ mediaSession.release();
+ }
+
+ @Override
+ public void setPlaybackState(int state) {
+ PlaybackState.Builder builder = new PlaybackState.Builder();
+
+ int newState = PlaybackState.STATE_NONE;
+ switch(state) {
+ case RemoteControlClient.PLAYSTATE_PLAYING:
+ newState = PlaybackState.STATE_PLAYING;
+ break;
+ case RemoteControlClient.PLAYSTATE_STOPPED:
+ newState = PlaybackState.STATE_STOPPED;
+ break;
+ case RemoteControlClient.PLAYSTATE_PAUSED:
+ newState = PlaybackState.STATE_PAUSED;
+ break;
+ case RemoteControlClient.PLAYSTATE_BUFFERING:
+ newState = PlaybackState.STATE_BUFFERING;
+ break;
+ }
+
+ long position = -1;
+ if(state == RemoteControlClient.PLAYSTATE_PLAYING || state == RemoteControlClient.PLAYSTATE_PAUSED) {
+ position = downloadService.getPlayerPosition();
+ }
+ builder.setState(newState, position, 1.0f);
+ builder.setActions(getPlaybackActions());
+
+ DownloadFile downloadFile = downloadService.getCurrentPlaying();
+ if(downloadFile != null) {
+ MusicDirectory.Entry entry = downloadFile.getSong();
+ addCustomActions(entry, builder);
+ builder.setActiveQueueItemId(entry.getId().hashCode());
+ }
+
+ PlaybackState playbackState = builder.build();
+ mediaSession.setPlaybackState(playbackState);
+ previousState = state;
+ }
+
+ @Override
+ public void updateMetadata(Context context, MusicDirectory.Entry currentSong) {
+ setMetadata(currentSong, null);
+
+ if(currentSong != null && imageLoader != null) {
+ imageLoader.loadImage(context, this, currentSong);
+ }
+ }
+
+ @Override
+ public void metadataChanged(MusicDirectory.Entry currentSong) {
+ setPlaybackState(previousState);
+ }
+
+ public void setMetadata(MusicDirectory.Entry currentSong, Bitmap bitmap) {
+ MediaMetadata.Builder builder = new MediaMetadata.Builder();
+ builder.putString(MediaMetadata.METADATA_KEY_ARTIST, (currentSong == null) ? null : currentSong.getArtist())
+ .putString(MediaMetadata.METADATA_KEY_ALBUM, (currentSong == null) ? null : currentSong.getAlbum())
+ .putString(MediaMetadata.METADATA_KEY_ALBUM_ARTIST, (currentSong == null) ? null : currentSong.getArtist())
+ .putString(MediaMetadata.METADATA_KEY_TITLE, (currentSong) == null ? null : currentSong.getTitle())
+ .putString(MediaMetadata.METADATA_KEY_GENRE, (currentSong) == null ? null : currentSong.getGenre())
+ .putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, (currentSong == null) ?
+ 0 : ((currentSong.getTrack() == null) ? 0 : currentSong.getTrack()))
+ .putLong(MediaMetadata.METADATA_KEY_DURATION, (currentSong == null) ?
+ 0 : ((currentSong.getDuration() == null) ? 0 : (currentSong.getDuration() * 1000)));
+
+ if(bitmap != null) {
+ builder.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, bitmap);
+ }
+
+ mediaSession.setMetadata(builder.build());
+ }
+
+ @Override
+ public void updateAlbumArt(MusicDirectory.Entry currentSong, Bitmap bitmap) {
+ setMetadata(currentSong, bitmap);
+ }
+
+ @Override
+ public void registerRoute(MediaRouter router) {
+ router.setMediaSession(mediaSession);
+ }
+
+ @Override
+ public void unregisterRoute(MediaRouter router) {
+ router.setMediaSession(null);
+ }
+
+ @Override
+ public void updatePlaylist(List<DownloadFile> playlist) {
+ List<MediaSession.QueueItem> queue = new ArrayList<>();
+
+ for(DownloadFile file: playlist) {
+ MusicDirectory.Entry entry = file.getSong();
+
+ MediaDescription description = new MediaDescription.Builder()
+ .setMediaId(entry.getId())
+ .setTitle(entry.getTitle())
+ .setSubtitle(entry.getAlbumDisplay())
+ .build();
+ MediaSession.QueueItem item = new MediaSession.QueueItem(description, entry.getId().hashCode());
+ queue.add(item);
+ }
+
+ mediaSession.setQueue(queue);
+ currentQueue = playlist;
+ }
+
+ public MediaSession getMediaSession() {
+ return mediaSession;
+ }
+
+ protected long getPlaybackActions() {
+ long actions = PlaybackState.ACTION_PLAY |
+ PlaybackState.ACTION_PAUSE |
+ PlaybackState.ACTION_SEEK_TO |
+ PlaybackState.ACTION_SKIP_TO_QUEUE_ITEM;
+
+ int currentIndex = downloadService.getCurrentPlayingIndex();
+ int size = downloadService.size();
+ if(currentIndex > 0) {
+ actions |= PlaybackState.ACTION_SKIP_TO_PREVIOUS;
+ }
+ if(currentIndex < size - 1) {
+ actions |= PlaybackState.ACTION_SKIP_TO_NEXT;
+ }
+
+ return actions;
+ }
+ protected void addCustomActions(MusicDirectory.Entry currentSong, PlaybackState.Builder builder) {
+ Bundle showOnWearExtras = new Bundle();
+ showOnWearExtras.putBoolean(SHOW_ON_WEAR, true);
+
+ int rating = currentSong.getRating();
+ PlaybackState.CustomAction thumbsUp = new PlaybackState.CustomAction.Builder(CUSTOM_ACTION_THUMBS_UP,
+ downloadService.getString(R.string.download_thumbs_up),
+ rating == 5 ? R.drawable.ic_action_rating_good_selected : R.drawable.ic_action_rating_good)
+ .setExtras(showOnWearExtras).build();
+
+ PlaybackState.CustomAction thumbsDown = new PlaybackState.CustomAction.Builder(CUSTOM_ACTION_THUMBS_DOWN,
+ downloadService.getString(R.string.download_thumbs_down),
+ rating == 1 ? R.drawable.ic_action_rating_bad_selected : R.drawable.ic_action_rating_bad)
+ .setExtras(showOnWearExtras).build();
+
+ PlaybackState.CustomAction star = new PlaybackState.CustomAction.Builder(CUSTOM_ACTION_STAR,
+ downloadService.getString(R.string.common_star),
+ currentSong.isStarred() ? R.drawable.ic_toggle_star : R.drawable.ic_toggle_star_outline)
+ .setExtras(showOnWearExtras).build();
+
+ builder.addCustomAction(thumbsDown).addCustomAction(star).addCustomAction(thumbsUp);
+ }
+
+ private void searchPlaylist(final String name) {
+ new SilentServiceTask<Void>(downloadService) {
+ @Override
+ protected Void doInBackground(MusicService musicService) throws Throwable {
+ List<Playlist> playlists = musicService.getPlaylists(false, downloadService, null);
+ for(Playlist playlist: playlists) {
+ if(playlist.getName().equals(name)) {
+ getPlaylist(playlist);
+ return null;
+ }
+ }
+
+ noResults();
+ return null;
+ }
+
+ private void getPlaylist(Playlist playlist) throws Exception {
+ MusicDirectory musicDirectory = musicService.getPlaylist(false, playlist.getId(), playlist.getName(), downloadService, null);
+ playSongs(musicDirectory.getChildren());
+ }
+ }.execute();
+ }
+ private void searchCriteria(final SearchCritera searchCritera) {
+ new SilentServiceTask<Void>(downloadService) {
+ @Override
+ protected Void doInBackground(MusicService musicService) throws Throwable {
+ SearchResult results = musicService.search(searchCritera, downloadService, null);
+
+ if(results.hasArtists()) {
+ playFromParent(new MusicDirectory.Entry(results.getArtists().get(0)));
+ } else if(results.hasAlbums()) {
+ playFromParent(results.getAlbums().get(0));
+ } else if(results.hasSongs()) {
+ playSong(results.getSongs().get(0));
+ } else {
+ noResults();
+ }
+
+ return null;
+ }
+
+ private void playFromParent(MusicDirectory.Entry parent) throws Exception {
+ List<MusicDirectory.Entry> songs = new ArrayList<>();
+ getSongsRecursively(parent, songs);
+ playSongs(songs);
+ }
+ private void getSongsRecursively(MusicDirectory.Entry parent, List<MusicDirectory.Entry> songs) throws Exception {
+ MusicDirectory musicDirectory;
+ if(Util.isTagBrowsing(downloadService) && !Util.isOffline(downloadService)) {
+ musicDirectory = musicService.getAlbum(parent.getId(), parent.getTitle(), false, downloadService, this);
+ } else {
+ musicDirectory = musicService.getMusicDirectory(parent.getId(), parent.getTitle(), false, downloadService, this);
+ }
+
+ for (MusicDirectory.Entry dir : musicDirectory.getChildren(true, false)) {
+ if (dir.getRating() == 1) {
+ continue;
+ }
+
+ getSongsRecursively(dir, songs);
+ }
+
+ for (MusicDirectory.Entry song : musicDirectory.getChildren(false, true)) {
+ if (!song.isVideo() && song.getRating() != 1) {
+ songs.add(song);
+ }
+ }
+ }
+ }.execute();
+ }
+
+ private void playPlaylist(final Playlist playlist, final boolean shuffle, final boolean append) {
+ new SilentServiceTask<Void>(downloadService) {
+ @Override
+ protected Void doInBackground(MusicService musicService) throws Throwable {
+ MusicDirectory musicDirectory = musicService.getPlaylist(false, playlist.getId(), playlist.getName(), downloadService, null);
+ playSongs(musicDirectory.getChildren(), shuffle, append);
+
+ return null;
+ }
+ }.execute();
+ }
+
+ private void playSong(MusicDirectory.Entry entry) {
+ List<MusicDirectory.Entry> entries = new ArrayList<>();
+ entries.add(entry);
+ playSongs(entries);
+ }
+ private void playSongs(List<MusicDirectory.Entry> entries) {
+ playSongs(entries, false, false);
+ }
+ private void playSongs(List<MusicDirectory.Entry> entries, boolean shuffle, boolean append) {
+ if(!append) {
+ downloadService.clear();
+ }
+ downloadService.download(entries, false, true, false, shuffle);
+ }
+
+ private void noResults() {
+
+ }
+
+ private class EventCallback extends MediaSession.Callback {
+ @Override
+ public void onPlay() {
+ downloadService.start();
+ }
+
+ @Override
+ public void onStop() {
+ downloadService.pause();
+ }
+
+ @Override
+ public void onPause() {
+ downloadService.pause();
+ }
+
+ @Override
+ public void onSeekTo(long position) {
+ downloadService.seekTo((int) position);
+ }
+
+ @Override
+ public void onSkipToNext() {
+ downloadService.next();
+ }
+ @Override
+ public void onSkipToPrevious() {
+ downloadService.previous();
+ }
+
+ @Override
+ public void onSkipToQueueItem(long queueId) {
+ if(currentQueue != null) {
+ for(DownloadFile file: currentQueue) {
+ if(file.getSong().getId().hashCode() == queueId) {
+ downloadService.play(file);
+ return;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onPlayFromSearch (String query, Bundle extras) {
+ // User just asked to playing something
+ if("".equals(query)) {
+ downloadService.setShufflePlayEnabled(true);
+ } else {
+ String mediaFocus = extras.getString(MediaStore.EXTRA_MEDIA_FOCUS);
+
+
+ // Play a specific playlist
+ if (MediaStore.Audio.Playlists.ENTRY_CONTENT_TYPE.equals(mediaFocus)) {
+ String playlist = extras.getString(MediaStore.EXTRA_MEDIA_PLAYLIST);
+ searchPlaylist(playlist);
+ }
+ // Play a specific genre
+ else if (MediaStore.Audio.Genres.ENTRY_CONTENT_TYPE.equals(mediaFocus)) {
+ String genre = extras.getString(MediaStore.EXTRA_MEDIA_GENRE);
+
+ SharedPreferences.Editor editor = Util.getPreferences(downloadService).edit();
+ editor.putString(Constants.PREFERENCES_KEY_SHUFFLE_START_YEAR, null);
+ editor.putString(Constants.PREFERENCES_KEY_SHUFFLE_END_YEAR, null);
+ editor.putString(Constants.PREFERENCES_KEY_SHUFFLE_GENRE, genre);
+ editor.commit();
+
+ downloadService.setShufflePlayEnabled(true);
+ }
+ else {
+ int artists = 10;
+ int albums = 10;
+ int songs = 10;
+
+ // Play a specific artist
+ if (MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE.equals(mediaFocus)) {
+ query = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST);
+ albums = 0;
+ songs = 0;
+ }
+ // Play a specific album
+ else if (MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE.equals(mediaFocus)) {
+ query = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM);
+ artists = 0;
+ songs = 0 ;
+ }
+ // Play a specific song
+ else if (MediaStore.Audio.Media.ENTRY_CONTENT_TYPE.equals(mediaFocus)) {
+ query = extras.getString(MediaStore.EXTRA_MEDIA_TITLE);
+ artists = 0;
+ albums = 0;
+ }
+
+ SearchCritera criteria = new SearchCritera(query, artists, albums, songs);
+ searchCriteria(criteria);
+ }
+ }
+ }
+
+ public void onPlayFromMediaId (String mediaId, Bundle extras) {
+ if(extras == null) {
+ return;
+ }
+
+ boolean shuffle = extras.getBoolean(Constants.INTENT_EXTRA_NAME_SHUFFLE, false);
+ boolean playLast = extras.getBoolean(Constants.INTENT_EXTRA_PLAY_LAST, false);
+ String playlistId = extras.getString(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID, null);
+ if(playlistId != null) {
+ Playlist playlist = new Playlist(playlistId, null);
+ playPlaylist(playlist, shuffle, playLast);
+ }
+ }
+
+ @Override
+ public void onCustomAction(String action, Bundle extras) {
+ if(CUSTOM_ACTION_THUMBS_UP.equals(action)) {
+ downloadService.toggleRating(5);
+ } else if(CUSTOM_ACTION_THUMBS_DOWN.equals(action)) {
+ downloadService.toggleRating(1);
+ } else if(CUSTOM_ACTION_STAR.equals(action)) {
+ downloadService.toggleStarred();
+ }
+ }
+ }
+}
diff --git a/app/src/main/res/drawable-hdpi/action_toggle_list_dark.png b/app/src/main/res/drawable-hdpi/action_toggle_list_dark.png
index 74b11687..75960936 100644
--- a/app/src/main/res/drawable-hdpi/action_toggle_list_dark.png
+++ b/app/src/main/res/drawable-hdpi/action_toggle_list_dark.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/action_toggle_list_light.png b/app/src/main/res/drawable-hdpi/action_toggle_list_light.png
index 36a56aac..0b4b552d 100644
--- a/app/src/main/res/drawable-hdpi/action_toggle_list_light.png
+++ b/app/src/main/res/drawable-hdpi/action_toggle_list_light.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/ic_action_rating_bad.png b/app/src/main/res/drawable-hdpi/ic_action_rating_bad.png
new file mode 100644
index 00000000..7f06020b
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_action_rating_bad.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/ic_action_rating_bad_selected.png b/app/src/main/res/drawable-hdpi/ic_action_rating_bad_selected.png
index 911eea25..b144900f 100644
--- a/app/src/main/res/drawable-hdpi/ic_action_rating_bad_selected.png
+++ b/app/src/main/res/drawable-hdpi/ic_action_rating_bad_selected.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/ic_action_rating_good.png b/app/src/main/res/drawable-hdpi/ic_action_rating_good.png
new file mode 100644
index 00000000..42640dbb
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_action_rating_good.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/ic_action_rating_good_selected.png b/app/src/main/res/drawable-hdpi/ic_action_rating_good_selected.png
index 09417791..7096c8c2 100644
--- a/app/src/main/res/drawable-hdpi/ic_action_rating_good_selected.png
+++ b/app/src/main/res/drawable-hdpi/ic_action_rating_good_selected.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/ic_toggle_star.png b/app/src/main/res/drawable-hdpi/ic_toggle_star.png
index f9ef8772..e62e5803 100644
--- a/app/src/main/res/drawable-hdpi/ic_toggle_star.png
+++ b/app/src/main/res/drawable-hdpi/ic_toggle_star.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/ic_toggle_star_outline.png b/app/src/main/res/drawable-hdpi/ic_toggle_star_outline.png
new file mode 100644
index 00000000..fead34b3
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_toggle_star_outline.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/action_toggle_list_dark.png b/app/src/main/res/drawable-mdpi/action_toggle_list_dark.png
index 442db2fb..19fcae8f 100644
--- a/app/src/main/res/drawable-mdpi/action_toggle_list_dark.png
+++ b/app/src/main/res/drawable-mdpi/action_toggle_list_dark.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/action_toggle_list_light.png b/app/src/main/res/drawable-mdpi/action_toggle_list_light.png
index f078c702..12ecc9a0 100644
--- a/app/src/main/res/drawable-mdpi/action_toggle_list_light.png
+++ b/app/src/main/res/drawable-mdpi/action_toggle_list_light.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_action_rating_bad.png b/app/src/main/res/drawable-mdpi/ic_action_rating_bad.png
new file mode 100644
index 00000000..6cb87ce9
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_action_rating_bad.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_action_rating_bad_selected.png b/app/src/main/res/drawable-mdpi/ic_action_rating_bad_selected.png
index ac563748..e96fc42e 100644
--- a/app/src/main/res/drawable-mdpi/ic_action_rating_bad_selected.png
+++ b/app/src/main/res/drawable-mdpi/ic_action_rating_bad_selected.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_action_rating_good.png b/app/src/main/res/drawable-mdpi/ic_action_rating_good.png
new file mode 100644
index 00000000..21ca0f92
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_action_rating_good.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_action_rating_good_selected.png b/app/src/main/res/drawable-mdpi/ic_action_rating_good_selected.png
index 197aa106..9340e80f 100644
--- a/app/src/main/res/drawable-mdpi/ic_action_rating_good_selected.png
+++ b/app/src/main/res/drawable-mdpi/ic_action_rating_good_selected.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_toggle_star.png b/app/src/main/res/drawable-mdpi/ic_toggle_star.png
index 6d949fd0..570104dc 100644
--- a/app/src/main/res/drawable-mdpi/ic_toggle_star.png
+++ b/app/src/main/res/drawable-mdpi/ic_toggle_star.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_toggle_star_outline.png b/app/src/main/res/drawable-mdpi/ic_toggle_star_outline.png
new file mode 100644
index 00000000..656336d6
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_toggle_star_outline.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/action_toggle_list_dark.png b/app/src/main/res/drawable-xhdpi/action_toggle_list_dark.png
index 1c973af3..7b05a06e 100644
--- a/app/src/main/res/drawable-xhdpi/action_toggle_list_dark.png
+++ b/app/src/main/res/drawable-xhdpi/action_toggle_list_dark.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/action_toggle_list_light.png b/app/src/main/res/drawable-xhdpi/action_toggle_list_light.png
index 34946bee..4b890155 100644
--- a/app/src/main/res/drawable-xhdpi/action_toggle_list_light.png
+++ b/app/src/main/res/drawable-xhdpi/action_toggle_list_light.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_action_rating_bad.png b/app/src/main/res/drawable-xhdpi/ic_action_rating_bad.png
new file mode 100644
index 00000000..8d36c8e7
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_action_rating_bad.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_action_rating_bad_selected.png b/app/src/main/res/drawable-xhdpi/ic_action_rating_bad_selected.png
index 98494b1f..a88e9483 100644
--- a/app/src/main/res/drawable-xhdpi/ic_action_rating_bad_selected.png
+++ b/app/src/main/res/drawable-xhdpi/ic_action_rating_bad_selected.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_action_rating_good.png b/app/src/main/res/drawable-xhdpi/ic_action_rating_good.png
new file mode 100644
index 00000000..88f6a6cb
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_action_rating_good.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_action_rating_good_selected.png b/app/src/main/res/drawable-xhdpi/ic_action_rating_good_selected.png
index f5065d18..5e30e89e 100644
--- a/app/src/main/res/drawable-xhdpi/ic_action_rating_good_selected.png
+++ b/app/src/main/res/drawable-xhdpi/ic_action_rating_good_selected.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_toggle_star.png b/app/src/main/res/drawable-xhdpi/ic_toggle_star.png
index 5c54298c..7b628888 100644
--- a/app/src/main/res/drawable-xhdpi/ic_toggle_star.png
+++ b/app/src/main/res/drawable-xhdpi/ic_toggle_star.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_toggle_star_outline.png b/app/src/main/res/drawable-xhdpi/ic_toggle_star_outline.png
new file mode 100644
index 00000000..55a2d40b
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_toggle_star_outline.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/action_toggle_list_dark.png b/app/src/main/res/drawable-xxhdpi/action_toggle_list_dark.png
index 409cbb50..c6c78b1c 100644
--- a/app/src/main/res/drawable-xxhdpi/action_toggle_list_dark.png
+++ b/app/src/main/res/drawable-xxhdpi/action_toggle_list_dark.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/action_toggle_list_light.png b/app/src/main/res/drawable-xxhdpi/action_toggle_list_light.png
index b04a1e6b..9898e130 100644
--- a/app/src/main/res/drawable-xxhdpi/action_toggle_list_light.png
+++ b/app/src/main/res/drawable-xxhdpi/action_toggle_list_light.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_rating_bad.png b/app/src/main/res/drawable-xxhdpi/ic_action_rating_bad.png
new file mode 100644
index 00000000..a09751b7
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_action_rating_bad.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_rating_bad_selected.png b/app/src/main/res/drawable-xxhdpi/ic_action_rating_bad_selected.png
index 945bb594..5adde418 100644
--- a/app/src/main/res/drawable-xxhdpi/ic_action_rating_bad_selected.png
+++ b/app/src/main/res/drawable-xxhdpi/ic_action_rating_bad_selected.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_rating_good.png b/app/src/main/res/drawable-xxhdpi/ic_action_rating_good.png
new file mode 100644
index 00000000..7adc2c24
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_action_rating_good.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_rating_good_selected.png b/app/src/main/res/drawable-xxhdpi/ic_action_rating_good_selected.png
index 273615ea..de797727 100644
--- a/app/src/main/res/drawable-xxhdpi/ic_action_rating_good_selected.png
+++ b/app/src/main/res/drawable-xxhdpi/ic_action_rating_good_selected.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_toggle_star.png b/app/src/main/res/drawable-xxhdpi/ic_toggle_star.png
index 4db2a37c..00e7c347 100644
--- a/app/src/main/res/drawable-xxhdpi/ic_toggle_star.png
+++ b/app/src/main/res/drawable-xxhdpi/ic_toggle_star.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_toggle_star_outline.png b/app/src/main/res/drawable-xxhdpi/ic_toggle_star_outline.png
new file mode 100644
index 00000000..096ebba7
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_toggle_star_outline.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxxhdpi/action_toggle_list_dark.png b/app/src/main/res/drawable-xxxhdpi/action_toggle_list_dark.png
index 5efdb642..93369e24 100644
--- a/app/src/main/res/drawable-xxxhdpi/action_toggle_list_dark.png
+++ b/app/src/main/res/drawable-xxxhdpi/action_toggle_list_dark.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxxhdpi/action_toggle_list_light.png b/app/src/main/res/drawable-xxxhdpi/action_toggle_list_light.png
index 957a3ac6..b0989dde 100644
--- a/app/src/main/res/drawable-xxxhdpi/action_toggle_list_light.png
+++ b/app/src/main/res/drawable-xxxhdpi/action_toggle_list_light.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_rating_bad.png b/app/src/main/res/drawable-xxxhdpi/ic_action_rating_bad.png
new file mode 100644
index 00000000..6c358480
--- /dev/null
+++ b/app/src/main/res/drawable-xxxhdpi/ic_action_rating_bad.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_rating_bad_selected.png b/app/src/main/res/drawable-xxxhdpi/ic_action_rating_bad_selected.png
index f585ba00..0088b4ef 100644
--- a/app/src/main/res/drawable-xxxhdpi/ic_action_rating_bad_selected.png
+++ b/app/src/main/res/drawable-xxxhdpi/ic_action_rating_bad_selected.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_rating_good.png b/app/src/main/res/drawable-xxxhdpi/ic_action_rating_good.png
new file mode 100644
index 00000000..b949b53b
--- /dev/null
+++ b/app/src/main/res/drawable-xxxhdpi/ic_action_rating_good.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_rating_good_selected.png b/app/src/main/res/drawable-xxxhdpi/ic_action_rating_good_selected.png
index 9ee267f7..3fca95ef 100644
--- a/app/src/main/res/drawable-xxxhdpi/ic_action_rating_good_selected.png
+++ b/app/src/main/res/drawable-xxxhdpi/ic_action_rating_good_selected.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_toggle_star.png b/app/src/main/res/drawable-xxxhdpi/ic_toggle_star.png
index 6116210d..ce14f384 100644
--- a/app/src/main/res/drawable-xxxhdpi/ic_toggle_star.png
+++ b/app/src/main/res/drawable-xxxhdpi/ic_toggle_star.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_toggle_star_outline.png b/app/src/main/res/drawable-xxxhdpi/ic_toggle_star_outline.png
new file mode 100644
index 00000000..c39bbcf5
--- /dev/null
+++ b/app/src/main/res/drawable-xxxhdpi/ic_toggle_star_outline.png
Binary files differ
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 8f5cec71..33bbc1fd 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -224,6 +224,8 @@
\nEstimated Size: %2$s</string>
<string name="download.failed_to_load">Failed to load</string>
<string name="download.restore_play_queue">continue from where you left off on another device at</string>
+ <string name="download.thumbs_up">Thumbs Up</string>
+ <string name="download.thumbs_down">Thumbs Down</string>
<string name="sync.new_podcasts">New podcasts available</string>
<string name="sync.new_playlists">New songs in playlists</string>
diff --git a/app/src/main/res/xml/auto_app_description.xml b/app/src/main/res/xml/auto_app_description.xml
new file mode 100644
index 00000000..48316457
--- /dev/null
+++ b/app/src/main/res/xml/auto_app_description.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<automotiveApp>
+ <uses name="media"/>
+</automotiveApp> \ No newline at end of file