diff options
Diffstat (limited to 'net')
19 files changed, 517 insertions, 310 deletions
diff --git a/net/common/build.gradle b/net/common/build.gradle index 67efdf43f..57b9f5f23 100644 --- a/net/common/build.gradle +++ b/net/common/build.gradle @@ -2,14 +2,20 @@ plugins { id("com.android.library") } apply from: "../../common.gradle" +apply from: "../../playFlavor.gradle" android { namespace "de.danoeh.antennapod.net.common" } dependencies { + implementation project(':model') + implementation project(':net:ssl') + implementation project(':storage:preferences') + annotationProcessor "androidx.annotation:annotation:$annotationVersion" implementation "com.squareup.okhttp3:okhttp:$okhttpVersion" + implementation "com.squareup.okhttp3:okhttp-urlconnection:$okhttpVersion" testImplementation "junit:junit:$junitVersion" testImplementation "org.robolectric:robolectric:$robolectricVersion" diff --git a/net/common/src/main/AndroidManifest.xml b/net/common/src/main/AndroidManifest.xml new file mode 100644 index 000000000..1ffc73012 --- /dev/null +++ b/net/common/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> + + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> +</manifest> diff --git a/net/common/src/main/java/de/danoeh/antennapod/net/common/AntennapodHttpClient.java b/net/common/src/main/java/de/danoeh/antennapod/net/common/AntennapodHttpClient.java new file mode 100644 index 000000000..0a5231172 --- /dev/null +++ b/net/common/src/main/java/de/danoeh/antennapod/net/common/AntennapodHttpClient.java @@ -0,0 +1,108 @@ +package de.danoeh.antennapod.net.common; + +import android.text.TextUtils; +import android.util.Log; +import androidx.annotation.NonNull; +import de.danoeh.antennapod.model.download.ProxyConfig; +import de.danoeh.antennapod.net.ssl.SslClientSetup; +import okhttp3.Cache; +import okhttp3.Credentials; +import okhttp3.JavaNetCookieJar; +import okhttp3.OkHttpClient; +import java.io.File; +import java.net.CookieManager; +import java.net.CookiePolicy; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.SocketAddress; +import java.util.concurrent.TimeUnit; + +/** + * Provides access to a HttpClient singleton. + */ +public class AntennapodHttpClient { + private static final String TAG = "AntennapodHttpClient"; + private static final int CONNECTION_TIMEOUT = 10000; + private static final int READ_TIMEOUT = 30000; + private static final int MAX_CONNECTIONS = 8; + private static File cacheDirectory; + private static ProxyConfig proxyConfig; + + private static volatile OkHttpClient httpClient = null; + + private AntennapodHttpClient() { + + } + + /** + * Returns the HttpClient singleton. + */ + public static synchronized OkHttpClient getHttpClient() { + if (httpClient == null) { + httpClient = newBuilder().build(); + } + return httpClient; + } + + public static synchronized void reinit() { + httpClient = newBuilder().build(); + } + + /** + * Creates a new HTTP client. Most users should just use + * getHttpClient() to get the standard AntennaPod client, + * but sometimes it's necessary for others to have their own + * copy so that the clients don't share state. + * @return http client + */ + @NonNull + public static OkHttpClient.Builder newBuilder() { + Log.d(TAG, "Creating new instance of HTTP client"); + + System.setProperty("http.maxConnections", String.valueOf(MAX_CONNECTIONS)); + + OkHttpClient.Builder builder = new OkHttpClient.Builder(); + builder.interceptors().add(new BasicAuthorizationInterceptor()); + builder.networkInterceptors().add(new UserAgentInterceptor()); + + // set cookie handler + CookieManager cm = new CookieManager(); + cm.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER); + builder.cookieJar(new JavaNetCookieJar(cm)); + + // set timeouts + builder.connectTimeout(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS); + builder.readTimeout(READ_TIMEOUT, TimeUnit.MILLISECONDS); + builder.writeTimeout(READ_TIMEOUT, TimeUnit.MILLISECONDS); + builder.cache(new Cache(cacheDirectory, 20L * 1000000)); // 20MB + + // configure redirects + builder.followRedirects(true); + builder.followSslRedirects(true); + + if (proxyConfig != null && proxyConfig.type != Proxy.Type.DIRECT && !TextUtils.isEmpty(proxyConfig.host)) { + int port = proxyConfig.port > 0 ? proxyConfig.port : ProxyConfig.DEFAULT_PORT; + SocketAddress address = InetSocketAddress.createUnresolved(proxyConfig.host, port); + builder.proxy(new Proxy(proxyConfig.type, address)); + if (!TextUtils.isEmpty(proxyConfig.username) && proxyConfig.password != null) { + builder.proxyAuthenticator((route, response) -> { + String credentials = Credentials.basic(proxyConfig.username, proxyConfig.password); + return response.request().newBuilder() + .header("Proxy-Authorization", credentials) + .build(); + }); + } + } + + SslClientSetup.installCertificates(builder); + return builder; + } + + public static void setCacheDirectory(File cacheDirectory) { + AntennapodHttpClient.cacheDirectory = cacheDirectory; + } + + public static void setProxyConfig(ProxyConfig proxyConfig) { + AntennapodHttpClient.proxyConfig = proxyConfig; + } +} diff --git a/net/common/src/main/java/de/danoeh/antennapod/net/common/BasicAuthorizationInterceptor.java b/net/common/src/main/java/de/danoeh/antennapod/net/common/BasicAuthorizationInterceptor.java new file mode 100644 index 000000000..8e7b9a4f4 --- /dev/null +++ b/net/common/src/main/java/de/danoeh/antennapod/net/common/BasicAuthorizationInterceptor.java @@ -0,0 +1,80 @@ +package de.danoeh.antennapod.net.common; + +import android.text.TextUtils; +import android.util.Log; +import androidx.annotation.NonNull; +import de.danoeh.antennapod.model.download.DownloadRequest; +import de.danoeh.antennapod.net.common.HttpCredentialEncoder; +import de.danoeh.antennapod.net.common.UriUtil; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.util.List; + +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +public class BasicAuthorizationInterceptor implements Interceptor { + private static final String TAG = "BasicAuthInterceptor"; + private static final String HEADER_AUTHORIZATION = "Authorization"; + + @Override + @NonNull + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + + Response response = chain.proceed(request); + + if (response.code() != HttpURLConnection.HTTP_UNAUTHORIZED) { + return response; + } + + Request.Builder newRequest = request.newBuilder(); + if (!TextUtils.equals(response.request().url().toString(), request.url().toString())) { + // Redirect detected. OkHTTP does not re-add the headers on redirect, so calling the new location directly. + newRequest.url(response.request().url()); + + List<String> authorizationHeaders = request.headers().values(HEADER_AUTHORIZATION); + if (!authorizationHeaders.isEmpty() && !TextUtils.isEmpty(authorizationHeaders.get(0))) { + // Call already had authorization headers. Try again with the same credentials. + newRequest.header(HEADER_AUTHORIZATION, authorizationHeaders.get(0)); + return chain.proceed(newRequest.build()); + } + } + + String userInfo = null; + if (request.tag() instanceof DownloadRequest) { + DownloadRequest downloadRequest = (DownloadRequest) request.tag(); + userInfo = UriUtil.getURIFromRequestUrl(downloadRequest.getSource()).getUserInfo(); + if (TextUtils.isEmpty(userInfo) + && (!TextUtils.isEmpty(downloadRequest.getUsername()) + || !TextUtils.isEmpty(downloadRequest.getPassword()))) { + userInfo = downloadRequest.getUsername() + ":" + downloadRequest.getPassword(); + } + } + + if (TextUtils.isEmpty(userInfo)) { + Log.d(TAG, "no credentials for '" + request.url() + "'"); + return response; + } + + if (!userInfo.contains(":")) { + Log.d(TAG, "Invalid credentials for '" + request.url() + "'"); + return response; + } + String username = userInfo.substring(0, userInfo.indexOf(':')); + String password = userInfo.substring(userInfo.indexOf(':') + 1); + + Log.d(TAG, "Authorization failed, re-trying with ISO-8859-1 encoded credentials"); + newRequest.header(HEADER_AUTHORIZATION, HttpCredentialEncoder.encode(username, password, "ISO-8859-1")); + response = chain.proceed(newRequest.build()); + + if (response.code() != HttpURLConnection.HTTP_UNAUTHORIZED) { + return response; + } + + Log.d(TAG, "Authorization failed, re-trying with UTF-8 encoded credentials"); + newRequest.header(HEADER_AUTHORIZATION, HttpCredentialEncoder.encode(username, password, "UTF-8")); + return chain.proceed(newRequest.build()); + } +} diff --git a/net/common/src/main/java/de/danoeh/antennapod/net/common/HttpCredentialEncoder.java b/net/common/src/main/java/de/danoeh/antennapod/net/common/HttpCredentialEncoder.java new file mode 100644 index 000000000..9b2063ce6 --- /dev/null +++ b/net/common/src/main/java/de/danoeh/antennapod/net/common/HttpCredentialEncoder.java @@ -0,0 +1,18 @@ +package de.danoeh.antennapod.net.common; + +import android.util.Base64; + +import java.io.UnsupportedEncodingException; + +public abstract class HttpCredentialEncoder { + public static String encode(String username, String password, String charset) { + try { + String credentials = username + ":" + password; + byte[] bytes = credentials.getBytes(charset); + String encoded = Base64.encodeToString(bytes, Base64.NO_WRAP); + return "Basic " + encoded; + } catch (UnsupportedEncodingException e) { + throw new AssertionError(e); + } + } +} diff --git a/net/common/src/main/java/de/danoeh/antennapod/net/common/NetworkUtils.java b/net/common/src/main/java/de/danoeh/antennapod/net/common/NetworkUtils.java new file mode 100644 index 000000000..179c4e13e --- /dev/null +++ b/net/common/src/main/java/de/danoeh/antennapod/net/common/NetworkUtils.java @@ -0,0 +1,144 @@ +package de.danoeh.antennapod.net.common; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.net.wifi.WifiManager; +import android.os.Build; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import de.danoeh.antennapod.storage.preferences.UserPreferences; + +public class NetworkUtils { + private static final String REGEX_PATTERN_IP_ADDRESS = "([0-9]{1,3}[\\.]){3}[0-9]{1,3}"; + + private NetworkUtils(){} + + private static Context context; + + public static void init(Context context) { + NetworkUtils.context = context; + } + + public static boolean isAutoDownloadAllowed() { + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = cm.getActiveNetworkInfo(); + if (networkInfo == null) { + return false; + } + if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) { + if (UserPreferences.isEnableAutodownloadWifiFilter()) { + return isInAllowedWifiNetwork(); + } else { + return !isNetworkMetered(); + } + } else if (networkInfo.getType() == ConnectivityManager.TYPE_ETHERNET) { + return true; + } else { + return UserPreferences.isAllowMobileAutoDownload() || !NetworkUtils.isNetworkRestricted(); + } + } + + public static boolean networkAvailable() { + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo info = cm.getActiveNetworkInfo(); + return info != null && info.isConnected(); + } + + public static boolean isEpisodeDownloadAllowed() { + return UserPreferences.isAllowMobileEpisodeDownload() || !NetworkUtils.isNetworkRestricted(); + } + + public static boolean isEpisodeHeadDownloadAllowed() { + // It is not an image but it is a similarly tiny request + // that is probably not even considered a download by most users + return isImageAllowed(); + } + + public static boolean isImageAllowed() { + return UserPreferences.isAllowMobileImages() || !NetworkUtils.isNetworkRestricted(); + } + + public static boolean isStreamingAllowed() { + return UserPreferences.isAllowMobileStreaming() || !NetworkUtils.isNetworkRestricted(); + } + + public static boolean isFeedRefreshAllowed() { + return UserPreferences.isAllowMobileFeedRefresh() || !NetworkUtils.isNetworkRestricted(); + } + + public static boolean isNetworkRestricted() { + return isNetworkMetered() || isNetworkCellular(); + } + + private static boolean isNetworkMetered() { + ConnectivityManager connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + return connManager.isActiveNetworkMetered(); + } + + public static boolean isVpnOverWifi() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return false; + } + ConnectivityManager connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkCapabilities capabilities = connManager.getNetworkCapabilities(connManager.getActiveNetwork()); + return capabilities != null + && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) + && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN); + } + + private static boolean isNetworkCellular() { + ConnectivityManager connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (Build.VERSION.SDK_INT >= 23) { + Network network = connManager.getActiveNetwork(); + if (network == null) { + return false; // Nothing connected + } + NetworkInfo info = connManager.getNetworkInfo(network); + if (info == null) { + return true; // Better be safe than sorry + } + NetworkCapabilities capabilities = connManager.getNetworkCapabilities(network); + if (capabilities == null) { + return true; // Better be safe than sorry + } + return capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR); + } else { + // if the default network is a VPN, + // this method will return the NetworkInfo for one of its underlying networks + NetworkInfo info = connManager.getActiveNetworkInfo(); + if (info == null) { + return false; // Nothing connected + } + //noinspection deprecation + return info.getType() == ConnectivityManager.TYPE_MOBILE; + } + } + + private static boolean isInAllowedWifiNetwork() { + WifiManager wm = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE); + List<String> selectedNetworks = Arrays.asList(UserPreferences.getAutodownloadSelectedNetworks()); + return selectedNetworks.contains(Integer.toString(wm.getConnectionInfo().getNetworkId())); + } + + public static boolean wasDownloadBlocked(Throwable throwable) { + String message = throwable.getMessage(); + if (message != null) { + Pattern pattern = Pattern.compile(REGEX_PATTERN_IP_ADDRESS); + Matcher matcher = pattern.matcher(message); + if (matcher.find()) { + String ip = matcher.group(); + return ip.startsWith("127.") || ip.startsWith("0."); + } + } + if (throwable.getCause() != null) { + return wasDownloadBlocked(throwable.getCause()); + } + return false; + } +} diff --git a/net/common/src/main/java/de/danoeh/antennapod/net/common/UriUtil.java b/net/common/src/main/java/de/danoeh/antennapod/net/common/UriUtil.java new file mode 100644 index 000000000..63fc087d6 --- /dev/null +++ b/net/common/src/main/java/de/danoeh/antennapod/net/common/UriUtil.java @@ -0,0 +1,28 @@ +package de.danoeh.antennapod.net.common; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; + +/** + * Utility methods for dealing with URL encoding. + */ +public class UriUtil { + private UriUtil() {} + + public static URI getURIFromRequestUrl(String source) { + // try without encoding the URI + try { + return new URI(source); + } catch (URISyntaxException ignore) { + System.out.println("Source is not encoded, encoding now"); + } + try { + URL url = new URL(source); + return new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef()); + } catch (MalformedURLException | URISyntaxException e) { + throw new IllegalArgumentException(e); + } + } +} diff --git a/net/common/src/main/java/de/danoeh/antennapod/net/common/UserAgentInterceptor.java b/net/common/src/main/java/de/danoeh/antennapod/net/common/UserAgentInterceptor.java new file mode 100644 index 000000000..7a61aa070 --- /dev/null +++ b/net/common/src/main/java/de/danoeh/antennapod/net/common/UserAgentInterceptor.java @@ -0,0 +1,17 @@ +package de.danoeh.antennapod.net.common; + +import okhttp3.Interceptor; +import okhttp3.Response; + +import java.io.IOException; + +public class UserAgentInterceptor implements Interceptor { + public static String USER_AGENT = "AntennaPod/0.0.0"; + + @Override + public Response intercept(Chain chain) throws IOException { + return chain.proceed(chain.request().newBuilder() + .header("User-Agent", USER_AGENT) + .build()); + } +} diff --git a/net/common/src/test/java/de/danoeh/antennapod/net/common/UriUtilTest.java b/net/common/src/test/java/de/danoeh/antennapod/net/common/UriUtilTest.java new file mode 100644 index 000000000..32a856284 --- /dev/null +++ b/net/common/src/test/java/de/danoeh/antennapod/net/common/UriUtilTest.java @@ -0,0 +1,25 @@ +package de.danoeh.antennapod.net.common; + +import de.danoeh.antennapod.net.common.UriUtil; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Test class for URIUtil + */ +public class UriUtilTest { + + @Test + public void testGetURIFromRequestUrlShouldNotEncode() { + final String testUrl = "http://example.com/this%20is%20encoded"; + assertEquals(testUrl, UriUtil.getURIFromRequestUrl(testUrl).toString()); + } + + @Test + public void testGetURIFromRequestUrlShouldEncode() { + final String testUrl = "http://example.com/this is not encoded"; + final String expected = "http://example.com/this%20is%20not%20encoded"; + assertEquals(expected, UriUtil.getURIFromRequestUrl(testUrl).toString()); + } +} diff --git a/net/discovery/build.gradle b/net/discovery/build.gradle index df47258fa..d071f1ba7 100644 --- a/net/discovery/build.gradle +++ b/net/discovery/build.gradle @@ -21,6 +21,7 @@ android { dependencies { implementation project(':core') implementation project(':model') + implementation project(':net:common') implementation project(':net:sync:gpoddernet') implementation project(':net:sync:model') diff --git a/net/discovery/src/main/java/de/danoeh/antennapod/net/discovery/FyydPodcastSearcher.java b/net/discovery/src/main/java/de/danoeh/antennapod/net/discovery/FyydPodcastSearcher.java index 30be87931..2899af677 100644 --- a/net/discovery/src/main/java/de/danoeh/antennapod/net/discovery/FyydPodcastSearcher.java +++ b/net/discovery/src/main/java/de/danoeh/antennapod/net/discovery/FyydPodcastSearcher.java @@ -1,6 +1,6 @@ package de.danoeh.antennapod.net.discovery; -import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; +import de.danoeh.antennapod.net.common.AntennapodHttpClient; import de.mfietz.fyydlin.FyydClient; import de.mfietz.fyydlin.FyydResponse; import de.mfietz.fyydlin.SearchHit; diff --git a/net/discovery/src/main/java/de/danoeh/antennapod/net/discovery/GpodnetPodcastSearcher.java b/net/discovery/src/main/java/de/danoeh/antennapod/net/discovery/GpodnetPodcastSearcher.java index 222c415ab..a3f2c648f 100644 --- a/net/discovery/src/main/java/de/danoeh/antennapod/net/discovery/GpodnetPodcastSearcher.java +++ b/net/discovery/src/main/java/de/danoeh/antennapod/net/discovery/GpodnetPodcastSearcher.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.net.discovery; import de.danoeh.antennapod.core.sync.SynchronizationCredentials; -import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; +import de.danoeh.antennapod.net.common.AntennapodHttpClient; import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetService; import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetServiceException; import de.danoeh.antennapod.net.sync.gpoddernet.model.GpodnetPodcast; diff --git a/net/discovery/src/main/java/de/danoeh/antennapod/net/discovery/ItunesPodcastSearcher.java b/net/discovery/src/main/java/de/danoeh/antennapod/net/discovery/ItunesPodcastSearcher.java index 32e150f76..b9df9d185 100644 --- a/net/discovery/src/main/java/de/danoeh/antennapod/net/discovery/ItunesPodcastSearcher.java +++ b/net/discovery/src/main/java/de/danoeh/antennapod/net/discovery/ItunesPodcastSearcher.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.net.discovery; import de.danoeh.antennapod.core.feed.FeedUrlNotFoundException; -import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; +import de.danoeh.antennapod.net.common.AntennapodHttpClient; import io.reactivex.Single; import io.reactivex.SingleOnSubscribe; import io.reactivex.android.schedulers.AndroidSchedulers; diff --git a/net/discovery/src/main/java/de/danoeh/antennapod/net/discovery/ItunesTopListLoader.java b/net/discovery/src/main/java/de/danoeh/antennapod/net/discovery/ItunesTopListLoader.java index 5fa48458e..128aad593 100644 --- a/net/discovery/src/main/java/de/danoeh/antennapod/net/discovery/ItunesTopListLoader.java +++ b/net/discovery/src/main/java/de/danoeh/antennapod/net/discovery/ItunesTopListLoader.java @@ -2,8 +2,8 @@ package de.danoeh.antennapod.net.discovery; import android.content.Context; import android.util.Log; -import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; import de.danoeh.antennapod.model.feed.Feed; +import de.danoeh.antennapod.net.common.AntennapodHttpClient; import okhttp3.CacheControl; import okhttp3.OkHttpClient; import okhttp3.Request; diff --git a/net/discovery/src/main/java/de/danoeh/antennapod/net/discovery/PodcastIndexPodcastSearcher.java b/net/discovery/src/main/java/de/danoeh/antennapod/net/discovery/PodcastIndexPodcastSearcher.java index 105667393..c777827df 100644 --- a/net/discovery/src/main/java/de/danoeh/antennapod/net/discovery/PodcastIndexPodcastSearcher.java +++ b/net/discovery/src/main/java/de/danoeh/antennapod/net/discovery/PodcastIndexPodcastSearcher.java @@ -1,5 +1,7 @@ package de.danoeh.antennapod.net.discovery; +import de.danoeh.antennapod.net.common.AntennapodHttpClient; +import de.danoeh.antennapod.net.common.UserAgentInterceptor; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -15,8 +17,6 @@ import java.util.List; import java.util.Locale; import java.util.TimeZone; -import de.danoeh.antennapod.core.ClientConfig; -import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; import io.reactivex.Single; import io.reactivex.SingleOnSubscribe; import io.reactivex.android.schedulers.AndroidSchedulers; @@ -100,7 +100,7 @@ public class PodcastIndexPodcastSearcher implements PodcastSearcher { .addHeader("X-Auth-Date", apiHeaderTime) .addHeader("X-Auth-Key", BuildConfig.PODCASTINDEX_API_KEY) .addHeader("Authorization", hashString) - .addHeader("User-Agent", ClientConfig.USER_AGENT) + .addHeader("User-Agent", UserAgentInterceptor.USER_AGENT) .url(url); return httpReq.build(); } diff --git a/net/download/service-interface/build.gradle b/net/download/service-interface/build.gradle index 784c1375f..84a8dfd05 100644 --- a/net/download/service-interface/build.gradle +++ b/net/download/service-interface/build.gradle @@ -3,6 +3,7 @@ plugins { id("java-test-fixtures") } apply from: "../../../common.gradle" +apply from: "../../../playFlavor.gradle" android { namespace "de.danoeh.antennapod.net.download.serviceinterface" diff --git a/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/DownloadRequest.java b/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/DownloadRequest.java deleted file mode 100644 index f789fda5c..000000000 --- a/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/DownloadRequest.java +++ /dev/null @@ -1,297 +0,0 @@ -package de.danoeh.antennapod.net.download.serviceinterface; - -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.text.TextUtils; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import de.danoeh.antennapod.model.feed.Feed; -import de.danoeh.antennapod.net.common.UrlChecker; -import de.danoeh.antennapod.model.feed.FeedMedia; - -public class DownloadRequest implements Parcelable { - public static final String REQUEST_ARG_PAGE_NR = "page"; - - private final String destination; - private final String source; - private final String title; - private String username; - private String password; - private String lastModified; - private final long feedfileId; - private final int feedfileType; - private final Bundle arguments; - - private int progressPercent; - private long soFar; - private long size; - private int statusMsg; - private boolean mediaEnqueued; - private boolean initiatedByUser; - - public DownloadRequest(@NonNull String destination, @NonNull String source, @NonNull String title, long feedfileId, - int feedfileType, String username, String password, - Bundle arguments, boolean initiatedByUser) { - this(destination, source, title, feedfileId, feedfileType, null, username, password, false, - arguments, initiatedByUser); - } - - private DownloadRequest(Builder builder) { - this(builder.destination, builder.source, builder.title, builder.feedfileId, builder.feedfileType, - builder.lastModified, builder.username, builder.password, false, - builder.arguments, builder.initiatedByUser); - } - - private DownloadRequest(Parcel in) { - this(in.readString(), in.readString(), in.readString(), in.readLong(), in.readInt(), in.readString(), - nullIfEmpty(in.readString()), nullIfEmpty(in.readString()), in.readByte() > 0, - in.readBundle(), in.readByte() > 0); - } - - private DownloadRequest(String destination, String source, String title, long feedfileId, int feedfileType, - String lastModified, String username, String password, - boolean mediaEnqueued, Bundle arguments, boolean initiatedByUser) { - this.destination = destination; - this.source = source; - this.title = title; - this.feedfileId = feedfileId; - this.feedfileType = feedfileType; - this.lastModified = lastModified; - this.username = username; - this.password = password; - this.mediaEnqueued = mediaEnqueued; - this.arguments = arguments; - this.initiatedByUser = initiatedByUser; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(destination); - dest.writeString(source); - dest.writeString(title); - dest.writeLong(feedfileId); - dest.writeInt(feedfileType); - dest.writeString(lastModified); - // in case of null username/password, still write an empty string - // (rather than skipping it). Otherwise, unmarshalling a collection - // of them from a Parcel (from an Intent extra to submit a request to DownloadService) will fail. - // - // see: https://stackoverflow.com/a/22926342 - dest.writeString(nonNullString(username)); - dest.writeString(nonNullString(password)); - dest.writeByte((mediaEnqueued) ? (byte) 1 : 0); - dest.writeBundle(arguments); - dest.writeByte(initiatedByUser ? (byte) 1 : 0); - } - - private static String nonNullString(String str) { - return str != null ? str : ""; - } - - private static String nullIfEmpty(String str) { - return TextUtils.isEmpty(str) ? null : str; - } - - public static final Parcelable.Creator<DownloadRequest> CREATOR = new Parcelable.Creator<DownloadRequest>() { - public DownloadRequest createFromParcel(Parcel in) { - return new DownloadRequest(in); - } - - public DownloadRequest[] newArray(int size) { - return new DownloadRequest[size]; - } - }; - - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof DownloadRequest)) return false; - - DownloadRequest that = (DownloadRequest) o; - - if (lastModified != null ? !lastModified.equals(that.lastModified) : that.lastModified != null) - return false; - if (feedfileId != that.feedfileId) return false; - if (feedfileType != that.feedfileType) return false; - if (progressPercent != that.progressPercent) return false; - if (size != that.size) return false; - if (soFar != that.soFar) return false; - if (statusMsg != that.statusMsg) return false; - if (!destination.equals(that.destination)) return false; - if (password != null ? !password.equals(that.password) : that.password != null) - return false; - if (!source.equals(that.source)) return false; - if (title != null ? !title.equals(that.title) : that.title != null) return false; - if (username != null ? !username.equals(that.username) : that.username != null) - return false; - if (mediaEnqueued != that.mediaEnqueued) return false; - if (initiatedByUser != that.initiatedByUser) return false; - return true; - } - - @Override - public int hashCode() { - int result = destination.hashCode(); - result = 31 * result + source.hashCode(); - result = 31 * result + (title != null ? title.hashCode() : 0); - result = 31 * result + (username != null ? username.hashCode() : 0); - result = 31 * result + (password != null ? password.hashCode() : 0); - result = 31 * result + (lastModified != null ? lastModified.hashCode() : 0); - result = 31 * result + (int) (feedfileId ^ (feedfileId >>> 32)); - result = 31 * result + feedfileType; - result = 31 * result + arguments.hashCode(); - result = 31 * result + progressPercent; - result = 31 * result + (int) (soFar ^ (soFar >>> 32)); - result = 31 * result + (int) (size ^ (size >>> 32)); - result = 31 * result + statusMsg; - result = 31 * result + (mediaEnqueued ? 1 : 0); - return result; - } - - public String getDestination() { - return destination; - } - - public String getSource() { - return source; - } - - public String getTitle() { - return title; - } - - public long getFeedfileId() { - return feedfileId; - } - - public int getFeedfileType() { - return feedfileType; - } - - public int getProgressPercent() { - return progressPercent; - } - - public void setProgressPercent(int progressPercent) { - this.progressPercent = progressPercent; - } - - public long getSoFar() { - return soFar; - } - - public void setSoFar(long soFar) { - this.soFar = soFar; - } - - public long getSize() { - return size; - } - - public void setSize(long size) { - this.size = size; - } - - public void setStatusMsg(int statusMsg) { - this.statusMsg = statusMsg; - } - - public String getUsername() { - return username; - } - - public String getPassword() { - return password; - } - - public void setUsername(String username) { - this.username = username; - } - - public void setPassword(String password) { - this.password = password; - } - - public DownloadRequest setLastModified(@Nullable String lastModified) { - this.lastModified = lastModified; - return this; - } - - @Nullable - public String getLastModified() { - return lastModified; - } - - public Bundle getArguments() { - return arguments; - } - - public static class Builder { - private final String destination; - private String source; - private final String title; - private String username; - private String password; - private String lastModified; - private final long feedfileId; - private final int feedfileType; - private final Bundle arguments = new Bundle(); - private boolean initiatedByUser = true; - - public Builder(@NonNull String destination, @NonNull FeedMedia media) { - this.destination = destination; - this.source = UrlChecker.prepareUrl(media.getDownload_url()); - this.title = media.getHumanReadableIdentifier(); - this.feedfileId = media.getId(); - this.feedfileType = FeedMedia.FEEDFILETYPE_FEEDMEDIA; - } - - public Builder(@NonNull String destination, @NonNull Feed feed) { - this.destination = destination; - this.source = feed.isLocalFeed() ? feed.getDownload_url() : UrlChecker.prepareUrl(feed.getDownload_url()); - this.title = feed.getHumanReadableIdentifier(); - this.feedfileId = feed.getId(); - this.feedfileType = Feed.FEEDFILETYPE_FEED; - arguments.putInt(REQUEST_ARG_PAGE_NR, feed.getPageNr()); - } - - public Builder withInitiatedByUser(boolean initiatedByUser) { - this.initiatedByUser = initiatedByUser; - return this; - } - - public void setSource(String source) { - this.source = source; - } - - public void setForce(boolean force) { - if (force) { - lastModified = null; - } - } - - public Builder lastModified(String lastModified) { - this.lastModified = lastModified; - return this; - } - - public Builder withAuthentication(String username, String password) { - this.username = username; - this.password = password; - return this; - } - - public DownloadRequest build() { - return new DownloadRequest(this); - } - } -} diff --git a/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/DownloadRequestBuilder.java b/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/DownloadRequestBuilder.java new file mode 100644 index 000000000..15d2858bc --- /dev/null +++ b/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/DownloadRequestBuilder.java @@ -0,0 +1,69 @@ +package de.danoeh.antennapod.net.download.serviceinterface; + +import android.os.Bundle; +import androidx.annotation.NonNull; +import de.danoeh.antennapod.model.download.DownloadRequest; +import de.danoeh.antennapod.model.feed.Feed; +import de.danoeh.antennapod.model.feed.FeedMedia; +import de.danoeh.antennapod.net.common.UrlChecker; + +public class DownloadRequestBuilder { + private final String destination; + private String source; + private final String title; + private String username; + private String password; + private String lastModified; + private final long feedfileId; + private final int feedfileType; + private final Bundle arguments = new Bundle(); + private boolean initiatedByUser = true; + + public DownloadRequestBuilder(@NonNull String destination, @NonNull FeedMedia media) { + this.destination = destination; + this.source = UrlChecker.prepareUrl(media.getDownload_url()); + this.title = media.getHumanReadableIdentifier(); + this.feedfileId = media.getId(); + this.feedfileType = FeedMedia.FEEDFILETYPE_FEEDMEDIA; + } + + public DownloadRequestBuilder(@NonNull String destination, @NonNull Feed feed) { + this.destination = destination; + this.source = feed.isLocalFeed() ? feed.getDownload_url() : UrlChecker.prepareUrl(feed.getDownload_url()); + this.title = feed.getHumanReadableIdentifier(); + this.feedfileId = feed.getId(); + this.feedfileType = Feed.FEEDFILETYPE_FEED; + arguments.putInt(DownloadRequest.REQUEST_ARG_PAGE_NR, feed.getPageNr()); + } + + public DownloadRequestBuilder withInitiatedByUser(boolean initiatedByUser) { + this.initiatedByUser = initiatedByUser; + return this; + } + + public void setSource(String source) { + this.source = source; + } + + public void setForce(boolean force) { + if (force) { + lastModified = null; + } + } + + public DownloadRequestBuilder lastModified(String lastModified) { + this.lastModified = lastModified; + return this; + } + + public DownloadRequestBuilder withAuthentication(String username, String password) { + this.username = username; + this.password = password; + return this; + } + + public DownloadRequest build() { + return new DownloadRequest(destination, source, title, feedfileId, feedfileType, + lastModified, username, password, false, arguments, initiatedByUser); + } +}
\ No newline at end of file diff --git a/net/download/service-interface/src/test/java/de/danoeh/antennapod/net/download/serviceinterface/DownloadRequestTest.java b/net/download/service-interface/src/test/java/de/danoeh/antennapod/net/download/serviceinterface/DownloadRequestBuilderTest.java index a48934cfe..e9876a949 100644 --- a/net/download/service-interface/src/test/java/de/danoeh/antennapod/net/download/serviceinterface/DownloadRequestTest.java +++ b/net/download/service-interface/src/test/java/de/danoeh/antennapod/net/download/serviceinterface/DownloadRequestBuilderTest.java @@ -3,6 +3,7 @@ package de.danoeh.antennapod.net.download.serviceinterface; import android.os.Bundle; import android.os.Parcel; +import de.danoeh.antennapod.model.download.DownloadRequest; import de.danoeh.antennapod.model.feed.FeedMedia; import org.junit.Test; import org.junit.runner.RunWith; @@ -14,7 +15,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; @RunWith(RobolectricTestRunner.class) -public class DownloadRequestTest { +public class DownloadRequestBuilderTest { @Test public void parcelInArrayListTest_WithAuth() { @@ -40,15 +41,15 @@ public class DownloadRequestTest { String username = "testUser"; String password = "testPassword"; FeedMedia item = createFeedItem(1); - DownloadRequest request1 = new DownloadRequest.Builder(destStr, item) + DownloadRequest request1 = new DownloadRequestBuilder(destStr, item) .withAuthentication(username, password) .build(); - DownloadRequest request2 = new DownloadRequest.Builder(destStr, item) + DownloadRequest request2 = new DownloadRequestBuilder(destStr, item) .withAuthentication(username, password) .build(); - DownloadRequest request3 = new DownloadRequest.Builder(destStr, item) + DownloadRequest request3 = new DownloadRequestBuilder(destStr, item) .withAuthentication("diffUsername", "diffPassword") .build(); @@ -65,12 +66,12 @@ public class DownloadRequestTest { { // test DownloadRequests to parcel String destStr = "file://location/media.mp3"; FeedMedia item1 = createFeedItem(1); - DownloadRequest request1 = new DownloadRequest.Builder(destStr, item1) + DownloadRequest request1 = new DownloadRequestBuilder(destStr, item1) .withAuthentication(username1, password1) .build(); FeedMedia item2 = createFeedItem(2); - DownloadRequest request2 = new DownloadRequest.Builder(destStr, item2) + DownloadRequest request2 = new DownloadRequestBuilder(destStr, item2) .withAuthentication(username2, password2) .build(); |
