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 listener)138     public Request(String url, Response.ErrorListener listener) {
139         this(Method.DEPRECATED_GET_OR_POST, url, listener);
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      */
Request(int method, String url, @Nullable Response.ErrorListener listener)148     public Request(int method, String url, @Nullable Response.ErrorListener listener) {
149         mMethod = method;
150         mUrl = url;
151         mErrorListener = listener;
152         setRetryPolicy(new DefaultRetryPolicy());
153 
154         mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
155     }
156 
157     /** Return the method for this request. Can be one of the values in {@link Method}. */
getMethod()158     public int getMethod() {
159         return mMethod;
160     }
161 
162     /**
163      * Set a tag on this request. Can be used to cancel all requests with this tag by {@link
164      * RequestQueue#cancelAll(Object)}.
165      *
166      * @return This Request object to allow for chaining.
167      */
setTag(Object tag)168     public Request<?> setTag(Object tag) {
169         mTag = tag;
170         return this;
171     }
172 
173     /**
174      * Returns this request's tag.
175      *
176      * @see Request#setTag(Object)
177      */
getTag()178     public Object getTag() {
179         return mTag;
180     }
181 
182     /** @return this request's {@link com.android.volley.Response.ErrorListener}. */
183     @Nullable
getErrorListener()184     public Response.ErrorListener getErrorListener() {
185         synchronized (mLock) {
186             return mErrorListener;
187         }
188     }
189 
190     /** @return A tag for use with {@link TrafficStats#setThreadStatsTag(int)} */
getTrafficStatsTag()191     public int getTrafficStatsTag() {
192         return mDefaultTrafficStatsTag;
193     }
194 
195     /** @return The hashcode of the URL's host component, or 0 if there is none. */
findDefaultTrafficStatsTag(String url)196     private static int findDefaultTrafficStatsTag(String url) {
197         if (!TextUtils.isEmpty(url)) {
198             Uri uri = Uri.parse(url);
199             if (uri != null) {
200                 String host = uri.getHost();
201                 if (host != null) {
202                     return host.hashCode();
203                 }
204             }
205         }
206         return 0;
207     }
208 
209     /**
210      * Sets the retry policy for this request.
211      *
212      * @return This Request object to allow for chaining.
213      */
setRetryPolicy(RetryPolicy retryPolicy)214     public Request<?> setRetryPolicy(RetryPolicy retryPolicy) {
215         mRetryPolicy = retryPolicy;
216         return this;
217     }
218 
219     /** Adds an event to this request's event log; for debugging. */
addMarker(String tag)220     public void addMarker(String tag) {
221         if (MarkerLog.ENABLED) {
222             mEventLog.add(tag, Thread.currentThread().getId());
223         }
224     }
225 
226     /**
227      * Notifies the request queue that this request has finished (successfully or with error).
228      *
229      * <p>Also dumps all events from this request's event log; for debugging.
230      */
finish(final String tag)231     void finish(final String tag) {
232         if (mRequestQueue != null) {
233             mRequestQueue.finish(this);
234         }
235         if (MarkerLog.ENABLED) {
236             final long threadId = Thread.currentThread().getId();
237             if (Looper.myLooper() != Looper.getMainLooper()) {
238                 // If we finish marking off of the main thread, we need to
239                 // actually do it on the main thread to ensure correct ordering.
240                 Handler mainThread = new Handler(Looper.getMainLooper());
241                 mainThread.post(
242                         new Runnable() {
243                             @Override
244                             public void run() {
245                                 mEventLog.add(tag, threadId);
246                                 mEventLog.finish(Request.this.toString());
247                             }
248                         });
249                 return;
250             }
251 
252             mEventLog.add(tag, threadId);
253             mEventLog.finish(this.toString());
254         }
255     }
256 
sendEvent(@equestQueue.RequestEvent int event)257     void sendEvent(@RequestQueue.RequestEvent int event) {
258         if (mRequestQueue != null) {
259             mRequestQueue.sendRequestEvent(this, event);
260         }
261     }
262 
263     /**
264      * Associates this request with the given queue. The request queue will be notified when this
265      * request has finished.
266      *
267      * @return This Request object to allow for chaining.
268      */
setRequestQueue(RequestQueue requestQueue)269     public Request<?> setRequestQueue(RequestQueue requestQueue) {
270         mRequestQueue = requestQueue;
271         return this;
272     }
273 
274     /**
275      * Sets the sequence number of this request. Used by {@link RequestQueue}.
276      *
277      * @return This Request object to allow for chaining.
278      */
setSequence(int sequence)279     public final Request<?> setSequence(int sequence) {
280         mSequence = sequence;
281         return this;
282     }
283 
284     /** Returns the sequence number of this request. */
getSequence()285     public final int getSequence() {
286         if (mSequence == null) {
287             throw new IllegalStateException("getSequence called before setSequence");
288         }
289         return mSequence;
290     }
291 
292     /** Returns the URL of this request. */
getUrl()293     public String getUrl() {
294         return mUrl;
295     }
296 
297     /** Returns the cache key for this request. By default, this is the URL. */
getCacheKey()298     public String getCacheKey() {
299         String url = getUrl();
300         // If this is a GET request, just use the URL as the key.
301         // For callers using DEPRECATED_GET_OR_POST, we assume the method is GET, which matches
302         // legacy behavior where all methods had the same cache key. We can't determine which method
303         // will be used because doing so requires calling getPostBody() which is expensive and may
304         // throw AuthFailureError.
305         // TODO(#190): Remove support for non-GET methods.
306         int method = getMethod();
307         if (method == Method.GET || method == Method.DEPRECATED_GET_OR_POST) {
308             return url;
309         }
310         return Integer.toString(method) + '-' + url;
311     }
312 
313     /**
314      * Annotates this request with an entry retrieved for it from cache. Used for cache coherency
315      * support.
316      *
317      * @return This Request object to allow for chaining.
318      */
setCacheEntry(Cache.Entry entry)319     public Request<?> setCacheEntry(Cache.Entry entry) {
320         mCacheEntry = entry;
321         return this;
322     }
323 
324     /** Returns the annotated cache entry, or null if there isn't one. */
325     @Nullable
getCacheEntry()326     public Cache.Entry getCacheEntry() {
327         return mCacheEntry;
328     }
329 
330     /**
331      * Mark this request as canceled.
332      *
333      * <p>No callback will be delivered as long as either:
334      *
335      * <ul>
336      *   <li>This method is called on the same thread as the {@link ResponseDelivery} is running on.
337      *       By default, this is the main thread.
338      *   <li>The request subclass being used overrides cancel() and ensures that it does not invoke
339      *       the listener in {@link #deliverResponse} after cancel() has been called in a
340      *       thread-safe manner.
341      * </ul>
342      *
343      * <p>There are no guarantees if both of these conditions aren't met.
344      */
345     @CallSuper
cancel()346     public void cancel() {
347         synchronized (mLock) {
348             mCanceled = true;
349             mErrorListener = null;
350         }
351     }
352 
353     /** Returns true if this request has been canceled. */
isCanceled()354     public boolean isCanceled() {
355         synchronized (mLock) {
356             return mCanceled;
357         }
358     }
359 
360     /**
361      * Returns a list of extra HTTP headers to go along with this request. Can throw {@link
362      * AuthFailureError} as authentication may be required to provide these values.
363      *
364      * @throws AuthFailureError In the event of auth failure
365      */
getHeaders()366     public Map<String, String> getHeaders() throws AuthFailureError {
367         return Collections.emptyMap();
368     }
369 
370     /**
371      * Returns a Map of POST parameters to be used for this request, or null if a simple GET should
372      * be used. Can throw {@link AuthFailureError} as authentication may be required to provide
373      * these values.
374      *
375      * <p>Note that only one of getPostParams() and getPostBody() can return a non-null value.
376      *
377      * @throws AuthFailureError In the event of auth failure
378      * @deprecated Use {@link #getParams()} instead.
379      */
380     @Deprecated
381     @Nullable
getPostParams()382     protected Map<String, String> getPostParams() throws AuthFailureError {
383         return getParams();
384     }
385 
386     /**
387      * Returns which encoding should be used when converting POST parameters returned by {@link
388      * #getPostParams()} into a raw POST body.
389      *
390      * <p>This controls both encodings:
391      *
392      * <ol>
393      *   <li>The string encoding used when converting parameter names and values into bytes prior to
394      *       URL encoding them.
395      *   <li>The string encoding used when converting the URL encoded parameters into a raw byte
396      *       array.
397      * </ol>
398      *
399      * @deprecated Use {@link #getParamsEncoding()} instead.
400      */
401     @Deprecated
getPostParamsEncoding()402     protected String getPostParamsEncoding() {
403         return getParamsEncoding();
404     }
405 
406     /** @deprecated Use {@link #getBodyContentType()} instead. */
407     @Deprecated
getPostBodyContentType()408     public String getPostBodyContentType() {
409         return getBodyContentType();
410     }
411 
412     /**
413      * Returns the raw POST body to be sent.
414      *
415      * @throws AuthFailureError In the event of auth failure
416      * @deprecated Use {@link #getBody()} instead.
417      */
418     @Deprecated
getPostBody()419     public byte[] getPostBody() throws AuthFailureError {
420         // Note: For compatibility with legacy clients of volley, this implementation must remain
421         // here instead of simply calling the getBody() function because this function must
422         // call getPostParams() and getPostParamsEncoding() since legacy clients would have
423         // overridden these two member functions for POST requests.
424         Map<String, String> postParams = getPostParams();
425         if (postParams != null && postParams.size() > 0) {
426             return encodeParameters(postParams, getPostParamsEncoding());
427         }
428         return null;
429     }
430 
431     /**
432      * Returns a Map of parameters to be used for a POST or PUT request. Can throw {@link
433      * AuthFailureError} as authentication may be required to provide these values.
434      *
435      * <p>Note that you can directly override {@link #getBody()} for custom data.
436      *
437      * @throws AuthFailureError in the event of auth failure
438      */
439     @Nullable
getParams()440     protected Map<String, String> getParams() throws AuthFailureError {
441         return null;
442     }
443 
444     /**
445      * Returns which encoding should be used when converting POST or PUT parameters returned by
446      * {@link #getParams()} into a raw POST or PUT body.
447      *
448      * <p>This controls both encodings:
449      *
450      * <ol>
451      *   <li>The string encoding used when converting parameter names and values into bytes prior to
452      *       URL encoding them.
453      *   <li>The string encoding used when converting the URL encoded parameters into a raw byte
454      *       array.
455      * </ol>
456      */
getParamsEncoding()457     protected String getParamsEncoding() {
458         return DEFAULT_PARAMS_ENCODING;
459     }
460 
461     /** Returns the content type of the POST or PUT body. */
getBodyContentType()462     public String getBodyContentType() {
463         return "application/x-www-form-urlencoded; charset=" + getParamsEncoding();
464     }
465 
466     /**
467      * Returns the raw POST or PUT body to be sent.
468      *
469      * <p>By default, the body consists of the request parameters in
470      * application/x-www-form-urlencoded format. When overriding this method, consider overriding
471      * {@link #getBodyContentType()} as well to match the new body format.
472      *
473      * @throws AuthFailureError in the event of auth failure
474      */
getBody()475     public byte[] getBody() throws AuthFailureError {
476         Map<String, String> params = getParams();
477         if (params != null && params.size() > 0) {
478             return encodeParameters(params, getParamsEncoding());
479         }
480         return null;
481     }
482 
483     /** Converts <code>params</code> into an application/x-www-form-urlencoded encoded string. */
encodeParameters(Map<String, String> params, String paramsEncoding)484     private byte[] encodeParameters(Map<String, String> params, String paramsEncoding) {
485         StringBuilder encodedParams = new StringBuilder();
486         try {
487             for (Map.Entry<String, String> entry : params.entrySet()) {
488                 if (entry.getKey() == null || entry.getValue() == null) {
489                     throw new IllegalArgumentException(
490                             String.format(
491                                     "Request#getParams() or Request#getPostParams() returned a map "
492                                             + "containing a null key or value: (%s, %s). All keys "
493                                             + "and values must be non-null.",
494                                     entry.getKey(), entry.getValue()));
495                 }
496                 encodedParams.append(URLEncoder.encode(entry.getKey(), paramsEncoding));
497                 encodedParams.append('=');
498                 encodedParams.append(URLEncoder.encode(entry.getValue(), paramsEncoding));
499                 encodedParams.append('&');
500             }
501             return encodedParams.toString().getBytes(paramsEncoding);
502         } catch (UnsupportedEncodingException uee) {
503             throw new RuntimeException("Encoding not supported: " + paramsEncoding, uee);
504         }
505     }
506 
507     /**
508      * Set whether or not responses to this request should be cached.
509      *
510      * @return This Request object to allow for chaining.
511      */
setShouldCache(boolean shouldCache)512     public final Request<?> setShouldCache(boolean shouldCache) {
513         mShouldCache = shouldCache;
514         return this;
515     }
516 
517     /** Returns true if responses to this request should be cached. */
shouldCache()518     public final boolean shouldCache() {
519         return mShouldCache;
520     }
521 
522     /**
523      * Sets whether or not the request should be retried in the event of an HTTP 5xx (server) error.
524      *
525      * @return This Request object to allow for chaining.
526      */
setShouldRetryServerErrors(boolean shouldRetryServerErrors)527     public final Request<?> setShouldRetryServerErrors(boolean shouldRetryServerErrors) {
528         mShouldRetryServerErrors = shouldRetryServerErrors;
529         return this;
530     }
531 
532     /**
533      * Returns true if this request should be retried in the event of an HTTP 5xx (server) error.
534      */
shouldRetryServerErrors()535     public final boolean shouldRetryServerErrors() {
536         return mShouldRetryServerErrors;
537     }
538 
539     /**
540      * Sets whether or not the request should be retried in the event that no connection could be
541      * established.
542      *
543      * @return This Request object to allow for chaining.
544      */
setShouldRetryConnectionErrors(boolean shouldRetryConnectionErrors)545     public final Request<?> setShouldRetryConnectionErrors(boolean shouldRetryConnectionErrors) {
546         mShouldRetryConnectionErrors = shouldRetryConnectionErrors;
547         return this;
548     }
549 
550     /**
551      * Returns true if this request should be retried in the event that no connection could be
552      * established.
553      */
shouldRetryConnectionErrors()554     public final boolean shouldRetryConnectionErrors() {
555         return mShouldRetryConnectionErrors;
556     }
557 
558     /**
559      * Priority values. Requests will be processed from higher priorities to lower priorities, in
560      * FIFO order.
561      */
562     public enum Priority {
563         LOW,
564         NORMAL,
565         HIGH,
566         IMMEDIATE
567     }
568 
569     /** Returns the {@link Priority} of this request; {@link Priority#NORMAL} by default. */
getPriority()570     public Priority getPriority() {
571         return Priority.NORMAL;
572     }
573 
574     /**
575      * Returns the socket timeout in milliseconds per retry attempt. (This value can be changed per
576      * retry attempt if a backoff is specified via backoffTimeout()). If there are no retry attempts
577      * remaining, this will cause delivery of a {@link TimeoutError} error.
578      */
getTimeoutMs()579     public final int getTimeoutMs() {
580         return getRetryPolicy().getCurrentTimeout();
581     }
582 
583     /** Returns the retry policy that should be used for this request. */
getRetryPolicy()584     public RetryPolicy getRetryPolicy() {
585         return mRetryPolicy;
586     }
587 
588     /**
589      * Mark this request as having a response delivered on it. This can be used later in the
590      * request's lifetime for suppressing identical responses.
591      */
markDelivered()592     public void markDelivered() {
593         synchronized (mLock) {
594             mResponseDelivered = true;
595         }
596     }
597 
598     /** Returns true if this request has had a response delivered for it. */
hasHadResponseDelivered()599     public boolean hasHadResponseDelivered() {
600         synchronized (mLock) {
601             return mResponseDelivered;
602         }
603     }
604 
605     /**
606      * Subclasses must implement this to parse the raw network response and return an appropriate
607      * response type. This method will be called from a worker thread. The response will not be
608      * delivered if you return null.
609      *
610      * @param response Response from the network
611      * @return The parsed response, or null in the case of an error
612      */
parseNetworkResponse(NetworkResponse response)613     protected abstract Response<T> parseNetworkResponse(NetworkResponse response);
614 
615     /**
616      * Subclasses can override this method to parse 'networkError' and return a more specific error.
617      *
618      * <p>The default implementation just returns the passed 'networkError'.
619      *
620      * @param volleyError the error retrieved from the network
621      * @return an NetworkError augmented with additional information
622      */
parseNetworkError(VolleyError volleyError)623     protected VolleyError parseNetworkError(VolleyError volleyError) {
624         return volleyError;
625     }
626 
627     /**
628      * Subclasses must implement this to perform delivery of the parsed response to their listeners.
629      * The given response is guaranteed to be non-null; responses that fail to parse are not
630      * delivered.
631      *
632      * @param response The parsed response returned by {@link
633      *     #parseNetworkResponse(NetworkResponse)}
634      */
deliverResponse(T response)635     protected abstract void deliverResponse(T response);
636 
637     /**
638      * Delivers error message to the ErrorListener that the Request was initialized with.
639      *
640      * @param error Error details
641      */
deliverError(VolleyError error)642     public void deliverError(VolleyError error) {
643         Response.ErrorListener listener;
644         synchronized (mLock) {
645             listener = mErrorListener;
646         }
647         if (listener != null) {
648             listener.onErrorResponse(error);
649         }
650     }
651 
652     /**
653      * {@link NetworkRequestCompleteListener} that will receive callbacks when the request returns
654      * from the network.
655      */
setNetworkRequestCompleteListener( NetworkRequestCompleteListener requestCompleteListener)656     /* package */ void setNetworkRequestCompleteListener(
657             NetworkRequestCompleteListener requestCompleteListener) {
658         synchronized (mLock) {
659             mRequestCompleteListener = requestCompleteListener;
660         }
661     }
662 
663     /**
664      * Notify NetworkRequestCompleteListener that a valid response has been received which can be
665      * used for other, waiting requests.
666      *
667      * @param response received from the network
668      */
notifyListenerResponseReceived(Response<?> response)669     /* package */ void notifyListenerResponseReceived(Response<?> response) {
670         NetworkRequestCompleteListener listener;
671         synchronized (mLock) {
672             listener = mRequestCompleteListener;
673         }
674         if (listener != null) {
675             listener.onResponseReceived(this, response);
676         }
677     }
678 
679     /**
680      * Notify NetworkRequestCompleteListener that the network request did not result in a response
681      * which can be used for other, waiting requests.
682      */
notifyListenerResponseNotUsable()683     /* package */ void notifyListenerResponseNotUsable() {
684         NetworkRequestCompleteListener listener;
685         synchronized (mLock) {
686             listener = mRequestCompleteListener;
687         }
688         if (listener != null) {
689             listener.onNoUsableResponseReceived(this);
690         }
691     }
692 
693     /**
694      * Our comparator sorts from high to low priority, and secondarily by sequence number to provide
695      * FIFO ordering.
696      */
697     @Override
compareTo(Request<T> other)698     public int compareTo(Request<T> other) {
699         Priority left = this.getPriority();
700         Priority right = other.getPriority();
701 
702         // High-priority requests are "lesser" so they are sorted to the front.
703         // Equal priorities are sorted by sequence number to provide FIFO ordering.
704         return left == right ? this.mSequence - other.mSequence : right.ordinal() - left.ordinal();
705     }
706 
707     @Override
toString()708     public String toString() {
709         String trafficStatsTag = "0x" + Integer.toHexString(getTrafficStatsTag());
710         return (isCanceled() ? "[X] " : "[ ] ")
711                 + getUrl()
712                 + " "
713                 + trafficStatsTag
714                 + " "
715                 + getPriority()
716                 + " "
717                 + mSequence;
718     }
719 }
720