From 7c4b98be59e344dc742c0780f1b5462f60664b95 Mon Sep 17 00:00:00 2001 From: orionlee Date: Sat, 5 Jan 2019 19:12:29 -0800 Subject: [PATCH] Cherry-Pick: Add a generic java8-like Optional class For use with RxJava2 where null was to be returned (RxJava2 requires non-null). Cherry-picked from PR #2954 --- .../danoeh/antennapod/core/util/Optional.java | 213 ++++++++++++++++++ .../util/playback/PlaybackController.java | 19 +- 2 files changed, 223 insertions(+), 9 deletions(-) create mode 100644 core/src/main/java/de/danoeh/antennapod/core/util/Optional.java 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. + * + *

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 33c02fae0..a77086f2a 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.support.v4.content.ContextCompat; import android.text.TextUtils; import android.util.Log; @@ -37,6 +36,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,13 +187,13 @@ 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) { + if (optionalIntent.isPresent()) { Log.d(TAG, "Calling start service"); - ContextCompat.startForegroundService(activity, intent); - bound = activity.bindService(intent, mConnection, 0); + ContextCompat.startForegroundService(activity, optionalIntent.get()); + bound = activity.bindService(optionalIntent.get(), mConnection, 0); } else { status = PlayerStatus.STOPPED; setupGUI(); @@ -212,12 +212,13 @@ 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 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(); @@ -226,10 +227,10 @@ public abstract class PlaybackController { DBTasks.notifyMissingFeedMediaFile(activity, (FeedMedia) media); } - return new PlaybackServiceStarter(activity, media) + return Optional.of(new PlaybackServiceStarter(activity, media) .startWhenPrepared(false) .shouldStream(lastIsStream || !fileExists) - .getIntent(); + .getIntent()); }