diff --git a/src/androidTest/java/de/blau/android/osm/TransferMenuTest.java b/src/androidTest/java/de/blau/android/osm/TransferMenuTest.java index 848ae43ff3..fb8c356b54 100644 --- a/src/androidTest/java/de/blau/android/osm/TransferMenuTest.java +++ b/src/androidTest/java/de/blau/android/osm/TransferMenuTest.java @@ -130,10 +130,7 @@ public void dataUpload() { } catch (UiObjectNotFoundException e1) { fail(e1.getMessage()); } - try { - Thread.sleep(10000); // NOSONAR - } catch (InterruptedException e) { - } + TestUtils.sleep(10000); Node n = (Node) App.getDelegator().getOsmElement(Node.NAME, 101792984); assertNotNull(n); assertEquals(OsmElement.STATE_UNCHANGED, n.getState()); @@ -171,6 +168,39 @@ public void dataUpload() { } } + /** + * Upload to changes (mock-)server get a 429 response + */ + @Test + public void dataUploadError() { + + loadTestData(); + + mockServer.enqueue("capabilities1"); // for whatever reason this gets asked for twice + mockServer.enqueue("capabilities1"); + mockServer.enqueue("changeset1"); + mockServer.enqueue("429"); + + TestUtils.clickMenuButton(device, main.getString(R.string.menu_transfer), false, true); + TestUtils.clickText(device, false, main.getString(R.string.menu_transfer_upload), true, false); // menu item + + UiSelector uiSelector = new UiSelector().className("android.widget.Button").instance(1); // dialog upload button + UiObject button = device.findObject(uiSelector); + try { + button.click(); + } catch (UiObjectNotFoundException e1) { + fail(e1.getMessage()); + } + UploadConflictTest.fillCommentAndSource(instrumentation, device); + try { + button.clickAndWaitForNewWindow(); + } catch (UiObjectNotFoundException e1) { + fail(e1.getMessage()); + } + assertTrue(TestUtils.findText(device, false, main.getString(R.string.upload_limit_title))); + assertTrue(TestUtils.clickText(device, false, main.getString(android.R.string.ok), true)); + } + /** * Clear data */ @@ -181,7 +211,7 @@ public void clearData() { assertFalse(App.getDelegator().getApiStorage().isEmpty()); TestUtils.clickMenuButton(device, main.getString(R.string.menu_transfer), false, true); TestUtils.clickText(device, false, main.getString(R.string.menu_transfer_data_clear), true, false); - TestUtils.clickText(device, false, main.getString(R.string.unsaved_data_proceed), true, false); + TestUtils.clickText(device, false, main.getString(R.string.unsaved_data_proceed), true, false); assertTrue(App.getDelegator().getCurrentStorage().isEmpty()); assertTrue(App.getDelegator().getApiStorage().isEmpty()); } diff --git a/src/main/java/de/blau/android/ErrorCodes.java b/src/main/java/de/blau/android/ErrorCodes.java index a6ca08fd01..fbfb1a6e93 100644 --- a/src/main/java/de/blau/android/ErrorCodes.java +++ b/src/main/java/de/blau/android/ErrorCodes.java @@ -11,22 +11,24 @@ private ErrorCodes() { public static final int OK = 0; - public static final int NO_LOGIN_DATA = 1; - public static final int NO_CONNECTION = 2; - public static final int UPLOAD_PROBLEM = 3; - public static final int DATA_CONFLICT = 4; - public static final int BAD_REQUEST = 5; - public static final int API_OFFLINE = 6; - public static final int OUT_OF_MEMORY = 7; - public static final int OUT_OF_MEMORY_DIRTY = 8; - public static final int INVALID_DATA_RECEIVED = 9; - public static final int FILE_WRITE_FAILED = 10; - public static final int NAN = 11; - public static final int INVALID_BOUNDING_BOX = 12; - public static final int SSL_HANDSHAKE = 13; - public static final int INVALID_DATA_READ = 14; - public static final int BOUNDING_BOX_TOO_LARGE = 15; - public static final int CORRUPTED_DATA = 16; + public static final int NO_LOGIN_DATA = 1; + public static final int NO_CONNECTION = 2; + public static final int UPLOAD_PROBLEM = 3; + public static final int DATA_CONFLICT = 4; + public static final int BAD_REQUEST = 5; + public static final int API_OFFLINE = 6; + public static final int OUT_OF_MEMORY = 7; + public static final int OUT_OF_MEMORY_DIRTY = 8; + public static final int INVALID_DATA_RECEIVED = 9; + public static final int FILE_WRITE_FAILED = 10; + public static final int NAN = 11; + public static final int INVALID_BOUNDING_BOX = 12; + public static final int SSL_HANDSHAKE = 13; + public static final int INVALID_DATA_READ = 14; + public static final int BOUNDING_BOX_TOO_LARGE = 15; + public static final int CORRUPTED_DATA = 16; + public static final int DOWNLOAD_LIMIT_EXCEEDED = 17; + public static final int UPLOAD_LIMIT_EXCEEDED = 18; public static final int UPLOAD_CONFLICT = 50; public static final int INVALID_LOGIN = 51; diff --git a/src/main/java/de/blau/android/Logic.java b/src/main/java/de/blau/android/Logic.java index 9cc63fee10..cfe84aa030 100644 --- a/src/main/java/de/blau/android/Logic.java +++ b/src/main/java/de/blau/android/Logic.java @@ -52,6 +52,7 @@ import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.FragmentActivity; +import de.blau.android.contract.HttpStatusCodes; import de.blau.android.contract.Urls; import de.blau.android.dialogs.AttachedObjectWarning; import de.blau.android.dialogs.ErrorAlert; @@ -2984,15 +2985,15 @@ public AsyncResult download(@NonNull final Context ctx, @NonNull Server server, } catch (SAXException e) { Exception ce = e.getException(); if ((ce instanceof StorageException) && ((StorageException) ce).getCode() == StorageException.OOM) { - result = new AsyncResult(ErrorCodes.OUT_OF_MEMORY, ""); + result = new AsyncResult(ErrorCodes.OUT_OF_MEMORY, ""); } else { - result = new AsyncResult(ErrorCodes.INVALID_DATA_RECEIVED, e.getMessage()); + result = new AsyncResult(ErrorCodes.INVALID_DATA_RECEIVED, e.getMessage()); } } catch (ParserConfigurationException | UnsupportedFormatException e) { result = new AsyncResult(ErrorCodes.INVALID_DATA_RECEIVED, e.getMessage()); } catch (OsmServerException e) { - int code = e.getErrorCode(); - if (code == HttpURLConnection.HTTP_BAD_REQUEST) { + switch (e.getErrorCode()) { + case HttpURLConnection.HTTP_BAD_REQUEST: // check error messages Matcher m = Server.ERROR_MESSAGE_BAD_OAUTH_REQUEST.matcher(e.getMessage()); if (m.matches()) { @@ -3000,7 +3001,11 @@ public AsyncResult download(@NonNull final Context ctx, @NonNull Server server, } else { result = new AsyncResult(ErrorCodes.BOUNDING_BOX_TOO_LARGE); } - } else { + break; + case HttpStatusCodes.HTTP_BANDWIDTH_LIMIT_EXCEEDED: + result = new AsyncResult(ErrorCodes.DOWNLOAD_LIMIT_EXCEEDED); + break; + default: result = new AsyncResult(ErrorCodes.UNKNOWN_ERROR, e.getMessage()); } } catch (SSLProtocolException e) { @@ -4113,6 +4118,10 @@ protected UploadResult doInBackground(Void params) { case HttpURLConnection.HTTP_UNAVAILABLE: result.setError(ErrorCodes.UPLOAD_PROBLEM); break; + case HttpStatusCodes.HTTP_TOO_MANY_REQUESTS: + result.setError(ErrorCodes.UPLOAD_LIMIT_EXCEEDED); + result.setMessage(e.getMessage()); + break; default: Log.e(DEBUG_TAG, METHOD_UPLOAD, e); result.setError(ErrorCodes.UNKNOWN_ERROR); @@ -4158,7 +4167,7 @@ protected void onPostExecute(UploadResult result) { } else if (error == ErrorCodes.FORBIDDEN) { ForbiddenLogin.showDialog(activity, result.getMessage()); } else if (error == ErrorCodes.BAD_REQUEST || error == ErrorCodes.NOT_FOUND || error == ErrorCodes.UNKNOWN_ERROR - || error == ErrorCodes.UPLOAD_PROBLEM) { + || error == ErrorCodes.UPLOAD_PROBLEM || error == ErrorCodes.UPLOAD_LIMIT_EXCEEDED) { ErrorAlert.showDialog(activity, error, result.getMessage()); } else if (error != 0) { ErrorAlert.showDialog(activity, error); diff --git a/src/main/java/de/blau/android/contract/HttpStatusCodes.java b/src/main/java/de/blau/android/contract/HttpStatusCodes.java new file mode 100644 index 0000000000..b952d1c92c --- /dev/null +++ b/src/main/java/de/blau/android/contract/HttpStatusCodes.java @@ -0,0 +1,20 @@ +package de.blau.android.contract; + +/** + * Constants for codes missing from HttpURLConnection. + * + * @author simon + * + */ +public final class HttpStatusCodes { + + /** + * Private constructor + */ + private HttpStatusCodes() { + // don't instantiate + } + + public static final int HTTP_TOO_MANY_REQUESTS = 429; + public static final int HTTP_BANDWIDTH_LIMIT_EXCEEDED = 509; +} diff --git a/src/main/java/de/blau/android/dialogs/ErrorAlert.java b/src/main/java/de/blau/android/dialogs/ErrorAlert.java index db928fc7b0..b37e311f36 100644 --- a/src/main/java/de/blau/android/dialogs/ErrorAlert.java +++ b/src/main/java/de/blau/android/dialogs/ErrorAlert.java @@ -147,6 +147,10 @@ private static String getTag(int errorCode) { return "applying_osc_failed"; case ErrorCodes.CORRUPTED_DATA: return "alert_corrupt_data"; + case ErrorCodes.DOWNLOAD_LIMIT_EXCEEDED: + return "download_limit_exceeded"; + case ErrorCodes.UPLOAD_LIMIT_EXCEEDED: + return "upload_limit_exceeded"; case ErrorCodes.DUPLICATE_TAG_KEY: return "alert_duplicate_tag_key"; default: @@ -209,6 +213,10 @@ private static ErrorAlert newInstance(int errorCode, @Nullable String msg) { return createNewInstance(R.string.applying_osc_failed_title, R.string.applying_osc_failed_message, msg); case ErrorCodes.CORRUPTED_DATA: return createNewInstance(R.string.corrupted_data_title, R.string.corrupted_data_message, msg); + case ErrorCodes.DOWNLOAD_LIMIT_EXCEEDED: + return createNewInstance(R.string.download_limit_title, R.string.download_limit_message, msg); + case ErrorCodes.UPLOAD_LIMIT_EXCEEDED: + return createNewInstance(R.string.upload_limit_title, R.string.upload_limit_message, msg); case ErrorCodes.DUPLICATE_TAG_KEY: return createNewInstance(R.string.duplicate_tag_key_title, R.string.duplicate_tag_key_message, msg); default: @@ -259,7 +267,7 @@ public AppCompatDialog onCreateDialog(Bundle savedInstanceState) { if (messageId != 0) { String message = getString(messageId); if (originalMessage != null) { - message = message + "
" + originalMessage; + message = message + "" + originalMessage + ""; } builder.setMessage(Util.fromHtml(message)); } diff --git a/src/main/java/de/blau/android/exception/OsmServerException.java b/src/main/java/de/blau/android/exception/OsmServerException.java index 21a72ba63c..fa1497d377 100755 --- a/src/main/java/de/blau/android/exception/OsmServerException.java +++ b/src/main/java/de/blau/android/exception/OsmServerException.java @@ -3,6 +3,7 @@ import java.net.HttpURLConnection; import android.util.Log; +import de.blau.android.contract.HttpStatusCodes; public class OsmServerException extends OsmException { @@ -123,6 +124,10 @@ private static String errorCodeToMeaning(final int errorCode) { return "An internal error occurred. This is usually an uncaught Ruby exception and should be reported as a bug. There have been cases where such errors were caused by timeouts, i.e. a retry after a short waiting period could succeed. "; case HttpURLConnection.HTTP_UNAVAILABLE: return "The database has been taken offline for maintenance. "; + case HttpStatusCodes.HTTP_TOO_MANY_REQUESTS: + return "The upload allowance for the account has been exhausted. "; + case HttpStatusCodes.HTTP_BANDWIDTH_LIMIT_EXCEEDED: + return "Data download has been rate-limited. "; default: Log.w(DEBUG_TAG, "Unknown error code " + errorCode); } diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 17bdfa30af..452e4b125a 100755 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -69,6 +69,10 @@