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