From 3fca616e30005c6fe192e3201d29b0b9107d4c32 Mon Sep 17 00:00:00 2001 From: ByteHamster Date: Fri, 17 May 2019 09:45:22 +0200 Subject: Revert "Merge pull request #2954 from orionlee/bugfix_phantom_notification_rework_2716" This reverts commit 643173de14ade8acfa5e6b92464482c7402e172e, reversing changes made to f2d103736d20bca481817808ee73b240fc2f7be4. --- .../core/service/playback/PlaybackService.java | 295 ++++++++------------- .../de/danoeh/antennapod/core/util/Optional.java | 213 --------------- .../core/util/playback/PlaybackController.java | 39 +-- .../core/util/playback/PlaybackServiceStarter.java | 9 +- .../playback/PlaybackServiceFlavorHelper.java | 2 + 5 files changed, 129 insertions(+), 429 deletions(-) delete mode 100644 core/src/main/java/de/danoeh/antennapod/core/util/Optional.java (limited to 'core/src') 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 { /** @@ -198,6 +192,10 @@ 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 */ @@ -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 serviceManager.moveServiceToStartedState() - // - 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); @@ -1886,6 +1893,24 @@ public class PlaybackService extends MediaBrowserServiceCompat { PlaybackService.this.saveCurrentPosition(fromMediaPlayer, playable, position); } + @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. - * - *

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 { - /** - * 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 Type of the non-existent value - * @return an empty {@code Optional} - */ - public static Optional empty() { - @SuppressWarnings("unchecked") - Optional t = (Optional) 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 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 Optional 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 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 Optional 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: - *

- * - * @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 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; } diff --git a/core/src/play/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java b/core/src/play/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java index 15675c966..7ab1be380 100644 --- a/core/src/play/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java +++ b/core/src/play/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java @@ -153,6 +153,7 @@ public class PlaybackServiceFlavorHelper { // hardware volume buttons control the local device volume mediaRouter.setMediaSessionCompat(null); unregisterWifiBroadcastReceiver(); + callback.setupNotification(false, info); } }; } @@ -182,6 +183,7 @@ public class PlaybackServiceFlavorHelper { // hardware volume buttons control the remote device volume mediaRouter.setMediaSessionCompat(callback.getMediaSession()); registerWifiBroadcastReceiver(); + callback.setupNotification(true, info); } private void switchMediaPlayer(@NonNull PlaybackServiceMediaPlayer newPlayer, -- cgit v1.2.3