aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/android/volley/toolbox
diff options
context:
space:
mode:
authorAnonymous <no-reply@google.com>2020-04-30 10:56:07 -0700
committerJeff Davidson <jpd@google.com>2020-04-30 11:32:32 -0700
commitec9c6e0d0e8e5e947017e24c270ca9f13487f937 (patch)
tree27e047f7667354b69c19637d9cb728f70cbf1892 /src/main/java/com/android/volley/toolbox
parent582bbb822930556f855cad998a393edf7b64e1d4 (diff)
downloadvolley-ec9c6e0d0e8e5e947017e24c270ca9f13487f937.tar.gz
Import of Volley from GitHub to AOSP.
- c9b2623cb524d2ec29a5a7f012528115f45b24cd Tolerate null header maps in HttpHeaderParser. (#334) by Jeff Davidson <jpd@google.com> - 455a16125ae9d01176d68a2ea7d4747130e55801 Allow creation of custom input/output streams in HurlStac... by Nicolas <ndagnas@live.fr> - addc11dbd37bd3a5d8136557076af0e8d0a995a4 Annotate more nullable methods and fields (#333) by Julien Biral <jbir789@users.noreply.github.com> - d41f34a43520cd1d30fe71ada3271161adb0e9c0 Add @Nullable annotations to Response (#325) by justin-morey <justin.morey@gmail.com> - 8a3a7baa07dda3fb4c5d561664470311c13d215b Send request to network when cache entry parsing fails (#... by Ulrike Hager <uhager42@gmail.com> - 514eac8c33492f69f62799dcd3669c877474626c Re-initialize the cache if the directory was deleted. (#2... by Ulrike Hager <uhager42@gmail.com> - b95b0159a14f053590cb18fd1cf3a1b159e478a1 Fix timezone formatting for RFC1123 on some Android devic... by Jeff Davidson <jpd@google.com> - 96c88101bcb6139fc9dd2ded4ef7c0008085d4b1 httpheaderparser: log invalid date headers only verbosely... by Florens <Floens@gmail.com> - 8e7a6dbf4dfbea56da48814ecf6e7270dcec9b0f Allow Volley.newRequestQueue() to be called on main threa... by Yuhan Zhao <poligun@gmail.com> GitOrigin-RevId: c9b2623cb524d2ec29a5a7f012528115f45b24cd Change-Id: I8e677f68212e6ba0812f398744895a1a12b6ed70
Diffstat (limited to 'src/main/java/com/android/volley/toolbox')
-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
5 files changed, 143 insertions, 30 deletions
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;
}