diff options
author | orionlee <orionlee@yahoo.com> | 2019-01-05 19:12:29 -0800 |
---|---|---|
committer | orionlee <orionlee@yahoo.com> | 2019-02-27 14:52:34 -0800 |
commit | 3f14fac4790f54a911f5b4b5ee8a81cf2ee0eea3 (patch) | |
tree | c1345147f7c300bb3d582126495b4ebddaff3fbd /core | |
parent | e26a54bdbccac6eb9d9060b725192c575b3913bb (diff) | |
download | AntennaPod-3f14fac4790f54a911f5b4b5ee8a81cf2ee0eea3.zip |
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).
Diffstat (limited to 'core')
3 files changed, 240 insertions, 37 deletions
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java index f34866c25..10175b59c 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java @@ -201,10 +201,6 @@ public class PlaybackService extends MediaBrowserServiceCompat { */ 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 */ private static boolean transientPause = false; @@ -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() { diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/Optional.java b/core/src/main/java/de/danoeh/antennapod/core/util/Optional.java new file mode 100644 index 000000000..0fe11ec53 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/util/Optional.java @@ -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"; + } +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java index 565ef8a96..fd87c9905 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java @@ -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()); } |