diff options
author | Scott Jackson <daneren2005@gmail.com> | 2016-10-22 14:37:14 -0700 |
---|---|---|
committer | Scott Jackson <daneren2005@gmail.com> | 2016-10-22 14:37:14 -0700 |
commit | 61979e9893ce4bd5e4fdee1fc3349a04ccc7a8a7 (patch) | |
tree | d23db8f707e67507f2b43359544f9f635a816f37 /app/src/main | |
parent | 2b973ff6571e565bb6752be616439dbe26b2ce88 (diff) | |
parent | 2b4eb81c25e6170e881c8074672d4f2ae0226f4f (diff) | |
download | dsub-61979e9893ce4bd5e4fdee1fc3349a04ccc7a8a7.tar.gz dsub-61979e9893ce4bd5e4fdee1fc3349a04ccc7a8a7.tar.bz2 dsub-61979e9893ce4bd5e4fdee1fc3349a04ccc7a8a7.zip |
Merge branch 'edge'
Diffstat (limited to 'app/src/main')
46 files changed, 640 insertions, 1458 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 65305cbd..37643a21 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -23,12 +23,15 @@ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> + <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-feature android:name="android.hardware.touchscreen" android:required="false" /> <uses-feature android:name="android.hardware.bluetooth" android:required="false" /> <uses-feature android:name="android.hardware.microphone" android:required="false" /> <uses-feature android:name="android.hardware.wifi" android:required="false" /> <uses-feature android:name="android.software.leanback" android:required="false"/> + <uses-feature android:name="android.hardware.location" android:required="false"/> + <uses-feature android:name="android.hardware.location.network" android:required="false"/> <supports-screens android:anyDensity="true" android:xlargeScreens="true" android:largeScreens="true" android:normalScreens="true" android:smallScreens="true"/> 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..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<Runnable> afterServiceAvailable = new ArrayList<>(); private boolean drawerIdle = true; @@ -245,7 +246,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 c7190046..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,8 +28,8 @@ 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; import android.support.v4.app.FragmentTransaction; import android.support.v7.app.AlertDialog; @@ -37,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; @@ -48,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; @@ -77,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; @@ -138,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; @@ -402,9 +399,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<Serializable> 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<Serializable> 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<MusicDirectory.Entry> getSelectedEntries() { + protected List<Entry> getSelectedEntries() { List<Serializable> selected = adapter.getSelected(); - List<MusicDirectory.Entry> selectedMedia = new ArrayList<>(); + List<Entry> 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<Integer, Entry> tree = new TreeMap<>(); + for(Entry song: searchResult.getSongs()) { + tree.put(Util.getStringDistance(song.getTitle().toLowerCase(), titleQuery), song); + } + + Map.Entry<Integer, 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<Integer, Entry> tree = new TreeMap<>(); + for(Entry album: searchResult.getAlbums()) { + tree.put(Util.getStringDistance(album.getTitle().toLowerCase(), albumQuery), album); + } + + Map.Entry<Integer, 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<Integer, Artist> tree = new TreeMap<>(); + for(Artist artist: searchResult.getArtists()) { + tree.put(Util.getStringDistance(artist.getName().toLowerCase(), artistQuery), artist); + } + Map.Entry<Integer, Artist> 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); 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..d3a0bfe8 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<Entry> songs = new ArrayList<Entry>(); 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<Entry> songs) throws Exception { @@ -733,7 +737,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section if(!artist) { entryGridAdapter.setShowArtist(true); } - if(topTracks) { + if(topTracks || showAll) { entryGridAdapter.setShowAlbum(true); } @@ -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 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<I connection.disconnect(); } } catch (Exception e) { - Log.e(TAG, "Failed to get stream data from playlist"); + Log.e(TAG, "Failed to get stream data from playlist", e); } } 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(); 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..de230309 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 @@ -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(); 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 00fb4624..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; @@ -733,11 +732,16 @@ 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 - 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); } @@ -1157,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 @@ -1188,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/ChromeCastController.java b/app/src/main/java/github/daneren2005/dsub/service/ChromeCastController.java index 2df290cf..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; @@ -66,17 +67,12 @@ public class ChromeCastController extends RemoteController { private boolean isStopping = false; private Runnable afterUpdateComplete = null; - private ServerProxy proxy; - private String rootLocation; private RemoteMediaPlayer mediaPlayer; private double gain = 0.5; public ChromeCastController(DownloadService downloadService, CastDevice castDevice) { - this.downloadService = downloadService; + super(downloadService); this.castDevice = castDevice; - - SharedPreferences prefs = Util.getPreferences(downloadService); - rootLocation = prefs.getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, null); } @Override @@ -287,49 +283,7 @@ public class ChromeCastController extends RemoteController { try { MusicService musicService = MusicServiceFactory.getMusicService(downloadService); - String url; - // Offline, use file proxy - 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(currentPlaying.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(), currentPlaying.getBitRate(), downloadService); - } else { - url = musicService.getMusicUrl(downloadService, song, currentPlaying.getBitRate()); - } - - // If proxy is going, it is a WebProxy - if(proxy != null) { - url = proxy.getPublicAddress(url); - } - } + String url = getStreamUrl(musicService, currentPlaying); // Setup song/video information MediaMetadata meta = new MediaMetadata(song.isVideo() ? MediaMetadata.MEDIA_TYPE_MOVIE : MediaMetadata.MEDIA_TYPE_MUSIC_TRACK); @@ -390,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(); @@ -506,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); diff --git a/app/src/main/java/github/daneren2005/dsub/service/DLNAController.java b/app/src/main/java/github/daneren2005/dsub/service/DLNAController.java index 24890057..143be330 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/DLNAController.java +++ b/app/src/main/java/github/daneren2005/dsub/service/DLNAController.java @@ -83,9 +83,6 @@ public class DLNAController extends RemoteController { SubscriptionCallback callback; boolean supportsSeek = false; boolean supportsSetupNext = false; - - private ServerProxy proxy; - String rootLocation = ""; boolean error = false; final AtomicLong lastUpdate = new AtomicLong(); @@ -108,12 +105,9 @@ public class DLNAController extends RemoteController { }; public DLNAController(DownloadService downloadService, ControlPoint controlPoint, DLNADevice device) { - this.downloadService = downloadService; + super(downloadService); this.controlPoint = controlPoint; this.device = device; - - SharedPreferences prefs = Util.getPreferences(downloadService); - rootLocation = prefs.getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, null); nextSupported = true; } @@ -473,49 +467,7 @@ public class DLNAController extends RemoteController { // Get url for entry MusicService musicService = MusicServiceFactory.getMusicService(downloadService); - String url; - // In offline mode or playing offline song - 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); - } - } + String url = getStreamUrl(musicService, downloadFile); // Create metadata for entry Item track; @@ -585,7 +537,7 @@ public class DLNAController extends RemoteController { Log.w(TAG, "Metadata generation failed", e); } - return new Pair<String, String>(url, metadata); + return new Pair<>(url, metadata); } private void failedLoad() { 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/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/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/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 6ccf562c..913e30bf 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java +++ b/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java @@ -24,52 +24,25 @@ 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; import java.util.Collections; +import java.util.HashMap; 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; -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.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 com.google.android.gms.security.ProviderInstaller; + import github.daneren2005.dsub.R; import github.daneren2005.dsub.domain.*; import github.daneren2005.dsub.fragments.MainFragment; @@ -100,9 +73,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.BackgroundTask; import github.daneren2005.dsub.util.Pair; import github.daneren2005.dsub.util.SilentBackgroundTask; import github.daneren2005.dsub.util.Constants; @@ -111,19 +81,23 @@ 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; -/** - * @author Sindre Mehus - */ +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 { 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_RANDOM_SONGS = 60 * 1000; private static final int SOCKET_READ_TIMEOUT_GET_PLAYLIST = 60 * 1000; // Allow 20 seconds extra timeout per MB offset. @@ -132,51 +106,46 @@ 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 final DefaultHttpClient httpClient; + private SSLSocketFactory sslSocketFactory; + private HostnameVerifier selfSignedHostnameVerifier; private long redirectionLastChecked; private int redirectionNetworkType = -1; private String redirectFrom; private String redirectTo; - private final ThreadSafeClientConnManager connManager; private Integer instance; + 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) { + } - // 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(); - } + selfSignedHostnameVerifier = new HostnameVerifier() { + public boolean verify(String hostname, SSLSession session) { + return true; + } + }; } @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 { @@ -187,7 +156,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(); @@ -197,7 +166,7 @@ public class RESTMusicService implements MusicService { } public List<MusicFolder> 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 { @@ -207,7 +176,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 { @@ -217,7 +186,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) { @@ -244,7 +213,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 { @@ -290,7 +259,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 { @@ -300,7 +269,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 { @@ -310,7 +279,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 { @@ -334,7 +303,7 @@ public class RESTMusicService implements MusicService { private SearchResult searchOld(SearchCritera critera, Context context, ProgressListener progressListener) throws Exception { List<String> parameterNames = Arrays.asList("any", "songCount"); List<Object> parameterValues = Arrays.<Object>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 { @@ -366,7 +335,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 { @@ -376,10 +345,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, SOCKET_READ_TIMEOUT_GET_PLAYLIST); try { return new PlaylistParser(context, getInstance(context)).parse(reader, progressListener); } finally { @@ -389,7 +355,7 @@ public class RESTMusicService implements MusicService { @Override public List<Playlist> 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 { @@ -415,7 +381,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 { @@ -425,7 +391,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 { @@ -444,7 +410,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 { @@ -463,7 +429,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 { @@ -488,7 +454,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 { @@ -499,7 +465,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.<Object>asList(id, name, comment, pub)); + Reader reader = getReader(context, progressListener, "updatePlaylist", Arrays.asList("playlistId", "name", "comment", "public"), Arrays.<Object>asList(id, name, comment, pub)); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -509,7 +475,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.<Object>asList(artist, title)); + Reader reader = getReader(context, progressListener, "getLyrics", Arrays.asList("artist", "title"), Arrays.<Object>asList(artist, title)); try { return new LyricsParser(context, getInstance(context)).parse(reader, progressListener); } finally { @@ -528,10 +494,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.<Object>asList(id, submission, time)); + reader = getReader(context, progressListener, "scrobble", Arrays.asList("id", "submission", "time"), Arrays.<Object>asList(id, submission, time)); } else - reader = getReader(context, progressListener, "scrobble", null, Arrays.asList("id", "submission"), Arrays.<Object>asList(id, submission)); + reader = getReader(context, progressListener, "scrobble", Arrays.asList("id", "submission"), Arrays.<Object>asList(id, submission)); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -572,7 +538,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 { @@ -638,7 +604,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 { @@ -674,7 +640,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 { @@ -713,7 +679,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 { @@ -747,7 +713,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 { @@ -757,10 +723,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<String> names = new ArrayList<String>(); + List<String> names = new ArrayList<String>(); List<Object> values = new ArrayList<Object>(); names.add("size"); @@ -799,7 +762,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 { @@ -829,87 +792,24 @@ 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) { + String url = getRestUrl(context, "getCoverArt"); + List<String> parameterNames = Arrays.asList("id"); + List<Object> parameterValues = Arrays.<Object>asList(entry.getCoverArt()); - // Use cached file, if existing. - Bitmap bitmap = FileUtil.getAlbumArtBitmap(context, entry, size); - if (bitmap != null) { - return bitmap; - } - - String url = getRestUrl(context, "getCoverArt"); - - InputStream in = null; - try { - List<String> parameterNames = Arrays.asList("id"); - List<Object> parameterValues = Arrays.<Object>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<Header> headers = new ArrayList<Header>(); - if (offset > 0) { - headers.add(new BasicHeader("Range", "bytes=" + offset + "-")); - } - List<String> parameterNames = new ArrayList<String>(); parameterNames.add("id"); parameterNames.add("maxBitRate"); - List<Object> parameterValues = new ArrayList<Object>(); + List<Object> parameterValues = new ArrayList<>(); parameterValues.add(song.getId()); parameterValues.add(maxBitrate); @@ -929,24 +829,32 @@ 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); + // Add "Range" header if offset is given + Map<String, String> headers = new HashMap<>(); + if (offset > 0) { + 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, headers, 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 @@ -1057,7 +965,7 @@ public class RESTMusicService implements MusicService { private RemoteStatus executeJukeboxCommand(Context context, ProgressListener progressListener, List<String> parameterNames, List<Object> 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 { @@ -1096,7 +1004,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 { @@ -1108,7 +1016,7 @@ public class RESTMusicService implements MusicService { public List<Share> 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 { @@ -1136,7 +1044,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); } @@ -1149,16 +1057,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<String> parameterNames = new ArrayList<String>(); List<Object> parameterValues = new ArrayList<Object>(); 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); @@ -1172,9 +1077,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<String> parameterNames = new ArrayList<String>(); List<Object> parameterValues = new ArrayList<Object>(); @@ -1189,7 +1091,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); } @@ -1202,16 +1104,13 @@ public class RESTMusicService implements MusicService { public List<ChatMessage> 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<String> parameterNames = new ArrayList<String>(); List<Object> parameterValues = new ArrayList<Object>(); 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); @@ -1224,16 +1123,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<String> parameterNames = new ArrayList<String>(); List<Object> parameterValues = new ArrayList<Object>(); 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); @@ -1246,7 +1142,7 @@ public class RESTMusicService implements MusicService { public List<Genre> 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 { @@ -1258,9 +1154,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<String> parameterNames = new ArrayList<String>(); List<Object> parameterValues = new ArrayList<Object>(); @@ -1281,7 +1174,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 { @@ -1300,7 +1193,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 { @@ -1312,7 +1205,7 @@ public class RESTMusicService implements MusicService { public List<PodcastChannel> 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.<Object>asList("false")); + Reader reader = getReader(context, progressListener, "getPodcasts", Arrays.asList("includeEpisodes"), Arrays.<Object>asList("false")); try { List<PodcastChannel> channels = new PodcastChannelParser(context, getInstance(context)).parse(reader, progressListener); @@ -1334,7 +1227,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.<Object>asList(id)); + Reader reader = getReader(context, progressListener, "getPodcasts", Arrays.asList("id"), Arrays.<Object>asList(id)); try { return new PodcastEntryParser(context, getInstance(context)).parse(id, reader, progressListener); } finally { @@ -1344,7 +1237,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.<Object>asList(count), true); + Reader reader = getReader(context, progressListener, "getNewestPodcasts", Arrays.asList("count"), Arrays.<Object>asList(count), true); try { return new PodcastEntryParser(context, getInstance(context)).parse(null, reader, progressListener); @@ -1357,7 +1250,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 { @@ -1369,7 +1262,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 { @@ -1381,7 +1274,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 { @@ -1393,7 +1286,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 { @@ -1405,7 +1298,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 { @@ -1417,7 +1310,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.<Object>asList(entry.getId(), rating)); + Reader reader = getReader(context, progressListener, "setRating", Arrays.asList("id", "rating"), Arrays.<Object>asList(entry.getId(), rating)); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -1429,7 +1322,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 { @@ -1441,7 +1334,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.<Object>asList(entry.getId(), position, comment)); + Reader reader = getReader(context, progressListener, "createBookmark", Arrays.asList("id", "position", "comment"), Arrays.<Object>asList(entry.getId(), position, comment)); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -1453,7 +1346,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.<Object>asList(entry.getId())); + Reader reader = getReader(context, progressListener, "deleteBookmark", Arrays.asList("id"), Arrays.<Object>asList(entry.getId())); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -1463,7 +1356,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.<Object>asList(username)); + Reader reader = getReader(context, progressListener, "getUser", Arrays.asList("username"), Arrays.<Object>asList(username)); try { List<User> users = new UserParser(context, getInstance(context)).parse(reader, progressListener); if(users.size() > 0) { @@ -1481,7 +1374,7 @@ public class RESTMusicService implements MusicService { public List<User> 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 { @@ -1515,7 +1408,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 { @@ -1549,7 +1442,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 { @@ -1559,7 +1452,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.<Object>asList(username)); + Reader reader = getReader(context, progressListener, "deleteUser", Arrays.asList("username"), Arrays.<Object>asList(username)); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -1569,7 +1462,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.<Object>asList(username, email)); + Reader reader = getReader(context, progressListener, "updateUser", Arrays.asList("username", "email"), Arrays.<Object>asList(username, email)); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -1579,7 +1472,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.<Object>asList(username, password)); + Reader reader = getReader(context, progressListener, "changePassword", Arrays.asList("username", "password"), Arrays.<Object>asList(username, password)); try { new ErrorParser(context, getInstance(context)).parse(reader); } finally { @@ -1597,55 +1490,11 @@ 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 - { - List<String> parameterNames; - List<Object> parameterValues; - - parameterNames = Collections.singletonList("username"); - parameterValues = Arrays.<Object>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<String> parameterNames = Collections.singletonList("username"); + List<Object> parameterValues = Arrays.<Object>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); } } @@ -1665,7 +1514,7 @@ public class RESTMusicService implements MusicService { method = "getArtistInfo"; } - Reader reader = getReader(context, progressListener, method, null, Arrays.asList("id", "includeNotPresent"), Arrays.<Object>asList(id, "true")); + Reader reader = getReader(context, progressListener, method, Arrays.asList("id", "includeNotPresent"), Arrays.<Object>asList(id, "true")); try { return new ArtistInfoParser(context, getInstance(context)).parse(reader, progressListener); } finally { @@ -1677,53 +1526,13 @@ 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); - 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); } } @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 { @@ -1747,7 +1556,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 { @@ -1757,7 +1566,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 { @@ -1769,7 +1578,7 @@ public class RESTMusicService implements MusicService { public List<InternetRadioStation> 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 { @@ -1889,210 +1698,237 @@ 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); + protected Bitmap getBitmapFromUrl(Context context, String url, List<String> parameterNames, List<Object> 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); + } } - private Reader getReader(Context context, ProgressListener progressListener, String method, HttpParams requestParams, boolean throwsError) throws Exception { - return getReader(context, progressListener, method, requestParams, Collections.<String>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.<Object>asList(parameterValue)); - } + // 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<String>)null, null); + } - private Reader getReader(Context context, ProgressListener progressListener, String method, - HttpParams requestParams, List<String> parameterNames, List<Object> parameterValues) throws Exception { - return getReader(context, progressListener, method, requestParams, parameterNames, parameterValues, false); + 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, - HttpParams requestParams, List<String> parameterNames, List<Object> parameterValues, boolean throwErrors) throws Exception { + 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<String> parameterNames, List<Object> parameterValues) throws Exception { + return getReader(context, progressListener, method, parameterNames, parameterValues, 0); + } + private Reader getReader(Context context, ProgressListener progressListener, String method, List<String> parameterNames, List<Object> parameterValues, int minNetworkTimeout) throws Exception { + return getReader(context, progressListener, method, parameterNames, parameterValues, minNetworkTimeout, false); + } + private Reader getReader(Context context, ProgressListener progressListener, String method, List<String> parameterNames, List<Object> parameterValues, boolean throwErrors) throws Exception { + return getReader(context, progressListener, method, parameterNames, parameterValues, 0, throwErrors); + } + private Reader getReader(Context context, ProgressListener progressListener, String method, List<String> parameterNames, List<Object> 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, requestParams, parameterNames, parameterValues, progressListener, throwErrors); + return getReaderForURL(context, url, parameterNames, parameterValues, minNetworkTimeout, progressListener, throwErrors); } - private Reader getReaderForURL(Context context, String url, HttpParams requestParams, List<String> parameterNames, - List<Object> parameterValues, ProgressListener progressListener) throws Exception { - return getReaderForURL(context, url, requestParams, parameterNames, parameterValues, progressListener, true); + private Reader getReaderForURL(Context context, String url, List<String> parameterNames, List<Object> parameterValues, ProgressListener progressListener) throws Exception { + return getReaderForURL(context, url, parameterNames, parameterValues, progressListener, true); } - private Reader getReaderForURL(Context context, String url, HttpParams requestParams, List<String> parameterNames, - List<Object> 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<String> parameterNames, List<Object> parameterValues, ProgressListener progressListener, boolean throwErrors) throws Exception { + return getReaderForURL(context, url, parameterNames, parameterValues, 0, progressListener, throwErrors); + } + private Reader getReaderForURL(Context context, String url, List<String> parameterNames, List<Object> 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); + } - 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<String> parameterNames, List<Object> parameterValues, ProgressListener progressListener, boolean throwsErrors) throws Exception { + return getInputStream(context, url, parameterNames, parameterValues, 0, progressListener, throwsErrors); + } + private InputStream getInputStream(Context context, String url, List<String> parameterNames, List<Object> 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); } - return new InputStreamReader(in, Constants.UTF_8); - } - private HttpEntity getEntityForURL(Context context, String url, HttpParams requestParams, List<String> parameterNames, - List<Object> parameterValues, ProgressListener progressListener, boolean throwErrors) throws Exception { + return in; + } - return getEntityForURL(context, url, requestParams, parameterNames, parameterValues, progressListener, null, throwErrors); + private HttpURLConnection getConnection(Context context, String url, List<String> parameterNames, List<Object> parameterValues, Map<String, String> headers, int minNetworkTimeout) throws Exception { + return getConnection(context, url, parameterNames, parameterValues, headers, minNetworkTimeout, null, true); } + private HttpURLConnection getConnection(Context context, String url, List<String> parameterNames, List<Object> parameterValues, ProgressListener progressListener, boolean throwErrors) throws Exception { + return getConnection(context, url, parameterNames, parameterValues, 0, progressListener, throwErrors); + } + private HttpURLConnection getConnection(Context context, String url, List<String> parameterNames, List<Object> 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<String> parameterNames, List<Object> parameterValues, Map<String, String> headers, int minNetworkTimeout, ProgressListener progressListener, boolean throwErrors) throws Exception { + if(throwErrors) { + 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, headers, minNetworkTimeout, progressListener, HTTP_REQUEST_MAX_ATTEMPTS, 0); + } + } + + private HttpURLConnection getConnection(Context context, String url, List<String> parameterNames, List<Object> parameterValues, Map<String, String> 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, headers, minNetworkTimeout); + } 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); + } - private HttpEntity getEntityForURL(Context context, String url, HttpParams requestParams, List<String> parameterNames, - List<Object> parameterValues, ProgressListener progressListener, SilentBackgroundTask task) throws Exception { - return getResponseForURL(context, url, requestParams, parameterNames, parameterValues, null, progressListener, task, false).getEntity(); + Log.w(TAG, "Got IOException " + x + " (" + attempts + "), will retry"); + Thread.sleep(2000L); + + minNetworkTimeout = (int) (minNetworkTimeout * 1.3); + return getConnection(context, url, parameterNames, parameterValues, headers, minNetworkTimeout, progressListener, retriesLeft, attempts); + } else { + throw x; + } + } } - private HttpEntity getEntityForURL(Context context, String url, HttpParams requestParams, List<String> parameterNames, - List<Object> 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<String> parameterNames, List<Object> parameterValues, - List<Header> 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("="); + private HttpURLConnection getConnectionDirect(Context context, String url, List<String> parameterNames, List<Object> parameterValues, Map<String, String> headers, int minNetworkTimeout) 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(); - parameterNames = null; - parameterValues = null; - } + builder.append(part); + } + url = builder.toString(); + } - String rewrittenUrl = rewriteUrlWithRedirect(context, url); - return executeWithRetry(context, rewrittenUrl, url, requestParams, parameterNames, parameterValues, headers, progressListener, task, throwsErrors); - } + // Rewrite url based on redirects + String rewrittenUrl = rewriteUrlWithRedirect(context, url); + if(rewrittenUrl.indexOf("scanstatus") == -1) { + Log.i(TAG, stripUrlInfo(rewrittenUrl)); + } - private HttpResponse executeWithRetry(final Context context, String url, String originalUrl, HttpParams requestParams, - List<String> parameterNames, List<Object> parameterValues, - List<Header> 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)); + return getConnectionDirect(context, rewrittenUrl, headers, minNetworkTimeout); + } + + private HttpURLConnection getConnectionDirect(Context context, String url, Map<String, String> headers, 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; } - 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<Boolean> isCancelled = new AtomicReference<Boolean>(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<Void>(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<NameValuePair> params = new ArrayList<NameValuePair>(); - 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"); + // 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 + connection.setConnectTimeout(minNetworkTimeout); + connection.setReadTimeout(minNetworkTimeout); + + // Add headers + if(headers != null) { + for(Map.Entry<String, String> header: headers.entrySet()) { + connection.setRequestProperty(header.getKey(), header.getValue()); } - 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)); - } - } - } + if(connection instanceof HttpsURLConnection) { + HttpsURLConnection sslConnection = (HttpsURLConnection) connection; + sslConnection.setSSLSocketFactory(sslSocketFactory); + sslConnection.setHostnameVerifier(selfSignedHostnameVerifier); + } - 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(); + // Force the connection to initiate + if(connection.getResponseCode() >= 500) { + throw new IOException("Error code: " + connection.getResponseCode()); + } + if(detectRedirect(context, urlObj, connection)) { + String rewrittenUrl = rewriteUrlWithRedirect(context, url); + if(!rewrittenUrl.equals(url)) { + connection.disconnect(); + return getConnectionDirect(context, rewrittenUrl, headers, minNetworkTimeout); + } } + return connection; + } + + // 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()); + } + 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/"); } @@ -2109,7 +1945,7 @@ public class RESTMusicService implements MusicService { redirectionLastChecked = System.currentTimeMillis(); redirectionNetworkType = getCurrentNetworkType(context); } - } + } private String rewriteUrlWithRedirect(Context context, String url) { @@ -2158,7 +1994,10 @@ public class RESTMusicService implements MusicService { } } - public HttpClient getHttpClient() { - return httpClient; + 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 6c70496d..617144d7 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(); @@ -105,9 +117,62 @@ public abstract class RemoteController { protected WebProxy createWebProxy() { MusicService musicService = MusicServiceFactory.getMusicService(downloadService); if(musicService instanceof CachedMusicService) { - return new WebProxy(downloadService, ((CachedMusicService)musicService).getMusicService().getHttpClient()); + RESTMusicService restMusicService = ((CachedMusicService)musicService).getMusicService(); + return new WebProxy(downloadService, restMusicService.getSSLSocketFactory(), restMusicService.getHostNameVerifier()); } else { 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; + } } 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 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 - * <http://www.apache.org/>. - * - */ - -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. - * <p> - * 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. - * <p> - * 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. - * <p> - * Use JDK keytool utility to import a trusted certificate and generate a trust-store file: - * <pre> - * keytool -import -alias "my server cert" -file server.crt -keystore my.truststore - * </pre> - * <p> - * 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. - * <p> - * The following parameters can be used to customize the behavior of this - * class: - * <ul> - * <li>{@link org.apache.http.params.CoreConnectionPNames#CONNECTION_TIMEOUT}</li> - * <li>{@link org.apache.http.params.CoreConnectionPNames#SO_TIMEOUT}</li> - * </ul> - * <p> - * 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 - * <p> - * Use the following sequence of actions to generate a key-store file - * </p> - * <ul> - * <li> - * <p> - * Use JDK keytool utility to generate a new key - * <pre>keytool -genkey -v -alias "my client key" -validity 365 -keystore my.keystore</pre> - * For simplicity use the same password for the key as that of the key-store - * </p> - * </li> - * <li> - * <p> - * Issue a certificate signing request (CSR) - * <pre>keytool -certreq -alias "my client key" -file mycertreq.csr -keystore my.keystore</pre> - * </p> - * </li> - * <li> - * <p> - * 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. - * </p> - * </li> - * <li> - * <p> - * Import the trusted CA root certificate - * <pre>keytool -import -alias "my trusted ca" -file caroot.crt -keystore my.keystore</pre> - * </p> - * </li> - * <li> - * <p> - * Import the PKCS#7 file containg the complete certificate chain - * <pre>keytool -import -alias "my client key" -file mycert.p7 -keystore my.keystore</pre> - * </p> - * </li> - * <li> - * <p> - * Verify the content the resultant keystore file - * <pre>keytool -list -v -keystore my.keystore</pre> - * </p> - * </li> - * </ul> - * - * @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. - * <br/> - * Derived classes may override this method to perform - * runtime checks, for example based on the cypher suite. - * - * @param sock the connected socket - * - * @return <code>true</code> - * - * @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<String> 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<String> 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 - * <http://www.apache.org/>. - * - */ -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 - * <http://www.apache.org/>. - * - */ -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 - * <http://www.apache.org/>. - * - */ -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. - * <p> - * Please note that, if this method returns <code>false</code>, 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 <code>true</code> if the certificate can be trusted without verification by - * the trust manager, <code>false</code> 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/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(); } 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); } 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); 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; 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"> - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + <LinearLayout android:id="@+id/download_other_controls_layout" android:orientation="horizontal" android:layout_width="wrap_content" diff --git a/app/src/main/res/layout/abstract_fragment_activity.xml b/app/src/main/res/layout/abstract_fragment_activity.xml index 462ee7ef..ae6647c4 100644 --- a/app/src/main/res/layout/abstract_fragment_activity.xml +++ b/app/src/main/res/layout/abstract_fragment_activity.xml @@ -137,7 +137,7 @@ </LinearLayout> </FrameLayout> - <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + <FrameLayout android:id="@+id/now_playing_fragment_container" android:layout_width="match_parent" android:layout_height="0dp" diff --git a/app/src/main/res/layout/change_email.xml b/app/src/main/res/layout/change_email.xml index 87d297be..d78edd13 100644 --- a/app/src/main/res/layout/change_email.xml +++ b/app/src/main/res/layout/change_email.xml @@ -4,7 +4,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content"> 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"> - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content"> 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"> - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" 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"> - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" 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"> - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content"> @@ -26,7 +26,7 @@ android:textColor="?android:textColorPrimary"/> </LinearLayout> - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content"> @@ -48,7 +48,7 @@ android:textColor="?android:textColorPrimary"/> </LinearLayout> - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content"> 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" /> <LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" diff --git a/app/src/main/res/layout/notification_expanded.xml b/app/src/main/res/layout/notification_expanded.xml index 7b378e12..a1586214 100644 --- a/app/src/main/res/layout/notification_expanded.xml +++ b/app/src/main/res/layout/notification_expanded.xml @@ -12,7 +12,6 @@ android:gravity="center" /> <LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="0.0" diff --git a/app/src/main/res/layout/select_album_header.xml b/app/src/main/res/layout/select_album_header.xml index 5b2294f0..891db891 100644 --- a/app/src/main/res/layout/select_album_header.xml +++ b/app/src/main/res/layout/select_album_header.xml @@ -121,7 +121,7 @@ android:contentDescription="@null"/> </RelativeLayout> - <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + <FrameLayout android:id="@+id/header_progress" android:layout_width="fill_parent" android:layout_height="fill_parent" diff --git a/app/src/main/res/layout/shuffle_dialog.xml b/app/src/main/res/layout/shuffle_dialog.xml index 63778ed7..012c220f 100644 --- a/app/src/main/res/layout/shuffle_dialog.xml +++ b/app/src/main/res/layout/shuffle_dialog.xml @@ -3,7 +3,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content"> - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content"> @@ -26,7 +26,7 @@ android:hint="@string/shuffle.startYear" /> </LinearLayout> - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content"> @@ -49,7 +49,7 @@ android:hint="@string/shuffle.endYear" /> </LinearLayout> - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content"> 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"> - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content"> @@ -26,7 +26,7 @@ android:textColor="?android:textColorPrimary"/> </LinearLayout> - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content"> @@ -49,7 +49,7 @@ android:hint="@string/common.comment" /> </LinearLayout> - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content"> 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"> - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content"> @@ -26,7 +26,7 @@ android:hint="@string/common.name" /> </LinearLayout> - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content"> @@ -48,7 +48,7 @@ android:calendarViewShown="false"/> </LinearLayout> - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content"> 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 @@ <string name="menu.add_podcast">Add Channel</string> <string name="menu.keep_synced">Keep Synced</string> <string name="menu.stop_sync">Stop syncing</string> - <string name="menu.show_all">Show all media</string> + <string name="menu.show_all">Show all songs</string> <string name="menu.show_artist">Show Artist</string> <string name="menu.share">Share</string> <string name="menu.delete_cache">Delete Cache</string> diff --git a/app/src/main/res/xml/changelog.xml b/app/src/main/res/xml/changelog.xml index 169edaa0..dc77a5a3 100644 --- a/app/src/main/res/xml/changelog.xml +++ b/app/src/main/res/xml/changelog.xml @@ -1,5 +1,15 @@ <?xml version="1.0" encoding="utf-8"?> <changelog> + <release version="5.3.2" versioncode="190" releasedate="10/22/2016"> + <change>Add support for casting Internet Radio to ChromeCast/DLNA</change> + <change>Add support for Play Title by Artist from Google Search</change> + <change>Move to more modern connection framework</change> + <change>Use Google Play SSL</change> + <change>Show album instead of artist for Show all media</change> + <change>Ask for location permissions for Day/Night themes</change> + <change>Fix a change to the ChromeCast API</change> + <change>Fix Show all media sometimes failing</change> + </release> <release version="5.3.1" versioncode="187" releasedate="10/4/2016"> <change>Fix Internet Radio streams which point to playlists</change> <change>Don't show playback speed button below Android 6.0</change> |