summaryrefslogtreecommitdiff
path: root/core/src/main
diff options
context:
space:
mode:
authorByteHamster <info@bytehamster.com>2019-05-17 09:45:22 +0200
committerByteHamster <info@bytehamster.com>2019-05-17 09:45:22 +0200
commit3fca616e30005c6fe192e3201d29b0b9107d4c32 (patch)
tree95c7ca60a33283ab5d76b317879c6b41efc9ed72 /core/src/main
parentb6173973971e7f0a761133c67e9e30d7797d3e16 (diff)
downloadAntennaPod-3fca616e30005c6fe192e3201d29b0b9107d4c32.zip
Revert "Merge pull request #2954 from orionlee/bugfix_phantom_notification_rework_2716"
This reverts commit 643173de14ade8acfa5e6b92464482c7402e172e, reversing changes made to f2d103736d20bca481817808ee73b240fc2f7be4.
Diffstat (limited to 'core/src/main')
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java295
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/Optional.java213
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java39
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackServiceStarter.java9
4 files changed, 127 insertions, 429 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 29c424e1b..12673db12 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
@@ -25,7 +25,6 @@ import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
import android.support.v4.app.NotificationCompat;
-import android.support.v4.content.ContextCompat;
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.MediaBrowserServiceCompat;
import android.support.v4.media.MediaDescriptionCompat;
@@ -76,11 +75,6 @@ import de.greenrobot.event.EventBus;
/**
* Controls the MediaPlayer that plays a FeedMedia-file
- *
- * Callers should connect to the service with either:
- * - .bindService()
- * - ContextCompat.startForegroundService(), optionally with arguments, such as media to be played, in intent extras
- *
*/
public class PlaybackService extends MediaBrowserServiceCompat {
/**
@@ -199,6 +193,10 @@ 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;
@@ -266,6 +264,9 @@ public class PlaybackService extends MediaBrowserServiceCompat {
Log.d(TAG, "Service created.");
isRunning = true;
+ NotificationCompat.Builder notificationBuilder = createBasicNotification();
+ startForeground(NOTIFICATION_ID, notificationBuilder.build());
+
registerReceiver(autoStateUpdated, new IntentFilter("com.google.android.gms.car.media.STATUS"));
registerReceiver(headsetDisconnected, new IntentFilter(Intent.ACTION_HEADSET_PLUG));
registerReceiver(shutdownReceiver, new IntentFilter(ACTION_SHUTDOWN_PLAYBACK_SERVICE));
@@ -343,6 +344,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
Log.d(TAG, "Service is about to be destroyed");
stopForeground(true);
isRunning = false;
+ started = false;
currentMediaType = MediaType.UNKNOWN;
PreferenceManager.getDefaultSharedPreferences(this)
@@ -364,6 +366,11 @@ public class PlaybackService extends MediaBrowserServiceCompat {
taskManager.shutdown();
}
+ private void stopService() {
+ stopForeground(true);
+ stopSelf();
+ }
+
@Override
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) {
Log.d(TAG, "OnGetRoot: clientPackageName=" + clientPackageName +
@@ -460,32 +467,37 @@ public class PlaybackService extends MediaBrowserServiceCompat {
final boolean castDisconnect = intent.getBooleanExtra(EXTRA_CAST_DISCONNECT, false);
Playable playable = intent.getParcelableExtra(EXTRA_PLAYABLE);
if (keycode == -1 && playable == null && !castDisconnect) {
- // Typical cases when the service was started with no argument
- // - when it is first bound, and then moved to startedState, as in <code>serviceManager.moveServiceToStartedState()</code>
- // - callers (e.g., Controller) explicitly
- Log.d(TAG, "PlaybackService was started with no arguments.");
+ Log.e(TAG, "PlaybackService was started with no arguments");
+ stopService();
return Service.START_NOT_STICKY;
}
- if (keycode != -1) {
- Log.d(TAG, "Received media button event");
- boolean handled = handleKeycode(keycode, true);
- if (!handled) {
- // Just silently ignores unsupported keycode. Whether the service will
- // continue to run is solely dependent on whether it is playing some media.
- return Service.START_NOT_STICKY;
- }
- } else if (!flavorHelper.castDisconnect(castDisconnect) && playable != null) {
- boolean stream = intent.getBooleanExtra(EXTRA_SHOULD_STREAM, true);
- boolean startWhenPrepared = intent.getBooleanExtra(EXTRA_START_WHEN_PREPARED, false);
- boolean prepareImmediately = intent.getBooleanExtra(EXTRA_PREPARE_IMMEDIATELY, false);
- sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0);
- //If the user asks to play External Media, the casting session, if on, should end.
- flavorHelper.castDisconnect(playable instanceof ExternalMedia);
- if (playable instanceof FeedMedia) {
- playable = DBReader.getFeedMedia(((FeedMedia) playable).getId());
+ if ((flags & Service.START_FLAG_REDELIVERY) != 0) {
+ Log.d(TAG, "onStartCommand is a redelivered intent, calling stopForeground now.");
+ stopForeground(true);
+ } else {
+ if (keycode != -1) {
+ Log.d(TAG, "Received media button event");
+ boolean handled = handleKeycode(keycode, true);
+ if (!handled) {
+ stopService();
+ 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);
+ boolean prepareImmediately = intent.getBooleanExtra(EXTRA_PREPARE_IMMEDIATELY, false);
+ sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0);
+ //If the user asks to play External Media, the casting session, if on, should end.
+ flavorHelper.castDisconnect(playable instanceof ExternalMedia);
+ if (playable instanceof FeedMedia) {
+ playable = DBReader.getFeedMedia(((FeedMedia) playable).getId());
+ }
+ mediaPlayer.playMediaObject(playable, stream, startWhenPrepared, prepareImmediately);
}
- mediaPlayer.playMediaObject(playable, stream, startWhenPrepared, prepareImmediately);
+ setupNotification(playable);
}
return Service.START_NOT_STICKY;
@@ -559,23 +571,12 @@ public class PlaybackService extends MediaBrowserServiceCompat {
mediaPlayer.seekDelta(-UserPreferences.getRewindSecs() * 1000);
return true;
case KeyEvent.KEYCODE_MEDIA_STOP:
- // The logic gives UI illusion of stop by removing notification
- // In the UI within AntennaPod, including widgets, it is seen as PAUSE, e.g.,
- // users can still user on-screen widget to resume playing.
if (status == PlayerStatus.PLAYING) {
- // Implementation note: Use of a state in serviceManager to tell it to
- // show stop state UI (i.e., stopForeground(true)) is a bit awkward.
- //
- // More intuitive API would be for mediaPlayer.pause() returns a Future that
- // returns after pause, including the related async notification work completes.
- // However, it has its own complication, that mediaPlayer.pause() does not
- // really know when all the related work completes, as they are buried into
- // (asynchronous) callbacks.
- serviceManager.treatNextPauseAsStopOnUI();
mediaPlayer.pause(true, true);
- } else {
- serviceManager.showUIForStopState();
+ started = false;
}
+
+ stopForeground(true); // gets rid of persistent notification
return true;
default:
Log.d(TAG, "Unhandled key code: " + keycode);
@@ -591,6 +592,7 @@ 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);
}
}
@@ -605,9 +607,10 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
public void notifyVideoSurfaceAbandoned() {
- Log.v(TAG, "notifyVideoSurfaceAbandoned()");
mediaPlayer.pause(true, false);
mediaPlayer.resetVideoSurface();
+ setupNotification(getPlayable());
+ stopForeground(!UserPreferences.isPersistNotify());
}
private final PlaybackServiceTaskManager.PSTMCallback taskManagerCallback = new PlaybackServiceTaskManager.PSTMCallback() {
@@ -670,15 +673,27 @@ public class PlaybackService extends MediaBrowserServiceCompat {
break;
case PAUSED:
+ if ((UserPreferences.isPersistNotify() || isCasting) &&
+ android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ // do not remove notification on pause based on user pref and whether android version supports expanded notifications
+ // Change [Play] button to [Pause]
+ setupNotification(newInfo);
+ } else if (!UserPreferences.isPersistNotify() && !isCasting) {
+ // remove notification on pause
+ stopForeground(true);
+ }
writePlayerStatusPlaybackPreferences();
break;
case STOPPED:
//writePlaybackPreferencesNoMediaPlaying();
+ //stopService();
break;
case PLAYING:
writePlayerStatusPlaybackPreferences();
+ setupNotification(newInfo);
+ started = true;
// set sleep timer if auto-enabled
if (newInfo.oldPlayerStatus != null && newInfo.oldPlayerStatus != PlayerStatus.SEEKING &&
SleepTimerPreferences.autoEnable() && !sleepTimerActive()) {
@@ -689,6 +704,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
case ERROR:
writePlaybackPreferencesNoMediaPlaying();
+ stopService();
break;
}
@@ -701,7 +717,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
@Override
public void shouldStop() {
- serviceManager.stopService();
+ stopService();
}
@Override
@@ -750,6 +766,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
sendNotificationBroadcast(NOTIFICATION_TYPE_ERROR, what);
writePlaybackPreferencesNoMediaPlaying();
+ stopService();
return true;
}
@@ -833,6 +850,9 @@ public class PlaybackService extends MediaBrowserServiceCompat {
if (stopPlaying) {
taskManager.cancelPositionSaver();
writePlaybackPreferencesNoMediaPlaying();
+ if (!isCasting) {
+ stopForeground(true);
+ }
}
if (mediaType == null) {
sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0);
@@ -1046,7 +1066,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
private void updateMediaSession(final PlayerStatus playerStatus) {
PlaybackStateCompat.Builder sessionState = new PlaybackStateCompat.Builder();
- @PlaybackStateCompat.State
int state;
if (playerStatus != null) {
switch (playerStatus) {
@@ -1110,9 +1129,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
flavorHelper.mediaSessionSetExtraForWear(mediaSession);
- final PlaybackStateCompat sessionStateBuilt = sessionState.build();
- mediaSession.setPlaybackState(sessionStateBuilt);
- serviceManager.onPlaybackStateChange(sessionStateBuilt);
+ mediaSession.setPlaybackState(sessionState.build());
}
private static boolean useSkipToPreviousForRewindInLockscreen() {
@@ -1166,7 +1183,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
builder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, imageLocation);
}
}
- if (!Thread.currentThread().isInterrupted() && isStarted()) {
+ if (!Thread.currentThread().isInterrupted() && started) {
mediaSession.setSessionActivity(PendingIntent.getActivity(this, 0,
PlaybackService.getPlayerActivityIntent(this),
PendingIntent.FLAG_UPDATE_CURRENT));
@@ -1189,14 +1206,21 @@ public class PlaybackService extends MediaBrowserServiceCompat {
*/
private Thread notificationSetupThread;
- private synchronized void setupNotification(final Playable playable, boolean treatPauseAsStop) {
+ /**
+ * Prepares notification and starts the service in the foreground.
+ */
+ private void setupNotification(final PlaybackServiceMediaPlayer.PSMPInfo info) {
+ setupNotification(info.playable);
+ }
+
+ private synchronized void setupNotification(final Playable playable) {
if (notificationSetupThread != null) {
notificationSetupThread.interrupt();
}
if (playable == null) {
Log.d(TAG, "setupNotification: playable is null");
- if (!isStarted()) {
- serviceManager.stopService();
+ if (!started) {
+ stopService();
}
return;
}
@@ -1205,12 +1229,12 @@ public class PlaybackService extends MediaBrowserServiceCompat {
@Override
public void run() {
- Log.d(TAG, "notificationSetupTask: Starting background work");
+ Log.d(TAG, "Starting background work");
if (mediaPlayer == null) {
Log.d(TAG, "notificationSetupTask: mediaPlayer is null");
- if (!isStarted()) {
- serviceManager.stopService();
+ if (!started) {
+ stopService();
}
return;
}
@@ -1235,9 +1259,8 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
PlayerStatus playerStatus = mediaPlayer.getPlayerStatus();
- Log.v(TAG, "notificationSetupTask: playerStatus=" + playerStatus);
- if (!Thread.currentThread().isInterrupted() && isStarted()) {
+ if (!Thread.currentThread().isInterrupted() && started) {
String contentText = playable.getEpisodeTitle();
String contentTitle = playable.getFeedTitle();
Notification notification;
@@ -1329,33 +1352,15 @@ public class PlaybackService extends MediaBrowserServiceCompat {
playerStatus == PlayerStatus.PREPARING ||
playerStatus == PlayerStatus.SEEKING ||
isCasting) {
- Log.v(TAG, "notificationSetupTask: make service foreground");
startForeground(NOTIFICATION_ID, notification);
- } else if (playerStatus == PlayerStatus.PAUSED) {
- if (treatPauseAsStop) {
- stopForeground(true);
- } else if ((UserPreferences.isPersistNotify() || isCasting) &&
- android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- // do not remove notification on pause based on user pref and whether android version supports expanded notifications
- // Change [Play] button to [Pause]
- leaveNotificationAsBackground(notification);
- } else if (!UserPreferences.isPersistNotify() && !isCasting) {
- // remove notification on pause
- stopForeground(true);
- }
} else {
- leaveNotificationAsBackground(notification);
+ stopForeground(false);
+ NotificationManager mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ mNotificationManager.notify(NOTIFICATION_ID, notification);
}
Log.d(TAG, "Notification set up");
}
}
-
- private void leaveNotificationAsBackground(@NonNull Notification notification) {
- stopForeground(false);
- NotificationManager mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
- mNotificationManager.notify(NOTIFICATION_ID, notification);
- }
-
};
notificationSetupThread = new Thread(notificationSetupTask);
notificationSetupThread.start();
@@ -1550,7 +1555,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
@Override
public void onReceive(Context context, Intent intent) {
if (TextUtils.equals(intent.getAction(), ACTION_SHUTDOWN_PLAYBACK_SERVICE)) {
- serviceManager.stopService();
+ stopService();
}
}
@@ -1848,6 +1853,8 @@ public class PlaybackService extends MediaBrowserServiceCompat {
void saveCurrentPosition(boolean fromMediaPlayer, Playable playable, int position);
+ void setupNotification(boolean connected, PlaybackServiceMediaPlayer.PSMPInfo info);
+
MediaSessionCompat getMediaSession();
Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter);
@@ -1887,6 +1894,24 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
@Override
+ public void setupNotification(boolean connected, PlaybackServiceMediaPlayer.PSMPInfo info) {
+ if (connected) {
+ PlaybackService.this.setupNotification(info);
+ } else {
+ PlayerStatus status = info.playerStatus;
+ if ((status == PlayerStatus.PLAYING ||
+ status == PlayerStatus.SEEKING ||
+ status == PlayerStatus.PREPARING ||
+ UserPreferences.isPersistNotify()) &&
+ android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ PlaybackService.this.setupNotification(info);
+ } else if (!UserPreferences.isPersistNotify()) {
+ PlaybackService.this.stopForeground(true);
+ }
+ }
+ }
+
+ @Override
public MediaSessionCompat getMediaSession() {
return PlaybackService.this.mediaSession;
}
@@ -1901,116 +1926,4 @@ public class PlaybackService extends MediaBrowserServiceCompat {
PlaybackService.this.unregisterReceiver(receiver);
}
};
-
- private boolean isStarted() {
- return serviceManager.serviceInStartedState;
- }
-
- /**
- * The helper that manages PlaybackService's foreground service life cycle and the associated
- * notification control.
- *
- * The logic is adapted from a sample app from The Android Open Source Project.
- * See https://github.com/googlesamples/android-MediaBrowserService/blob/6cf01be9ef82ca2dd653f03e2a4af0b075cc0021/Application/src/main/java/com/example/android/mediasession/service/MusicService.java#L211
- *
- */
- private class ServiceManager {
- private boolean serviceInStartedState;
- private boolean toTreatNextPauseAsStopOnUI = false;
-
- /**
- *
- * Entry point method for callers. Upon PlaybackState changes,
- * the manager start/stop the PlaybackService as well as relevant notification
- */
- void onPlaybackStateChange(PlaybackStateCompat state) {
- // Report the state to the MediaSession.
-
- Log.v(TAG, "onPlaybackStateChange(" + (state != null ? state.getState() : "null") + ")");
- try {
- // Manage the started state of this service.
- switch (state.getState()) {
- case PlaybackStateCompat.STATE_CONNECTING:
- // move the service to started, aka, making it foreground
- // upon STATE_CONNECTING, i.e., in preparing to play a media.
- // This is done so that in case the preparation takes a long time, e.g.,
- // streaming over a slow network,
- // the service won't be killed by the system prematurely.
- moveServiceToStartedState(state);
- break;
- case PlaybackStateCompat.STATE_PLAYING:
- moveServiceToStartedState(state);
- break;
- case PlaybackStateCompat.STATE_PAUSED:
- updateNotificationForPause(state);
- break;
- case PlaybackStateCompat.STATE_STOPPED:
- moveServiceOutOfStartedState(state);
- break;
- case PlaybackStateCompat.STATE_ERROR:
- moveServiceOutOfStartedState(state);
- break;
- }
- } finally {
- if (toTreatNextPauseAsStopOnUI) {
- Log.v(TAG, "onPlaybackStateChange() - toTreatNextPauseAsStopOnUI enabled. The actual state (should be PAUSED, aka 2): " + state.getState());
- toTreatNextPauseAsStopOnUI = false;
- }
- }
- }
-
- /**
- * Tell service manager that on the next state change, if the state is STATE_PAUSED,
- * give UI treatment as if it is stopped.
- *
- * @see #handleKeycode(int, boolean) the use case
- */
- public void treatNextPauseAsStopOnUI() {
- this.toTreatNextPauseAsStopOnUI = true;
- }
-
- public void showUIForStopState() {
- Log.v(TAG, "serviceManager.showUIForStopState()");
- stopForeground(true); // gets rid of persistent notification, to give the UI illusion of STOP
- }
-
- public void stopService() {
- stopForeground(true);
- stopSelf();
- serviceInStartedState = false;
- }
-
- private void moveServiceToStartedState(PlaybackStateCompat state) {
- if (!serviceInStartedState) {
- ContextCompat.startForegroundService(
- PlaybackService.this,
- new Intent(PlaybackService.this, PlaybackService.class));
- serviceInStartedState = true;
- }
-
- doSetupNotification();
- }
-
- private void updateNotificationForPause(PlaybackStateCompat state) {
- doSetupNotification();
- }
-
- private void moveServiceOutOfStartedState(PlaybackStateCompat state) {
- stopService();
- }
-
- private void doSetupNotification() {
- if (mediaPlayer != null && mediaPlayer.getPlayable() != null) {
- // it updates notification and set foreground status
- // based on player status (similar to PlaybackState)
- setupNotification(mediaPlayer.getPlayable(), toTreatNextPauseAsStopOnUI);
- } else {
- // should not happen unless there are bugs.
- Log.e(TAG, "doSetupNotification() - unexpectedly there is no playable. No notification setup done. mediaPlayer." + mediaPlayer);
- }
- }
- }
-
- private final ServiceManager serviceManager = new ServiceManager();
-
}
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
deleted file mode 100644
index 0fe11ec53..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/util/Optional.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * 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 4d5ed449b..33c02fae0 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,6 +12,8 @@ import android.media.MediaPlayer;
import android.os.Build;
import android.os.IBinder;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
@@ -35,7 +37,6 @@ 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;
@@ -104,7 +105,6 @@ public abstract class PlaybackController {
}
private synchronized void initServiceRunning() {
- Log.v(TAG, "initServiceRunning()");
if (initialized) {
return;
}
@@ -187,15 +187,22 @@ public abstract class PlaybackController {
serviceBinder = Observable.fromCallable(this::getPlayLastPlayedMediaIntent)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
- .subscribe(optionalIntent -> {
+ .subscribe(intent -> {
boolean bound = false;
- if (optionalIntent.isPresent()) {
- Log.d(TAG, "Calling bind service");
- bound = activity.bindService(optionalIntent.get(), mConnection, 0);
+ if (!PlaybackService.started) {
+ if (intent != null) {
+ Log.d(TAG, "Calling start service");
+ ContextCompat.startForegroundService(activity, intent);
+ bound = activity.bindService(intent, mConnection, 0);
+ } else {
+ status = PlayerStatus.STOPPED;
+ setupGUI();
+ handleStatus();
+ }
} else {
- status = PlayerStatus.STOPPED;
- setupGUI();
- handleStatus();
+ Log.d(TAG, "PlaybackService is running, trying to connect without start command.");
+ bound = activity.bindService(new Intent(activity, PlaybackService.class),
+ mConnection, 0);
}
Log.d(TAG, "Result for service binding: " + bound);
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
@@ -205,26 +212,24 @@ 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.
*/
- @NonNull
- private Optional<Intent> getPlayLastPlayedMediaIntent() {
+ @Nullable private 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 Optional.empty();
+ return null;
}
-
boolean fileExists = media.localFileAvailable();
boolean lastIsStream = PlaybackPreferences.getCurrentEpisodeIsStream();
if (!fileExists && !lastIsStream && media instanceof FeedMedia) {
DBTasks.notifyMissingFeedMediaFile(activity, (FeedMedia) media);
}
- return Optional.of(new PlaybackServiceStarter(activity, media)
+ return new PlaybackServiceStarter(activity, media)
.startWhenPrepared(false)
.shouldStream(lastIsStream || !fileExists)
- .getIntent());
+ .getIntent();
}
@@ -582,8 +587,7 @@ public abstract class PlaybackController {
.startWhenPrepared(true)
.streamIfLastWasStream()
.start();
- Log.d(TAG, "Play/Pause button was pressed, but playbackservice was null - " +
- "it is likely to have been released by Android system. Restarting it.");
+ Log.w(TAG, "Play/Pause button was pressed, but playbackservice was null!");
return;
}
switch (status) {
@@ -760,7 +764,6 @@ public abstract class PlaybackController {
}
public void notifyVideoSurfaceAbandoned() {
- Log.v(TAG, "notifyVideoSurfaceAbandoned() - hasPlaybackService=" + (playbackService != null));
if (playbackService != null) {
playbackService.notifyVideoSurfaceAbandoned();
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackServiceStarter.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackServiceStarter.java
index 64cf61457..f7d2ee409 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackServiceStarter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackServiceStarter.java
@@ -2,15 +2,14 @@ package de.danoeh.antennapod.core.util.playback;
import android.content.Context;
import android.content.Intent;
+import android.media.MediaPlayer;
+import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
-import android.util.Log;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
public class PlaybackServiceStarter {
- private static final String TAG = "PlaybackServiceStarter";
-
private final Context context;
private final Playable media;
private boolean startWhenPrepared = false;
@@ -67,10 +66,6 @@ public class PlaybackServiceStarter {
launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, shouldStream);
launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY, prepareImmediately);
- if (media == null) {
- Log.e(TAG, "getIntent() - media is unexpectedly null. intent:" + launchIntent);
- }
-
return launchIntent;
}