diff --git a/OsmAnd/res/drawable/ic_action_alert_circle.xml b/OsmAnd/res/drawable/ic_action_alert_circle.xml new file mode 100644 index 0000000000..38e1947883 --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_alert_circle.xml @@ -0,0 +1,10 @@ + + + diff --git a/OsmAnd/res/drawable/ic_action_cellular.xml b/OsmAnd/res/drawable/ic_action_cellular.xml new file mode 100644 index 0000000000..0d154db6b7 --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_cellular.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/OsmAnd/res/drawable/ic_action_cellular_disable.xml b/OsmAnd/res/drawable/ic_action_cellular_disable.xml new file mode 100644 index 0000000000..06a8cc4845 --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_cellular_disable.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/OsmAnd/res/drawable/ic_action_cloud.xml b/OsmAnd/res/drawable/ic_action_cloud.xml new file mode 100644 index 0000000000..4d778fb6e5 --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_cloud.xml @@ -0,0 +1,9 @@ + + + diff --git a/OsmAnd/res/drawable/ic_action_cloud_alert.xml b/OsmAnd/res/drawable/ic_action_cloud_alert.xml new file mode 100644 index 0000000000..61b5efee0b --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_cloud_alert.xml @@ -0,0 +1,10 @@ + + + diff --git a/OsmAnd/res/drawable/ic_action_cloud_upload.xml b/OsmAnd/res/drawable/ic_action_cloud_upload.xml new file mode 100644 index 0000000000..7b2497436d --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_cloud_upload.xml @@ -0,0 +1,9 @@ + + + diff --git a/OsmAnd/res/drawable/ic_action_cloud_upload_colored.xml b/OsmAnd/res/drawable/ic_action_cloud_upload_colored.xml new file mode 100644 index 0000000000..3336a301b1 --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_cloud_upload_colored.xml @@ -0,0 +1,12 @@ + + + + diff --git a/OsmAnd/res/drawable/ic_action_read_from_file.xml b/OsmAnd/res/drawable/ic_action_read_from_file.xml new file mode 100644 index 0000000000..3492a4bcd2 --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_read_from_file.xml @@ -0,0 +1,15 @@ + + + + diff --git a/OsmAnd/res/drawable/ic_action_restore.xml b/OsmAnd/res/drawable/ic_action_restore.xml new file mode 100644 index 0000000000..dfe803dc2c --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_restore.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + diff --git a/OsmAnd/res/drawable/ic_action_user_folder.xml b/OsmAnd/res/drawable/ic_action_user_folder.xml new file mode 100644 index 0000000000..2990186fb5 --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_user_folder.xml @@ -0,0 +1,15 @@ + + + + diff --git a/OsmAnd/src/net/osmand/AndroidNetworkUtils.java b/OsmAnd/src/net/osmand/AndroidNetworkUtils.java index f25d08565b..46812373ad 100644 --- a/OsmAnd/src/net/osmand/AndroidNetworkUtils.java +++ b/OsmAnd/src/net/osmand/AndroidNetworkUtils.java @@ -48,13 +48,14 @@ public class AndroidNetworkUtils { private static final Log LOG = PlatformUtil.getLog(AndroidNetworkUtils.class); public interface OnRequestResultListener { - void onResult(String result); + void onResult(@Nullable String result, @Nullable String error); } public interface OnFilesUploadCallback { @Nullable Map getAdditionalParams(@NonNull File file); void onFileUploadProgress(@NonNull File file, int percent); + void onFileUploadDone(@NonNull File file); void onFilesUploadDone(@NonNull Map errors); } @@ -64,16 +65,26 @@ public class AndroidNetworkUtils { void onFileDownloadProgress(@NonNull File file, int percent); @WorkerThread void onFileDownloadedAsync(@NonNull File file); + + void onFileDownloadDone(@NonNull File file); void onFilesDownloadDone(@NonNull Map errors); } public static class RequestResponse { - private Request request; - private String response; + private final Request request; + private final String response; + private final String error; RequestResponse(@NonNull Request request, @Nullable String response) { this.request = request; this.response = response; + this.error = null; + } + + RequestResponse(@NonNull Request request, @Nullable String response, @Nullable String error) { + this.request = request; + this.response = response; + this.error = error; } public Request getRequest() { @@ -83,6 +94,10 @@ public class AndroidNetworkUtils { public String getResponse() { return response; } + + public String getError() { + return error; + } } public interface OnSendRequestsListener { @@ -109,11 +124,18 @@ public class AndroidNetworkUtils { for (Request request : requests) { RequestResponse requestResponse; try { - String response = sendRequest(ctx, request.getUrl(), request.getParameters(), - request.getUserOperation(), request.isToastAllowed(), request.isPost()); - requestResponse = new RequestResponse(request, response); + final String[] response = {null, null}; + sendRequest(ctx, request.getUrl(), request.getParameters(), + request.getUserOperation(), request.isToastAllowed(), request.isPost(), new OnRequestResultListener() { + @Override + public void onResult(@Nullable String result, @Nullable String error) { + response[0] = result; + response[1] = error; + } + }); + requestResponse = new RequestResponse(request, response[0], response[1]); } catch (Exception e) { - requestResponse = new RequestResponse(request, null); + requestResponse = new RequestResponse(request, null, "Unexpected error"); } responses.add(requestResponse); publishProgress(requestResponse); @@ -157,21 +179,29 @@ public class AndroidNetworkUtils { final boolean post, final OnRequestResultListener listener, final Executor executor) { - new AsyncTask() { + new AsyncTask() { @Override - protected String doInBackground(Void... params) { + protected String[] doInBackground(Void... params) { + final String[] res = {null, null}; try { - return sendRequest(ctx, url, parameters, userOperation, toastAllowed, post); + sendRequest(ctx, url, parameters, userOperation, toastAllowed, post, new OnRequestResultListener() { + @Override + public void onResult(@Nullable String result, @Nullable String error) { + res[0] = result; + res[1] = error; + } + }); } catch (Exception e) { - return null; + // ignore } + return res; } @Override - protected void onPostExecute(String response) { + protected void onPostExecute(String[] response) { if (listener != null) { - listener.onResult(response); + listener.onResult(response[0], response[1]); } } @@ -255,7 +285,7 @@ public class AndroidNetworkUtils { } catch (Exception e) { errors.put(file, e.getMessage()); } - publishProgress(file, Integer.MAX_VALUE); + publishProgress(file, -1); } return errors; } @@ -263,7 +293,13 @@ public class AndroidNetworkUtils { @Override protected void onProgressUpdate(Object... objects) { if (callback != null) { - callback.onFileDownloadProgress((File) objects[0], (Integer) objects[1]); + File file = (File) objects[0]; + Integer progress = (Integer) objects[1]; + if (progress >= 0) { + callback.onFileDownloadProgress(file, progress); + } else { + callback.onFileDownloadDone(file); + } } } @@ -280,9 +316,17 @@ public class AndroidNetworkUtils { public static String sendRequest(@Nullable OsmandApplication ctx, @NonNull String url, @Nullable Map parameters, @Nullable String userOperation, boolean toastAllowed, boolean post) { + return sendRequest(ctx, url, parameters, userOperation, toastAllowed, post, null); + } + + public static String sendRequest(@Nullable OsmandApplication ctx, @NonNull String url, + @Nullable Map parameters, + @Nullable String userOperation, boolean toastAllowed, boolean post, + @Nullable OnRequestResultListener listener) { + String result = null; + String error = null; HttpURLConnection connection = null; try { - String params = null; if (parameters != null && parameters.size() > 0) { StringBuilder sb = new StringBuilder(); @@ -312,68 +356,66 @@ public class AndroidNetworkUtils { output.write(params.getBytes("UTF-8")); output.flush(); output.close(); - } else { - connection.setRequestMethod("GET"); connection.connect(); } - if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + if (ctx != null) { + error = (!Algorithms.isEmpty(userOperation) ? userOperation + " " : "") + + ctx.getString(R.string.failed_op) + ": " + connection.getResponseMessage(); + } else { + error = (!Algorithms.isEmpty(userOperation) ? userOperation + " " : "") + + "failed: " + connection.getResponseMessage(); + } if (toastAllowed && ctx != null) { - String msg = (!Algorithms.isEmpty(userOperation) ? userOperation + " " : "") - + ctx.getString(R.string.failed_op) + ": " - + connection.getResponseMessage(); - showToast(ctx, msg); + showToast(ctx, error); + } + InputStream errorStream = connection.getErrorStream(); + if (errorStream != null) { + error = streamToString(errorStream); } } else { - StringBuilder responseBody = new StringBuilder(); - responseBody.setLength(0); - InputStream i = connection.getInputStream(); - if (i != null) { - BufferedReader in = new BufferedReader(new InputStreamReader(i, "UTF-8"), 256); - String s; - boolean f = true; - while ((s = in.readLine()) != null) { - if (!f) { - responseBody.append("\n"); - } else { - f = false; - } - responseBody.append(s); - } - try { - in.close(); - i.close(); - } catch (Exception e) { - // ignore exception - } - } - return responseBody.toString(); + result = streamToString(connection.getInputStream()); } - } catch (NullPointerException e) { // that's tricky case why NPE is thrown to fix that problem httpClient could be used + if (ctx != null) { + error = ctx.getString(R.string.auth_failed); + } else { + error = "Authorization failed"; + } if (toastAllowed && ctx != null) { - String msg = ctx.getString(R.string.auth_failed); - showToast(ctx, msg); + showToast(ctx, error); } } catch (MalformedURLException e) { + if (ctx != null) { + error = MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template) + + ": " + ctx.getResources().getString(R.string.shared_string_unexpected_error), userOperation); + } else { + error = "Action " + userOperation + ": Unexpected error"; + } if (toastAllowed && ctx != null) { - showToast(ctx, MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template) - + ": " + ctx.getResources().getString(R.string.shared_string_unexpected_error), userOperation)); + showToast(ctx, error); } } catch (IOException e) { + if (ctx != null) { + error = MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template) + + ": " + ctx.getResources().getString(R.string.shared_string_io_error), userOperation); + } else { + error = "Action " + userOperation + ": I/O error"; + } if (toastAllowed && ctx != null) { - showToast(ctx, MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template) - + ": " + ctx.getResources().getString(R.string.shared_string_io_error), userOperation)); + showToast(ctx, error); } } finally { if (connection != null) { connection.disconnect(); } } - + if (listener != null) { + listener.onResult(result, error); + } return null; } @@ -401,35 +443,64 @@ public class AndroidNetworkUtils { public static String downloadFile(@NonNull String url, @NonNull File fileToSave, boolean gzip, @Nullable IProgress progress) { String error = null; try { - URLConnection connection = NetworkUtils.getHttpURLConnection(url); + HttpURLConnection connection = NetworkUtils.getHttpURLConnection(url); connection.setConnectTimeout(CONNECTION_TIMEOUT); connection.setReadTimeout(CONNECTION_TIMEOUT); if (gzip) { connection.setRequestProperty("Accept-Encoding", "deflate, gzip"); } - InputStream inputStream = gzip - ? new GZIPInputStream(connection.getInputStream()) - : new BufferedInputStream(connection.getInputStream(), 8 * 1024); - fileToSave.getParentFile().mkdirs(); - OutputStream stream = null; - try { - stream = new FileOutputStream(fileToSave); - Algorithms.streamCopy(inputStream, stream, progress, 1024); - stream.flush(); - } finally { - Algorithms.closeStream(inputStream); - Algorithms.closeStream(stream); + connection.connect(); + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + return streamToString(connection.getErrorStream()); + } else { + InputStream inputStream = gzip + ? new GZIPInputStream(connection.getInputStream()) + : new BufferedInputStream(connection.getInputStream(), 8 * 1024); + fileToSave.getParentFile().mkdirs(); + OutputStream stream = null; + try { + stream = new FileOutputStream(fileToSave); + Algorithms.streamCopy(inputStream, stream, progress, 1024); + stream.flush(); + } finally { + Algorithms.closeStream(inputStream); + Algorithms.closeStream(stream); + } } } catch (UnknownHostException e) { error = e.getMessage(); LOG.error("UnknownHostException, cannot download file " + url + " " + error); } catch (Exception e) { error = e.getMessage(); - LOG.warn("Cannot download file : " + url, e); + LOG.warn("Cannot download file: " + url, e); } return error; } + private static String streamToString(InputStream inputStream) throws IOException { + StringBuilder result = new StringBuilder(); + if (inputStream != null) { + BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"), 256); + String buffer; + boolean f = true; + while ((buffer = in.readLine()) != null) { + if (!f) { + result.append("\n"); + } else { + f = false; + } + result.append(buffer); + } + try { + in.close(); + inputStream.close(); + } catch (Exception e) { + // ignore exception + } + } + return result.toString(); + } + private static final String BOUNDARY = "CowMooCowMooCowCowCow"; public static String uploadFile(@NonNull String urlText, @NonNull File file, boolean gzip, @@ -444,7 +515,6 @@ public class AndroidNetworkUtils { @NonNull Map additionalParams, @Nullable Map headers, @Nullable IProgress progress) { - URL url; try { boolean firstPrm = !urlText.contains("?"); StringBuilder sb = new StringBuilder(urlText); @@ -455,7 +525,7 @@ public class AndroidNetworkUtils { urlText = sb.toString(); LOG.info("Start uploading file to " + urlText + " " + fileName); - url = new URL(urlText); + URL url = new URL(urlText); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setDoInput(true); @@ -496,6 +566,10 @@ public class AndroidNetworkUtils { LOG.info("Finish uploading file " + fileName); LOG.info("Response code and message : " + conn.getResponseCode() + " " + conn.getResponseMessage()); if (conn.getResponseCode() != 200) { + InputStream errorStream = conn.getErrorStream(); + if (errorStream != null) { + return streamToString(errorStream); + } return conn.getResponseMessage(); } InputStream is = conn.getInputStream(); @@ -575,7 +649,7 @@ public class AndroidNetworkUtils { } catch (Exception e) { errors.put(file, e.getMessage()); } - publishProgress(file, Integer.MAX_VALUE); + publishProgress(file, -1); } return errors; } @@ -583,7 +657,13 @@ public class AndroidNetworkUtils { @Override protected void onProgressUpdate(Object... objects) { if (callback != null) { - callback.onFileUploadProgress((File) objects[0], (Integer) objects[1]); + File file = (File) objects[0]; + Integer progress = (Integer) objects[1]; + if (progress >= 0) { + callback.onFileUploadProgress(file, progress); + } else { + callback.onFileUploadDone(file); + } } } @@ -602,11 +682,11 @@ public class AndroidNetworkUtils { } public static class Request { - private String url; - private Map parameters; - private String userOperation; - private boolean toastAllowed; - private boolean post; + private final String url; + private final Map parameters; + private final String userOperation; + private final boolean toastAllowed; + private final boolean post; public Request(String url, Map parameters, String userOperation, boolean toastAllowed, boolean post) { this.url = url; diff --git a/OsmAnd/src/net/osmand/plus/CustomRegion.java b/OsmAnd/src/net/osmand/plus/CustomRegion.java index 7cf4a3c9f9..dd72c6008b 100644 --- a/OsmAnd/src/net/osmand/plus/CustomRegion.java +++ b/OsmAnd/src/net/osmand/plus/CustomRegion.java @@ -225,7 +225,7 @@ public class CustomRegion extends WorldRegion { && app.getSettings().isInternetConnectionAvailable()) { OnRequestResultListener resultListener = new OnRequestResultListener() { @Override - public void onResult(String result) { + public void onResult(@Nullable String result, @Nullable String error) { if (!Algorithms.isEmpty(result)) { if ("json".equalsIgnoreCase(dynamicDownloadItems.format)) { dynamicItemsJson = mapJsonItems(result); diff --git a/OsmAnd/src/net/osmand/plus/backup/BackupHelper.java b/OsmAnd/src/net/osmand/plus/backup/BackupHelper.java index 87423ec582..5bdedcaa48 100644 --- a/OsmAnd/src/net/osmand/plus/backup/BackupHelper.java +++ b/OsmAnd/src/net/osmand/plus/backup/BackupHelper.java @@ -37,6 +37,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -65,10 +66,6 @@ public class BackupHelper { public final static int STATUS_EMPTY_RESPONSE_ERROR = 2; public final static int STATUS_SERVER_ERROR = 3; - public interface OnResultListener { - void onResult(int status, @Nullable String message, @Nullable JSONObject json); - } - public interface OnRegisterUserListener { void onRegisterUser(int status, @Nullable String message); } @@ -83,7 +80,6 @@ public class BackupHelper { public interface OnCollectLocalFilesListener { void onFileCollected(@NonNull GpxFileInfo fileInfo); - void onFilesCollected(@NonNull List fileInfos); } @@ -93,20 +89,21 @@ public class BackupHelper { public interface OnUploadFilesListener { void onFileUploadProgress(@NonNull File file, int progress); - + void onFileUploadDone(@NonNull File file); void onFilesUploadDone(@NonNull Map errors); } public interface OnDeleteFilesListener { void onFileDeleteProgress(@NonNull UserFile file); - void onFilesDeleteDone(@NonNull Map errors); } public interface OnDownloadFileListener { void onFileDownloadProgress(@NonNull UserFile userFile, int progress); + @WorkerThread void onFileDownloadedAsync(@NonNull File file); + void onFileDownloaded(@NonNull File file); void onFilesDownloadDone(@NonNull Map errors); } @@ -174,20 +171,22 @@ public class BackupHelper { params.put("orderid", orderId); } params.put("deviceid", app.getUserAndroidId()); - AndroidNetworkUtils.sendRequestAsync(app, USER_REGISTER_URL, params, "Register user", true, true, new OnRequestResultListener() { + AndroidNetworkUtils.sendRequestAsync(app, USER_REGISTER_URL, params, "Register user", false, true, new OnRequestResultListener() { @Override - public void onResult(String resultJson) { + public void onResult(@Nullable String resultJson, @Nullable String error) { int status; String message; - if (!Algorithms.isEmpty(resultJson)) { + if (!Algorithms.isEmpty(error)) { + message = "User registration error: " + parseServerError(error); + status = STATUS_SERVER_ERROR; + } else if (!Algorithms.isEmpty(resultJson)) { try { JSONObject result = new JSONObject(resultJson); - String statusStr = result.getString("status"); - if (statusStr.equals("ok")) { + if (result.has("status") && "ok".equals(result.getString("status"))) { message = "You have been registered successfully. Please check for email with activation code."; status = STATUS_SUCCESS; } else { - message = "User registration error: " + statusStr; + message = "User registration error: unknown"; status = STATUS_SERVER_ERROR; } } catch (JSONException e) { @@ -217,12 +216,15 @@ public class BackupHelper { params.put("deviceid", androidId); } params.put("token", token); - AndroidNetworkUtils.sendRequestAsync(app, DEVICE_REGISTER_URL, params, "Register device", true, true, new OnRequestResultListener() { + AndroidNetworkUtils.sendRequestAsync(app, DEVICE_REGISTER_URL, params, "Register device", false, true, new OnRequestResultListener() { @Override - public void onResult(String resultJson) { + public void onResult(@Nullable String resultJson, @Nullable String error) { int status; String message; - if (!Algorithms.isEmpty(resultJson)) { + if (!Algorithms.isEmpty(error)) { + message = "Device registration error: " + parseServerError(error); + status = STATUS_SERVER_ERROR; + } else if (!Algorithms.isEmpty(resultJson)) { try { JSONObject result = new JSONObject(resultJson); settings.BACKUP_DEVICE_ID.set(result.getString("id")); @@ -230,8 +232,9 @@ public class BackupHelper { settings.BACKUP_NATIVE_DEVICE_ID.set(result.getString("deviceid")); settings.BACKUP_ACCESS_TOKEN.set(result.getString("accesstoken")); settings.BACKUP_ACCESS_TOKEN_UPDATE_TIME.set(result.getString("udpatetime")); - status = STATUS_SUCCESS; + message = "Device have been registered successfully"; + status = STATUS_SUCCESS; } catch (JSONException e) { message = "Device registration error: json parsing"; status = STATUS_PARSE_JSON_ERROR; @@ -271,14 +274,6 @@ public class BackupHelper { additionaParams.put("name", gpxFileInfo.getFileName(true)); additionaParams.put("type", Algorithms.getFileExtension(file)); gpxFileInfo.uploadTime = System.currentTimeMillis(); - if (file.equals(favoritesFile)) { - favouritesHelper.setLastUploadedTime(gpxFileInfo.uploadTime); - } else { - GpxDataItem gpxItem = gpxHelper.getItem(file); - if (gpxItem != null) { - gpxHelper.updateLastUploadedTime(gpxItem, gpxFileInfo.uploadTime); - } - } additionaParams.put("clienttime", String.valueOf(gpxFileInfo.uploadTime)); } return additionaParams; @@ -291,13 +286,31 @@ public class BackupHelper { } } + @Override + public void onFileUploadDone(@NonNull File file) { + if (listener != null) { + GpxFileInfo gpxFileInfo = gpxInfos.get(file); + if (gpxFileInfo != null) { + if (file.equals(favoritesFile)) { + favouritesHelper.setLastUploadedTime(gpxFileInfo.uploadTime); + } else { + GpxDataItem gpxItem = gpxHelper.getItem(file); + if (gpxItem != null) { + gpxHelper.updateLastUploadedTime(gpxItem, gpxFileInfo.uploadTime); + } + } + } + listener.onFileUploadDone(file); + } + } + @Override public void onFilesUploadDone(@NonNull Map errors) { if (errors.isEmpty()) { settings.BACKUP_LAST_UPLOADED_TIME.set(System.currentTimeMillis() + 1); } if (listener != null) { - listener.onFilesUploadDone(errors); + listener.onFilesUploadDone(resolveServerErrors(errors)); } } }, EXECUTOR); @@ -338,17 +351,29 @@ public class BackupHelper { for (RequestResponse response : results) { UserFile userFile = filesMap.get(response.getRequest()); if (userFile != null) { - String responseStr = response.getResponse(); boolean success; - try { - JSONObject json = new JSONObject(responseStr); - String status = json.getString("status"); - success = status.equalsIgnoreCase("ok"); - } catch (JSONException e) { + String message = null; + String errorStr = response.getError(); + if (!Algorithms.isEmpty(errorStr)) { + message = parseServerError(errorStr); success = false; + } else { + String responseStr = response.getResponse(); + try { + JSONObject result = new JSONObject(responseStr); + if (result.has("status") && "ok".equals(result.getString("status"))) { + success = true; + } else { + message = "Unknown error"; + success = false; + } + } catch (JSONException e) { + message = "Json parsing error"; + success = false; + } } if (!success) { - errors.put(userFile, responseStr); + errors.put(userFile, message); } } } @@ -364,13 +389,16 @@ public class BackupHelper { Map params = new HashMap<>(); params.put("deviceid", getDeviceId()); params.put("accessToken", getAccessToken()); - AndroidNetworkUtils.sendRequestAsync(app, LIST_FILES_URL, params, "Download file list", true, false, new OnRequestResultListener() { + AndroidNetworkUtils.sendRequestAsync(app, LIST_FILES_URL, params, "Download file list", false, false, new OnRequestResultListener() { @Override - public void onResult(String resultJson) { + public void onResult(@Nullable String resultJson, @Nullable String error) { int status; String message; List userFiles = new ArrayList<>(); - if (!Algorithms.isEmpty(resultJson)) { + if (!Algorithms.isEmpty(error)) { + status = STATUS_SERVER_ERROR; + message = "Download file list error: " + parseServerError(error); + } else if (!Algorithms.isEmpty(resultJson)) { try { JSONObject result = new JSONObject(resultJson); String totalZipSize = result.getString("totalZipSize"); @@ -425,6 +453,13 @@ public class BackupHelper { } } + @Override + public void onFileDownloadDone(@NonNull File file) { + if (listener != null) { + listener.onFileDownloaded(file); + } + } + @Override public void onFileDownloadedAsync(@NonNull File file) { if (listener != null) { @@ -435,7 +470,7 @@ public class BackupHelper { @Override public void onFilesDownloadDone(@NonNull Map errors) { if (listener != null) { - listener.onFilesDownloadDone(errors); + listener.onFilesDownloadDone(resolveServerErrors(errors)); } } }, EXECUTOR); @@ -523,6 +558,36 @@ public class BackupHelper { task.executeOnExecutor(EXECUTOR); } + private Map resolveServerErrors(@NonNull Map errors) { + Map resolvedErrors = new HashMap<>(); + for (Entry fileError : errors.entrySet()) { + File file = fileError.getKey(); + String errorStr = fileError.getValue(); + try { + JSONObject errorJson = new JSONObject(errorStr); + JSONObject error = errorJson.getJSONObject("error"); + errorStr = "Error " + error.getInt("errorCode") + " (" + error.getString("message") + ")"; + } catch (JSONException e) { + // ignore + } + resolvedErrors.put(file, errorStr); + } + return resolvedErrors; + } + + private String parseServerError(@NonNull String error) { + try { + JSONObject resultError = new JSONObject(error); + if (resultError.has("error")) { + JSONObject errorObj = resultError.getJSONObject("error"); + return errorObj.getInt("errorCode") + " (" + errorObj.getString("message") + ")"; + } + } catch (JSONException e) { + // ignore + } + return error; + } + @SuppressLint("StaticFieldLeak") public void generateBackupInfo(@NonNull final List localFiles, @NonNull final List remoteFiles, @Nullable final OnGenerateBackupInfoListener listener) { diff --git a/OsmAnd/src/net/osmand/plus/backup/BackupTask.java b/OsmAnd/src/net/osmand/plus/backup/BackupTask.java index 27a698f73d..ed8313a9ac 100644 --- a/OsmAnd/src/net/osmand/plus/backup/BackupTask.java +++ b/OsmAnd/src/net/osmand/plus/backup/BackupTask.java @@ -179,6 +179,11 @@ public class BackupTask { } } + @Override + public void onFileUploadDone(@NonNull File file) { + onTaskProgressDone(); + } + @Override public void onFilesUploadDone(@NonNull Map errors) { uploadErrors = errors; @@ -223,6 +228,11 @@ public class BackupTask { } } + @Override + public void onFileDownloaded(@NonNull File file) { + onTaskProgressDone(); + } + @Override public void onFileDownloadedAsync(@NonNull File file) { UserFile userFile = filesMap.get(file); @@ -299,7 +309,7 @@ public class BackupTask { progress.startTask((String) objects[0], -1); } else if (objects[0] instanceof Integer) { int progressValue = (Integer) objects[0]; - if (progressValue < Integer.MAX_VALUE) { + if (progressValue >= 0) { progress.progress(progressValue); } else { progress.finishTask(); @@ -312,6 +322,13 @@ public class BackupTask { } } + private void onTaskProgressDone() { + Context ctx = contextRef.get(); + if (ctx instanceof Activity && AndroidUtils.isActivityNotDestroyed((Activity) ctx) && progress != null) { + progress.finishTask(); + } + } + private void onError(@NonNull String message) { this.error = message; runningTasks.clear(); diff --git a/OsmAnd/src/net/osmand/plus/development/TestBackupActivity.java b/OsmAnd/src/net/osmand/plus/development/TestBackupActivity.java index 8f78c63151..54057adb72 100644 --- a/OsmAnd/src/net/osmand/plus/development/TestBackupActivity.java +++ b/OsmAnd/src/net/osmand/plus/development/TestBackupActivity.java @@ -137,6 +137,7 @@ public class TestBackupActivity extends OsmandActionBarActivity { a.buttonVerify.setVisibility(View.VISIBLE); a.buttonVerify.setEnabled(status == BackupHelper.STATUS_SUCCESS); a.tokenEditText.requestFocus(); + a.infoView.setText(message); } } }); @@ -162,10 +163,11 @@ public class TestBackupActivity extends OsmandActionBarActivity { a.progressBar.setVisibility(View.GONE); a.buttonVerify.setEnabled(status != BackupHelper.STATUS_SUCCESS); if (status == BackupHelper.STATUS_SUCCESS) { - tokenEdit.setVisibility(View.GONE); - buttonVerify.setVisibility(View.GONE); + a.tokenEdit.setVisibility(View.GONE); + a.buttonVerify.setVisibility(View.GONE); + a.prepareBackup(); } - a.prepareBackup(); + a.infoView.setText(message); } } }); @@ -196,15 +198,19 @@ public class TestBackupActivity extends OsmandActionBarActivity { String description; if (error != null) { description = error; - } else if (uploadErrors == null && downloadErrors == null) { + } else if (uploadErrors == null && deleteErrors == null) { description = "No data"; } else { description = getBackupErrorsDescription(uploadErrors, downloadErrors, deleteErrors, error); } a.infoView.setText(description); a.infoView.requestFocus(); - a.prepareBackup(); a.buttonBackup.setEnabled(true); + if (Algorithms.isEmpty(description)) { + a.prepareBackup(); + } else { + a.backupInfo = null; + } } } }); @@ -233,8 +239,12 @@ public class TestBackupActivity extends OsmandActionBarActivity { } a.infoView.setText(description); a.infoView.requestFocus(); - a.prepareBackup(); a.buttonRestore.setEnabled(true); + if (Algorithms.isEmpty(description)) { + a.prepareBackup(); + } else { + a.backupInfo = null; + } } } }); @@ -249,21 +259,21 @@ public class TestBackupActivity extends OsmandActionBarActivity { private String getBackupErrorsDescription(@Nullable Map uploadErrors, @Nullable Map downloadErrors, @Nullable Map deleteErrors, @Nullable String error) { StringBuilder sb = new StringBuilder(); if (!Algorithms.isEmpty(uploadErrors)) { - sb.append("--- Upload errors ---").append("\n"); + sb.append("--- Upload errors ---").append("\n\n"); for (Entry uploadEntry : uploadErrors.entrySet()) { - sb.append(uploadEntry.getKey().getName()).append(": ").append(uploadEntry.getValue()).append("\n"); + sb.append(uploadEntry.getKey().getName()).append(": ").append(uploadEntry.getValue()).append("\n\n"); } } if (!Algorithms.isEmpty(downloadErrors)) { - sb.append("--- Download errors ---").append("\n"); + sb.append("--- Download errors ---").append("\n\n"); for (Entry downloadEntry : downloadErrors.entrySet()) { - sb.append(downloadEntry.getKey().getName()).append(": ").append(downloadEntry.getValue()).append("\n"); + sb.append(downloadEntry.getKey().getName()).append(": ").append(downloadEntry.getValue()).append("\n\n"); } } if (!Algorithms.isEmpty(deleteErrors)) { - sb.append("--- Delete errors ---").append("\n"); + sb.append("--- Delete errors ---").append("\n\n"); for (Entry deleteEntry : deleteErrors.entrySet()) { - sb.append(deleteEntry.getKey().getName()).append(": ").append(deleteEntry.getValue()).append("\n"); + sb.append(deleteEntry.getKey().getName()).append(": ").append(deleteEntry.getValue()).append("\n\n"); } } return sb.length() == 0 ? "OK" : sb.toString(); @@ -272,32 +282,32 @@ public class TestBackupActivity extends OsmandActionBarActivity { private String getBackupDescription(@NonNull BackupInfo backupInfo) { StringBuilder sb = new StringBuilder(); if (!Algorithms.isEmpty(backupInfo.filesToUpload)) { - sb.append("\n").append("--- Upload ---").append("\n"); + sb.append("\n").append("--- Upload ---").append("\n\n"); for (GpxFileInfo info : backupInfo.filesToUpload) { sb.append(info.getFileName(true)) .append(" L: ").append(DF.format(new Date(info.getFileDate()))) .append(" U: ").append(DF.format(new Date(info.uploadTime))) - .append("\n"); + .append("\n\n"); } } if (!Algorithms.isEmpty(backupInfo.filesToDownload)) { - sb.append("\n").append("--- Download ---").append("\n"); + sb.append("\n").append("--- Download ---").append("\n\n"); for (UserFile userFile : backupInfo.filesToDownload) { sb.append(userFile.getName()) .append(" R: ").append(DF.format(new Date(userFile.getClienttimems()))) - .append("\n"); + .append("\n\n"); } } if (!Algorithms.isEmpty(backupInfo.filesToDelete)) { - sb.append("\n").append("--- Delete ---").append("\n"); + sb.append("\n").append("--- Delete ---").append("\n\n"); for (UserFile userFile : backupInfo.filesToDelete) { sb.append(userFile.getName()) .append(" R: ").append(DF.format(new Date(userFile.getClienttimems()))) - .append("\n"); + .append("\n\n"); } } if (!Algorithms.isEmpty(backupInfo.filesToMerge)) { - sb.append("\n").append("--- Conflicts ---").append("\n"); + sb.append("\n").append("--- Conflicts ---").append("\n\n"); for (Pair localRemote : backupInfo.filesToMerge) { GpxFileInfo local = localRemote.first; UserFile remote = localRemote.second; @@ -305,7 +315,7 @@ public class TestBackupActivity extends OsmandActionBarActivity { .append(" L: ").append(DF.format(new Date(local.getFileDate()))) .append(" U: ").append(DF.format(new Date(local.uploadTime))) .append(" R: ").append(DF.format(new Date(remote.getClienttimems()))) - .append("\n"); + .append("\n\n"); } } return sb.toString(); diff --git a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java index 8a54df1b86..a9d53da11e 100644 --- a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java +++ b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java @@ -8,6 +8,9 @@ import android.os.AsyncTask; import android.text.TextUtils; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import net.osmand.AndroidNetworkUtils; import net.osmand.AndroidNetworkUtils.OnRequestResultListener; import net.osmand.AndroidNetworkUtils.OnSendRequestsListener; @@ -41,9 +44,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - public abstract class InAppPurchaseHelper { // Debug tag, for logging protected static final org.apache.commons.logging.Log LOG = PlatformUtil.getLog(InAppPurchaseHelper.class); @@ -466,7 +466,7 @@ public abstract class InAppPurchaseHelper { protected void onSkuDetailsResponseDone(List purchaseInfoList) { final AndroidNetworkUtils.OnRequestResultListener listener = new AndroidNetworkUtils.OnRequestResultListener() { @Override - public void onResult(String result) { + public void onResult(@Nullable String result, @Nullable String error) { notifyDismissProgress(InAppPurchaseTaskType.REQUEST_INVENTORY); notifyGetItems(); stop(true); @@ -477,7 +477,7 @@ public abstract class InAppPurchaseHelper { if (purchaseInfoList.size() > 0) { sendTokens(purchaseInfoList, listener); } else { - listener.onResult("OK"); + listener.onResult("OK", null); } } @@ -503,7 +503,7 @@ public abstract class InAppPurchaseHelper { liveUpdatesPurchase.setState(ctx, SubscriptionState.UNDEFINED); sendTokens(Collections.singletonList(info), new OnRequestResultListener() { @Override - public void onResult(String result) { + public void onResult(@Nullable String result, @Nullable String error) { boolean active = ctx.getSettings().LIVE_UPDATES_PURCHASED.get(); ctx.getSettings().LIVE_UPDATES_PURCHASED.set(true); ctx.getSettings().getCustomRenderBooleanProperty("depthContours").set(true); @@ -642,7 +642,7 @@ public abstract class InAppPurchaseHelper { } } if (listener != null) { - listener.onResult("OK"); + listener.onResult("OK", null); } } @@ -695,7 +695,7 @@ public abstract class InAppPurchaseHelper { } catch (Exception e) { logError("SendToken Error", e); if (listener != null) { - listener.onResult("Error"); + listener.onResult("Error", null); } } } diff --git a/OsmAnd/src/net/osmand/plus/liveupdates/PerformLiveUpdateAsyncTask.java b/OsmAnd/src/net/osmand/plus/liveupdates/PerformLiveUpdateAsyncTask.java index 9729cd9b3a..1386101792 100644 --- a/OsmAnd/src/net/osmand/plus/liveupdates/PerformLiveUpdateAsyncTask.java +++ b/OsmAnd/src/net/osmand/plus/liveupdates/PerformLiveUpdateAsyncTask.java @@ -6,6 +6,7 @@ import android.content.Context; import android.os.AsyncTask; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import net.osmand.AndroidNetworkUtils; import net.osmand.AndroidNetworkUtils.OnRequestResultListener; @@ -194,7 +195,7 @@ public class PerformLiveUpdateAsyncTask AndroidNetworkUtils.sendRequestAsync( app, LiveUpdatesFragment.URL, null, "Requesting map updates info...", false, false, new OnRequestResultListener() { @Override - public void onResult(String result) { + public void onResult(@Nullable String result, @Nullable String error) { if (!Algorithms.isEmpty(result)) { SimpleDateFormat source = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US); source.setTimeZone(TimeZone.getTimeZone("UTC")); diff --git a/OsmAnd/src/net/osmand/plus/liveupdates/SubscriptionFragment.java b/OsmAnd/src/net/osmand/plus/liveupdates/SubscriptionFragment.java index ba8990f64f..099a23282d 100644 --- a/OsmAnd/src/net/osmand/plus/liveupdates/SubscriptionFragment.java +++ b/OsmAnd/src/net/osmand/plus/liveupdates/SubscriptionFragment.java @@ -208,7 +208,7 @@ public class SubscriptionFragment extends BaseOsmAndDialogFragment implements In "https://osmand.net/subscription/update", parameters, "Sending data...", true, true, new AndroidNetworkUtils.OnRequestResultListener() { @Override - public void onResult(String result) { + public void onResult(@Nullable String result, @Nullable String error) { dismissProgress(null); OsmandApplication app = getMyApplication(); if (result != null) { diff --git a/OsmAnd/src/net/osmand/plus/search/SendSearchQueryBottomSheet.java b/OsmAnd/src/net/osmand/plus/search/SendSearchQueryBottomSheet.java index 39e91503ec..375cfffd20 100644 --- a/OsmAnd/src/net/osmand/plus/search/SendSearchQueryBottomSheet.java +++ b/OsmAnd/src/net/osmand/plus/search/SendSearchQueryBottomSheet.java @@ -6,6 +6,8 @@ import android.view.View; import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.Nullable; + import net.osmand.AndroidNetworkUtils; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; @@ -71,7 +73,7 @@ public class SendSearchQueryBottomSheet extends MenuBottomSheetDialogFragment { AndroidNetworkUtils.sendRequestAsync(app, "https://osmand.net/api/missing_search", params, null, true, true, new AndroidNetworkUtils.OnRequestResultListener() { @Override - public void onResult(String result) { + public void onResult(@Nullable String result, @Nullable String error) { if (result != null && isAdded()) { try { JSONObject obj = new JSONObject(result);