From 55c776c9b6a7ee6df7f955547204ea0a1f86b1a9 Mon Sep 17 00:00:00 2001 From: simonpoole Date: Wed, 13 Dec 2023 13:17:40 +0100 Subject: [PATCH 1/2] Import MSF files on Android 10+ and correctness fixes This adds importing MSF files on Android 10 and later, including displaying a tip the 1st time a file is "imported". Fixes https://github.com/MarcusWolschon/osmeditor4android/issues/2455 This further corrects the behaviour when files are selected and the initial activity is paused and potentially destroyed by using the restored activity and using the fragment manager to find fragments that need to be updated from the imported data. --- src/main/java/de/blau/android/Main.java | 54 ++-- .../blau/android/dialogs/ConsoleDialog.java | 26 +- .../java/de/blau/android/dialogs/Layers.java | 13 +- .../de/blau/android/layer/mvt/MapOverlay.java | 12 +- .../blau/android/prefs/APIEditorActivity.java | 301 +++++++++++------- .../android/prefs/PrefEditorActivity.java | 2 +- .../android/prefs/PresetEditorActivity.java | 201 +++++++----- .../android/prefs/URLListEditActivity.java | 6 +- .../PropertyEditorFragment.java | 2 +- .../android/resources/TileLayerDialog.java | 117 +++---- .../de/blau/android/util/DatabaseUtil.java | 3 +- .../java/de/blau/android/util/FileUtil.java | 54 ++++ .../de/blau/android/util/FragmentUtil.java | 63 ++++ .../java/de/blau/android/util/ReadFile.java | 9 +- .../java/de/blau/android/util/SaveFile.java | 5 +- .../java/de/blau/android/util/SelectFile.java | 28 +- src/main/res/values/strings.xml | 1 + src/main/res/values/tipkeys.xml | 1 + 18 files changed, 555 insertions(+), 343 deletions(-) create mode 100644 src/main/java/de/blau/android/util/FragmentUtil.java diff --git a/src/main/java/de/blau/android/Main.java b/src/main/java/de/blau/android/Main.java index 41fce75191..872939c2cf 100644 --- a/src/main/java/de/blau/android/Main.java +++ b/src/main/java/de/blau/android/Main.java @@ -2288,8 +2288,8 @@ protected void onPostExecute(Void result) { private static final long serialVersionUID = 1L; @Override - public boolean save(Uri fileUri) { - SavingHelper.asyncExport(Main.this, delegator, fileUri); + public boolean save(FragmentActivity currentActivity, Uri fileUri) { + SavingHelper.asyncExport(currentActivity, delegator, fileUri); SelectFile.savePref(prefs, R.string.config_osmPreferredDir_key, fileUri); return true; } @@ -2301,9 +2301,9 @@ public boolean save(Uri fileUri) { private static final long serialVersionUID = 1L; @Override - public boolean read(Uri fileUri) { + public boolean read(FragmentActivity currentActivity, Uri fileUri) { try { - logic.applyOscFile(Main.this, fileUri, new PostFileReadCallback(Main.this, fileUri.toString())); + logic.applyOscFile(currentActivity, fileUri, new PostFileReadCallback(currentActivity, fileUri.toString())); } catch (FileNotFoundException e) { fileNotFound(fileUri); } @@ -2320,12 +2320,12 @@ public boolean read(Uri fileUri) { private static final long serialVersionUID = 1L; @Override - public boolean read(Uri fileUri) { + public boolean read(FragmentActivity currentActivity, Uri fileUri) { try { if (item.getItemId() == R.id.menu_transfer_read_file) { - logic.readOsmFile(Main.this, fileUri, false); + logic.readOsmFile(currentActivity, fileUri, false); } else { - logic.readPbfFile(Main.this, fileUri, false); + logic.readPbfFile(currentActivity, fileUri, false); } } catch (FileNotFoundException e) { fileNotFound(fileUri); @@ -2347,8 +2347,8 @@ public boolean read(Uri fileUri) { private static final long serialVersionUID = 1L; @Override - public boolean save(Uri fileUri) { - App.getLogic().writeOsmFile(Main.this, fileUri, new PostFileWriteCallback(Main.this, fileUri.getPath())); + public boolean save(FragmentActivity currentActivity, Uri fileUri) { + App.getLogic().writeOsmFile(currentActivity, fileUri, new PostFileWriteCallback(currentActivity, fileUri.getPath())); SelectFile.savePref(prefs, R.string.config_osmPreferredDir_key, fileUri); return true; } @@ -2392,9 +2392,9 @@ public boolean save(Uri fileUri) { private static final long serialVersionUID = 1L; @Override - public boolean save(Uri fileUri) { - TransferTasks.writeOsnFile(Main.this, item.getItemId() == R.id.menu_transfer_save_notes_all, fileUri, - new PostFileWriteCallback(Main.this, fileUri.toString())); + public boolean save(FragmentActivity currentActivity, Uri fileUri) { + TransferTasks.writeOsnFile(currentActivity, item.getItemId() == R.id.menu_transfer_save_notes_all, fileUri, + new PostFileWriteCallback(currentActivity, fileUri.toString())); SelectFile.savePref(prefs, R.string.config_notesPreferredDir_key, fileUri); return true; } @@ -2406,8 +2406,8 @@ public boolean save(Uri fileUri) { private static final long serialVersionUID = 1L; @Override - public boolean read(Uri fileUri) { - TransferTasks.readOsnFile(Main.this, fileUri, true, new PostFileReadCallback(Main.this, fileUri.toString())); + public boolean read(FragmentActivity currentActivity, Uri fileUri) { + TransferTasks.readOsnFile(currentActivity, fileUri, true, new PostFileReadCallback(currentActivity, fileUri.toString())); SelectFile.savePref(prefs, R.string.config_osmPreferredDir_key, fileUri); map.invalidate(); return true; @@ -2420,8 +2420,8 @@ public boolean read(Uri fileUri) { private static final long serialVersionUID = 1L; @Override - public boolean read(Uri fileUri) { - TransferTasks.readTodos(Main.this, fileUri, false, new PostFileReadCallback(Main.this, fileUri.toString())); + public boolean read(FragmentActivity currentActivity, Uri fileUri) { + TransferTasks.readTodos(currentActivity, fileUri, false, new PostFileReadCallback(currentActivity, fileUri.toString())); SelectFile.savePref(prefs, R.string.config_osmPreferredDir_key, fileUri); map.invalidate(); return true; @@ -2488,9 +2488,9 @@ public boolean read(Uri fileUri) { private static final long serialVersionUID = 1L; @Override - public boolean read(Uri fileUri) { - try (KeyDatabaseHelper keyDatabase = new KeyDatabaseHelper(Main.this)) { - keyDatabase.keysFromStream(Main.this, Main.this.getContentResolver().openInputStream(fileUri)); + public boolean read(FragmentActivity currentActivity, Uri fileUri) { + try (KeyDatabaseHelper keyDatabase = new KeyDatabaseHelper(currentActivity)) { + keyDatabase.keysFromStream(currentActivity, currentActivity.getContentResolver().openInputStream(fileUri)); SelectFile.savePref(prefs, R.string.config_osmPreferredDir_key, fileUri); } catch (FileNotFoundException fex) { fileNotFound(fileUri); @@ -2505,10 +2505,10 @@ public boolean read(Uri fileUri) { private static final long serialVersionUID = 1L; @Override - public boolean read(Uri fileUri) { - try (InputStream in = Main.this.getContentResolver().openInputStream(fileUri)) { - File destDir = FileUtil.getApplicationDirectory(Main.this, Paths.DIRECTORY_PATH_STYLES); - String filename = ContentResolverUtil.getDisplaynameColumn(Main.this, fileUri); + public boolean read(FragmentActivity currentActivity, Uri fileUri) { + try (InputStream in = currentActivity.getContentResolver().openInputStream(fileUri)) { + File destDir = FileUtil.getApplicationDirectory(currentActivity, Paths.DIRECTORY_PATH_STYLES); + String filename = ContentResolverUtil.getDisplaynameColumn(currentActivity, fileUri); File dest = new File(destDir, filename); FileUtil.copy(in, dest); if (filename.toLowerCase(Locale.US).endsWith("." + FileExtensions.ZIP)) { @@ -2516,7 +2516,7 @@ public boolean read(Uri fileUri) { dest.delete(); // NOSONAR delete the zip file } DataStyle.reset(); - DataStyle.getStylesFromFiles(Main.this); + DataStyle.getStylesFromFiles(currentActivity); SelectFile.savePref(prefs, R.string.config_osmPreferredDir_key, fileUri); } catch (IOException fex) { fileNotFound(fileUri); @@ -2666,8 +2666,8 @@ private void writeTodos(@Nullable String listName) { private static final long serialVersionUID = 1L; @Override - public boolean save(Uri fileUri) { - TransferTasks.writeTodoFile(Main.this, fileUri, listName, true, null); + public boolean save(FragmentActivity currentActivity, Uri fileUri) { + TransferTasks.writeTodoFile(currentActivity, fileUri, listName, true, null); SelectFile.savePref(prefs, R.string.config_osmPreferredDir_key, fileUri); return true; } @@ -3060,7 +3060,7 @@ protected void onActivityResult(final int requestCode, final int resultCode, fin if (requestCode == REQUEST_EDIT_TAG && resultCode == RESULT_OK) { handlePropertyEditorResult(); } else if ((requestCode == SelectFile.READ_FILE || requestCode == SelectFile.SAVE_FILE) && resultCode == RESULT_OK) { - SelectFile.handleResult(requestCode, data); + SelectFile.handleResult(this, requestCode, data); } else { ActivityResultHandler.Listener listener = activityResultListeners.get(requestCode); if (listener != null) { diff --git a/src/main/java/de/blau/android/dialogs/ConsoleDialog.java b/src/main/java/de/blau/android/dialogs/ConsoleDialog.java index 44bf70aa21..e7d02bebde 100644 --- a/src/main/java/de/blau/android/dialogs/ConsoleDialog.java +++ b/src/main/java/de/blau/android/dialogs/ConsoleDialog.java @@ -58,6 +58,8 @@ public class ConsoleDialog extends DialogFragment { private static final String CHECKBOX1_KEY = "checkbox1"; private static final String CHECKBOX2_KEY = "checkbox2"; + private EditText input; + /** * Show an info dialog for the supplied OsmElement * @@ -136,7 +138,7 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { final LayoutInflater inflater = ThemeUtils.getLayoutInflater(activity); View v = inflater.inflate(R.layout.console, null); - final EditText input = (EditText) v.findViewById(R.id.input); + input = (EditText) v.findViewById(R.id.input); final TextView output = (TextView) v.findViewById(R.id.output); final CheckBox checkbox1 = (CheckBox) v.findViewById(R.id.checkbox1); final CheckBox checkbox2 = (CheckBox) v.findViewById(R.id.checkbox2); @@ -224,13 +226,19 @@ private OnMenuItemClickListener getOnItemClickListener(@NonNull final Preference private static final long serialVersionUID = 1L; @Override - public boolean save(Uri fileUri) { - fileUri = FileUtil.contentUriToFileUri(activity, fileUri); + public boolean save(FragmentActivity currentActivity, Uri fileUri) { + FragmentManager fm = currentActivity.getSupportFragmentManager(); + ConsoleDialog fragment = (ConsoleDialog) fm.findFragmentByTag(TAG); + if (fragment == null) { + Log.e(DEBUG_TAG, "Restored fragment is null"); + return false; + } + fileUri = FileUtil.contentUriToFileUri(currentActivity, fileUri); if (fileUri == null) { Log.e(DEBUG_TAG, "Couldn't convert " + fileUri); return false; } - writeScriptFile(activity, fileUri, input.getText().toString(), null); + writeScriptFile(currentActivity, fileUri, fragment.input.getText().toString(), null); SelectFile.savePref(prefs, R.string.config_scriptsPreferredDir_key, fileUri); return true; } @@ -241,8 +249,14 @@ public boolean save(Uri fileUri) { private static final long serialVersionUID = 1L; @Override - public boolean read(Uri fileUri) { - readScriptFile(activity, fileUri, input, null); + public boolean read(FragmentActivity currentActivity, Uri fileUri) { + FragmentManager fm = currentActivity.getSupportFragmentManager(); + ConsoleDialog fragment = (ConsoleDialog) fm.findFragmentByTag(TAG); + if (fragment == null) { + Log.e(DEBUG_TAG, "Restored fragment is null"); + return false; + } + readScriptFile(currentActivity, fileUri, fragment.input, null); SelectFile.savePref(prefs, R.string.config_scriptsPreferredDir_key, fileUri); return true; } diff --git a/src/main/java/de/blau/android/dialogs/Layers.java b/src/main/java/de/blau/android/dialogs/Layers.java index 182cdea599..8b7e207e07 100644 --- a/src/main/java/de/blau/android/dialogs/Layers.java +++ b/src/main/java/de/blau/android/dialogs/Layers.java @@ -119,7 +119,7 @@ public class Layers extends AbstractConfigurationDialog implements OnUpdateListe private static final int VERTICAL_OFFSET = 64; private static final long MAX_STYLE_FILE_SIZE = 10000000L; - private static final String TAG = "fragment_layers"; + public static final String TAG = "fragment_layers"; private int visibleId; private int invisibleId; @@ -410,13 +410,13 @@ private void addStyleableLayerFromFile(final FragmentActivity activity, final Pr private static final long serialVersionUID = 1L; @Override - public boolean read(Uri fileUri) { + public boolean read(FragmentActivity activity, Uri fileUri) { addStyleableLayerFromUri(activity, prefs, map, type, fileUri, true); return true; } @Override - public void read(List fileUris) { + public void read(FragmentActivity activity, List fileUris) { for (Uri fileUri : fileUris) { addStyleableLayerFromUri(activity, prefs, map, type, fileUri, false); } @@ -471,7 +471,7 @@ private void addMVTLayerFromStyle(@NonNull final FragmentActivity activity, @Non private static final long serialVersionUID = 1L; @Override - public boolean read(Uri fileUri) { + public boolean read(FragmentActivity activity, Uri fileUri) { Style style = new Style(); try { if (ContentResolverUtil.getSizeColumn(activity, fileUri) > MAX_STYLE_FILE_SIZE) { @@ -989,11 +989,12 @@ protected void onPostExecute(Void result) { private static final long serialVersionUID = 1L; @Override - public boolean save(Uri fileUri) { + public boolean save(FragmentActivity currentActivity, Uri fileUri) { + // FIXME layer will likely not be valid if the activity has been recreated if (layer != null) { final Track track = ((de.blau.android.layer.gpx.MapOverlay) layer).getTrack(); if (track != null) { - SavingHelper.asyncExport(activity, track, fileUri); + SavingHelper.asyncExport(currentActivity, track, fileUri); SelectFile.savePref(App.getLogic().getPrefs(), R.string.config_osmPreferredDir_key, fileUri); } } diff --git a/src/main/java/de/blau/android/layer/mvt/MapOverlay.java b/src/main/java/de/blau/android/layer/mvt/MapOverlay.java index bf7e04618e..faf02b2139 100644 --- a/src/main/java/de/blau/android/layer/mvt/MapOverlay.java +++ b/src/main/java/de/blau/android/layer/mvt/MapOverlay.java @@ -524,10 +524,10 @@ public void loadStyleFromFile(@NonNull FragmentActivity activity) throws IOExcep private static final long serialVersionUID = 1L; @Override - public boolean read(Uri fileUri) { - try (InputStream is = activity.getContentResolver().openInputStream(fileUri)) { + public boolean read(FragmentActivity currentActivity, Uri fileUri) { + try (InputStream is = currentActivity.getContentResolver().openInputStream(fileUri)) { Style style = new Style(); - style.loadStyle(activity, is); + style.loadStyle(currentActivity, is); setStyle(style); flushTileCache(); dirty = true; @@ -536,12 +536,12 @@ public boolean read(Uri fileUri) { } catch (SecurityException sex) { Log.e(DEBUG_TAG, sex.getMessage()); // note need a context here that is on the ui thread - ScreenMessage.toastTopError(map.getContext(), activity.getString(R.string.toast_permission_denied, fileUri.toString())); + ScreenMessage.toastTopError(currentActivity, currentActivity.getString(R.string.toast_permission_denied, fileUri.toString())); return false; } catch (FileNotFoundException e) { - ScreenMessage.toastTopError(map.getContext(), activity.getString(R.string.toast_file_not_found, fileUri.toString())); + ScreenMessage.toastTopError(currentActivity, currentActivity.getString(R.string.toast_file_not_found, fileUri.toString())); } catch (IOException e) { - ScreenMessage.toastTopError(map.getContext(), activity.getString(R.string.toast_error_reading, fileUri.toString())); + ScreenMessage.toastTopError(currentActivity, currentActivity.getString(R.string.toast_error_reading, fileUri.toString())); } return true; } diff --git a/src/main/java/de/blau/android/prefs/APIEditorActivity.java b/src/main/java/de/blau/android/prefs/APIEditorActivity.java index 6d11db7df5..3d58ee73d9 100644 --- a/src/main/java/de/blau/android/prefs/APIEditorActivity.java +++ b/src/main/java/de/blau/android/prefs/APIEditorActivity.java @@ -1,13 +1,17 @@ package de.blau.android.prefs; +import java.io.File; +import java.io.IOException; import java.util.List; import java.util.Map.Entry; import android.app.Activity; +import android.app.Dialog; import android.content.DialogInterface; import android.content.Intent; import android.database.sqlite.SQLiteException; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.text.InputType; import android.util.Log; @@ -24,18 +28,25 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatDialog; import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; import de.blau.android.App; import de.blau.android.Logic; import de.blau.android.Main; import de.blau.android.R; +import de.blau.android.contract.Paths; import de.blau.android.dialogs.DataLoss; +import de.blau.android.util.ContentResolverUtil; import de.blau.android.util.DatabaseUtil; import de.blau.android.util.FileUtil; +import de.blau.android.util.FragmentUtil; +import de.blau.android.util.ImmersiveDialogFragment; import de.blau.android.util.ReadFile; import de.blau.android.util.SelectFile; import de.blau.android.util.ScreenMessage; import de.blau.android.util.ThemeUtils; +import de.blau.android.util.Util; /** Provides an activity for editing the API list */ public class APIEditorActivity extends URLListEditActivity { @@ -156,18 +167,15 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuIn @Override public void onAdditionalMenuItemClick(int menuItemId, ListEditItem clickedItem) { - switch (menuItemId) { - case MENU_COPY: + if (menuItemId == MENU_COPY) { ListEditItem item = new ListEditItem(getString(R.string.copy_of, clickedItem.name), clickedItem.value, clickedItem.value2, clickedItem.value3, clickedItem.boolean0); db.addAPI(item.id, item.name, item.value, item.value2, item.value3, "", "", item.boolean0); items.clear(); onLoadList(items); updateAdapter(); - break; - default: + } else { Log.e(DEBUG_TAG, "Unknown menu item " + menuItemId); - break; } } @@ -178,126 +186,187 @@ public void onAdditionalMenuItemClick(int menuItemId, ListEditItem clickedItem) */ @Override protected void itemEditDialog(final ListEditItem item) { - final AlertDialog.Builder builder = new AlertDialog.Builder(ctx); - final LayoutInflater inflater = ThemeUtils.getLayoutInflater(ctx); - final View mainView = inflater.inflate(R.layout.listedit_apiedit, null); - final TextView editName = (TextView) mainView.findViewById(R.id.listedit_editName); - final TextView editValue = (TextView) mainView.findViewById(R.id.listedit_editValue); - final TextView editValue2 = (TextView) mainView.findViewById(R.id.listedit_editValue_2); - final TextView editValue3 = (TextView) mainView.findViewById(R.id.listedit_editValue_3); - final CheckBox oauth = (CheckBox) mainView.findViewById(R.id.listedit_oauth); - final ImageButton fileButton = (ImageButton) mainView.findViewById(R.id.listedit_file_button); - - if (item != null) { - editName.setText(item.name); - editValue.setText(item.value); - editValue2.setText(item.value2); - editValue3.setText(item.value3); - oauth.setChecked(item.boolean0); - } else if (isAddingViaIntent()) { - String tmpName = getIntent().getExtras().getString(EXTRA_NAME); - String tmpValue = getIntent().getExtras().getString(EXTRA_VALUE); - editName.setText(tmpName == null ? "" : tmpName); - editValue.setText(tmpValue == null ? "" : tmpValue); - oauth.setChecked(false); - } - if (item != null && item.id.equals(LISTITEM_ID_DEFAULT)) { - // name and value are not editable - editName.setInputType(InputType.TYPE_NULL); - editName.setBackground(null); - editValue.setBackground(null); - editValue.setInputType(InputType.TYPE_NULL); - editValue2.setEnabled(true); - editValue3.setEnabled(false); - } + Bundle args = new Bundle(); + args.putSerializable(ApiItemEditDialog.ITEM_KEY, item); + FragmentManager fm = getSupportFragmentManager(); + ApiItemEditDialog f = new ApiItemEditDialog(); + f.setArguments(args); + f.setShowsDialog(true); + f.show(fm, ApiItemEditDialog.ITEM_EDIT_DIALOG_TAG); + } - setViewAndButtons(builder, mainView); + public static class ApiItemEditDialog extends ImmersiveDialogFragment { - fileButton.setOnClickListener(view -> SelectFile.read(APIEditorActivity.this, R.string.config_msfPreferredDir_key, new ReadFile() { - private static final long serialVersionUID = 1L; + private static final String ITEM_EDIT_DIALOG_TAG = "api_item_edit_dialog"; + static final String ITEM_KEY = "item"; - @Override - public boolean read(Uri uri) { - Uri fileUri = FileUtil.contentUriToFileUri(APIEditorActivity.this, uri); - if (fileUri == null) { - ScreenMessage.toastTopError(APIEditorActivity.this, R.string.not_found_title); - return false; - } - try { - if (!DatabaseUtil.isValidSQLite(fileUri.getPath())) { - throw new SQLiteException("Not a SQLite database file"); - } - editValue2.setText(fileUri.toString()); - SelectFile.savePref(new Preferences(APIEditorActivity.this), R.string.config_msfPreferredDir_key, fileUri); - return true; - } catch (SQLiteException sqex) { - ScreenMessage.toastTopError(APIEditorActivity.this, R.string.toast_not_mbtiles); - return false; - } - } - })); - - final AlertDialog dialog = builder.create(); - dialog.show(); - - // overriding the handlers - dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { - boolean validAPIURL = true; - boolean validReadOnlyAPIURL = true; - boolean validNotesAPIURL = true; - String name = editName.getText().toString().trim(); - String apiURL = editValue.getText().toString().trim(); - String readOnlyAPIURL = editValue2.getText().toString().trim(); - String notesAPIURL = editValue3.getText().toString().trim(); - boolean enabled = oauth.isChecked(); - - // (re-)set to black - changeBackgroundColor(editValue, VALID_COLOR); - changeBackgroundColor(editValue2, VALID_COLOR); - changeBackgroundColor(editValue3, VALID_COLOR); - - // validate entries - validAPIURL = Patterns.WEB_URL.matcher(apiURL).matches(); - if (!"".equals(readOnlyAPIURL)) { - validReadOnlyAPIURL = Patterns.WEB_URL.matcher(readOnlyAPIURL).matches() || readOnlyAPIURL.startsWith(FileUtil.FILE_SCHEME_PREFIX); - } else { - readOnlyAPIURL = null; + @Override + public AppCompatDialog onCreateDialog(Bundle savedInstanceState) { + ListEditItem item = Util.getSerializeable(getArguments(), ITEM_KEY, ListEditItem.class); + + final AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + final LayoutInflater inflater = ThemeUtils.getLayoutInflater(getContext()); + final View mainView = inflater.inflate(R.layout.listedit_apiedit, null); + final TextView editName = (TextView) mainView.findViewById(R.id.listedit_editName); + final TextView editValue = (TextView) mainView.findViewById(R.id.listedit_editValue); + final TextView editValue2 = (TextView) mainView.findViewById(R.id.listedit_editValue_2); + final TextView editValue3 = (TextView) mainView.findViewById(R.id.listedit_editValue_3); + final CheckBox oauth = (CheckBox) mainView.findViewById(R.id.listedit_oauth); + final ImageButton fileButton = (ImageButton) mainView.findViewById(R.id.listedit_file_button); + + final URLListEditActivity activity = (URLListEditActivity) getActivity(); + if (item != null) { + editName.setText(item.name); + editValue.setText(item.value); + editValue2.setText(item.value2); + editValue3.setText(item.value3); + oauth.setChecked(item.boolean0); + } else if (activity.isAddingViaIntent()) { + String tmpName = activity.getIntent().getExtras().getString(EXTRA_NAME); + String tmpValue = activity.getIntent().getExtras().getString(EXTRA_VALUE); + editName.setText(tmpName == null ? "" : tmpName); + editValue.setText(tmpValue == null ? "" : tmpValue); + oauth.setChecked(false); } - if (!"".equals(notesAPIURL)) { - validNotesAPIURL = Patterns.WEB_URL.matcher(notesAPIURL).matches(); - } else { - notesAPIURL = null; + if (item != null && item.id.equals(LISTITEM_ID_DEFAULT)) { + // name and value are not editable + editName.setInputType(InputType.TYPE_NULL); + editName.setBackground(null); + editValue.setBackground(null); + editValue.setInputType(InputType.TYPE_NULL); + editValue2.setEnabled(true); + editValue3.setEnabled(false); } - // save or display toast - if (validAPIURL && validNotesAPIURL && validReadOnlyAPIURL) { // check if fields valid, optional ones - // checked if values entered - if (!"".equals(apiURL)) { - if (item == null) { - // new item - finishCreateItem(new ListEditItem(name, apiURL, readOnlyAPIURL, notesAPIURL, enabled)); + activity.setViewAndButtons(builder, mainView); + + fileButton.setOnClickListener(view -> SelectFile.read(activity, R.string.config_msfPreferredDir_key, new ReadFile() { + private static final long serialVersionUID = 1L; + + @Override + public boolean read(FragmentActivity currentActivity, Uri uri) { + final Dialog dialog = FragmentUtil.findDialogByTag(currentActivity, ITEM_EDIT_DIALOG_TAG); + if (dialog == null) { + Log.e(DEBUG_TAG, "Dialog is null"); + return false; + } + final TextView editValue2 = (TextView) dialog.findViewById(R.id.listedit_editValue_2); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // copy file + String fileName = ContentResolverUtil.getDisplaynameColumn(currentActivity, uri); + if (fileName == null) { + ScreenMessage.toastTopError(currentActivity, R.string.not_found_title); + return false; + } + try { + final File destination = new File(FileUtil.getPublicDirectory(FileUtil.getPublicDirectory(), Paths.DIRECTORY_PATH_IMPORTS), + fileName); + FileUtil.importFile(currentActivity, uri, destination, + () -> setFileUri(editValue2, Uri.parse(FileUtil.FILE_SCHEME_PREFIX + destination.getAbsolutePath()))); + return true; + } catch (IOException ex) { + return false; + } } else { - item.name = name; - item.value = apiURL; - item.value2 = readOnlyAPIURL; - item.value3 = notesAPIURL; - item.boolean0 = enabled; - finishEditItem(item); + Uri fileUri = FileUtil.contentUriToFileUri(currentActivity, uri); + if (fileUri == null) { + ScreenMessage.toastTopError(currentActivity, R.string.not_found_title); + return false; + } + SelectFile.savePref(new Preferences(currentActivity), R.string.config_msfPreferredDir_key, fileUri); + return setFileUri(editValue2, fileUri); } } - dialog.dismiss(); - } else if (!validAPIURL) { // if garbage value entered show toasts - ScreenMessage.barError(APIEditorActivity.this, R.string.toast_invalid_apiurl); - changeBackgroundColor(editValue, ERROR_COLOR); - } else if (!validReadOnlyAPIURL) { - ScreenMessage.barError(APIEditorActivity.this, R.string.toast_invalid_readonlyurl); - changeBackgroundColor(editValue2, ERROR_COLOR); - } else if (!validNotesAPIURL) { - ScreenMessage.barError(APIEditorActivity.this, R.string.toast_invalid_notesurl); - changeBackgroundColor(editValue3, ERROR_COLOR); - } - }); - dialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener(v -> dialog.dismiss()); + /** + * Set the TextView displaying the Uri + * + * @param textView the TextView + * @param fileUri the Uri + * @return true if successful + */ + private boolean setFileUri(@NonNull final TextView textView, @NonNull Uri fileUri) { + Log.d(DEBUG_TAG, "setFileUri " + fileUri); + try { + if (!DatabaseUtil.isValidSQLite(fileUri.getPath())) { + throw new SQLiteException("Not a SQLite database file"); + } + textView.setText(fileUri.toString()); + return true; + } catch (SQLiteException sqex) { + ScreenMessage.toastTopError(getActivity(), R.string.toast_not_mbtiles); + return false; + } + } + })); + + final AlertDialog dialog = builder.create(); + + // overriding the handlers + dialog.setOnShowListener((DialogInterface d) -> { + dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { + + boolean validAPIURL = true; + boolean validReadOnlyAPIURL = true; + boolean validNotesAPIURL = true; + String name = editName.getText().toString().trim(); + String apiURL = editValue.getText().toString().trim(); + String readOnlyAPIURL = editValue2.getText().toString().trim(); + String notesAPIURL = editValue3.getText().toString().trim(); + boolean enabled = oauth.isChecked(); + + // (re-)set to black + changeBackgroundColor(editValue, VALID_COLOR); + changeBackgroundColor(editValue2, VALID_COLOR); + changeBackgroundColor(editValue3, VALID_COLOR); + + // validate entries + validAPIURL = Patterns.WEB_URL.matcher(apiURL).matches(); + if (!"".equals(readOnlyAPIURL)) { + validReadOnlyAPIURL = Patterns.WEB_URL.matcher(readOnlyAPIURL).matches() || readOnlyAPIURL.startsWith(FileUtil.FILE_SCHEME_PREFIX); + } else { + readOnlyAPIURL = null; + } + if (!"".equals(notesAPIURL)) { + validNotesAPIURL = Patterns.WEB_URL.matcher(notesAPIURL).matches(); + } else { + notesAPIURL = null; + } + + // save or display toast + if (validAPIURL && validNotesAPIURL && validReadOnlyAPIURL) { // check if fields valid, optional + // ones + // checked if values entered + if (!"".equals(apiURL)) { + if (item == null) { + // new item + activity.finishCreateItem(new ListEditItem(name, apiURL, readOnlyAPIURL, notesAPIURL, enabled)); + } else { + item.name = name; + item.value = apiURL; + item.value2 = readOnlyAPIURL; + item.value3 = notesAPIURL; + item.boolean0 = enabled; + activity.finishEditItem(item); + } + } + dialog.dismiss(); + } else if (!validAPIURL) { // if garbage value entered show toasts + ScreenMessage.barError(activity, R.string.toast_invalid_apiurl); + changeBackgroundColor(editValue, ERROR_COLOR); + } else if (!validReadOnlyAPIURL) { + ScreenMessage.barError(activity, R.string.toast_invalid_readonlyurl); + changeBackgroundColor(editValue2, ERROR_COLOR); + } else if (!validNotesAPIURL) { + ScreenMessage.barError(activity, R.string.toast_invalid_notesurl); + changeBackgroundColor(editValue3, ERROR_COLOR); + } + }); + dialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener(v -> dialog.dismiss()); + }); + + return dialog; + } } } \ No newline at end of file diff --git a/src/main/java/de/blau/android/prefs/PrefEditorActivity.java b/src/main/java/de/blau/android/prefs/PrefEditorActivity.java index 526fc8a24e..80c0b5066d 100644 --- a/src/main/java/de/blau/android/prefs/PrefEditorActivity.java +++ b/src/main/java/de/blau/android/prefs/PrefEditorActivity.java @@ -76,7 +76,7 @@ protected void onActivityResult(final int requestCode, final int resultCode, fin Log.d(DEBUG_TAG, "onActivityResult"); super.onActivityResult(requestCode, resultCode, data); if ((requestCode == SelectFile.READ_FILE || requestCode == SelectFile.SAVE_FILE) && resultCode == RESULT_OK) { - SelectFile.handleResult(requestCode, data); + SelectFile.handleResult(this, requestCode, data); } } diff --git a/src/main/java/de/blau/android/prefs/PresetEditorActivity.java b/src/main/java/de/blau/android/prefs/PresetEditorActivity.java index 45692f90e6..cb37766992 100644 --- a/src/main/java/de/blau/android/prefs/PresetEditorActivity.java +++ b/src/main/java/de/blau/android/prefs/PresetEditorActivity.java @@ -6,8 +6,8 @@ import java.util.List; import java.util.Map.Entry; -import android.annotation.SuppressLint; import android.app.Activity; +import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -29,6 +29,9 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatDialog; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; import de.blau.android.App; import de.blau.android.HelpViewer; import de.blau.android.R; @@ -40,10 +43,13 @@ import de.blau.android.presets.PresetIconManager; import de.blau.android.presets.PresetParser; import de.blau.android.util.ExecutorTask; +import de.blau.android.util.FragmentUtil; +import de.blau.android.util.ImmersiveDialogFragment; import de.blau.android.util.ReadFile; -import de.blau.android.util.SelectFile; import de.blau.android.util.ScreenMessage; +import de.blau.android.util.SelectFile; import de.blau.android.util.ThemeUtils; +import de.blau.android.util.Util; /** Provides an activity to edit the preset list. Downloads preset data when necessary. */ public class PresetEditorActivity extends URLListEditActivity { @@ -348,96 +354,123 @@ protected boolean canAutoClose() { // download needs to get done * * @param item the selected item */ - @SuppressLint("InflateParams") @Override protected void itemEditDialog(final ListEditItem item) { - final AlertDialog.Builder builder = new AlertDialog.Builder(ctx); - final LayoutInflater inflater = ThemeUtils.getLayoutInflater(ctx); - final View mainView = inflater.inflate(R.layout.listedit_presetedit, null); - final TextView editName = (TextView) mainView.findViewById(R.id.listedit_editName); - final TextView editValue = (TextView) mainView.findViewById(R.id.listedit_editValue); - final TextView versionLabel = (TextView) mainView.findViewById(R.id.listedit_labelVersion); - final TextView version = (TextView) mainView.findViewById(R.id.listedit_version); - final CheckBox useTranslations = (CheckBox) mainView.findViewById(R.id.listedit_translations); - final ImageButton fileButton = (ImageButton) mainView.findViewById(R.id.listedit_file_button); - - if (item != null) { - editName.setText(item.name); - editValue.setText(item.value); - useTranslations.setChecked(item.boolean0); - - } else if (isAddingViaIntent()) { - String tmpName = getIntent().getExtras().getString(EXTRA_NAME); - String tmpValue = getIntent().getExtras().getString(EXTRA_VALUE); - editName.setText(tmpName == null ? "" : tmpName); - editValue.setText(tmpValue == null ? "" : tmpValue); - useTranslations.setChecked(true); - } - if (item != null && item.value3 != null) { - version.setText(item.value3); - } else { - versionLabel.setVisibility(View.GONE); - version.setVisibility(View.GONE); - } - if (item != null && LISTITEM_ID_DEFAULT.equals(item.id)) { - // name and value are not editable - editName.setInputType(InputType.TYPE_NULL); - editName.setBackground(null); - editValue.setEnabled(false); - fileButton.setEnabled(false); - } + Bundle args = new Bundle(); + args.putSerializable(PresetItemEditDialog.ITEM_KEY, item); + FragmentManager fm = getSupportFragmentManager(); + PresetItemEditDialog f = new PresetItemEditDialog(); + f.setArguments(args); + f.setShowsDialog(true); + f.show(fm, PresetItemEditDialog.ITEM_EDIT_DIALOG_TAG); + } - setViewAndButtons(builder, mainView); + public static class PresetItemEditDialog extends ImmersiveDialogFragment { + + private static final String ITEM_EDIT_DIALOG_TAG = "preset_item_edit_dialog"; + static final String ITEM_KEY = "item"; + + @Override + public AppCompatDialog onCreateDialog(Bundle savedInstanceState) { + ListEditItem item = Util.getSerializeable(getArguments(), ITEM_KEY, ListEditItem.class); + final AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + final LayoutInflater inflater = ThemeUtils.getLayoutInflater(getContext()); + final View mainView = inflater.inflate(R.layout.listedit_presetedit, null); + final TextView editName = (TextView) mainView.findViewById(R.id.listedit_editName); + final TextView editValue = (TextView) mainView.findViewById(R.id.listedit_editValue); + final TextView versionLabel = (TextView) mainView.findViewById(R.id.listedit_labelVersion); + final TextView version = (TextView) mainView.findViewById(R.id.listedit_version); + final CheckBox useTranslations = (CheckBox) mainView.findViewById(R.id.listedit_translations); + final ImageButton fileButton = (ImageButton) mainView.findViewById(R.id.listedit_file_button); + + final PresetEditorActivity activity = (PresetEditorActivity) getActivity(); + + if (item != null) { + editName.setText(item.name); + editValue.setText(item.value); + useTranslations.setChecked(item.boolean0); + + } else if (activity.isAddingViaIntent()) { + String tmpName = activity.getIntent().getExtras().getString(EXTRA_NAME); + String tmpValue = activity.getIntent().getExtras().getString(EXTRA_VALUE); + editName.setText(tmpName == null ? "" : tmpName); + editValue.setText(tmpValue == null ? "" : tmpValue); + useTranslations.setChecked(true); + } + if (item != null && item.value3 != null) { + version.setText(item.value3); + } else { + versionLabel.setVisibility(View.GONE); + version.setVisibility(View.GONE); + } + if (item != null && LISTITEM_ID_DEFAULT.equals(item.id)) { + // name and value are not editable + editName.setInputType(InputType.TYPE_NULL); + editName.setBackground(null); + editValue.setEnabled(false); + fileButton.setEnabled(false); + } - final AlertDialog dialog = builder.create(); - dialog.show(); + activity.setViewAndButtons(builder, mainView); - fileButton.setOnClickListener(v -> SelectFile.read(PresetEditorActivity.this, R.string.config_presetsPreferredDir_key, new ReadFile() { - private static final long serialVersionUID = 1L; + final AlertDialog dialog = builder.create(); - @Override - public boolean read(Uri fileUri) { - editValue.setText(fileUri.toString()); - SelectFile.savePref(new Preferences(PresetEditorActivity.this), R.string.config_presetsPreferredDir_key, fileUri); - return true; - } - })); - - // overriding the handlers - dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { - String name = editName.getText().toString().trim(); - String presetURL = editValue.getText().toString().trim(); - boolean useTranslationsEnabled = useTranslations.isChecked(); - changeBackgroundColor(editValue, VALID_COLOR); - // validate entries - boolean validPresetURL = Patterns.WEB_URL.matcher(presetURL).matches(); - URL url = null; - try { - url = new URL(presetURL); - } catch (MalformedURLException e) { - validPresetURL = false; - } + fileButton.setOnClickListener(v -> SelectFile.read(activity, R.string.config_presetsPreferredDir_key, new ReadFile() { + private static final long serialVersionUID = 1L; - // save or display toast, exception for localhost is needed for testing - if (validPresetURL || presetURL.startsWith(Schemes.FILE) || presetURL.startsWith(Schemes.CONTENT) - || (url != null && "localhost".equals(url.getHost())) || (item != null && item.id.equals(LISTITEM_ID_DEFAULT))) { - if (item == null) { - // new item - finishCreateItem(new ListEditItem(name, presetURL, null, null, useTranslationsEnabled)); - } else { - item.name = name; - item.value = presetURL; - item.boolean0 = useTranslationsEnabled; - finishEditItem(item); + @Override + public boolean read(FragmentActivity currentActivity, Uri fileUri) { + final Dialog dialog = FragmentUtil.findDialogByTag(currentActivity, ITEM_EDIT_DIALOG_TAG); + if (dialog == null) { + Log.e(DEBUG_TAG, "Dialog is null"); + return false; + } + final TextView editValue = (TextView) dialog.findViewById(R.id.listedit_editValue); + + editValue.setText(fileUri.toString()); + SelectFile.savePref(new Preferences(currentActivity), R.string.config_presetsPreferredDir_key, fileUri); + return true; } - dialog.dismiss(); - } else { - // if garbage value entered show toasts - ScreenMessage.barError(PresetEditorActivity.this, R.string.toast_invalid_preseturl); - changeBackgroundColor(editValue, ERROR_COLOR); - } - }); + })); + + // overriding the handlers + dialog.setOnShowListener((DialogInterface d) -> { + dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { + String name = editName.getText().toString().trim(); + String presetURL = editValue.getText().toString().trim(); + boolean useTranslationsEnabled = useTranslations.isChecked(); + changeBackgroundColor(editValue, VALID_COLOR); + // validate entries + boolean validPresetURL = Patterns.WEB_URL.matcher(presetURL).matches(); + URL url = null; + try { + url = new URL(presetURL); + } catch (MalformedURLException e) { + validPresetURL = false; + } - dialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener(v -> dialog.dismiss()); + // save or display toast, exception for localhost is needed for testing + if (validPresetURL || presetURL.startsWith(Schemes.FILE) || presetURL.startsWith(Schemes.CONTENT) + || (url != null && "localhost".equals(url.getHost())) || (item != null && item.id.equals(LISTITEM_ID_DEFAULT))) { + if (item == null) { + // new item + activity.finishCreateItem(new ListEditItem(name, presetURL, null, null, useTranslationsEnabled)); + } else { + item.name = name; + item.value = presetURL; + item.boolean0 = useTranslationsEnabled; + activity.finishEditItem(item); + } + dialog.dismiss(); + } else { + // if garbage value entered show toasts + ScreenMessage.barError(activity, R.string.toast_invalid_preseturl); + changeBackgroundColor(editValue, ERROR_COLOR); + } + }); + dialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener(v -> dialog.dismiss()); + }); + return dialog; + } } } diff --git a/src/main/java/de/blau/android/prefs/URLListEditActivity.java b/src/main/java/de/blau/android/prefs/URLListEditActivity.java index bf907f58cf..ae18d28539 100644 --- a/src/main/java/de/blau/android/prefs/URLListEditActivity.java +++ b/src/main/java/de/blau/android/prefs/URLListEditActivity.java @@ -272,10 +272,10 @@ void onAdditionalMenuItemClick(int menuItemId, ListEditItem clickedItem) { * @param textView the TextView * @param colorRes the color resource id */ - void changeBackgroundColor(@Nullable TextView textView, int colorRes) { + static void changeBackgroundColor(@Nullable TextView textView, int colorRes) { if (textView != null && textView.getBackground() != null) { textView.getBackground().mutate().setColorFilter( - BlendModeColorFilterCompat.createBlendModeColorFilterCompat(ContextCompat.getColor(this, colorRes), BlendModeCompat.SRC_ATOP)); + BlendModeColorFilterCompat.createBlendModeColorFilterCompat(ContextCompat.getColor(textView.getContext(), colorRes), BlendModeCompat.SRC_ATOP)); } } @@ -556,7 +556,7 @@ protected void onActivityResult(final int requestCode, final int resultCode, fin Log.d(DEBUG_TAG, "onActivityResult"); super.onActivityResult(requestCode, resultCode, data); if ((requestCode == SelectFile.READ_FILE || requestCode == SelectFile.SAVE_FILE) && resultCode == RESULT_OK) { - SelectFile.handleResult(requestCode, data); + SelectFile.handleResult(this, requestCode, data); } } diff --git a/src/main/java/de/blau/android/propertyeditor/PropertyEditorFragment.java b/src/main/java/de/blau/android/propertyeditor/PropertyEditorFragment.java index d301acea42..3ce3c09fd2 100644 --- a/src/main/java/de/blau/android/propertyeditor/PropertyEditorFragment.java +++ b/src/main/java/de/blau/android/propertyeditor/PropertyEditorFragment.java @@ -473,7 +473,7 @@ public void onActivityResult(final int requestCode, final int resultCode, final Log.d(DEBUG_TAG, "onActivityResult"); super.onActivityResult(requestCode, resultCode, data); if ((requestCode == SelectFile.READ_FILE || requestCode == SelectFile.SAVE_FILE) && resultCode == Activity.RESULT_OK) { - SelectFile.handleResult(requestCode, data); + SelectFile.handleResult(getActivity(), requestCode, data); } else if (requestCode == PREFERENCES_CODE) { // Preferences may have been changed prefs = new Preferences(getContext()); diff --git a/src/main/java/de/blau/android/resources/TileLayerDialog.java b/src/main/java/de/blau/android/resources/TileLayerDialog.java index fc4a18f639..5cb8a12fa6 100644 --- a/src/main/java/de/blau/android/resources/TileLayerDialog.java +++ b/src/main/java/de/blau/android/resources/TileLayerDialog.java @@ -38,7 +38,6 @@ import ch.poole.geo.pmtiles.Constants; import ch.poole.geo.pmtiles.Reader; import de.blau.android.App; -import de.blau.android.Logic; import de.blau.android.Main; import de.blau.android.R; import de.blau.android.contract.FileExtensions; @@ -57,6 +56,7 @@ import de.blau.android.util.DatabaseUtil; import de.blau.android.util.ExecutorTask; import de.blau.android.util.FileUtil; +import de.blau.android.util.FragmentUtil; import de.blau.android.util.GeoMath; import de.blau.android.util.ImmersiveDialogFragment; import de.blau.android.util.OkHttpFileChannel; @@ -174,30 +174,32 @@ private static TileLayerDialog newInstance(final long id, @Nullable LayerEntry l private static final long serialVersionUID = 1L; @Override - public boolean read(final Uri contentUri) { + public boolean read(FragmentActivity currentActivity, final Uri contentUri) { + TileLayerDialog fragment = (TileLayerDialog) FragmentUtil.findFragmentByTag(currentActivity, TAG); + if (fragment == null) { + Log.e(DEBUG_TAG, "Restored fragment is null"); + return false; + } // on Android API 29 and up we need to copy the file if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // copy file - String fileName = ContentResolverUtil.getDisplaynameColumn(activity, contentUri); + String fileName = ContentResolverUtil.getDisplaynameColumn(currentActivity, contentUri); try { final File destination = new File(FileUtil.getPublicDirectory(FileUtil.getPublicDirectory(), Paths.DIRECTORY_PATH_IMPORTS), fileName); - if (destination.exists()) { - ScreenMessage.toastTopError(activity, R.string.toast_import_destination_exists); - return false; - } - copyFile(contentUri, destination); + FileUtil.importFile(currentActivity, contentUri, destination, + () -> configureFromFile(fragment, Uri.parse(FileUtil.FILE_SCHEME_PREFIX + destination.getAbsolutePath()))); } catch (IOException ex) { return false; } return true; } else { // rewrite content: Uris - final Uri fileUri = FileUtil.contentUriToFileUri(activity, contentUri); + final Uri fileUri = FileUtil.contentUriToFileUri(currentActivity, contentUri); if (fileUri == null) { - ScreenMessage.toastTopError(activity, R.string.not_found_title); + ScreenMessage.toastTopError(currentActivity, R.string.not_found_title); return false; } - return configureFromFile(fileUri); + return configureFromFile(fragment, fileUri); } } }); @@ -205,14 +207,15 @@ public boolean read(final Uri contentUri) { /** * Configure a pmtiles source from the data in the file * + * @param fragment the current, potentially recreated instance of this * @param reader a Reader instance * @param json the json metadata from the Reader */ - private void configureFromPMTiles(@NonNull Reader reader, @NonNull String json) { + private void configureFromPMTiles(TileLayerDialog fragment, @NonNull Reader reader, @NonNull String json) { metadataMap.put(SOURCE_TYPE, TileLayerSource.TYPE_PMT_3); metadataMap.put(TILE_TYPE, Constants.TYPE_MVT == reader.getTileType() ? TileType.MVT : null); double[] box = reader.getBounds(); - setBoundingBoxFields(formatDouble(box[0]), formatDouble(box[1]), formatDouble(box[2]), formatDouble(box[3])); + setBoundingBoxFields(fragment, formatDouble(box[0]), formatDouble(box[1]), formatDouble(box[2]), formatDouble(box[3])); minZoomPicker.setValue(reader.getMinZoom()); maxZoomPicker.setValue(reader.getMaxZoom()); try { @@ -240,9 +243,10 @@ private void configureFromPMTiles(@NonNull Reader reader, @NonNull String json) /** * Configure a MapBoxTiles source from the file metadata * + * @param fragment the current, potentially recreated instance of this * @param fileUri the uri for the file */ - private void configureFromMBT(Uri fileUri) { + private void configureFromMBT(@NonNull TileLayerDialog fragment, @NonNull Uri fileUri) { MBTileProviderDataBase db = new MBTileProviderDataBase(getActivity(), fileUri, 1); Map metadata = db.getMetadata(); if (metadata == null || metadata.isEmpty()) { @@ -250,8 +254,8 @@ private void configureFromMBT(Uri fileUri) { } int[] zooms = db.getMinMaxZoom(); if (zooms.length == 2) { - minZoomPicker.setValue(zooms[0]); - maxZoomPicker.setValue(zooms[1]); + fragment.minZoomPicker.setValue(zooms[0]); + fragment.maxZoomPicker.setValue(zooms[1]); } db.close(); @@ -261,18 +265,18 @@ private void configureFromMBT(Uri fileUri) { ScreenMessage.toastTopError(activity, activity.getResources().getString(R.string.toast_unsupported_format, format)); return; } - metadataMap.put(TILE_TYPE, isMVT ? TileType.MVT : null); + fragment.metadataMap.put(TILE_TYPE, isMVT ? TileType.MVT : null); String name = metadata.get(MBTileConstants.NAME); if (name != null) { - nameEdit.setText(name); + fragment.nameEdit.setText(name); } - overlayCheck.setChecked(MBTileConstants.OVERLAY.equals(metadata.get(MBTileConstants.TYPE))); + fragment.overlayCheck.setChecked(MBTileConstants.OVERLAY.equals(metadata.get(MBTileConstants.TYPE))); String bounds = metadata.get(MBTileConstants.BOUNDS); if (bounds != null) { String[] corners = bounds.split(",", 4); if (corners.length == 4) { - setBoundingBoxFields(corners[0], corners[1], corners[2], corners[3]); + setBoundingBoxFields(fragment, corners[0], corners[1], corners[2], corners[3]); } } } @@ -280,22 +284,24 @@ private void configureFromMBT(Uri fileUri) { /** * Configure the entry from the contents of the file * + * @param fragment the current, potentially recreated instance of this * @param fileUri the file Uri for the file + * * @return true if successful */ - private boolean configureFromFile(@NonNull Uri fileUri) { + private boolean configureFromFile(@NonNull TileLayerDialog fragment, @NonNull Uri fileUri) { try { final String path = fileUri.getPath(); if (DatabaseUtil.isValidSQLite(path)) { - configureFromMBT(fileUri); + configureFromMBT(fragment, fileUri); } else { try (Reader reader = new Reader(new File(path))) { - configureFromPMTiles(reader, reader.getMetadata()); + configureFromPMTiles(fragment, reader, reader.getMetadata()); } } // this should really be in the metadata - tileSizePicker.setValue(TileLayerSource.DEFAULT_TILE_SIZE); - urlEdit.setText(fileUri.toString()); + fragment.tileSizePicker.setValue(TileLayerSource.DEFAULT_TILE_SIZE); + fragment.urlEdit.setText(fileUri.toString()); SelectFile.savePref(App.getLogic().getPrefs(), R.string.config_mbtilesPreferredDir_key, fileUri); return true; } catch (JsonSyntaxException e) { @@ -307,44 +313,6 @@ private boolean configureFromFile(@NonNull Uri fileUri) { return false; } - /** - * Copy a file to an internal location suitable for random access on recent Android version - * - * As a side effect this will read configuration from the file afte rthe copying is finished - * - * @param contentUri the source Uri - * @param destination the destination - */ - private void copyFile(@NonNull final Uri contentUri, @NonNull final File destination) { - Logic logic = App.getLogic(); - new ExecutorTask(logic.getExecutorService(), logic.getHandler()) { - - @Override - protected void onPreExecute() { - Progress.showDialog(activity, Progress.PROGRESS_IMPORTING_FILE); - } - - @Override - protected Boolean doInBackground(Void param) { - try { - FileUtil.copy(activity.getContentResolver().openInputStream(contentUri), destination); - return true; - } catch (IOException ioex) { - Log.e(DEBUG_TAG, "Unable to copy file " + contentUri + " " + ioex.getMessage()); - } - return false; - } - - @Override - protected void onPostExecute(Boolean result) { - Progress.dismissDialog(activity, Progress.PROGRESS_IMPORTING_FILE); - if (result != null && result && !isCancelled()) { - configureFromFile(Uri.parse(FileUtil.FILE_SCHEME_PREFIX + destination.getAbsolutePath())); - } - } - }.execute(); - } - private class SaveListener implements View.OnClickListener { String layerId = null; boolean isOverlay = false; @@ -487,7 +455,7 @@ protected Void doInBackground(Void input) throws Exception { @Override protected void onPostExecute(Void result) { Progress.dismissDialog(activity, Progress.PROGRESS_DOWNLOAD); - configureFromPMTiles(reader, json); + configureFromPMTiles(TileLayerDialog.this, reader, json); } @Override @@ -588,7 +556,7 @@ public AppCompatDialog onCreateDialog(Bundle savedInstanceState) { BoundingBox box = coverages.get(0).getBoundingBox(); Log.d(DEBUG_TAG, "Coverage box " + box); if (box != null) { - setBoundingBoxFields(box); + setBoundingBoxFields(this, box); } } tileSizePicker.setValue(layer.getTileWidth()); @@ -608,7 +576,7 @@ public AppCompatDialog onCreateDialog(Bundle savedInstanceState) { minZoomPicker.setValue(TileLayerSource.DEFAULT_MIN_ZOOM); int maxZoom = TileLayerSource.DEFAULT_MAX_ZOOM; if (layerEntry.box != null) { - setBoundingBoxFields(layerEntry.box); + setBoundingBoxFields(this, layerEntry.box); try { double centerLat = (layerEntry.box.getBottom() + layerEntry.box.getHeight() / 2D) / 1E7D; maxZoom = GeoMath.resolutionToZoom(layerEntry.gsd, centerLat); @@ -693,25 +661,28 @@ private String formatDouble(double d) { /** * Set the TextViews for a BoundingBox * + * @param fragment the current, potentially recreated instance of this * @param box the BoundingBox */ - private void setBoundingBoxFields(@NonNull BoundingBox box) { - setBoundingBoxFields(formatDouble(box.getLeft() / 1E7D), formatDouble(box.getBottom() / 1E7D), formatDouble(box.getRight() / 1E7D), + private void setBoundingBoxFields(@NonNull TileLayerDialog fragment, @NonNull BoundingBox box) { + setBoundingBoxFields(fragment, formatDouble(box.getLeft() / 1E7D), formatDouble(box.getBottom() / 1E7D), formatDouble(box.getRight() / 1E7D), formatDouble(box.getTop() / 1E7D)); } /** * Set the TextViews for a BoundingBox * + * @param fragment the current, potentially recreated instance of this * @param left coordinate of left side * @param bottom coordinate of bottom * @param right coordinate of right * @param top coordinate of top */ - private void setBoundingBoxFields(@NonNull String left, @NonNull String bottom, @NonNull String right, @NonNull String top) { - leftEdit.setText(left); - bottomEdit.setText(bottom); - rightEdit.setText(right); - topEdit.setText(top); + private void setBoundingBoxFields(@NonNull TileLayerDialog fragment, @NonNull String left, @NonNull String bottom, @NonNull String right, + @NonNull String top) { + fragment.leftEdit.setText(left); + fragment.bottomEdit.setText(bottom); + fragment.rightEdit.setText(right); + fragment.topEdit.setText(top); } } \ No newline at end of file diff --git a/src/main/java/de/blau/android/util/DatabaseUtil.java b/src/main/java/de/blau/android/util/DatabaseUtil.java index b5691df7b9..136b37347f 100644 --- a/src/main/java/de/blau/android/util/DatabaseUtil.java +++ b/src/main/java/de/blau/android/util/DatabaseUtil.java @@ -27,8 +27,7 @@ public static boolean isValidSQLite(String dbPath) { if (!file.exists() || !file.canRead()) { return false; } - try (FileReader fr = new FileReader(file)){ - + try (FileReader fr = new FileReader(file)){ char[] buffer = new char[16]; if (fr.read(buffer, 0, 16) == 16) { String str = String.valueOf(buffer); diff --git a/src/main/java/de/blau/android/util/FileUtil.java b/src/main/java/de/blau/android/util/FileUtil.java index c455baad57..23b23efa08 100644 --- a/src/main/java/de/blau/android/util/FileUtil.java +++ b/src/main/java/de/blau/android/util/FileUtil.java @@ -21,8 +21,15 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentActivity; +import de.blau.android.App; +import de.blau.android.Logic; +import de.blau.android.PostAsyncActionHandler; +import de.blau.android.R; import de.blau.android.contract.Paths; import de.blau.android.contract.Schemes; +import de.blau.android.dialogs.Progress; +import de.blau.android.dialogs.Tip; public final class FileUtil { private static final String DEBUG_TAG = FileUtil.class.getSimpleName(); @@ -409,4 +416,51 @@ public static boolean hasPrivateFile(@NonNull Context context, @NonNull String f } return false; } + + /** + * Copy a file to an internal location suitable for random access on recent Android versions + * + * @param activity the calling Activity + * @param contentUri the source Uri + * @param destination the destination + * @param postImport this is run after successful copying + */ + public static void importFile(@NonNull final FragmentActivity activity, @NonNull final Uri contentUri, @NonNull final File destination, + @Nullable PostAsyncActionHandler postImport) { + Logic logic = App.getLogic(); + if (destination.exists()) { + ScreenMessage.toastTopWarning(activity, R.string.toast_import_destination_exists); + } + new ExecutorTask(logic.getExecutorService(), logic.getHandler()) { + + @Override + protected void onPreExecute() { + Progress.showDialog(activity, Progress.PROGRESS_IMPORTING_FILE); + Tip.showDialog(activity, R.string.tip_file_import_key, R.string.tip_file_import); + } + + @Override + protected Boolean doInBackground(Void param) { + try { + FileUtil.copy(activity.getContentResolver().openInputStream(contentUri), destination); + return true; + } catch (IOException ioex) { + Log.e(DEBUG_TAG, "Unable to copy file " + contentUri + " " + ioex.getMessage()); + } + return false; + } + + @Override + protected void onPostExecute(Boolean result) { + Progress.dismissDialog(activity, Progress.PROGRESS_IMPORTING_FILE); + if (postImport != null && !isCancelled()) { + if (result != null && result) { + postImport.onSuccess(); + } else { + postImport.onError(null); + } + } + } + }.execute(); + } } \ No newline at end of file diff --git a/src/main/java/de/blau/android/util/FragmentUtil.java b/src/main/java/de/blau/android/util/FragmentUtil.java new file mode 100644 index 0000000000..aaaa701524 --- /dev/null +++ b/src/main/java/de/blau/android/util/FragmentUtil.java @@ -0,0 +1,63 @@ +package de.blau.android.util; + +import java.util.ArrayList; +import java.util.List; + +import android.app.Dialog; +import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; + +public final class FragmentUtil { + private static final String DEBUG_TAG = FragmentUtil.class.getSimpleName(); + + /** + * Private constructor + */ + private FragmentUtil() { + // nothing + } + + /** + * Find a Fragment by tag, descending in to one layer of children + * + * @param activity current Activity + * @param tag the tag + * @return the fragment or null if not found + */ + @Nullable + public static Fragment findFragmentByTag(@NonNull FragmentActivity activity, @NonNull String tag) { + FragmentManager fm = activity.getSupportFragmentManager(); + List parents = new ArrayList<>(fm.getFragments()); + for (Fragment parent : parents) { + Fragment fragment = parent.getChildFragmentManager().findFragmentByTag(tag); + if (fragment != null) { + return fragment; + } + } + Log.e(DEBUG_TAG, "Fragment not found"); + return null; + } + + /** + * Get the Dialog associated with a DialogFragment by tag + * + * @param activity current Activity + * @param tag the tag + * @return the Dialog or null if not found + */ + @Nullable + public static Dialog findDialogByTag(@NonNull FragmentActivity activity, @NonNull String tag) { + FragmentManager fm = activity.getSupportFragmentManager(); + Fragment fragment = fm.findFragmentByTag(tag); + if (fragment instanceof DialogFragment) { + return ((DialogFragment) fragment).getDialog(); + } + Log.e(DEBUG_TAG, "Fragment not found or not a DialogFragment"); + return null; + } +} diff --git a/src/main/java/de/blau/android/util/ReadFile.java b/src/main/java/de/blau/android/util/ReadFile.java index 4b715d73e2..00ea93ff2b 100644 --- a/src/main/java/de/blau/android/util/ReadFile.java +++ b/src/main/java/de/blau/android/util/ReadFile.java @@ -5,6 +5,7 @@ import android.net.Uri; import androidx.annotation.NonNull; +import androidx.fragment.app.FragmentActivity; public abstract class ReadFile implements Serializable { /** @@ -15,17 +16,19 @@ public abstract class ReadFile implements Serializable { /** * Read a file * + * @param activity the current activity * @param fileUri Uri of the file to read - * @return true if sucessful + * @return true if successful */ - public abstract boolean read(@NonNull Uri fileUri); + public abstract boolean read(@NonNull FragmentActivity currentActivity, @NonNull Uri fileUri); /** * Read multiple files, empty default implementation * + * @param activity the current activity * @param uris List of Uri to read */ - public void read(@NonNull List uris) { + public void read(@NonNull FragmentActivity currentActivity, @NonNull List uris) { // empty } } diff --git a/src/main/java/de/blau/android/util/SaveFile.java b/src/main/java/de/blau/android/util/SaveFile.java index afe6e50d3e..38720bb0b8 100644 --- a/src/main/java/de/blau/android/util/SaveFile.java +++ b/src/main/java/de/blau/android/util/SaveFile.java @@ -3,6 +3,8 @@ import java.io.Serializable; import android.net.Uri; +import androidx.annotation.NonNull; +import androidx.fragment.app.FragmentActivity; public abstract class SaveFile implements Serializable { /** @@ -13,8 +15,9 @@ public abstract class SaveFile implements Serializable { /** * Save a file * + * @param currentActivity current Activity * @param fileUri Uri file to save * @return true if successful */ - public abstract boolean save(Uri fileUri); + public abstract boolean save(@NonNull FragmentActivity currentActivity, @NonNull Uri fileUri); } diff --git a/src/main/java/de/blau/android/util/SelectFile.java b/src/main/java/de/blau/android/util/SelectFile.java index 5eeaeda63f..be39c97324 100644 --- a/src/main/java/de/blau/android/util/SelectFile.java +++ b/src/main/java/de/blau/android/util/SelectFile.java @@ -56,9 +56,8 @@ public final class SelectFile { private static SaveFile saveCallback; private static final Object saveCallbackLock = new Object(); - private static ReadFile readCallback; - private static final Object readCallbackLock = new Object(); - private static FragmentActivity activity = null; + private static ReadFile readCallback; + private static final Object readCallbackLock = new Object(); /** * Unused default constructor @@ -77,7 +76,6 @@ private SelectFile() { public static void save(@NonNull FragmentActivity activity, int directoryPrefKey, @NonNull de.blau.android.util.SaveFile callback) { synchronized (saveCallbackLock) { saveCallback = callback; - SelectFile.activity = activity; } String path = App.getPreferences(activity).getString(directoryPrefKey); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { @@ -107,7 +105,6 @@ public static void read(@NonNull FragmentActivity activity, int directoryPrefKey public static void read(@NonNull FragmentActivity activity, int directoryPrefKey, @NonNull ReadFile readFile, boolean allowMultiple) { synchronized (readCallbackLock) { readCallback = readFile; - SelectFile.activity = activity; } String path = App.getPreferences(activity).getString(directoryPrefKey); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { @@ -240,10 +237,11 @@ public View getView(int position, View convertView, @NonNull ViewGroup parent) { /** * Handle the file selector result * + * @param activity the current Activity * @param code returned request code * @param data the returned intent */ - public static void handleResult(int code, @NonNull Intent data) { + public static void handleResult(@NonNull FragmentActivity activity, int code, @NonNull Intent data) { Uri uri; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { uri = data.getData(); @@ -253,9 +251,9 @@ public static void handleResult(int code, @NonNull Intent data) { ContentResolverUtil.persistPermissions(activity, data.getFlags(), uri); try { if (code == SAVE_FILE) { - callSaveCallback(uri); + callSaveCallback(activity, uri); } else if (code == READ_FILE) { - callReadCallback(data, uri); + callReadCallback(activity, data, uri); } } catch (NetworkOnMainThreadException nex) { Log.e(DEBUG_TAG, "Got exception for " + " uri " + nex.getMessage()); @@ -266,9 +264,10 @@ public static void handleResult(int code, @NonNull Intent data) { /** * Call the callback for saving to a file * + * @param activity the current Activity * @param uri the file Uri */ - private static void callSaveCallback(@Nullable Uri uri) { + private static void callSaveCallback(@NonNull FragmentActivity activity, @Nullable Uri uri) { if (uri == null) { Log.e(DEBUG_TAG, "callSaveCallback called with null uri"); return; @@ -278,7 +277,7 @@ private static void callSaveCallback(@Nullable Uri uri) { ScreenMessage.barWarning(activity, activity.getResources().getString(R.string.toast_file_exists, file.getName()), R.string.overwrite, v -> { synchronized (saveCallbackLock) { if (saveCallback != null) { - saveCallback.save(uri); + saveCallback.save(activity, uri); } } }); @@ -287,7 +286,7 @@ private static void callSaveCallback(@Nullable Uri uri) { synchronized (saveCallbackLock) { if (saveCallback != null) { Log.d(DEBUG_TAG, "saving to " + uri); - saveCallback.save(uri); + saveCallback.save(activity, uri); } } } @@ -295,10 +294,11 @@ private static void callSaveCallback(@Nullable Uri uri) { /** * Call the callback for reading an or multiple files * + * @param the current Activity * @param data the Intent * @param uri the file Uri */ - private static void callReadCallback(@NonNull Intent data, @Nullable Uri uri) { + private static void callReadCallback(@NonNull FragmentActivity activity, @NonNull Intent data, @Nullable Uri uri) { synchronized (readCallbackLock) { if (readCallback != null) { Log.d(DEBUG_TAG, "reading " + uri); @@ -312,10 +312,10 @@ private static void callReadCallback(@NonNull Intent data, @Nullable Uri uri) { uris.add(u); } } - readCallback.read(uris); + readCallback.read(activity, uris); return; } - readCallback.read(uri); + readCallback.read(activity, uri); } } } diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 0b5f892a29..41f7a6b13b 100755 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -514,6 +514,7 @@ Touch a highlighted node to determine the split position. A long touch will allow you to select which part is used for the new way in a further step. A closed way has to be split in two places. Touch a highlighted node to determine the 1st split position, the next step will ask you to select the 2nd position. When multiple OSM elements or other objects are very close together or elements are members of relations the disambiguation menu will be displayed. You can then select the specific element from the list.<p>If an element is currently already selected it will be highlighted. + On devices running Android 10 or later we need to copy files to ba able to access them fully. The copies are located in <i>Downloads/Vespucci/imports</i>. If necessary you can delete the originals and just retain the copies. + - diff --git a/src/main/res/values/tipkeys.xml b/src/main/res/values/tipkeys.xml index 33b09f178f..a233a67bb8 100644 --- a/src/main/res/values/tipkeys.xml +++ b/src/main/res/values/tipkeys.xml @@ -21,4 +21,5 @@ waySplitting closedWaySplitting disambiguationMenu + fileImport \ No newline at end of file From 98a1a8fc37a63f9bf27cc92f5ab8e19b555efba1 Mon Sep 17 00:00:00 2001 From: simonpoole Date: Wed, 13 Dec 2023 16:39:44 +0100 Subject: [PATCH 2/2] Click away Tip if necessary --- .../java/de/blau/android/TestUtils.java | 17 +++++++++++++++-- .../layer/LayerDialogCustomImageryTest.java | 8 ++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/androidTest/java/de/blau/android/TestUtils.java b/src/androidTest/java/de/blau/android/TestUtils.java index c351aa442a..b943b3186b 100644 --- a/src/androidTest/java/de/blau/android/TestUtils.java +++ b/src/androidTest/java/de/blau/android/TestUtils.java @@ -8,6 +8,8 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.nio.file.Files; import java.util.ArrayList; import java.util.List; @@ -1577,13 +1579,24 @@ public static void disableTip(@NonNull Context ctx, int res) { } /** - * Click away a tip dialod + * Click away a tip dialog * * @param device the current UiDevice * @param ctx an Android Context */ public static void clickAwayTip(@NonNull UiDevice device, @NonNull Context ctx) { - if (TestUtils.findText(device, false, ctx.getString(R.string.tip_title))) { + clickAwayTip(device, ctx, 500); + } + + /** + * Click away a tip dialog + * + * @param device the current UiDevice + * @param ctx an Android Context + * @param wait time mS to wait for the tip + */ + public static void clickAwayTip(@NonNull UiDevice device, @NonNull Context ctx, long wait) { + if (TestUtils.findText(device, false, ctx.getString(R.string.tip_title), wait)) { TestUtils.clickText(device, false, ctx.getString(R.string.okay), true, false); // TIP } } diff --git a/src/androidTest/java/de/blau/android/layer/LayerDialogCustomImageryTest.java b/src/androidTest/java/de/blau/android/layer/LayerDialogCustomImageryTest.java index 12ea26ec20..217f08caae 100644 --- a/src/androidTest/java/de/blau/android/layer/LayerDialogCustomImageryTest.java +++ b/src/androidTest/java/de/blau/android/layer/LayerDialogCustomImageryTest.java @@ -22,6 +22,7 @@ import com.orhanobut.mockwebserverplus.MockWebServerPlus; import android.app.Instrumentation; +import android.os.Build; import android.util.Log; import androidx.annotation.NonNull; import androidx.test.core.app.ApplicationProvider; @@ -64,8 +65,8 @@ @LargeTest public class LayerDialogCustomImageryTest { - public static final int EXTENT_BUTTON = 1; - public static final int MENU_BUTTON = 3; + public static final int EXTENT_BUTTON = 1; + public static final int MENU_BUTTON = 3; AdvancedPrefDatabase prefDB = null; Main main = null; @@ -158,7 +159,9 @@ public void customImageryMBT() { assertTrue(TestUtils.findText(device, false, main.getString(R.string.add_layer_title))); assertTrue(TestUtils.clickResource(device, true, device.getCurrentPackageName() + ":id/file_button", true)); TestUtils.selectFile(device, main, null, fileName, true); + TestUtils.clickAwayTip(device, main, 5000); // only used when the file is imported assertTrue(TestUtils.findText(device, false, "Vespucci Test")); + TestUtils.findText(device, false, main.getString(R.string.save_and_set), 2000); assertTrue(TestUtils.clickText(device, false, main.getString(R.string.save_and_set), true)); assertTrue(TestUtils.textGone(device, main.getString(R.string.layer_add_custom_imagery), 2000)); assertTrue(TestUtils.findText(device, false, "Vespucci Test")); // layer dialog @@ -188,6 +191,7 @@ public void customImageryLocalPMTiles() { assertTrue(TestUtils.findText(device, false, main.getString(R.string.add_layer_title))); assertTrue(TestUtils.clickResource(device, true, device.getCurrentPackageName() + ":id/file_button", true)); TestUtils.selectFile(device, main, null, fileName, true); + TestUtils.clickAwayTip(device, main, 5000); // only used when the file is imported assertTrue(TestUtils.findText(device, false, "protomaps 2023-01")); assertTrue(TestUtils.clickText(device, false, main.getString(R.string.save_and_set), true)); assertTrue(TestUtils.textGone(device, main.getString(R.string.layer_add_custom_imagery), 2000));