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