diff --git a/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java b/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java
index 01a5be4b5106..bbf85f14409c 100644
--- a/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java
+++ b/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java
@@ -265,6 +265,10 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
Logger.error(LOG_TAG, "Failed to get token.", e);
callback.handleError(null, e);
}
+
+ @Override
+ public void handleBackoff(int backoffSeconds) {
+ }
});
}
diff --git a/mobile/android/base/resources/drawable-hdpi/fxaccount_checkbox.png b/mobile/android/base/resources/drawable-hdpi/fxaccount_checkbox.png
index 11e89d34aba2..f6ef37ba9405 100755
Binary files a/mobile/android/base/resources/drawable-hdpi/fxaccount_checkbox.png and b/mobile/android/base/resources/drawable-hdpi/fxaccount_checkbox.png differ
diff --git a/mobile/android/base/resources/drawable-hdpi/fxaccount_ddarrow_active.png b/mobile/android/base/resources/drawable-hdpi/fxaccount_ddarrow_active.png
index 4abb71ee3cdb..a8782e2d6656 100644
Binary files a/mobile/android/base/resources/drawable-hdpi/fxaccount_ddarrow_active.png and b/mobile/android/base/resources/drawable-hdpi/fxaccount_ddarrow_active.png differ
diff --git a/mobile/android/base/resources/drawable-hdpi/fxaccount_ddarrow_inactive.png b/mobile/android/base/resources/drawable-hdpi/fxaccount_ddarrow_inactive.png
index 204562d39f09..8314fc45b04f 100644
Binary files a/mobile/android/base/resources/drawable-hdpi/fxaccount_ddarrow_inactive.png and b/mobile/android/base/resources/drawable-hdpi/fxaccount_ddarrow_inactive.png differ
diff --git a/mobile/android/base/resources/drawable-hdpi/fxaccount_icon.png b/mobile/android/base/resources/drawable-hdpi/fxaccount_icon.png
index 131f05dd288a..03a16d427eae 100755
Binary files a/mobile/android/base/resources/drawable-hdpi/fxaccount_icon.png and b/mobile/android/base/resources/drawable-hdpi/fxaccount_icon.png differ
diff --git a/mobile/android/base/resources/drawable-hdpi/fxaccount_mail.png b/mobile/android/base/resources/drawable-hdpi/fxaccount_mail.png
index 3a2e33adfcc3..23ebe23741ed 100755
Binary files a/mobile/android/base/resources/drawable-hdpi/fxaccount_mail.png and b/mobile/android/base/resources/drawable-hdpi/fxaccount_mail.png differ
diff --git a/mobile/android/base/resources/drawable-hdpi/fxaccount_sync_icon.png b/mobile/android/base/resources/drawable-hdpi/fxaccount_sync_icon.png
index 1b6e67572fb2..fe2e174886c9 100644
Binary files a/mobile/android/base/resources/drawable-hdpi/fxaccount_sync_icon.png and b/mobile/android/base/resources/drawable-hdpi/fxaccount_sync_icon.png differ
diff --git a/mobile/android/base/resources/drawable-mdpi/fxaccount_checkbox.png b/mobile/android/base/resources/drawable-mdpi/fxaccount_checkbox.png
index 1a50daf6de8d..1c1d14c0c9fe 100644
Binary files a/mobile/android/base/resources/drawable-mdpi/fxaccount_checkbox.png and b/mobile/android/base/resources/drawable-mdpi/fxaccount_checkbox.png differ
diff --git a/mobile/android/base/resources/drawable-mdpi/fxaccount_ddarrow_active.png b/mobile/android/base/resources/drawable-mdpi/fxaccount_ddarrow_active.png
index bb978372f0ea..d1fd254cd5f2 100644
Binary files a/mobile/android/base/resources/drawable-mdpi/fxaccount_ddarrow_active.png and b/mobile/android/base/resources/drawable-mdpi/fxaccount_ddarrow_active.png differ
diff --git a/mobile/android/base/resources/drawable-mdpi/fxaccount_ddarrow_inactive.png b/mobile/android/base/resources/drawable-mdpi/fxaccount_ddarrow_inactive.png
index 7d82a89cb183..82304783977b 100644
Binary files a/mobile/android/base/resources/drawable-mdpi/fxaccount_ddarrow_inactive.png and b/mobile/android/base/resources/drawable-mdpi/fxaccount_ddarrow_inactive.png differ
diff --git a/mobile/android/base/resources/drawable-mdpi/fxaccount_icon.png b/mobile/android/base/resources/drawable-mdpi/fxaccount_icon.png
index b6e7595e0bf7..b328c7b44144 100644
Binary files a/mobile/android/base/resources/drawable-mdpi/fxaccount_icon.png and b/mobile/android/base/resources/drawable-mdpi/fxaccount_icon.png differ
diff --git a/mobile/android/base/resources/drawable-mdpi/fxaccount_mail.png b/mobile/android/base/resources/drawable-mdpi/fxaccount_mail.png
index bcb5f1596358..2f7d92b706c5 100644
Binary files a/mobile/android/base/resources/drawable-mdpi/fxaccount_mail.png and b/mobile/android/base/resources/drawable-mdpi/fxaccount_mail.png differ
diff --git a/mobile/android/base/resources/drawable-mdpi/fxaccount_sync_error.png b/mobile/android/base/resources/drawable-mdpi/fxaccount_sync_error.png
index 71f60fb224ee..5b084016df13 100644
Binary files a/mobile/android/base/resources/drawable-mdpi/fxaccount_sync_error.png and b/mobile/android/base/resources/drawable-mdpi/fxaccount_sync_error.png differ
diff --git a/mobile/android/base/resources/drawable-mdpi/sync_desktop.png b/mobile/android/base/resources/drawable-mdpi/sync_desktop.png
index 83f98d3c0b11..e470ebc6d671 100644
Binary files a/mobile/android/base/resources/drawable-mdpi/sync_desktop.png and b/mobile/android/base/resources/drawable-mdpi/sync_desktop.png differ
diff --git a/mobile/android/base/resources/drawable-mdpi/sync_mobile.png b/mobile/android/base/resources/drawable-mdpi/sync_mobile.png
index c237c29559c2..51efb99a2175 100644
Binary files a/mobile/android/base/resources/drawable-mdpi/sync_mobile.png and b/mobile/android/base/resources/drawable-mdpi/sync_mobile.png differ
diff --git a/mobile/android/base/sync/net/SyncResponse.java b/mobile/android/base/sync/net/SyncResponse.java
index 2409f7c72585..b0e676a7e8fe 100644
--- a/mobile/android/base/sync/net/SyncResponse.java
+++ b/mobile/android/base/sync/net/SyncResponse.java
@@ -49,6 +49,20 @@ public class SyncResponse {
return this.getStatusCode() == 200;
}
+ /**
+ * Fetch the content type of the HTTP response body.
+ *
+ * @return a Header
instance, or null
if there was
+ * no body or no valid Content-Type.
+ */
+ public Header getContentType() {
+ HttpEntity entity = this.response.getEntity();
+ if (entity == null) {
+ return null;
+ }
+ return entity.getContentType();
+ }
+
private String body = null;
public String body() throws IllegalStateException, IOException {
if (body != null) {
@@ -145,6 +159,14 @@ public class SyncResponse {
}
}
+ /**
+ * @return A number of seconds, or -1 if the 'X-Backoff' header was not
+ * present.
+ */
+ public int backoffInSeconds() throws NumberFormatException {
+ return this.getIntegerHeader("x-backoff");
+ }
+
/**
* @return A number of seconds, or -1 if the 'X-Weave-Backoff' header was not
* present.
@@ -154,14 +176,21 @@ public class SyncResponse {
}
/**
- * @return A number of milliseconds, or -1 if neither the 'Retry-After' or
- * 'X-Weave-Backoff' header was present.
+ * Extract a number of seconds, or -1 if none of the specified headers were present.
+ *
+ * @param includeRetryAfter
+ * if true
, the Retry-After header is excluded. This is
+ * useful for processing non-error responses where a Retry-After
+ * header would be unexpected.
+ * @return the maximum of the three possible backoff headers, in seconds.
*/
- public long totalBackoffInMilliseconds() {
+ public int totalBackoffInSeconds(boolean includeRetryAfter) {
int retryAfterInSeconds = -1;
- try {
- retryAfterInSeconds = retryAfterInSeconds();
- } catch (NumberFormatException e) {
+ if (includeRetryAfter) {
+ try {
+ retryAfterInSeconds = retryAfterInSeconds();
+ } catch (NumberFormatException e) {
+ }
}
int weaveBackoffInSeconds = -1;
@@ -170,7 +199,26 @@ public class SyncResponse {
} catch (NumberFormatException e) {
}
- long totalBackoff = (long) Math.max(retryAfterInSeconds, weaveBackoffInSeconds);
+ int backoffInSeconds = -1;
+ try {
+ backoffInSeconds = backoffInSeconds();
+ } catch (NumberFormatException e) {
+ }
+
+ int totalBackoff = Math.max(retryAfterInSeconds, Math.max(backoffInSeconds, weaveBackoffInSeconds));
+ if (totalBackoff < 0) {
+ return -1;
+ } else {
+ return totalBackoff;
+ }
+ }
+
+ /**
+ * @return A number of milliseconds, or -1 if neither the 'Retry-After',
+ * 'X-Backoff', or 'X-Weave-Backoff' header were present.
+ */
+ public long totalBackoffInMilliseconds() {
+ long totalBackoff = totalBackoffInSeconds(true);
if (totalBackoff < 0) {
return -1;
} else {
diff --git a/mobile/android/base/sync/net/SyncStorageRecordRequest.java b/mobile/android/base/sync/net/SyncStorageRecordRequest.java
index 88bf81ab73ab..5e87ce8c8ef9 100644
--- a/mobile/android/base/sync/net/SyncStorageRecordRequest.java
+++ b/mobile/android/base/sync/net/SyncStorageRecordRequest.java
@@ -25,6 +25,7 @@ import org.mozilla.gecko.sync.ThreadPool;
* * Headers:
* * Retry-After
* * X-Weave-Backoff
+ * * X-Backoff
* * X-Weave-Records?
* * ...
* * Timeouts
diff --git a/mobile/android/base/tokenserver/TokenServerClient.java b/mobile/android/base/tokenserver/TokenServerClient.java
index e13d0fe3c31d..dd8229fa1d79 100644
--- a/mobile/android/base/tokenserver/TokenServerClient.java
+++ b/mobile/android/base/tokenserver/TokenServerClient.java
@@ -29,6 +29,7 @@ import org.mozilla.gecko.tokenserver.TokenServerException.TokenServerMalformedRe
import org.mozilla.gecko.tokenserver.TokenServerException.TokenServerMalformedResponseException;
import org.mozilla.gecko.tokenserver.TokenServerException.TokenServerUnknownServiceException;
+import ch.boye.httpclientandroidlib.Header;
import ch.boye.httpclientandroidlib.HttpHeaders;
import ch.boye.httpclientandroidlib.HttpResponse;
import ch.boye.httpclientandroidlib.client.ClientProtocolException;
@@ -93,6 +94,28 @@ public class TokenServerClient {
});
}
+ /**
+ * Notify the delegate that some kind of backoff header (X-Backoff,
+ * X-Weave-Backoff, Retry-After) was received and should be acted upon.
+ *
+ * This method is non-terminal, and will be followed by a separate
+ * invoke*
call.
+ *
+ * @param delegate
+ * the delegate to inform.
+ * @param backoffSeconds
+ * the number of seconds for which the system should wait before
+ * making another token server request to this server.
+ */
+ protected void notifyBackoff(final TokenServerClientDelegate delegate, final int backoffSeconds) {
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ delegate.handleBackoff(backoffSeconds);
+ }
+ });
+ }
+
protected void invokeHandleError(final TokenServerClientDelegate delegate, final Exception e) {
executor.execute(new Runnable() {
@Override
@@ -102,17 +125,21 @@ public class TokenServerClient {
});
}
- public TokenServerToken processResponse(HttpResponse response)
- throws TokenServerException {
- SyncResponse res = new SyncResponse(response);
+ public TokenServerToken processResponse(SyncResponse res) throws TokenServerException {
int statusCode = res.getStatusCode();
Logger.debug(LOG_TAG, "Got token response with status code " + statusCode + ".");
// Responses should *always* be JSON, even in the case of 4xx and 5xx
// errors. If we don't see JSON, the server is likely very unhappy.
- String contentType = response.getEntity().getContentType().getValue();
- if (!contentType.equals("application/json") && !contentType.startsWith("application/json;")) {
+ final Header contentType = res.getContentType();
+ if (contentType == null) {
+ throw new TokenServerMalformedResponseException(null, "Non-JSON response Content-Type.");
+ }
+
+ final String type = contentType.getValue();
+ if (!type.equals("application/json") &&
+ !type.startsWith("application/json;")) {
Logger.warn(LOG_TAG, "Got non-JSON response with Content-Type " +
contentType + ". Misconfigured server?");
throw new TokenServerMalformedResponseException(null, "Non-JSON response Content-Type.");
@@ -230,10 +257,21 @@ public class TokenServerClient {
@Override
public void handleHttpResponse(HttpResponse response) {
+ // Skew.
SkewHandler skewHandler = SkewHandler.getSkewHandlerForResource(resource);
skewHandler.updateSkew(response, System.currentTimeMillis());
+
+ // Extract backoff regardless of whether this was an error response, and
+ // Retry-After for 503 responses. The error will be handled elsewhere.)
+ SyncResponse res = new SyncResponse(response);
+ final boolean includeRetryAfter = res.getStatusCode() == 503;
+ int backoffInSeconds = res.totalBackoffInSeconds(includeRetryAfter);
+ if (backoffInSeconds > -1) {
+ client.notifyBackoff(delegate, backoffInSeconds);
+ }
+
try {
- TokenServerToken token = client.processResponse(response);
+ TokenServerToken token = client.processResponse(res);
client.invokeHandleSuccess(delegate, token);
} catch (TokenServerException e) {
client.invokeHandleFailure(delegate, e);
diff --git a/mobile/android/base/tokenserver/TokenServerClientDelegate.java b/mobile/android/base/tokenserver/TokenServerClientDelegate.java
index 2fc48b08ef99..d34d04c73ffa 100644
--- a/mobile/android/base/tokenserver/TokenServerClientDelegate.java
+++ b/mobile/android/base/tokenserver/TokenServerClientDelegate.java
@@ -4,9 +4,13 @@
package org.mozilla.gecko.tokenserver;
-
public interface TokenServerClientDelegate {
void handleSuccess(TokenServerToken token);
void handleFailure(TokenServerException e);
void handleError(Exception e);
+
+ /**
+ * Might be called multiple times, in addition to the other terminating handler methods.
+ */
+ void handleBackoff(int backoffSeconds);
}
\ No newline at end of file