/* 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.MediaMetadata; 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.Artist; import github.daneren2005.dsub.domain.Indexes; import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.domain.MusicDirectory.Entry; import github.daneren2005.dsub.domain.MusicFolder; import github.daneren2005.dsub.domain.Playlist; import github.daneren2005.dsub.domain.PodcastChannel; import github.daneren2005.dsub.domain.PodcastEpisode; 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 BROWSER_PODCASTS = "podcasts"; private static final String BROWSER_BOOKMARKS = "bookmarks"; private static final String PLAYLIST_PREFIX = "pl-"; private static final String PODCAST_PREFIX = "po-"; private static final String ALBUM_TYPE_PREFIX = "ty-"; private static final String MUSIC_DIRECTORY_PREFIX = "md-"; private static final String MUSIC_FOLDER_PREFIX = "mf-"; private static final String MUSIC_DIRECTORY_CONTENTS_PREFIX = "mdc-"; 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(parentId.startsWith(MUSIC_DIRECTORY_PREFIX)) { String id = parentId.substring(MUSIC_DIRECTORY_PREFIX.length()); getPlayOptions(result, id, Constants.INTENT_EXTRA_NAME_ID); } else if(BROWSER_LIBRARY.equals(parentId)) { getLibrary(result); } else if(parentId.startsWith(MUSIC_FOLDER_PREFIX)) { String id = parentId.substring(MUSIC_FOLDER_PREFIX.length()); getIndexes(result, id); } else if(parentId.startsWith(MUSIC_DIRECTORY_CONTENTS_PREFIX)) { String id = parentId.substring(MUSIC_DIRECTORY_CONTENTS_PREFIX.length()); getMusicDirectory(result, id); } else if(BROWSER_PLAYLISTS.equals(parentId)) { getPlaylists(result); } else if(parentId.startsWith(PLAYLIST_PREFIX)) { String id = parentId.substring(PLAYLIST_PREFIX.length()); getPlayOptions(result, id, Constants.INTENT_EXTRA_NAME_PLAYLIST_ID); } else if(BROWSER_PODCASTS.equals(parentId)) { getPodcasts(result); } else if(parentId.startsWith(PODCAST_PREFIX)) { String id = parentId.substring(PODCAST_PREFIX.length()); getPodcastEpisodes(result, id); } else if(BROWSER_BOOKMARKS.equals(parentId)) { getBookmarks(result); } 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)); if(Util.getPreferences(downloadService).getBoolean(Constants.PREFERENCES_KEY_PODCASTS_ENABLED, true)) { MediaDescription.Builder podcasts = new MediaDescription.Builder(); podcasts.setTitle(downloadService.getString(R.string.button_bar_podcasts)) .setMediaId(BROWSER_PODCASTS); mediaItems.add(new MediaBrowser.MediaItem(podcasts.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE)); } if(Util.getPreferences(downloadService).getBoolean(Constants.PREFERENCES_KEY_BOOKMARKS_ENABLED, true)) { MediaDescription.Builder podcasts = new MediaDescription.Builder(); podcasts.setTitle(downloadService.getString(R.string.button_bar_bookmarks)) .setMediaId(BROWSER_BOOKMARKS); mediaItems.add(new MediaBrowser.MediaItem(podcasts.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(!Util.isTagBrowsing(downloadService)) { albums.add(R.string.main_albums_highest); } albums.add(R.string.main_albums_starred); 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(final Result<List<MediaBrowser.MediaItem>> result, final int id) { new SilentServiceTask<MusicDirectory>(downloadService) { @Override protected MusicDirectory doInBackground(MusicService musicService) throws Throwable { String albumListType; switch(id) { case R.string.main_albums_newest: albumListType = "newest"; break; case R.string.main_albums_random: albumListType = "random"; break; case R.string.main_albums_highest: albumListType = "highest"; break; case R.string.main_albums_starred: albumListType = "starred"; break; case R.string.main_albums_recent: albumListType = "recent"; break; case R.string.main_albums_frequent: albumListType = "frequent"; break; default: albumListType = "newest"; } return musicService.getAlbumList(albumListType, 20, 0, true, downloadService, null); } @Override protected void done(MusicDirectory albumSet) { List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>(); for(Entry album: albumSet.getChildren(true, false)) { MediaDescription description = new MediaDescription.Builder() .setTitle(album.getAlbumDisplay()) .setSubtitle(album.getArtist()) .setMediaId(MUSIC_DIRECTORY_PREFIX + album.getId()) .build(); mediaItems.add(new MediaBrowser.MediaItem(description, MediaBrowser.MediaItem.FLAG_BROWSABLE)); } result.sendResult(mediaItems); } }.execute(); result.detach(); } private void getLibrary(final Result<List<MediaBrowser.MediaItem>> result) { new SilentServiceTask<List<MusicFolder>>(downloadService) { @Override protected List<MusicFolder> doInBackground(MusicService musicService) throws Throwable { return musicService.getMusicFolders(false, downloadService, null); } @Override protected void done(List<MusicFolder> folders) { List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>(); for(MusicFolder folder: folders) { MediaDescription description = new MediaDescription.Builder() .setTitle(folder.getName()) .setMediaId(MUSIC_FOLDER_PREFIX + folder.getId()) .build(); mediaItems.add(new MediaBrowser.MediaItem(description, MediaBrowser.MediaItem.FLAG_BROWSABLE)); } result.sendResult(mediaItems); } }.execute(); result.detach(); } private void getIndexes(final Result<List<MediaBrowser.MediaItem>> result, final String musicFolderId) { new SilentServiceTask<Indexes>(downloadService) { @Override protected Indexes doInBackground(MusicService musicService) throws Throwable { return musicService.getIndexes(musicFolderId, false, downloadService, null); } @Override protected void done(Indexes indexes) { List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>(); // music directories for(Artist artist : indexes.getArtists()) { MediaDescription description = new MediaDescription.Builder() .setTitle(artist.getName()) .setMediaId(MUSIC_DIRECTORY_CONTENTS_PREFIX + artist.getId()) .build(); mediaItems.add(new MediaBrowser.MediaItem(description, MediaBrowser.MediaItem.FLAG_BROWSABLE)); } // music files for(Entry entry: indexes.getEntries()) { MediaDescription description = new MediaDescription.Builder() .setTitle(entry.getTitle()) .setMediaId(MUSIC_DIRECTORY_PREFIX + entry.getId()) .build(); mediaItems.add(new MediaBrowser.MediaItem(description, MediaBrowser.MediaItem.FLAG_BROWSABLE)); } result.sendResult(mediaItems); } }.execute(); result.detach(); } private void getMusicDirectory(final Result<List<MediaBrowser.MediaItem>> result, final String musicDirectoryId) { new SilentServiceTask<MusicDirectory>(downloadService) { @Override protected MusicDirectory doInBackground(MusicService musicService) throws Throwable { return musicService.getMusicDirectory(musicDirectoryId, "", false, downloadService, null); } @Override protected void done(MusicDirectory directory) { List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>(); addPlayOptions(mediaItems, musicDirectoryId, Constants.INTENT_EXTRA_NAME_ID); for(Entry entry : directory.getChildren()) { MediaDescription description; if (entry.isDirectory()) { // browse deeper description = new MediaDescription.Builder() .setTitle(entry.getTitle()) .setMediaId(MUSIC_DIRECTORY_CONTENTS_PREFIX + entry.getId()) .build(); } else { // playback options for a single item description = new MediaDescription.Builder() .setTitle(entry.getTitle()) .setMediaId(MUSIC_DIRECTORY_PREFIX + entry.getId()) .build(); } mediaItems.add(new MediaBrowser.MediaItem(description, MediaBrowser.MediaItem.FLAG_BROWSABLE)); } result.sendResult(mediaItems); } }.execute(); result.detach(); } 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 getPodcasts(final Result<List<MediaBrowser.MediaItem>> result) { new SilentServiceTask<List<PodcastChannel>>(downloadService) { @Override protected List<PodcastChannel> doInBackground(MusicService musicService) throws Throwable { return musicService.getPodcastChannels(false, downloadService, null); } @Override protected void done(List<PodcastChannel> podcasts) { List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>(); for(PodcastChannel podcast: podcasts) { MediaDescription description = new MediaDescription.Builder() .setTitle(podcast.getName()) .setMediaId(PODCAST_PREFIX + podcast.getId()) .build(); mediaItems.add(new MediaBrowser.MediaItem(description, MediaBrowser.MediaItem.FLAG_BROWSABLE)); } result.sendResult(mediaItems); } }.execute(); result.detach(); } private void getPodcastEpisodes(final Result<List<MediaBrowser.MediaItem>> result, final String podcastId) { new SilentServiceTask<MusicDirectory>(downloadService) { @Override protected MusicDirectory doInBackground(MusicService musicService) throws Throwable { return musicService.getPodcastEpisodes(false, podcastId, downloadService, null); } @Override protected void done(MusicDirectory podcasts) { List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>(); for(Entry entry: podcasts.getChildren(false, true)) { PodcastEpisode podcast = (PodcastEpisode) entry; Bundle podcastExtras = new Bundle(); podcastExtras.putSerializable(Constants.INTENT_EXTRA_ENTRY, podcast); podcastExtras.putString(Constants.INTENT_EXTRA_NAME_PODCAST_ID, podcast.getId()); MediaDescription description = new MediaDescription.Builder() .setTitle(podcast.getTitle()) .setSubtitle(Util.formatDate(downloadService, podcast.getDate(), false)) .setMediaId(PODCAST_PREFIX + podcast.getId()) .setExtras(podcastExtras) .build(); mediaItems.add(new MediaBrowser.MediaItem(description, MediaBrowser.MediaItem.FLAG_PLAYABLE)); } result.sendResult(mediaItems); } }.execute(); result.detach(); } private void getBookmarks(final Result<List<MediaBrowser.MediaItem>> result) { new SilentServiceTask<MusicDirectory>(downloadService) { @Override protected MusicDirectory doInBackground(MusicService musicService) throws Throwable { return musicService.getBookmarks(false, downloadService, null); } @Override protected void done(MusicDirectory bookmarkList) { List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>(); for(Entry entry: bookmarkList.getChildren(false, true)) { Bundle extras = new Bundle(); extras.putSerializable(Constants.INTENT_EXTRA_ENTRY, entry); extras.putString(Constants.INTENT_EXTRA_NAME_CHILD_ID, entry.getId()); MediaDescription description = new MediaDescription.Builder() .setTitle(entry.getTitle()) .setSubtitle(Util.formatDuration(entry.getBookmark().getPosition() / 1000)) .setMediaId(entry.getId()) .setExtras(extras) .build(); mediaItems.add(new MediaBrowser.MediaItem(description, MediaBrowser.MediaItem.FLAG_PLAYABLE)); } result.sendResult(mediaItems); } }.execute(); result.detach(); } private void addPlayOptions(List<MediaBrowser.MediaItem> mediaItems, String id, String idConstant) { 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)); } private void getPlayOptions(Result<List<MediaBrowser.MediaItem>> result, String id, String idConstant) { List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>(); addPlayOptions(mediaItems, id, idConstant); 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()); } } }