aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnonymous <no-reply@google.com>2020-04-30 20:29:00 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2020-04-30 20:29:00 +0000
commit728463db3db89a0b56d6170ac40354f733a2024f (patch)
tree27e047f7667354b69c19637d9cb728f70cbf1892
parentae005a4c3fa292db120e8141da7b0a40ac832f1f (diff)
parentec9c6e0d0e8e5e947017e24c270ca9f13487f937 (diff)
downloadvolley-728463db3db89a0b56d6170ac40354f733a2024f.tar.gz
Import of Volley from GitHub to AOSP. am: ec9c6e0d0e
Change-Id: Id4f28845afa5fd89dc55d0316c7760166f361afd
-rw-r--r--src/main/java/com/android/volley/Cache.java2
-rw-r--r--src/main/java/com/android/volley/CacheDispatcher.java9
-rw-r--r--src/main/java/com/android/volley/NetworkResponse.java26
-rw-r--r--src/main/java/com/android/volley/Request.java5
-rw-r--r--src/main/java/com/android/volley/Response.java16
-rw-r--r--src/main/java/com/android/volley/toolbox/DiskBasedCache.java75
-rw-r--r--src/main/java/com/android/volley/toolbox/HttpHeaderParser.java37
-rw-r--r--src/main/java/com/android/volley/toolbox/HurlStack.java41
-rw-r--r--src/main/java/com/android/volley/toolbox/ImageLoader.java2
-rw-r--r--src/main/java/com/android/volley/toolbox/Volley.java18
-rw-r--r--src/test/java/com/android/volley/CacheDispatcherTest.java19
-rw-r--r--src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java17
-rw-r--r--src/test/java/com/android/volley/toolbox/HttpHeaderParserTest.java9
-rw-r--r--src/test/java/com/android/volley/toolbox/HurlStackTest.java104
14 files changed, 319 insertions, 61 deletions
diff --git a/src/main/java/com/android/volley/Cache.java b/src/main/java/com/android/volley/Cache.java
index 35b2a96..b8908ac 100644
--- a/src/main/java/com/android/volley/Cache.java
+++ b/src/main/java/com/android/volley/Cache.java
@@ -16,6 +16,7 @@
package com.android.volley;
+import androidx.annotation.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -28,6 +29,7 @@ public interface Cache {
* @param key Cache key
* @return An {@link Entry} or null in the event of a cache miss
*/
+ @Nullable
Entry get(String key);
/**
diff --git a/src/main/java/com/android/volley/CacheDispatcher.java b/src/main/java/com/android/volley/CacheDispatcher.java
index be06d1f..12b1035 100644
--- a/src/main/java/com/android/volley/CacheDispatcher.java
+++ b/src/main/java/com/android/volley/CacheDispatcher.java
@@ -159,6 +159,15 @@ public class CacheDispatcher extends Thread {
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
+ if (!response.isSuccess()) {
+ request.addMarker("cache-parsing-failed");
+ mCache.invalidate(request.getCacheKey(), true);
+ request.setCacheEntry(null);
+ if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
+ mNetworkQueue.put(request);
+ }
+ return;
+ }
if (!entry.refreshNeeded()) {
// Completely unexpired cache hit. Just deliver the response.
mDelivery.postResponse(request, response);
diff --git a/src/main/java/com/android/volley/NetworkResponse.java b/src/main/java/com/android/volley/NetworkResponse.java
index 01f48c6..cfbc371 100644
--- a/src/main/java/com/android/volley/NetworkResponse.java
+++ b/src/main/java/com/android/volley/NetworkResponse.java
@@ -16,6 +16,7 @@
package com.android.volley;
+import androidx.annotation.Nullable;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Collections;
@@ -42,7 +43,7 @@ public class NetworkResponse {
public NetworkResponse(
int statusCode,
byte[] data,
- Map<String, String> headers,
+ @Nullable Map<String, String> headers,
boolean notModified,
long networkTimeMs) {
this(statusCode, data, headers, toAllHeaderList(headers), notModified, networkTimeMs);
@@ -62,7 +63,7 @@ public class NetworkResponse {
byte[] data,
boolean notModified,
long networkTimeMs,
- List<Header> allHeaders) {
+ @Nullable List<Header> allHeaders) {
this(statusCode, data, toHeaderMap(allHeaders), allHeaders, notModified, networkTimeMs);
}
@@ -79,7 +80,10 @@ public class NetworkResponse {
*/
@Deprecated
public NetworkResponse(
- int statusCode, byte[] data, Map<String, String> headers, boolean notModified) {
+ int statusCode,
+ byte[] data,
+ @Nullable Map<String, String> headers,
+ boolean notModified) {
this(statusCode, data, headers, notModified, /* networkTimeMs= */ 0);
}
@@ -107,7 +111,7 @@ public class NetworkResponse {
* constructor may be removed in a future release of Volley.
*/
@Deprecated
- public NetworkResponse(byte[] data, Map<String, String> headers) {
+ public NetworkResponse(byte[] data, @Nullable Map<String, String> headers) {
this(
HttpURLConnection.HTTP_OK,
data,
@@ -119,8 +123,8 @@ public class NetworkResponse {
private NetworkResponse(
int statusCode,
byte[] data,
- Map<String, String> headers,
- List<Header> allHeaders,
+ @Nullable Map<String, String> headers,
+ @Nullable List<Header> allHeaders,
boolean notModified,
long networkTimeMs) {
this.statusCode = statusCode;
@@ -150,10 +154,10 @@ public class NetworkResponse {
* map will only contain the last one. Use {@link #allHeaders} to inspect all headers returned
* by the server.
*/
- public final Map<String, String> headers;
+ @Nullable public final Map<String, String> headers;
/** All response headers. Must not be mutated directly. */
- public final List<Header> allHeaders;
+ @Nullable public final List<Header> allHeaders;
/** True if the server returned a 304 (Not Modified). */
public final boolean notModified;
@@ -161,7 +165,8 @@ public class NetworkResponse {
/** Network roundtrip time in milliseconds. */
public final long networkTimeMs;
- private static Map<String, String> toHeaderMap(List<Header> allHeaders) {
+ @Nullable
+ private static Map<String, String> toHeaderMap(@Nullable List<Header> allHeaders) {
if (allHeaders == null) {
return null;
}
@@ -176,7 +181,8 @@ public class NetworkResponse {
return headers;
}
- private static List<Header> toAllHeaderList(Map<String, String> headers) {
+ @Nullable
+ private static List<Header> toAllHeaderList(@Nullable Map<String, String> headers) {
if (headers == null) {
return null;
}
diff --git a/src/main/java/com/android/volley/Request.java b/src/main/java/com/android/volley/Request.java
index 104b046..2b53f96 100644
--- a/src/main/java/com/android/volley/Request.java
+++ b/src/main/java/com/android/volley/Request.java
@@ -115,7 +115,7 @@ public abstract class Request<T> implements Comparable<Request<T>> {
* entry will be stored here so that in the event of a "Not Modified" response, we can be sure
* it hasn't been evicted from cache.
*/
- private Cache.Entry mCacheEntry = null;
+ @Nullable private Cache.Entry mCacheEntry = null;
/** An opaque token tagging this request; used for bulk cancellation. */
private Object mTag;
@@ -319,6 +319,7 @@ public abstract class Request<T> implements Comparable<Request<T>> {
}
/** Returns the annotated cache entry, or null if there isn't one. */
+ @Nullable
public Cache.Entry getCacheEntry() {
return mCacheEntry;
}
@@ -374,6 +375,7 @@ public abstract class Request<T> implements Comparable<Request<T>> {
* @deprecated Use {@link #getParams()} instead.
*/
@Deprecated
+ @Nullable
protected Map<String, String> getPostParams() throws AuthFailureError {
return getParams();
}
@@ -431,6 +433,7 @@ public abstract class Request<T> implements Comparable<Request<T>> {
*
* @throws AuthFailureError in the event of auth failure
*/
+ @Nullable
protected Map<String, String> getParams() throws AuthFailureError {
return null;
}
diff --git a/src/main/java/com/android/volley/Response.java b/src/main/java/com/android/volley/Response.java
index 2f50e2d..622bdc4 100644
--- a/src/main/java/com/android/volley/Response.java
+++ b/src/main/java/com/android/volley/Response.java
@@ -16,6 +16,8 @@
package com.android.volley;
+import androidx.annotation.Nullable;
+
/**
* Encapsulates a parsed response for delivery.
*
@@ -39,7 +41,7 @@ public class Response<T> {
}
/** Returns a successful response containing the parsed result. */
- public static <T> Response<T> success(T result, Cache.Entry cacheEntry) {
+ public static <T> Response<T> success(@Nullable T result, @Nullable Cache.Entry cacheEntry) {
return new Response<>(result, cacheEntry);
}
@@ -51,14 +53,14 @@ public class Response<T> {
return new Response<>(error);
}
- /** Parsed response, or null in the case of error. */
- public final T result;
+ /** Parsed response, can be null; always null in the case of error. */
+ @Nullable public final T result;
- /** Cache metadata for this response, or null in the case of error. */
- public final Cache.Entry cacheEntry;
+ /** Cache metadata for this response; null if not cached or in the case of error. */
+ @Nullable public final Cache.Entry cacheEntry;
/** Detailed error information if <code>errorCode != OK</code>. */
- public final VolleyError error;
+ @Nullable public final VolleyError error;
/** True if this response was a soft-expired one and a second one MAY be coming. */
public boolean intermediate = false;
@@ -68,7 +70,7 @@ public class Response<T> {
return error == null;
}
- private Response(T result, Cache.Entry cacheEntry) {
+ private Response(@Nullable T result, @Nullable Cache.Entry cacheEntry) {
this.result = result;
this.cacheEntry = cacheEntry;
this.error = null;
diff --git a/src/main/java/com/android/volley/toolbox/DiskBasedCache.java b/src/main/java/com/android/volley/toolbox/DiskBasedCache.java
index a6a0c83..d4310e0 100644
--- a/src/main/java/com/android/volley/toolbox/DiskBasedCache.java
+++ b/src/main/java/com/android/volley/toolbox/DiskBasedCache.java
@@ -55,8 +55,8 @@ public class DiskBasedCache implements Cache {
/** Total amount of space currently used by the cache in bytes. */
private long mTotalSize = 0;
- /** The root directory to use for the cache. */
- private final File mRootDirectory;
+ /** The supplier for the root directory to use for the cache. */
+ private final FileSupplier mRootDirectorySupplier;
/** The maximum size of the cache in bytes. */
private final int mMaxCacheSizeInBytes;
@@ -78,8 +78,27 @@ public class DiskBasedCache implements Cache {
* briefly exceed this size on disk when writing a new entry that pushes it over the limit
* until the ensuing pruning completes.
*/
- public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
- mRootDirectory = rootDirectory;
+ public DiskBasedCache(final File rootDirectory, int maxCacheSizeInBytes) {
+ mRootDirectorySupplier =
+ new FileSupplier() {
+ @Override
+ public File get() {
+ return rootDirectory;
+ }
+ };
+ mMaxCacheSizeInBytes = maxCacheSizeInBytes;
+ }
+
+ /**
+ * Constructs an instance of the DiskBasedCache at the specified directory.
+ *
+ * @param rootDirectorySupplier The supplier for the root directory of the cache.
+ * @param maxCacheSizeInBytes The maximum size of the cache in bytes. Note that the cache may
+ * briefly exceed this size on disk when writing a new entry that pushes it over the limit
+ * until the ensuing pruning completes.
+ */
+ public DiskBasedCache(FileSupplier rootDirectorySupplier, int maxCacheSizeInBytes) {
+ mRootDirectorySupplier = rootDirectorySupplier;
mMaxCacheSizeInBytes = maxCacheSizeInBytes;
}
@@ -93,10 +112,20 @@ public class DiskBasedCache implements Cache {
this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);
}
+ /**
+ * Constructs an instance of the DiskBasedCache at the specified directory using the default
+ * maximum cache size of 5MB.
+ *
+ * @param rootDirectorySupplier The supplier for the root directory of the cache.
+ */
+ public DiskBasedCache(FileSupplier rootDirectorySupplier) {
+ this(rootDirectorySupplier, DEFAULT_DISK_USAGE_BYTES);
+ }
+
/** Clears the cache. Deletes all cached files from disk. */
@Override
public synchronized void clear() {
- File[] files = mRootDirectory.listFiles();
+ File[] files = mRootDirectorySupplier.get().listFiles();
if (files != null) {
for (File file : files) {
file.delete();
@@ -150,13 +179,14 @@ public class DiskBasedCache implements Cache {
*/
@Override
public synchronized void initialize() {
- if (!mRootDirectory.exists()) {
- if (!mRootDirectory.mkdirs()) {
- VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());
+ File rootDirectory = mRootDirectorySupplier.get();
+ if (!rootDirectory.exists()) {
+ if (!rootDirectory.mkdirs()) {
+ VolleyLog.e("Unable to create cache dir %s", rootDirectory.getAbsolutePath());
}
return;
}
- File[] files = mRootDirectory.listFiles();
+ File[] files = rootDirectory.listFiles();
if (files == null) {
return;
}
@@ -226,12 +256,12 @@ public class DiskBasedCache implements Cache {
e.size = file.length();
putEntry(key, e);
pruneIfNeeded();
- return;
} catch (IOException e) {
- }
- boolean deleted = file.delete();
- if (!deleted) {
- VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
+ boolean deleted = file.delete();
+ if (!deleted) {
+ VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
+ }
+ initializeIfRootDirectoryDeleted();
}
}
@@ -262,7 +292,22 @@ public class DiskBasedCache implements Cache {
/** Returns a file object for the given cache key. */
public File getFileForKey(String key) {
- return new File(mRootDirectory, getFilenameForKey(key));
+ return new File(mRootDirectorySupplier.get(), getFilenameForKey(key));
+ }
+
+ /** Re-initialize the cache if the directory was deleted. */
+ private void initializeIfRootDirectoryDeleted() {
+ if (!mRootDirectorySupplier.get().exists()) {
+ VolleyLog.d("Re-initializing cache after external clearing.");
+ mEntries.clear();
+ mTotalSize = 0;
+ initialize();
+ }
+ }
+
+ /** Represents a supplier for {@link File}s. */
+ public interface FileSupplier {
+ File get();
}
/** Prunes the cache to fit the maximum size. */
diff --git a/src/main/java/com/android/volley/toolbox/HttpHeaderParser.java b/src/main/java/com/android/volley/toolbox/HttpHeaderParser.java
index 27d1268..1b410af 100644
--- a/src/main/java/com/android/volley/toolbox/HttpHeaderParser.java
+++ b/src/main/java/com/android/volley/toolbox/HttpHeaderParser.java
@@ -16,6 +16,7 @@
package com.android.volley.toolbox;
+import androidx.annotation.Nullable;
import com.android.volley.Cache;
import com.android.volley.Header;
import com.android.volley.NetworkResponse;
@@ -37,7 +38,11 @@ public class HttpHeaderParser {
private static final String DEFAULT_CONTENT_CHARSET = "ISO-8859-1";
- private static final String RFC1123_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";
+ private static final String RFC1123_PARSE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";
+
+ // Hardcode 'GMT' rather than using 'zzz' since some platforms append an extraneous +00:00.
+ // See #287.
+ private static final String RFC1123_OUTPUT_FORMAT = "EEE, dd MMM yyyy HH:mm:ss 'GMT'";
/**
* Extracts a {@link com.android.volley.Cache.Entry} from a {@link NetworkResponse}.
@@ -45,10 +50,14 @@ public class HttpHeaderParser {
* @param response The network response to parse headers from
* @return a cache entry for the given response, or null if the response is not cacheable.
*/
+ @Nullable
public static Cache.Entry parseCacheHeaders(NetworkResponse response) {
long now = System.currentTimeMillis();
Map<String, String> headers = response.headers;
+ if (headers == null) {
+ return null;
+ }
long serverDate = 0;
long lastModified = 0;
@@ -132,21 +141,29 @@ public class HttpHeaderParser {
public static long parseDateAsEpoch(String dateStr) {
try {
// Parse date in RFC1123 format if this header contains one
- return newRfc1123Formatter().parse(dateStr).getTime();
+ return newUsGmtFormatter(RFC1123_PARSE_FORMAT).parse(dateStr).getTime();
} catch (ParseException e) {
// Date in invalid format, fallback to 0
- VolleyLog.e(e, "Unable to parse dateStr: %s, falling back to 0", dateStr);
+ // If the value is either "0" or "-1" we only log to verbose,
+ // these values are pretty common and cause log spam.
+ String message = "Unable to parse dateStr: %s, falling back to 0";
+ if ("0".equals(dateStr) || "-1".equals(dateStr)) {
+ VolleyLog.v(message, dateStr);
+ } else {
+ VolleyLog.e(e, message, dateStr);
+ }
+
return 0;
}
}
/** Format an epoch date in RFC1123 format. */
static String formatEpochAsRfc1123(long epoch) {
- return newRfc1123Formatter().format(new Date(epoch));
+ return newUsGmtFormatter(RFC1123_OUTPUT_FORMAT).format(new Date(epoch));
}
- private static SimpleDateFormat newRfc1123Formatter() {
- SimpleDateFormat formatter = new SimpleDateFormat(RFC1123_FORMAT, Locale.US);
+ private static SimpleDateFormat newUsGmtFormatter(String format) {
+ SimpleDateFormat formatter = new SimpleDateFormat(format, Locale.US);
formatter.setTimeZone(TimeZone.getTimeZone("GMT"));
return formatter;
}
@@ -159,7 +176,11 @@ public class HttpHeaderParser {
* @return Returns the charset specified in the Content-Type of this header, or the
* defaultCharset if none can be found.
*/
- public static String parseCharset(Map<String, String> headers, String defaultCharset) {
+ public static String parseCharset(
+ @Nullable Map<String, String> headers, String defaultCharset) {
+ if (headers == null) {
+ return defaultCharset;
+ }
String contentType = headers.get(HEADER_CONTENT_TYPE);
if (contentType != null) {
String[] params = contentType.split(";", 0);
@@ -180,7 +201,7 @@ public class HttpHeaderParser {
* Returns the charset specified in the Content-Type of this header, or the HTTP default
* (ISO-8859-1) if none can be found.
*/
- public static String parseCharset(Map<String, String> headers) {
+ public static String parseCharset(@Nullable Map<String, String> headers) {
return parseCharset(headers, DEFAULT_CONTENT_CHARSET);
}
diff --git a/src/main/java/com/android/volley/toolbox/HurlStack.java b/src/main/java/com/android/volley/toolbox/HurlStack.java
index f85d42c..9c38023 100644
--- a/src/main/java/com/android/volley/toolbox/HurlStack.java
+++ b/src/main/java/com/android/volley/toolbox/HurlStack.java
@@ -25,6 +25,7 @@ import java.io.DataOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
@@ -111,7 +112,7 @@ public class HurlStack extends BaseHttpStack {
responseCode,
convertHeaders(connection.getHeaderFields()),
connection.getContentLength(),
- new UrlConnectionInputStream(connection));
+ createInputStream(request, connection));
} finally {
if (!keepConnectionOpen) {
connection.disconnect();
@@ -169,6 +170,19 @@ public class HurlStack extends BaseHttpStack {
}
/**
+ * Create and return an InputStream from which the response will be read.
+ *
+ * <p>May be overridden by subclasses to manipulate or monitor this input stream.
+ *
+ * @param request current request.
+ * @param connection current connection of request.
+ * @return an InputStream from which the response will be read.
+ */
+ protected InputStream createInputStream(Request<?> request, HttpURLConnection connection) {
+ return new UrlConnectionInputStream(connection);
+ }
+
+ /**
* Initializes an {@link InputStream} from the given {@link HttpURLConnection}.
*
* @param connection
@@ -223,7 +237,7 @@ public class HurlStack extends BaseHttpStack {
// NOTE: Any request headers added here (via setRequestProperty or addRequestProperty) should be
// checked against the existing properties in the connection and not overridden if already set.
@SuppressWarnings("deprecation")
- /* package */ static void setConnectionParametersForRequest(
+ /* package */ void setConnectionParametersForRequest(
HttpURLConnection connection, Request<?> request) throws IOException, AuthFailureError {
switch (request.getMethod()) {
case Method.DEPRECATED_GET_OR_POST:
@@ -270,7 +284,7 @@ public class HurlStack extends BaseHttpStack {
}
}
- private static void addBodyIfExists(HttpURLConnection connection, Request<?> request)
+ private void addBodyIfExists(HttpURLConnection connection, Request<?> request)
throws IOException, AuthFailureError {
byte[] body = request.getBody();
if (body != null) {
@@ -278,7 +292,7 @@ public class HurlStack extends BaseHttpStack {
}
}
- private static void addBody(HttpURLConnection connection, Request<?> request, byte[] body)
+ private void addBody(HttpURLConnection connection, Request<?> request, byte[] body)
throws IOException {
// Prepare output. There is no need to set Content-Length explicitly,
// since this is handled by HttpURLConnection using the size of the prepared
@@ -289,8 +303,25 @@ public class HurlStack extends BaseHttpStack {
connection.setRequestProperty(
HttpHeaderParser.HEADER_CONTENT_TYPE, request.getBodyContentType());
}
- DataOutputStream out = new DataOutputStream(connection.getOutputStream());
+ DataOutputStream out =
+ new DataOutputStream(createOutputStream(request, connection, body.length));
out.write(body);
out.close();
}
+
+ /**
+ * Create and return an OutputStream to which the request body will be written.
+ *
+ * <p>May be overridden by subclasses to manipulate or monitor this output stream.
+ *
+ * @param request current request.
+ * @param connection current connection of request.
+ * @param length size of stream to write.
+ * @return an OutputStream to which the request body will be written.
+ * @throws IOException if an I/O error occurs while creating the stream.
+ */
+ protected OutputStream createOutputStream(
+ Request<?> request, HttpURLConnection connection, int length) throws IOException {
+ return connection.getOutputStream();
+ }
}
diff --git a/src/main/java/com/android/volley/toolbox/ImageLoader.java b/src/main/java/com/android/volley/toolbox/ImageLoader.java
index b80072b..eece2cf 100644
--- a/src/main/java/com/android/volley/toolbox/ImageLoader.java
+++ b/src/main/java/com/android/volley/toolbox/ImageLoader.java
@@ -20,6 +20,7 @@ import android.os.Looper;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import androidx.annotation.MainThread;
+import androidx.annotation.Nullable;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response.ErrorListener;
@@ -70,6 +71,7 @@ public class ImageLoader {
* LruCache is recommended.
*/
public interface ImageCache {
+ @Nullable
Bitmap getBitmap(String url);
void putBitmap(String url, Bitmap bitmap);
diff --git a/src/main/java/com/android/volley/toolbox/Volley.java b/src/main/java/com/android/volley/toolbox/Volley.java
index 1982802..bc65c9c 100644
--- a/src/main/java/com/android/volley/toolbox/Volley.java
+++ b/src/main/java/com/android/volley/toolbox/Volley.java
@@ -86,8 +86,22 @@ public class Volley {
}
private static RequestQueue newRequestQueue(Context context, Network network) {
- File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
- RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
+ final Context appContext = context.getApplicationContext();
+ // Use a lazy supplier for the cache directory so that newRequestQueue() can be called on
+ // main thread without causing strict mode violation.
+ DiskBasedCache.FileSupplier cacheSupplier =
+ new DiskBasedCache.FileSupplier() {
+ private File cacheDir = null;
+
+ @Override
+ public File get() {
+ if (cacheDir == null) {
+ cacheDir = new File(appContext.getCacheDir(), DEFAULT_CACHE_DIR);
+ }
+ return cacheDir;
+ }
+ };
+ RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheSupplier), network);
queue.start();
return queue;
}
diff --git a/src/test/java/com/android/volley/CacheDispatcherTest.java b/src/test/java/com/android/volley/CacheDispatcherTest.java
index 2592a0b..aef6785 100644
--- a/src/test/java/com/android/volley/CacheDispatcherTest.java
+++ b/src/test/java/com/android/volley/CacheDispatcherTest.java
@@ -140,6 +140,25 @@ public class CacheDispatcherTest {
assertSame(entry, mRequest.getCacheEntry());
}
+ // An fresh cache hit with parse error, does not post a response and queues to the network.
+ @Test
+ public void freshCacheHit_parseError() throws Exception {
+ Request request = mock(Request.class);
+ when(request.parseNetworkResponse(any(NetworkResponse.class)))
+ .thenReturn(Response.error(new ParseError()));
+ when(request.getCacheKey()).thenReturn("cache/key");
+ Cache.Entry entry = CacheTestUtils.makeRandomCacheEntry(null, false, false);
+ when(mCache.get(anyString())).thenReturn(entry);
+
+ mDispatcher.processRequest(request);
+
+ verifyNoResponse(mDelivery);
+ verify(mNetworkQueue).put(request);
+ assertNull(request.getCacheEntry());
+ verify(mCache).invalidate("cache/key", true);
+ verify(request).addMarker("cache-parsing-failed");
+ }
+
@Test
public void duplicateCacheMiss() throws Exception {
StringRequest secondRequest =
diff --git a/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java b/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java
index e499a37..ccf68fa 100644
--- a/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java
+++ b/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java
@@ -587,11 +587,28 @@ public class DiskBasedCacheTest {
public void publicMethods() throws Exception {
// Catch-all test to find API-breaking changes.
assertNotNull(DiskBasedCache.class.getConstructor(File.class, int.class));
+ assertNotNull(
+ DiskBasedCache.class.getConstructor(DiskBasedCache.FileSupplier.class, int.class));
assertNotNull(DiskBasedCache.class.getConstructor(File.class));
+ assertNotNull(DiskBasedCache.class.getConstructor(DiskBasedCache.FileSupplier.class));
assertNotNull(DiskBasedCache.class.getMethod("getFileForKey", String.class));
}
+ @Test
+ public void initializeIfRootDirectoryDeleted() {
+ temporaryFolder.delete();
+
+ Cache.Entry entry = randomData(101);
+ cache.put("key1", entry);
+
+ assertThat(cache.get("key1"), is(nullValue()));
+
+ // confirm that we can now store entries
+ cache.put("key2", entry);
+ assertThatEntriesAreEqual(cache.get("key2"), entry);
+ }
+
/* Test helpers */
private void assertThatEntriesAreEqual(Cache.Entry actual, Cache.Entry expected) {
diff --git a/src/test/java/com/android/volley/toolbox/HttpHeaderParserTest.java b/src/test/java/com/android/volley/toolbox/HttpHeaderParserTest.java
index 9b670f9..7780c3e 100644
--- a/src/test/java/com/android/volley/toolbox/HttpHeaderParserTest.java
+++ b/src/test/java/com/android/volley/toolbox/HttpHeaderParserTest.java
@@ -67,6 +67,12 @@ public class HttpHeaderParserTest {
}
@Test
+ public void parseCacheHeaders_nullHeaders() {
+ response = new NetworkResponse(0, null, null, false);
+ assertNull(HttpHeaderParser.parseCacheHeaders(response));
+ }
+
+ @Test
public void parseCacheHeaders_headersSet() {
headers.put("MyCustomHeader", "42");
@@ -282,6 +288,9 @@ public class HttpHeaderParserTest {
// None specified, extra semicolon
headers.put("Content-Type", "text/plain;");
assertEquals("ISO-8859-1", HttpHeaderParser.parseCharset(headers));
+
+ // No headers, use default charset
+ assertEquals("utf-8", HttpHeaderParser.parseCharset(null, "utf-8"));
}
@Test
diff --git a/src/test/java/com/android/volley/toolbox/HurlStackTest.java b/src/test/java/com/android/volley/toolbox/HurlStackTest.java
index c1fc92d..7508244 100644
--- a/src/test/java/com/android/volley/toolbox/HurlStackTest.java
+++ b/src/test/java/com/android/volley/toolbox/HurlStackTest.java
@@ -17,6 +17,7 @@
package com.android.volley.toolbox;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.never;
@@ -24,11 +25,16 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.android.volley.Header;
+import com.android.volley.Request;
import com.android.volley.Request.Method;
import com.android.volley.mock.TestRequest;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.FilterInputStream;
+import java.io.FilterOutputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;
@@ -62,6 +68,26 @@ public class HurlStackTest {
protected HttpURLConnection createConnection(URL url) {
return mMockConnection;
}
+
+ @Override
+ protected InputStream createInputStream(
+ Request<?> request, HttpURLConnection connection) {
+ return new MonitoringInputStream(
+ super.createInputStream(request, connection));
+ }
+
+ @Override
+ protected OutputStream createOutputStream(
+ Request<?> request, HttpURLConnection connection, int length)
+ throws IOException {
+ if (request instanceof MonitoredRequest) {
+ return new MonitoringOutputStream(
+ super.createOutputStream(request, connection, length),
+ (MonitoredRequest) request,
+ length);
+ }
+ return super.createOutputStream(request, connection, length);
+ }
};
}
@@ -70,7 +96,7 @@ public class HurlStackTest {
TestRequest.DeprecatedGet request = new TestRequest.DeprecatedGet();
assertEquals(request.getMethod(), Method.DEPRECATED_GET_OR_POST);
- HurlStack.setConnectionParametersForRequest(mMockConnection, request);
+ mHurlStack.setConnectionParametersForRequest(mMockConnection, request);
verify(mMockConnection, never()).setRequestMethod(anyString());
verify(mMockConnection, never()).setDoOutput(true);
}
@@ -80,7 +106,7 @@ public class HurlStackTest {
TestRequest.DeprecatedPost request = new TestRequest.DeprecatedPost();
assertEquals(request.getMethod(), Method.DEPRECATED_GET_OR_POST);
- HurlStack.setConnectionParametersForRequest(mMockConnection, request);
+ mHurlStack.setConnectionParametersForRequest(mMockConnection, request);
verify(mMockConnection).setRequestMethod("POST");
verify(mMockConnection).setDoOutput(true);
}
@@ -90,7 +116,7 @@ public class HurlStackTest {
TestRequest.Get request = new TestRequest.Get();
assertEquals(request.getMethod(), Method.GET);
- HurlStack.setConnectionParametersForRequest(mMockConnection, request);
+ mHurlStack.setConnectionParametersForRequest(mMockConnection, request);
verify(mMockConnection).setRequestMethod("GET");
verify(mMockConnection, never()).setDoOutput(true);
}
@@ -100,7 +126,7 @@ public class HurlStackTest {
TestRequest.Post request = new TestRequest.Post();
assertEquals(request.getMethod(), Method.POST);
- HurlStack.setConnectionParametersForRequest(mMockConnection, request);
+ mHurlStack.setConnectionParametersForRequest(mMockConnection, request);
verify(mMockConnection).setRequestMethod("POST");
verify(mMockConnection, never()).setDoOutput(true);
}
@@ -110,7 +136,7 @@ public class HurlStackTest {
TestRequest.PostWithBody request = new TestRequest.PostWithBody();
assertEquals(request.getMethod(), Method.POST);
- HurlStack.setConnectionParametersForRequest(mMockConnection, request);
+ mHurlStack.setConnectionParametersForRequest(mMockConnection, request);
verify(mMockConnection).setRequestMethod("POST");
verify(mMockConnection).setDoOutput(true);
}
@@ -120,7 +146,7 @@ public class HurlStackTest {
TestRequest.Put request = new TestRequest.Put();
assertEquals(request.getMethod(), Method.PUT);
- HurlStack.setConnectionParametersForRequest(mMockConnection, request);
+ mHurlStack.setConnectionParametersForRequest(mMockConnection, request);
verify(mMockConnection).setRequestMethod("PUT");
verify(mMockConnection, never()).setDoOutput(true);
}
@@ -130,7 +156,7 @@ public class HurlStackTest {
TestRequest.PutWithBody request = new TestRequest.PutWithBody();
assertEquals(request.getMethod(), Method.PUT);
- HurlStack.setConnectionParametersForRequest(mMockConnection, request);
+ mHurlStack.setConnectionParametersForRequest(mMockConnection, request);
verify(mMockConnection).setRequestMethod("PUT");
verify(mMockConnection).setDoOutput(true);
}
@@ -140,7 +166,7 @@ public class HurlStackTest {
TestRequest.Delete request = new TestRequest.Delete();
assertEquals(request.getMethod(), Method.DELETE);
- HurlStack.setConnectionParametersForRequest(mMockConnection, request);
+ mHurlStack.setConnectionParametersForRequest(mMockConnection, request);
verify(mMockConnection).setRequestMethod("DELETE");
verify(mMockConnection, never()).setDoOutput(true);
}
@@ -150,7 +176,7 @@ public class HurlStackTest {
TestRequest.Head request = new TestRequest.Head();
assertEquals(request.getMethod(), Method.HEAD);
- HurlStack.setConnectionParametersForRequest(mMockConnection, request);
+ mHurlStack.setConnectionParametersForRequest(mMockConnection, request);
verify(mMockConnection).setRequestMethod("HEAD");
verify(mMockConnection, never()).setDoOutput(true);
}
@@ -160,7 +186,7 @@ public class HurlStackTest {
TestRequest.Options request = new TestRequest.Options();
assertEquals(request.getMethod(), Method.OPTIONS);
- HurlStack.setConnectionParametersForRequest(mMockConnection, request);
+ mHurlStack.setConnectionParametersForRequest(mMockConnection, request);
verify(mMockConnection).setRequestMethod("OPTIONS");
verify(mMockConnection, never()).setDoOutput(true);
}
@@ -170,7 +196,7 @@ public class HurlStackTest {
TestRequest.Trace request = new TestRequest.Trace();
assertEquals(request.getMethod(), Method.TRACE);
- HurlStack.setConnectionParametersForRequest(mMockConnection, request);
+ mHurlStack.setConnectionParametersForRequest(mMockConnection, request);
verify(mMockConnection).setRequestMethod("TRACE");
verify(mMockConnection, never()).setDoOutput(true);
}
@@ -180,7 +206,7 @@ public class HurlStackTest {
TestRequest.Patch request = new TestRequest.Patch();
assertEquals(request.getMethod(), Method.PATCH);
- HurlStack.setConnectionParametersForRequest(mMockConnection, request);
+ mHurlStack.setConnectionParametersForRequest(mMockConnection, request);
verify(mMockConnection).setRequestMethod("PATCH");
verify(mMockConnection, never()).setDoOutput(true);
}
@@ -190,7 +216,7 @@ public class HurlStackTest {
TestRequest.PatchWithBody request = new TestRequest.PatchWithBody();
assertEquals(request.getMethod(), Method.PATCH);
- HurlStack.setConnectionParametersForRequest(mMockConnection, request);
+ mHurlStack.setConnectionParametersForRequest(mMockConnection, request);
verify(mMockConnection).setRequestMethod("PATCH");
verify(mMockConnection).setDoOutput(true);
}
@@ -256,4 +282,56 @@ public class HurlStackTest {
expected.add(new Header("HeaderB", "ValueB_2"));
assertEquals(expected, result);
}
+
+ @Test
+ public void interceptResponseStream() throws Exception {
+ when(mMockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK);
+ when(mMockConnection.getInputStream())
+ .thenReturn(new ByteArrayInputStream("hello".getBytes(StandardCharsets.UTF_8)));
+ HttpResponse response =
+ mHurlStack.executeRequest(
+ new TestRequest.Get(), Collections.<String, String>emptyMap());
+ assertTrue(response.getContent() instanceof MonitoringInputStream);
+ }
+
+ @Test
+ public void interceptRequestStream() throws Exception {
+ MonitoredRequest request = new MonitoredRequest();
+ mHurlStack.executeRequest(request, Collections.<String, String>emptyMap());
+ assertTrue(request.totalRequestBytes > 0);
+ assertEquals(request.totalRequestBytes, request.requestBytesRead);
+ }
+
+ private static class MonitoringInputStream extends FilterInputStream {
+ private MonitoringInputStream(InputStream in) {
+ super(in);
+ }
+ }
+
+ private static class MonitoringOutputStream extends FilterOutputStream {
+ private MonitoredRequest request;
+
+ private MonitoringOutputStream(OutputStream out, MonitoredRequest request, int length) {
+ super(out);
+ this.request = request;
+ this.request.totalRequestBytes = length;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ this.request.requestBytesRead++;
+ out.write(b);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ this.request.requestBytesRead += len;
+ out.write(b, off, len);
+ }
+ }
+
+ private static class MonitoredRequest extends TestRequest.PostWithBody {
+ int requestBytesRead = 0;
+ int totalRequestBytes = 0;
+ }
}