remove static PlaybackService.started, in favor of the start state
managed by inner ServiceManager. Also add a generic java8-like Optional class for use with RxJava2 where null was to be returned (RxJava2 requires non-null).
This commit is contained in:
parent
e26a54bdbc
commit
3f14fac479
|
@ -200,10 +200,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
* Is true if service is running.
|
||||
*/
|
||||
public static boolean isRunning = false;
|
||||
/**
|
||||
* Is true if service has received a valid start command.
|
||||
*/
|
||||
public static boolean started = false;
|
||||
/**
|
||||
* Is true if the service was running, but paused due to headphone disconnect
|
||||
*/
|
||||
|
@ -349,7 +345,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
Log.d(TAG, "Service is about to be destroyed");
|
||||
stopForeground(true); // TODO: [2716] unclear if stopForeground is still needed
|
||||
isRunning = false;
|
||||
started = false;
|
||||
currentMediaType = MediaType.UNKNOWN;
|
||||
|
||||
PreferenceManager.getDefaultSharedPreferences(this)
|
||||
|
@ -488,7 +483,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
return Service.START_NOT_STICKY;
|
||||
}
|
||||
} else if (!flavorHelper.castDisconnect(castDisconnect) && playable != null) {
|
||||
started = true;
|
||||
boolean stream = intent.getBooleanExtra(EXTRA_SHOULD_STREAM,
|
||||
true);
|
||||
boolean startWhenPrepared = intent.getBooleanExtra(EXTRA_START_WHEN_PREPARED, false);
|
||||
|
@ -576,7 +570,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
case KeyEvent.KEYCODE_MEDIA_STOP:
|
||||
if (status == PlayerStatus.PLAYING) {
|
||||
mediaPlayer.pause(true, true);
|
||||
started = false;
|
||||
}
|
||||
|
||||
stopForeground(true); // gets rid of persistent notification // TODO: [2716] unclear if stopForeground is still needed
|
||||
|
@ -595,7 +588,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
Playable playable = Playable.PlayableUtils.createInstanceFromPreferences(getApplicationContext());
|
||||
if (playable != null) {
|
||||
mediaPlayer.playMediaObject(playable, false, true, true);
|
||||
started = true;
|
||||
PlaybackService.this.updateMediaSessionMetadata(playable);
|
||||
}
|
||||
}
|
||||
|
@ -685,7 +677,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
|
||||
case PLAYING:
|
||||
writePlayerStatusPlaybackPreferences();
|
||||
started = true;
|
||||
// set sleep timer if auto-enabled
|
||||
if (newInfo.oldPlayerStatus != null && newInfo.oldPlayerStatus != PlayerStatus.SEEKING &&
|
||||
SleepTimerPreferences.autoEnable() && !sleepTimerActive()) {
|
||||
|
@ -1176,7 +1167,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
builder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, imageLocation);
|
||||
}
|
||||
}
|
||||
if (!Thread.currentThread().isInterrupted() && started) {
|
||||
if (!Thread.currentThread().isInterrupted() && isStarted()) {
|
||||
mediaSession.setSessionActivity(PendingIntent.getActivity(this, 0,
|
||||
PlaybackService.getPlayerActivityIntent(this),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
|
@ -1205,7 +1196,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
}
|
||||
if (playable == null) {
|
||||
Log.d(TAG, "setupNotification: playable is null");
|
||||
if (!started) {
|
||||
if (!isStarted()) {
|
||||
stopService();
|
||||
}
|
||||
return;
|
||||
|
@ -1219,7 +1210,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
|
||||
if (mediaPlayer == null) {
|
||||
Log.d(TAG, "notificationSetupTask: mediaPlayer is null");
|
||||
if (!started) {
|
||||
if (!isStarted()) {
|
||||
stopService();
|
||||
}
|
||||
return;
|
||||
|
@ -1247,7 +1238,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
PlayerStatus playerStatus = mediaPlayer.getPlayerStatus();
|
||||
Log.v(TAG, "notificationSetupTask: playerStatus=" + playerStatus);
|
||||
|
||||
if (!Thread.currentThread().isInterrupted() && started) {
|
||||
if (!Thread.currentThread().isInterrupted() && isStarted()) {
|
||||
String contentText = playable.getEpisodeTitle();
|
||||
String contentTitle = playable.getFeedTitle();
|
||||
Notification notification;
|
||||
|
@ -1909,6 +1900,10 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
}
|
||||
};
|
||||
|
||||
private boolean isStarted() {
|
||||
return serviceManager.serviceInStartedState;
|
||||
}
|
||||
|
||||
/**
|
||||
* The helper that manages PlaybackService's foreground service life cycle and the associated
|
||||
* notification control.
|
||||
|
@ -1918,8 +1913,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
*
|
||||
*/
|
||||
private class ServiceManager {
|
||||
private boolean mServiceInStartedState; // TODO: rationalize it with parent's static started
|
||||
// TODO LATER: rename the variable, do not use m prefix
|
||||
private boolean serviceInStartedState;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -1948,13 +1942,13 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
}
|
||||
|
||||
private void moveServiceToStartedState(PlaybackStateCompat state) {
|
||||
Log.v(TAG, "DBG - ServiceManager.moveServiceToStartedState() - mServiceInStartedState:" + mServiceInStartedState);
|
||||
Log.v(TAG, "DBG - ServiceManager.moveServiceToStartedState() - serviceInStartedState:" + serviceInStartedState);
|
||||
|
||||
if (!mServiceInStartedState) {
|
||||
if (!serviceInStartedState) {
|
||||
ContextCompat.startForegroundService(
|
||||
PlaybackService.this,
|
||||
new Intent(PlaybackService.this, PlaybackService.class));
|
||||
mServiceInStartedState = true;
|
||||
serviceInStartedState = true;
|
||||
}
|
||||
|
||||
doSetupNotification();
|
||||
|
@ -1967,7 +1961,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
private void moveServiceOutOfStartedState(PlaybackStateCompat state) {
|
||||
stopForeground(true);
|
||||
stopSelf();
|
||||
mServiceInStartedState = false;
|
||||
serviceInStartedState = false;
|
||||
}
|
||||
|
||||
private void doSetupNotification() {
|
||||
|
|
|
@ -0,0 +1,213 @@
|
|||
/*
|
||||
* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package de.danoeh.antennapod.core.util;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
|
||||
// AntennaPod's stripped-down version of Java/Android platform's java.util.Optional
|
||||
// so that it can be used on lower API level (API level 14)
|
||||
|
||||
// Android-changed: removed ValueBased paragraph.
|
||||
/**
|
||||
* A container object which may or may not contain a non-null value.
|
||||
* If a value is present, {@code isPresent()} will return {@code true} and
|
||||
* {@code get()} will return the value.
|
||||
*
|
||||
* <p>Additional methods that depend on the presence or absence of a contained
|
||||
* value are provided, such as {@link #orElse(java.lang.Object) orElse()}
|
||||
* (return a default value if value not present) and
|
||||
* {@link #ifPresent(java.util.function.Consumer) ifPresent()} (execute a block
|
||||
* of code if the value is present).
|
||||
*
|
||||
* @since 1.8
|
||||
*/
|
||||
public final class Optional<T> {
|
||||
/**
|
||||
* Common instance for {@code empty()}.
|
||||
*/
|
||||
private static final Optional<?> EMPTY = new Optional<>();
|
||||
|
||||
/**
|
||||
* If non-null, the value; if null, indicates no value is present
|
||||
*/
|
||||
private final T value;
|
||||
|
||||
/**
|
||||
* Constructs an empty instance.
|
||||
*
|
||||
* @implNote Generally only one empty instance, {@link Optional#EMPTY},
|
||||
* should exist per VM.
|
||||
*/
|
||||
private Optional() {
|
||||
this.value = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an empty {@code Optional} instance. No value is present for this
|
||||
* Optional.
|
||||
*
|
||||
* @apiNote Though it may be tempting to do so, avoid testing if an object
|
||||
* is empty by comparing with {@code ==} against instances returned by
|
||||
* {@code Option.empty()}. There is no guarantee that it is a singleton.
|
||||
* Instead, use {@link #isPresent()}.
|
||||
*
|
||||
* @param <T> Type of the non-existent value
|
||||
* @return an empty {@code Optional}
|
||||
*/
|
||||
public static<T> Optional<T> empty() {
|
||||
@SuppressWarnings("unchecked")
|
||||
Optional<T> t = (Optional<T>) EMPTY;
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance with the value present.
|
||||
*
|
||||
* @param value the non-null value to be present
|
||||
* @throws NullPointerException if value is null
|
||||
*/
|
||||
private Optional(T value) {
|
||||
this.value = Objects.requireNonNull(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@code Optional} with the specified present non-null value.
|
||||
*
|
||||
* @param <T> the class of the value
|
||||
* @param value the value to be present, which must be non-null
|
||||
* @return an {@code Optional} with the value present
|
||||
* @throws NullPointerException if value is null
|
||||
*/
|
||||
public static <T> Optional<T> of(T value) {
|
||||
return new Optional<>(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@code Optional} describing the specified value, if non-null,
|
||||
* otherwise returns an empty {@code Optional}.
|
||||
*
|
||||
* @param <T> the class of the value
|
||||
* @param value the possibly-null value to describe
|
||||
* @return an {@code Optional} with a present value if the specified value
|
||||
* is non-null, otherwise an empty {@code Optional}
|
||||
*/
|
||||
public static <T> Optional<T> ofNullable(T value) {
|
||||
return value == null ? empty() : of(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* If a value is present in this {@code Optional}, returns the value,
|
||||
* otherwise throws {@code NoSuchElementException}.
|
||||
*
|
||||
* @return the non-null value held by this {@code Optional}
|
||||
* @throws NoSuchElementException if there is no value present
|
||||
*
|
||||
* @see Optional#isPresent()
|
||||
*/
|
||||
public T get() {
|
||||
if (value == null) {
|
||||
throw new NoSuchElementException("No value present");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if there is a value present, otherwise {@code false}.
|
||||
*
|
||||
* @return {@code true} if there is a value present, otherwise {@code false}
|
||||
*/
|
||||
public boolean isPresent() {
|
||||
return value != null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the value if present, otherwise return {@code other}.
|
||||
*
|
||||
* @param other the value to be returned if there is no value present, may
|
||||
* be null
|
||||
* @return the value, if present, otherwise {@code other}
|
||||
*/
|
||||
public T orElse(T other) {
|
||||
return value != null ? value : other;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether some other object is "equal to" this Optional. The
|
||||
* other object is considered equal if:
|
||||
* <ul>
|
||||
* <li>it is also an {@code Optional} and;
|
||||
* <li>both instances have no value present or;
|
||||
* <li>the present values are "equal to" each other via {@code equals()}.
|
||||
* </ul>
|
||||
*
|
||||
* @param obj an object to be tested for equality
|
||||
* @return {code true} if the other object is "equal to" this object
|
||||
* otherwise {@code false}
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(obj instanceof Optional)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Optional<?> other = (Optional<?>) obj;
|
||||
return (value == other.value) || (value != null && value.equals(other.value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hash code value of the present value, if any, or 0 (zero) if
|
||||
* no value is present.
|
||||
*
|
||||
* @return hash code value of the present value or 0 if no value is present
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return value != null ? value.hashCode() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a non-empty string representation of this Optional suitable for
|
||||
* debugging. The exact presentation format is unspecified and may vary
|
||||
* between implementations and versions.
|
||||
*
|
||||
* @implSpec If a value is present the result must include its string
|
||||
* representation in the result. Empty and present Optionals must be
|
||||
* unambiguously differentiable.
|
||||
*
|
||||
* @return the string representation of this instance
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return value != null
|
||||
? String.format("Optional[%s]", value)
|
||||
: "Optional.empty";
|
||||
}
|
||||
}
|
|
@ -12,7 +12,6 @@ import android.media.MediaPlayer;
|
|||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
@ -36,6 +35,7 @@ import de.danoeh.antennapod.core.service.playback.PlaybackServiceMediaPlayer;
|
|||
import de.danoeh.antennapod.core.service.playback.PlayerStatus;
|
||||
import de.danoeh.antennapod.core.storage.DBTasks;
|
||||
import de.danoeh.antennapod.core.util.Converter;
|
||||
import de.danoeh.antennapod.core.util.Optional;
|
||||
import de.danoeh.antennapod.core.util.playback.Playable.PlayableUtils;
|
||||
import io.reactivex.Maybe;
|
||||
import io.reactivex.MaybeOnSubscribe;
|
||||
|
@ -187,21 +187,15 @@ public abstract class PlaybackController {
|
|||
serviceBinder = Observable.fromCallable(this::getPlayLastPlayedMediaIntent)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(intent -> {
|
||||
.subscribe(optionalIntent -> {
|
||||
boolean bound = false;
|
||||
if (!PlaybackService.started) {
|
||||
if (intent != null) {
|
||||
Log.d(TAG, "Calling start service");
|
||||
bound = activity.bindService(intent, mConnection, 0);
|
||||
} else {
|
||||
status = PlayerStatus.STOPPED;
|
||||
setupGUI();
|
||||
handleStatus();
|
||||
}
|
||||
if (optionalIntent.isPresent()) {
|
||||
Log.d(TAG, "Calling bind service");
|
||||
bound = activity.bindService(optionalIntent.get(), mConnection, 0);
|
||||
} else {
|
||||
Log.d(TAG, "PlaybackService is running, trying to connect without start command.");
|
||||
bound = activity.bindService(new Intent(activity, PlaybackService.class),
|
||||
mConnection, 0);
|
||||
status = PlayerStatus.STOPPED;
|
||||
setupGUI();
|
||||
handleStatus();
|
||||
}
|
||||
Log.d(TAG, "Result for service binding: " + bound);
|
||||
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
|
||||
|
@ -211,24 +205,26 @@ public abstract class PlaybackController {
|
|||
* Returns an intent that starts the PlaybackService and plays the last
|
||||
* played media or null if no last played media could be found.
|
||||
*/
|
||||
@Nullable private Intent getPlayLastPlayedMediaIntent() {
|
||||
@NonNull
|
||||
private Optional<Intent> getPlayLastPlayedMediaIntent() {
|
||||
Log.d(TAG, "Trying to restore last played media");
|
||||
Playable media = PlayableUtils.createInstanceFromPreferences(activity);
|
||||
if (media == null) {
|
||||
Log.d(TAG, "No last played media found");
|
||||
return null;
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
|
||||
boolean fileExists = media.localFileAvailable();
|
||||
boolean lastIsStream = PlaybackPreferences.getCurrentEpisodeIsStream();
|
||||
if (!fileExists && !lastIsStream && media instanceof FeedMedia) {
|
||||
DBTasks.notifyMissingFeedMediaFile(activity, (FeedMedia) media);
|
||||
}
|
||||
|
||||
return new PlaybackServiceStarter(activity, media)
|
||||
return Optional.of(new PlaybackServiceStarter(activity, media)
|
||||
.startWhenPrepared(false)
|
||||
.shouldStream(lastIsStream || !fileExists)
|
||||
.getIntent();
|
||||
.getIntent());
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue