From 0f974c59d5c7253dee643513bb4c363554feec5c Mon Sep 17 00:00:00 2001 From: Andrew Rabert Date: Sun, 12 Mar 2017 14:40:13 -0400 Subject: [PATCH] Stream media with OkHttp --- .../audinaut/service/CachedMusicService.java | 4 +- .../audinaut/service/DownloadFile.java | 23 +- .../audinaut/service/DownloadService.java | 3736 ++++++++--------- .../audinaut/service/MusicService.java | 4 +- .../audinaut/service/OfflineMusicService.java | 4 +- .../audinaut/service/RESTMusicService.java | 280 +- .../service/ssl/SSLSocketFactory.java | 553 --- .../service/ssl/TrustManagerDecorator.java | 65 - .../service/ssl/TrustSelfSignedStrategy.java | 44 - .../audinaut/service/ssl/TrustStrategy.java | 57 - .../java/net/nullsum/audinaut/util/Util.java | 2 - 11 files changed, 1903 insertions(+), 2869 deletions(-) delete mode 100644 app/src/main/java/net/nullsum/audinaut/service/ssl/SSLSocketFactory.java delete mode 100644 app/src/main/java/net/nullsum/audinaut/service/ssl/TrustManagerDecorator.java delete mode 100644 app/src/main/java/net/nullsum/audinaut/service/ssl/TrustSelfSignedStrategy.java delete mode 100644 app/src/main/java/net/nullsum/audinaut/service/ssl/TrustStrategy.java diff --git a/app/src/main/java/net/nullsum/audinaut/service/CachedMusicService.java b/app/src/main/java/net/nullsum/audinaut/service/CachedMusicService.java index 578e7b4..74b0ebc 100644 --- a/app/src/main/java/net/nullsum/audinaut/service/CachedMusicService.java +++ b/app/src/main/java/net/nullsum/audinaut/service/CachedMusicService.java @@ -27,7 +27,7 @@ import java.util.List; import java.util.ListIterator; import java.util.concurrent.TimeUnit; -import org.apache.http.HttpResponse; +import okhttp3.Response; import android.content.Context; import android.graphics.Bitmap; @@ -607,7 +607,7 @@ public class CachedMusicService implements MusicService { } @Override - public HttpResponse getDownloadInputStream(Context context, Entry song, long offset, int maxBitrate, SilentBackgroundTask task) throws Exception { + public Response getDownloadInputStream(Context context, Entry song, long offset, int maxBitrate, SilentBackgroundTask task) throws Exception { return musicService.getDownloadInputStream(context, song, offset, maxBitrate, task); } diff --git a/app/src/main/java/net/nullsum/audinaut/service/DownloadFile.java b/app/src/main/java/net/nullsum/audinaut/service/DownloadFile.java index 204c04a..679d2a6 100644 --- a/app/src/main/java/net/nullsum/audinaut/service/DownloadFile.java +++ b/app/src/main/java/net/nullsum/audinaut/service/DownloadFile.java @@ -38,10 +38,7 @@ import net.nullsum.audinaut.util.FileUtil; import net.nullsum.audinaut.util.SilentBackgroundTask; import net.nullsum.audinaut.util.Util; -import org.apache.http.Header; - -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; +import okhttp3.Response; /** * @author Sindre Mehus @@ -454,21 +451,19 @@ 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); - } + Response response = musicService.getDownloadInputStream(context, song, partialFile.length(), bitRate, DownloadTask.this); + if(response.header("Content-Length") != null) { + Log.i(TAG, "Content Length: " + contentLength); + contentLength = contentLength; } - in = response.getEntity().getContent(); - boolean partial = response.getStatusLine().getStatusCode() == HttpStatus.SC_PARTIAL_CONTENT; + + boolean partial = response.code() == 206; if (partial) { Log.i(TAG, "Executed partial HTTP GET, skipping " + partialFile.length() + " bytes"); } + in = response.body().byteStream(); + out = new FileOutputStream(partialFile, partial); long n = copy(in, out); Log.i(TAG, "Downloaded " + n + " bytes to " + partialFile); diff --git a/app/src/main/java/net/nullsum/audinaut/service/DownloadService.java b/app/src/main/java/net/nullsum/audinaut/service/DownloadService.java index 6ff07ba..a664054 100644 --- a/app/src/main/java/net/nullsum/audinaut/service/DownloadService.java +++ b/app/src/main/java/net/nullsum/audinaut/service/DownloadService.java @@ -83,680 +83,680 @@ import android.view.KeyEvent; * @version $Id$ */ public class DownloadService extends Service { - private static final String TAG = DownloadService.class.getSimpleName(); + private static final String TAG = DownloadService.class.getSimpleName(); - public static final String CMD_PLAY = "net.nullsum.audinaut.CMD_PLAY"; - public static final String CMD_TOGGLEPAUSE = "net.nullsum.audinaut.CMD_TOGGLEPAUSE"; - public static final String CMD_PAUSE = "net.nullsum.audinaut.CMD_PAUSE"; - public static final String CMD_STOP = "net.nullsum.audinaut.CMD_STOP"; - public static final String CMD_PREVIOUS = "net.nullsum.audinaut.CMD_PREVIOUS"; - public static final String CMD_NEXT = "net.nullsum.audinaut.CMD_NEXT"; - public static final String CANCEL_DOWNLOADS = "net.nullsum.audinaut.CANCEL_DOWNLOADS"; - public static final String START_PLAY = "net.nullsum.audinaut.START_PLAYING"; - public static final int FAST_FORWARD = 30000; - public static final int REWIND = 10000; - private static final long DEFAULT_DELAY_UPDATE_PROGRESS = 1000L; - private static final double DELETE_CUTOFF = 0.84; - private static final int REQUIRED_ALBUM_MATCHES = 4; - private static final int REMOTE_PLAYLIST_TOTAL = 3; - private static final int SHUFFLE_MODE_NONE = 0; - private static final int SHUFFLE_MODE_ALL = 1; - private static final int SHUFFLE_MODE_ARTIST = 2; + public static final String CMD_PLAY = "net.nullsum.audinaut.CMD_PLAY"; + public static final String CMD_TOGGLEPAUSE = "net.nullsum.audinaut.CMD_TOGGLEPAUSE"; + public static final String CMD_PAUSE = "net.nullsum.audinaut.CMD_PAUSE"; + public static final String CMD_STOP = "net.nullsum.audinaut.CMD_STOP"; + public static final String CMD_PREVIOUS = "net.nullsum.audinaut.CMD_PREVIOUS"; + public static final String CMD_NEXT = "net.nullsum.audinaut.CMD_NEXT"; + public static final String CANCEL_DOWNLOADS = "net.nullsum.audinaut.CANCEL_DOWNLOADS"; + public static final String START_PLAY = "net.nullsum.audinaut.START_PLAYING"; + public static final int FAST_FORWARD = 30000; + public static final int REWIND = 10000; + private static final long DEFAULT_DELAY_UPDATE_PROGRESS = 1000L; + private static final double DELETE_CUTOFF = 0.84; + private static final int REQUIRED_ALBUM_MATCHES = 4; + private static final int REMOTE_PLAYLIST_TOTAL = 3; + private static final int SHUFFLE_MODE_NONE = 0; + private static final int SHUFFLE_MODE_ALL = 1; + private static final int SHUFFLE_MODE_ARTIST = 2; - public static final int METADATA_UPDATED_ALL = 0; - public static final int METADATA_UPDATED_STAR = 1; - public static final int METADATA_UPDATED_COVER_ART = 8; + public static final int METADATA_UPDATED_ALL = 0; + public static final int METADATA_UPDATED_STAR = 1; + public static final int METADATA_UPDATED_COVER_ART = 8; - private final IBinder binder = new SimpleServiceBinder<>(this); - private Looper mediaPlayerLooper; - private MediaPlayer mediaPlayer; - private MediaPlayer nextMediaPlayer; - private int audioSessionId; - private boolean nextSetup = false; - private final List downloadList = new ArrayList(); - private final List backgroundDownloadList = new ArrayList(); - private final List toDelete = new ArrayList(); - private final Handler handler = new Handler(); - private Handler mediaPlayerHandler; - private final DownloadServiceLifecycleSupport lifecycleSupport = new DownloadServiceLifecycleSupport(this); - private ShufflePlayBuffer shufflePlayBuffer; + private final IBinder binder = new SimpleServiceBinder<>(this); + private Looper mediaPlayerLooper; + private MediaPlayer mediaPlayer; + private MediaPlayer nextMediaPlayer; + private int audioSessionId; + private boolean nextSetup = false; + private final List downloadList = new ArrayList(); + private final List backgroundDownloadList = new ArrayList(); + private final List toDelete = new ArrayList(); + private final Handler handler = new Handler(); + private Handler mediaPlayerHandler; + private final DownloadServiceLifecycleSupport lifecycleSupport = new DownloadServiceLifecycleSupport(this); + private ShufflePlayBuffer shufflePlayBuffer; - private final LruCache downloadFileCache = new LruCache(100); - private final List cleanupCandidates = new ArrayList(); - private DownloadFile currentPlaying; - private int currentPlayingIndex = -1; - private DownloadFile nextPlaying; - private DownloadFile currentDownloading; - private SilentBackgroundTask bufferTask; - private SilentBackgroundTask nextPlayingTask; - private PlayerState playerState = IDLE; - private PlayerState nextPlayerState = IDLE; - private boolean removePlayed; - private boolean shufflePlay; - private final List onSongChangedListeners = new ArrayList<>(); - private long revision; - private static DownloadService instance; - private String suggestedPlaylistName; - private String suggestedPlaylistId; - private PowerManager.WakeLock wakeLock; - private WifiManager.WifiLock wifiLock; - private boolean keepScreenOn; - private int cachedPosition = 0; - private boolean downloadOngoing = false; - private float volume = 1.0f; - private long delayUpdateProgress = DEFAULT_DELAY_UPDATE_PROGRESS; + private final LruCache downloadFileCache = new LruCache(100); + private final List cleanupCandidates = new ArrayList(); + private DownloadFile currentPlaying; + private int currentPlayingIndex = -1; + private DownloadFile nextPlaying; + private DownloadFile currentDownloading; + private SilentBackgroundTask bufferTask; + private SilentBackgroundTask nextPlayingTask; + private PlayerState playerState = IDLE; + private PlayerState nextPlayerState = IDLE; + private boolean removePlayed; + private boolean shufflePlay; + private final List onSongChangedListeners = new ArrayList<>(); + private long revision; + private static DownloadService instance; + private String suggestedPlaylistName; + private String suggestedPlaylistId; + private PowerManager.WakeLock wakeLock; + private WifiManager.WifiLock wifiLock; + private boolean keepScreenOn; + private int cachedPosition = 0; + private boolean downloadOngoing = false; + private float volume = 1.0f; + private long delayUpdateProgress = DEFAULT_DELAY_UPDATE_PROGRESS; - private AudioEffectsController effectsController; - private PositionCache positionCache; - private BufferProxy proxy; + private AudioEffectsController effectsController; + private PositionCache positionCache; + private BufferProxy proxy; - private boolean autoPlayStart = false; - private boolean runListenersOnInit = false; + private boolean autoPlayStart = false; + private boolean runListenersOnInit = false; - // Variables to manage getCurrentPosition sometimes starting from an arbitrary non-zero number - private long subtractNextPosition = 0; - private int subtractPosition = 0; + // Variables to manage getCurrentPosition sometimes starting from an arbitrary non-zero number + private long subtractNextPosition = 0; + private int subtractPosition = 0; - @Override - public void onCreate() { - super.onCreate(); + @Override + public void onCreate() { + super.onCreate(); - final SharedPreferences prefs = Util.getPreferences(this); - new Thread(new Runnable() { - public void run() { - Looper.prepare(); + final SharedPreferences prefs = Util.getPreferences(this); + new Thread(new Runnable() { + public void run() { + Looper.prepare(); - mediaPlayer = new MediaPlayer(); - mediaPlayer.setWakeMode(DownloadService.this, PowerManager.PARTIAL_WAKE_LOCK); + mediaPlayer = new MediaPlayer(); + mediaPlayer.setWakeMode(DownloadService.this, PowerManager.PARTIAL_WAKE_LOCK); - audioSessionId = -1; - Integer id = prefs.getInt(Constants.CACHE_AUDIO_SESSION_ID, -1); - if(id != -1) { - try { - audioSessionId = id; - mediaPlayer.setAudioSessionId(audioSessionId); - } catch (Throwable e) { - audioSessionId = -1; - } - } + audioSessionId = -1; + Integer id = prefs.getInt(Constants.CACHE_AUDIO_SESSION_ID, -1); + if(id != -1) { + try { + audioSessionId = id; + mediaPlayer.setAudioSessionId(audioSessionId); + } catch (Throwable e) { + audioSessionId = -1; + } + } - if(audioSessionId == -1) { - mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); - try { - audioSessionId = mediaPlayer.getAudioSessionId(); - prefs.edit().putInt(Constants.CACHE_AUDIO_SESSION_ID, audioSessionId).commit(); - } catch (Throwable t) { - // Froyo or lower - } - } + if(audioSessionId == -1) { + mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); + try { + audioSessionId = mediaPlayer.getAudioSessionId(); + prefs.edit().putInt(Constants.CACHE_AUDIO_SESSION_ID, audioSessionId).commit(); + } catch (Throwable t) { + // Froyo or lower + } + } - mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { - @Override - public boolean onError(MediaPlayer mediaPlayer, int what, int more) { - handleError(new Exception("MediaPlayer error: " + what + " (" + more + ")")); - return false; - } - }); + mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { + @Override + public boolean onError(MediaPlayer mediaPlayer, int what, int more) { + handleError(new Exception("MediaPlayer error: " + what + " (" + more + ")")); + return false; + } + }); - /*try { - Intent i = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION); - i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, audioSessionId); - i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName()); - sendBroadcast(i); - } catch(Throwable e) { - // Froyo or lower - }*/ + /*try { + Intent i = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION); + i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, audioSessionId); + i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName()); + sendBroadcast(i); + } catch(Throwable e) { + // Froyo or lower + }*/ - effectsController = new AudioEffectsController(DownloadService.this, audioSessionId); - if(prefs.getBoolean(Constants.PREFERENCES_EQUALIZER_ON, false)) { - getEqualizerController(); - } + effectsController = new AudioEffectsController(DownloadService.this, audioSessionId); + if(prefs.getBoolean(Constants.PREFERENCES_EQUALIZER_ON, false)) { + getEqualizerController(); + } - mediaPlayerLooper = Looper.myLooper(); - mediaPlayerHandler = new Handler(mediaPlayerLooper); + mediaPlayerLooper = Looper.myLooper(); + mediaPlayerHandler = new Handler(mediaPlayerLooper); - if(runListenersOnInit) { - onSongsChanged(); - onSongProgress(); - onStateUpdate(); - } + if(runListenersOnInit) { + onSongsChanged(); + onSongProgress(); + onStateUpdate(); + } - Looper.loop(); - } - }, "DownloadService").start(); + Looper.loop(); + } + }, "DownloadService").start(); - Util.registerMediaButtonEventReceiver(this); + Util.registerMediaButtonEventReceiver(this); - PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); - wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName()); - wakeLock.setReferenceCounted(false); + PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); + wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName()); + wakeLock.setReferenceCounted(false); - WifiManager wifiManager = (WifiManager) this.getSystemService(Context.WIFI_SERVICE); - wifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, "downloadServiceLock"); + WifiManager wifiManager = (WifiManager) this.getSystemService(Context.WIFI_SERVICE); + wifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, "downloadServiceLock"); - keepScreenOn = prefs.getBoolean(Constants.PREFERENCES_KEY_KEEP_SCREEN_ON, false); + keepScreenOn = prefs.getBoolean(Constants.PREFERENCES_KEY_KEEP_SCREEN_ON, false); - instance = this; - shufflePlayBuffer = new ShufflePlayBuffer(this); - lifecycleSupport.onCreate(); - } + instance = this; + shufflePlayBuffer = new ShufflePlayBuffer(this); + lifecycleSupport.onCreate(); + } - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - super.onStartCommand(intent, flags, startId); - lifecycleSupport.onStart(intent); - return START_NOT_STICKY; - } + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + super.onStartCommand(intent, flags, startId); + lifecycleSupport.onStart(intent); + return START_NOT_STICKY; + } - @Override - public void onTrimMemory(int level) { - ImageLoader imageLoader = SubsonicActivity.getStaticImageLoader(this); - if(imageLoader != null) { - Log.i(TAG, "Memory Trim Level: " + level); - if (level < ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { - if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) { - imageLoader.onLowMemory(0.75f); - } else if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW) { - imageLoader.onLowMemory(0.50f); - } else if(level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE) { - imageLoader.onLowMemory(0.25f); - } - } else if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) { - imageLoader.onLowMemory(0.25f); - } else if(level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) { - imageLoader.onLowMemory(0.75f); - } - } - } + @Override + public void onTrimMemory(int level) { + ImageLoader imageLoader = SubsonicActivity.getStaticImageLoader(this); + if(imageLoader != null) { + Log.i(TAG, "Memory Trim Level: " + level); + if (level < ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { + if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) { + imageLoader.onLowMemory(0.75f); + } else if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW) { + imageLoader.onLowMemory(0.50f); + } else if(level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE) { + imageLoader.onLowMemory(0.25f); + } + } else if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) { + imageLoader.onLowMemory(0.25f); + } else if(level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) { + imageLoader.onLowMemory(0.75f); + } + } + } - @Override - public void onDestroy() { - super.onDestroy(); - instance = null; + @Override + public void onDestroy() { + super.onDestroy(); + instance = null; - if(currentPlaying != null) currentPlaying.setPlaying(false); - lifecycleSupport.onDestroy(); + if(currentPlaying != null) currentPlaying.setPlaying(false); + lifecycleSupport.onDestroy(); - try { - Intent i = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION); - i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, audioSessionId); - i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName()); - sendBroadcast(i); - } catch(Throwable e) { - // Froyo or lower - } + try { + Intent i = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION); + i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, audioSessionId); + i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName()); + sendBroadcast(i); + } catch(Throwable e) { + // Froyo or lower + } - mediaPlayer.release(); - if(nextMediaPlayer != null) { - nextMediaPlayer.release(); - } - mediaPlayerLooper.quit(); - shufflePlayBuffer.shutdown(); - effectsController.release(); + mediaPlayer.release(); + if(nextMediaPlayer != null) { + nextMediaPlayer.release(); + } + mediaPlayerLooper.quit(); + shufflePlayBuffer.shutdown(); + effectsController.release(); - if(bufferTask != null) { - bufferTask.cancel(); - bufferTask = null; - } - if(nextPlayingTask != null) { - nextPlayingTask.cancel(); - nextPlayingTask = null; - } - if(proxy != null) { - proxy.stop(); - proxy = null; - } - Notifications.hidePlayingNotification(this, this, handler); - Notifications.hideDownloadingNotification(this, this, handler); - } + if(bufferTask != null) { + bufferTask.cancel(); + bufferTask = null; + } + if(nextPlayingTask != null) { + nextPlayingTask.cancel(); + nextPlayingTask = null; + } + if(proxy != null) { + proxy.stop(); + proxy = null; + } + Notifications.hidePlayingNotification(this, this, handler); + Notifications.hideDownloadingNotification(this, this, handler); + } - public static DownloadService getInstance() { - return instance; - } + public static DownloadService getInstance() { + return instance; + } - @Override - public IBinder onBind(Intent intent) { - return binder; - } - - public void post(Runnable r) { - handler.post(r); - } - public void postDelayed(Runnable r, long millis) { - handler.postDelayed(r, millis); - } + @Override + public IBinder onBind(Intent intent) { + return binder; + } - public synchronized void download(List songs, boolean save, boolean autoplay, boolean playNext, boolean shuffle) { - download(songs, save, autoplay, playNext, shuffle, 0, 0); - } - public synchronized void download(List songs, boolean save, boolean autoplay, boolean playNext, boolean shuffle, int start, int position) { - setShufflePlayEnabled(false); - int offset = 1; - boolean noNetwork = !Util.isOffline(this) && !Util.isNetworkConnected(this); - boolean warnNetwork = false; + public void post(Runnable r) { + handler.post(r); + } + public void postDelayed(Runnable r, long millis) { + handler.postDelayed(r, millis); + } - if (songs.isEmpty()) { - return; - } + public synchronized void download(List songs, boolean save, boolean autoplay, boolean playNext, boolean shuffle) { + download(songs, save, autoplay, playNext, shuffle, 0, 0); + } + public synchronized void download(List songs, boolean save, boolean autoplay, boolean playNext, boolean shuffle, int start, int position) { + setShufflePlayEnabled(false); + int offset = 1; + boolean noNetwork = !Util.isOffline(this) && !Util.isNetworkConnected(this); + boolean warnNetwork = false; - if (playNext) { - if (autoplay && getCurrentPlayingIndex() >= 0) { - offset = 0; - } - for (MusicDirectory.Entry song : songs) { - if(song != null) { - DownloadFile downloadFile = new DownloadFile(this, song, save); - addToDownloadList(downloadFile, getCurrentPlayingIndex() + offset); - if(noNetwork && !warnNetwork) { - if(!downloadFile.isCompleteFileAvailable()) { - warnNetwork = true; - } - } - offset++; - } - } + if (songs.isEmpty()) { + return; + } + + if (playNext) { + if (autoplay && getCurrentPlayingIndex() >= 0) { + offset = 0; + } + for (MusicDirectory.Entry song : songs) { + if(song != null) { + DownloadFile downloadFile = new DownloadFile(this, song, save); + addToDownloadList(downloadFile, getCurrentPlayingIndex() + offset); + if(noNetwork && !warnNetwork) { + if(!downloadFile.isCompleteFileAvailable()) { + warnNetwork = true; + } + } + offset++; + } + } setNextPlaying(); - } else { - int size = size(); - int index = getCurrentPlayingIndex(); - for (MusicDirectory.Entry song : songs) { - if(song == null) { - continue; - } + } else { + int size = size(); + int index = getCurrentPlayingIndex(); + for (MusicDirectory.Entry song : songs) { + if(song == null) { + continue; + } - DownloadFile downloadFile = new DownloadFile(this, song, save); - addToDownloadList(downloadFile, -1); - if(noNetwork && !warnNetwork) { - if(!downloadFile.isCompleteFileAvailable()) { - warnNetwork = true; - } - } - } - if(!autoplay && (size - 1) == index) { - setNextPlaying(); - } - } - revision++; - onSongsChanged(); - updateRemotePlaylist(); + DownloadFile downloadFile = new DownloadFile(this, song, save); + addToDownloadList(downloadFile, -1); + if(noNetwork && !warnNetwork) { + if(!downloadFile.isCompleteFileAvailable()) { + warnNetwork = true; + } + } + } + if(!autoplay && (size - 1) == index) { + setNextPlaying(); + } + } + revision++; + onSongsChanged(); + updateRemotePlaylist(); - if(shuffle) { - shuffle(); - } - if(warnNetwork) { - Util.toast(this, R.string.select_album_no_network); - } + if(shuffle) { + shuffle(); + } + if(warnNetwork) { + Util.toast(this, R.string.select_album_no_network); + } - if (autoplay) { - play(start, true, position); - } else if(start != 0 || position != 0) { - play(start, false, position); - } else { - if (currentPlaying == null) { - currentPlaying = downloadList.get(0); - currentPlayingIndex = 0; - currentPlaying.setPlaying(true); - } else { - currentPlayingIndex = downloadList.indexOf(currentPlaying); - } - checkDownloads(); - } - lifecycleSupport.serializeDownloadQueue(); - } - private void addToDownloadList(DownloadFile file, int offset) { - if(offset == -1) { - downloadList.add(file); - } else { - downloadList.add(offset, file); - } - } - public synchronized void downloadBackground(List songs, boolean save) { - for (MusicDirectory.Entry song : songs) { - DownloadFile downloadFile = new DownloadFile(this, song, save); - if(!downloadFile.isWorkDone() || (downloadFile.shouldSave() && !downloadFile.isSaved())) { - // Only add to list if there is work to be done - backgroundDownloadList.add(downloadFile); - } else if(downloadFile.isSaved() && !save) { - // Quickly unpin song instead of adding it to work to be done - downloadFile.unpin(); - } - } - revision++; + if (autoplay) { + play(start, true, position); + } else if(start != 0 || position != 0) { + play(start, false, position); + } else { + if (currentPlaying == null) { + currentPlaying = downloadList.get(0); + currentPlayingIndex = 0; + currentPlaying.setPlaying(true); + } else { + currentPlayingIndex = downloadList.indexOf(currentPlaying); + } + checkDownloads(); + } + lifecycleSupport.serializeDownloadQueue(); + } + private void addToDownloadList(DownloadFile file, int offset) { + if(offset == -1) { + downloadList.add(file); + } else { + downloadList.add(offset, file); + } + } + public synchronized void downloadBackground(List songs, boolean save) { + for (MusicDirectory.Entry song : songs) { + DownloadFile downloadFile = new DownloadFile(this, song, save); + if(!downloadFile.isWorkDone() || (downloadFile.shouldSave() && !downloadFile.isSaved())) { + // Only add to list if there is work to be done + backgroundDownloadList.add(downloadFile); + } else if(downloadFile.isSaved() && !save) { + // Quickly unpin song instead of adding it to work to be done + downloadFile.unpin(); + } + } + revision++; - if(!Util.isOffline(this) && !Util.isNetworkConnected(this)) { - Util.toast(this, R.string.select_album_no_network); - } + if(!Util.isOffline(this) && !Util.isNetworkConnected(this)) { + Util.toast(this, R.string.select_album_no_network); + } - checkDownloads(); - lifecycleSupport.serializeDownloadQueue(); - } + checkDownloads(); + lifecycleSupport.serializeDownloadQueue(); + } - private synchronized void updateRemotePlaylist() { - List playlist = new ArrayList<>(); - if(currentPlaying != null) { - int index = downloadList.indexOf(currentPlaying); - if(index == -1) { - index = 0; - } + private synchronized void updateRemotePlaylist() { + List playlist = new ArrayList<>(); + if(currentPlaying != null) { + int index = downloadList.indexOf(currentPlaying); + if(index == -1) { + index = 0; + } - int size = size(); - int end = index + REMOTE_PLAYLIST_TOTAL; - for(int i = index; i < size && i < end; i++) { - playlist.add(downloadList.get(i)); - } - } - } + int size = size(); + int end = index + REMOTE_PLAYLIST_TOTAL; + for(int i = index; i < size && i < end; i++) { + playlist.add(downloadList.get(i)); + } + } + } - public synchronized void restore(List songs, List toDelete, int currentPlayingIndex, int currentPlayingPosition) { - SharedPreferences prefs = Util.getPreferences(this); - if(prefs.getBoolean(Constants.PREFERENCES_KEY_REMOVE_PLAYED, false)) { - removePlayed = true; - } - int startShufflePlay = prefs.getInt(Constants.PREFERENCES_KEY_SHUFFLE_MODE, SHUFFLE_MODE_NONE); - download(songs, false, false, false, false); - if(startShufflePlay != SHUFFLE_MODE_NONE) { - if(startShufflePlay == SHUFFLE_MODE_ALL) { - shufflePlay = true; - } - SharedPreferences.Editor editor = prefs.edit(); - editor.putInt(Constants.PREFERENCES_KEY_SHUFFLE_MODE, startShufflePlay); - editor.commit(); - } - if (currentPlayingIndex != -1) { - while(mediaPlayer == null) { - Util.sleepQuietly(50L); - } + public synchronized void restore(List songs, List toDelete, int currentPlayingIndex, int currentPlayingPosition) { + SharedPreferences prefs = Util.getPreferences(this); + if(prefs.getBoolean(Constants.PREFERENCES_KEY_REMOVE_PLAYED, false)) { + removePlayed = true; + } + int startShufflePlay = prefs.getInt(Constants.PREFERENCES_KEY_SHUFFLE_MODE, SHUFFLE_MODE_NONE); + download(songs, false, false, false, false); + if(startShufflePlay != SHUFFLE_MODE_NONE) { + if(startShufflePlay == SHUFFLE_MODE_ALL) { + shufflePlay = true; + } + SharedPreferences.Editor editor = prefs.edit(); + editor.putInt(Constants.PREFERENCES_KEY_SHUFFLE_MODE, startShufflePlay); + editor.commit(); + } + if (currentPlayingIndex != -1) { + while(mediaPlayer == null) { + Util.sleepQuietly(50L); + } - play(currentPlayingIndex, autoPlayStart, currentPlayingPosition); - autoPlayStart = false; - } + play(currentPlayingIndex, autoPlayStart, currentPlayingPosition); + autoPlayStart = false; + } - if(toDelete != null) { - for(MusicDirectory.Entry entry: toDelete) { - this.toDelete.add(forSong(entry)); - } - } - - suggestedPlaylistName = prefs.getString(Constants.PREFERENCES_KEY_PLAYLIST_NAME, null); - suggestedPlaylistId = prefs.getString(Constants.PREFERENCES_KEY_PLAYLIST_ID, null); - } + if(toDelete != null) { + for(MusicDirectory.Entry entry: toDelete) { + this.toDelete.add(forSong(entry)); + } + } - public boolean isInitialized() { - return lifecycleSupport != null && lifecycleSupport.isInitialized(); - } + suggestedPlaylistName = prefs.getString(Constants.PREFERENCES_KEY_PLAYLIST_NAME, null); + suggestedPlaylistId = prefs.getString(Constants.PREFERENCES_KEY_PLAYLIST_ID, null); + } - public synchronized Date getLastStateChanged() { - return lifecycleSupport.getLastChange(); - } + public boolean isInitialized() { + return lifecycleSupport != null && lifecycleSupport.isInitialized(); + } - public synchronized void setRemovePlayed(boolean enabled) { - removePlayed = enabled; - if(removePlayed) { - checkDownloads(); - lifecycleSupport.serializeDownloadQueue(); - } - SharedPreferences.Editor editor = Util.getPreferences(this).edit(); - editor.putBoolean(Constants.PREFERENCES_KEY_REMOVE_PLAYED, enabled); - editor.commit(); - } - public boolean isRemovePlayed() { - return removePlayed; - } + public synchronized Date getLastStateChanged() { + return lifecycleSupport.getLastChange(); + } - public synchronized void setShufflePlayEnabled(boolean enabled) { - shufflePlay = enabled; - if (shufflePlay) { - checkDownloads(); - } - SharedPreferences.Editor editor = Util.getPreferences(this).edit(); - editor.putInt(Constants.PREFERENCES_KEY_SHUFFLE_MODE, enabled ? SHUFFLE_MODE_ALL : SHUFFLE_MODE_NONE); - editor.commit(); - } + public synchronized void setRemovePlayed(boolean enabled) { + removePlayed = enabled; + if(removePlayed) { + checkDownloads(); + lifecycleSupport.serializeDownloadQueue(); + } + SharedPreferences.Editor editor = Util.getPreferences(this).edit(); + editor.putBoolean(Constants.PREFERENCES_KEY_REMOVE_PLAYED, enabled); + editor.commit(); + } + public boolean isRemovePlayed() { + return removePlayed; + } - public boolean isShufflePlayEnabled() { - return shufflePlay; - } + public synchronized void setShufflePlayEnabled(boolean enabled) { + shufflePlay = enabled; + if (shufflePlay) { + checkDownloads(); + } + SharedPreferences.Editor editor = Util.getPreferences(this).edit(); + editor.putInt(Constants.PREFERENCES_KEY_SHUFFLE_MODE, enabled ? SHUFFLE_MODE_ALL : SHUFFLE_MODE_NONE); + editor.commit(); + } - public synchronized void shuffle() { - Collections.shuffle(downloadList); - currentPlayingIndex = downloadList.indexOf(currentPlaying); - if (currentPlaying != null) { - downloadList.remove(getCurrentPlayingIndex()); - downloadList.add(0, currentPlaying); - currentPlayingIndex = 0; - } - revision++; - onSongsChanged(); - lifecycleSupport.serializeDownloadQueue(); - updateRemotePlaylist(); - setNextPlaying(); - } + public boolean isShufflePlayEnabled() { + return shufflePlay; + } - public RepeatMode getRepeatMode() { - return Util.getRepeatMode(this); - } + public synchronized void shuffle() { + Collections.shuffle(downloadList); + currentPlayingIndex = downloadList.indexOf(currentPlaying); + if (currentPlaying != null) { + downloadList.remove(getCurrentPlayingIndex()); + downloadList.add(0, currentPlaying); + currentPlayingIndex = 0; + } + revision++; + onSongsChanged(); + lifecycleSupport.serializeDownloadQueue(); + updateRemotePlaylist(); + setNextPlaying(); + } - public void setRepeatMode(RepeatMode repeatMode) { - Util.setRepeatMode(this, repeatMode); - setNextPlaying(); - } + public RepeatMode getRepeatMode() { + return Util.getRepeatMode(this); + } - public boolean getKeepScreenOn() { - return keepScreenOn; - } + public void setRepeatMode(RepeatMode repeatMode) { + Util.setRepeatMode(this, repeatMode); + setNextPlaying(); + } - public void setKeepScreenOn(boolean keepScreenOn) { - this.keepScreenOn = keepScreenOn; + public boolean getKeepScreenOn() { + return keepScreenOn; + } - SharedPreferences prefs = Util.getPreferences(this); - SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(Constants.PREFERENCES_KEY_KEEP_SCREEN_ON, keepScreenOn); - editor.commit(); - } + public void setKeepScreenOn(boolean keepScreenOn) { + this.keepScreenOn = keepScreenOn; - public synchronized DownloadFile forSong(MusicDirectory.Entry song) { - DownloadFile returnFile = null; - for (DownloadFile downloadFile : downloadList) { - if (downloadFile.getSong().equals(song)) { - if(((downloadFile.isDownloading() && !downloadFile.isDownloadCancelled() && downloadFile.getPartialFile().exists()) || downloadFile.isWorkDone())) { - // If downloading, return immediately - return downloadFile; - } else { - // Otherwise, check to make sure there isn't a background download going on first - returnFile = downloadFile; - } - } - } - for (DownloadFile downloadFile : backgroundDownloadList) { - if (downloadFile.getSong().equals(song)) { - return downloadFile; - } - } + SharedPreferences prefs = Util.getPreferences(this); + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean(Constants.PREFERENCES_KEY_KEEP_SCREEN_ON, keepScreenOn); + editor.commit(); + } - if(returnFile != null) { - return returnFile; - } + public synchronized DownloadFile forSong(MusicDirectory.Entry song) { + DownloadFile returnFile = null; + for (DownloadFile downloadFile : downloadList) { + if (downloadFile.getSong().equals(song)) { + if(((downloadFile.isDownloading() && !downloadFile.isDownloadCancelled() && downloadFile.getPartialFile().exists()) || downloadFile.isWorkDone())) { + // If downloading, return immediately + return downloadFile; + } else { + // Otherwise, check to make sure there isn't a background download going on first + returnFile = downloadFile; + } + } + } + for (DownloadFile downloadFile : backgroundDownloadList) { + if (downloadFile.getSong().equals(song)) { + return downloadFile; + } + } - DownloadFile downloadFile = downloadFileCache.get(song); - if (downloadFile == null) { - downloadFile = new DownloadFile(this, song, false); - downloadFileCache.put(song, downloadFile); - } - return downloadFile; - } + if(returnFile != null) { + return returnFile; + } - public synchronized void clearBackground() { - if(currentDownloading != null && backgroundDownloadList.contains(currentDownloading)) { - currentDownloading.cancelDownload(); - currentDownloading = null; - } - backgroundDownloadList.clear(); - revision++; - Notifications.hideDownloadingNotification(this, this, handler); - } + DownloadFile downloadFile = downloadFileCache.get(song); + if (downloadFile == null) { + downloadFile = new DownloadFile(this, song, false); + downloadFileCache.put(song, downloadFile); + } + return downloadFile; + } - public synchronized void clearIncomplete() { - Iterator iterator = downloadList.iterator(); - while (iterator.hasNext()) { - DownloadFile downloadFile = iterator.next(); - if (!downloadFile.isCompleteFileAvailable()) { - iterator.remove(); - - // Reset if the current playing song has been removed - if(currentPlaying == downloadFile) { - reset(); - } + public synchronized void clearBackground() { + if(currentDownloading != null && backgroundDownloadList.contains(currentDownloading)) { + currentDownloading.cancelDownload(); + currentDownloading = null; + } + backgroundDownloadList.clear(); + revision++; + Notifications.hideDownloadingNotification(this, this, handler); + } - currentPlayingIndex = downloadList.indexOf(currentPlaying); - } - } - lifecycleSupport.serializeDownloadQueue(); - updateRemotePlaylist(); - onSongsChanged(); - } + public synchronized void clearIncomplete() { + Iterator iterator = downloadList.iterator(); + while (iterator.hasNext()) { + DownloadFile downloadFile = iterator.next(); + if (!downloadFile.isCompleteFileAvailable()) { + iterator.remove(); - public void setOnline(final boolean online) { - if(shufflePlay) { - setShufflePlayEnabled(false); - } + // Reset if the current playing song has been removed + if(currentPlaying == downloadFile) { + reset(); + } - lifecycleSupport.post(new Runnable() { - @Override - public void run() { - if (online) { - checkDownloads(); - } else { - clearIncomplete(); - } - } - }); - } + currentPlayingIndex = downloadList.indexOf(currentPlaying); + } + } + lifecycleSupport.serializeDownloadQueue(); + updateRemotePlaylist(); + onSongsChanged(); + } - public synchronized int size() { - return downloadList.size(); - } + public void setOnline(final boolean online) { + if(shufflePlay) { + setShufflePlayEnabled(false); + } - public synchronized void clear() { - clear(true); - } - public synchronized void clear(boolean serialize) { - // Delete podcast if fully listened to - int position = getPlayerPosition(); - int duration = getPlayerDuration(); - boolean cutoff = isPastCutoff(position, duration, true); - for(DownloadFile podcast: toDelete) { - podcast.delete(); - } - toDelete.clear(); - - reset(); - downloadList.clear(); - onSongsChanged(); - if (currentDownloading != null && !backgroundDownloadList.contains(currentDownloading)) { - currentDownloading.cancelDownload(); - currentDownloading = null; - } - setCurrentPlaying(null, false); + lifecycleSupport.post(new Runnable() { + @Override + public void run() { + if (online) { + checkDownloads(); + } else { + clearIncomplete(); + } + } + }); + } - if (serialize) { - lifecycleSupport.serializeDownloadQueue(); - } - updateRemotePlaylist(); - setNextPlaying(); - if(proxy != null) { - proxy.stop(); - proxy = null; - } + public synchronized int size() { + return downloadList.size(); + } - suggestedPlaylistName = null; - suggestedPlaylistId = null; + public synchronized void clear() { + clear(true); + } + public synchronized void clear(boolean serialize) { + // Delete podcast if fully listened to + int position = getPlayerPosition(); + int duration = getPlayerDuration(); + boolean cutoff = isPastCutoff(position, duration, true); + for(DownloadFile podcast: toDelete) { + podcast.delete(); + } + toDelete.clear(); - setShufflePlayEnabled(false); - checkDownloads(); - } + reset(); + downloadList.clear(); + onSongsChanged(); + if (currentDownloading != null && !backgroundDownloadList.contains(currentDownloading)) { + currentDownloading.cancelDownload(); + currentDownloading = null; + } + setCurrentPlaying(null, false); - public synchronized void remove(int which) { - downloadList.remove(which); - currentPlayingIndex = downloadList.indexOf(currentPlaying); - } + if (serialize) { + lifecycleSupport.serializeDownloadQueue(); + } + updateRemotePlaylist(); + setNextPlaying(); + if(proxy != null) { + proxy.stop(); + proxy = null; + } - public synchronized void remove(DownloadFile downloadFile) { - if (downloadFile == currentDownloading) { - currentDownloading.cancelDownload(); - currentDownloading = null; - } - if (downloadFile == currentPlaying) { - reset(); - setCurrentPlaying(null, false); - } - downloadList.remove(downloadFile); - currentPlayingIndex = downloadList.indexOf(currentPlaying); - backgroundDownloadList.remove(downloadFile); - revision++; - onSongsChanged(); - lifecycleSupport.serializeDownloadQueue(); - updateRemotePlaylist(); - if(downloadFile == nextPlaying) { - setNextPlaying(); - } + suggestedPlaylistName = null; + suggestedPlaylistId = null; - checkDownloads(); - } - public synchronized void removeBackground(DownloadFile downloadFile) { - if (downloadFile == currentDownloading && downloadFile != currentPlaying && downloadFile != nextPlaying) { - currentDownloading.cancelDownload(); - currentDownloading = null; - } + setShufflePlayEnabled(false); + checkDownloads(); + } - backgroundDownloadList.remove(downloadFile); - revision++; - checkDownloads(); - } + public synchronized void remove(int which) { + downloadList.remove(which); + currentPlayingIndex = downloadList.indexOf(currentPlaying); + } - public synchronized void delete(List songs) { - for (MusicDirectory.Entry song : songs) { - forSong(song).delete(); - } - } + public synchronized void remove(DownloadFile downloadFile) { + if (downloadFile == currentDownloading) { + currentDownloading.cancelDownload(); + currentDownloading = null; + } + if (downloadFile == currentPlaying) { + reset(); + setCurrentPlaying(null, false); + } + downloadList.remove(downloadFile); + currentPlayingIndex = downloadList.indexOf(currentPlaying); + backgroundDownloadList.remove(downloadFile); + revision++; + onSongsChanged(); + lifecycleSupport.serializeDownloadQueue(); + updateRemotePlaylist(); + if(downloadFile == nextPlaying) { + setNextPlaying(); + } - public synchronized void unpin(List songs) { - for (MusicDirectory.Entry song : songs) { - forSong(song).unpin(); - } - } + checkDownloads(); + } + public synchronized void removeBackground(DownloadFile downloadFile) { + if (downloadFile == currentDownloading && downloadFile != currentPlaying && downloadFile != nextPlaying) { + currentDownloading.cancelDownload(); + currentDownloading = null; + } - synchronized void setCurrentPlaying(int currentPlayingIndex, boolean showNotification) { - try { - setCurrentPlaying(downloadList.get(currentPlayingIndex), showNotification); - } catch (IndexOutOfBoundsException x) { - // Ignored - } - } + backgroundDownloadList.remove(downloadFile); + revision++; + checkDownloads(); + } - synchronized void setCurrentPlaying(DownloadFile currentPlaying, boolean showNotification) { - if(this.currentPlaying != null) { - this.currentPlaying.setPlaying(false); - } - this.currentPlaying = currentPlaying; - if(currentPlaying == null) { - currentPlayingIndex = -1; - setPlayerState(IDLE); - } else { - currentPlayingIndex = downloadList.indexOf(currentPlaying); - } + public synchronized void delete(List songs) { + for (MusicDirectory.Entry song : songs) { + forSong(song).delete(); + } + } - if (currentPlaying != null && currentPlaying.getSong() != null) { - Util.broadcastNewTrackInfo(this, currentPlaying.getSong()); - } else { - Util.broadcastNewTrackInfo(this, null); - Notifications.hidePlayingNotification(this, this, handler); - } - onSongChanged(); - } + public synchronized void unpin(List songs) { + for (MusicDirectory.Entry song : songs) { + forSong(song).unpin(); + } + } - synchronized void setNextPlaying() { - SharedPreferences prefs = Util.getPreferences(DownloadService.this); + synchronized void setCurrentPlaying(int currentPlayingIndex, boolean showNotification) { + try { + setCurrentPlaying(downloadList.get(currentPlayingIndex), showNotification); + } catch (IndexOutOfBoundsException x) { + // Ignored + } + } + + synchronized void setCurrentPlaying(DownloadFile currentPlaying, boolean showNotification) { + if(this.currentPlaying != null) { + this.currentPlaying.setPlaying(false); + } + this.currentPlaying = currentPlaying; + if(currentPlaying == null) { + currentPlayingIndex = -1; + setPlayerState(IDLE); + } else { + currentPlayingIndex = downloadList.indexOf(currentPlaying); + } + + if (currentPlaying != null && currentPlaying.getSong() != null) { + Util.broadcastNewTrackInfo(this, currentPlaying.getSong()); + } else { + Util.broadcastNewTrackInfo(this, null); + Notifications.hidePlayingNotification(this, this, handler); + } + onSongChanged(); + } + + synchronized void setNextPlaying() { + SharedPreferences prefs = Util.getPreferences(DownloadService.this); boolean gaplessPlayback = prefs.getBoolean(Constants.PREFERENCES_KEY_GAPLESS_PLAYBACK, true); if (!gaplessPlayback) { @@ -764,218 +764,218 @@ public class DownloadService extends Service { nextPlayerState = IDLE; return; } - setNextPlayerState(IDLE); + setNextPlayerState(IDLE); - int index = getNextPlayingIndex(); + int index = getNextPlayingIndex(); - if(nextPlayingTask != null) { - nextPlayingTask.cancel(); - nextPlayingTask = null; - } - resetNext(); + if(nextPlayingTask != null) { + nextPlayingTask.cancel(); + nextPlayingTask = null; + } + resetNext(); - if(index < size() && index != -1 && index != currentPlayingIndex) { - nextPlaying = downloadList.get(index); + if(index < size() && index != -1 && index != currentPlayingIndex) { + nextPlaying = downloadList.get(index); nextPlayingTask = new CheckCompletionTask(nextPlaying); nextPlayingTask.execute(); - } else { - nextPlaying = null; - } - } + } else { + nextPlaying = null; + } + } - public int getCurrentPlayingIndex() { - return currentPlayingIndex; - } - private int getNextPlayingIndex() { - int index = getCurrentPlayingIndex(); - if (index != -1) { - RepeatMode repeatMode = getRepeatMode(); - switch (repeatMode) { - case OFF: - index = index + 1; - break; - case ALL: - index = (index + 1) % size(); - break; - case SINGLE: - break; - default: - break; - } + public int getCurrentPlayingIndex() { + return currentPlayingIndex; + } + private int getNextPlayingIndex() { + int index = getCurrentPlayingIndex(); + if (index != -1) { + RepeatMode repeatMode = getRepeatMode(); + switch (repeatMode) { + case OFF: + index = index + 1; + break; + case ALL: + index = (index + 1) % size(); + break; + case SINGLE: + break; + default: + break; + } - index = checkNextIndexValid(index, repeatMode); - } - return index; - } - private int checkNextIndexValid(int index, RepeatMode repeatMode) { - int startIndex = index; - int size = size(); - if(index < size && index != -1) { - if(!Util.isAllowedToDownload(this)){ - DownloadFile next = downloadList.get(index); - while(!next.isCompleteFileAvailable()) { - index++; + index = checkNextIndexValid(index, repeatMode); + } + return index; + } + private int checkNextIndexValid(int index, RepeatMode repeatMode) { + int startIndex = index; + int size = size(); + if(index < size && index != -1) { + if(!Util.isAllowedToDownload(this)){ + DownloadFile next = downloadList.get(index); + while(!next.isCompleteFileAvailable()) { + index++; - if (index >= size) { - if(repeatMode == RepeatMode.ALL) { - index = 0; - } else { - return -1; - } - } else if(index == startIndex) { - handler.post(new Runnable() { - @Override - public void run() { - Util.toast(DownloadService.this, R.string.download_playerstate_mobile_disabled); - } - }); - return -1; - } + if (index >= size) { + if(repeatMode == RepeatMode.ALL) { + index = 0; + } else { + return -1; + } + } else if(index == startIndex) { + handler.post(new Runnable() { + @Override + public void run() { + Util.toast(DownloadService.this, R.string.download_playerstate_mobile_disabled); + } + }); + return -1; + } - next = downloadList.get(index); - } - } - } + next = downloadList.get(index); + } + } + } - return index; - } + return index; + } - public DownloadFile getCurrentPlaying() { - return currentPlaying; - } + public DownloadFile getCurrentPlaying() { + return currentPlaying; + } - public DownloadFile getCurrentDownloading() { - return currentDownloading; - } + public DownloadFile getCurrentDownloading() { + return currentDownloading; + } - public DownloadFile getNextPlaying() { - return nextPlaying; - } + public DownloadFile getNextPlaying() { + return nextPlaying; + } - public List getSongs() { - return downloadList; - } + public List getSongs() { + return downloadList; + } - public List getToDelete() { return toDelete; } + public List getToDelete() { return toDelete; } - public synchronized List getDownloads() { - List temp = new ArrayList(); - temp.addAll(downloadList); - temp.addAll(backgroundDownloadList); - return temp; - } + public synchronized List getDownloads() { + List temp = new ArrayList(); + temp.addAll(downloadList); + temp.addAll(backgroundDownloadList); + return temp; + } - public List getBackgroundDownloads() { - return backgroundDownloadList; - } + public List getBackgroundDownloads() { + return backgroundDownloadList; + } - /** Plays either the current song (resume) or the first/next one in queue. */ - public synchronized void play() - { - int current = getCurrentPlayingIndex(); - if (current == -1) { - play(0); - } else { - play(current); - } - } + /** Plays either the current song (resume) or the first/next one in queue. */ + public synchronized void play() + { + int current = getCurrentPlayingIndex(); + if (current == -1) { + play(0); + } else { + play(current); + } + } - public synchronized void play(int index) { - play(index, true); - } - public synchronized void play(DownloadFile downloadFile) { - play(downloadList.indexOf(downloadFile)); - } - private synchronized void play(int index, boolean start) { - play(index, start, 0); - } - private synchronized void play(int index, boolean start, int position) { - int size = this.size(); - cachedPosition = 0; - if (index < 0 || index >= size) { - reset(); - if(index >= size && size != 0) { - setCurrentPlaying(0, false); - Notifications.hidePlayingNotification(this, this, handler); - } else { - setCurrentPlaying(null, false); - } - lifecycleSupport.serializeDownloadQueue(); - } else { - if(nextPlayingTask != null) { - nextPlayingTask.cancel(); - nextPlayingTask = null; - } - setCurrentPlaying(index, start); + public synchronized void play(int index) { + play(index, true); + } + public synchronized void play(DownloadFile downloadFile) { + play(downloadList.indexOf(downloadFile)); + } + private synchronized void play(int index, boolean start) { + play(index, start, 0); + } + private synchronized void play(int index, boolean start, int position) { + int size = this.size(); + cachedPosition = 0; + if (index < 0 || index >= size) { + reset(); + if(index >= size && size != 0) { + setCurrentPlaying(0, false); + Notifications.hidePlayingNotification(this, this, handler); + } else { + setCurrentPlaying(null, false); + } + lifecycleSupport.serializeDownloadQueue(); + } else { + if(nextPlayingTask != null) { + nextPlayingTask.cancel(); + nextPlayingTask = null; + } + setCurrentPlaying(index, start); bufferAndPlay(position, start); checkDownloads(); setNextPlaying(); - } - } - private synchronized void playNext() { - if(nextPlaying != null && nextPlayerState == PlayerState.PREPARED) { - if(!nextSetup) { - playNext(true); - } else { - nextSetup = false; - playNext(false); - } - } else { - onSongCompleted(); - } - } - private synchronized void playNext(boolean start) { - Util.broadcastPlaybackStatusChange(this, currentPlaying.getSong(), PlayerState.PREPARED); - - // Swap the media players since nextMediaPlayer is ready to play - subtractPosition = 0; - if(start) { - nextMediaPlayer.start(); - } else if(!nextMediaPlayer.isPlaying()) { - Log.w(TAG, "nextSetup lied about it's state!"); - nextMediaPlayer.start(); - } else { - Log.i(TAG, "nextMediaPlayer already playing"); - - // Next time the cachedPosition is updated, use that as position 0 - subtractNextPosition = System.currentTimeMillis(); - } - MediaPlayer tmp = mediaPlayer; - mediaPlayer = nextMediaPlayer; - nextMediaPlayer = tmp; - setCurrentPlaying(nextPlaying, true); - setPlayerState(PlayerState.STARTED); - setupHandlers(currentPlaying, false, start); - setNextPlaying(); + } + } + private synchronized void playNext() { + if(nextPlaying != null && nextPlayerState == PlayerState.PREPARED) { + if(!nextSetup) { + playNext(true); + } else { + nextSetup = false; + playNext(false); + } + } else { + onSongCompleted(); + } + } + private synchronized void playNext(boolean start) { + Util.broadcastPlaybackStatusChange(this, currentPlaying.getSong(), PlayerState.PREPARED); - // Proxy should not be being used here since the next player was already setup to play - if(proxy != null) { - proxy.stop(); - proxy = null; - } - checkDownloads(); - updateRemotePlaylist(); - } + // Swap the media players since nextMediaPlayer is ready to play + subtractPosition = 0; + if(start) { + nextMediaPlayer.start(); + } else if(!nextMediaPlayer.isPlaying()) { + Log.w(TAG, "nextSetup lied about it's state!"); + nextMediaPlayer.start(); + } else { + Log.i(TAG, "nextMediaPlayer already playing"); - /** Plays or resumes the playback, depending on the current player state. */ - public synchronized void togglePlayPause() { - if (playerState == PAUSED || playerState == COMPLETED || playerState == STOPPED) { - start(); - } else if (playerState == STOPPED || playerState == IDLE) { - autoPlayStart = true; - play(); - } else if (playerState == STARTED) { - pause(); - } - } + // Next time the cachedPosition is updated, use that as position 0 + subtractNextPosition = System.currentTimeMillis(); + } + MediaPlayer tmp = mediaPlayer; + mediaPlayer = nextMediaPlayer; + nextMediaPlayer = tmp; + setCurrentPlaying(nextPlaying, true); + setPlayerState(PlayerState.STARTED); + setupHandlers(currentPlaying, false, start); + setNextPlaying(); - public synchronized void seekTo(int position) { - if(position < 0) { - position = 0; - } + // Proxy should not be being used here since the next player was already setup to play + if(proxy != null) { + proxy.stop(); + proxy = null; + } + checkDownloads(); + updateRemotePlaylist(); + } - try { + /** Plays or resumes the playback, depending on the current player state. */ + public synchronized void togglePlayPause() { + if (playerState == PAUSED || playerState == COMPLETED || playerState == STOPPED) { + start(); + } else if (playerState == STOPPED || playerState == IDLE) { + autoPlayStart = true; + play(); + } else if (playerState == STARTED) { + pause(); + } + } + + public synchronized void seekTo(int position) { + if(position < 0) { + position = 0; + } + + try { if(proxy != null && currentPlaying.isCompleteFileAvailable()) { doPlay(currentPlaying, position, playerState == STARTED); return; @@ -983,472 +983,472 @@ public class DownloadService extends Service { mediaPlayer.seekTo(position); subtractPosition = 0; - cachedPosition = position; + cachedPosition = position; - onSongProgress(); - if(playerState == PAUSED) { - lifecycleSupport.serializeDownloadQueue(); - } - } catch (Exception x) { - handleError(x); - } - } - public synchronized int rewind() { - return seekToWrapper(-REWIND); - } - public synchronized int fastForward() { - return seekToWrapper(FAST_FORWARD); - } - protected int seekToWrapper(int difference) { - int msPlayed = Math.max(0, getPlayerPosition()); - Integer duration = getPlayerDuration(); - int msTotal = duration == null ? 0 : duration; + onSongProgress(); + if(playerState == PAUSED) { + lifecycleSupport.serializeDownloadQueue(); + } + } catch (Exception x) { + handleError(x); + } + } + public synchronized int rewind() { + return seekToWrapper(-REWIND); + } + public synchronized int fastForward() { + return seekToWrapper(FAST_FORWARD); + } + protected int seekToWrapper(int difference) { + int msPlayed = Math.max(0, getPlayerPosition()); + Integer duration = getPlayerDuration(); + int msTotal = duration == null ? 0 : duration; - int seekTo; - if(msPlayed + difference > msTotal) { - seekTo = msTotal; - } else { - seekTo = msPlayed + difference; - } - seekTo(seekTo); + int seekTo; + if(msPlayed + difference > msTotal) { + seekTo = msTotal; + } else { + seekTo = msPlayed + difference; + } + seekTo(seekTo); - return seekTo; - } + return seekTo; + } - public synchronized void previous() { - int index = getCurrentPlayingIndex(); - if (index == -1) { - return; - } + public synchronized void previous() { + int index = getCurrentPlayingIndex(); + if (index == -1) { + return; + } - // If only one song, just skip within song - if(size() == 1 || (currentPlaying != null && !currentPlaying.isSong())) { - rewind(); - return; - } + // If only one song, just skip within song + if(size() == 1 || (currentPlaying != null && !currentPlaying.isSong())) { + rewind(); + return; + } - // Restart song if played more than five seconds. - if (getPlayerPosition() > 5000 || (index == 0 && getRepeatMode() != RepeatMode.ALL)) { - seekTo(0); - } else { - if(index == 0) { - index = size(); - } + // Restart song if played more than five seconds. + if (getPlayerPosition() > 5000 || (index == 0 && getRepeatMode() != RepeatMode.ALL)) { + seekTo(0); + } else { + if(index == 0) { + index = size(); + } - play(index - 1, playerState != PAUSED && playerState != STOPPED && playerState != IDLE); - } - } + play(index - 1, playerState != PAUSED && playerState != STOPPED && playerState != IDLE); + } + } - public synchronized void next() { - next(false); - } - public synchronized void next(boolean forceCutoff) { - next(forceCutoff, false); - } - public synchronized void next(boolean forceCutoff, boolean forceStart) { - // If only one song, just skip within song - if(size() == 1 || (currentPlaying != null && !currentPlaying.isSong())) { - fastForward(); - return; - } else if(playerState == PREPARING || playerState == PREPARED) { - return; - } + public synchronized void next() { + next(false); + } + public synchronized void next(boolean forceCutoff) { + next(forceCutoff, false); + } + public synchronized void next(boolean forceCutoff, boolean forceStart) { + // If only one song, just skip within song + if(size() == 1 || (currentPlaying != null && !currentPlaying.isSong())) { + fastForward(); + return; + } else if(playerState == PREPARING || playerState == PREPARED) { + return; + } - // Delete podcast if fully listened to - int position = getPlayerPosition(); - int duration = getPlayerDuration(); - boolean cutoff; - if(forceCutoff) { - cutoff = true; - } else { - cutoff = isPastCutoff(position, duration); - } + // Delete podcast if fully listened to + int position = getPlayerPosition(); + int duration = getPlayerDuration(); + boolean cutoff; + if(forceCutoff) { + cutoff = true; + } else { + cutoff = isPastCutoff(position, duration); + } - int index = getCurrentPlayingIndex(); - int nextPlayingIndex = getNextPlayingIndex(); - // Make sure to actually go to next when repeat song is on - if(index == nextPlayingIndex) { - nextPlayingIndex++; - } - if (index != -1 && nextPlayingIndex < size()) { - play(nextPlayingIndex, playerState != PAUSED && playerState != STOPPED && playerState != IDLE || forceStart); - } - } + int index = getCurrentPlayingIndex(); + int nextPlayingIndex = getNextPlayingIndex(); + // Make sure to actually go to next when repeat song is on + if(index == nextPlayingIndex) { + nextPlayingIndex++; + } + if (index != -1 && nextPlayingIndex < size()) { + play(nextPlayingIndex, playerState != PAUSED && playerState != STOPPED && playerState != IDLE || forceStart); + } + } - public void onSongCompleted() { - setPlayerStateCompleted(); - postPlayCleanup(); - play(getNextPlayingIndex()); - } - public void onNextStarted(DownloadFile nextPlaying) { - setPlayerStateCompleted(); - postPlayCleanup(); - setCurrentPlaying(nextPlaying, true); - setPlayerState(STARTED); - setNextPlayerState(IDLE); - } + public void onSongCompleted() { + setPlayerStateCompleted(); + postPlayCleanup(); + play(getNextPlayingIndex()); + } + public void onNextStarted(DownloadFile nextPlaying) { + setPlayerStateCompleted(); + postPlayCleanup(); + setCurrentPlaying(nextPlaying, true); + setPlayerState(STARTED); + setNextPlayerState(IDLE); + } - public synchronized void pause() { - pause(false); - } - public synchronized void pause(boolean temp) { - try { - if (playerState == STARTED) { + public synchronized void pause() { + pause(false); + } + public synchronized void pause(boolean temp) { + try { + if (playerState == STARTED) { mediaPlayer.pause(); - setPlayerState(temp ? PAUSED_TEMP : PAUSED); - } else if(playerState == PAUSED_TEMP) { - setPlayerState(temp ? PAUSED_TEMP : PAUSED); - } - } catch (Exception x) { - handleError(x); - } - } + setPlayerState(temp ? PAUSED_TEMP : PAUSED); + } else if(playerState == PAUSED_TEMP) { + setPlayerState(temp ? PAUSED_TEMP : PAUSED); + } + } catch (Exception x) { + handleError(x); + } + } - public synchronized void stop() { - try { - if (playerState == STARTED) { + public synchronized void stop() { + try { + if (playerState == STARTED) { mediaPlayer.pause(); setPlayerState(STOPPED); - } else if(playerState == PAUSED) { - setPlayerState(STOPPED); - } - } catch(Exception x) { - handleError(x); - } - } + } else if(playerState == PAUSED) { + setPlayerState(STOPPED); + } + } catch(Exception x) { + handleError(x); + } + } - public synchronized void start() { - try { + public synchronized void start() { + try { // Only start if done preparing if(playerState != PREPARING) { mediaPlayer.start(); } else { // Otherwise, we need to set it up to start when done preparing autoPlayStart = true; - } - setPlayerState(STARTED); - } catch (Exception x) { - handleError(x); - } - } + } + setPlayerState(STARTED); + } catch (Exception x) { + handleError(x); + } + } - public synchronized void reset() { - if (bufferTask != null) { - bufferTask.cancel(); - bufferTask = null; - } - try { + public synchronized void reset() { + if (bufferTask != null) { + bufferTask.cancel(); + bufferTask = null; + } + try { setPlayerState(IDLE); - mediaPlayer.setOnErrorListener(null); - mediaPlayer.setOnCompletionListener(null); - if(nextSetup) { - mediaPlayer.setNextMediaPlayer(null); - nextSetup = false; - } - mediaPlayer.reset(); - subtractPosition = 0; - } catch (Exception x) { - handleError(x); - } - } + mediaPlayer.setOnErrorListener(null); + mediaPlayer.setOnCompletionListener(null); + if(nextSetup) { + mediaPlayer.setNextMediaPlayer(null); + nextSetup = false; + } + mediaPlayer.reset(); + subtractPosition = 0; + } catch (Exception x) { + handleError(x); + } + } - public synchronized void resetNext() { - try { - if (nextMediaPlayer != null) { - if (nextSetup) { - mediaPlayer.setNextMediaPlayer(null); - } - nextSetup = false; + public synchronized void resetNext() { + try { + if (nextMediaPlayer != null) { + if (nextSetup) { + mediaPlayer.setNextMediaPlayer(null); + } + nextSetup = false; - nextMediaPlayer.setOnCompletionListener(null); - nextMediaPlayer.setOnErrorListener(null); - nextMediaPlayer.reset(); - nextMediaPlayer.release(); - nextMediaPlayer = null; - } else if(nextSetup) { - nextSetup = false; - } - } catch (Exception e) { - Log.w(TAG, "Failed to reset next media player"); - } - } + nextMediaPlayer.setOnCompletionListener(null); + nextMediaPlayer.setOnErrorListener(null); + nextMediaPlayer.reset(); + nextMediaPlayer.release(); + nextMediaPlayer = null; + } else if(nextSetup) { + nextSetup = false; + } + } catch (Exception e) { + Log.w(TAG, "Failed to reset next media player"); + } + } - public int getPlayerPosition() { - try { - if (playerState == IDLE || playerState == DOWNLOADING || playerState == PREPARING) { - return 0; - } + public int getPlayerPosition() { + try { + if (playerState == IDLE || playerState == DOWNLOADING || playerState == PREPARING) { + return 0; + } return Math.max(0, cachedPosition - subtractPosition); - } catch (Exception x) { - handleError(x); - return 0; - } - } + } catch (Exception x) { + handleError(x); + return 0; + } + } - public synchronized int getPlayerDuration() { - if (playerState != IDLE && playerState != DOWNLOADING && playerState != PlayerState.PREPARING) { - int duration = 0; + public synchronized int getPlayerDuration() { + if (playerState != IDLE && playerState != DOWNLOADING && playerState != PlayerState.PREPARING) { + int duration = 0; try { duration = mediaPlayer.getDuration(); } catch (Exception x) { duration = 0; } - if(duration != 0) { - return duration; - } - } + if(duration != 0) { + return duration; + } + } - if (currentPlaying != null) { - Integer duration = currentPlaying.getSong().getDuration(); - if (duration != null) { - return duration * 1000; - } - } + if (currentPlaying != null) { + Integer duration = currentPlaying.getSong().getDuration(); + if (duration != null) { + return duration * 1000; + } + } - return 0; - } + return 0; + } - public PlayerState getPlayerState() { - return playerState; - } + public PlayerState getPlayerState() { + return playerState; + } - public PlayerState getNextPlayerState() { - return nextPlayerState; - } + public PlayerState getNextPlayerState() { + return nextPlayerState; + } - public synchronized void setPlayerState(final PlayerState playerState) { - Log.i(TAG, this.playerState.name() + " -> " + playerState.name() + " (" + currentPlaying + ")"); + public synchronized void setPlayerState(final PlayerState playerState) { + Log.i(TAG, this.playerState.name() + " -> " + playerState.name() + " (" + currentPlaying + ")"); - if (playerState == PAUSED) { - lifecycleSupport.serializeDownloadQueue(); - } + if (playerState == PAUSED) { + lifecycleSupport.serializeDownloadQueue(); + } - boolean show = playerState == PlayerState.STARTED; - boolean pause = playerState == PlayerState.PAUSED; - boolean hide = playerState == PlayerState.STOPPED; - Util.broadcastPlaybackStatusChange(this, (currentPlaying != null) ? currentPlaying.getSong() : null, playerState); + boolean show = playerState == PlayerState.STARTED; + boolean pause = playerState == PlayerState.PAUSED; + boolean hide = playerState == PlayerState.STOPPED; + Util.broadcastPlaybackStatusChange(this, (currentPlaying != null) ? currentPlaying.getSong() : null, playerState); - this.playerState = playerState; + this.playerState = playerState; - if(playerState == STARTED) { - Util.requestAudioFocus(this); - } + if(playerState == STARTED) { + Util.requestAudioFocus(this); + } - if (show) { - Notifications.showPlayingNotification(this, this, handler, currentPlaying.getSong()); - } else if (pause) { - SharedPreferences prefs = Util.getPreferences(this); - if(prefs.getBoolean(Constants.PREFERENCES_KEY_PERSISTENT_NOTIFICATION, false)) { - Notifications.showPlayingNotification(this, this, handler, currentPlaying.getSong()); - } else { - Notifications.hidePlayingNotification(this, this, handler); - } - } else if(hide) { - Notifications.hidePlayingNotification(this, this, handler); - } - if(playerState == STARTED && positionCache == null) { + if (show) { + Notifications.showPlayingNotification(this, this, handler, currentPlaying.getSong()); + } else if (pause) { + SharedPreferences prefs = Util.getPreferences(this); + if(prefs.getBoolean(Constants.PREFERENCES_KEY_PERSISTENT_NOTIFICATION, false)) { + Notifications.showPlayingNotification(this, this, handler, currentPlaying.getSong()); + } else { + Notifications.hidePlayingNotification(this, this, handler); + } + } else if(hide) { + Notifications.hidePlayingNotification(this, this, handler); + } + if(playerState == STARTED && positionCache == null) { positionCache = new LocalPositionCache(); - Thread thread = new Thread(positionCache, "PositionCache"); - thread.start(); - } else if(playerState != STARTED && positionCache != null) { - positionCache.stop(); - positionCache = null; - } + Thread thread = new Thread(positionCache, "PositionCache"); + thread.start(); + } else if(playerState != STARTED && positionCache != null) { + positionCache.stop(); + positionCache = null; + } - onStateUpdate(); - } + onStateUpdate(); + } - public void setPlayerStateCompleted() { - // Acquire a temporary wakelock - acquireWakelock(); + public void setPlayerStateCompleted() { + // Acquire a temporary wakelock + acquireWakelock(); - Log.i(TAG, this.playerState.name() + " -> " + PlayerState.COMPLETED + " (" + currentPlaying + ")"); - this.playerState = PlayerState.COMPLETED; - if(positionCache != null) { - positionCache.stop(); - positionCache = null; - } + Log.i(TAG, this.playerState.name() + " -> " + PlayerState.COMPLETED + " (" + currentPlaying + ")"); + this.playerState = PlayerState.COMPLETED; + if(positionCache != null) { + positionCache.stop(); + positionCache = null; + } - onStateUpdate(); - } + onStateUpdate(); + } - private class PositionCache implements Runnable { - boolean isRunning = true; + private class PositionCache implements Runnable { + boolean isRunning = true; - public void stop() { - isRunning = false; - } + public void stop() { + isRunning = false; + } - @Override - public void run() { - // Stop checking position before the song reaches completion - while(isRunning) { - try { - onSongProgress(); - Thread.sleep(delayUpdateProgress); - } - catch(Exception e) { - isRunning = false; - positionCache = null; - } - } - } - } - private class LocalPositionCache extends PositionCache { - boolean isRunning = true; + @Override + public void run() { + // Stop checking position before the song reaches completion + while(isRunning) { + try { + onSongProgress(); + Thread.sleep(delayUpdateProgress); + } + catch(Exception e) { + isRunning = false; + positionCache = null; + } + } + } + } + private class LocalPositionCache extends PositionCache { + boolean isRunning = true; - public void stop() { - isRunning = false; - } + public void stop() { + isRunning = false; + } - @Override - public void run() { - // Stop checking position before the song reaches completion - while(isRunning) { - try { - if(mediaPlayer != null && playerState == STARTED) { - int newPosition = mediaPlayer.getCurrentPosition(); + @Override + public void run() { + // Stop checking position before the song reaches completion + while(isRunning) { + try { + if(mediaPlayer != null && playerState == STARTED) { + int newPosition = mediaPlayer.getCurrentPosition(); - // If sudden jump in position, something is wrong - if(subtractNextPosition == 0 && newPosition > (cachedPosition + 5000)) { - // Only 1 second should have gone by, subtract the rest - subtractPosition += (newPosition - cachedPosition) - 1000; - } + // If sudden jump in position, something is wrong + if(subtractNextPosition == 0 && newPosition > (cachedPosition + 5000)) { + // Only 1 second should have gone by, subtract the rest + subtractPosition += (newPosition - cachedPosition) - 1000; + } - cachedPosition = newPosition; + cachedPosition = newPosition; - if(subtractNextPosition > 0) { - // Subtraction amount is current position - how long ago onCompletionListener was called - subtractPosition = cachedPosition - (int) (System.currentTimeMillis() - subtractNextPosition); - if(subtractPosition < 0) { - subtractPosition = 0; - } - subtractNextPosition = 0; - } - } - onSongProgress(cachedPosition < 2000 ? true: false); - Thread.sleep(delayUpdateProgress); - } - catch(Exception e) { - Log.w(TAG, "Crashed getting current position", e); - isRunning = false; - positionCache = null; - } - } - } - } + if(subtractNextPosition > 0) { + // Subtraction amount is current position - how long ago onCompletionListener was called + subtractPosition = cachedPosition - (int) (System.currentTimeMillis() - subtractNextPosition); + if(subtractPosition < 0) { + subtractPosition = 0; + } + subtractNextPosition = 0; + } + } + onSongProgress(cachedPosition < 2000 ? true: false); + Thread.sleep(delayUpdateProgress); + } + catch(Exception e) { + Log.w(TAG, "Crashed getting current position", e); + isRunning = false; + positionCache = null; + } + } + } + } - public synchronized void setNextPlayerState(PlayerState playerState) { - Log.i(TAG, "Next: " + this.nextPlayerState.name() + " -> " + playerState.name() + " (" + nextPlaying + ")"); - this.nextPlayerState = playerState; - } + public synchronized void setNextPlayerState(PlayerState playerState) { + Log.i(TAG, "Next: " + this.nextPlayerState.name() + " -> " + playerState.name() + " (" + nextPlaying + ")"); + this.nextPlayerState = playerState; + } - public void setSuggestedPlaylistName(String name, String id) { - this.suggestedPlaylistName = name; - this.suggestedPlaylistId = id; + public void setSuggestedPlaylistName(String name, String id) { + this.suggestedPlaylistName = name; + this.suggestedPlaylistId = id; - SharedPreferences.Editor editor = Util.getPreferences(this).edit(); - editor.putString(Constants.PREFERENCES_KEY_PLAYLIST_NAME, name); - editor.putString(Constants.PREFERENCES_KEY_PLAYLIST_ID, id); - editor.commit(); - } + SharedPreferences.Editor editor = Util.getPreferences(this).edit(); + editor.putString(Constants.PREFERENCES_KEY_PLAYLIST_NAME, name); + editor.putString(Constants.PREFERENCES_KEY_PLAYLIST_ID, id); + editor.commit(); + } - public String getSuggestedPlaylistName() { - return suggestedPlaylistName; - } + public String getSuggestedPlaylistName() { + return suggestedPlaylistName; + } - public String getSuggestedPlaylistId() { - return suggestedPlaylistId; - } + public String getSuggestedPlaylistId() { + return suggestedPlaylistId; + } - public EqualizerController getEqualizerController() { - EqualizerController controller = null; - try { - controller = effectsController.getEqualizerController(); - if(controller.getEqualizer() == null) { - throw new Exception("Failed to get EQ"); - } - } catch(Exception e) { - Log.w(TAG, "Failed to start EQ, retrying with new mediaPlayer: " + e); + public EqualizerController getEqualizerController() { + EqualizerController controller = null; + try { + controller = effectsController.getEqualizerController(); + if(controller.getEqualizer() == null) { + throw new Exception("Failed to get EQ"); + } + } catch(Exception e) { + Log.w(TAG, "Failed to start EQ, retrying with new mediaPlayer: " + e); - // If we failed, we are going to try to reinitialize the MediaPlayer - boolean playing = playerState == STARTED; - int pos = getPlayerPosition(); - mediaPlayer.pause(); - Util.sleepQuietly(10L); - reset(); + // If we failed, we are going to try to reinitialize the MediaPlayer + boolean playing = playerState == STARTED; + int pos = getPlayerPosition(); + mediaPlayer.pause(); + Util.sleepQuietly(10L); + reset(); - try { - // Resetup media player - mediaPlayer.setAudioSessionId(audioSessionId); - mediaPlayer.setDataSource(currentPlaying.getFile().getCanonicalPath()); + try { + // Resetup media player + mediaPlayer.setAudioSessionId(audioSessionId); + mediaPlayer.setDataSource(currentPlaying.getFile().getCanonicalPath()); - controller = effectsController.getEqualizerController(); - if(controller.getEqualizer() == null) { - throw new Exception("Failed to get EQ"); - } - } catch(Exception e2) { - Log.w(TAG, "Failed to setup EQ even after reinitialization"); - // Don't try again, just resetup media player and continue on - controller = null; - } + controller = effectsController.getEqualizerController(); + if(controller.getEqualizer() == null) { + throw new Exception("Failed to get EQ"); + } + } catch(Exception e2) { + Log.w(TAG, "Failed to setup EQ even after reinitialization"); + // Don't try again, just resetup media player and continue on + controller = null; + } - // Restart from same position and state we left off in - play(getCurrentPlayingIndex(), false, pos); - } + // Restart from same position and state we left off in + play(getCurrentPlayingIndex(), false, pos); + } - return controller; - } + return controller; + } - public boolean isSeekable() { + public boolean isSeekable() { return currentPlaying != null && currentPlaying.isWorkDone() && playerState != PREPARING; - } + } - public void updateRemoteVolume(boolean up) { - AudioManager audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE); - audioManager.adjustVolume(up ? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER, AudioManager.FLAG_SHOW_UI); - } + public void updateRemoteVolume(boolean up) { + AudioManager audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE); + audioManager.adjustVolume(up ? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER, AudioManager.FLAG_SHOW_UI); + } - private synchronized void bufferAndPlay() { - bufferAndPlay(0); - } - private synchronized void bufferAndPlay(int position) { - bufferAndPlay(position, true); - } - private synchronized void bufferAndPlay(int position, boolean start) { - if(!currentPlaying.isCompleteFileAvailable()) { - if(Util.isAllowedToDownload(this)) { - reset(); + private synchronized void bufferAndPlay() { + bufferAndPlay(0); + } + private synchronized void bufferAndPlay(int position) { + bufferAndPlay(position, true); + } + private synchronized void bufferAndPlay(int position, boolean start) { + if(!currentPlaying.isCompleteFileAvailable()) { + if(Util.isAllowedToDownload(this)) { + reset(); - bufferTask = new BufferTask(currentPlaying, position, start); - bufferTask.execute(); - } else { - next(false, start); - } - } else { - doPlay(currentPlaying, position, start); - } - } + bufferTask = new BufferTask(currentPlaying, position, start); + bufferTask.execute(); + } else { + next(false, start); + } + } else { + doPlay(currentPlaying, position, start); + } + } - private synchronized void doPlay(final DownloadFile downloadFile, final int position, final boolean start) { - try { - subtractPosition = 0; - mediaPlayer.setOnCompletionListener(null); - mediaPlayer.setOnPreparedListener(null); - mediaPlayer.setOnErrorListener(null); - mediaPlayer.reset(); - setPlayerState(IDLE); - try { - mediaPlayer.setAudioSessionId(audioSessionId); - } catch(Throwable e) { - mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); - } + private synchronized void doPlay(final DownloadFile downloadFile, final int position, final boolean start) { + try { + subtractPosition = 0; + mediaPlayer.setOnCompletionListener(null); + mediaPlayer.setOnPreparedListener(null); + mediaPlayer.setOnErrorListener(null); + mediaPlayer.reset(); + setPlayerState(IDLE); + try { + mediaPlayer.setAudioSessionId(audioSessionId); + } catch(Throwable e) { + mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); + } - String dataSource; - boolean isPartial = false; + String dataSource; + boolean isPartial = false; downloadFile.setPlaying(true); final File file = downloadFile.isCompleteFileAvailable() ? downloadFile.getCompleteFile() : downloadFile.getPartialFile(); isPartial = file.equals(downloadFile.getPartialFile()); @@ -1468,797 +1468,797 @@ public class DownloadService extends Service { proxy = null; } - mediaPlayer.setDataSource(dataSource); - setPlayerState(PREPARING); + mediaPlayer.setDataSource(dataSource); + setPlayerState(PREPARING); - mediaPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() { - public void onBufferingUpdate(MediaPlayer mp, int percent) { - Log.i(TAG, "Buffered " + percent + "%"); - if (percent == 100) { - mediaPlayer.setOnBufferingUpdateListener(null); - } - } - }); + mediaPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() { + public void onBufferingUpdate(MediaPlayer mp, int percent) { + Log.i(TAG, "Buffered " + percent + "%"); + if (percent == 100) { + mediaPlayer.setOnBufferingUpdateListener(null); + } + } + }); - mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { - public void onPrepared(MediaPlayer mediaPlayer) { - try { - setPlayerState(PREPARED); + mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { + public void onPrepared(MediaPlayer mediaPlayer) { + try { + setPlayerState(PREPARED); - synchronized (DownloadService.this) { - if (position != 0) { - Log.i(TAG, "Restarting player from position " + position); - mediaPlayer.seekTo(position); - } - cachedPosition = position; + synchronized (DownloadService.this) { + if (position != 0) { + Log.i(TAG, "Restarting player from position " + position); + mediaPlayer.seekTo(position); + } + cachedPosition = position; - applyReplayGain(mediaPlayer, downloadFile); + applyReplayGain(mediaPlayer, downloadFile); - if (start || autoPlayStart) { - mediaPlayer.start(); - setPlayerState(STARTED); + if (start || autoPlayStart) { + mediaPlayer.start(); + setPlayerState(STARTED); - // Disable autoPlayStart after done - autoPlayStart = false; - } else { - setPlayerState(PAUSED); - onSongProgress(); - } + // Disable autoPlayStart after done + autoPlayStart = false; + } else { + setPlayerState(PAUSED); + onSongProgress(); + } - updateRemotePlaylist(); - } + updateRemotePlaylist(); + } - // Only call when starting, setPlayerState(PAUSED) already calls this - if(start) { - lifecycleSupport.serializeDownloadQueue(); - } - } catch (Exception x) { - handleError(x); - } - } - }); + // Only call when starting, setPlayerState(PAUSED) already calls this + if(start) { + lifecycleSupport.serializeDownloadQueue(); + } + } catch (Exception x) { + handleError(x); + } + } + }); - setupHandlers(downloadFile, isPartial, start); + setupHandlers(downloadFile, isPartial, start); - mediaPlayer.prepareAsync(); - } catch (Exception x) { - handleError(x); - } - } + mediaPlayer.prepareAsync(); + } catch (Exception x) { + handleError(x); + } + } - private synchronized void setupNext(final DownloadFile downloadFile) { - try { - final File file = downloadFile.isCompleteFileAvailable() ? downloadFile.getCompleteFile() : downloadFile.getPartialFile(); - resetNext(); + private synchronized void setupNext(final DownloadFile downloadFile) { + try { + final File file = downloadFile.isCompleteFileAvailable() ? downloadFile.getCompleteFile() : downloadFile.getPartialFile(); + resetNext(); - nextMediaPlayer = new MediaPlayer(); - nextMediaPlayer.setWakeMode(DownloadService.this, PowerManager.PARTIAL_WAKE_LOCK); - try { - nextMediaPlayer.setAudioSessionId(audioSessionId); - } catch(Throwable e) { - nextMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); - } - nextMediaPlayer.setDataSource(file.getPath()); - setNextPlayerState(PREPARING); + nextMediaPlayer = new MediaPlayer(); + nextMediaPlayer.setWakeMode(DownloadService.this, PowerManager.PARTIAL_WAKE_LOCK); + try { + nextMediaPlayer.setAudioSessionId(audioSessionId); + } catch(Throwable e) { + nextMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); + } + nextMediaPlayer.setDataSource(file.getPath()); + setNextPlayerState(PREPARING); - nextMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { - public void onPrepared(MediaPlayer mp) { - // Changed to different while preparing so ignore - if(nextMediaPlayer != mp) { - return; - } + nextMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { + public void onPrepared(MediaPlayer mp) { + // Changed to different while preparing so ignore + if(nextMediaPlayer != mp) { + return; + } - try { - setNextPlayerState(PREPARED); + try { + setNextPlayerState(PREPARED); - if(playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED) { - mediaPlayer.setNextMediaPlayer(nextMediaPlayer); - nextSetup = true; - } + if(playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED) { + mediaPlayer.setNextMediaPlayer(nextMediaPlayer); + nextSetup = true; + } - applyReplayGain(nextMediaPlayer, downloadFile); - } catch (Exception x) { - handleErrorNext(x); - } - } - }); + applyReplayGain(nextMediaPlayer, downloadFile); + } catch (Exception x) { + handleErrorNext(x); + } + } + }); - nextMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { - public boolean onError(MediaPlayer mediaPlayer, int what, int extra) { - Log.w(TAG, "Error on playing next " + "(" + what + ", " + extra + "): " + downloadFile); - return true; - } - }); + nextMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { + public boolean onError(MediaPlayer mediaPlayer, int what, int extra) { + Log.w(TAG, "Error on playing next " + "(" + what + ", " + extra + "): " + downloadFile); + return true; + } + }); - nextMediaPlayer.prepareAsync(); - } catch (Exception x) { - handleErrorNext(x); - } - } + nextMediaPlayer.prepareAsync(); + } catch (Exception x) { + handleErrorNext(x); + } + } - private void setupHandlers(final DownloadFile downloadFile, final boolean isPartial, final boolean isPlaying) { - final int duration = downloadFile.getSong().getDuration() == null ? 0 : downloadFile.getSong().getDuration() * 1000; - mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { - public boolean onError(MediaPlayer mediaPlayer, int what, int extra) { - Log.w(TAG, "Error on playing file " + "(" + what + ", " + extra + "): " + downloadFile); - int pos = getPlayerPosition(); - reset(); - if (!isPartial || (downloadFile.isWorkDone() && (Math.abs(duration - pos) < 10000))) { - playNext(); - } else { - downloadFile.setPlaying(false); - doPlay(downloadFile, pos, isPlaying); - downloadFile.setPlaying(true); - } - return true; - } - }); + private void setupHandlers(final DownloadFile downloadFile, final boolean isPartial, final boolean isPlaying) { + final int duration = downloadFile.getSong().getDuration() == null ? 0 : downloadFile.getSong().getDuration() * 1000; + mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { + public boolean onError(MediaPlayer mediaPlayer, int what, int extra) { + Log.w(TAG, "Error on playing file " + "(" + what + ", " + extra + "): " + downloadFile); + int pos = getPlayerPosition(); + reset(); + if (!isPartial || (downloadFile.isWorkDone() && (Math.abs(duration - pos) < 10000))) { + playNext(); + } else { + downloadFile.setPlaying(false); + doPlay(downloadFile, pos, isPlaying); + downloadFile.setPlaying(true); + } + return true; + } + }); - mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { - @Override - public void onCompletion(MediaPlayer mediaPlayer) { - setPlayerStateCompleted(); + mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { + @Override + public void onCompletion(MediaPlayer mediaPlayer) { + setPlayerStateCompleted(); - int pos = getPlayerPosition(); - Log.i(TAG, "Ending position " + pos + " of " + duration); - if (!isPartial || (downloadFile.isWorkDone() && (Math.abs(duration - pos) < 10000)) || nextSetup) { - playNext(); - postPlayCleanup(downloadFile); - } else { - // If file is not completely downloaded, restart the playback from the current position. - synchronized (DownloadService.this) { - if (downloadFile.isWorkDone()) { - // Complete was called early even though file is fully buffered - Log.i(TAG, "Requesting restart from " + pos + " of " + duration); - reset(); - downloadFile.setPlaying(false); - doPlay(downloadFile, pos, true); - downloadFile.setPlaying(true); - } else { - Log.i(TAG, "Requesting restart from " + pos + " of " + duration); - reset(); - bufferTask = new BufferTask(downloadFile, pos, true); - bufferTask.execute(); - } - } - checkDownloads(); - } - } - }); - } + int pos = getPlayerPosition(); + Log.i(TAG, "Ending position " + pos + " of " + duration); + if (!isPartial || (downloadFile.isWorkDone() && (Math.abs(duration - pos) < 10000)) || nextSetup) { + playNext(); + postPlayCleanup(downloadFile); + } else { + // If file is not completely downloaded, restart the playback from the current position. + synchronized (DownloadService.this) { + if (downloadFile.isWorkDone()) { + // Complete was called early even though file is fully buffered + Log.i(TAG, "Requesting restart from " + pos + " of " + duration); + reset(); + downloadFile.setPlaying(false); + doPlay(downloadFile, pos, true); + downloadFile.setPlaying(true); + } else { + Log.i(TAG, "Requesting restart from " + pos + " of " + duration); + reset(); + bufferTask = new BufferTask(downloadFile, pos, true); + bufferTask.execute(); + } + } + checkDownloads(); + } + } + }); + } - public void setVolume(float volume) { - if(mediaPlayer != null && (playerState == STARTED || playerState == PAUSED || playerState == STOPPED)) { - try { - this.volume = volume; - reapplyVolume(); - } catch(Exception e) { - Log.w(TAG, "Failed to set volume"); - } - } - } + public void setVolume(float volume) { + if(mediaPlayer != null && (playerState == STARTED || playerState == PAUSED || playerState == STOPPED)) { + try { + this.volume = volume; + reapplyVolume(); + } catch(Exception e) { + Log.w(TAG, "Failed to set volume"); + } + } + } - public void reapplyVolume() { - applyReplayGain(mediaPlayer, currentPlaying); - } + public void reapplyVolume() { + applyReplayGain(mediaPlayer, currentPlaying); + } - public synchronized void swap(boolean mainList, DownloadFile from, DownloadFile to) { - List list = mainList ? downloadList : backgroundDownloadList; - swap(mainList, list.indexOf(from), list.indexOf(to)); - } - public synchronized void swap(boolean mainList, int from, int to) { - List list = mainList ? downloadList : backgroundDownloadList; - int max = list.size(); - if(to >= max) { - to = max - 1; - } - else if(to < 0) { - to = 0; - } + public synchronized void swap(boolean mainList, DownloadFile from, DownloadFile to) { + List list = mainList ? downloadList : backgroundDownloadList; + swap(mainList, list.indexOf(from), list.indexOf(to)); + } + public synchronized void swap(boolean mainList, int from, int to) { + List list = mainList ? downloadList : backgroundDownloadList; + int max = list.size(); + if(to >= max) { + to = max - 1; + } + else if(to < 0) { + to = 0; + } - DownloadFile movedSong = list.remove(from); - list.add(to, movedSong); - currentPlayingIndex = downloadList.indexOf(currentPlaying); - if(mainList) { + DownloadFile movedSong = list.remove(from); + list.add(to, movedSong); + currentPlayingIndex = downloadList.indexOf(currentPlaying); + if(mainList) { // Moving next playing, current playing, or moving a song to be next playing if(movedSong == nextPlaying || movedSong == currentPlaying || (currentPlayingIndex + 1) == to) { setNextPlaying(); } - } - } + } + } - public synchronized void serializeQueue() { - serializeQueue(true); - } - public synchronized void serializeQueue(boolean serializeRemote) { - if(playerState == PlayerState.PAUSED) { - lifecycleSupport.serializeDownloadQueue(serializeRemote); - } - } + public synchronized void serializeQueue() { + serializeQueue(true); + } + public synchronized void serializeQueue(boolean serializeRemote) { + if(playerState == PlayerState.PAUSED) { + lifecycleSupport.serializeDownloadQueue(serializeRemote); + } + } - private void handleError(Exception x) { - Log.w(TAG, "Media player error: " + x, x); - if(mediaPlayer != null) { - try { - mediaPlayer.reset(); - } catch(Exception e) { - Log.e(TAG, "Failed to reset player in error handler"); - } - } - setPlayerState(IDLE); - } - private void handleErrorNext(Exception x) { - Log.w(TAG, "Next Media player error: " + x, x); - try { - nextMediaPlayer.reset(); - } catch(Exception e) { - Log.e(TAG, "Failed to reset next media player", x); - } - setNextPlayerState(IDLE); - } + private void handleError(Exception x) { + Log.w(TAG, "Media player error: " + x, x); + if(mediaPlayer != null) { + try { + mediaPlayer.reset(); + } catch(Exception e) { + Log.e(TAG, "Failed to reset player in error handler"); + } + } + setPlayerState(IDLE); + } + private void handleErrorNext(Exception x) { + Log.w(TAG, "Next Media player error: " + x, x); + try { + nextMediaPlayer.reset(); + } catch(Exception e) { + Log.e(TAG, "Failed to reset next media player", x); + } + setNextPlayerState(IDLE); + } - public synchronized void checkDownloads() { - if (!Util.isExternalStoragePresent() || !lifecycleSupport.isExternalStorageAvailable()) { - return; - } + public synchronized void checkDownloads() { + if (!Util.isExternalStoragePresent() || !lifecycleSupport.isExternalStorageAvailable()) { + return; + } - if(removePlayed) { - checkRemovePlayed(); - } - if (shufflePlay) { - checkShufflePlay(); - } + if(removePlayed) { + checkRemovePlayed(); + } + if (shufflePlay) { + checkShufflePlay(); + } - if (!Util.isAllowedToDownload(this)) { - return; - } + if (!Util.isAllowedToDownload(this)) { + return; + } - if (downloadList.isEmpty() && backgroundDownloadList.isEmpty()) { - return; - } + if (downloadList.isEmpty() && backgroundDownloadList.isEmpty()) { + return; + } - // Need to download current playing? - if (currentPlaying != null && currentPlaying != currentDownloading && !currentPlaying.isWorkDone()) { - // Cancel current download, if necessary. - if (currentDownloading != null) { - currentDownloading.cancelDownload(); - } + // Need to download current playing? + if (currentPlaying != null && currentPlaying != currentDownloading && !currentPlaying.isWorkDone()) { + // Cancel current download, if necessary. + if (currentDownloading != null) { + currentDownloading.cancelDownload(); + } - currentDownloading = currentPlaying; - currentDownloading.download(); - cleanupCandidates.add(currentDownloading); - } + currentDownloading = currentPlaying; + currentDownloading.download(); + cleanupCandidates.add(currentDownloading); + } - // Find a suitable target for download. - else if (currentDownloading == null || currentDownloading.isWorkDone() || currentDownloading.isFailed() && (!downloadList.isEmpty() || !backgroundDownloadList.isEmpty())) { - currentDownloading = null; - int n = size(); + // Find a suitable target for download. + else if (currentDownloading == null || currentDownloading.isWorkDone() || currentDownloading.isFailed() && (!downloadList.isEmpty() || !backgroundDownloadList.isEmpty())) { + currentDownloading = null; + int n = size(); - int preloaded = 0; + int preloaded = 0; - if(n != 0) { - int start = currentPlaying == null ? 0 : getCurrentPlayingIndex(); - if(start == -1) { - start = 0; - } - int i = start; - do { - DownloadFile downloadFile = downloadList.get(i); - if (!downloadFile.isWorkDone() && !downloadFile.isFailedMax()) { - if (downloadFile.shouldSave() || preloaded < Util.getPreloadCount(this)) { - currentDownloading = downloadFile; - currentDownloading.download(); - cleanupCandidates.add(currentDownloading); - if(i == (start + 1)) { - setNextPlayerState(DOWNLOADING); - } - break; - } - } else if (currentPlaying != downloadFile) { - preloaded++; - } + if(n != 0) { + int start = currentPlaying == null ? 0 : getCurrentPlayingIndex(); + if(start == -1) { + start = 0; + } + int i = start; + do { + DownloadFile downloadFile = downloadList.get(i); + if (!downloadFile.isWorkDone() && !downloadFile.isFailedMax()) { + if (downloadFile.shouldSave() || preloaded < Util.getPreloadCount(this)) { + currentDownloading = downloadFile; + currentDownloading.download(); + cleanupCandidates.add(currentDownloading); + if(i == (start + 1)) { + setNextPlayerState(DOWNLOADING); + } + break; + } + } else if (currentPlaying != downloadFile) { + preloaded++; + } - i = (i + 1) % n; - } while (i != start); - } + i = (i + 1) % n; + } while (i != start); + } - if((preloaded + 1 == n || preloaded >= Util.getPreloadCount(this) || downloadList.isEmpty()) && !backgroundDownloadList.isEmpty()) { - for(int i = 0; i < backgroundDownloadList.size(); i++) { - DownloadFile downloadFile = backgroundDownloadList.get(i); - if(downloadFile.isWorkDone() && (!downloadFile.shouldSave() || downloadFile.isSaved()) || downloadFile.isFailedMax()) { - // Don't need to keep list like active song list - backgroundDownloadList.remove(i); - revision++; - i--; - } else { - currentDownloading = downloadFile; - currentDownloading.download(); - cleanupCandidates.add(currentDownloading); - break; - } - } - } - } + if((preloaded + 1 == n || preloaded >= Util.getPreloadCount(this) || downloadList.isEmpty()) && !backgroundDownloadList.isEmpty()) { + for(int i = 0; i < backgroundDownloadList.size(); i++) { + DownloadFile downloadFile = backgroundDownloadList.get(i); + if(downloadFile.isWorkDone() && (!downloadFile.shouldSave() || downloadFile.isSaved()) || downloadFile.isFailedMax()) { + // Don't need to keep list like active song list + backgroundDownloadList.remove(i); + revision++; + i--; + } else { + currentDownloading = downloadFile; + currentDownloading.download(); + cleanupCandidates.add(currentDownloading); + break; + } + } + } + } - if(!backgroundDownloadList.isEmpty()) { - Notifications.showDownloadingNotification(this, this, handler, currentDownloading, backgroundDownloadList.size()); - downloadOngoing = true; - } else if(backgroundDownloadList.isEmpty() && downloadOngoing) { - Notifications.hideDownloadingNotification(this, this, handler); - downloadOngoing = false; - } + if(!backgroundDownloadList.isEmpty()) { + Notifications.showDownloadingNotification(this, this, handler, currentDownloading, backgroundDownloadList.size()); + downloadOngoing = true; + } else if(backgroundDownloadList.isEmpty() && downloadOngoing) { + Notifications.hideDownloadingNotification(this, this, handler); + downloadOngoing = false; + } - // Delete obsolete .partial and .complete files. - cleanup(); - } + // Delete obsolete .partial and .complete files. + cleanup(); + } - private synchronized void checkRemovePlayed() { - boolean changed = false; - SharedPreferences prefs = Util.getPreferences(this); - int keepCount = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_KEEP_PLAYED_CNT, "0")); - while(currentPlayingIndex > keepCount) { - downloadList.remove(0); - currentPlayingIndex = downloadList.indexOf(currentPlaying); - changed = true; - } + private synchronized void checkRemovePlayed() { + boolean changed = false; + SharedPreferences prefs = Util.getPreferences(this); + int keepCount = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_KEEP_PLAYED_CNT, "0")); + while(currentPlayingIndex > keepCount) { + downloadList.remove(0); + currentPlayingIndex = downloadList.indexOf(currentPlaying); + changed = true; + } - if(changed) { - revision++; - onSongsChanged(); - } - } + if(changed) { + revision++; + onSongsChanged(); + } + } - private synchronized void checkShufflePlay() { + private synchronized void checkShufflePlay() { - // Get users desired random playlist size - SharedPreferences prefs = Util.getPreferences(this); - int listSize = Math.max(1, Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_RANDOM_SIZE, "20"))); - boolean wasEmpty = downloadList.isEmpty(); + // Get users desired random playlist size + SharedPreferences prefs = Util.getPreferences(this); + int listSize = Math.max(1, Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_RANDOM_SIZE, "20"))); + boolean wasEmpty = downloadList.isEmpty(); - long revisionBefore = revision; + long revisionBefore = revision; - // First, ensure that list is at least 20 songs long. - int size = size(); - if (size < listSize) { - for (MusicDirectory.Entry song : shufflePlayBuffer.get(listSize - size)) { - DownloadFile downloadFile = new DownloadFile(this, song, false); - downloadList.add(downloadFile); - revision++; - } - } + // First, ensure that list is at least 20 songs long. + int size = size(); + if (size < listSize) { + for (MusicDirectory.Entry song : shufflePlayBuffer.get(listSize - size)) { + DownloadFile downloadFile = new DownloadFile(this, song, false); + downloadList.add(downloadFile); + revision++; + } + } - int currIndex = currentPlaying == null ? 0 : getCurrentPlayingIndex(); + int currIndex = currentPlaying == null ? 0 : getCurrentPlayingIndex(); - // Only shift playlist if playing song #5 or later. - if (currIndex > 4) { - int songsToShift = currIndex - 2; - for (MusicDirectory.Entry song : shufflePlayBuffer.get(songsToShift)) { - downloadList.add(new DownloadFile(this, song, false)); - downloadList.get(0).cancelDownload(); - downloadList.remove(0); - revision++; - } - } - currentPlayingIndex = downloadList.indexOf(currentPlaying); + // Only shift playlist if playing song #5 or later. + if (currIndex > 4) { + int songsToShift = currIndex - 2; + for (MusicDirectory.Entry song : shufflePlayBuffer.get(songsToShift)) { + downloadList.add(new DownloadFile(this, song, false)); + downloadList.get(0).cancelDownload(); + downloadList.remove(0); + revision++; + } + } + currentPlayingIndex = downloadList.indexOf(currentPlaying); - if (revisionBefore != revision) { - onSongsChanged(); - updateRemotePlaylist(); - } + if (revisionBefore != revision) { + onSongsChanged(); + updateRemotePlaylist(); + } - if (wasEmpty && !downloadList.isEmpty()) { - play(0); - } - } + if (wasEmpty && !downloadList.isEmpty()) { + play(0); + } + } - public long getDownloadListUpdateRevision() { - return revision; - } + public long getDownloadListUpdateRevision() { + return revision; + } - private synchronized void cleanup() { - Iterator iterator = cleanupCandidates.iterator(); - while (iterator.hasNext()) { - DownloadFile downloadFile = iterator.next(); - if (downloadFile != currentPlaying && downloadFile != currentDownloading) { - if (downloadFile.cleanup()) { - iterator.remove(); - } - } - } - } + private synchronized void cleanup() { + Iterator iterator = cleanupCandidates.iterator(); + while (iterator.hasNext()) { + DownloadFile downloadFile = iterator.next(); + if (downloadFile != currentPlaying && downloadFile != currentDownloading) { + if (downloadFile.cleanup()) { + iterator.remove(); + } + } + } + } - public void postPlayCleanup() { - postPlayCleanup(currentPlaying); - } - public void postPlayCleanup(DownloadFile downloadFile) { - if(downloadFile == null) { - return; - } - } + public void postPlayCleanup() { + postPlayCleanup(currentPlaying); + } + public void postPlayCleanup(DownloadFile downloadFile) { + if(downloadFile == null) { + return; + } + } - private boolean isPastCutoff() { - return isPastCutoff(getPlayerPosition(), getPlayerDuration()); - } - private boolean isPastCutoff(int position, int duration) { - return isPastCutoff(position, duration, false); - } - private boolean isPastCutoff(int position, int duration, boolean allowSkipping) { - if(currentPlaying == null) { - return false; - } + private boolean isPastCutoff() { + return isPastCutoff(getPlayerPosition(), getPlayerDuration()); + } + private boolean isPastCutoff(int position, int duration) { + return isPastCutoff(position, duration, false); + } + private boolean isPastCutoff(int position, int duration, boolean allowSkipping) { + if(currentPlaying == null) { + return false; + } - // Make cutoff a maximum of 10 minutes - int cutoffPoint = Math.max((int) (duration * DELETE_CUTOFF), duration - 10 * 60 * 1000); - boolean isPastCutoff = duration > 0 && position > cutoffPoint; - - return isPastCutoff; - } - - private void applyReplayGain(MediaPlayer mediaPlayer, DownloadFile downloadFile) { - if(currentPlaying == null) { - return; - } + // Make cutoff a maximum of 10 minutes + int cutoffPoint = Math.max((int) (duration * DELETE_CUTOFF), duration - 10 * 60 * 1000); + boolean isPastCutoff = duration > 0 && position > cutoffPoint; - SharedPreferences prefs = Util.getPreferences(this); - try { - float adjust = 0f; - if (prefs.getBoolean(Constants.PREFERENCES_KEY_REPLAY_GAIN, false)) { - float[] rg = BastpUtil.getReplayGainValues(downloadFile.getFile().getCanonicalPath()); /* track, album */ - boolean singleAlbum = false; - - String replayGainType = prefs.getString(Constants.PREFERENCES_KEY_REPLAY_GAIN_TYPE, "1"); - // 1 => Smart replay gain - if("1".equals(replayGainType)) { - // Check if part of at least consequetive songs of the same album - - int index = downloadList.indexOf(downloadFile); - if(index != -1) { - String albumName = downloadFile.getSong().getAlbum(); - int matched = 0; - - // Check forwards - for(int i = index + 1; i < downloadList.size() && matched < REQUIRED_ALBUM_MATCHES; i++) { - if(albumName.equals(downloadList.get(i).getSong().getAlbum())) { - matched++; - } else { - break; - } - } - - // Check backwards - for(int i = index - 1; i >= 0 && matched < REQUIRED_ALBUM_MATCHES; i--) { - if(albumName.equals(downloadList.get(i).getSong().getAlbum())) { - matched++; - } else { - break; - } - } - - if(matched >= REQUIRED_ALBUM_MATCHES) { - singleAlbum = true; - } - } - } - // 2 => Use album tags - else if("2".equals(replayGainType)) { - singleAlbum = true; - } - // 3 => Use track tags - // Already false, no need to do anything here - - - // If playing a single album or no track gain, use album gain - if((singleAlbum || rg[0] == 0) && rg[1] != 0) { - adjust = rg[1]; - } else { - // Otherwise, give priority to track gain - adjust = rg[0]; - } - - if (adjust == 0) { - /* No RG value found: decrease volume for untagged song if requested by user */ - int untagged = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_REPLAY_GAIN_UNTAGGED, "0")); - adjust = (untagged - 150) / 10f; - } else { - int bump = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_REPLAY_GAIN_BUMP, "150")); - adjust += (bump - 150) / 10f; - } - } - - float rg_result = ((float) Math.pow(10, (adjust / 20))) * volume; - if (rg_result > 1.0f) { - rg_result = 1.0f; /* android would IGNORE the change if this is > 1 and we would end up with the wrong volume */ - } else if (rg_result < 0.0f) { - rg_result = 0.0f; - } - mediaPlayer.setVolume(rg_result, rg_result); - } catch(IOException e) { - Log.w(TAG, "Failed to apply replay gain values", e); - } - } + return isPastCutoff; + } - private synchronized boolean isNextPlayingSameAlbum() { - return isNextPlayingSameAlbum(currentPlaying, nextPlaying); - } - private synchronized boolean isNextPlayingSameAlbum(DownloadFile currentPlaying, DownloadFile nextPlaying) { - if(currentPlaying == null || nextPlaying == null) { - return false; - } else { - return currentPlaying.getSong().getAlbum().equals(nextPlaying.getSong().getAlbum()); - } - } + private void applyReplayGain(MediaPlayer mediaPlayer, DownloadFile downloadFile) { + if(currentPlaying == null) { + return; + } - public void acquireWakelock() { - acquireWakelock(30000); - } - public void acquireWakelock(int ms) { - wakeLock.acquire(ms); - } + SharedPreferences prefs = Util.getPreferences(this); + try { + float adjust = 0f; + if (prefs.getBoolean(Constants.PREFERENCES_KEY_REPLAY_GAIN, false)) { + float[] rg = BastpUtil.getReplayGainValues(downloadFile.getFile().getCanonicalPath()); /* track, album */ + boolean singleAlbum = false; - public void handleKeyEvent(KeyEvent keyEvent) { - lifecycleSupport.handleKeyEvent(keyEvent); - } + String replayGainType = prefs.getString(Constants.PREFERENCES_KEY_REPLAY_GAIN_TYPE, "1"); + // 1 => Smart replay gain + if("1".equals(replayGainType)) { + // Check if part of at least consequetive songs of the same album - public void addOnSongChangedListener(OnSongChangedListener listener) { - addOnSongChangedListener(listener, false); - } - public void addOnSongChangedListener(OnSongChangedListener listener, boolean run) { - synchronized(onSongChangedListeners) { - int index = onSongChangedListeners.indexOf(listener); - if (index == -1) { - onSongChangedListeners.add(listener); - } - } + int index = downloadList.indexOf(downloadFile); + if(index != -1) { + String albumName = downloadFile.getSong().getAlbum(); + int matched = 0; - if(run) { - if(mediaPlayerHandler != null) { - mediaPlayerHandler.post(new Runnable() { - @Override - public void run() { - onSongsChanged(); - onSongProgress(); - onStateUpdate(); - onMetadataUpdate(METADATA_UPDATED_ALL); - } - }); - } else { - runListenersOnInit = true; - } - } - } - public void removeOnSongChangeListener(OnSongChangedListener listener) { - synchronized(onSongChangedListeners) { - int index = onSongChangedListeners.indexOf(listener); - if (index != -1) { - onSongChangedListeners.remove(index); - } - } - } + // Check forwards + for(int i = index + 1; i < downloadList.size() && matched < REQUIRED_ALBUM_MATCHES; i++) { + if(albumName.equals(downloadList.get(i).getSong().getAlbum())) { + matched++; + } else { + break; + } + } - private void onSongChanged() { - final long atRevision = revision; - synchronized(onSongChangedListeners) { - for (final OnSongChangedListener listener : onSongChangedListeners) { - handler.post(new Runnable() { - @Override - public void run() { - if (revision == atRevision && instance != null) { - listener.onSongChanged(currentPlaying, currentPlayingIndex); + // Check backwards + for(int i = index - 1; i >= 0 && matched < REQUIRED_ALBUM_MATCHES; i--) { + if(albumName.equals(downloadList.get(i).getSong().getAlbum())) { + matched++; + } else { + break; + } + } - MusicDirectory.Entry entry = currentPlaying != null ? currentPlaying.getSong() : null; - listener.onMetadataUpdate(entry, METADATA_UPDATED_ALL); - } - } - }); - } + if(matched >= REQUIRED_ALBUM_MATCHES) { + singleAlbum = true; + } + } + } + // 2 => Use album tags + else if("2".equals(replayGainType)) { + singleAlbum = true; + } + // 3 => Use track tags + // Already false, no need to do anything here - if (mediaPlayerHandler != null && !onSongChangedListeners.isEmpty()) { - mediaPlayerHandler.post(new Runnable() { - @Override - public void run() { - onSongProgress(); - } - }); - } - } - } - private void onSongsChanged() { - final long atRevision = revision; - synchronized(onSongChangedListeners) { - for (final OnSongChangedListener listener : onSongChangedListeners) { - handler.post(new Runnable() { - @Override - public void run() { - if (revision == atRevision && instance != null) { - listener.onSongsChanged(downloadList, currentPlaying, currentPlayingIndex); - } - } - }); - } - } - } - private void onSongProgress() { - onSongProgress(true); - } - private synchronized void onSongProgress(boolean manual) { - final long atRevision = revision; - final Integer duration = getPlayerDuration(); - final boolean isSeekable = isSeekable(); - final int position = getPlayerPosition(); + // If playing a single album or no track gain, use album gain + if((singleAlbum || rg[0] == 0) && rg[1] != 0) { + adjust = rg[1]; + } else { + // Otherwise, give priority to track gain + adjust = rg[0]; + } - synchronized(onSongChangedListeners) { - for (final OnSongChangedListener listener : onSongChangedListeners) { - handler.post(new Runnable() { - @Override - public void run() { - if (revision == atRevision && instance != null) { - listener.onSongProgress(currentPlaying, position, duration, isSeekable); - } - } - }); - } - } + if (adjust == 0) { + /* No RG value found: decrease volume for untagged song if requested by user */ + int untagged = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_REPLAY_GAIN_UNTAGGED, "0")); + adjust = (untagged - 150) / 10f; + } else { + int bump = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_REPLAY_GAIN_BUMP, "150")); + adjust += (bump - 150) / 10f; + } + } - if(manual) { - handler.post(new Runnable() { - @Override - public void run() { - } - }); - } - } - private void onStateUpdate() { - final long atRevision = revision; - synchronized(onSongChangedListeners) { - for (final OnSongChangedListener listener : onSongChangedListeners) { - handler.post(new Runnable() { - @Override - public void run() { - if (revision == atRevision && instance != null) { - listener.onStateUpdate(currentPlaying, playerState); - } - } - }); - } - } - } - public void onMetadataUpdate() { - onMetadataUpdate(METADATA_UPDATED_ALL); - } - public void onMetadataUpdate(final int updateType) { - synchronized(onSongChangedListeners) { - for (final OnSongChangedListener listener : onSongChangedListeners) { - handler.post(new Runnable() { - @Override - public void run() { - if (instance != null) { - MusicDirectory.Entry entry = currentPlaying != null ? currentPlaying.getSong() : null; - listener.onMetadataUpdate(entry, updateType); - } - } - }); - } - } + float rg_result = ((float) Math.pow(10, (adjust / 20))) * volume; + if (rg_result > 1.0f) { + rg_result = 1.0f; /* android would IGNORE the change if this is > 1 and we would end up with the wrong volume */ + } else if (rg_result < 0.0f) { + rg_result = 0.0f; + } + mediaPlayer.setVolume(rg_result, rg_result); + } catch(IOException e) { + Log.w(TAG, "Failed to apply replay gain values", e); + } + } - handler.post(new Runnable() { - @Override - public void run() { - } - }); - } + private synchronized boolean isNextPlayingSameAlbum() { + return isNextPlayingSameAlbum(currentPlaying, nextPlaying); + } + private synchronized boolean isNextPlayingSameAlbum(DownloadFile currentPlaying, DownloadFile nextPlaying) { + if(currentPlaying == null || nextPlaying == null) { + return false; + } else { + return currentPlaying.getSong().getAlbum().equals(nextPlaying.getSong().getAlbum()); + } + } - private class BufferTask extends SilentBackgroundTask { - private final DownloadFile downloadFile; - private final int position; - private final long expectedFileSize; - private final File partialFile; - private final boolean start; + public void acquireWakelock() { + acquireWakelock(30000); + } + public void acquireWakelock(int ms) { + wakeLock.acquire(ms); + } - public BufferTask(DownloadFile downloadFile, int position, boolean start) { - super(instance); - this.downloadFile = downloadFile; - this.position = position; - partialFile = downloadFile.getPartialFile(); - this.start = start; + public void handleKeyEvent(KeyEvent keyEvent) { + lifecycleSupport.handleKeyEvent(keyEvent); + } - // Calculate roughly how many bytes BUFFER_LENGTH_SECONDS corresponds to. - int bitRate = downloadFile.getBitRate(); - long byteCount = Math.max(100000, bitRate * 1024L / 8L * 5L); + public void addOnSongChangedListener(OnSongChangedListener listener) { + addOnSongChangedListener(listener, false); + } + public void addOnSongChangedListener(OnSongChangedListener listener, boolean run) { + synchronized(onSongChangedListeners) { + int index = onSongChangedListeners.indexOf(listener); + if (index == -1) { + onSongChangedListeners.add(listener); + } + } - // Find out how large the file should grow before resuming playback. - Log.i(TAG, "Buffering from position " + position + " and bitrate " + bitRate); - expectedFileSize = (position * bitRate / 8) + byteCount; - } + if(run) { + if(mediaPlayerHandler != null) { + mediaPlayerHandler.post(new Runnable() { + @Override + public void run() { + onSongsChanged(); + onSongProgress(); + onStateUpdate(); + onMetadataUpdate(METADATA_UPDATED_ALL); + } + }); + } else { + runListenersOnInit = true; + } + } + } + public void removeOnSongChangeListener(OnSongChangedListener listener) { + synchronized(onSongChangedListeners) { + int index = onSongChangedListeners.indexOf(listener); + if (index != -1) { + onSongChangedListeners.remove(index); + } + } + } - @Override - public Void doInBackground() throws InterruptedException { - setPlayerState(DOWNLOADING); + private void onSongChanged() { + final long atRevision = revision; + synchronized(onSongChangedListeners) { + for (final OnSongChangedListener listener : onSongChangedListeners) { + handler.post(new Runnable() { + @Override + public void run() { + if (revision == atRevision && instance != null) { + listener.onSongChanged(currentPlaying, currentPlayingIndex); - while (!bufferComplete()) { - Thread.sleep(1000L); - if (isCancelled() || downloadFile.isFailedMax()) { - return null; - } else if(!downloadFile.isFailedMax() && !downloadFile.isDownloading()) { - checkDownloads(); - } - } - doPlay(downloadFile, position, start); + MusicDirectory.Entry entry = currentPlaying != null ? currentPlaying.getSong() : null; + listener.onMetadataUpdate(entry, METADATA_UPDATED_ALL); + } + } + }); + } - return null; - } + if (mediaPlayerHandler != null && !onSongChangedListeners.isEmpty()) { + mediaPlayerHandler.post(new Runnable() { + @Override + public void run() { + onSongProgress(); + } + }); + } + } + } + private void onSongsChanged() { + final long atRevision = revision; + synchronized(onSongChangedListeners) { + for (final OnSongChangedListener listener : onSongChangedListeners) { + handler.post(new Runnable() { + @Override + public void run() { + if (revision == atRevision && instance != null) { + listener.onSongsChanged(downloadList, currentPlaying, currentPlayingIndex); + } + } + }); + } + } + } - private boolean bufferComplete() { - boolean completeFileAvailable = downloadFile.isWorkDone(); - long size = partialFile.length(); + private void onSongProgress() { + onSongProgress(true); + } + private synchronized void onSongProgress(boolean manual) { + final long atRevision = revision; + final Integer duration = getPlayerDuration(); + final boolean isSeekable = isSeekable(); + final int position = getPlayerPosition(); - Log.i(TAG, "Buffering " + partialFile + " (" + size + "/" + expectedFileSize + ", " + completeFileAvailable + ")"); - return completeFileAvailable || size >= expectedFileSize; - } + synchronized(onSongChangedListeners) { + for (final OnSongChangedListener listener : onSongChangedListeners) { + handler.post(new Runnable() { + @Override + public void run() { + if (revision == atRevision && instance != null) { + listener.onSongProgress(currentPlaying, position, duration, isSeekable); + } + } + }); + } + } - @Override - public String toString() { - return "BufferTask (" + downloadFile + ")"; - } - } + if(manual) { + handler.post(new Runnable() { + @Override + public void run() { + } + }); + } + } + private void onStateUpdate() { + final long atRevision = revision; + synchronized(onSongChangedListeners) { + for (final OnSongChangedListener listener : onSongChangedListeners) { + handler.post(new Runnable() { + @Override + public void run() { + if (revision == atRevision && instance != null) { + listener.onStateUpdate(currentPlaying, playerState); + } + } + }); + } + } + } + public void onMetadataUpdate() { + onMetadataUpdate(METADATA_UPDATED_ALL); + } + public void onMetadataUpdate(final int updateType) { + synchronized(onSongChangedListeners) { + for (final OnSongChangedListener listener : onSongChangedListeners) { + handler.post(new Runnable() { + @Override + public void run() { + if (instance != null) { + MusicDirectory.Entry entry = currentPlaying != null ? currentPlaying.getSong() : null; + listener.onMetadataUpdate(entry, updateType); + } + } + }); + } + } - private class CheckCompletionTask extends SilentBackgroundTask { - private final DownloadFile downloadFile; - private final File partialFile; + handler.post(new Runnable() { + @Override + public void run() { + } + }); + } - public CheckCompletionTask(DownloadFile downloadFile) { - super(instance); - this.downloadFile = downloadFile; - if(downloadFile != null) { - partialFile = downloadFile.getPartialFile(); - } else { - partialFile = null; - } - } + private class BufferTask extends SilentBackgroundTask { + private final DownloadFile downloadFile; + private final int position; + private final long expectedFileSize; + private final File partialFile; + private final boolean start; - @Override - public Void doInBackground() throws InterruptedException { - if(downloadFile == null) { - return null; - } + public BufferTask(DownloadFile downloadFile, int position, boolean start) { + super(instance); + this.downloadFile = downloadFile; + this.position = position; + partialFile = downloadFile.getPartialFile(); + this.start = start; - // Do an initial sleep so this prepare can't compete with main prepare - Thread.sleep(5000L); - while (!bufferComplete()) { - Thread.sleep(5000L); - if (isCancelled()) { - return null; - } - } + // Calculate roughly how many bytes BUFFER_LENGTH_SECONDS corresponds to. + int bitRate = downloadFile.getBitRate(); + long byteCount = Math.max(100000, bitRate * 1024L / 8L * 5L); - // Start the setup of the next media player - mediaPlayerHandler.post(new Runnable() { - public void run() { - if(!CheckCompletionTask.this.isCancelled()) { - setupNext(downloadFile); - } - } - }); - return null; - } + // Find out how large the file should grow before resuming playback. + Log.i(TAG, "Buffering from position " + position + " and bitrate " + bitRate); + expectedFileSize = (position * bitRate / 8) + byteCount; + } - private boolean bufferComplete() { - boolean completeFileAvailable = downloadFile.isWorkDone(); - Log.i(TAG, "Buffering next " + partialFile + " (" + partialFile.length() + "): " + completeFileAvailable); - return completeFileAvailable && (playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED); - } + @Override + public Void doInBackground() throws InterruptedException { + setPlayerState(DOWNLOADING); - @Override - public String toString() { - return "CheckCompletionTask (" + downloadFile + ")"; - } - } + while (!bufferComplete()) { + Thread.sleep(1000L); + if (isCancelled() || downloadFile.isFailedMax()) { + return null; + } else if(!downloadFile.isFailedMax() && !downloadFile.isDownloading()) { + checkDownloads(); + } + } + doPlay(downloadFile, position, start); - public interface OnSongChangedListener { - void onSongChanged(DownloadFile currentPlaying, int currentPlayingIndex); - void onSongsChanged(List songs, DownloadFile currentPlaying, int currentPlayingIndex); - void onSongProgress(DownloadFile currentPlaying, int millisPlayed, Integer duration, boolean isSeekable); - void onStateUpdate(DownloadFile downloadFile, PlayerState playerState); - void onMetadataUpdate(MusicDirectory.Entry entry, int fieldChange); - } + return null; + } + + private boolean bufferComplete() { + boolean completeFileAvailable = downloadFile.isWorkDone(); + long size = partialFile.length(); + + Log.i(TAG, "Buffering " + partialFile + " (" + size + "/" + expectedFileSize + ", " + completeFileAvailable + ")"); + return completeFileAvailable || size >= expectedFileSize; + } + + @Override + public String toString() { + return "BufferTask (" + downloadFile + ")"; + } + } + + private class CheckCompletionTask extends SilentBackgroundTask { + private final DownloadFile downloadFile; + private final File partialFile; + + public CheckCompletionTask(DownloadFile downloadFile) { + super(instance); + this.downloadFile = downloadFile; + if(downloadFile != null) { + partialFile = downloadFile.getPartialFile(); + } else { + partialFile = null; + } + } + + @Override + public Void doInBackground() throws InterruptedException { + if(downloadFile == null) { + return null; + } + + // Do an initial sleep so this prepare can't compete with main prepare + Thread.sleep(5000L); + while (!bufferComplete()) { + Thread.sleep(5000L); + if (isCancelled()) { + return null; + } + } + + // Start the setup of the next media player + mediaPlayerHandler.post(new Runnable() { + public void run() { + if(!CheckCompletionTask.this.isCancelled()) { + setupNext(downloadFile); + } + } + }); + return null; + } + + private boolean bufferComplete() { + boolean completeFileAvailable = downloadFile.isWorkDone(); + Log.i(TAG, "Buffering next " + partialFile + " (" + partialFile.length() + "): " + completeFileAvailable); + return completeFileAvailable && (playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED); + } + + @Override + public String toString() { + return "CheckCompletionTask (" + downloadFile + ")"; + } + } + + public interface OnSongChangedListener { + void onSongChanged(DownloadFile currentPlaying, int currentPlayingIndex); + void onSongsChanged(List songs, DownloadFile currentPlaying, int currentPlayingIndex); + void onSongProgress(DownloadFile currentPlaying, int millisPlayed, Integer duration, boolean isSeekable); + void onStateUpdate(DownloadFile downloadFile, PlayerState playerState); + void onMetadataUpdate(MusicDirectory.Entry entry, int fieldChange); + } } diff --git a/app/src/main/java/net/nullsum/audinaut/service/MusicService.java b/app/src/main/java/net/nullsum/audinaut/service/MusicService.java index dcc3bdb..97ca82a 100644 --- a/app/src/main/java/net/nullsum/audinaut/service/MusicService.java +++ b/app/src/main/java/net/nullsum/audinaut/service/MusicService.java @@ -20,7 +20,7 @@ package net.nullsum.audinaut.service; import java.util.List; -import org.apache.http.HttpResponse; +import okhttp3.Response; import android.content.Context; import android.graphics.Bitmap; @@ -87,7 +87,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; + Response 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/net/nullsum/audinaut/service/OfflineMusicService.java b/app/src/main/java/net/nullsum/audinaut/service/OfflineMusicService.java index 2824303..961c743 100644 --- a/app/src/main/java/net/nullsum/audinaut/service/OfflineMusicService.java +++ b/app/src/main/java/net/nullsum/audinaut/service/OfflineMusicService.java @@ -34,7 +34,7 @@ import android.content.SharedPreferences; import android.graphics.Bitmap; import android.util.Log; -import org.apache.http.HttpResponse; +import okhttp3.Response; import net.nullsum.audinaut.domain.Artist; import net.nullsum.audinaut.domain.Genre; @@ -206,7 +206,7 @@ public class OfflineMusicService implements MusicService { } @Override - public HttpResponse getDownloadInputStream(Context context, Entry song, long offset, int maxBitrate, SilentBackgroundTask task) throws Exception { + public Response getDownloadInputStream(Context context, Entry song, long offset, int maxBitrate, SilentBackgroundTask task) throws Exception { throw new OfflineException(ERRORMSG); } diff --git a/app/src/main/java/net/nullsum/audinaut/service/RESTMusicService.java b/app/src/main/java/net/nullsum/audinaut/service/RESTMusicService.java index 0690236..5a54dba 100644 --- a/app/src/main/java/net/nullsum/audinaut/service/RESTMusicService.java +++ b/app/src/main/java/net/nullsum/audinaut/service/RESTMusicService.java @@ -34,37 +34,6 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.TimeUnit; - -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 okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; @@ -97,8 +66,6 @@ import net.nullsum.audinaut.service.parser.PlaylistsParser; import net.nullsum.audinaut.service.parser.RandomSongsParser; import net.nullsum.audinaut.service.parser.SearchResult2Parser; import net.nullsum.audinaut.service.parser.UserParser; -import net.nullsum.audinaut.service.ssl.SSLSocketFactory; -import net.nullsum.audinaut.service.ssl.TrustSelfSignedStrategy; import net.nullsum.audinaut.util.BackgroundTask; import net.nullsum.audinaut.util.Pair; import net.nullsum.audinaut.util.SilentBackgroundTask; @@ -130,46 +97,13 @@ 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 long redirectionLastChecked; private int redirectionNetworkType = -1; private String redirectFrom; private String redirectTo; - private final ThreadSafeClientConnManager connManager; private Integer instance; public RESTMusicService() { - - // Create and initialize default HTTP parameters - HttpParams params = new BasicHttpParams(); - ConnManagerParams.setMaxTotalConnections(params, 20); - ConnManagerParams.setMaxConnectionsPerRoute(params, new ConnPerRouteBean(20)); - HttpConnectionParams.setConnectionTimeout(params, SOCKET_CONNECT_TIMEOUT); - HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_DEFAULT); - - // Turn off stale checking. Our connections break all the time anyway, - // and it's not worth it to pay the penalty of checking every time. - HttpConnectionParams.setStaleCheckingEnabled(params, false); - - // Create and initialize scheme registry - SchemeRegistry schemeRegistry = new SchemeRegistry(); - schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); - schemeRegistry.register(new Scheme("https", createSSLSocketFactory(), 443)); - - // Create an HttpClient with the ThreadSafeClientConnManager. - // This connection manager must be used if more than one thread will - // be using the HttpClient. - connManager = new ThreadSafeClientConnManager(params, schemeRegistry); - httpClient = new DefaultHttpClient(connManager, params); - } - - private SocketFactory createSSLSocketFactory() { - try { - return new SSLSocketFactory(new TrustSelfSignedStrategy(), SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); - } catch (Throwable x) { - Log.e(TAG, "Failed to create custom SSL socket factory, using default.", x); - return org.apache.http.conn.ssl.SSLSocketFactory.getSocketFactory(); - } } @Override @@ -744,48 +678,35 @@ public class RESTMusicService implements MusicService { } @Override - public HttpResponse getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, SilentBackgroundTask task) throws Exception { + public Response getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, SilentBackgroundTask task) throws Exception { + + OkHttpClient eagerClient = client.newBuilder() + .readTimeout(30, TimeUnit.SECONDS) + .build(); 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); + url += "&id=" + song.getId(); - // Add "Range" header if offset is given. - List
headers = new ArrayList
(); + Log.i(TAG, "Using music URL: " + url); + + Builder builder = new FormBody.Builder(); + builder.add("id", song.getId()); + builder.add("maxBitRate", Integer.toString(maxBitrate)); + + RequestBody formBody = builder.build(); + + Request.Builder requestBuilder= new Request.Builder(); if (offset > 0) { - headers.add(new BasicHeader("Range", "bytes=" + offset + "-")); + requestBuilder.header("Range", "bytes=" + offset + "-"); } - List parameterNames = new ArrayList(); - parameterNames.add("id"); - parameterNames.add("maxBitRate"); + requestBuilder.url(url); +// requestBuilder.post(formBody); - List parameterValues = new ArrayList(); - parameterValues.add(song.getId()); - parameterValues.add(maxBitrate); - - HttpResponse response = getResponseForURL(context, url, params, parameterNames, parameterValues, headers, null, task, false); - - // If content type is XML, an error occurred. Get it. - String contentType = response.getEntity().getContentType().getValue(); - 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); - } - try { - new ErrorParser(context, getInstance(context)).parse(in); - } finally { - Util.close(in); - } - } + Request request = requestBuilder.build(); + Response response = eagerClient.newCall(request).execute(); return response; } @@ -972,163 +893,6 @@ public class RESTMusicService implements MusicService { this.instance = instance; } - private HttpResponse getResponseForURL(Context context, String url, HttpParams requestParams, - List parameterNames, List parameterValues, - List
headers, ProgressListener progressListener, SilentBackgroundTask task, boolean throwsErrors) throws Exception { - // If not too many parameters, extract them to the URL rather than relying on the HTTP POST request being - // received intact. Remember, HTTP POST requests are converted to GET requests during HTTP redirects, thus - // loosing its entity. - if (parameterNames != null && parameterNames.size() < 10) { - StringBuilder builder = new StringBuilder(url); - for (int i = 0; i < parameterNames.size(); i++) { - builder.append("&").append(parameterNames.get(i)).append("="); - String part = URLEncoder.encode(String.valueOf(parameterValues.get(i)), "UTF-8"); - part = part.replaceAll("\\%27", "'"); - builder.append(part); - } - url = builder.toString(); - parameterNames = null; - parameterValues = null; - } - - String rewrittenUrl = rewriteUrlWithRedirect(context, url); - return executeWithRetry(context, rewrittenUrl, url, requestParams, parameterNames, parameterValues, headers, progressListener, task, throwsErrors); - } - - private HttpResponse executeWithRetry(final Context context, String url, String originalUrl, HttpParams requestParams, - List parameterNames, List parameterValues, - List
headers, ProgressListener progressListener, SilentBackgroundTask task, boolean throwErrors) throws Exception { - // Strip out sensitive information from log - if(url.indexOf("scanstatus") == -1) { - Log.i(TAG, stripUrlInfo(url)); - } - - SharedPreferences prefs = Util.getPreferences(context); - int networkTimeout = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT, "15000")); - HttpParams newParams = httpClient.getParams(); - HttpConnectionParams.setSoTimeout(newParams, networkTimeout); - httpClient.setParams(newParams); - - final AtomicReference isCancelled = new AtomicReference(false); - int attempts = 0; - while (true) { - attempts++; - HttpContext httpContext = new BasicHttpContext(); - final HttpRequestBase request = (url.indexOf("rest") == -1) ? new HttpGet(url) : new HttpPost(url); - - if (task != null) { - // Attempt to abort the HTTP request if the task is cancelled. - task.setOnCancelListener(new BackgroundTask.OnCancelListener() { - @Override - public void onCancel() { - try { - isCancelled.set(true); - if(Thread.currentThread() == Looper.getMainLooper().getThread()) { - new SilentBackgroundTask(context) { - @Override - protected Void doInBackground() throws Throwable { - request.abort(); - return null; - } - }.execute(); - } else { - request.abort(); - } - } catch(Exception e) { - Log.e(TAG, "Failed to stop http task", e); - } - } - }); - } - - if (parameterNames != null && request instanceof HttpPost) { - List params = new ArrayList(); - for (int i = 0; i < parameterNames.size(); i++) { - params.add(new BasicNameValuePair(parameterNames.get(i), String.valueOf(parameterValues.get(i)))); - } - ((HttpPost) request).setEntity(new UrlEncodedFormEntity(params, Constants.UTF_8)); - } - - if (requestParams != null) { - request.setParams(requestParams); - } - - if (headers != null) { - for (Header header : headers) { - request.addHeader(header); - } - } - if(url.indexOf("getCoverArt") == -1 && url.indexOf("stream") == -1) { - request.addHeader("Accept-Encoding", "gzip"); - } - request.addHeader("User-Agent", Constants.REST_CLIENT_ID); - - // Set credentials to get through apache proxies that require authentication. - int instance = getInstance(context); - String username = prefs.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null); - String password = prefs.getString(Constants.PREFERENCES_KEY_PASSWORD + instance, null); - httpClient.getCredentialsProvider().setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), - new UsernamePasswordCredentials(username, password)); - - try { - HttpResponse response = httpClient.execute(request, httpContext); - detectRedirect(originalUrl, context, httpContext); - return response; - } catch (IOException x) { - request.abort(); - if (attempts >= HTTP_REQUEST_MAX_ATTEMPTS || isCancelled.get() || throwErrors) { - throw x; - } - if (progressListener != null) { - String msg = context.getResources().getString(R.string.music_service_retry, attempts, HTTP_REQUEST_MAX_ATTEMPTS - 1); - progressListener.updateProgress(msg); - } - Log.w(TAG, "Got IOException " + x + " (" + attempts + "), will retry"); - increaseTimeouts(requestParams); - Thread.sleep(2000L); - } - } - } - - private void increaseTimeouts(HttpParams requestParams) { - if (requestParams != null) { - int connectTimeout = HttpConnectionParams.getConnectionTimeout(requestParams); - if (connectTimeout != 0) { - HttpConnectionParams.setConnectionTimeout(requestParams, (int) (connectTimeout * 1.3F)); - } - int readTimeout = HttpConnectionParams.getSoTimeout(requestParams); - if (readTimeout != 0) { - HttpConnectionParams.setSoTimeout(requestParams, (int) (readTimeout * 1.5F)); - } - } - } - - private void detectRedirect(String originalUrl, Context context, HttpContext httpContext) throws Exception { - HttpUriRequest request = (HttpUriRequest) httpContext.getAttribute(ExecutionContext.HTTP_REQUEST); - HttpHost host = (HttpHost) httpContext.getAttribute(ExecutionContext.HTTP_TARGET_HOST); - - // Sometimes the request doesn't contain the "http://host" part - String redirectedUrl; - if (request.getURI().getScheme() == null) { - redirectedUrl = host.toURI() + request.getURI(); - } else { - redirectedUrl = request.getURI().toString(); - } - - int fromIndex = originalUrl.indexOf("/rest/"); - int toIndex = redirectedUrl.indexOf("/rest/"); - if(fromIndex != -1 && toIndex != -1 && !Util.equals(originalUrl, redirectedUrl)) { - redirectFrom = originalUrl.substring(0, fromIndex); - redirectTo = redirectedUrl.substring(0, toIndex); - - if (redirectFrom.compareTo(redirectTo) != 0) { - Log.i(TAG, redirectFrom + " redirects to " + redirectTo); - } - redirectionLastChecked = System.currentTimeMillis(); - redirectionNetworkType = getCurrentNetworkType(context); - } - } - private String rewriteUrlWithRedirect(Context context, String url) { // Only cache for a certain time. @@ -1177,8 +941,4 @@ public class RESTMusicService implements MusicService { return Util.getRestUrl(context, method, instance, allowAltAddress); } } - - public HttpClient getHttpClient() { - return httpClient; - } } diff --git a/app/src/main/java/net/nullsum/audinaut/service/ssl/SSLSocketFactory.java b/app/src/main/java/net/nullsum/audinaut/service/ssl/SSLSocketFactory.java deleted file mode 100644 index b8ca673..0000000 --- a/app/src/main/java/net/nullsum/audinaut/service/ssl/SSLSocketFactory.java +++ /dev/null @@ -1,553 +0,0 @@ -/* - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ - -package net.nullsum.audinaut.service.ssl; - -import android.os.Build; -import android.util.Log; - -import org.apache.http.conn.ConnectTimeoutException; -import org.apache.http.conn.scheme.HostNameResolver; -import org.apache.http.conn.scheme.LayeredSocketFactory; -import org.apache.http.conn.ssl.AllowAllHostnameVerifier; -import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier; -import org.apache.http.conn.ssl.StrictHostnameVerifier; -import org.apache.http.conn.ssl.X509HostnameVerifier; -import org.apache.http.params.HttpConnectionParams; -import org.apache.http.params.HttpParams; - -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; - -import java.io.IOException; -import java.lang.reflect.Array; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; -import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.Provider; -import java.security.SecureRandom; -import java.security.Security; -import java.security.UnrecoverableKeyException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * Layered socket factory for TLS/SSL connections. - *

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

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

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

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

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

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

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

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

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

- *
    - *
  • - *

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

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

    - *
  • - *
  • - *

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

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

    - *
  • - *
  • - *

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

    - *
  • - *
  • - *

    - * Import the trusted CA root certificate - *

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

    - *
  • - *
  • - *

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

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

    - *
  • - *
  • - *

    - * Verify the content the resultant keystore file - *

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

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

- * Please note that, if this method returns false, the trust manager configured - * in the actual SSL context can still clear the certificate as trusted. - * - * @param chain the peer certificate chain - * @param authType the authentication type based on the client certificate - * @return true if the certificate can be trusted without verification by - * the trust manager, false otherwise. - * @throws CertificateException thrown if the certificate is not trusted or invalid. - */ - boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException; - -} diff --git a/app/src/main/java/net/nullsum/audinaut/util/Util.java b/app/src/main/java/net/nullsum/audinaut/util/Util.java index b3b80a0..60e56bb 100644 --- a/app/src/main/java/net/nullsum/audinaut/util/Util.java +++ b/app/src/main/java/net/nullsum/audinaut/util/Util.java @@ -65,8 +65,6 @@ import net.nullsum.audinaut.domain.RepeatMode; import net.nullsum.audinaut.receiver.MediaButtonIntentReceiver; import net.nullsum.audinaut.service.DownloadService; -import org.apache.http.HttpEntity; - import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.File;