• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.volley;
18 
19 import android.net.TrafficStats;
20 import android.net.Uri;
21 import android.os.Handler;
22 import android.os.Looper;
23 import android.text.TextUtils;
24 import androidx.annotation.CallSuper;
25 import androidx.annotation.GuardedBy;
26 import androidx.annotation.Nullable;
27 import com.android.volley.VolleyLog.MarkerLog;
28 import java.io.UnsupportedEncodingException;
29 import java.net.URLEncoder;
30 import java.util.Collections;
31 import java.util.Map;
32 
33 /**
34  * Base class for all network requests.
35  *
36  * @param <T> The type of parsed response this request expects.
37  */
38 public abstract class Request<T> implements Comparable<Request<T>> {
39 
40     /** Default encoding for POST or PUT parameters. See {@link #getParamsEncoding()}. */
41     private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";
42 
43     /** Supported request methods. */
44     public interface Method {
45         int DEPRECATED_GET_OR_POST = -1;
46         int GET = 0;
47         int POST = 1;
48         int PUT = 2;
49         int DELETE = 3;
50         int HEAD = 4;
51         int OPTIONS = 5;
52         int TRACE = 6;
53         int PATCH = 7;
54     }
55 
56     /** Callback to notify when the network request returns. */
57     /* package */ interface NetworkRequestCompleteListener {
58 
59         /** Callback when a network response has been received. */
onResponseReceived(Request<?> request, Response<?> response)60         void onResponseReceived(Request<?> request, Response<?> response);
61 
62         /** Callback when request returns from network without valid response. */
onNoUsableResponseReceived(Request<?> request)63         void onNoUsableResponseReceived(Request<?> request);
64     }
65 
66     /** An event log tracing the lifetime of this request; for debugging. */
67     private final MarkerLog mEventLog = MarkerLog.ENABLED ? new MarkerLog() : null;
68 
69     /**
70      * Request method of this request. Currently supports GET, POST, PUT, DELETE, HEAD, OPTIONS,
71      * TRACE, and PATCH.
72      */
73     private final int mMethod;
74 
75     /** URL of this request. */
76     private final String mUrl;
77 
78     /** Default tag for {@link TrafficStats}. */
79     private final int mDefaultTrafficStatsTag;
80 
81     /** Lock to guard state which can be mutated after a request is added to the queue. */
82     private final Object mLock = new Object();
83 
84     /** Listener interface for errors. */
85     @Nullable
86     @GuardedBy("mLock")
87     private Response.ErrorListener mErrorListener;
88 
89     /** Sequence number of this request, used to enforce FIFO ordering. */
90     private Integer mSequence;
91 
92     /** The request queue this request is associated with. */
93     private RequestQueue mRequestQueue;
94 
95     /** Whether or not responses to this request should be cached. */
96     // TODO(#190): Turn this off by default for anything other than GET requests.
97     private boolean mShouldCache = true;
98 
99     /** Whether or not this request has been canceled. */
100     @GuardedBy("mLock")
101     private boolean mCanceled = false;
102 
103     /** Whether or not a response has been delivered for this request yet. */
104     @GuardedBy("mLock")
105     private boolean mResponseDelivered = false;
106 
107     /** Whether the request should be retried in the event of an HTTP 5xx (server) error. */
108     private boolean mShouldRetryServerErrors = false;
109 
110     /** Whether the request should be retried in the event of a {@link NoConnectionError}. */
111     private boolean mShouldRetryConnectionErrors = false;
112 
113     /** The retry policy for this request. */
114     private RetryPolicy mRetryPolicy;
115 
116     /**
117      * When a request can be retrieved from cache but must be refreshed from the network, the cache
118      * entry will be stored here so that in the event of a "Not Modified" response, we can be sure
119      * it hasn't been evicted from cache.
120      */
121     @Nullable private Cache.Entry mCacheEntry = null;
122 
123     /** An opaque token tagging this request; used for bulk cancellation. */
124     private Object mTag;
125 
126     /** Listener that will be notified when a response has been delivered. */
127     @GuardedBy("mLock")
128     private NetworkRequestCompleteListener mRequestCompleteListener;
129 
130     /**
131      * Creates a new request with the given URL and error listener. Note that the normal response
132      * listener is not provided here as delivery of responses is provided by subclasses, who have a
133      * better idea of how to deliver an already-parsed response.
134      *
135      * @deprecated Use {@link #Request(int, String, com.android.volley.Response.ErrorListener)}.
136      */
137     @Deprecated
Request(String url, Response.ErrorListener errorListener)138     public Request(String url, Response.ErrorListener errorListener) {
139         this(Method.DEPRECATED_GET_OR_POST, url, errorListener);
140     }
141 
142     /**
143      * Creates a new request with the given method (one of the values from {@link Method}), URL, and
144      * error listener. Note that the normal response listener is not provided here as delivery of
145      * responses is provided by subclasses, who have a better idea of how to deliver an
146      * already-parsed response.
147      *
148      * @param method the HTTP method to use
149      * @param url URL to fetch the response from
150      * @param errorListener Error listener, or null to ignore errors.
151      */
Request(int method, String url, @Nullable Response.ErrorListener errorListener)152     public Request(int method, String url, @Nullable Response.ErrorListener errorListener) {
153         mMethod = method;
154         mUrl = url;
155         mErrorListener = errorListener;
156         setRetryPolicy(new DefaultRetryPolicy());
157 
158         mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
159     }
160 
161     /** Return the method for this request. Can be one of the values in {@link Method}. */
getMethod()162     public int getMethod() {
163         return mMethod;
164     }
165 
166     /**
167      * Set a tag on this request. Can be used to cancel all requests with this tag by {@link
168      * RequestQueue#cancelAll(Object)}.
169      *
170      * @return This Request object to allow for chaining.
171      */
setTag(Object tag)172     public Request<?> setTag(Object tag) {
173         mTag = tag;
174         return this;
175     }
176 
177     /**
178      * Returns this request's tag.
179      *
180      * @see Request#setTag(Object)
181      */
getTag()182     public Object getTag() {
183         return mTag;
184     }
185 
186     /** @return this request's {@link com.android.volley.Response.ErrorListener}. */
187     @Nullable
getErrorListener()188     public Response.ErrorListener getErrorListener() {
189         synchronized (mLock) {
190             return mErrorListener;
191         }
192     }
193 
194     /** @return A tag for use with {@link TrafficStats#setThreadStatsTag(int)} */
getTrafficStatsTag()195     public int getTrafficStatsTag() {
196         return mDefaultTrafficStatsTag;
197     }
198 
199     /** @return The hashcode of the URL's host component, or 0 if there is none. */
findDefaultTrafficStatsTag(String url)200     private static int findDefaultTrafficStatsTag(String url) {
201         if (!TextUtils.isEmpty(url)) {
202             Uri uri = Uri.parse(url);
203             if (uri != null) {
204                 String host = uri.getHost();
205                 if (host != null) {
206                     return host.hashCode();
207                 }
208             }
209         }
210         return 0;
211     }
212 
213     /**
214      * Sets the retry policy for this request.
215      *
216      * @return This Request object to allow for chaining.
217      */
setRetryPolicy(RetryPolicy retryPolicy)218     public Request<?> setRetryPolicy(RetryPolicy retryPolicy) {
219         mRetryPolicy = retryPolicy;
220         return this;
221     }
222 
223     /** Adds an event to this request's event log; for debugging. */
addMarker(String tag)224     public void addMarker(String tag) {
225         if (MarkerLog.ENABLED) {
226             mEventLog.add(tag, Thread.currentThread().getId());
227         }
228     }
229 
230     /**
231      * Notifies the request queue that this request has finished (successfully or with error).
232      *
233      * <p>Also dumps all events from this request's event log; for debugging.
234      */
finish(final String tag)235     void finish(final String tag) {
236         if (mRequestQueue != null) {
237             mRequestQueue.finish(this);
238         }
239         if (MarkerLog.ENABLED) {
240             final long threadId = Thread.currentThread().getId();
241             if (Looper.myLooper() != Looper.getMainLooper()) {
242                 // If we finish marking off of the main thread, we need to
243                 // actually do it on the main thread to ensure correct ordering.
244                 Handler mainThread = new Handler(Looper.getMainLooper());
245                 mainThread.post(
246                         new Runnable() {
247                             @Override
248                             public void run() {
249                                 mEventLog.add(tag, threadId);
250                                 mEventLog.finish(Request.this.toString());
251                             }
252                         });
253                 return;
254             }
255 
256             mEventLog.add(tag, threadId);
257             mEventLog.finish(this.toString());
258         }
259     }
260 
sendEvent(@equestQueue.RequestEvent int event)261     void sendEvent(@RequestQueue.RequestEvent int event) {
262         if (mRequestQueue != null) {
263             mRequestQueue.sendRequestEvent(this, event);
264         }
265     }
266 
267     /**
268      * Associates this request with the given queue. The request queue will be notified when this
269      * request has finished.
270      *
271      * @return This Request object to allow for chaining.
272      */
setRequestQueue(RequestQueue requestQueue)273     public Request<?> setRequestQueue(RequestQueue requestQueue) {
274         mRequestQueue = requestQueue;
275         return this;
276     }
277 
278     /**
279      * Sets the sequence number of this request. Used by {@link RequestQueue}.
280      *
281      * @return This Request object to allow for chaining.
282      */
setSequence(int sequence)283     public final Request<?> setSequence(int sequence) {
284         mSequence = sequence;
285         return this;
286     }
287 
288     /** Returns the sequence number of this request. */
getSequence()289     public final int getSequence() {
290         if (mSequence == null) {
291             throw new IllegalStateException("getSequence called before setSequence");
292         }
293         return mSequence;
294     }
295 
296     /** Returns the URL of this request. */
getUrl()297     public String getUrl() {
298         return mUrl;
299     }
300 
301     /** Returns the cache key for this request. By default, this is the URL. */
getCacheKey()302     public String getCacheKey() {
303         String url = getUrl();
304         // If this is a GET request, just use the URL as the key.
305         // For callers using DEPRECATED_GET_OR_POST, we assume the method is GET, which matches
306         // legacy behavior where all methods had the same cache key. We can't determine which method
307         // will be used because doing so requires calling getPostBody() which is expensive and may
308         // throw AuthFailureError.
309         // TODO(#190): Remove support for non-GET methods.
310         int method = getMethod();
311         if (method == Method.GET || method == Method.DEPRECATED_GET_OR_POST) {
312             return url;
313         }
314         return Integer.toString(method) + '-' + url;
315     }
316 
317     /**
318      * Annotates this request with an entry retrieved for it from cache. Used for cache coherency
319      * support.
320      *
321      * @return This Request object to allow for chaining.
322      */
setCacheEntry(Cache.Entry entry)323     public Request<?> setCacheEntry(Cache.Entry entry) {
324         mCacheEntry = entry;
325         return this;
326     }
327 
328     /** Returns the annotated cache entry, or null if there isn't one. */
329     @Nullable
getCacheEntry()330     public Cache.Entry getCacheEntry() {
331         return mCacheEntry;
332     }
333 
334     /**
335      * Mark this request as canceled.
336      *
337      * <p>No callback will be delivered as long as either:
338      *
339      * <ul>
340      *   <li>This method is called on the same thread as the {@link ResponseDelivery} is running on.
341      *       By default, this is the main thread.
342      *   <li>The request subclass being used overrides cancel() and ensures that it does not invoke
343      *       the listener in {@link #deliverResponse} after cancel() has been called in a
344      *       thread-safe manner.
345      * </ul>
346      *
347      * <p>There are no guarantees if both of these conditions aren't met.
348      */
349     @CallSuper
cancel()350     public void cancel() {
351         synchronized (mLock) {
352             mCanceled = true;
353             mErrorListener = null;
354         }
355     }
356 
357     /** Returns true if this request has been canceled. */
isCanceled()358     public boolean isCanceled() {
359         synchronized (mLock) {
360             return mCanceled;
361         }
362     }
363 
364     /**
365      * Returns a list of extra HTTP headers to go along with this request. Can throw {@link
366      * AuthFailureError} as authentication may be required to provide these values.
367      *
368      * @throws AuthFailureError In the event of auth failure
369      */
getHeaders()370     public Map<String, String> getHeaders() throws AuthFailureError {
371         return Collections.emptyMap();
372     }
373 
374     /**
375      * Returns a Map of POST parameters to be used for this request, or null if a simple GET should
376      * be used. Can throw {@link AuthFailureError} as authentication may be required to provide
377      * these values.
378      *
379      * <p>Note that only one of getPostParams() and getPostBody() can return a non-null value.
380      *
381      * @throws AuthFailureError In the event of auth failure
382      * @deprecated Use {@link #getParams()} instead.
383      */
384     @Deprecated
385     @Nullable
getPostParams()386     protected Map<String, String> getPostParams() throws AuthFailureError {
387         return getParams();
388     }
389 
390     /**
391      * Returns which encoding should be used when converting POST parameters returned by {@link
392      * #getPostParams()} into a raw POST body.
393      *
394      * <p>This controls both encodings:
395      *
396      * <ol>
397      *   <li>The string encoding used when converting parameter names and values into bytes prior to
398      *       URL encoding them.
399      *   <li>The string encoding used when converting the URL encoded parameters into a raw byte
400      *       array.
401      * </ol>
402      *
403      * @deprecated Use {@link #getParamsEncoding()} instead.
404      */
405     @Deprecated
getPostParamsEncoding()406     protected String getPostParamsEncoding() {
407         return getParamsEncoding();
408     }
409 
410     /** @deprecated Use {@link #getBodyContentType()} instead. */
411     @Deprecated
getPostBodyContentType()412     public String getPostBodyContentType() {
413         return getBodyContentType();
414     }
415 
416     /**
417      * Returns the raw POST body to be sent.
418      *
419      * @throws AuthFailureError In the event of auth failure
420      * @deprecated Use {@link #getBody()} instead.
421      */
422     @Deprecated
getPostBody()423     public byte[] getPostBody() throws AuthFailureError {
424         // Note: For compatibility with legacy clients of volley, this implementation must remain
425         // here instead of simply calling the getBody() function because this function must
426         // call getPostParams() and getPostParamsEncoding() since legacy clients would have
427         // overridden these two member functions for POST requests.
428         Map<String, String> postParams = getPostParams();
429         if (postParams != null && postParams.size() > 0) {
430             return encodeParameters(postParams, getPostParamsEncoding());
431         }
432         return null;
433     }
434 
435     /**
436      * Returns a Map of parameters to be used for a POST or PUT request. Can throw {@link
437      * AuthFailureError} as authentication may be required to provide these values.
438      *
439      * <p>Note that you can directly override {@link #getBody()} for custom data.
440      *
441      * @throws AuthFailureError in the event of auth failure
442      */
443     @Nullable
getParams()444     protected Map<String, String> getParams() throws AuthFailureError {
445         return null;
446     }
447 
448     /**
449      * Returns which encoding should be used when converting POST or PUT parameters returned by
450      * {@link #getParams()} into a raw POST or PUT body.
451      *
452      * <p>This controls both encodings:
453      *
454      * <ol>
455      *   <li>The string encoding used when converting parameter names and values into bytes prior to
456      *       URL encoding them.
457      *   <li>The string encoding used when converting the URL encoded parameters into a raw byte
458      *       array.
459      * </ol>
460      */
getParamsEncoding()461     protected String getParamsEncoding() {
462         return DEFAULT_PARAMS_ENCODING;
463     }
464 
465     /** Returns the content type of the POST or PUT body. */
getBodyContentType()466     public String getBodyContentType() {
467         return "application/x-www-form-urlencoded; charset=" + getParamsEncoding();
468     }
469 
470     /**
471      * Returns the raw POST or PUT body to be sent.
472      *
473      * <p>By default, the body consists of the request parameters in
474      * application/x-www-form-urlencoded format. When overriding this method, consider overriding
475      * {@link #getBodyContentType()} as well to match the new body format.
476      *
477      * @throws AuthFailureError in the event of auth failure
478      */
getBody()479     public byte[] getBody() throws AuthFailureError {
480         Map<String, String> params = getParams();
481         if (params != null && params.size() > 0) {
482             return encodeParameters(params, getParamsEncoding());
483         }
484         return null;
485     }
486 
487     /** Converts <code>params</code> into an application/x-www-form-urlencoded encoded string. */
encodeParameters(Map<String, String> params, String paramsEncoding)488     private byte[] encodeParameters(Map<String, String> params, String paramsEncoding) {
489         StringBuilder encodedParams = new StringBuilder();
490         try {
491             for (Map.Entry<String, String> entry : params.entrySet()) {
492                 if (entry.getKey() == null || entry.getValue() == null) {
493                     throw new IllegalArgumentException(
494                             String.format(
495                                     "Request#getParams() or Request#getPostParams() returned a map "
496                                             + "containing a null key or value: (%s, %s). All keys "
497                                             + "and values must be non-null.",
498                                     entry.getKey(), entry.getValue()));
499                 }
500                 encodedParams.append(URLEncoder.encode(entry.getKey(), paramsEncoding));
501                 encodedParams.append('=');
502                 encodedParams.append(URLEncoder.encode(entry.getValue(), paramsEncoding));
503                 encodedParams.append('&');
504             }
505             return encodedParams.toString().getBytes(paramsEncoding);
506         } catch (UnsupportedEncodingException uee) {
507             throw new RuntimeException("Encoding not supported: " + paramsEncoding, uee);
508         }
509     }
510 
511     /**
512      * Set whether or not responses to this request should be cached.
513      *
514      * @return This Request object to allow for chaining.
515      */
setShouldCache(boolean shouldCache)516     public final Request<?> setShouldCache(boolean shouldCache) {
517         mShouldCache = shouldCache;
518         return this;
519     }
520 
521     /** Returns true if responses to this request should be cached. */
shouldCache()522     public final boolean shouldCache() {
523         return mShouldCache;
524     }
525 
526     /**
527      * Sets whether or not the request should be retried in the event of an HTTP 5xx (server) error.
528      *
529      * @return This Request object to allow for chaining.
530      */
setShouldRetryServerErrors(boolean shouldRetryServerErrors)531     public final Request<?> setShouldRetryServerErrors(boolean shouldRetryServerErrors) {
532         mShouldRetryServerErrors = shouldRetryServerErrors;
533         return this;
534     }
535 
536     /**
537      * Returns true if this request should be retried in the event of an HTTP 5xx (server) error.
538      */
shouldRetryServerErrors()539     public final boolean shouldRetryServerErrors() {
540         return mShouldRetryServerErrors;
541     }
542 
543     /**
544      * Sets whether or not the request should be retried in the event that no connection could be
545      * established.
546      *
547      * @return This Request object to allow for chaining.
548      */
setShouldRetryConnectionErrors(boolean shouldRetryConnectionErrors)549     public final Request<?> setShouldRetryConnectionErrors(boolean shouldRetryConnectionErrors) {
550         mShouldRetryConnectionErrors = shouldRetryConnectionErrors;
551         return this;
552     }
553 
554     /**
555      * Returns true if this request should be retried in the event that no connection could be
556      * established.
557      */
shouldRetryConnectionErrors()558     public final boolean shouldRetryConnectionErrors() {
559         return mShouldRetryConnectionErrors;
560     }
561 
562     /**
563      * Priority values. Requests will be processed from higher priorities to lower priorities, in
564      * FIFO order.
565      */
566     public enum Priority {
567         LOW,
568         NORMAL,
569         HIGH,
570         IMMEDIATE
571     }
572 
573     /** Returns the {@link Priority} of this request; {@link Priority#NORMAL} by default. */
getPriority()574     public Priority getPriority() {
575         return Priority.NORMAL;
576     }
577 
578     /**
579      * Returns the socket timeout in milliseconds per retry attempt. (This value can be changed per
580      * retry attempt if a backoff is specified via backoffTimeout()). If there are no retry attempts
581      * remaining, this will cause delivery of a {@link TimeoutError} error.
582      */
getTimeoutMs()583     public final int getTimeoutMs() {
584         return getRetryPolicy().getCurrentTimeout();
585     }
586 
587     /** Returns the retry policy that should be used for this request. */
getRetryPolicy()588     public RetryPolicy getRetryPolicy() {
589         return mRetryPolicy;
590     }
591 
592     /**
593      * Mark this request as having a response delivered on it. This can be used later in the
594      * request's lifetime for suppressing identical responses.
595      */
markDelivered()596     public void markDelivered() {
597         synchronized (mLock) {
598             mResponseDelivered = true;
599         }
600     }
601 
602     /** Returns true if this request has had a response delivered for it. */
hasHadResponseDelivered()603     public boolean hasHadResponseDelivered() {
604         synchronized (mLock) {
605             return mResponseDelivered;
606         }
607     }
608 
609     /**
610      * Subclasses must implement this to parse the raw network response and return an appropriate
611      * response type. This method will be called from a worker thread. The response will not be
612      * delivered if you return null.
613      *
614      * @param response Response from the network
615      * @return The parsed response, or null in the case of an error
616      */
parseNetworkResponse(NetworkResponse response)617     protected abstract Response<T> parseNetworkResponse(NetworkResponse response);
618 
619     /**
620      * Subclasses can override this method to parse 'networkError' and return a more specific error.
621      *
622      * <p>The default implementation just returns the passed 'networkError'.
623      *
624      * @param volleyError the error retrieved from the network
625      * @return an NetworkError augmented with additional information
626      */
parseNetworkError(VolleyError volleyError)627     protected VolleyError parseNetworkError(VolleyError volleyError) {
628         return volleyError;
629     }
630 
631     /**
632      * Subclasses must implement this to perform delivery of the parsed response to their listeners.
633      * The given response is guaranteed to be non-null; responses that fail to parse are not
634      * delivered.
635      *
636      * @param response The parsed response returned by {@link
637      *     #parseNetworkResponse(NetworkResponse)}
638      */
deliverResponse(T response)639     protected abstract void deliverResponse(T response);
640 
641     /**
642      * Delivers error message to the ErrorListener that the Request was initialized with.
643      *
644      * @param error Error details
645      */
deliverError(VolleyError error)646     public void deliverError(VolleyError error) {
647         Response.ErrorListener listener;
648         synchronized (mLock) {
649             listener = mErrorListener;
650         }
651         if (listener != null) {
652             listener.onErrorResponse(error);
653         }
654     }
655 
656     /**
657      * {@link NetworkRequestCompleteListener} that will receive callbacks when the request returns
658      * from the network.
659      */
setNetworkRequestCompleteListener( NetworkRequestCompleteListener requestCompleteListener)660     /* package */ void setNetworkRequestCompleteListener(
661             NetworkRequestCompleteListener requestCompleteListener) {
662         synchronized (mLock) {
663             mRequestCompleteListener = requestCompleteListener;
664         }
665     }
666 
667     /**
668      * Notify NetworkRequestCompleteListener that a valid response has been received which can be
669      * used for other, waiting requests.
670      *
671      * @param response received from the network
672      */
notifyListenerResponseReceived(Response<?> response)673     /* package */ void notifyListenerResponseReceived(Response<?> response) {
674         NetworkRequestCompleteListener listener;
675         synchronized (mLock) {
676             listener = mRequestCompleteListener;
677         }
678         if (listener != null) {
679             listener.onResponseReceived(this, response);
680         }
681     }
682 
683     /**
684      * Notify NetworkRequestCompleteListener that the network request did not result in a response
685      * which can be used for other, waiting requests.
686      */
notifyListenerResponseNotUsable()687     /* package */ void notifyListenerResponseNotUsable() {
688         NetworkRequestCompleteListener listener;
689         synchronized (mLock) {
690             listener = mRequestCompleteListener;
691         }
692         if (listener != null) {
693             listener.onNoUsableResponseReceived(this);
694         }
695     }
696 
697     /**
698      * Our comparator sorts from high to low priority, and secondarily by sequence number to provide
699      * FIFO ordering.
700      */
701     @Override
compareTo(Request<T> other)702     public int compareTo(Request<T> other) {
703         Priority left = this.getPriority();
704         Priority right = other.getPriority();
705 
706         // High-priority requests are "lesser" so they are sorted to the front.
707         // Equal priorities are sorted by sequence number to provide FIFO ordering.
708         return left == right ? this.mSequence - other.mSequence : right.ordinal() - left.ordinal();
709     }
710 
711     @Override
toString()712     public String toString() {
713         String trafficStatsTag = "0x" + Integer.toHexString(getTrafficStatsTag());
714         return (isCanceled() ? "[X] " : "[ ] ")
715                 + getUrl()
716                 + " "
717                 + trafficStatsTag
718                 + " "
719                 + getPriority()
720                 + " "
721                 + mSequence;
722     }
723 }
724