From 7cbd580a5b1745fb72c00de097aef19a822fc10f Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Mon, 5 Sep 2016 12:57:31 -0700 Subject: Start of #564 conversion to HttpUrlConnection --- .../dsub/service/CachedMusicService.java | 21 +- .../daneren2005/dsub/service/RESTMusicService.java | 353 +++++++++++++-------- .../dsub/service/parser/ErrorParser.java | 2 +- 3 files changed, 234 insertions(+), 142 deletions(-) (limited to 'app/src') diff --git a/app/src/main/java/github/daneren2005/dsub/service/CachedMusicService.java b/app/src/main/java/github/daneren2005/dsub/service/CachedMusicService.java index 1a17dfb3..014c2aa6 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/CachedMusicService.java +++ b/app/src/main/java/github/daneren2005/dsub/service/CachedMusicService.java @@ -732,7 +732,12 @@ public class CachedMusicService implements MusicService { @Override public Bitmap getCoverArt(Context context, Entry entry, int size, ProgressListener progressListener, SilentBackgroundTask task) throws Exception { - return musicService.getCoverArt(context, entry, size, progressListener, task); + Bitmap bitmap = FileUtil.getAlbumArtBitmap(context, entry, size); + if (bitmap != null) { + return bitmap; + } else { + return musicService.getCoverArt(context, entry, size, progressListener, task); + } } @Override @@ -1156,7 +1161,12 @@ public class CachedMusicService implements MusicService { @Override public Bitmap getAvatar(String username, int size, Context context, ProgressListener progressListener, SilentBackgroundTask task) throws Exception { - return musicService.getAvatar(username, size, context, progressListener, task); + Bitmap bitmap = FileUtil.getAvatarBitmap(context, username, size); + if(bitmap != null) { + return bitmap; + } else { + return musicService.getAvatar(username, size, context, progressListener, task); + } } @Override @@ -1187,7 +1197,12 @@ public class CachedMusicService implements MusicService { @Override public Bitmap getBitmap(String url, int size, Context context, ProgressListener progressListener, SilentBackgroundTask task) throws Exception { - return musicService.getBitmap(url, size, context, progressListener, task); + Bitmap bitmap = FileUtil.getMiscBitmap(context, url, size); + if(bitmap != null) { + return bitmap; + } else { + return musicService.getBitmap(url, size, context, progressListener, task); + } } @Override diff --git a/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java b/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java index 1f9e5494..0b67af9c 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java +++ b/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java @@ -24,6 +24,8 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; +import java.net.HttpURLConnection; +import java.net.URL; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Arrays; @@ -175,7 +177,7 @@ public class RESTMusicService implements MusicService { @Override public void ping(Context context, ProgressListener progressListener) throws Exception { - Reader reader = getReader(context, progressListener, "ping", null); + Reader reader = getReader(context, progressListener, "ping"); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -186,7 +188,7 @@ public class RESTMusicService implements MusicService { @Override public boolean isLicenseValid(Context context, ProgressListener progressListener) throws Exception { - Reader reader = getReader(context, progressListener, "getLicense", null); + Reader reader = getReader(context, progressListener, "getLicense"); try { ServerInfo serverInfo = new LicenseParser(context, getInstance(context)).parse(reader); return serverInfo.isLicenseValid(); @@ -196,7 +198,7 @@ public class RESTMusicService implements MusicService { } public List getMusicFolders(boolean refresh, Context context, ProgressListener progressListener) throws Exception { - Reader reader = getReader(context, progressListener, "getMusicFolders", null); + Reader reader = getReader(context, progressListener, "getMusicFolders"); try { return new MusicFoldersParser(context, getInstance(context)).parse(reader, progressListener); } finally { @@ -206,7 +208,7 @@ public class RESTMusicService implements MusicService { @Override public void startRescan(Context context, ProgressListener listener) throws Exception { - Reader reader = getReader(context, listener, "startRescan", null); + Reader reader = getReader(context, listener, "startRescan"); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -216,7 +218,7 @@ public class RESTMusicService implements MusicService { // Now check if still running boolean done = false; while(!done) { - reader = getReader(context, null, "scanstatus", null); + reader = getReader(context, null, "scanstatus"); try { boolean running = new ScanStatusParser(context, getInstance(context)).parse(reader, listener); if(running) { @@ -243,7 +245,7 @@ public class RESTMusicService implements MusicService { parameterValues.add(musicFolderId); } - Reader reader = getReader(context, progressListener, Util.isTagBrowsing(context, getInstance(context)) ? "getArtists" : "getIndexes", null, parameterNames, parameterValues); + Reader reader = getReader(context, progressListener, Util.isTagBrowsing(context, getInstance(context)) ? "getArtists" : "getIndexes", parameterNames, parameterValues); try { return new IndexesParser(context, getInstance(context)).parse(reader, progressListener); } finally { @@ -289,7 +291,7 @@ public class RESTMusicService implements MusicService { } private MusicDirectory getMusicDirectoryImpl(String id, String name, boolean refresh, Context context, ProgressListener progressListener) throws Exception { - Reader reader = getReader(context, progressListener, "getMusicDirectory", null, "id", id); + Reader reader = getReader(context, progressListener, "getMusicDirectory", "id", id); try { return new MusicDirectoryParser(context, getInstance(context)).parse(name, reader, progressListener); } finally { @@ -299,7 +301,7 @@ public class RESTMusicService implements MusicService { @Override public MusicDirectory getArtist(String id, String name, boolean refresh, Context context, ProgressListener progressListener) throws Exception { - Reader reader = getReader(context, progressListener, "getArtist", null, "id", id); + Reader reader = getReader(context, progressListener, "getArtist", "id", id); try { return new MusicDirectoryParser(context, getInstance(context)).parse(name, reader, progressListener); } finally { @@ -309,7 +311,7 @@ public class RESTMusicService implements MusicService { @Override public MusicDirectory getAlbum(String id, String name, boolean refresh, Context context, ProgressListener progressListener) throws Exception { - Reader reader = getReader(context, progressListener, "getAlbum", null, "id", id); + Reader reader = getReader(context, progressListener, "getAlbum", "id", id); try { return new MusicDirectoryParser(context, getInstance(context)).parse(name, reader, progressListener); } finally { @@ -333,7 +335,7 @@ public class RESTMusicService implements MusicService { private SearchResult searchOld(SearchCritera critera, Context context, ProgressListener progressListener) throws Exception { List parameterNames = Arrays.asList("any", "songCount"); List parameterValues = Arrays.asList(critera.getQuery(), critera.getSongCount()); - Reader reader = getReader(context, progressListener, "search", null, parameterNames, parameterValues); + Reader reader = getReader(context, progressListener, "search", parameterNames, parameterValues); try { return new SearchResultParser(context, getInstance(context)).parse(reader, progressListener); } finally { @@ -365,7 +367,7 @@ public class RESTMusicService implements MusicService { method = "search2"; } } - Reader reader = getReader(context, progressListener, method, null, parameterNames, parameterValues); + Reader reader = getReader(context, progressListener, method, parameterNames, parameterValues); try { return new SearchResult2Parser(context, getInstance(context)).parse(reader, progressListener); } finally { @@ -375,10 +377,7 @@ public class RESTMusicService implements MusicService { @Override public MusicDirectory getPlaylist(boolean refresh, String id, String name, Context context, ProgressListener progressListener) throws Exception { - HttpParams params = new BasicHttpParams(); - HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_GET_PLAYLIST); - - Reader reader = getReader(context, progressListener, "getPlaylist", params, "id", id); + Reader reader = getReader(context, progressListener, "getPlaylist", "id", id); try { return new PlaylistParser(context, getInstance(context)).parse(reader, progressListener); } finally { @@ -388,7 +387,7 @@ public class RESTMusicService implements MusicService { @Override public List getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception { - Reader reader = getReader(context, progressListener, "getPlaylists", null); + Reader reader = getReader(context, progressListener, "getPlaylists"); try { return new PlaylistsParser(context, getInstance(context)).parse(reader, progressListener); } finally { @@ -414,7 +413,7 @@ public class RESTMusicService implements MusicService { parameterValues.add(getOfflineSongId(entry.getId(), context, progressListener)); } - Reader reader = getReader(context, progressListener, "createPlaylist", null, parameterNames, parameterValues); + Reader reader = getReader(context, progressListener, "createPlaylist", parameterNames, parameterValues); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -424,7 +423,7 @@ public class RESTMusicService implements MusicService { @Override public void deletePlaylist(String id, Context context, ProgressListener progressListener) throws Exception { - Reader reader = getReader(context, progressListener, "deletePlaylist", null, "id", id); + Reader reader = getReader(context, progressListener, "deletePlaylist", "id", id); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -443,7 +442,7 @@ public class RESTMusicService implements MusicService { names.add("songIdToAdd"); values.add(getOfflineSongId(song.getId(), context, progressListener)); } - Reader reader = getReader(context, progressListener, "updatePlaylist", null, names, values); + Reader reader = getReader(context, progressListener, "updatePlaylist", names, values); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -462,7 +461,7 @@ public class RESTMusicService implements MusicService { names.add("songIndexToRemove"); values.add(song); } - Reader reader = getReader(context, progressListener, "updatePlaylist", null, names, values); + Reader reader = getReader(context, progressListener, "updatePlaylist", names, values); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -487,7 +486,7 @@ public class RESTMusicService implements MusicService { names.add("songIndexToRemove"); values.add(i); } - Reader reader = getReader(context, progressListener, "updatePlaylist", null, names, values); + Reader reader = getReader(context, progressListener, "updatePlaylist", names, values); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -498,7 +497,7 @@ public class RESTMusicService implements MusicService { @Override public void updatePlaylist(String id, String name, String comment, boolean pub, Context context, ProgressListener progressListener) throws Exception { checkServerVersion(context, "1.8", "Updating playlists is not supported."); - Reader reader = getReader(context, progressListener, "updatePlaylist", null, Arrays.asList("playlistId", "name", "comment", "public"), Arrays.asList(id, name, comment, pub)); + Reader reader = getReader(context, progressListener, "updatePlaylist", Arrays.asList("playlistId", "name", "comment", "public"), Arrays.asList(id, name, comment, pub)); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -508,7 +507,7 @@ public class RESTMusicService implements MusicService { @Override public Lyrics getLyrics(String artist, String title, Context context, ProgressListener progressListener) throws Exception { - Reader reader = getReader(context, progressListener, "getLyrics", null, Arrays.asList("artist", "title"), Arrays.asList(artist, title)); + Reader reader = getReader(context, progressListener, "getLyrics", Arrays.asList("artist", "title"), Arrays.asList(artist, title)); try { return new LyricsParser(context, getInstance(context)).parse(reader, progressListener); } finally { @@ -527,10 +526,10 @@ public class RESTMusicService implements MusicService { Reader reader; if(time > 0){ checkServerVersion(context, "1.8", "Scrobbling with a time not supported."); - reader = getReader(context, progressListener, "scrobble", null, Arrays.asList("id", "submission", "time"), Arrays.asList(id, submission, time)); + reader = getReader(context, progressListener, "scrobble", Arrays.asList("id", "submission", "time"), Arrays.asList(id, submission, time)); } else - reader = getReader(context, progressListener, "scrobble", null, Arrays.asList("id", "submission"), Arrays.asList(id, submission)); + reader = getReader(context, progressListener, "scrobble", Arrays.asList("id", "submission"), Arrays.asList(id, submission)); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -571,7 +570,7 @@ public class RESTMusicService implements MusicService { method = "getAlbumList"; } - Reader reader = getReader(context, progressListener, method, null, names, values, true); + Reader reader = getReader(context, progressListener, method, names, values, true); try { return new EntryListParser(context, getInstance(context)).parse(reader, progressListener); } finally { @@ -637,7 +636,7 @@ public class RESTMusicService implements MusicService { method = "getAlbumList"; } - Reader reader = getReader(context, progressListener, method, null, names, values, true); + Reader reader = getReader(context, progressListener, method, names, values, true); try { return new EntryListParser(context, instance).parse(reader, progressListener); } finally { @@ -673,7 +672,7 @@ public class RESTMusicService implements MusicService { method = "getNewaddedSongs"; } - Reader reader = getReader(context, progressListener, method, null, names, values, true); + Reader reader = getReader(context, progressListener, method, names, values, true); try { return new EntryListParser(context, getInstance(context)).parse(reader, progressListener); } finally { @@ -712,7 +711,7 @@ public class RESTMusicService implements MusicService { } } - Reader reader = getReader(context, progressListener, method, null, names, values); + Reader reader = getReader(context, progressListener, method, names, values); try { return new RandomSongsParser(context, instance).parse(reader, progressListener); } finally { @@ -746,7 +745,7 @@ public class RESTMusicService implements MusicService { method = "getStarred"; } - Reader reader = getReader(context, progressListener, method, null, names, values, true); + Reader reader = getReader(context, progressListener, method, names, values, true); try { return new StarredListParser(context, instance).parse(reader, progressListener); } finally { @@ -756,10 +755,7 @@ public class RESTMusicService implements MusicService { @Override public MusicDirectory getRandomSongs(int size, String musicFolderId, String genre, String startYear, String endYear, Context context, ProgressListener progressListener) throws Exception { - HttpParams params = new BasicHttpParams(); - HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_GET_RANDOM_SONGS); - - List names = new ArrayList(); + List names = new ArrayList(); List values = new ArrayList(); names.add("size"); @@ -798,7 +794,7 @@ public class RESTMusicService implements MusicService { values.add(endYear); } - Reader reader = getReader(context, progressListener, "getRandomSongs", params, names, values); + Reader reader = getReader(context, progressListener, "getRandomSongs", names, values); try { return new RandomSongsParser(context, getInstance(context)).parse(reader, progressListener); } finally { @@ -828,15 +824,12 @@ public class RESTMusicService implements MusicService { @Override public Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, ProgressListener progressListener, SilentBackgroundTask task) throws Exception { - // Synchronize on the entry so that we don't download concurrently for the same song. synchronized (entry) { - // Use cached file, if existing. - Bitmap bitmap = FileUtil.getAlbumArtBitmap(context, entry, size); - if (bitmap != null) { - return bitmap; - } + + + String url = getRestUrl(context, "getCoverArt"); @@ -1056,7 +1049,7 @@ public class RESTMusicService implements MusicService { private RemoteStatus executeJukeboxCommand(Context context, ProgressListener progressListener, List parameterNames, List parameterValues) throws Exception { checkServerVersion(context, "1.7", "Jukebox not supported."); - Reader reader = getReader(context, progressListener, "jukeboxControl", null, parameterNames, parameterValues); + Reader reader = getReader(context, progressListener, "jukeboxControl", parameterNames, parameterValues); try { return new JukeboxStatusParser(context, getInstance(context)).parse(reader); } finally { @@ -1095,7 +1088,7 @@ public class RESTMusicService implements MusicService { } } - Reader reader = getReader(context, progressListener, starred ? "star" : "unstar", null, names, values); + Reader reader = getReader(context, progressListener, starred ? "star" : "unstar", names, values); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -1107,7 +1100,7 @@ public class RESTMusicService implements MusicService { public List getShares(Context context, ProgressListener progressListener) throws Exception { checkServerVersion(context, "1.6", "Shares not supported."); - Reader reader = getReader(context, progressListener, "getShares", null); + Reader reader = getReader(context, progressListener, "getShares"); try { return new ShareParser(context, getInstance(context)).parse(reader, progressListener); } finally { @@ -1135,7 +1128,7 @@ public class RESTMusicService implements MusicService { parameterValues.add(expires); } - Reader reader = getReader(context, progressListener, "createShare", null, parameterNames, parameterValues); + Reader reader = getReader(context, progressListener, "createShare", parameterNames, parameterValues); try { return new ShareParser(context, getInstance(context)).parse(reader, progressListener); } @@ -1148,16 +1141,13 @@ public class RESTMusicService implements MusicService { public void deleteShare(String id, Context context, ProgressListener progressListener) throws Exception { checkServerVersion(context, "1.6", "Shares not supported."); - HttpParams params = new BasicHttpParams(); - HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_GET_RANDOM_SONGS); - List parameterNames = new ArrayList(); List parameterValues = new ArrayList(); parameterNames.add("id"); parameterValues.add(id); - Reader reader = getReader(context, progressListener, "deleteShare", params, parameterNames, parameterValues); + Reader reader = getReader(context, progressListener, "deleteShare", parameterNames, parameterValues); try { new ErrorParser(context, getInstance(context)).parse(reader); @@ -1171,9 +1161,6 @@ public class RESTMusicService implements MusicService { public void updateShare(String id, String description, Long expires, Context context, ProgressListener progressListener) throws Exception { checkServerVersion(context, "1.6", "Updating share not supported."); - HttpParams params = new BasicHttpParams(); - HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_GET_RANDOM_SONGS); - List parameterNames = new ArrayList(); List parameterValues = new ArrayList(); @@ -1188,7 +1175,7 @@ public class RESTMusicService implements MusicService { parameterNames.add("expires"); parameterValues.add(expires); - Reader reader = getReader(context, progressListener, "updateShare", params, parameterNames, parameterValues); + Reader reader = getReader(context, progressListener, "updateShare", parameterNames, parameterValues); try { new ErrorParser(context, getInstance(context)).parse(reader); } @@ -1201,16 +1188,13 @@ public class RESTMusicService implements MusicService { public List getChatMessages(Long since, Context context, ProgressListener progressListener) throws Exception { checkServerVersion(context, "1.2", "Chat not supported."); - HttpParams params = new BasicHttpParams(); - HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_GET_RANDOM_SONGS); - List parameterNames = new ArrayList(); List parameterValues = new ArrayList(); parameterNames.add("since"); parameterValues.add(since); - Reader reader = getReader(context, progressListener, "getChatMessages", params, parameterNames, parameterValues); + Reader reader = getReader(context, progressListener, "getChatMessages", parameterNames, parameterValues); try { return new ChatMessageParser(context, getInstance(context)).parse(reader, progressListener); @@ -1223,16 +1207,13 @@ public class RESTMusicService implements MusicService { public void addChatMessage(String message, Context context, ProgressListener progressListener) throws Exception { checkServerVersion(context, "1.2", "Chat not supported."); - HttpParams params = new BasicHttpParams(); - HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_GET_RANDOM_SONGS); - List parameterNames = new ArrayList(); List parameterValues = new ArrayList(); parameterNames.add("message"); parameterValues.add(message); - Reader reader = getReader(context, progressListener, "addChatMessage", params, parameterNames, parameterValues); + Reader reader = getReader(context, progressListener, "addChatMessage", parameterNames, parameterValues); try { new ErrorParser(context, getInstance(context)).parse(reader); @@ -1245,7 +1226,7 @@ public class RESTMusicService implements MusicService { public List getGenres(boolean refresh, Context context, ProgressListener progressListener) throws Exception { checkServerVersion(context, "1.9", "Genres not supported."); - Reader reader = getReader(context, progressListener, "getGenres", null); + Reader reader = getReader(context, progressListener, "getGenres"); try { return new GenreParser(context, getInstance(context)).parse(reader, progressListener); } finally { @@ -1257,9 +1238,6 @@ public class RESTMusicService implements MusicService { public MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context, ProgressListener progressListener) throws Exception { checkServerVersion(context, "1.9", "Genres not supported."); - HttpParams params = new BasicHttpParams(); - HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_GET_RANDOM_SONGS); - List parameterNames = new ArrayList(); List parameterValues = new ArrayList(); @@ -1280,7 +1258,7 @@ public class RESTMusicService implements MusicService { } } - Reader reader = getReader(context, progressListener, "getSongsByGenre", params, parameterNames, parameterValues, true); + Reader reader = getReader(context, progressListener, "getSongsByGenre", parameterNames, parameterValues, true); try { return new RandomSongsParser(context, instance).parse(reader, progressListener); } finally { @@ -1299,7 +1277,7 @@ public class RESTMusicService implements MusicService { parameterValues.add(size); String method = ServerInfo.isMadsonic(context, getInstance(context)) ? "getTopTrackSongs" : "getTopSongs"; - Reader reader = getReader(context, progressListener, method, null, parameterNames, parameterValues); + Reader reader = getReader(context, progressListener, method, parameterNames, parameterValues); try { return new TopSongsParser(context, getInstance(context)).parse(reader, progressListener); } finally { @@ -1311,7 +1289,7 @@ public class RESTMusicService implements MusicService { public List getPodcastChannels(boolean refresh, Context context, ProgressListener progressListener) throws Exception { checkServerVersion(context, "1.6", "Podcasts not supported."); - Reader reader = getReader(context, progressListener, "getPodcasts", null, Arrays.asList("includeEpisodes"), Arrays.asList("false")); + Reader reader = getReader(context, progressListener, "getPodcasts", Arrays.asList("includeEpisodes"), Arrays.asList("false")); try { List channels = new PodcastChannelParser(context, getInstance(context)).parse(reader, progressListener); @@ -1333,7 +1311,7 @@ public class RESTMusicService implements MusicService { @Override public MusicDirectory getPodcastEpisodes(boolean refresh, String id, Context context, ProgressListener progressListener) throws Exception { - Reader reader = getReader(context, progressListener, "getPodcasts", null, Arrays.asList("id"), Arrays.asList(id)); + Reader reader = getReader(context, progressListener, "getPodcasts", Arrays.asList("id"), Arrays.asList(id)); try { return new PodcastEntryParser(context, getInstance(context)).parse(id, reader, progressListener); } finally { @@ -1343,7 +1321,7 @@ public class RESTMusicService implements MusicService { @Override public MusicDirectory getNewestPodcastEpisodes(boolean refresh, Context context, ProgressListener progressListener, int count) throws Exception { - Reader reader = getReader(context, progressListener, "getNewestPodcasts", null, Arrays.asList("count"), Arrays.asList(count), true); + Reader reader = getReader(context, progressListener, "getNewestPodcasts", Arrays.asList("count"), Arrays.asList(count), true); try { return new PodcastEntryParser(context, getInstance(context)).parse(null, reader, progressListener); @@ -1356,7 +1334,7 @@ public class RESTMusicService implements MusicService { public void refreshPodcasts(Context context, ProgressListener progressListener) throws Exception { checkServerVersion(context, "1.9", "Refresh podcasts not supported."); - Reader reader = getReader(context, progressListener, "refreshPodcasts", null); + Reader reader = getReader(context, progressListener, "refreshPodcasts"); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -1368,7 +1346,7 @@ public class RESTMusicService implements MusicService { public void createPodcastChannel(String url, Context context, ProgressListener progressListener) throws Exception{ checkServerVersion(context, "1.9", "Creating podcasts not supported."); - Reader reader = getReader(context, progressListener, "createPodcastChannel", null, "url", url); + Reader reader = getReader(context, progressListener, "createPodcastChannel", "url", url); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -1380,7 +1358,7 @@ public class RESTMusicService implements MusicService { public void deletePodcastChannel(String id, Context context, ProgressListener progressListener) throws Exception { checkServerVersion(context, "1.9", "Deleting podcasts not supported."); - Reader reader = getReader(context, progressListener, "deletePodcastChannel", null, "id", id); + Reader reader = getReader(context, progressListener, "deletePodcastChannel", "id", id); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -1392,7 +1370,7 @@ public class RESTMusicService implements MusicService { public void downloadPodcastEpisode(String id, Context context, ProgressListener progressListener) throws Exception{ checkServerVersion(context, "1.9", "Downloading podcasts not supported."); - Reader reader = getReader(context, progressListener, "downloadPodcastEpisode", null, "id", id); + Reader reader = getReader(context, progressListener, "downloadPodcastEpisode", "id", id); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -1404,7 +1382,7 @@ public class RESTMusicService implements MusicService { public void deletePodcastEpisode(String id, String parent, ProgressListener progressListener, Context context) throws Exception{ checkServerVersion(context, "1.9", "Deleting podcasts not supported."); - Reader reader = getReader(context, progressListener, "deletePodcastEpisode", null, "id", id); + Reader reader = getReader(context, progressListener, "deletePodcastEpisode", "id", id); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -1416,7 +1394,7 @@ public class RESTMusicService implements MusicService { public void setRating(MusicDirectory.Entry entry, int rating, Context context, ProgressListener progressListener) throws Exception { checkServerVersion(context, "1.6", "Setting ratings not supported."); - Reader reader = getReader(context, progressListener, "setRating", null, Arrays.asList("id", "rating"), Arrays.asList(entry.getId(), rating)); + Reader reader = getReader(context, progressListener, "setRating", Arrays.asList("id", "rating"), Arrays.asList(entry.getId(), rating)); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -1428,7 +1406,7 @@ public class RESTMusicService implements MusicService { public MusicDirectory getBookmarks(boolean refresh, Context context, ProgressListener progressListener) throws Exception { checkServerVersion(context, "1.9", "Bookmarks not supported."); - Reader reader = getReader(context, progressListener, "getBookmarks", null); + Reader reader = getReader(context, progressListener, "getBookmarks"); try { return new BookmarkParser(context, getInstance(context)).parse(reader, progressListener); } finally { @@ -1440,7 +1418,7 @@ public class RESTMusicService implements MusicService { public void createBookmark(MusicDirectory.Entry entry, int position, String comment, Context context, ProgressListener progressListener) throws Exception { checkServerVersion(context, "1.9", "Creating bookmarks not supported."); - Reader reader = getReader(context, progressListener, "createBookmark", null, Arrays.asList("id", "position", "comment"), Arrays.asList(entry.getId(), position, comment)); + Reader reader = getReader(context, progressListener, "createBookmark", Arrays.asList("id", "position", "comment"), Arrays.asList(entry.getId(), position, comment)); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -1452,7 +1430,7 @@ public class RESTMusicService implements MusicService { public void deleteBookmark(MusicDirectory.Entry entry, Context context, ProgressListener progressListener) throws Exception { checkServerVersion(context, "1.9", "Deleting bookmarks not supported."); - Reader reader = getReader(context, progressListener, "deleteBookmark", null, Arrays.asList("id"), Arrays.asList(entry.getId())); + Reader reader = getReader(context, progressListener, "deleteBookmark", Arrays.asList("id"), Arrays.asList(entry.getId())); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -1462,7 +1440,7 @@ public class RESTMusicService implements MusicService { @Override public User getUser(boolean refresh, String username, Context context, ProgressListener progressListener) throws Exception { - Reader reader = getReader(context, progressListener, "getUser", null, Arrays.asList("username"), Arrays.asList(username)); + Reader reader = getReader(context, progressListener, "getUser", Arrays.asList("username"), Arrays.asList(username)); try { List users = new UserParser(context, getInstance(context)).parse(reader, progressListener); if(users.size() > 0) { @@ -1480,7 +1458,7 @@ public class RESTMusicService implements MusicService { public List getUsers(boolean refresh, Context context, ProgressListener progressListener) throws Exception { checkServerVersion(context, "1.8", "Getting user list is not supported"); - Reader reader = getReader(context, progressListener, "getUsers", null); + Reader reader = getReader(context, progressListener, "getUsers"); try { return new UserParser(context, getInstance(context)).parse(reader, progressListener); } finally { @@ -1514,7 +1492,7 @@ public class RESTMusicService implements MusicService { } } - Reader reader = getReader(context, progressListener, "createUser", null, names, values); + Reader reader = getReader(context, progressListener, "createUser", names, values); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -1548,7 +1526,7 @@ public class RESTMusicService implements MusicService { } } - Reader reader = getReader(context, progressListener, "updateUser", null, names, values); + Reader reader = getReader(context, progressListener, "updateUser", names, values); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -1558,7 +1536,7 @@ public class RESTMusicService implements MusicService { @Override public void deleteUser(String username, Context context, ProgressListener progressListener) throws Exception { - Reader reader = getReader(context, progressListener, "deleteUser", null, Arrays.asList("username"), Arrays.asList(username)); + Reader reader = getReader(context, progressListener, "deleteUser", Arrays.asList("username"), Arrays.asList(username)); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -1568,7 +1546,7 @@ public class RESTMusicService implements MusicService { @Override public void changeEmail(String username, String email, Context context, ProgressListener progressListener) throws Exception { - Reader reader = getReader(context, progressListener, "updateUser", null, Arrays.asList("username", "email"), Arrays.asList(username, email)); + Reader reader = getReader(context, progressListener, "updateUser", Arrays.asList("username", "email"), Arrays.asList(username, email)); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -1578,7 +1556,7 @@ public class RESTMusicService implements MusicService { @Override public void changePassword(String username, String password, Context context, ProgressListener progressListener) throws Exception { - Reader reader = getReader(context, progressListener, "changePassword", null, Arrays.asList("username", "password"), Arrays.asList(username, password)); + Reader reader = getReader(context, progressListener, "changePassword", Arrays.asList("username", "password"), Arrays.asList(username, password)); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -1596,12 +1574,6 @@ public class RESTMusicService implements MusicService { // Synchronize on the username so that we don't download concurrently for // the same user. synchronized (username) { - // Use cached file, if existing. - Bitmap bitmap = FileUtil.getAvatarBitmap(context, username, size); - if(bitmap != null) { - return bitmap; - } - String url = Util.getRestUrl(context, "getAvatar"); InputStream in = null; try @@ -1664,7 +1636,7 @@ public class RESTMusicService implements MusicService { method = "getArtistInfo"; } - Reader reader = getReader(context, progressListener, method, null, Arrays.asList("id", "includeNotPresent"), Arrays.asList(id, "true")); + Reader reader = getReader(context, progressListener, method, Arrays.asList("id", "includeNotPresent"), Arrays.asList(id, "true")); try { return new ArtistInfoParser(context, getInstance(context)).parse(reader, progressListener); } finally { @@ -1676,12 +1648,6 @@ public class RESTMusicService implements MusicService { public Bitmap getBitmap(String url, int size, Context context, ProgressListener progressListener, SilentBackgroundTask task) throws Exception { // Synchronize on the url so that we don't download concurrently synchronized (url) { - // Use cached file, if existing. - Bitmap bitmap = FileUtil.getMiscBitmap(context, url, size); - if(bitmap != null) { - return bitmap; - } - InputStream in = null; try { HttpEntity entity = getEntityForURL(context, url, null, null, null, progressListener, task); @@ -1722,7 +1688,7 @@ public class RESTMusicService implements MusicService { @Override public MusicDirectory getVideos(boolean refresh, Context context, ProgressListener progressListener) throws Exception { - Reader reader = getReader(context, progressListener, "getVideos", null, true); + Reader reader = getReader(context, progressListener, "getVideos"); try { return new VideosParser(context, getInstance(context)).parse(reader, progressListener); } finally { @@ -1746,7 +1712,7 @@ public class RESTMusicService implements MusicService { parameterNames.add("position"); parameterValues.add(position); - Reader reader = getReader(context, progressListener, "savePlayQueue", null, parameterNames, parameterValues); + Reader reader = getReader(context, progressListener, "savePlayQueue", parameterNames, parameterValues); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -1756,7 +1722,7 @@ public class RESTMusicService implements MusicService { @Override public PlayerQueue getPlayQueue(Context context, ProgressListener progressListener) throws Exception { - Reader reader = getReader(context, progressListener, "getPlayQueue", null); + Reader reader = getReader(context, progressListener, "getPlayQueue"); try { return new PlayQueueParser(context, getInstance(context)).parse(reader, progressListener); } finally { @@ -1876,66 +1842,138 @@ public class RESTMusicService implements MusicService { this.instance = instance; } - private Reader getReader(Context context, ProgressListener progressListener, String method, HttpParams requestParams) throws Exception { - return getReader(context, progressListener, method, requestParams, false); + // Helper classes to get a reader for the request + private Reader getReader(Context context, ProgressListener progressListener, String method) throws Exception { + return getReader(context, progressListener, method, (List)null, null); } - private Reader getReader(Context context, ProgressListener progressListener, String method, HttpParams requestParams, boolean throwsError) throws Exception { - return getReader(context, progressListener, method, requestParams, Collections.emptyList(), Collections.emptyList(), throwsError); - } - private Reader getReader(Context context, ProgressListener progressListener, String method, - HttpParams requestParams, String parameterName, Object parameterValue) throws Exception { - return getReader(context, progressListener, method, requestParams, Arrays.asList(parameterName), Arrays.asList(parameterValue)); + private Reader getReader(Context context, ProgressListener progressListener, String method, String parameterName, Object parameterValue) throws Exception { + return getReader(context, progressListener, method, Arrays.asList(parameterName), Arrays.asList(parameterValue)); } - private Reader getReader(Context context, ProgressListener progressListener, String method, - HttpParams requestParams, List parameterNames, List parameterValues) throws Exception { - return getReader(context, progressListener, method, requestParams, parameterNames, parameterValues, false); + private Reader getReader(Context context, ProgressListener progressListener, String method, List parameterNames, List parameterValues) throws Exception { + return getReader(context, progressListener, method, parameterNames, parameterValues, false); } - private Reader getReader(Context context, ProgressListener progressListener, String method, - HttpParams requestParams, List parameterNames, List parameterValues, boolean throwErrors) throws Exception { - + private Reader getReader(Context context, ProgressListener progressListener, String method, List parameterNames, List parameterValues, boolean throwErrors) throws Exception { if (progressListener != null) { progressListener.updateProgress(R.string.service_connecting); } String url = getRestUrl(context, method); - return getReaderForURL(context, url, requestParams, parameterNames, parameterValues, progressListener, throwErrors); + return getReaderForURL(context, url, parameterNames, parameterValues, progressListener, throwErrors); } - private Reader getReaderForURL(Context context, String url, HttpParams requestParams, List parameterNames, - List parameterValues, ProgressListener progressListener) throws Exception { - return getReaderForURL(context, url, requestParams, parameterNames, parameterValues, progressListener, true); + private Reader getReaderForURL(Context context, String url, List parameterNames, List parameterValues, ProgressListener progressListener) throws Exception { + return getReaderForURL(context, url, parameterNames, parameterValues, progressListener, true); } - private Reader getReaderForURL(Context context, String url, HttpParams requestParams, List parameterNames, - List parameterValues, ProgressListener progressListener, boolean throwErrors) throws Exception { - HttpEntity entity = getEntityForURL(context, url, requestParams, parameterNames, parameterValues, progressListener, throwErrors); - if (entity == null) { - throw new RuntimeException("No entity received for URL " + url); - } + private Reader getReaderForURL(Context context, String url, List parameterNames, List parameterValues, ProgressListener progressListener, boolean throwErrors) throws Exception { + InputStream in = getInputStream(context, url, parameterNames, parameterValues, progressListener, throwErrors); + return new InputStreamReader(in, Constants.UTF_8); + } - InputStream in = entity.getContent(); - Header contentEncoding = entity.getContentEncoding(); - if (contentEncoding != null && contentEncoding.getValue().equalsIgnoreCase("gzip")) { + // Helper classes to open a connection to a server + private InputStream getInputStream(Context context, String url, List parameterNames, List parameterValues, ProgressListener progressListener, boolean throwsErrors) throws Exception { + HttpURLConnection connection = getConnection(context, url, parameterNames, parameterValues, progressListener, throwsErrors); + + InputStream in = connection.getInputStream(); + if("gzip".equals(connection.getContentEncoding())) { in = new GZIPInputStream(in); } - return new InputStreamReader(in, Constants.UTF_8); - } - private HttpEntity getEntityForURL(Context context, String url, HttpParams requestParams, List parameterNames, - List parameterValues, ProgressListener progressListener, boolean throwErrors) throws Exception { + return in; + } + + private HttpURLConnection getConnection(Context context, String url, List parameterNames, List parameterValues, ProgressListener progressListener, boolean throwErrors) throws Exception { + if(throwErrors) { + return getConnection(context, url, parameterNames, parameterValues); + } else { + return getConnection(context, url, parameterNames, parameterValues, progressListener, HTTP_REQUEST_MAX_ATTEMPTS, 0); + } + } + private HttpURLConnection getConnection(Context context, String url, List parameterNames, List parameterValues, ProgressListener progressListener, int retriesLeft, int attempts) throws Exception { + try { + return getConnection(context, url, parameterNames, parameterValues); + } catch (IOException x) { + if(retriesLeft > 0) { + if (progressListener != null) { + String msg = context.getResources().getString(R.string.music_service_retry, attempts, HTTP_REQUEST_MAX_ATTEMPTS - 1); + progressListener.updateProgress(msg); + } + + Log.w(TAG, "Got IOException " + x + " (" + attempts + "), will retry"); + Thread.sleep(2000L); + + // TODO: Increase timeouts on failures + return getConnection(context, url, parameterNames, parameterValues, progressListener, retriesLeft--, attempts + 1); + } else { + throw x; + } + } + } - return getEntityForURL(context, url, requestParams, parameterNames, parameterValues, progressListener, null, throwErrors); + private HttpURLConnection getConnection(Context context, String url, List parameterNames, List parameterValues) throws Exception { + // Add params to query + if (parameterNames != null) { + StringBuilder builder = new StringBuilder(url); + for (int i = 0; i < parameterNames.size(); i++) { + builder.append("&").append(parameterNames.get(i)).append("="); + String part = URLEncoder.encode(String.valueOf(parameterValues.get(i)), "UTF-8"); + part = part.replaceAll("\\%27", "'"); + builder.append(part); + } + url = builder.toString(); + } + + // Rewrite url based on redirects + String rewrittenUrl = rewriteUrlWithRedirect(context, url); + if(rewrittenUrl.indexOf("scanstatus") == -1) { + Log.i(TAG, stripUrlInfo(rewrittenUrl)); + } + + return getConnection(context, rewrittenUrl); } + private HttpURLConnection getConnection(Context context, String url) throws Exception { + // Connect and add headers + URL urlObj = new URL(url); + HttpURLConnection connection = (HttpURLConnection) urlObj.openConnection(); + if(url.indexOf("getCoverArt") == -1 && url.indexOf("stream") == -1 && url.indexOf("getAvatar") == -1) { + connection.addRequestProperty("Accept-Encoding", "gzip"); + } + connection.addRequestProperty("User-Agent", Constants.REST_CLIENT_ID); + + // Set timeout + // TODO: Should we have more fine grained timeouts like before? + SharedPreferences prefs = Util.getPreferences(context); + int networkTimeout = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT, SOCKET_CONNECT_TIMEOUT + "")); + connection.setConnectTimeout(networkTimeout); + connection.setReadTimeout(SOCKET_READ_TIMEOUT_DEFAULT); + + // TODO: Self signed certificates + detectRedirect(context, urlObj, connection); + return connection; + } + + + + + + + + + + + + + + + + + private HttpEntity getEntityForURL(Context context, String url, HttpParams requestParams, List parameterNames, List parameterValues, ProgressListener progressListener, SilentBackgroundTask task) throws Exception { return getResponseForURL(context, url, requestParams, parameterNames, parameterValues, null, progressListener, task, false).getEntity(); } - private HttpEntity getEntityForURL(Context context, String url, HttpParams requestParams, List parameterNames, - List parameterValues, ProgressListener progressListener, SilentBackgroundTask task, boolean throwsError) throws Exception { - return getResponseForURL(context, url, requestParams, parameterNames, parameterValues, null, progressListener, task, throwsError).getEntity(); - } private HttpResponse getResponseForURL(Context context, String url, HttpParams requestParams, List parameterNames, List parameterValues, @@ -2098,6 +2136,45 @@ public class RESTMusicService implements MusicService { } } + + + + + + + + + + + + + + + private void detectRedirect(Context context, URL originalUrl, HttpURLConnection connection) throws Exception { + detectRedirect(context, originalUrl, connection.getURL()); + } + private void detectRedirect(Context context, URL originalUrl, URL redirectedUrl) throws Exception { + detectRedirect(context, originalUrl.toExternalForm(), redirectedUrl.toExternalForm()); + } + private void detectRedirect(Context context, String originalUrl, String redirectedUrl) throws Exception { + if(redirectedUrl != null && "http://subsonic.org/pages/".equals(redirectedUrl)) { + throw new Exception("Invalid url, redirects to http://subsonic.org/pages/"); + } + + int fromIndex = originalUrl.indexOf("/rest/"); + int toIndex = redirectedUrl.indexOf("/rest/"); + if(fromIndex != -1 && toIndex != -1 && !Util.equals(originalUrl, redirectedUrl)) { + redirectFrom = originalUrl.substring(0, fromIndex); + redirectTo = redirectedUrl.substring(0, toIndex); + + if (redirectFrom.compareTo(redirectTo) != 0) { + Log.i(TAG, redirectFrom + " redirects to " + redirectTo); + } + redirectionLastChecked = System.currentTimeMillis(); + redirectionNetworkType = getCurrentNetworkType(context); + } + } + private String rewriteUrlWithRedirect(Context context, String url) { // Only cache for a certain time. diff --git a/app/src/main/java/github/daneren2005/dsub/service/parser/ErrorParser.java b/app/src/main/java/github/daneren2005/dsub/service/parser/ErrorParser.java index afb05928..1b389f80 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/parser/ErrorParser.java +++ b/app/src/main/java/github/daneren2005/dsub/service/parser/ErrorParser.java @@ -27,7 +27,6 @@ import java.io.Reader; * @author Sindre Mehus */ public class ErrorParser extends AbstractParser { - public ErrorParser(Context context, int instance) { super(context, instance); } @@ -45,5 +44,6 @@ public class ErrorParser extends AbstractParser { } while (eventType != XmlPullParser.END_DOCUMENT); validate(); + reader.close(); } } \ No newline at end of file -- cgit v1.2.3 From a1a01664483fc4a39b265002a44f32df8332f2a3 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Wed, 28 Sep 2016 17:41:33 -0700 Subject: #564: Implement HttpUrlConnection for bitmaps/song downloads as well --- .../dsub/service/CachedMusicService.java | 5 +- .../daneren2005/dsub/service/DownloadFile.java | 28 +- .../daneren2005/dsub/service/MusicService.java | 5 +- .../dsub/service/OfflineMusicService.java | 5 +- .../daneren2005/dsub/service/RESTMusicService.java | 537 ++++++--------------- 5 files changed, 153 insertions(+), 427 deletions(-) (limited to 'app/src') diff --git a/app/src/main/java/github/daneren2005/dsub/service/CachedMusicService.java b/app/src/main/java/github/daneren2005/dsub/service/CachedMusicService.java index e3990453..9fd26fe5 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/CachedMusicService.java +++ b/app/src/main/java/github/daneren2005/dsub/service/CachedMusicService.java @@ -20,6 +20,7 @@ package github.daneren2005.dsub.service; import java.io.File; import java.io.IOException; +import java.net.HttpURLConnection; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; @@ -27,8 +28,6 @@ import java.util.List; import java.util.ListIterator; import java.util.concurrent.TimeUnit; -import org.apache.http.HttpResponse; - import android.content.Context; import android.graphics.Bitmap; import android.util.Log; @@ -742,7 +741,7 @@ public class CachedMusicService implements MusicService { } @Override - public HttpResponse getDownloadInputStream(Context context, Entry song, long offset, int maxBitrate, SilentBackgroundTask task) throws Exception { + public HttpURLConnection getDownloadInputStream(Context context, Entry song, long offset, int maxBitrate, SilentBackgroundTask task) throws Exception { return musicService.getDownloadInputStream(context, song, offset, maxBitrate, task); } diff --git a/app/src/main/java/github/daneren2005/dsub/service/DownloadFile.java b/app/src/main/java/github/daneren2005/dsub/service/DownloadFile.java index d1c594ce..30e06982 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/DownloadFile.java +++ b/app/src/main/java/github/daneren2005/dsub/service/DownloadFile.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.io.FileNotFoundException; import java.io.InputStream; import java.io.OutputStream; +import java.net.HttpURLConnection; import android.content.Context; import android.net.wifi.WifiManager; @@ -39,15 +40,6 @@ import github.daneren2005.dsub.util.Util; import github.daneren2005.dsub.util.CacheCleaner; import github.daneren2005.serverproxy.BufferFile; -import org.apache.http.Header; - -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; - -/** - * @author Sindre Mehus - * @version $Id$ - */ public class DownloadFile implements BufferFile { private static final String TAG = DownloadFile.class.getSimpleName(); private static final int MAX_FAILURES = 5; @@ -467,17 +459,15 @@ public class DownloadFile implements BufferFile { } if(compare) { // Attempt partial HTTP GET, appending to the file if it exists. - HttpResponse response = musicService.getDownloadInputStream(context, song, partialFile.length(), bitRate, DownloadTask.this); - Header contentLengthHeader = response.getFirstHeader("Content-Length"); - if(contentLengthHeader != null) { - String contentLengthString = contentLengthHeader.getValue(); - if(contentLengthString != null) { - Log.i(TAG, "Content Length: " + contentLengthString); - contentLength = Long.parseLong(contentLengthString); - } + HttpURLConnection connection = musicService.getDownloadInputStream(context, song, partialFile.length(), bitRate, DownloadTask.this); + long contentLength = connection.getContentLength(); + if(contentLength > 0) { + Log.i(TAG, "Content Length: " + contentLength); + DownloadFile.this.contentLength = contentLength; } - in = response.getEntity().getContent(); - boolean partial = response.getStatusLine().getStatusCode() == HttpStatus.SC_PARTIAL_CONTENT; + + in = connection.getInputStream(); + boolean partial = connection.getResponseCode() == HttpURLConnection.HTTP_PARTIAL; if (partial) { Log.i(TAG, "Executed partial HTTP GET, skipping " + partialFile.length() + " bytes"); } diff --git a/app/src/main/java/github/daneren2005/dsub/service/MusicService.java b/app/src/main/java/github/daneren2005/dsub/service/MusicService.java index 1e275108..6a58e340 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/MusicService.java +++ b/app/src/main/java/github/daneren2005/dsub/service/MusicService.java @@ -18,15 +18,14 @@ */ package github.daneren2005.dsub.service; +import java.net.HttpURLConnection; import java.util.List; -import org.apache.http.HttpResponse; import android.content.Context; import android.graphics.Bitmap; import github.daneren2005.dsub.domain.ArtistInfo; -import github.daneren2005.dsub.domain.Bookmark; import github.daneren2005.dsub.domain.ChatMessage; import github.daneren2005.dsub.domain.Genre; import github.daneren2005.dsub.domain.Indexes; @@ -104,7 +103,7 @@ public interface MusicService { Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, ProgressListener progressListener, SilentBackgroundTask task) throws Exception; - HttpResponse getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, SilentBackgroundTask task) throws Exception; + HttpURLConnection getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, SilentBackgroundTask task) throws Exception; String getMusicUrl(Context context, MusicDirectory.Entry song, int maxBitrate) throws Exception; diff --git a/app/src/main/java/github/daneren2005/dsub/service/OfflineMusicService.java b/app/src/main/java/github/daneren2005/dsub/service/OfflineMusicService.java index 2c439ec4..da6c37f1 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/OfflineMusicService.java +++ b/app/src/main/java/github/daneren2005/dsub/service/OfflineMusicService.java @@ -21,6 +21,7 @@ package github.daneren2005.dsub.service; import java.io.File; import java.io.Reader; import java.io.FileReader; +import java.net.HttpURLConnection; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -34,8 +35,6 @@ import android.content.SharedPreferences; import android.graphics.Bitmap; import android.util.Log; -import org.apache.http.HttpResponse; - import github.daneren2005.dsub.domain.Artist; import github.daneren2005.dsub.domain.ArtistInfo; import github.daneren2005.dsub.domain.ChatMessage; @@ -227,7 +226,7 @@ public class OfflineMusicService implements MusicService { } @Override - public HttpResponse getDownloadInputStream(Context context, Entry song, long offset, int maxBitrate, SilentBackgroundTask task) throws Exception { + public HttpURLConnection getDownloadInputStream(Context context, Entry song, long offset, int maxBitrate, SilentBackgroundTask task) throws Exception { throw new OfflineException(ERRORMSG); } diff --git a/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java b/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java index 50404ffe..cc475d4f 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java +++ b/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java @@ -32,21 +32,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; -import java.util.concurrent.atomic.AtomicReference; - -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpHost; -import org.apache.http.HttpResponse; -import org.apache.http.NameValuePair; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.UsernamePasswordCredentials; + import org.apache.http.client.HttpClient; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.conn.params.ConnManagerParams; import org.apache.http.conn.params.ConnPerRouteBean; import org.apache.http.conn.scheme.PlainSocketFactory; @@ -55,21 +42,15 @@ import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.scheme.SocketFactory; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; -import org.apache.http.message.BasicHeader; -import org.apache.http.message.BasicNameValuePair; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; -import org.apache.http.protocol.BasicHttpContext; -import org.apache.http.protocol.ExecutionContext; -import org.apache.http.protocol.HttpContext; import android.content.Context; import android.content.SharedPreferences; import android.graphics.Bitmap; import android.net.ConnectivityManager; import android.net.NetworkInfo; -import android.os.Looper; import android.util.Log; import github.daneren2005.dsub.R; @@ -104,7 +85,6 @@ import github.daneren2005.dsub.service.parser.UserParser; import github.daneren2005.dsub.service.parser.VideosParser; import github.daneren2005.dsub.service.ssl.SSLSocketFactory; import github.daneren2005.dsub.service.ssl.TrustSelfSignedStrategy; -import github.daneren2005.dsub.util.BackgroundTask; import github.daneren2005.dsub.util.Pair; import github.daneren2005.dsub.util.SilentBackgroundTask; import github.daneren2005.dsub.util.Constants; @@ -115,9 +95,10 @@ import github.daneren2005.dsub.util.Util; import java.io.*; import java.util.zip.GZIPInputStream; -/** - * @author Sindre Mehus - */ +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSession; + public class RESTMusicService implements MusicService { private static final String TAG = RESTMusicService.class.getSimpleName(); @@ -125,7 +106,6 @@ public class RESTMusicService implements MusicService { private static final int SOCKET_CONNECT_TIMEOUT = 10 * 1000; private static final int SOCKET_READ_TIMEOUT_DEFAULT = 10 * 1000; private static final int SOCKET_READ_TIMEOUT_DOWNLOAD = 30 * 1000; - private static final int SOCKET_READ_TIMEOUT_GET_RANDOM_SONGS = 60 * 1000; private static final int SOCKET_READ_TIMEOUT_GET_PLAYLIST = 60 * 1000; // Allow 20 seconds extra timeout per MB offset. @@ -134,6 +114,7 @@ public class RESTMusicService implements MusicService { private static final int HTTP_REQUEST_MAX_ATTEMPTS = 5; private static final long REDIRECTION_CHECK_INTERVAL_MILLIS = 60L * 60L * 1000L; + private HostnameVerifier selfSignedHostnameVerifier; private final DefaultHttpClient httpClient; private long redirectionLastChecked; private int redirectionNetworkType = -1; @@ -143,6 +124,11 @@ public class RESTMusicService implements MusicService { private Integer instance; public RESTMusicService() { + selfSignedHostnameVerifier = new HostnameVerifier() { + public boolean verify(String hostname, SSLSession session) { + return true; + } + }; // Create and initialize default HTTP parameters HttpParams params = new BasicHttpParams(); @@ -378,7 +364,7 @@ public class RESTMusicService implements MusicService { @Override public MusicDirectory getPlaylist(boolean refresh, String id, String name, Context context, ProgressListener progressListener) throws Exception { - Reader reader = getReader(context, progressListener, "getPlaylist", "id", id); + Reader reader = getReader(context, progressListener, "getPlaylist", "id", id, SOCKET_READ_TIMEOUT_GET_PLAYLIST); try { return new PlaylistParser(context, getInstance(context)).parse(reader, progressListener); } finally { @@ -827,82 +813,22 @@ public class RESTMusicService implements MusicService { public Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, ProgressListener progressListener, SilentBackgroundTask task) throws Exception { // Synchronize on the entry so that we don't download concurrently for the same song. synchronized (entry) { + String url = getRestUrl(context, "getCoverArt"); + List parameterNames = Arrays.asList("id"); + List parameterValues = Arrays.asList(entry.getCoverArt()); - - - - - String url = getRestUrl(context, "getCoverArt"); - - InputStream in = null; - try { - List parameterNames = Arrays.asList("id"); - List parameterValues = Arrays.asList(entry.getCoverArt()); - HttpEntity entity = getEntityForURL(context, url, null, parameterNames, parameterValues, progressListener, task); - - in = entity.getContent(); - Header contentEncoding = entity.getContentEncoding(); - if (contentEncoding != null && contentEncoding.getValue().equalsIgnoreCase("gzip")) { - in = new GZIPInputStream(in); - } - - // If content type is XML, an error occured. Get it. - String contentType = Util.getContentType(entity); - if (contentType != null && (contentType.startsWith("text/xml") || contentType.startsWith("text/html"))) { - new ErrorParser(context, getInstance(context)).parse(new InputStreamReader(in, Constants.UTF_8)); - return null; // Never reached. - } - - byte[] bytes = Util.toByteArray(in); - - // Handle case where partial was downloaded before being cancelled - if(task != null && task.isCancelled()) { - return null; - } - - OutputStream out = null; - try { - out = new FileOutputStream(FileUtil.getAlbumArtFile(context, entry)); - out.write(bytes); - } finally { - Util.close(out); - } - - // Size == 0 -> only want to download - if(size == 0) { - return null; - } else { - return FileUtil.getSampledBitmap(bytes, size); - } - } finally { - Util.close(in); - } + return getBitmapFromUrl(context, url, parameterNames, parameterValues, size, FileUtil.getAlbumArtFile(context, entry), true, progressListener, task); } } @Override - public HttpResponse getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, SilentBackgroundTask task) throws Exception { - + public HttpURLConnection getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, SilentBackgroundTask task) throws Exception { String url = getRestUrl(context, "stream"); - - // Set socket read timeout. Note: The timeout increases as the offset gets larger. This is - // to avoid the thrashing effect seen when offset is combined with transcoding/downsampling on the server. - // In that case, the server uses a long time before sending any data, causing the client to time out. - HttpParams params = new BasicHttpParams(); - int timeout = (int) (SOCKET_READ_TIMEOUT_DOWNLOAD + offset * TIMEOUT_MILLIS_PER_OFFSET_BYTE); - HttpConnectionParams.setSoTimeout(params, timeout); - - // Add "Range" header if offset is given. - List
headers = new ArrayList
(); - if (offset > 0) { - headers.add(new BasicHeader("Range", "bytes=" + offset + "-")); - } - List parameterNames = new ArrayList(); parameterNames.add("id"); parameterNames.add("maxBitRate"); - List parameterValues = new ArrayList(); + List parameterValues = new ArrayList<>(); parameterValues.add(song.getId()); parameterValues.add(maxBitrate); @@ -922,24 +848,34 @@ public class RESTMusicService implements MusicService { parameterValues.add("raw"); } } - HttpResponse response = getResponseForURL(context, url, params, parameterNames, parameterValues, headers, null, task, false); - // If content type is XML, an error occurred. Get it. - String contentType = Util.getContentType(response.getEntity()); - if (contentType != null && (contentType.startsWith("text/xml") || contentType.startsWith("text/html"))) { - InputStream in = response.getEntity().getContent(); - Header contentEncoding = response.getEntity().getContentEncoding(); - if (contentEncoding != null && contentEncoding.getValue().equalsIgnoreCase("gzip")) { - in = new GZIPInputStream(in); + // TODO: Use task + // TODO: Add range header + // Add "Range" header if offset is given. + /*List
headers = new ArrayList
(); + if (offset > 0) { + headers.add(new BasicHeader("Range", "bytes=" + offset + "-")); + }*/ + + // Set socket read timeout. Note: The timeout increases as the offset gets larger. This is + // to avoid the thrashing effect seen when offset is combined with transcoding/downsampling on the server. + // In that case, the server uses a long time before sending any data, causing the client to time out. + int timeout = (int) (SOCKET_READ_TIMEOUT_DOWNLOAD + offset * TIMEOUT_MILLIS_PER_OFFSET_BYTE); + HttpURLConnection connection = getConnection(context, url, parameterNames, parameterValues, timeout); + + // If content type is XML, an error occurred. Get it. + String contentType = connection.getContentType(); + if (contentType != null && (contentType.startsWith("text/xml") || contentType.startsWith("text/html"))) { + InputStream in = getInputStreamFromConnection(connection); + + try { + new ErrorParser(context, getInstance(context)).parse(new InputStreamReader(in, Constants.UTF_8)); + } finally { + Util.close(in); } - try { - new ErrorParser(context, getInstance(context)).parse(new InputStreamReader(in, Constants.UTF_8)); - } finally { - Util.close(in); - } - } + } - return response; + return connection; } @Override @@ -1576,48 +1512,10 @@ public class RESTMusicService implements MusicService { // the same user. synchronized (username) { String url = Util.getRestUrl(context, "getAvatar"); - InputStream in = null; - try - { - List parameterNames; - List parameterValues; - - parameterNames = Collections.singletonList("username"); - parameterValues = Arrays.asList(username); - - HttpEntity entity = getEntityForURL(context, url, null, parameterNames, parameterValues, progressListener, task); - in = entity.getContent(); - Header contentEncoding = entity.getContentEncoding(); - if (contentEncoding != null && contentEncoding.getValue().equalsIgnoreCase("gzip")) { - in = new GZIPInputStream(in); - } - - // If content type is XML, an error occurred. Get it. - String contentType = Util.getContentType(entity); - if (contentType != null && (contentType.startsWith("text/xml") || contentType.startsWith("text/html"))) { - new ErrorParser(context, getInstance(context)).parse(new InputStreamReader(in, Constants.UTF_8)); - return null; // Never reached. - } - - byte[] bytes = Util.toByteArray(in); - if(task != null && task.isCancelled()) { - // Handle case where partial is downloaded and cancelled - return null; - } + List parameterNames = Collections.singletonList("username"); + List parameterValues = Arrays.asList(username); - OutputStream out = null; - try { - out = new FileOutputStream(FileUtil.getAvatarFile(context, username)); - out.write(bytes); - } finally { - Util.close(out); - } - - return FileUtil.getSampledBitmap(bytes, size, false); - } - finally { - Util.close(in); - } + return getBitmapFromUrl(context, url, parameterNames, parameterValues, size, FileUtil.getAvatarFile(context, username), false, progressListener, task); } } @@ -1649,41 +1547,7 @@ public class RESTMusicService implements MusicService { public Bitmap getBitmap(String url, int size, Context context, ProgressListener progressListener, SilentBackgroundTask task) throws Exception { // Synchronize on the url so that we don't download concurrently synchronized (url) { - InputStream in = null; - try { - HttpEntity entity = getEntityForURL(context, url, null, null, null, progressListener, task); - in = entity.getContent(); - Header contentEncoding = entity.getContentEncoding(); - if (contentEncoding != null && contentEncoding.getValue().equalsIgnoreCase("gzip")) { - in = new GZIPInputStream(in); - } - - // If content type is XML, an error occurred. Get it. - String contentType = Util.getContentType(entity); - if (contentType != null && (contentType.startsWith("text/xml") || contentType.startsWith("text/html"))) { - new ErrorParser(context, getInstance(context)).parse(new InputStreamReader(in, Constants.UTF_8)); - return null; // Never reached. - } - - byte[] bytes = Util.toByteArray(in); - if(task != null && task.isCancelled()) { - // Handle case where partial is downloaded and cancelled - return null; - } - - OutputStream out = null; - try { - out = new FileOutputStream(FileUtil.getMiscFile(context, url)); - out.write(bytes); - } finally { - Util.close(out); - } - - return FileUtil.getSampledBitmap(bytes, size, false); - } - finally { - Util.close(in); - } + return getBitmapFromUrl(context, url, null, null, size, FileUtil.getMiscFile(context, url), false, progressListener, task); } } @@ -1735,7 +1599,7 @@ public class RESTMusicService implements MusicService { public List getInternetRadioStations(boolean refresh, Context context, ProgressListener progressListener) throws Exception { checkServerVersion(context, "1.9", null); - Reader reader = getReader(context, progressListener, "getInternetRadioStations", null); + Reader reader = getReader(context, progressListener, "getInternetRadioStations"); try { return new InternetRadioStationParser(context, getInstance(context)).parse(reader, progressListener); } finally { @@ -1855,39 +1719,93 @@ public class RESTMusicService implements MusicService { this.instance = instance; } + protected Bitmap getBitmapFromUrl(Context context, String url, List parameterNames, List parameterValues, int size, File saveToFile, boolean allowUnscaled, ProgressListener progressListener, SilentBackgroundTask task) throws Exception { + InputStream in = null; + try { + HttpURLConnection connection = getConnection(context, url, parameterNames, parameterValues, progressListener, true); + in = getInputStreamFromConnection(connection); + + String contentType = connection.getContentType(); + if (contentType != null && (contentType.startsWith("text/xml") || contentType.startsWith("text/html"))) { + new ErrorParser(context, getInstance(context)).parse(new InputStreamReader(in, Constants.UTF_8)); + } + + byte[] bytes = Util.toByteArray(in); + + // Handle case where partial was downloaded before being cancelled + if(task != null && task.isCancelled()) { + return null; + } + + OutputStream out = null; + try { + out = new FileOutputStream(saveToFile); + out.write(bytes); + } finally { + Util.close(out); + } + + // Size == 0 -> only want to download + if(size == 0) { + return null; + } else { + return FileUtil.getSampledBitmap(bytes, size, allowUnscaled); + } + } finally { + Util.close(in); + } + } + // Helper classes to get a reader for the request private Reader getReader(Context context, ProgressListener progressListener, String method) throws Exception { return getReader(context, progressListener, method, (List)null, null); } - private Reader getReader(Context context, ProgressListener progressListener, String method, String parameterName, Object parameterValue) throws Exception { - return getReader(context, progressListener, method, Arrays.asList(parameterName), Arrays.asList(parameterValue)); + private Reader getReader(Context context, ProgressListener progressListener, String method, String parameterName, Object parameterValue) throws Exception { + return getReader(context, progressListener, method, parameterName, parameterValue, 0); + } + private Reader getReader(Context context, ProgressListener progressListener, String method, String parameterName, Object parameterValue, int minNetworkTimeout) throws Exception { + return getReader(context, progressListener, method, Arrays.asList(parameterName), Arrays.asList(parameterValue), minNetworkTimeout); } private Reader getReader(Context context, ProgressListener progressListener, String method, List parameterNames, List parameterValues) throws Exception { - return getReader(context, progressListener, method, parameterNames, parameterValues, false); + return getReader(context, progressListener, method, parameterNames, parameterValues, 0); + } + private Reader getReader(Context context, ProgressListener progressListener, String method, List parameterNames, List parameterValues, int minNetworkTimeout) throws Exception { + return getReader(context, progressListener, method, parameterNames, parameterValues, minNetworkTimeout, false); } - private Reader getReader(Context context, ProgressListener progressListener, String method, List parameterNames, List parameterValues, boolean throwErrors) throws Exception { + private Reader getReader(Context context, ProgressListener progressListener, String method, List parameterNames, List parameterValues, boolean throwErrors) throws Exception { + return getReader(context, progressListener, method, parameterNames, parameterValues, 0, throwErrors); + } + private Reader getReader(Context context, ProgressListener progressListener, String method, List parameterNames, List parameterValues, int minNetworkTimeout, boolean throwErrors) throws Exception { if (progressListener != null) { progressListener.updateProgress(R.string.service_connecting); } String url = getRestUrl(context, method); - return getReaderForURL(context, url, parameterNames, parameterValues, progressListener, throwErrors); + return getReaderForURL(context, url, parameterNames, parameterValues, minNetworkTimeout, progressListener, throwErrors); } private Reader getReaderForURL(Context context, String url, List parameterNames, List parameterValues, ProgressListener progressListener) throws Exception { return getReaderForURL(context, url, parameterNames, parameterValues, progressListener, true); } - private Reader getReaderForURL(Context context, String url, List parameterNames, List parameterValues, ProgressListener progressListener, boolean throwErrors) throws Exception { - InputStream in = getInputStream(context, url, parameterNames, parameterValues, progressListener, throwErrors); + private Reader getReaderForURL(Context context, String url, List parameterNames, List parameterValues, ProgressListener progressListener, boolean throwErrors) throws Exception { + return getReaderForURL(context, url, parameterNames, parameterValues, 0, progressListener, throwErrors); + } + private Reader getReaderForURL(Context context, String url, List parameterNames, List parameterValues, int minNetworkTimeout, ProgressListener progressListener, boolean throwErrors) throws Exception { + InputStream in = getInputStream(context, url, parameterNames, parameterValues, minNetworkTimeout, progressListener, throwErrors); return new InputStreamReader(in, Constants.UTF_8); } // Helper classes to open a connection to a server private InputStream getInputStream(Context context, String url, List parameterNames, List parameterValues, ProgressListener progressListener, boolean throwsErrors) throws Exception { - HttpURLConnection connection = getConnection(context, url, parameterNames, parameterValues, progressListener, throwsErrors); - + return getInputStream(context, url, parameterNames, parameterValues, 0, progressListener, throwsErrors); + } + private InputStream getInputStream(Context context, String url, List parameterNames, List parameterValues, int minNetworkTimeout, ProgressListener progressListener, boolean throwsErrors) throws Exception { + HttpURLConnection connection = getConnection(context, url, parameterNames, parameterValues, minNetworkTimeout, progressListener, throwsErrors); + return getInputStreamFromConnection(connection); + } + private InputStream getInputStreamFromConnection(HttpURLConnection connection) throws Exception { InputStream in = connection.getInputStream(); if("gzip".equals(connection.getContentEncoding())) { in = new GZIPInputStream(in); @@ -1896,16 +1814,29 @@ public class RESTMusicService implements MusicService { return in; } + private HttpURLConnection getConnection(Context context, String url, List parameterNames, List parameterValues) throws Exception { + return getConnection(context, url, parameterNames, parameterValues, null, true); + } + private HttpURLConnection getConnection(Context context, String url, List parameterNames, List parameterValues, int minNetworkTimeout) throws Exception { + return getConnection(context, url, parameterNames, parameterValues, minNetworkTimeout, null, true); + } private HttpURLConnection getConnection(Context context, String url, List parameterNames, List parameterValues, ProgressListener progressListener, boolean throwErrors) throws Exception { + return getConnection(context, url, parameterNames, parameterValues, 0, progressListener, throwErrors); + } + private HttpURLConnection getConnection(Context context, String url, List parameterNames, List parameterValues, int minNetworkTimeout, ProgressListener progressListener, boolean throwErrors) throws Exception { if(throwErrors) { - return getConnection(context, url, parameterNames, parameterValues); + return getConnectionDirect(context, url, parameterNames, parameterValues, minNetworkTimeout); } else { - return getConnection(context, url, parameterNames, parameterValues, progressListener, HTTP_REQUEST_MAX_ATTEMPTS, 0); + return getConnection(context, url, parameterNames, parameterValues, minNetworkTimeout, progressListener, HTTP_REQUEST_MAX_ATTEMPTS, 0); } } + private HttpURLConnection getConnection(Context context, String url, List parameterNames, List parameterValues, ProgressListener progressListener, int retriesLeft, int attempts) throws Exception { + return getConnection(context, url, parameterNames, parameterValues, 0, progressListener, retriesLeft, attempts); + } + private HttpURLConnection getConnection(Context context, String url, List parameterNames, List parameterValues, int minNetworkTimeout, ProgressListener progressListener, int retriesLeft, int attempts) throws Exception { try { - return getConnection(context, url, parameterNames, parameterValues); + return getConnectionDirect(context, url, parameterNames, parameterValues, minNetworkTimeout); } catch (IOException x) { if(retriesLeft > 0) { if (progressListener != null) { @@ -1917,14 +1848,15 @@ public class RESTMusicService implements MusicService { Thread.sleep(2000L); // TODO: Increase timeouts on failures - return getConnection(context, url, parameterNames, parameterValues, progressListener, retriesLeft--, attempts + 1); + // TODO: Detect task cancellation + return getConnection(context, url, parameterNames, parameterValues, minNetworkTimeout, progressListener, retriesLeft--, attempts + 1); } else { throw x; } } } - private HttpURLConnection getConnection(Context context, String url, List parameterNames, List parameterValues) throws Exception { + private HttpURLConnection getConnectionDirect(Context context, String url, List parameterNames, List parameterValues, int minNetworkTimeout) throws Exception { // Add params to query if (parameterNames != null) { StringBuilder builder = new StringBuilder(url); @@ -1943,10 +1875,10 @@ public class RESTMusicService implements MusicService { Log.i(TAG, stripUrlInfo(rewrittenUrl)); } - return getConnection(context, rewrittenUrl); + return getConnectionDirect(context, rewrittenUrl, minNetworkTimeout); } - private HttpURLConnection getConnection(Context context, String url) throws Exception { + private HttpURLConnection getConnectionDirect(Context context, String url, int minNetworkTimeout) throws Exception { // Connect and add headers URL urlObj = new URL(url); HttpURLConnection connection = (HttpURLConnection) urlObj.openConnection(); @@ -1956,213 +1888,20 @@ public class RESTMusicService implements MusicService { connection.addRequestProperty("User-Agent", Constants.REST_CLIENT_ID); // Set timeout - // TODO: Should we have more fine grained timeouts like before? SharedPreferences prefs = Util.getPreferences(context); int networkTimeout = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT, SOCKET_CONNECT_TIMEOUT + "")); - connection.setConnectTimeout(networkTimeout); + connection.setConnectTimeout(Math.max(minNetworkTimeout, networkTimeout)); connection.setReadTimeout(SOCKET_READ_TIMEOUT_DEFAULT); - // TODO: Self signed certificates + if(connection instanceof HttpsURLConnection) { + HttpsURLConnection sslConnection = (HttpsURLConnection) connection; + sslConnection.setHostnameVerifier(selfSignedHostnameVerifier); + } + detectRedirect(context, urlObj, connection); return connection; } - - - - - - - - - - - - - - - - - private HttpEntity getEntityForURL(Context context, String url, HttpParams requestParams, List parameterNames, - List parameterValues, ProgressListener progressListener, SilentBackgroundTask task) throws Exception { - return getResponseForURL(context, url, requestParams, parameterNames, parameterValues, null, progressListener, task, false).getEntity(); - } - - private HttpResponse getResponseForURL(Context context, String url, HttpParams requestParams, - List parameterNames, List parameterValues, - List
headers, ProgressListener progressListener, SilentBackgroundTask task, boolean throwsErrors) throws Exception { - // If not too many parameters, extract them to the URL rather than relying on the HTTP POST request being - // received intact. Remember, HTTP POST requests are converted to GET requests during HTTP redirects, thus - // loosing its entity. - if (parameterNames != null && parameterNames.size() < 10) { - StringBuilder builder = new StringBuilder(url); - for (int i = 0; i < parameterNames.size(); i++) { - builder.append("&").append(parameterNames.get(i)).append("="); - String part = URLEncoder.encode(String.valueOf(parameterValues.get(i)), "UTF-8"); - part = part.replaceAll("\\%27", "'"); - builder.append(part); - } - url = builder.toString(); - parameterNames = null; - parameterValues = null; - } - - String rewrittenUrl = rewriteUrlWithRedirect(context, url); - return executeWithRetry(context, rewrittenUrl, url, requestParams, parameterNames, parameterValues, headers, progressListener, task, throwsErrors); - } - - private HttpResponse executeWithRetry(final Context context, String url, String originalUrl, HttpParams requestParams, - List parameterNames, List parameterValues, - List
headers, ProgressListener progressListener, SilentBackgroundTask task, boolean throwErrors) throws Exception { - // Strip out sensitive information from log - if(url.indexOf("scanstatus") == -1) { - Log.i(TAG, stripUrlInfo(url)); - } - - SharedPreferences prefs = Util.getPreferences(context); - int networkTimeout = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT, "15000")); - HttpParams newParams = httpClient.getParams(); - HttpConnectionParams.setSoTimeout(newParams, networkTimeout); - httpClient.setParams(newParams); - - final AtomicReference isCancelled = new AtomicReference(false); - int attempts = 0; - while (true) { - attempts++; - HttpContext httpContext = new BasicHttpContext(); - final HttpRequestBase request = (url.indexOf("rest") == -1) ? new HttpGet(url) : new HttpPost(url); - - if (task != null) { - // Attempt to abort the HTTP request if the task is cancelled. - task.setOnCancelListener(new BackgroundTask.OnCancelListener() { - @Override - public void onCancel() { - try { - isCancelled.set(true); - if(Thread.currentThread() == Looper.getMainLooper().getThread()) { - new SilentBackgroundTask(context) { - @Override - protected Void doInBackground() throws Throwable { - request.abort(); - return null; - } - }.execute(); - } else { - request.abort(); - } - } catch(Exception e) { - Log.e(TAG, "Failed to stop http task", e); - } - } - }); - } - - if (parameterNames != null && request instanceof HttpPost) { - List params = new ArrayList(); - for (int i = 0; i < parameterNames.size(); i++) { - params.add(new BasicNameValuePair(parameterNames.get(i), String.valueOf(parameterValues.get(i)))); - } - ((HttpPost) request).setEntity(new UrlEncodedFormEntity(params, Constants.UTF_8)); - } - - if (requestParams != null) { - request.setParams(requestParams); - } - - if (headers != null) { - for (Header header : headers) { - request.addHeader(header); - } - } - if(url.indexOf("getCoverArt") == -1 && url.indexOf("stream") == -1 && url.indexOf("getAvatar") == -1) { - request.addHeader("Accept-Encoding", "gzip"); - } - request.addHeader("User-Agent", Constants.REST_CLIENT_ID); - - // Set credentials to get through apache proxies that require authentication. - int instance = getInstance(context); - String username = prefs.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null); - String password = prefs.getString(Constants.PREFERENCES_KEY_PASSWORD + instance, null); - httpClient.getCredentialsProvider().setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), - new UsernamePasswordCredentials(username, password)); - - try { - HttpResponse response = httpClient.execute(request, httpContext); - detectRedirect(originalUrl, context, httpContext); - return response; - } catch (IOException x) { - request.abort(); - if (attempts >= HTTP_REQUEST_MAX_ATTEMPTS || isCancelled.get() || throwErrors) { - throw x; - } - if (progressListener != null) { - String msg = context.getResources().getString(R.string.music_service_retry, attempts, HTTP_REQUEST_MAX_ATTEMPTS - 1); - progressListener.updateProgress(msg); - } - Log.w(TAG, "Got IOException " + x + " (" + attempts + "), will retry"); - increaseTimeouts(requestParams); - Thread.sleep(2000L); - } - } - } - - private void increaseTimeouts(HttpParams requestParams) { - if (requestParams != null) { - int connectTimeout = HttpConnectionParams.getConnectionTimeout(requestParams); - if (connectTimeout != 0) { - HttpConnectionParams.setConnectionTimeout(requestParams, (int) (connectTimeout * 1.3F)); - } - int readTimeout = HttpConnectionParams.getSoTimeout(requestParams); - if (readTimeout != 0) { - HttpConnectionParams.setSoTimeout(requestParams, (int) (readTimeout * 1.5F)); - } - } - } - - private void detectRedirect(String originalUrl, Context context, HttpContext httpContext) throws Exception { - HttpUriRequest request = (HttpUriRequest) httpContext.getAttribute(ExecutionContext.HTTP_REQUEST); - HttpHost host = (HttpHost) httpContext.getAttribute(ExecutionContext.HTTP_TARGET_HOST); - - // Sometimes the request doesn't contain the "http://host" part - String redirectedUrl; - if (request.getURI().getScheme() == null) { - redirectedUrl = host.toURI() + request.getURI(); - } else { - redirectedUrl = request.getURI().toString(); - } - - if(redirectedUrl != null && "http://subsonic.org/pages/".equals(redirectedUrl)) { - throw new Exception("Invalid url, redirects to http://subsonic.org/pages/"); - } - - int fromIndex = originalUrl.indexOf("/rest/"); - int toIndex = redirectedUrl.indexOf("/rest/"); - if(fromIndex != -1 && toIndex != -1 && !Util.equals(originalUrl, redirectedUrl)) { - redirectFrom = originalUrl.substring(0, fromIndex); - redirectTo = redirectedUrl.substring(0, toIndex); - - if (redirectFrom.compareTo(redirectTo) != 0) { - Log.i(TAG, redirectFrom + " redirects to " + redirectTo); - } - redirectionLastChecked = System.currentTimeMillis(); - redirectionNetworkType = getCurrentNetworkType(context); - } - } - - - - - - - - - - - - - - - private void detectRedirect(Context context, URL originalUrl, HttpURLConnection connection) throws Exception { detectRedirect(context, originalUrl, connection.getURL()); } -- cgit v1.2.3 From 2a5b7f82f8dfea7d72c40daab32325b766111d19 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Wed, 5 Oct 2016 17:35:26 -0700 Subject: Load playlist size in background before hitting RCC::setPlaybackState --- .../github/daneren2005/dsub/service/DownloadService.java | 6 ++++-- .../dsub/util/compat/RemoteControlClientBase.java | 2 +- .../dsub/util/compat/RemoteControlClientICS.java | 2 +- .../dsub/util/compat/RemoteControlClientJB.java | 13 +++---------- .../dsub/util/compat/RemoteControlClientLP.java | 14 +++++++------- 5 files changed, 16 insertions(+), 21 deletions(-) (limited to 'app/src') 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 a6bbc327..1cf482ca 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/DownloadService.java +++ b/app/src/main/java/github/daneren2005/dsub/service/DownloadService.java @@ -1469,7 +1469,7 @@ public class DownloadService extends Service { Notifications.hidePlayingNotification(this, this, handler); } if(mRemoteControl != null) { - mRemoteControl.setPlaybackState(playerState.getRemoteControlClientPlayState()); + mRemoteControl.setPlaybackState(playerState.getRemoteControlClientPlayState(), getCurrentPlayingIndex(), size()); } if (playerState == STARTED) { @@ -2842,6 +2842,8 @@ public class DownloadService extends Service { final Integer duration = getPlayerDuration(); final boolean isSeekable = isSeekable(); final int position = getPlayerPosition(); + final int index = getCurrentPlayingIndex(); + final int queueSize = size(); synchronized(onSongChangedListeners) { for (final OnSongChangedListener listener : onSongChangedListeners) { @@ -2861,7 +2863,7 @@ public class DownloadService extends Service { @Override public void run() { if(mRemoteControl != null) { - mRemoteControl.setPlaybackState(playerState.getRemoteControlClientPlayState()); + mRemoteControl.setPlaybackState(playerState.getRemoteControlClientPlayState(), index, queueSize); } } }); 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 1f7035dc..4f9a27f0 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 @@ -29,7 +29,7 @@ public abstract class RemoteControlClientBase { 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 setPlaybackState(int state, int index, int queueSize); 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); 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 2a06e798..74076afb 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 @@ -54,7 +54,7 @@ public class RemoteControlClientICS extends RemoteControlClientBase { audioManager.unregisterRemoteControlClient(mRemoteControl); } - public void setPlaybackState(final int state) { + public void setPlaybackState(final int state, int index, int queueSize) { if(mRemoteControl == null) { return; } diff --git a/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientJB.java b/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientJB.java index e61e9a47..d10c8594 100644 --- a/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientJB.java +++ b/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientJB.java @@ -1,17 +1,10 @@ package github.daneren2005.dsub.util.compat; -import github.daneren2005.dsub.domain.MusicDirectory; -import github.daneren2005.dsub.util.ImageLoader; import android.annotation.TargetApi; -import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; -import android.content.Intent; -import android.media.AudioManager; -import android.media.MediaMetadataRetriever; import android.media.RemoteControlClient; -import github.daneren2005.dsub.activity.SubsonicActivity; -import github.daneren2005.dsub.service.DownloadService; + import github.daneren2005.dsub.util.SilentBackgroundTask; @TargetApi(18) @@ -36,13 +29,13 @@ public class RemoteControlClientJB extends RemoteControlClientICS { return null; } }.execute(); - setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING); + setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING, 0, 0); } }); } @Override - public void setPlaybackState(final int state) { + public void setPlaybackState(final int state, int index, int queueSize) { if(mRemoteControl == null) { return; } 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 index df468155..d666afb2 100644 --- a/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientLP.java +++ b/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientLP.java @@ -39,7 +39,6 @@ import android.support.v7.media.MediaRouter; import android.util.Log; import android.view.KeyEvent; -import java.io.Serializable; import java.util.ArrayList; import java.util.List; @@ -50,7 +49,6 @@ import github.daneren2005.dsub.domain.Bookmark; import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.domain.MusicDirectory.Entry; import github.daneren2005.dsub.domain.Playlist; -import github.daneren2005.dsub.domain.PodcastEpisode; import github.daneren2005.dsub.domain.SearchCritera; import github.daneren2005.dsub.domain.SearchResult; import github.daneren2005.dsub.service.DownloadFile; @@ -123,8 +121,12 @@ public class RemoteControlClientLP extends RemoteControlClientBase { mediaSession.release(); } + private void setPlaybackState(int state) { + setPlaybackState(state, downloadService.getCurrentPlayingIndex(), downloadService.size()); + } + @Override - public void setPlaybackState(int state) { + public void setPlaybackState(int state, int index, int queueSize) { PlaybackState.Builder builder = new PlaybackState.Builder(); int newState = PlaybackState.STATE_NONE; @@ -156,7 +158,7 @@ public class RemoteControlClientLP extends RemoteControlClientBase { isSong = entry.isSong(); } - builder.setActions(getPlaybackActions(isSong)); + builder.setActions(getPlaybackActions(isSong, index, queueSize)); if(entry != null) { addCustomActions(entry, builder); @@ -240,14 +242,12 @@ public class RemoteControlClientLP extends RemoteControlClientBase { return mediaSession; } - protected long getPlaybackActions(boolean isSong) { + protected long getPlaybackActions(boolean isSong, int currentIndex, int size) { 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(isSong) { if (currentIndex > 0) { actions |= PlaybackState.ACTION_SKIP_TO_PREVIOUS; -- cgit v1.2.3 From 74986cb2a593deca85e2eda83d1fb18d5485f7f0 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Thu, 6 Oct 2016 17:43:31 -0700 Subject: Convert ServerProxy to HttpUrlConnection and remove last traces of apache.http (closes #564) --- ServerProxy | 2 +- app/build.gradle | 1 - .../daneren2005/dsub/service/RESTMusicService.java | 52 +- .../daneren2005/dsub/service/RemoteController.java | 2 +- .../dsub/service/ssl/SSLSocketFactory.java | 553 --------------------- .../dsub/service/ssl/TrustManagerDecorator.java | 65 --- .../dsub/service/ssl/TrustSelfSignedStrategy.java | 44 -- .../dsub/service/ssl/TrustStrategy.java | 57 --- .../java/github/daneren2005/dsub/util/Util.java | 16 - 9 files changed, 4 insertions(+), 788 deletions(-) delete mode 100644 app/src/main/java/github/daneren2005/dsub/service/ssl/SSLSocketFactory.java delete mode 100644 app/src/main/java/github/daneren2005/dsub/service/ssl/TrustManagerDecorator.java delete mode 100644 app/src/main/java/github/daneren2005/dsub/service/ssl/TrustSelfSignedStrategy.java delete mode 100644 app/src/main/java/github/daneren2005/dsub/service/ssl/TrustStrategy.java (limited to 'app/src') diff --git a/ServerProxy b/ServerProxy index a74d706b..8e9308c2 160000 --- a/ServerProxy +++ b/ServerProxy @@ -1 +1 @@ -Subproject commit a74d706b529ebba4ab73b64169e769affb6ae2a3 +Subproject commit 8e9308c21d34d2d8c2b315c7d93f036b824706eb diff --git a/app/build.gradle b/app/build.gradle index 075e403c..50bbb69b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,7 +3,6 @@ apply plugin: 'com.android.application' android { compileSdkVersion 23 buildToolsVersion "23.0.3" - useLibrary 'org.apache.http.legacy' defaultConfig { applicationId "github.daneren2005.dsub" diff --git a/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java b/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java index cc475d4f..72faa7e1 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java +++ b/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java @@ -33,19 +33,6 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; -import org.apache.http.client.HttpClient; -import org.apache.http.conn.params.ConnManagerParams; -import org.apache.http.conn.params.ConnPerRouteBean; -import org.apache.http.conn.scheme.PlainSocketFactory; -import org.apache.http.conn.scheme.Scheme; -import org.apache.http.conn.scheme.SchemeRegistry; -import org.apache.http.conn.scheme.SocketFactory; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; -import org.apache.http.params.BasicHttpParams; -import org.apache.http.params.HttpConnectionParams; -import org.apache.http.params.HttpParams; - import android.content.Context; import android.content.SharedPreferences; import android.graphics.Bitmap; @@ -83,8 +70,6 @@ import github.daneren2005.dsub.service.parser.StarredListParser; import github.daneren2005.dsub.service.parser.TopSongsParser; import github.daneren2005.dsub.service.parser.UserParser; import github.daneren2005.dsub.service.parser.VideosParser; -import github.daneren2005.dsub.service.ssl.SSLSocketFactory; -import github.daneren2005.dsub.service.ssl.TrustSelfSignedStrategy; import github.daneren2005.dsub.util.Pair; import github.daneren2005.dsub.util.SilentBackgroundTask; import github.daneren2005.dsub.util.Constants; @@ -115,12 +100,10 @@ public class RESTMusicService implements MusicService { private static final long REDIRECTION_CHECK_INTERVAL_MILLIS = 60L * 60L * 1000L; private HostnameVerifier selfSignedHostnameVerifier; - private final DefaultHttpClient httpClient; private long redirectionLastChecked; private int redirectionNetworkType = -1; private String redirectFrom; private String redirectTo; - private final ThreadSafeClientConnManager connManager; private Integer instance; public RESTMusicService() { @@ -129,37 +112,6 @@ public class RESTMusicService implements MusicService { return true; } }; - - // Create and initialize default HTTP parameters - HttpParams params = new BasicHttpParams(); - ConnManagerParams.setMaxTotalConnections(params, 20); - ConnManagerParams.setMaxConnectionsPerRoute(params, new ConnPerRouteBean(20)); - HttpConnectionParams.setConnectionTimeout(params, SOCKET_CONNECT_TIMEOUT); - HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_DEFAULT); - - // Turn off stale checking. Our connections break all the time anyway, - // and it's not worth it to pay the penalty of checking every time. - HttpConnectionParams.setStaleCheckingEnabled(params, false); - - // Create and initialize scheme registry - SchemeRegistry schemeRegistry = new SchemeRegistry(); - schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); - schemeRegistry.register(new Scheme("https", createSSLSocketFactory(), 443)); - - // Create an HttpClient with the ThreadSafeClientConnManager. - // This connection manager must be used if more than one thread will - // be using the HttpClient. - connManager = new ThreadSafeClientConnManager(params, schemeRegistry); - httpClient = new DefaultHttpClient(connManager, params); - } - - private SocketFactory createSSLSocketFactory() { - try { - return new SSLSocketFactory(new TrustSelfSignedStrategy(), SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); - } catch (Throwable x) { - Log.e(TAG, "Failed to create custom SSL socket factory, using default.", x); - return org.apache.http.conn.ssl.SSLSocketFactory.getSocketFactory(); - } } @Override @@ -1974,7 +1926,7 @@ public class RESTMusicService implements MusicService { } } - public HttpClient getHttpClient() { - return httpClient; + public HostnameVerifier getHostNameVerifier() { + return selfSignedHostnameVerifier; } } diff --git a/app/src/main/java/github/daneren2005/dsub/service/RemoteController.java b/app/src/main/java/github/daneren2005/dsub/service/RemoteController.java index 6c70496d..2815bcc9 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/RemoteController.java +++ b/app/src/main/java/github/daneren2005/dsub/service/RemoteController.java @@ -105,7 +105,7 @@ public abstract class RemoteController { protected WebProxy createWebProxy() { MusicService musicService = MusicServiceFactory.getMusicService(downloadService); if(musicService instanceof CachedMusicService) { - return new WebProxy(downloadService, ((CachedMusicService)musicService).getMusicService().getHttpClient()); + return new WebProxy(downloadService, ((CachedMusicService)musicService).getMusicService().getHostNameVerifier()); } else { return new WebProxy(downloadService); } diff --git a/app/src/main/java/github/daneren2005/dsub/service/ssl/SSLSocketFactory.java b/app/src/main/java/github/daneren2005/dsub/service/ssl/SSLSocketFactory.java deleted file mode 100644 index 830950c8..00000000 --- a/app/src/main/java/github/daneren2005/dsub/service/ssl/SSLSocketFactory.java +++ /dev/null @@ -1,553 +0,0 @@ -/* - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ - -package github.daneren2005.dsub.service.ssl; - -import android.os.Build; -import android.util.Log; - -import org.apache.http.conn.ConnectTimeoutException; -import org.apache.http.conn.scheme.HostNameResolver; -import org.apache.http.conn.scheme.LayeredSocketFactory; -import org.apache.http.conn.ssl.AllowAllHostnameVerifier; -import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier; -import org.apache.http.conn.ssl.StrictHostnameVerifier; -import org.apache.http.conn.ssl.X509HostnameVerifier; -import org.apache.http.params.HttpConnectionParams; -import org.apache.http.params.HttpParams; - -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; - -import java.io.IOException; -import java.lang.reflect.Array; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; -import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.Provider; -import java.security.SecureRandom; -import java.security.Security; -import java.security.UnrecoverableKeyException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * Layered socket factory for TLS/SSL connections. - *

- * SSLSocketFactory can be used to validate the identity of the HTTPS server against a list of - * trusted certificates and to authenticate to the HTTPS server using a private key. - *

- * SSLSocketFactory will enable server authentication when supplied with - * a {@link KeyStore trust-store} file containing one or several trusted certificates. The client - * secure socket will reject the connection during the SSL session handshake if the target HTTPS - * server attempts to authenticate itself with a non-trusted certificate. - *

- * Use JDK keytool utility to import a trusted certificate and generate a trust-store file: - *

- *     keytool -import -alias "my server cert" -file server.crt -keystore my.truststore
- *    
- *

- * In special cases the standard trust verification process can be bypassed by using a custom - * {@link TrustStrategy}. This interface is primarily intended for allowing self-signed - * certificates to be accepted as trusted without having to add them to the trust-store file. - *

- * The following parameters can be used to customize the behavior of this - * class: - *

    - *
  • {@link org.apache.http.params.CoreConnectionPNames#CONNECTION_TIMEOUT}
  • - *
  • {@link org.apache.http.params.CoreConnectionPNames#SO_TIMEOUT}
  • - *
- *

- * SSLSocketFactory will enable client authentication when supplied with - * a {@link KeyStore key-store} file containing a private key/public certificate - * pair. The client secure socket will use the private key to authenticate - * itself to the target HTTPS server during the SSL session handshake if - * requested to do so by the server. - * The target HTTPS server will in its turn verify the certificate presented - * by the client in order to establish client's authenticity - *

- * Use the following sequence of actions to generate a key-store file - *

- *
    - *
  • - *

    - * Use JDK keytool utility to generate a new key - *

    keytool -genkey -v -alias "my client key" -validity 365 -keystore my.keystore
    - * For simplicity use the same password for the key as that of the key-store - *

    - *
  • - *
  • - *

    - * Issue a certificate signing request (CSR) - *

    keytool -certreq -alias "my client key" -file mycertreq.csr -keystore my.keystore
    - *

    - *
  • - *
  • - *

    - * Send the certificate request to the trusted Certificate Authority for signature. - * One may choose to act as her own CA and sign the certificate request using a PKI - * tool, such as OpenSSL. - *

    - *
  • - *
  • - *

    - * Import the trusted CA root certificate - *

    keytool -import -alias "my trusted ca" -file caroot.crt -keystore my.keystore
    - *

    - *
  • - *
  • - *

    - * Import the PKCS#7 file containg the complete certificate chain - *

    keytool -import -alias "my client key" -file mycert.p7 -keystore my.keystore
    - *

    - *
  • - *
  • - *

    - * Verify the content the resultant keystore file - *

    keytool -list -v -keystore my.keystore
    - *

    - *
  • - *
- * - * @since 4.0 - */ -public class SSLSocketFactory implements LayeredSocketFactory { - private static final String TAG = SSLSocketFactory.class.getSimpleName(); - public static final String TLS = "TLS"; - - public static final X509HostnameVerifier ALLOW_ALL_HOSTNAME_VERIFIER - = new AllowAllHostnameVerifier(); - - public static final X509HostnameVerifier BROWSER_COMPATIBLE_HOSTNAME_VERIFIER - = new BrowserCompatHostnameVerifier(); - - public static final X509HostnameVerifier STRICT_HOSTNAME_VERIFIER - = new StrictHostnameVerifier(); - - /** - * The default factory using the default JVM settings for secure connections. - */ - private static final SSLSocketFactory DEFAULT_FACTORY = new SSLSocketFactory(); - - /** - * Gets the default factory, which uses the default JVM settings for secure - * connections. - * - * @return the default factory - */ - public static SSLSocketFactory getSocketFactory() { - return DEFAULT_FACTORY; - } - - private final javax.net.ssl.SSLSocketFactory socketfactory; - private final HostNameResolver nameResolver; - // TODO: make final - private volatile X509HostnameVerifier hostnameVerifier; - - private static SSLContext createSSLContext( - String algorithm, - final KeyStore keystore, - final String keystorePassword, - final KeyStore truststore, - final SecureRandom random, - final TrustStrategy trustStrategy) - throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, KeyManagementException { - if (algorithm == null) { - algorithm = TLS; - } - KeyManagerFactory kmfactory = KeyManagerFactory.getInstance( - KeyManagerFactory.getDefaultAlgorithm()); - kmfactory.init(keystore, keystorePassword != null ? keystorePassword.toCharArray(): null); - KeyManager[] keymanagers = kmfactory.getKeyManagers(); - TrustManagerFactory tmfactory = TrustManagerFactory.getInstance( - TrustManagerFactory.getDefaultAlgorithm()); - tmfactory.init(keystore); - TrustManager[] trustmanagers = tmfactory.getTrustManagers(); - if (trustmanagers != null && trustStrategy != null) { - for (int i = 0; i < trustmanagers.length; i++) { - TrustManager tm = trustmanagers[i]; - if (tm instanceof X509TrustManager) { - trustmanagers[i] = new TrustManagerDecorator( - (X509TrustManager) tm, trustStrategy); - } - } - } - - SSLContext sslcontext = SSLContext.getInstance(algorithm); - sslcontext.init(keymanagers, trustmanagers, random); - return sslcontext; - } - - /** - * @deprecated Use {@link #SSLSocketFactory(String, KeyStore, String, KeyStore, SecureRandom, X509HostnameVerifier)} - */ - @Deprecated - public SSLSocketFactory( - final String algorithm, - final KeyStore keystore, - final String keystorePassword, - final KeyStore truststore, - final SecureRandom random, - final HostNameResolver nameResolver) - throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { - this(createSSLContext( - algorithm, keystore, keystorePassword, truststore, random, null), - nameResolver); - } - - /** - * @since 4.1 - */ - public SSLSocketFactory( - String algorithm, - final KeyStore keystore, - final String keystorePassword, - final KeyStore truststore, - final SecureRandom random, - final X509HostnameVerifier hostnameVerifier) - throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { - this(createSSLContext( - algorithm, keystore, keystorePassword, truststore, random, null), - hostnameVerifier); - } - - /** - * @since 4.1 - */ - public SSLSocketFactory( - String algorithm, - final KeyStore keystore, - final String keystorePassword, - final KeyStore truststore, - final SecureRandom random, - final TrustStrategy trustStrategy, - final X509HostnameVerifier hostnameVerifier) - throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { - this(createSSLContext( - algorithm, keystore, keystorePassword, truststore, random, trustStrategy), - hostnameVerifier); - } - - public SSLSocketFactory( - final KeyStore keystore, - final String keystorePassword, - final KeyStore truststore) - throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { - this(TLS, keystore, keystorePassword, truststore, null, null, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); - } - - public SSLSocketFactory( - final KeyStore keystore, - final String keystorePassword) - throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException{ - this(TLS, keystore, keystorePassword, null, null, null, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); - } - - public SSLSocketFactory( - final KeyStore truststore) - throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { - this(TLS, null, null, truststore, null, null, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); - } - - /** - * @since 4.1 - */ - public SSLSocketFactory( - final TrustStrategy trustStrategy, - final X509HostnameVerifier hostnameVerifier) - throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { - this(TLS, null, null, null, null, trustStrategy, hostnameVerifier); - } - - /** - * @since 4.1 - */ - public SSLSocketFactory( - final TrustStrategy trustStrategy) - throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { - this(TLS, null, null, null, null, trustStrategy, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); - } - - public SSLSocketFactory(final SSLContext sslContext) { - this(sslContext, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); - } - - /** - * @deprecated Use {@link #SSLSocketFactory(SSLContext)} - */ - @Deprecated - public SSLSocketFactory( - final SSLContext sslContext, final HostNameResolver nameResolver) { - super(); - this.socketfactory = sslContext.getSocketFactory(); - this.hostnameVerifier = BROWSER_COMPATIBLE_HOSTNAME_VERIFIER; - this.nameResolver = nameResolver; - } - - /** - * @since 4.1 - */ - public SSLSocketFactory( - final SSLContext sslContext, final X509HostnameVerifier hostnameVerifier) { - super(); - this.socketfactory = sslContext.getSocketFactory(); - this.hostnameVerifier = hostnameVerifier; - this.nameResolver = null; - } - - private SSLSocketFactory() { - super(); - this.socketfactory = HttpsURLConnection.getDefaultSSLSocketFactory(); - this.hostnameVerifier = null; - this.nameResolver = null; - } - - /** - * @param params Optional parameters. Parameters passed to this method will have no effect. - * This method will create a unconnected instance of {@link Socket} class - * using {@link javax.net.ssl.SSLSocketFactory#createSocket()} method. - * @since 4.1 - */ - @SuppressWarnings("cast") - public Socket createSocket(final HttpParams params) throws IOException { - // the cast makes sure that the factory is working as expected - SSLSocket sslSocket = (SSLSocket) this.socketfactory.createSocket(); - sslSocket.setEnabledProtocols(getProtocols(sslSocket)); - sslSocket.setEnabledCipherSuites(getCiphers(sslSocket)); - return sslSocket; - } - - @SuppressWarnings("cast") - public Socket createSocket() throws IOException { - // the cast makes sure that the factory is working as expected - SSLSocket sslSocket = (SSLSocket) this.socketfactory.createSocket(); - sslSocket.setEnabledProtocols(getProtocols(sslSocket)); - sslSocket.setEnabledCipherSuites(getCiphers(sslSocket)); - return sslSocket; - } - - /** - * @since 4.1 - */ - public Socket connectSocket( - final Socket sock, - final InetSocketAddress remoteAddress, - final InetSocketAddress localAddress, - final HttpParams params) throws IOException, UnknownHostException, ConnectTimeoutException { - if (remoteAddress == null) { - throw new IllegalArgumentException("Remote address may not be null"); - } - if (params == null) { - throw new IllegalArgumentException("HTTP parameters may not be null"); - } - SSLSocket sslsock = (SSLSocket) (sock != null ? sock : createSocket()); - if (localAddress != null) { -// sslsock.setReuseAddress(HttpConnectionParams.getSoReuseaddr(params)); - sslsock.bind(localAddress); - } - - setHostName(sslsock, remoteAddress.getHostName()); - int connTimeout = HttpConnectionParams.getConnectionTimeout(params); - int soTimeout = HttpConnectionParams.getSoTimeout(params); - - try { - sslsock.connect(remoteAddress, connTimeout); - } catch (SocketTimeoutException ex) { - throw new ConnectTimeoutException("Connect to " + remoteAddress.getHostName() + "/" - + remoteAddress.getAddress() + " timed out"); - } - sslsock.setSoTimeout(soTimeout); - if (this.hostnameVerifier != null) { - try { - this.hostnameVerifier.verify(remoteAddress.getHostName(), sslsock); - // verifyHostName() didn't blowup - good! - } catch (IOException iox) { - // close the socket before re-throwing the exception - try { sslsock.close(); } catch (Exception x) { /*ignore*/ } - throw iox; - } - } - return sslsock; - } - - - /** - * Checks whether a socket connection is secure. - * This factory creates TLS/SSL socket connections - * which, by default, are considered secure. - *
- * Derived classes may override this method to perform - * runtime checks, for example based on the cypher suite. - * - * @param sock the connected socket - * - * @return true - * - * @throws IllegalArgumentException if the argument is invalid - */ - public boolean isSecure(final Socket sock) throws IllegalArgumentException { - if (sock == null) { - throw new IllegalArgumentException("Socket may not be null"); - } - // This instanceof check is in line with createSocket() above. - if (!(sock instanceof SSLSocket)) { - throw new IllegalArgumentException("Socket not created by this factory"); - } - // This check is performed last since it calls the argument object. - if (sock.isClosed()) { - throw new IllegalArgumentException("Socket is closed"); - } - return true; - } - - /** - * @since 4.1 - */ - public Socket createLayeredSocket( - final Socket socket, - final String host, - final int port, - final boolean autoClose) throws IOException, UnknownHostException { - SSLSocket sslSocket = (SSLSocket) this.socketfactory.createSocket( - socket, - host, - port, - autoClose - ); - sslSocket.setEnabledProtocols(getProtocols(sslSocket)); - sslSocket.setEnabledCipherSuites(getCiphers(sslSocket)); - if (this.hostnameVerifier != null) { - this.hostnameVerifier.verify(host, sslSocket); - } - // verifyHostName() didn't blowup - good! - return sslSocket; - } - - @Deprecated - public void setHostnameVerifier(X509HostnameVerifier hostnameVerifier) { - if ( hostnameVerifier == null ) { - throw new IllegalArgumentException("Hostname verifier may not be null"); - } - this.hostnameVerifier = hostnameVerifier; - } - - public X509HostnameVerifier getHostnameVerifier() { - return this.hostnameVerifier; - } - - /** - * @deprecated Use {@link #connectSocket(Socket, InetSocketAddress, InetSocketAddress, HttpParams)} - */ - @Deprecated - public Socket connectSocket( - final Socket socket, - final String host, int port, - final InetAddress localAddress, int localPort, - final HttpParams params) throws IOException, UnknownHostException, ConnectTimeoutException { - InetSocketAddress local = null; - if (localAddress != null || localPort > 0) { - // we need to bind explicitly - if (localPort < 0) { - localPort = 0; // indicates "any" - } - local = new InetSocketAddress(localAddress, localPort); - } - InetAddress remoteAddress; - if (this.nameResolver != null) { - remoteAddress = this.nameResolver.resolve(host); - } else { - remoteAddress = InetAddress.getByName(host); - } - InetSocketAddress remote = new InetSocketAddress(remoteAddress, port); - return connectSocket(socket, remote, local, params); - } - - /** - * @deprecated Use {@link #createLayeredSocket(Socket, String, int, boolean)} - */ - @Deprecated - public Socket createSocket( - final Socket socket, - final String host, int port, - boolean autoClose) throws IOException, UnknownHostException { - SSLSocket sslSocket = (SSLSocket) this.socketfactory.createSocket(socket, host, port, autoClose); - sslSocket.setEnabledProtocols(getProtocols(sslSocket)); - sslSocket.setEnabledCipherSuites(getCiphers(sslSocket)); - setHostName(sslSocket, host); - return sslSocket; - } - - private void setHostName(SSLSocket sslsock, String hostname){ - try { - java.lang.reflect.Method setHostnameMethod = sslsock.getClass().getMethod("setHostname", String.class); - setHostnameMethod.invoke(sslsock, hostname); - } catch (Exception e) { - Log.w(TAG, "SNI not useable", e); - } - } - - private String[] getProtocols(SSLSocket sslSocket) { - String[] protocols = sslSocket.getEnabledProtocols(); - - // Remove SSLv3 if it is not the only option - if(protocols.length > 1) { - List protocolList = new ArrayList(Arrays.asList(protocols)); - protocolList.remove("SSLv3"); - protocols = protocolList.toArray(new String[protocolList.size()]); - } - - return protocols; - } - - private String[] getCiphers(SSLSocket sslSocket) { - String[] ciphers = sslSocket.getEnabledCipherSuites(); - - List enabledCiphers = new ArrayList(Arrays.asList(ciphers)); - // On Android 5.0 release, Jetty doesn't seem to play nice with these ciphers - // Issue seems to have been fixed in M, and now won't work without them. Because Google - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) { - enabledCiphers.remove("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"); - enabledCiphers.remove("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"); - } - - ciphers = enabledCiphers.toArray(new String[enabledCiphers.size()]); - return ciphers; - } -} diff --git a/app/src/main/java/github/daneren2005/dsub/service/ssl/TrustManagerDecorator.java b/app/src/main/java/github/daneren2005/dsub/service/ssl/TrustManagerDecorator.java deleted file mode 100644 index f2364368..00000000 --- a/app/src/main/java/github/daneren2005/dsub/service/ssl/TrustManagerDecorator.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ -package github.daneren2005.dsub.service.ssl; - -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; - -import javax.net.ssl.X509TrustManager; - - -/** - * @since 4.1 - */ -class TrustManagerDecorator implements X509TrustManager { - - private final X509TrustManager trustManager; - private final TrustStrategy trustStrategy; - - TrustManagerDecorator(final X509TrustManager trustManager, final TrustStrategy trustStrategy) { - super(); - this.trustManager = trustManager; - this.trustStrategy = trustStrategy; - } - - public void checkClientTrusted( - final X509Certificate[] chain, final String authType) throws CertificateException { - this.trustManager.checkClientTrusted(chain, authType); - } - - public void checkServerTrusted( - final X509Certificate[] chain, final String authType) throws CertificateException { - if (!this.trustStrategy.isTrusted(chain, authType)) { - this.trustManager.checkServerTrusted(chain, authType); - } - } - - public X509Certificate[] getAcceptedIssuers() { - return this.trustManager.getAcceptedIssuers(); - } - -} diff --git a/app/src/main/java/github/daneren2005/dsub/service/ssl/TrustSelfSignedStrategy.java b/app/src/main/java/github/daneren2005/dsub/service/ssl/TrustSelfSignedStrategy.java deleted file mode 100644 index 637a8931..00000000 --- a/app/src/main/java/github/daneren2005/dsub/service/ssl/TrustSelfSignedStrategy.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ -package github.daneren2005.dsub.service.ssl; - -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; - -/** - * A trust strategy that accepts self-signed certificates as trusted. Verification of all other - * certificates is done by the trust manager configured in the SSL context. - * - * @since 4.1 - */ -public class TrustSelfSignedStrategy implements TrustStrategy { - - public boolean isTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { - return true; - } - -} diff --git a/app/src/main/java/github/daneren2005/dsub/service/ssl/TrustStrategy.java b/app/src/main/java/github/daneren2005/dsub/service/ssl/TrustStrategy.java deleted file mode 100644 index 334a97c5..00000000 --- a/app/src/main/java/github/daneren2005/dsub/service/ssl/TrustStrategy.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ -package github.daneren2005.dsub.service.ssl; - -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; - -/** - * A strategy to establish trustworthiness of certificates without consulting the trust manager - * configured in the actual SSL context. This interface can be used to override the standard - * JSSE certificate verification process. - * - * @since 4.1 - */ -public interface TrustStrategy { - - /** - * Determines whether the certificate chain can be trusted without consulting the trust manager - * configured in the actual SSL context. This method can be used to override the standard JSSE - * certificate verification process. - *

- * Please note that, if this method returns false, the trust manager configured - * in the actual SSL context can still clear the certificate as trusted. - * - * @param chain the peer certificate chain - * @param authType the authentication type based on the client certificate - * @return true if the certificate can be trusted without verification by - * the trust manager, false otherwise. - * @throws CertificateException thrown if the certificate is not trusted or invalid. - */ - boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException; - -} diff --git a/app/src/main/java/github/daneren2005/dsub/util/Util.java b/app/src/main/java/github/daneren2005/dsub/util/Util.java index b69ea55e..7f8a168d 100644 --- a/app/src/main/java/github/daneren2005/dsub/util/Util.java +++ b/app/src/main/java/github/daneren2005/dsub/util/Util.java @@ -19,7 +19,6 @@ package github.daneren2005.dsub.util; import android.annotation.TargetApi; import android.app.Activity; -import android.graphics.Color; import android.support.annotation.StringRes; import android.support.v7.app.AlertDialog; import android.content.ClipboardManager; @@ -29,7 +28,6 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; -import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -50,15 +48,11 @@ import android.util.Log; import android.util.SparseArray; import android.view.View; import android.view.Gravity; -import android.view.Window; -import android.view.WindowManager; import android.widget.AdapterView; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import github.daneren2005.dsub.R; -import github.daneren2005.dsub.activity.SettingsActivity; -import github.daneren2005.dsub.activity.SubsonicFragmentActivity; import github.daneren2005.dsub.adapter.DetailsAdapter; import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.domain.PlayerState; @@ -67,8 +61,6 @@ import github.daneren2005.dsub.domain.ServerInfo; import github.daneren2005.dsub.receiver.MediaButtonIntentReceiver; import github.daneren2005.dsub.service.DownloadService; -import org.apache.http.HttpEntity; - import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.File; @@ -80,7 +72,6 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.math.BigInteger; import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.text.DecimalFormat; import java.text.NumberFormat; @@ -603,13 +594,6 @@ public final class Util { } } - public static String getContentType(HttpEntity entity) { - if (entity == null || entity.getContentType() == null) { - return null; - } - return entity.getContentType().getValue(); - } - public static int getRemainingTrialDays(Context context) { SharedPreferences prefs = getPreferences(context); long installTime = prefs.getLong(Constants.PREFERENCES_KEY_INSTALL_TIME, 0L); -- cgit v1.2.3 From 3af3fc9495491c6263fb5794e10aa72a47b727c5 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Thu, 6 Oct 2016 17:48:53 -0700 Subject: Move to using Google Play Services SSL by default where available --- .../github/daneren2005/dsub/service/RESTMusicService.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'app/src') diff --git a/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java b/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java index 72faa7e1..178b8d13 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java +++ b/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java @@ -40,6 +40,8 @@ import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.util.Log; +import com.google.android.gms.security.ProviderInstaller; + import github.daneren2005.dsub.R; import github.daneren2005.dsub.domain.*; import github.daneren2005.dsub.fragments.MainFragment; @@ -105,6 +107,7 @@ public class RESTMusicService implements MusicService { private String redirectFrom; private String redirectTo; private Integer instance; + private boolean hasInstalledGoogleSSL = false; public RESTMusicService() { selfSignedHostnameVerifier = new HostnameVerifier() { @@ -1831,6 +1834,16 @@ public class RESTMusicService implements MusicService { } private HttpURLConnection getConnectionDirect(Context context, String url, int minNetworkTimeout) throws Exception { + if(!hasInstalledGoogleSSL) { + try { + ProviderInstaller.installIfNeeded(context); + } catch(Exception e) { + // Just continue on anyways, doesn't really harm anything if this fails + Log.w(TAG, "Failed to update to use Google Play SSL", e); + } + hasInstalledGoogleSSL = true; + } + // Connect and add headers URL urlObj = new URL(url); HttpURLConnection connection = (HttpURLConnection) urlObj.openConnection(); -- cgit v1.2.3 From 1926158ac2cae28b0b1c40bc989537e539bddddd Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Fri, 7 Oct 2016 17:52:01 -0700 Subject: #564 Reimplement getInputStream range header, increasing timeout, and fix retry logic --- .../daneren2005/dsub/service/RESTMusicService.java | 72 +++++++++++++--------- 1 file changed, 42 insertions(+), 30 deletions(-) (limited to 'app/src') diff --git a/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java b/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java index 178b8d13..b05a569d 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java +++ b/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java @@ -30,6 +30,7 @@ import java.net.URLEncoder; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -80,6 +81,7 @@ import github.daneren2005.dsub.util.ProgressListener; import github.daneren2005.dsub.util.SongDBHandler; import github.daneren2005.dsub.util.Util; import java.io.*; +import java.util.Map; import java.util.zip.GZIPInputStream; import javax.net.ssl.HostnameVerifier; @@ -90,7 +92,6 @@ public class RESTMusicService implements MusicService { private static final String TAG = RESTMusicService.class.getSimpleName(); - private static final int SOCKET_CONNECT_TIMEOUT = 10 * 1000; private static final int SOCKET_READ_TIMEOUT_DEFAULT = 10 * 1000; private static final int SOCKET_READ_TIMEOUT_DOWNLOAD = 30 * 1000; private static final int SOCKET_READ_TIMEOUT_GET_PLAYLIST = 60 * 1000; @@ -804,19 +805,17 @@ public class RESTMusicService implements MusicService { } } - // TODO: Use task - // TODO: Add range header - // Add "Range" header if offset is given. - /*List

headers = new ArrayList
(); + // Add "Range" header if offset is given + Map headers = new HashMap<>(); if (offset > 0) { - headers.add(new BasicHeader("Range", "bytes=" + offset + "-")); - }*/ + headers.put("Range", "bytes=" + offset + "-"); + } // Set socket read timeout. Note: The timeout increases as the offset gets larger. This is // to avoid the thrashing effect seen when offset is combined with transcoding/downsampling on the server. // In that case, the server uses a long time before sending any data, causing the client to time out. int timeout = (int) (SOCKET_READ_TIMEOUT_DOWNLOAD + offset * TIMEOUT_MILLIS_PER_OFFSET_BYTE); - HttpURLConnection connection = getConnection(context, url, parameterNames, parameterValues, timeout); + HttpURLConnection connection = getConnection(context, url, parameterNames, parameterValues, headers, timeout); // If content type is XML, an error occurred. Get it. String contentType = connection.getContentType(); @@ -1769,29 +1768,34 @@ public class RESTMusicService implements MusicService { return in; } - private HttpURLConnection getConnection(Context context, String url, List parameterNames, List parameterValues) throws Exception { - return getConnection(context, url, parameterNames, parameterValues, null, true); - } - private HttpURLConnection getConnection(Context context, String url, List parameterNames, List parameterValues, int minNetworkTimeout) throws Exception { - return getConnection(context, url, parameterNames, parameterValues, minNetworkTimeout, null, true); + private HttpURLConnection getConnection(Context context, String url, List parameterNames, List parameterValues, Map headers, int minNetworkTimeout) throws Exception { + return getConnection(context, url, parameterNames, parameterValues, headers, minNetworkTimeout, null, true); } private HttpURLConnection getConnection(Context context, String url, List parameterNames, List parameterValues, ProgressListener progressListener, boolean throwErrors) throws Exception { return getConnection(context, url, parameterNames, parameterValues, 0, progressListener, throwErrors); } private HttpURLConnection getConnection(Context context, String url, List parameterNames, List parameterValues, int minNetworkTimeout, ProgressListener progressListener, boolean throwErrors) throws Exception { + return getConnection(context, url, parameterNames, parameterValues, null, minNetworkTimeout, progressListener, throwErrors); + } + private HttpURLConnection getConnection(Context context, String url, List parameterNames, List parameterValues, Map headers, int minNetworkTimeout, ProgressListener progressListener, boolean throwErrors) throws Exception { if(throwErrors) { - return getConnectionDirect(context, url, parameterNames, parameterValues, minNetworkTimeout); + SharedPreferences prefs = Util.getPreferences(context); + int networkTimeout = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT, SOCKET_READ_TIMEOUT_DEFAULT + "")); + return getConnectionDirect(context, url, parameterNames, parameterValues, headers, Math.max(minNetworkTimeout, networkTimeout)); } else { - return getConnection(context, url, parameterNames, parameterValues, minNetworkTimeout, progressListener, HTTP_REQUEST_MAX_ATTEMPTS, 0); + return getConnection(context, url, parameterNames, parameterValues, headers, minNetworkTimeout, progressListener, HTTP_REQUEST_MAX_ATTEMPTS, 0); } } - private HttpURLConnection getConnection(Context context, String url, List parameterNames, List parameterValues, ProgressListener progressListener, int retriesLeft, int attempts) throws Exception { - return getConnection(context, url, parameterNames, parameterValues, 0, progressListener, retriesLeft, attempts); - } - private HttpURLConnection getConnection(Context context, String url, List parameterNames, List parameterValues, int minNetworkTimeout, ProgressListener progressListener, int retriesLeft, int attempts) throws Exception { + private HttpURLConnection getConnection(Context context, String url, List parameterNames, List parameterValues, Map headers, int minNetworkTimeout, ProgressListener progressListener, int retriesLeft, int attempts) throws Exception { + SharedPreferences prefs = Util.getPreferences(context); + int networkTimeout = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT, SOCKET_READ_TIMEOUT_DEFAULT + "")); + minNetworkTimeout = Math.max(minNetworkTimeout, networkTimeout); + attempts++; + retriesLeft--; + try { - return getConnectionDirect(context, url, parameterNames, parameterValues, minNetworkTimeout); + return getConnectionDirect(context, url, parameterNames, parameterValues, headers, minNetworkTimeout); } catch (IOException x) { if(retriesLeft > 0) { if (progressListener != null) { @@ -1802,16 +1806,15 @@ public class RESTMusicService implements MusicService { Log.w(TAG, "Got IOException " + x + " (" + attempts + "), will retry"); Thread.sleep(2000L); - // TODO: Increase timeouts on failures - // TODO: Detect task cancellation - return getConnection(context, url, parameterNames, parameterValues, minNetworkTimeout, progressListener, retriesLeft--, attempts + 1); + minNetworkTimeout = (int) (minNetworkTimeout * 1.3); + return getConnection(context, url, parameterNames, parameterValues, headers, minNetworkTimeout, progressListener, retriesLeft, attempts); } else { throw x; } } } - private HttpURLConnection getConnectionDirect(Context context, String url, List parameterNames, List parameterValues, int minNetworkTimeout) throws Exception { + private HttpURLConnection getConnectionDirect(Context context, String url, List parameterNames, List parameterValues, Map headers, int minNetworkTimeout) throws Exception { // Add params to query if (parameterNames != null) { StringBuilder builder = new StringBuilder(url); @@ -1830,10 +1833,10 @@ public class RESTMusicService implements MusicService { Log.i(TAG, stripUrlInfo(rewrittenUrl)); } - return getConnectionDirect(context, rewrittenUrl, minNetworkTimeout); + return getConnectionDirect(context, rewrittenUrl, headers, minNetworkTimeout); } - private HttpURLConnection getConnectionDirect(Context context, String url, int minNetworkTimeout) throws Exception { + private HttpURLConnection getConnectionDirect(Context context, String url, Map headers, int minNetworkTimeout) throws Exception { if(!hasInstalledGoogleSSL) { try { ProviderInstaller.installIfNeeded(context); @@ -1853,16 +1856,25 @@ public class RESTMusicService implements MusicService { connection.addRequestProperty("User-Agent", Constants.REST_CLIENT_ID); // Set timeout - SharedPreferences prefs = Util.getPreferences(context); - int networkTimeout = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT, SOCKET_CONNECT_TIMEOUT + "")); - connection.setConnectTimeout(Math.max(minNetworkTimeout, networkTimeout)); - connection.setReadTimeout(SOCKET_READ_TIMEOUT_DEFAULT); + connection.setConnectTimeout(minNetworkTimeout); + connection.setReadTimeout(minNetworkTimeout); + + // Add headers + if(headers != null) { + for(Map.Entry header: headers.entrySet()) { + connection.setRequestProperty(header.getKey(), header.getValue()); + } + } if(connection instanceof HttpsURLConnection) { HttpsURLConnection sslConnection = (HttpsURLConnection) connection; sslConnection.setHostnameVerifier(selfSignedHostnameVerifier); } + // Force the connection to initiate + if(connection.getResponseCode() >= 500) { + throw new IOException("Error code: " + connection.getResponseCode()); + } detectRedirect(context, urlObj, connection); return connection; } -- cgit v1.2.3 From e32c49329b7c665e96604ac047ce772e1155e468 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Mon, 10 Oct 2016 17:37:24 -0700 Subject: Remove extra xlms definitions on layouts --- app/src/main/res/layout-land/download.xml | 2 +- app/src/main/res/layout/abstract_fragment_activity.xml | 2 +- app/src/main/res/layout/change_email.xml | 2 +- app/src/main/res/layout/confirm_password.xml | 2 +- app/src/main/res/layout/create_bookmark.xml | 2 +- app/src/main/res/layout/create_podcast.xml | 2 +- app/src/main/res/layout/create_user.xml | 6 +++--- app/src/main/res/layout/notification.xml | 1 - app/src/main/res/layout/notification_expanded.xml | 1 - app/src/main/res/layout/select_album_header.xml | 2 +- app/src/main/res/layout/shuffle_dialog.xml | 6 +++--- app/src/main/res/layout/update_playlist.xml | 6 +++--- app/src/main/res/layout/update_share.xml | 6 +++--- 13 files changed, 19 insertions(+), 21 deletions(-) (limited to 'app/src') diff --git a/app/src/main/res/layout-land/download.xml b/app/src/main/res/layout-land/download.xml index 894ae62e..855bf2a9 100644 --- a/app/src/main/res/layout-land/download.xml +++ b/app/src/main/res/layout-land/download.xml @@ -37,7 +37,7 @@ android:layout_centerHorizontal="true" android:layout_above="@+id/download_song_title"> - - - diff --git a/app/src/main/res/layout/confirm_password.xml b/app/src/main/res/layout/confirm_password.xml index 9ec61c0a..d74eecfd 100644 --- a/app/src/main/res/layout/confirm_password.xml +++ b/app/src/main/res/layout/confirm_password.xml @@ -4,7 +4,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - diff --git a/app/src/main/res/layout/create_bookmark.xml b/app/src/main/res/layout/create_bookmark.xml index d6f077c3..22d96227 100644 --- a/app/src/main/res/layout/create_bookmark.xml +++ b/app/src/main/res/layout/create_bookmark.xml @@ -3,7 +3,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content"> - diff --git a/app/src/main/res/layout/create_podcast.xml b/app/src/main/res/layout/create_podcast.xml index 04e74ec3..a5e66792 100644 --- a/app/src/main/res/layout/create_podcast.xml +++ b/app/src/main/res/layout/create_podcast.xml @@ -3,7 +3,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content"> - diff --git a/app/src/main/res/layout/create_user.xml b/app/src/main/res/layout/create_user.xml index b2d8f6e0..7d77ade9 100644 --- a/app/src/main/res/layout/create_user.xml +++ b/app/src/main/res/layout/create_user.xml @@ -4,7 +4,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - @@ -26,7 +26,7 @@ android:textColor="?android:textColorPrimary"/> - @@ -48,7 +48,7 @@ android:textColor="?android:textColorPrimary"/> - diff --git a/app/src/main/res/layout/notification.xml b/app/src/main/res/layout/notification.xml index 4a89db49..0ab5a884 100644 --- a/app/src/main/res/layout/notification.xml +++ b/app/src/main/res/layout/notification.xml @@ -13,7 +13,6 @@ android:gravity="center" /> - - @@ -26,7 +26,7 @@ android:hint="@string/shuffle.startYear" /> - @@ -49,7 +49,7 @@ android:hint="@string/shuffle.endYear" /> - diff --git a/app/src/main/res/layout/update_playlist.xml b/app/src/main/res/layout/update_playlist.xml index cc7e5ee6..f9cc6a90 100644 --- a/app/src/main/res/layout/update_playlist.xml +++ b/app/src/main/res/layout/update_playlist.xml @@ -3,7 +3,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content"> - @@ -26,7 +26,7 @@ android:textColor="?android:textColorPrimary"/> - @@ -49,7 +49,7 @@ android:hint="@string/common.comment" /> - diff --git a/app/src/main/res/layout/update_share.xml b/app/src/main/res/layout/update_share.xml index ef44e304..0d06e00d 100644 --- a/app/src/main/res/layout/update_share.xml +++ b/app/src/main/res/layout/update_share.xml @@ -3,7 +3,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content"> - @@ -26,7 +26,7 @@ android:hint="@string/common.name" /> - @@ -48,7 +48,7 @@ android:calendarViewShown="false"/> - -- cgit v1.2.3 From a09b7f8b2bac877655e10f5f7f8f37779d119595 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Mon, 10 Oct 2016 17:47:05 -0700 Subject: Fix a couple of minor warnings --- .../java/github/daneren2005/dsub/fragments/SubsonicFragment.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'app/src') 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 818324ed..e138f4dd 100644 --- a/app/src/main/java/github/daneren2005/dsub/fragments/SubsonicFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/fragments/SubsonicFragment.java @@ -660,7 +660,7 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR } }); - refreshLayout.setColorScheme( + refreshLayout.setColorSchemeResources( R.color.holo_blue_light, R.color.holo_orange_light, R.color.holo_green_light, @@ -683,7 +683,7 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR } }); - refreshLayout.setColorScheme( + refreshLayout.setColorSchemeResources( R.color.holo_blue_light, R.color.holo_orange_light, R.color.holo_green_light, @@ -1113,7 +1113,7 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR @Override protected void done(Void result) { - Util.toast(context, context.getResources().getString(R.string.updated_playlist, songs.size(), playlist.getName())); + Util.toast(context, context.getResources().getString(R.string.updated_playlist, String.valueOf(songs.size()), playlist.getName())); } @Override -- cgit v1.2.3 From 1796bf6a4c007b39e22696b6ae3c38aac62454e8 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Tue, 11 Oct 2016 17:02:33 -0700 Subject: Fixes #747: Add support for Internet Radio while casting --- .../SelectInternetRadioStationFragment.java | 2 +- .../dsub/service/ChromeCastController.java | 51 +---------------- .../daneren2005/dsub/service/DLNAController.java | 54 +----------------- .../dsub/service/JukeboxController.java | 2 +- .../daneren2005/dsub/service/RemoteController.java | 64 ++++++++++++++++++++++ 5 files changed, 71 insertions(+), 102 deletions(-) (limited to 'app/src') diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SelectInternetRadioStationFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SelectInternetRadioStationFragment.java index c39e9f61..74c4b269 100644 --- a/app/src/main/java/github/daneren2005/dsub/fragments/SelectInternetRadioStationFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/fragments/SelectInternetRadioStationFragment.java @@ -133,7 +133,7 @@ public class SelectInternetRadioStationFragment extends SelectRecyclerFragment(url, metadata); + return new Pair<>(url, metadata); } private void failedLoad() { diff --git a/app/src/main/java/github/daneren2005/dsub/service/JukeboxController.java b/app/src/main/java/github/daneren2005/dsub/service/JukeboxController.java index 82ef45e1..b9f40f32 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/JukeboxController.java +++ b/app/src/main/java/github/daneren2005/dsub/service/JukeboxController.java @@ -48,7 +48,7 @@ public class JukeboxController extends RemoteController { private float gain = 0.5f; public JukeboxController(DownloadService downloadService, Handler handler) { - this.downloadService = downloadService; + super(downloadService); this.handler = handler; } diff --git a/app/src/main/java/github/daneren2005/dsub/service/RemoteController.java b/app/src/main/java/github/daneren2005/dsub/service/RemoteController.java index 2815bcc9..c6702e3e 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/RemoteController.java +++ b/app/src/main/java/github/daneren2005/dsub/service/RemoteController.java @@ -19,20 +19,32 @@ package github.daneren2005.dsub.service; +import android.content.SharedPreferences; import android.util.Log; import java.util.Iterator; import java.util.concurrent.LinkedBlockingQueue; +import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.domain.RemoteStatus; import github.daneren2005.dsub.util.Constants; import github.daneren2005.dsub.util.Util; +import github.daneren2005.serverproxy.FileProxy; +import github.daneren2005.serverproxy.ServerProxy; import github.daneren2005.serverproxy.WebProxy; public abstract class RemoteController { private static final String TAG = RemoteController.class.getSimpleName(); protected DownloadService downloadService; protected boolean nextSupported = false; + protected ServerProxy proxy; + protected String rootLocation = ""; + + public RemoteController(DownloadService downloadService) { + this.downloadService = downloadService; + SharedPreferences prefs = Util.getPreferences(downloadService); + rootLocation = prefs.getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, null); + } public abstract void create(boolean playing, int seconds); public abstract void start(); @@ -110,4 +122,56 @@ public abstract class RemoteController { return new WebProxy(downloadService); } } + + protected String getStreamUrl(MusicService musicService, DownloadFile downloadFile) throws Exception { + MusicDirectory.Entry song = downloadFile.getSong(); + + String url; + // In offline mode or playing offline song + if(downloadFile.isStream()) { + url = downloadFile.getStream(); + } else if(Util.isOffline(downloadService) || song.getId().indexOf(rootLocation) != -1) { + if(proxy == null) { + proxy = new FileProxy(downloadService); + proxy.start(); + } + + // Offline song + if(song.getId().indexOf(rootLocation) != -1) { + url = proxy.getPublicAddress(song.getId()); + } else { + // Playing online song in offline mode + url = proxy.getPublicAddress(downloadFile.getCompleteFile().getPath()); + } + } else { + // Check if we want a proxy going still + if(Util.isCastProxy(downloadService)) { + if(proxy instanceof FileProxy) { + proxy.stop(); + proxy = null; + } + + if(proxy == null) { + proxy = createWebProxy(); + proxy.start(); + } + } else if(proxy != null) { + proxy.stop(); + proxy = null; + } + + if(song.isVideo()) { + url = musicService.getHlsUrl(song.getId(), downloadFile.getBitRate(), downloadService); + } else { + url = musicService.getMusicUrl(downloadService, song, downloadFile.getBitRate()); + } + + // If proxy is going, it is a WebProxy + if(proxy != null) { + url = proxy.getPublicAddress(url); + } + } + + return url; + } } -- cgit v1.2.3 From c69779ca1c1ba11da80765f6c5545e5a30f6fd4f Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Tue, 11 Oct 2016 19:46:01 -0700 Subject: Fixes #746: Add support for Play Title by Artist voice commands --- .../dsub/activity/SubsonicFragmentActivity.java | 6 +- .../dsub/activity/VoiceQueryReceiverActivity.java | 16 ++++ .../daneren2005/dsub/fragments/SearchFragment.java | 85 ++++++++++++++++++---- 3 files changed, 90 insertions(+), 17 deletions(-) (limited to 'app/src') 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 c7190046..e37c6d49 100644 --- a/app/src/main/java/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java +++ b/app/src/main/java/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java @@ -30,6 +30,7 @@ import android.content.res.TypedArray; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; +import android.provider.MediaStore; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v7.app.AlertDialog; @@ -402,9 +403,12 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo if(currentFragment instanceof SearchFragment) { String query = intent.getStringExtra(Constants.INTENT_EXTRA_NAME_QUERY); boolean autoplay = intent.getBooleanExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false); + String artist = intent.getStringExtra(MediaStore.EXTRA_MEDIA_ARTIST); + String album = intent.getStringExtra(MediaStore.EXTRA_MEDIA_ALBUM); + String title = intent.getStringExtra(MediaStore.EXTRA_MEDIA_TITLE); if (query != null) { - ((SearchFragment)currentFragment).search(query, autoplay); + ((SearchFragment)currentFragment).search(query, autoplay, artist, album, title); } getIntent().removeExtra(Constants.INTENT_EXTRA_NAME_QUERY); } else { diff --git a/app/src/main/java/github/daneren2005/dsub/activity/VoiceQueryReceiverActivity.java b/app/src/main/java/github/daneren2005/dsub/activity/VoiceQueryReceiverActivity.java index c0effe27..641b118f 100644 --- a/app/src/main/java/github/daneren2005/dsub/activity/VoiceQueryReceiverActivity.java +++ b/app/src/main/java/github/daneren2005/dsub/activity/VoiceQueryReceiverActivity.java @@ -55,6 +55,22 @@ public class VoiceQueryReceiverActivity extends Activity { if(!GMS_SEARCH_ACTION.equals(getIntent().getAction())) { intent.putExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, true); } + + String artist = getIntent().getStringExtra(MediaStore.EXTRA_MEDIA_ARTIST); + if(artist != null) { + intent.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, artist); + } + + String album = getIntent().getStringExtra(MediaStore.EXTRA_MEDIA_ALBUM); + if(album != null) { + intent.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, album); + } + + String title = getIntent().getStringExtra(MediaStore.EXTRA_MEDIA_TITLE); + if(title != null) { + intent.putExtra(MediaStore.EXTRA_MEDIA_TITLE, title); + } + intent.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, getIntent().getStringExtra(MediaStore.EXTRA_MEDIA_FOCUS)); intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); Util.startActivityWithoutTransition(VoiceQueryReceiverActivity.this, intent); diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SearchFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SearchFragment.java index eed714af..dfff45cd 100644 --- a/app/src/main/java/github/daneren2005/dsub/fragments/SearchFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/fragments/SearchFragment.java @@ -3,7 +3,10 @@ package github.daneren2005.dsub.fragments; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.TreeMap; import android.content.Intent; import android.os.Bundle; @@ -26,6 +29,7 @@ import github.daneren2005.dsub.adapter.SearchAdapter; import github.daneren2005.dsub.adapter.SectionAdapter; import github.daneren2005.dsub.domain.Artist; import github.daneren2005.dsub.domain.MusicDirectory; +import github.daneren2005.dsub.domain.MusicDirectory.Entry; import github.daneren2005.dsub.domain.SearchCritera; import github.daneren2005.dsub.domain.SearchResult; import github.daneren2005.dsub.service.MusicService; @@ -132,7 +136,7 @@ public class SearchFragment extends SubsonicFragment implements SectionAdapter.O @Override public void onCreateContextMenu(Menu menu, MenuInflater menuInflater, UpdateView updateView, Serializable item) { onCreateContextMenuSupport(menu, menuInflater, updateView, item); - if(item instanceof MusicDirectory.Entry && !((MusicDirectory.Entry) item).isVideo() && !Util.isOffline(context)) { + if(item instanceof Entry && !((Entry) item).isVideo() && !Util.isOffline(context)) { menu.removeItem(R.id.song_menu_remove_playlist); } recreateContextMenu(menu); @@ -152,8 +156,8 @@ public class SearchFragment extends SubsonicFragment implements SectionAdapter.O public void onItemClicked(UpdateView updateView, Serializable item) { if (item instanceof Artist) { onArtistSelected((Artist) item, false); - } else if (item instanceof MusicDirectory.Entry) { - MusicDirectory.Entry entry = (MusicDirectory.Entry) item; + } else if (item instanceof Entry) { + Entry entry = (Entry) item; if (entry.isDirectory()) { onAlbumSelected(entry, false); } else if (entry.isVideo()) { @@ -165,12 +169,12 @@ public class SearchFragment extends SubsonicFragment implements SectionAdapter.O } @Override - protected List getSelectedEntries() { + protected List getSelectedEntries() { List selected = adapter.getSelected(); - List selectedMedia = new ArrayList<>(); + List selectedMedia = new ArrayList<>(); for(Serializable ser: selected) { - if(ser instanceof MusicDirectory.Entry) { - selectedMedia.add((MusicDirectory.Entry) ser); + if(ser instanceof Entry) { + selectedMedia.add((Entry) ser); } } @@ -182,7 +186,7 @@ public class SearchFragment extends SubsonicFragment implements SectionAdapter.O return true; } - public void search(final String query, final boolean autoplay) { + public void search(final String query, final boolean autoplay, final String artist, final String album, final String title) { if(skipSearch) { skipSearch = false; return; @@ -202,7 +206,7 @@ public class SearchFragment extends SubsonicFragment implements SectionAdapter.O searchResult = result; recyclerView.setAdapter(adapter = new SearchAdapter(context, searchResult, getImageLoader(), largeAlbums, SearchFragment.this)); if (autoplay) { - autoplay(query); + autoplay(query, artist, album, title); } } @@ -232,7 +236,7 @@ public class SearchFragment extends SubsonicFragment implements SectionAdapter.O replaceFragment(fragment); } - private void onAlbumSelected(MusicDirectory.Entry album, boolean autoplay) { + private void onAlbumSelected(Entry album, boolean autoplay) { SubsonicFragment fragment = new SelectDirectoryFragment(); Bundle args = new Bundle(); args.putString(Constants.INTENT_EXTRA_NAME_ID, album.getId()); @@ -245,7 +249,7 @@ public class SearchFragment extends SubsonicFragment implements SectionAdapter.O replaceFragment(fragment); } - private void onSongSelected(MusicDirectory.Entry song, boolean save, boolean append, boolean autoplay, boolean playNext) { + private void onSongSelected(Entry song, boolean save, boolean append, boolean autoplay, boolean playNext) { DownloadService downloadService = getDownloadService(); if (downloadService != null) { if (!append) { @@ -260,7 +264,7 @@ public class SearchFragment extends SubsonicFragment implements SectionAdapter.O } } - private void onVideoSelected(MusicDirectory.Entry entry) { + private void onVideoSelected(Entry entry) { int maxBitrate = Util.getMaxVideoBitrate(context); Intent intent = new Intent(Intent.ACTION_VIEW); @@ -268,6 +272,55 @@ public class SearchFragment extends SubsonicFragment implements SectionAdapter.O startActivity(intent); } + private void autoplay(String query, String artistQuery, String albumQuery, String titleQuery) { + Log.i(TAG, "Query: '" + query + "' ( Artist: '" + artistQuery + "', Album: '" + albumQuery + "', Title: '" + titleQuery + "')"); + + if(titleQuery != null && !searchResult.getSongs().isEmpty()) { + titleQuery = titleQuery.toLowerCase(); + + TreeMap tree = new TreeMap<>(); + for(Entry song: searchResult.getSongs()) { + tree.put(Util.getStringDistance(song.getTitle().toLowerCase(), titleQuery), song); + } + + Map.Entry entry = tree.firstEntry(); + if(entry.getKey() <= MIN_CLOSENESS) { + onSongSelected(entry.getValue(), false, false, true, false); + } else { + autoplay(query); + } + } else if(albumQuery != null && !searchResult.getAlbums().isEmpty()) { + albumQuery = albumQuery.toLowerCase(); + + TreeMap tree = new TreeMap<>(); + for(Entry album: searchResult.getAlbums()) { + tree.put(Util.getStringDistance(album.getTitle().toLowerCase(), albumQuery), album); + } + + Map.Entry entry = tree.firstEntry(); + if(entry.getKey() <= MIN_CLOSENESS) { + onAlbumSelected(entry.getValue(), true); + } else { + autoplay(query); + } + } else if(artistQuery != null && !searchResult.getArtists().isEmpty()) { + artistQuery = artistQuery.toLowerCase(); + + TreeMap tree = new TreeMap<>(); + for(Artist artist: searchResult.getArtists()) { + tree.put(Util.getStringDistance(artist.getName().toLowerCase(), artistQuery), artist); + } + Map.Entry entry = tree.firstEntry(); + if(entry.getKey() <= MIN_CLOSENESS) { + onArtistSelected(entry.getValue(), true); + } else { + autoplay(query); + } + } else { + autoplay(query); + } + } + private void autoplay(String query) { query = query.toLowerCase(); @@ -276,12 +329,12 @@ public class SearchFragment extends SubsonicFragment implements SectionAdapter.O artist = searchResult.getArtists().get(0); artist.setCloseness(Util.getStringDistance(artist.getName().toLowerCase(), query)); } - MusicDirectory.Entry album = null; + Entry album = null; if(!searchResult.getAlbums().isEmpty()) { album = searchResult.getAlbums().get(0); album.setCloseness(Util.getStringDistance(album.getTitle().toLowerCase(), query)); } - MusicDirectory.Entry song = null; + Entry song = null; if(!searchResult.getSongs().isEmpty()) { song = searchResult.getSongs().get(0); song.setCloseness(Util.getStringDistance(song.getTitle().toLowerCase(), query)); @@ -289,10 +342,10 @@ public class SearchFragment extends SubsonicFragment implements SectionAdapter.O if(artist != null && (artist.getCloseness() <= MIN_CLOSENESS || (album == null || artist.getCloseness() <= album.getCloseness()) && - (song == null || artist.getCloseness() <= song.getCloseness()))) { + (song == null || artist.getCloseness() <= song.getCloseness()))) { onArtistSelected(artist, true); } else if(album != null && (album.getCloseness() <= MIN_CLOSENESS || - song == null || album.getCloseness() <= song.getCloseness())) { + song == null || album.getCloseness() <= song.getCloseness())) { onAlbumSelected(album, true); } else if(song != null) { onSongSelected(song, false, false, true, false); -- cgit v1.2.3 From 406b996e0cbb2a708606ddee4a0a12d90136dcf0 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Thu, 13 Oct 2016 16:05:46 -0700 Subject: DSub released to Beta to test out connection logic --- app/build.gradle | 4 ++-- app/src/main/res/xml/changelog.xml | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'app/src') diff --git a/app/build.gradle b/app/build.gradle index 50bbb69b..92dfa731 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { applicationId "github.daneren2005.dsub" minSdkVersion 14 targetSdkVersion 23 - versionCode 187 - versionName '5.3.1' + versionCode 188 + versionName '5.3.2 BETA' setProperty("archivesBaseName", "DSub $versionName") resConfigs "de", "es", "fr", "hu", "nl", "pt-rPT", "ru", "sv" } diff --git a/app/src/main/res/xml/changelog.xml b/app/src/main/res/xml/changelog.xml index 169edaa0..7abec09c 100644 --- a/app/src/main/res/xml/changelog.xml +++ b/app/src/main/res/xml/changelog.xml @@ -1,5 +1,11 @@ + + Add support for casting Internet Radio to ChromeCast/DLNA + Add support for Play Title by Artist from Google Search + Use Google Play SSL + Move to more modern connection framework + Fix Internet Radio streams which point to playlists Don't show playback speed button below Android 6.0 -- cgit v1.2.3 From 3789eba4794eed03d1bf703ed648f08b9e55b90a Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Thu, 13 Oct 2016 16:06:22 -0700 Subject: Fixes #749: Fix background cache refresh screwing up Show All --- .../daneren2005/dsub/fragments/SelectDirectoryFragment.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'app/src') 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 0ac968b7..a10c0ed5 100644 --- a/app/src/main/java/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java @@ -459,8 +459,12 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section } List songs = new ArrayList(); getSongsRecursively(root, songs); - root.replaceChildren(songs); - return root; + + // CachedMusicService is refreshing this data in the background, so will wipe out the songs list from root + MusicDirectory clonedRoot = new MusicDirectory(songs); + clonedRoot.setId(root.getId()); + clonedRoot.setName(root.getName()); + return clonedRoot; } private void getSongsRecursively(MusicDirectory parent, List songs) throws Exception { @@ -916,7 +920,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section for(Integer index: indexes) { entryGridAdapter.removeAt(index); } - Util.toast(context, context.getResources().getString(R.string.removed_playlist, indexes.size(), name)); + Util.toast(context, context.getResources().getString(R.string.removed_playlist, String.valueOf(indexes.size()), name)); } @Override -- cgit v1.2.3 From 811f2a397549ee8d819c39a75809f363d576d44e Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Thu, 13 Oct 2016 16:49:30 -0700 Subject: Fixes #748: Show albums for Show All --- .../java/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/src') 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 a10c0ed5..d3a0bfe8 100644 --- a/app/src/main/java/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java @@ -737,7 +737,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section if(!artist) { entryGridAdapter.setShowArtist(true); } - if(topTracks) { + if(topTracks || showAll) { entryGridAdapter.setShowAlbum(true); } -- cgit v1.2.3 From b53cff3acfb12980001c30cf262d8f74f4740e68 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Mon, 17 Oct 2016 16:59:35 -0700 Subject: Add some better error handling to saving playlists + log errors --- .../dsub/fragments/SubsonicFragment.java | 24 +++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) (limited to 'app/src') 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 e138f4dd..de230309 100644 --- a/app/src/main/java/github/daneren2005/dsub/fragments/SubsonicFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/fragments/SubsonicFragment.java @@ -1135,16 +1135,24 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR final EditText playlistNameView = (EditText) layout.findViewById(R.id.save_playlist_name); final CheckBox overwriteCheckBox = (CheckBox) layout.findViewById(R.id.save_playlist_overwrite); if(getSuggestion) { - String playlistName = (getDownloadService() != null) ? getDownloadService().getSuggestedPlaylistName() : null; + DownloadService downloadService = getDownloadService(); + String playlistName = null; + String playlistId = null; + if(downloadService != null) { + playlistName = downloadService.getSuggestedPlaylistName(); + playlistId = downloadService.getSuggestedPlaylistId(); + } if (playlistName != null) { playlistNameView.setText(playlistName); - try { - if(ServerInfo.checkServerVersion(context, "1.8.0") && Integer.parseInt(getDownloadService().getSuggestedPlaylistId()) != -1) { - overwriteCheckBox.setChecked(true); - overwriteCheckBox.setVisibility(View.VISIBLE); + if(playlistId != null) { + try { + if (ServerInfo.checkServerVersion(context, "1.8.0") && Integer.parseInt(playlistId) != -1) { + overwriteCheckBox.setChecked(true); + overwriteCheckBox.setVisibility(View.VISIBLE); + } + } catch (Exception e) { + Log.i(TAG, "Playlist id isn't a integer, probably MusicCabinet", e); } - } catch(Exception e) { - Log.i(TAG, "Playlist id isn't a integer, probably MusicCabinet"); } } else { DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); @@ -1205,6 +1213,7 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR @Override protected void error(Throwable error) { String msg = context.getResources().getString(R.string.download_playlist_error) + " " + getErrorMessage(error); + Log.e(TAG, "Failed to create playlist", error); Util.toast(context, msg); } }.execute(); @@ -1234,6 +1243,7 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR msg = context.getResources().getString(R.string.download_playlist_error) + " " + getErrorMessage(error); } + Log.e(TAG, "Failed to overwrite playlist", error); Util.toast(context, msg, false); } }.execute(); -- cgit v1.2.3 From 4f2655b59a323f1936a72b2622e693c589f18fbb Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Fri, 14 Oct 2016 13:30:01 -0700 Subject: HttpUrlConnection doesn't auto follow http -> https redirects --- .../daneren2005/dsub/service/RESTMusicService.java | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) (limited to 'app/src') diff --git a/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java b/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java index b05a569d..cb8871a0 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java +++ b/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java @@ -1875,12 +1875,29 @@ public class RESTMusicService implements MusicService { if(connection.getResponseCode() >= 500) { throw new IOException("Error code: " + connection.getResponseCode()); } - detectRedirect(context, urlObj, connection); + if(detectRedirect(context, urlObj, connection)) { + String rewrittenUrl = rewriteUrlWithRedirect(context, url); + if(!rewrittenUrl.equals(url)) { + connection.disconnect(); + return getConnectionDirect(context, rewrittenUrl, headers, minNetworkTimeout); + } + } + return connection; } - private void detectRedirect(Context context, URL originalUrl, HttpURLConnection connection) throws Exception { + // Returns true when we should immediately retry with the redirect + private boolean detectRedirect(Context context, URL originalUrl, HttpURLConnection connection) throws Exception { + if(connection.getResponseCode() == HttpURLConnection.HTTP_MOVED_TEMP || connection.getResponseCode() == HttpURLConnection.HTTP_MOVED_PERM) { + String redirectLocation = connection.getHeaderField("Location"); + if(redirectLocation != null) { + detectRedirect(context, originalUrl.toExternalForm(), redirectLocation); + return true; + } + } + detectRedirect(context, originalUrl, connection.getURL()); + return false; } private void detectRedirect(Context context, URL originalUrl, URL redirectedUrl) throws Exception { detectRedirect(context, originalUrl.toExternalForm(), redirectedUrl.toExternalForm()); -- cgit v1.2.3 From a76a2884f0457c32f4fd2284ff80b9ba9cb5ce3f Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Mon, 17 Oct 2016 17:07:43 -0700 Subject: Fix accepting self signed certificates --- ServerProxy | 2 +- .../daneren2005/dsub/service/RESTMusicService.java | 29 ++++++++++++++++++++++ .../daneren2005/dsub/service/RemoteController.java | 3 ++- 3 files changed, 32 insertions(+), 2 deletions(-) (limited to 'app/src') diff --git a/ServerProxy b/ServerProxy index ed3ff58d..a8a75626 160000 --- a/ServerProxy +++ b/ServerProxy @@ -1 +1 @@ -Subproject commit ed3ff58dc83f40ad3437fc30e63d7b437939c2bc +Subproject commit a8a756261fa3c38b1c7df443eacff77ae4aaf137 diff --git a/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java b/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java index cb8871a0..913e30bf 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java +++ b/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java @@ -86,7 +86,11 @@ import java.util.zip.GZIPInputStream; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; public class RESTMusicService implements MusicService { @@ -102,6 +106,7 @@ public class RESTMusicService implements MusicService { private static final int HTTP_REQUEST_MAX_ATTEMPTS = 5; private static final long REDIRECTION_CHECK_INTERVAL_MILLIS = 60L * 60L * 1000L; + private SSLSocketFactory sslSocketFactory; private HostnameVerifier selfSignedHostnameVerifier; private long redirectionLastChecked; private int redirectionNetworkType = -1; @@ -111,6 +116,26 @@ public class RESTMusicService implements MusicService { private boolean hasInstalledGoogleSSL = false; public RESTMusicService() { + TrustManager[] trustAllCerts = new TrustManager[]{ + new X509TrustManager() { + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return null; + } + public void checkClientTrusted( + java.security.cert.X509Certificate[] certs, String authType) { + } + public void checkServerTrusted( + java.security.cert.X509Certificate[] certs, String authType) { + } + } + }; + try { + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); + sslSocketFactory = sslContext.getSocketFactory(); + } catch (Exception e) { + } + selfSignedHostnameVerifier = new HostnameVerifier() { public boolean verify(String hostname, SSLSession session) { return true; @@ -1868,6 +1893,7 @@ public class RESTMusicService implements MusicService { if(connection instanceof HttpsURLConnection) { HttpsURLConnection sslConnection = (HttpsURLConnection) connection; + sslConnection.setSSLSocketFactory(sslSocketFactory); sslConnection.setHostnameVerifier(selfSignedHostnameVerifier); } @@ -1968,6 +1994,9 @@ public class RESTMusicService implements MusicService { } } + public SSLSocketFactory getSSLSocketFactory() { + return sslSocketFactory; + } public HostnameVerifier getHostNameVerifier() { return selfSignedHostnameVerifier; } diff --git a/app/src/main/java/github/daneren2005/dsub/service/RemoteController.java b/app/src/main/java/github/daneren2005/dsub/service/RemoteController.java index c6702e3e..617144d7 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/RemoteController.java +++ b/app/src/main/java/github/daneren2005/dsub/service/RemoteController.java @@ -117,7 +117,8 @@ public abstract class RemoteController { protected WebProxy createWebProxy() { MusicService musicService = MusicServiceFactory.getMusicService(downloadService); if(musicService instanceof CachedMusicService) { - return new WebProxy(downloadService, ((CachedMusicService)musicService).getMusicService().getHostNameVerifier()); + RESTMusicService restMusicService = ((CachedMusicService)musicService).getMusicService(); + return new WebProxy(downloadService, restMusicService.getSSLSocketFactory(), restMusicService.getHostNameVerifier()); } else { return new WebProxy(downloadService); } -- cgit v1.2.3 From cb3c7803ce51e6ebf86b83c4b10df47f0c2cc6a8 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Mon, 17 Oct 2016 17:08:17 -0700 Subject: Change Show all media -> Show all songs --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/src') diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9ac609c3..0d9c2aa1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -103,7 +103,7 @@ Add Channel Keep Synced Stop syncing - Show all media + Show all songs Show Artist Share Delete Cache -- cgit v1.2.3 From 862e767d938c1a3e574a8ecf58d9e7f0c83786f9 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Mon, 17 Oct 2016 17:08:34 -0700 Subject: Second beta released with self-signed cert fixes --- app/build.gradle | 4 ++-- app/src/main/res/xml/changelog.xml | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'app/src') diff --git a/app/build.gradle b/app/build.gradle index 92dfa731..47b3df01 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { applicationId "github.daneren2005.dsub" minSdkVersion 14 targetSdkVersion 23 - versionCode 188 - versionName '5.3.2 BETA' + versionCode 189 + versionName '5.3.2 BETA 2' setProperty("archivesBaseName", "DSub $versionName") resConfigs "de", "es", "fr", "hu", "nl", "pt-rPT", "ru", "sv" } diff --git a/app/src/main/res/xml/changelog.xml b/app/src/main/res/xml/changelog.xml index 7abec09c..b1dd5930 100644 --- a/app/src/main/res/xml/changelog.xml +++ b/app/src/main/res/xml/changelog.xml @@ -1,5 +1,11 @@ + + Fix errors with Self Signed Certificates + Fix error following redirect from http to https + Fix Show all media sometimes failing + Show album instead of artist for Show all media + Add support for casting Internet Radio to ChromeCast/DLNA Add support for Play Title by Artist from Google Search -- cgit v1.2.3 From 7e27f21daed8d79b0f582d26d1380e3967b1f7ac Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Wed, 19 Oct 2016 17:15:14 -0700 Subject: Clear tint cache on exit so we can fix if bad colors get stuck --- .../java/github/daneren2005/dsub/activity/SubsonicActivity.java | 2 +- .../daneren2005/dsub/activity/SubsonicFragmentActivity.java | 8 ++------ app/src/main/java/github/daneren2005/dsub/util/DrawableTint.java | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) (limited to 'app/src') diff --git a/app/src/main/java/github/daneren2005/dsub/activity/SubsonicActivity.java b/app/src/main/java/github/daneren2005/dsub/activity/SubsonicActivity.java index 9b14f4f6..dcf966a8 100644 --- a/app/src/main/java/github/daneren2005/dsub/activity/SubsonicActivity.java +++ b/app/src/main/java/github/daneren2005/dsub/activity/SubsonicActivity.java @@ -245,7 +245,7 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte if (theme != null && !theme.equals(ThemeUtil.getTheme(this)) || fullScreen != prefs.getBoolean(Constants.PREFERENCES_KEY_FULL_SCREEN, false) || actionbarColored != prefs.getBoolean(Constants.PREFERENCES_KEY_COLOR_ACTION_BAR, true)) { restart(); overridePendingTransition(R.anim.fade_in, R.anim.fade_out); - DrawableTint.wipeTintCache(); + DrawableTint.clearCache(); } populateTabs(); 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 e37c6d49..fb8221c8 100644 --- a/app/src/main/java/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java +++ b/app/src/main/java/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java @@ -28,7 +28,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.res.TypedArray; import android.os.Bundle; -import android.os.Handler; import android.preference.PreferenceManager; import android.provider.MediaStore; import android.support.v4.app.FragmentManager; @@ -38,8 +37,6 @@ import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.MenuItem; import android.view.View; -import android.view.ViewGroup; -import android.widget.CheckBox; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; @@ -49,9 +46,6 @@ import com.sothree.slidinguppanel.SlidingUpPanelLayout; import java.io.File; import java.util.Date; import java.util.List; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; import github.daneren2005.dsub.R; import github.daneren2005.dsub.domain.MusicDirectory; @@ -78,6 +72,7 @@ import github.daneren2005.dsub.service.MusicService; import github.daneren2005.dsub.service.MusicServiceFactory; import github.daneren2005.dsub.updates.Updater; import github.daneren2005.dsub.util.Constants; +import github.daneren2005.dsub.util.DrawableTint; import github.daneren2005.dsub.util.FileUtil; import github.daneren2005.dsub.util.SilentBackgroundTask; import github.daneren2005.dsub.util.UserUtil; @@ -139,6 +134,7 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo stopService(new Intent(this, DownloadService.class)); finish(); getImageLoader().clearCache(); + DrawableTint.clearCache(); } else if(getIntent().hasExtra(Constants.INTENT_EXTRA_NAME_DOWNLOAD_VIEW)) { getIntent().putExtra(Constants.INTENT_EXTRA_FRAGMENT_TYPE, "Download"); lastSelectedPosition = R.id.drawer_downloading; diff --git a/app/src/main/java/github/daneren2005/dsub/util/DrawableTint.java b/app/src/main/java/github/daneren2005/dsub/util/DrawableTint.java index cc8e241d..f03906a8 100644 --- a/app/src/main/java/github/daneren2005/dsub/util/DrawableTint.java +++ b/app/src/main/java/github/daneren2005/dsub/util/DrawableTint.java @@ -95,7 +95,7 @@ public class DrawableTint { return getTintedDrawable(context, drawableRes, colorAttr); } - public static void wipeTintCache() { + public static void clearCache() { attrMap.clear(); tintedDrawables.clear(); } -- cgit v1.2.3 From f9894f5ff28cdd166f64ba6d1fe6bee59b6dcc6d Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Wed, 19 Oct 2016 17:22:22 -0700 Subject: Remove debug timeout --- app/src/main/java/github/daneren2005/dsub/util/SongDBHandler.java | 1 - 1 file changed, 1 deletion(-) (limited to 'app/src') diff --git a/app/src/main/java/github/daneren2005/dsub/util/SongDBHandler.java b/app/src/main/java/github/daneren2005/dsub/util/SongDBHandler.java index 90c885f6..1309ee69 100644 --- a/app/src/main/java/github/daneren2005/dsub/util/SongDBHandler.java +++ b/app/src/main/java/github/daneren2005/dsub/util/SongDBHandler.java @@ -120,7 +120,6 @@ public class SongDBHandler extends SQLiteOpenHelper { values.put(SONGS_SERVER_KEY, serverKey); values.put(SONGS_SERVER_ID, entry.getFirst()); values.put(SONGS_COMPLETE_PATH, entry.getSecond()); - // Util.sleepQuietly(10000); db.insertWithOnConflict(TABLE_SONGS, null, values, SQLiteDatabase.CONFLICT_IGNORE); } -- cgit v1.2.3 From b7e728dc0788a23af2eecb9f5870c3c612aa50a7 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Fri, 21 Oct 2016 18:13:59 -0700 Subject: Fix for Chromecast issues. Cast API appears to have changed so starting a new song triggers an additional COMPLETED broadcast while it is getting the new song ready --- .../java/github/daneren2005/dsub/service/ChromeCastController.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'app/src') diff --git a/app/src/main/java/github/daneren2005/dsub/service/ChromeCastController.java b/app/src/main/java/github/daneren2005/dsub/service/ChromeCastController.java index 566ed773..f9e2bfb1 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/ChromeCastController.java +++ b/app/src/main/java/github/daneren2005/dsub/service/ChromeCastController.java @@ -23,6 +23,7 @@ import android.util.Log; import com.google.android.gms.cast.ApplicationMetadata; import com.google.android.gms.cast.Cast; import com.google.android.gms.cast.CastDevice; +import com.google.android.gms.cast.CastStatusCodes; import com.google.android.gms.cast.MediaInfo; import com.google.android.gms.cast.MediaMetadata; import com.google.android.gms.cast.MediaStatus; @@ -343,6 +344,8 @@ public class ChromeCastController extends RemoteController { public void onResult(RemoteMediaPlayer.MediaChannelResult result) { if (result.getStatus().isSuccess()) { // Handled in other handler + } else if(result.getStatus().getStatusCode() == CastStatusCodes.REPLACED) { + Log.w(TAG, "Request was replaced: " + currentPlaying.toString()); } else { Log.e(TAG, "Failed to load: " + result.getStatus().toString()); failedLoad(); @@ -459,7 +462,9 @@ public class ChromeCastController extends RemoteController { break; case MediaStatus.PLAYER_STATE_IDLE: if (mediaStatus.getIdleReason() == MediaStatus.IDLE_REASON_FINISHED) { - downloadService.onSongCompleted(); + if(downloadService.getPlayerState() != PlayerState.PREPARING) { + downloadService.onSongCompleted(); + } } else if (mediaStatus.getIdleReason() == MediaStatus.IDLE_REASON_INTERRUPTED) { if (downloadService.getPlayerState() != PlayerState.PREPARING) { downloadService.setPlayerState(PlayerState.PREPARING); -- cgit v1.2.3 From bb248548d25a14d695b48051d8df79422c398878 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Fri, 21 Oct 2016 18:16:12 -0700 Subject: Ask for location permissions in support for the day/night theme --- app/src/main/AndroidManifest.xml | 1 + .../github/daneren2005/dsub/activity/SubsonicActivity.java | 3 ++- .../github/daneren2005/dsub/fragments/SettingsFragment.java | 12 ++++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) (limited to 'app/src') diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 65305cbd..2cb24272 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -23,6 +23,7 @@ + diff --git a/app/src/main/java/github/daneren2005/dsub/activity/SubsonicActivity.java b/app/src/main/java/github/daneren2005/dsub/activity/SubsonicActivity.java index dcf966a8..3b533fae 100644 --- a/app/src/main/java/github/daneren2005/dsub/activity/SubsonicActivity.java +++ b/app/src/main/java/github/daneren2005/dsub/activity/SubsonicActivity.java @@ -95,7 +95,8 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte protected static boolean actionbarColored; private static final int MENU_GROUP_SERVER = 10; private static final int MENU_ITEM_SERVER_BASE = 100; - private static final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1; + public static final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1; + public static final int PERMISSIONS_REQUEST_LOCATION = 2; private final List afterServiceAvailable = new ArrayList<>(); private boolean drawerIdle = true; diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SettingsFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SettingsFragment.java index d5ba25f5..584a205a 100644 --- a/app/src/main/java/github/daneren2005/dsub/fragments/SettingsFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/fragments/SettingsFragment.java @@ -15,12 +15,14 @@ package github.daneren2005.dsub.fragments; +import android.Manifest; import android.accounts.Account; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.preference.CheckBoxPreference; @@ -29,6 +31,8 @@ import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceCategory; import android.preference.PreferenceScreen; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; import android.text.InputType; import android.util.Log; import android.view.LayoutInflater; @@ -47,6 +51,7 @@ import java.util.LinkedHashMap; import java.util.Map; import github.daneren2005.dsub.R; +import github.daneren2005.dsub.activity.SubsonicActivity; import github.daneren2005.dsub.service.DownloadService; import github.daneren2005.dsub.service.HeadphoneListenerService; import github.daneren2005.dsub.service.MusicService; @@ -188,6 +193,13 @@ public class SettingsFragment extends PreferenceCompatFragment implements Shared } else { context.stopService(serviceIntent); } + } else if(Constants.PREFERENCES_KEY_THEME.equals(key)) { + String value = sharedPreferences.getString(key, null); + if("day/night".equals(value) || "day/black".equals(value)) { + if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(context, new String[]{ Manifest.permission.ACCESS_COARSE_LOCATION }, SubsonicActivity.PERMISSIONS_REQUEST_LOCATION); + } + } } scheduleBackup(); -- cgit v1.2.3 From 2ed855cd6d14eacd77a578f4d8ac8c80ebe516de Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Sat, 22 Oct 2016 14:13:25 -0700 Subject: DSub 5.3.2 released --- app/build.gradle | 4 ++-- app/src/main/res/xml/changelog.xml | 14 ++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) (limited to 'app/src') diff --git a/app/build.gradle b/app/build.gradle index e6581796..5f46aab8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { applicationId "github.daneren2005.dsub" minSdkVersion 14 targetSdkVersion 23 - versionCode 189 - versionName '5.3.2 BETA 2' + versionCode 190 + versionName '5.3.2' setProperty("archivesBaseName", "DSub $versionName") resConfigs "de", "es", "fr", "hu", "nl", "pt-rPT", "ru", "sv" } diff --git a/app/src/main/res/xml/changelog.xml b/app/src/main/res/xml/changelog.xml index b1dd5930..dc77a5a3 100644 --- a/app/src/main/res/xml/changelog.xml +++ b/app/src/main/res/xml/changelog.xml @@ -1,16 +1,14 @@ - - Fix errors with Self Signed Certificates - Fix error following redirect from http to https - Fix Show all media sometimes failing - Show album instead of artist for Show all media - - + Add support for casting Internet Radio to ChromeCast/DLNA Add support for Play Title by Artist from Google Search - Use Google Play SSL Move to more modern connection framework + Use Google Play SSL + Show album instead of artist for Show all media + Ask for location permissions for Day/Night themes + Fix a change to the ChromeCast API + Fix Show all media sometimes failing Fix Internet Radio streams which point to playlists -- cgit v1.2.3 From 2b4eb81c25e6170e881c8074672d4f2ae0226f4f Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Sat, 22 Oct 2016 14:37:07 -0700 Subject: Fix location permission blocking install on devices without GPS --- app/src/main/AndroidManifest.xml | 2 ++ 1 file changed, 2 insertions(+) (limited to 'app/src') diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2cb24272..37643a21 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -30,6 +30,8 @@ + + -- cgit v1.2.3